随着发布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. 浅谈Java中Collections.sort对List排序的两种方法
  2. NPM 和webpack 的基础使用
  3. Python list sort方法的具体使用
  4. 【阿里云镜像】使用阿里巴巴DNS镜像源——DNS配置教程
  5. python list.sort()根据多个关键字排序的方法实现
  6. android上一些方法的区别和用法的注意事项
  7. 读取android手机流量信息
  8. android 使用html5作布局文件: webview跟javascript交互
  9. android实现字体闪烁动画的方法

随机推荐

  1. python3生成10个成绩列表,求其平均分
  2. python使用urlopen需要导入什么库
  3. Python -在文本文件中添加日期戳
  4. Django反向url与参数到基于类的视图
  5. Python爬虫-尝试使用人工和OCR处理验证码
  6. leet240. 搜索二维矩阵 II
  7. re表达式中单引号内的双引号(python)[dupli
  8. Python基础 条件判断和循环
  9. Python爬虫之post请求
  10. Python3 基本数据类型