随着发布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;@Overridepublic 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());}}@Overridepublic void onPrepared(MediaPlayer mp){// TODO Auto-generated method stub//当完成prepareAsync方法时,将调用活动的onPrepared方法statusTextView.setText("onPrepared called");startButton.setEnabled(true);}@Overridepublic void onBufferingUpdate(MediaPlayer mp, int percent){// TODO Auto-generated method stub//当MediaPlayer正在缓冲时,将调用活动的onBufferingUpdate方法bufferValueTextView.setText(""+percent+"%");}@Overridepublic void onCompletion(MediaPlayer mp){// TODO Auto-generated method stubstatusTextView.setText("onCompletion called");stopButton.setEnabled(false);startButton.setEnabled(true);}@Overridepublic boolean onError(MediaPlayer mp, int what, int extra){// TODO Auto-generated method stubswitch (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;}@Overridepublic void onClick(View v){// TODO Auto-generated method stubif (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 Namehttp://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 implementsOnClickListener, OnCompletionListener, OnPreparedListener{Vector playlistItems;Button parseBtn, playBtn, stopBtn;EditText editTextUrl;String baseURL = "";MediaPlayer mediaPlayer;int currentPlaylistItemNumber = 0;@Overrideprotected void onCreate(Bundle savedInstanceState){// TODO Auto-generated method stubsuper.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);}@Overridepublic void onPrepared(MediaPlayer mp){// TODO Auto-generated method stubstopBtn.setEnabled(true);Log.v("HTTPAUDIOPLAYLIST", "Playing");mediaPlayer.start();}@Overridepublic void onCompletion(MediaPlayer mp){// TODO Auto-generated method stubLog.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();}}}@Overridepublic void onClick(View v){// TODO Auto-generated method stubif (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 stubplaylistItems = 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,并返回一个HttpResponsetry{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://”开头那么就把它作为流的完整URLfilePath = 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 blocke.printStackTrace();} catch (IOException e){// TODO Auto-generated catch blocke.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;}}}




更多相关文章

  1. Android开机启动Activity或者Service方法
  2. 【Android 异步操作】AsyncTask 异步任务 ( 参数简介 | 方法简介
  3. android语音识别方法示例代码
  4. android studio 导入.so文件
  5. Android DexClassLoader动态加载类文件

随机推荐

  1. SQL货币数字转英文字符语句
  2. 判断一个表的数据不在另一个表中最优秀方
  3. sql 语句中的 NULL值
  4. MSSQL 数据库同步教程
  5. DBCC CHECKIDENT 重置数据库标识列从某一
  6. sqlserver 日志恢复方法(搞定drop和trunc
  7. select into 和 insert into select 两种
  8. 海量数据库查询语句
  9. Excel导入Sqlserver数据库脚本
  10. sql cast,convert,QUOTENAME,exec 函数学习