1. 程式人生 > >基於網路音訊的Android播放程式簡單示例

基於網路音訊的Android播放程式簡單示例

隨著釋出MP3檔案、播客以及流式音訊變得越來越受歡迎,構建可以利用這些服務的音訊播放程式的需求也越來越強烈。幸運的是,Android擁有豐富的功能用於處理網路上存在的各種型別的音訊。

1.基於HTTP音訊播放

這是最簡單的的情況,僅僅播放線上的、可通過HTTP對其進行訪問的音訊檔案。比如http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3

但是這裡和通常示例化MediaPlayer的方式不同,首先使用的是MediaPlayer的無參建構函式來例項化物件,接著,呼叫其setDataSource方法,傳入想要播放的音訊的HTTP位置,隨後我們呼叫prepare方法和start方法。

mediaPlayer = new MediaPlayer();

try {
  mediaPlayer
  .setDataSource("http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3");
  mediaPlayer.prepare();
  mediaPlayer.start();
} catch (IOException e) {
  Log.v("AUDIOHTTPPLAYER", e.getMessage());
}

但是,在應用程式載入到播放音訊之間有一個明顯的滯後時間。延遲的長度取決於用於構建電話Internet連線的資料網路的速度。如果詳細分析的話,可以找到是在呼叫prepare方法和start方法之間發生了這樣的延遲。在執行prepare期間,MediaPlayer將填充一個緩衝區,因為即使網路速度緩慢也能平穩的播放音訊。當這麼操作時,prepare方法實際上發生了阻塞。這意味著應用程式可能要等到prepare方法完成之後才會響應。幸運的是,有一種方法可以解決這個問題,即prepareAsync方法。該方法會立即返回,並在後臺執行緩衝和其他工作,從而允許應用程式繼續執行。

完整示例程式碼如下:

public class AudioHTTPPlayer extends Activity implements OnClickListener,
		OnErrorListener, OnCompletionListener, OnBufferingUpdateListener,
		OnPreparedListener
{
	/** Called when the activity is first created. */
	MediaPlayer mediaPlayer;
	Button stopButton, startButton;
	TextView statusTextView, bufferValueTextView;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		stopButton = (Button) findViewById(R.id.EndButton);
		startButton = (Button) findViewById(R.id.StartButton);
		startButton.setOnClickListener(this);
		stopButton.setOnClickListener(this);
		startButton.setEnabled(false);
		stopButton.setEnabled(false);

		bufferValueTextView = (TextView) findViewById(R.id.BufferValueTextView);
		statusTextView = (TextView) findViewById(R.id.StatusDisplayTextView);
		statusTextView.setText("onCreate");

		mediaPlayer = new MediaPlayer();
		mediaPlayer.setOnCompletionListener(this);
		mediaPlayer.setOnErrorListener(this);
		mediaPlayer.setOnBufferingUpdateListener(this);
		mediaPlayer.setOnPreparedListener(this);
		statusTextView.setText("MediaPlayer created");
		try
		{
			mediaPlayer
					.setDataSource("http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3");

			// mediaPlayer.prepare();
			// mediaPlayer.start();
			statusTextView.setText("setDataSource done");
			statusTextView.setText("calling prepareAsync");
			mediaPlayer.prepareAsync();// 開始在後臺緩衝音訊檔案並返回
		} catch (IOException e)
		{
			Log.v("AUDIOHTTPPLAYER", e.getMessage());
		}
	}

	@Override
	public void onPrepared(MediaPlayer mp)
	{
		// TODO Auto-generated method stub
//	當完成prepareAsync方法時,將呼叫活動的onPrepared方法
		statusTextView.setText("onPrepared called");
		startButton.setEnabled(true);
	}

	@Override
	public void onBufferingUpdate(MediaPlayer mp, int percent)
	{
		// TODO Auto-generated method stub
//		當MediaPlayer正在緩衝時,將呼叫活動的onBufferingUpdate方法
		bufferValueTextView.setText(""+percent+"%");
	}

	@Override
	public void onCompletion(MediaPlayer mp)
	{
		// TODO Auto-generated method stub
		statusTextView.setText("onCompletion called");
		stopButton.setEnabled(false);
		startButton.setEnabled(true);
	}

	@Override
	public boolean onError(MediaPlayer mp, int what, int extra)
	{
		// TODO Auto-generated method stub
		switch (what)
		{
			case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
				statusTextView
						.setText("MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK"
								+ extra);
				break;
			case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
				statusTextView.setText("MEDIA_ERROR_SERVER_DIED" + extra);
				break;
			case MediaPlayer.MEDIA_ERROR_UNKNOWN:
				statusTextView.setText("MEDIA_ERROR_UNKNOWN" + extra);
				break;
		}
		return false;
	}

	@Override
	public void onClick(View v)
	{
		// TODO Auto-generated method stub
		if (v == stopButton)
		{
			mediaPlayer.pause();
			statusTextView.setText("pause called");
			startButton.setEnabled(true);
		} else if (v == startButton)
		{
			mediaPlayer.start();
			statusTextView.setText("start called");
			startButton.setEnabled(false);
			stopButton.setEnabled(true);
		}
	}
}
如上所示,MediaPlayer有良好的功能集,用來處理HTTP線上獲取的音訊檔案。

2.基於HTTP的流式音訊

線上音訊常用的線上傳輸方法之一是通過HTTP流。有多種流方法屬於HTTP流方法的分支,包括伺服器推送,這在歷史上一直用於在瀏覽器中重新整理網路攝像頭影象顯示;以及一系列其他新方法。而聯機廣播事實上的標準則是ICY協議,其擴充套件了HTTP協議,目前大量的伺服器和播放軟體產品都支援這個協議。

幸運的是,android上的MediaPlayer支援播放ICY流,而無須開發人員費力地實現它。

然後,Internet廣播電臺並不直接公佈它們的音訊流的URL。這麼做是因為瀏覽器通常不支援ICY流,而是需要一個輔助應用程式或外掛來播放流。為了知道要開啟的是一個輔助應用程式,Internet廣播電臺會 傳遞一個特定的MIME型別的中間檔案,其中包含一個指向實際線上流的指標。在使用ICY流的情況下,這通常是一個PLS檔案或一個M3U檔案

PLS檔案:是一種多媒體播放列表檔案,其MIME型別是“audio/x-scpls”

M3U檔案:一個儲存多媒體播放列表的檔案,但是採用一種更基本的格式。它的MIME型別為“audio/x-mpegurl”。

例如M3U檔案的內容如下,其指向了一個虛假的線上流

#EXTM3U
#EXTINF:0,Live Stream Name
http://www.nostreamhere.org:8000/
第一行的#EXTM3U是必須的,其指定下面是一個擴充套件的M3U檔案,其中可以包含額外的資訊。可以在播放列表條目的上一行指定額外資訊,其以#EXTINF:開始,隨後是以秒為單位的持續時間和逗號,然後是媒體的名稱。

M3U檔案可以同時包含多個條目,這些條目依次指定一個檔案或流

#EXTM3U
#EXTINF:0,Live Stream Name
http://www.nostreamhere.org:8000/
#EXTINF:0,Other Live Stream Name
http://www.nostreamhere.org/
遺憾的是,android上的MediaPlayer不能自動分析M3U檔案。因此必須我們自己分析。下面就是一個示例,分析並播放來自聯機廣播電臺的M3U檔案或在URL欄位中輸入的任何M3U檔案。
public class HTTPAudioPlaylistPlayer extends Activity implements
		OnClickListener, OnCompletionListener, OnPreparedListener
{
	Vector playlistItems;
	Button parseBtn, playBtn, stopBtn;
	EditText editTextUrl;
	String baseURL = "";
	MediaPlayer mediaPlayer;

	int currentPlaylistItemNumber = 0;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main2);
		parseBtn = (Button) findViewById(R.id.ParseButton);
		playBtn = (Button) findViewById(R.id.PlayButton);
		stopBtn = (Button) findViewById(R.id.StopButton);
		editTextUrl=(EditText) findViewById(R.id.EditTextURL);

		playBtn.setOnClickListener(this);
		parseBtn.setOnClickListener(this);
		stopBtn.setOnClickListener(this);

		playBtn.setEnabled(false);
		stopBtn.setEnabled(false);

		mediaPlayer = new MediaPlayer();
		mediaPlayer.setOnCompletionListener(this);
		mediaPlayer.setOnPreparedListener(this);
	}

	@Override
	public void onPrepared(MediaPlayer mp)
	{
		// TODO Auto-generated method stub
		stopBtn.setEnabled(true);
		Log.v("HTTPAUDIOPLAYLIST", "Playing");
		mediaPlayer.start();
	}

	@Override
	public void onCompletion(MediaPlayer mp)
	{
		// TODO Auto-generated method stub
		Log.v("ONCOMPLETION", "called");
		mediaPlayer.stop();
		mediaPlayer.reset();

		if (playlistItems.size() > currentPlaylistItemNumber + 1)
		{
			currentPlaylistItemNumber++;
			String path = ((PlaylistFile) playlistItems
					.get(currentPlaylistItemNumber)).getFilePath();

			try
			{
				mediaPlayer.setDataSource(path);
				mediaPlayer.prepareAsync();
			} catch (IllegalArgumentException e)
			{
				e.printStackTrace();
			} catch (IllegalStateException e)
			{
				e.printStackTrace();
			} catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}

	@Override
	public void onClick(View v)
	{
		// TODO Auto-generated method stub
		if (v == parseBtn)
		{
			// 下載由editTextUrl物件中的URL指定的M3U檔案,並對它進行分析。
			// 分析的操作是選出任何表示待播放檔案的行,建立一個PlaylistItem物件,
			// 然後把它新增到playlistItems容器裡
			parsePlaylistFile();
		} else if (v == playBtn)
		{
			playPlaylistItems();
		} else if (v == stopBtn)
		{
			stop();
		}
	}

	private void parsePlaylistFile()
	{
		// TODO Auto-generated method stub
		playlistItems = new Vector();
		// 為了從Web獲取M3U檔案,可以使用Apache軟體基金會的HttpClient庫,
		// 它已被android所包括。
		// 首先建立一個HttpClient物件,其代表類似Web瀏覽器的事物;
		HttpClient httpClient = new DefaultHttpClient();
		// 然後建立一個HttpGet物件,其表示指向一個檔案的具體請求。
		HttpGet getRequest = new HttpGet(editTextUrl.getText().toString());
		Log.v("URI", getRequest.getURI().toString());
		// HttpClient將執行HttpGet,並返回一個HttpResponse
		try
		{
			HttpResponse httpResponse = httpClient.execute(getRequest);
			if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
			{
				Log.v("HTTP ERROR", httpResponse.getStatusLine()
						.getReasonPhrase());
			} else
			{
				// 在發出請求之後,可以從HttpRequest中獲取一個InputStream,
				// 其包含了所請求檔案的內容
				InputStream inputStream = httpResponse.getEntity().getContent();
				// 藉助一個BufferedReader可以逐行得遍歷該檔案
				BufferedReader bufferedReader = new BufferedReader(
						new InputStreamReader(inputStream));
				String line;
				while ((line = bufferedReader.readLine()) != null)
				{
					Log.v("PLAYLISTLINE", "ORIG:" + line);
					if (line.startsWith("#"))
					{
						// 元資料,可以做更多的處理,但現在忽略它
					} else if (line.length() > 0)
					{
						// 如果它的長度大於0,那麼就假設它是一個播放列表條目
						String filePath = "";
						if (line.startsWith("http://"))
						{
							// 如果行以“http://”開頭那麼就把它作為流的完整URL
							filePath = line;
						} else
						{
							// 否則把它作為一個相對的URL,
							// 同時把針對該M3U檔案的原始請求的URL附加上去
							filePath = getRequest.getURI().resolve(line)
									.toString();
						}
						// 將其新增到播放列表條目的容器中去
						PlaylistFile playlistFile = new PlaylistFile(filePath);
						playlistItems.add(playlistFile);
					}
				}
			}
		} catch (ClientProtocolException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		playBtn.setEnabled(true);
	}

	private void playPlaylistItems()
	{
		playBtn.setEnabled(false);
		currentPlaylistItemNumber = 0;
		if (playlistItems.size() > 0)
		{
			String path = ((PlaylistFile) playlistItems
					.get(currentPlaylistItemNumber)).getFilePath();
			// 在提取出流的或者檔案的路徑之後,就可以在MediaPlayer上的setDataSource方法使用它了
			try
			{
				mediaPlayer.setDataSource(path);
				mediaPlayer.prepareAsync();
			} catch (IllegalArgumentException e)
			{
				e.printStackTrace();
			} catch (IllegalStateException e)
			{
				e.printStackTrace();
			} catch (IOException e)
			{
				e.printStackTrace();
			}
		}
	}

	private void stop()
	{
		mediaPlayer.pause();
		playBtn.setEnabled(true);
		stopBtn.setEnabled(false);
	}

	class PlaylistFile
	{
		String filePath;

		public PlaylistFile(String _filePath)
		{
			filePath = _filePath;
		}

		public void setFilePath(String _filePath)
		{
			filePath = _filePath;
		}

		public String getFilePath()
		{
			return filePath;
		}
	}
}