基于网络音频的Android播放程序简单示例

举报
ShaderJoy 发表于 2021/12/30 00:43:42 2021/12/30
【摘要】 随着发布MP3文件、播客以及流式音频变得越来越受欢迎,构建可以利用这些服务的音频播放程序的需求也越来越强烈。幸运的是,Android拥有丰富的功能用于处理网络上存在的各种类型的音频。 1.基于HTTP音频播放 这是最简单的的情况,仅仅播放在线的、可通过HTTP对其进行访问的音频文件。比如http://www.mobvcasting....

随着发布MP3文件、播客以及流式音频变得越来越受欢迎,构建可以利用这些服务的音频播放程序的需求也越来越强烈。幸运的是,Android拥有丰富的功能用于处理网络上存在的各种类型的音频。

1.基于HTTP音频播放

这是最简单的的情况,仅仅播放在线的、可通过HTTP对其进行访问的音频文件。比如http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3

但是这里和通常示例化MediaPlayer的方式不同,首先使用的是MediaPlayer的无参构造函数来实例化对象,接着,调用其setDataSource方法,传入想要播放的音频的HTTP位置,随后我们调用prepare方法和start方法。


  
  1. mediaPlayer = new MediaPlayer();
  2. try {
  3. mediaPlayer
  4. .setDataSource("http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3");
  5. mediaPlayer.prepare();
  6. mediaPlayer.start();
  7. } catch (IOException e) {
  8. Log.v("AUDIOHTTPPLAYER", e.getMessage());
  9. }

但是,在应用程序加载到播放音频之间有一个明显的滞后时间。延迟的长度取决于用于构建电话Internet连接的数据网络的速度。如果详细分析的话,可以找到是在调用prepare方法和start方法之间发生了这样的延迟。在运行prepare期间,MediaPlayer将填充一个缓冲区,因为即使网络速度缓慢也能平稳的播放音频。当这么操作时,prepare方法实际上发生了阻塞。这意味着应用程序可能要等到prepare方法完成之后才会响应。幸运的是,有一种方法可以解决这个问题,即prepareAsync方法。该方法会立即返回,并在后台执行缓冲和其他工作,从而允许应用程序继续运行。

完整示例代码如下:


  
  1. public class AudioHTTPPlayer extends Activity implements OnClickListener,
  2. OnErrorListener, OnCompletionListener, OnBufferingUpdateListener,
  3. OnPreparedListener
  4. {
  5. /** Called when the activity is first created. */
  6. MediaPlayer mediaPlayer;
  7. Button stopButton, startButton;
  8. TextView statusTextView, bufferValueTextView;
  9. @Override
  10. public void onCreate(Bundle savedInstanceState)
  11. {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.main);
  14. stopButton = (Button) findViewById(R.id.EndButton);
  15. startButton = (Button) findViewById(R.id.StartButton);
  16. startButton.setOnClickListener(this);
  17. stopButton.setOnClickListener(this);
  18. startButton.setEnabled(false);
  19. stopButton.setEnabled(false);
  20. bufferValueTextView = (TextView) findViewById(R.id.BufferValueTextView);
  21. statusTextView = (TextView) findViewById(R.id.StatusDisplayTextView);
  22. statusTextView.setText("onCreate");
  23. mediaPlayer = new MediaPlayer();
  24. mediaPlayer.setOnCompletionListener(this);
  25. mediaPlayer.setOnErrorListener(this);
  26. mediaPlayer.setOnBufferingUpdateListener(this);
  27. mediaPlayer.setOnPreparedListener(this);
  28. statusTextView.setText("MediaPlayer created");
  29. try
  30. {
  31. mediaPlayer
  32. .setDataSource("http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3");
  33. // mediaPlayer.prepare();
  34. // mediaPlayer.start();
  35. statusTextView.setText("setDataSource done");
  36. statusTextView.setText("calling prepareAsync");
  37. mediaPlayer.prepareAsync();// 开始在后台缓冲音频文件并返回
  38. } catch (IOException e)
  39. {
  40. Log.v("AUDIOHTTPPLAYER", e.getMessage());
  41. }
  42. }
  43. @Override
  44. public void onPrepared(MediaPlayer mp)
  45. {
  46. // TODO Auto-generated method stub
  47. // 当完成prepareAsync方法时,将调用活动的onPrepared方法
  48. statusTextView.setText("onPrepared called");
  49. startButton.setEnabled(true);
  50. }
  51. @Override
  52. public void onBufferingUpdate(MediaPlayer mp, int percent)
  53. {
  54. // TODO Auto-generated method stub
  55. // 当MediaPlayer正在缓冲时,将调用活动的onBufferingUpdate方法
  56. bufferValueTextView.setText(""+percent+"%");
  57. }
  58. @Override
  59. public void onCompletion(MediaPlayer mp)
  60. {
  61. // TODO Auto-generated method stub
  62. statusTextView.setText("onCompletion called");
  63. stopButton.setEnabled(false);
  64. startButton.setEnabled(true);
  65. }
  66. @Override
  67. public boolean onError(MediaPlayer mp, int what, int extra)
  68. {
  69. // TODO Auto-generated method stub
  70. switch (what)
  71. {
  72. case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
  73. statusTextView
  74. .setText("MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK"
  75. + extra);
  76. break;
  77. case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
  78. statusTextView.setText("MEDIA_ERROR_SERVER_DIED" + extra);
  79. break;
  80. case MediaPlayer.MEDIA_ERROR_UNKNOWN:
  81. statusTextView.setText("MEDIA_ERROR_UNKNOWN" + extra);
  82. break;
  83. }
  84. return false;
  85. }
  86. @Override
  87. public void onClick(View v)
  88. {
  89. // TODO Auto-generated method stub
  90. if (v == stopButton)
  91. {
  92. mediaPlayer.pause();
  93. statusTextView.setText("pause called");
  94. startButton.setEnabled(true);
  95. } else if (v == startButton)
  96. {
  97. mediaPlayer.start();
  98. statusTextView.setText("start called");
  99. startButton.setEnabled(false);
  100. stopButton.setEnabled(true);
  101. }
  102. }
  103. }

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文件的内容如下,其指向了一个虚假的在线流


  
  1. #EXTM3U
  2. #EXTINF:0,Live Stream Name
  3. http://www.nostreamhere.org:8000/

M3U文件可以同时包含多个条目,这些条目依次指定一个文件或流

#EXTM3U
#EXTINF:0,Live Stream Name
http://www.nostreamhere.org:8000/
#EXTINF:0,Other Live Stream Name
http://www.nostreamhere.org/


  
  1. public class HTTPAudioPlaylistPlayer extends Activity implements
  2. OnClickListener, OnCompletionListener, OnPreparedListener
  3. {
  4. Vector playlistItems;
  5. Button parseBtn, playBtn, stopBtn;
  6. EditText editTextUrl;
  7. String baseURL = "";
  8. MediaPlayer mediaPlayer;
  9. int currentPlaylistItemNumber = 0;
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState)
  12. {
  13. // TODO Auto-generated method stub
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.main2);
  16. parseBtn = (Button) findViewById(R.id.ParseButton);
  17. playBtn = (Button) findViewById(R.id.PlayButton);
  18. stopBtn = (Button) findViewById(R.id.StopButton);
  19. editTextUrl=(EditText) findViewById(R.id.EditTextURL);
  20. playBtn.setOnClickListener(this);
  21. parseBtn.setOnClickListener(this);
  22. stopBtn.setOnClickListener(this);
  23. playBtn.setEnabled(false);
  24. stopBtn.setEnabled(false);
  25. mediaPlayer = new MediaPlayer();
  26. mediaPlayer.setOnCompletionListener(this);
  27. mediaPlayer.setOnPreparedListener(this);
  28. }
  29. @Override
  30. public void onPrepared(MediaPlayer mp)
  31. {
  32. // TODO Auto-generated method stub
  33. stopBtn.setEnabled(true);
  34. Log.v("HTTPAUDIOPLAYLIST", "Playing");
  35. mediaPlayer.start();
  36. }
  37. @Override
  38. public void onCompletion(MediaPlayer mp)
  39. {
  40. // TODO Auto-generated method stub
  41. Log.v("ONCOMPLETION", "called");
  42. mediaPlayer.stop();
  43. mediaPlayer.reset();
  44. if (playlistItems.size() > currentPlaylistItemNumber + 1)
  45. {
  46. currentPlaylistItemNumber++;
  47. String path = ((PlaylistFile) playlistItems
  48. .get(currentPlaylistItemNumber)).getFilePath();
  49. try
  50. {
  51. mediaPlayer.setDataSource(path);
  52. mediaPlayer.prepareAsync();
  53. } catch (IllegalArgumentException e)
  54. {
  55. e.printStackTrace();
  56. } catch (IllegalStateException e)
  57. {
  58. e.printStackTrace();
  59. } catch (IOException e)
  60. {
  61. e.printStackTrace();
  62. }
  63. }
  64. }
  65. @Override
  66. public void onClick(View v)
  67. {
  68. // TODO Auto-generated method stub
  69. if (v == parseBtn)
  70. {
  71. // 下载由editTextUrl对象中的URL指定的M3U文件,并对它进行分析。
  72. // 分析的操作是选出任何表示待播放文件的行,创建一个PlaylistItem对象,
  73. // 然后把它添加到playlistItems容器里
  74. parsePlaylistFile();
  75. } else if (v == playBtn)
  76. {
  77. playPlaylistItems();
  78. } else if (v == stopBtn)
  79. {
  80. stop();
  81. }
  82. }
  83. private void parsePlaylistFile()
  84. {
  85. // TODO Auto-generated method stub
  86. playlistItems = new Vector();
  87. // 为了从Web获取M3U文件,可以使用Apache软件基金会的HttpClient库,
  88. // 它已被android所包括。
  89. // 首先创建一个HttpClient对象,其代表类似Web浏览器的事物;
  90. HttpClient httpClient = new DefaultHttpClient();
  91. // 然后创建一个HttpGet对象,其表示指向一个文件的具体请求。
  92. HttpGet getRequest = new HttpGet(editTextUrl.getText().toString());
  93. Log.v("URI", getRequest.getURI().toString());
  94. // HttpClient将执行HttpGet,并返回一个HttpResponse
  95. try
  96. {
  97. HttpResponse httpResponse = httpClient.execute(getRequest);
  98. if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
  99. {
  100. Log.v("HTTP ERROR", httpResponse.getStatusLine()
  101. .getReasonPhrase());
  102. } else
  103. {
  104. // 在发出请求之后,可以从HttpRequest中获取一个InputStream,
  105. // 其包含了所请求文件的内容
  106. InputStream inputStream = httpResponse.getEntity().getContent();
  107. // 借助一个BufferedReader可以逐行得遍历该文件
  108. BufferedReader bufferedReader = new BufferedReader(
  109. new InputStreamReader(inputStream));
  110. String line;
  111. while ((line = bufferedReader.readLine()) != null)
  112. {
  113. Log.v("PLAYLISTLINE", "ORIG:" + line);
  114. if (line.startsWith("#"))
  115. {
  116. // 元数据,可以做更多的处理,但现在忽略它
  117. } else if (line.length() > 0)
  118. {
  119. // 如果它的长度大于0,那么就假设它是一个播放列表条目
  120. String filePath = "";
  121. if (line.startsWith("http://"))
  122. {
  123. // 如果行以“http://”开头那么就把它作为流的完整URL
  124. filePath = line;
  125. } else
  126. {
  127. // 否则把它作为一个相对的URL,
  128. // 同时把针对该M3U文件的原始请求的URL附加上去
  129. filePath = getRequest.getURI().resolve(line)
  130. .toString();
  131. }
  132. // 将其添加到播放列表条目的容器中去
  133. PlaylistFile playlistFile = new PlaylistFile(filePath);
  134. playlistItems.add(playlistFile);
  135. }
  136. }
  137. }
  138. } catch (ClientProtocolException e)
  139. {
  140. // TODO Auto-generated catch block
  141. e.printStackTrace();
  142. } catch (IOException e)
  143. {
  144. // TODO Auto-generated catch block
  145. e.printStackTrace();
  146. }
  147. playBtn.setEnabled(true);
  148. }
  149. private void playPlaylistItems()
  150. {
  151. playBtn.setEnabled(false);
  152. currentPlaylistItemNumber = 0;
  153. if (playlistItems.size() > 0)
  154. {
  155. String path = ((PlaylistFile) playlistItems
  156. .get(currentPlaylistItemNumber)).getFilePath();
  157. // 在提取出流的或者文件的路径之后,就可以在MediaPlayer上的setDataSource方法使用它了
  158. try
  159. {
  160. mediaPlayer.setDataSource(path);
  161. mediaPlayer.prepareAsync();
  162. } catch (IllegalArgumentException e)
  163. {
  164. e.printStackTrace();
  165. } catch (IllegalStateException e)
  166. {
  167. e.printStackTrace();
  168. } catch (IOException e)
  169. {
  170. e.printStackTrace();
  171. }
  172. }
  173. }
  174. private void stop()
  175. {
  176. mediaPlayer.pause();
  177. playBtn.setEnabled(true);
  178. stopBtn.setEnabled(false);
  179. }
  180. class PlaylistFile
  181. {
  182. String filePath;
  183. public PlaylistFile(String _filePath)
  184. {
  185. filePath = _filePath;
  186. }
  187. public void setFilePath(String _filePath)
  188. {
  189. filePath = _filePath;
  190. }
  191. public String getFilePath()
  192. {
  193. return filePath;
  194. }
  195. }
  196. }




文章来源: panda1234lee.blog.csdn.net,作者:panda1234lee,版权归原作者所有,如需转载,请联系作者。

原文链接:panda1234lee.blog.csdn.net/article/details/8764093

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。