随着釋出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 Namehttp://www.nostreamhere.org:8000/#EXTINF:0,Other Live Stream Namehttp://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;
}
}
}