在线音频常用的传输方式之一就是http,包括服务器推送等许多分支。
http实现在线音频流的主要方法是由 Nullsoft公司开发的,后被美国在线收购,Nullsoft公司是WinAMP的创作者,同时他们设立了一个名叫做SHOUTcast的http音频流在线的服务器,支持ICY协议(http的扩展协议)大部分服务器和播放软件产品都支持该协议。
android的MediaPlayer也支持ICY协议,
浏览器不直接支持ICY流,需要辅助程序来播放。
在使用ICY流时,Internet广播电台会发送一个特殊文件,一般会是一个M3U文件或者是一个PLS文件。
- PLS文件通常是一个多媒体播放列表文件,MIME类型为: audio / x-scpls
- M3U文件是一个存储多媒体播放列表文的件 MIME类型为:audio/x-mpegurl
M3U文件内容:
#EXTM3U //是必须的,指向一个M3U文件
#EXTINF:0,Live Stream Name //#EXTINF:开始,下来是 以秒为单位的持续时间 然后是多媒体名称
http://www.nostreamhere.org:8000/
android的MediaPlayer不能自动分析M3U文件,在android上创建基于http的流式多媒体播放器,必须手动分析
下面示例是一个播放联机广播电台的M3U文件
<uses-permission android:name="android.permission.INTERNET"/> 实例中有个错误就是网络地址的问题,服务器不正确也无法解析出来package com.example.day605; import android.media.MediaPlayer; import android.net.http.HttpResponseCache; import android.os.Handler; import android.os.Message; import android.os.StrictMode; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Vector; /** * 通过http获得mp3文件播放, * 该音频文件存在于服务器的位置 http://www.mobvcasting.com/android/audio/goodmorinigandroid.mp3 */ public class MainActivity extends AppCompatActivity implements MediaPlayer.OnCompletionListener, View.OnClickListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener { private MediaPlayer mediaPlayer; private TextView bufferText, statusText; private Button start, stop, parse; private EditText editTextUrl; private Vector playListItem; //保存播放列表的中的列表条目 // private String url = "http://www.mobvcasting.com/android/audio/goodmorinigandroid.mp3"; private String baseUrl; //只想一个包含M2U文件的URL private int currentPlayListItem = 0;//跟踪目前处于PlayListItem的哪一条 /** * 接收网络请求数据 */ private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //在MainThread线程中进行http请求时,加入可以解决主线程阻塞的异常,一般不建议将网络请求放在主线程中,可以使用在子线程里使用 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads().detectDiskWrites().detectNetwork() .penaltyLog().build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects().detectLeakedClosableObjects() .penaltyLog().penaltyDeath().build()); start = (Button) findViewById(R.id.start); stop = (Button) findViewById(R.id.stop); parse = (Button) findViewById(R.id.parse); bufferText = (TextView) findViewById(R.id.bufferText); statusText = (TextView) findViewById(R.id.statusText); editTextUrl = (EditText) findViewById(R.id.edit); statusText.setText("onCreate"); //editTextUrl.setText("http://live.kboo.fm:8000/high.m3u"); editTextUrl.setText("http://pubint.ic.llnwd.net/stream/pubint_kmfa.m3u"); //这种无参构造的方式和以前的有参调用不同需要几个步骤 mediaPlayer = new MediaPlayer(); mediaPlayer.setOnCompletionListener(this); mediaPlayer.setOnErrorListener(this); mediaPlayer.setOnPreparedListener(this); mediaPlayer.setOnBufferingUpdateListener(this); statusText.setText("MediaPalyer created"); // try { // mediaPlayer.setDataSource(url); // statusText.setText("setDataSource done"); // statusText.setText("calling prepareAsync"); mediaPlayer.prepare();//运行prepare()播放器会填充一个缓冲区,即使网速慢也能平稳的播放 但这里的prepare()会造成阻塞 // mediaPlayer.prepareAsync(); // } catch (IOException e) { // e.printStackTrace(); // } start.setOnClickListener(this); stop.setOnClickListener(this); parse.setOnClickListener(this); start.setEnabled(false); stop.setEnabled(false); } @Override public void onClick(View v) { if (v == start) { playPlayListItem(); //下载M3U文件,并进行解析,选出带播放文件的行然后添加到一个playListItem向量中 // mediaPlayer.start(); // statusText.setText("start called"); // start.setEnabled(false); // stop.setEnabled(true); } else if (v == stop) { stop(); // mediaPlayer.pause(); // statusText.setText("pause called"); // start.setEnabled(true); } else if (v == parse) { parsePlaylistFile(); } } private void stop() { mediaPlayer.pause(); start.setEnabled(true); stop.setEnabled(false); } /** * 下载有editTextUrl提供的m3u文件\ * 下载M3U文件,并进行解析,选出带播放文件的行然后添加到一个playListItem向量中 * 在Android 6.0(API 23) 中,Google已经移除了Apache HttpClient 想关类,推荐使用HttpUrlConnection * 如果要继续使用在 module下的build.gradle文件中加入 * android { * useLibrary 'org.apache.http.legacy' * } */ public void parsePlaylistFile() { playListItem = new Vector(); //创建HttpClient对象,类似web浏览器 HttpClient httpClient = new DefaultHttpClient(); //HttpGet对象表示指向一个具体的文件请求 HttpGet getRequest = new HttpGet(editTextUrl.getText().toString()); HttpResponse httpResponse = null; try { //httpClient执行HttpGet,返回一个HttpResponse httpResponse = httpClient.execute(getRequest); } catch (IOException e) { e.printStackTrace(); } if (httpResponse == null) { Toast toast=Toast.makeText(MainActivity.this,"解析错误",Toast.LENGTH_SHORT); toast.show(); return; } if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { //错误信息 Log.i("ERROR", "" + httpResponse.getStatusLine().getReasonPhrase()); } else { InputStream inputStream = null; try { //httpResponse返回一个InputStream流 inputStream = httpResponse.getEntity().getContent(); } catch (IOException e) { e.printStackTrace(); } //通过缓冲流BufferedReader,将内容读入缓存中,然后一行一行读取 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; try { while ((line = bufferedReader.readLine()) != null) { //如果行以#开头,忽略,这些是源数据 if (line.startsWith("#")) { //源数据做处理,目前忽略 } else if (line.length() > 0) { //如果不是一个空行,即长度大于0,那么假设它是一个播放列表条目 String filpath = ""; //如果行以http://开头,将其作为流的完整URL if (line.startsWith("http://")) { //假设是一个完成的URL filpath = line; } else { //否则将其作为一个相对URL,把针对该M3U文件的原始请求的URL附加上去 filpath = getRequest.getURI().resolve(line).toString(); } //然后将其放在播放列表条目中 PlayListFile playListFile = new PlayListFile(filpath); playListItem.add(playListFile); } } inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } //else //解析完成启动start按钮 start.setEnabled(true); } /** * 接收playListItem'向量中的第一个条目,然后交给MediaPlayer对象做准备 */ private void playPlayListItem() { start.setEnabled(false); currentPlayListItem = 0; if (playListItem.size() > 0) { //获取到向量中的第一个条目 String path = ((PlayListFile) playListItem.get(currentPlayListItem)).getFilePath(); try { //将提取出的文件或流路径设置给MediaPlayer对象 mediaPlayer.setDataSource(path); //准备程序,并允许MediaPlayer做缓冲 ,之后会调用onPrepared()方法 mediaPlayer.prepareAsync(); } catch (Exception e) { e.printStackTrace(); } } } /** * @param mp Mediaplayer播放完成时回调 */ @Override public void onCompletion(MediaPlayer mp) { statusText.setText("onComplete called"); //每播放完成一个条目时释放播放器 mediaPlayer.stop(); mediaPlayer.release(); //查看是否还有条目,如果有向下挪一个,准备播放下一个 if (playListItem.size() > currentPlayListItem + 1) { currentPlayListItem++; String path = ((PlayListFile) playListItem.get(currentPlayListItem)).getFilePath(); try { mediaPlayer.setDataSource(path); mediaPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); } } // start.setEnabled(false); // stop.setEnabled(false); } /** * @param mp 当完成prepareAsync()状态时回调,表明音频准备播放 */ @Override public void onPrepared(MediaPlayer mp) { statusText.setText("prepared called"); start.setEnabled(true); mediaPlayer.start(); } @Override protected void onStop() { super.onStop(); mediaPlayer.pause(); start.setEnabled(true); stop.setEnabled(false); } /** * @param mp * @param what * @param extra * @return 当MediaPalyer发生错误后调用 */ @Override public boolean onError(MediaPlayer mp, int what, int extra) { statusText.setText("onError called"); switch (what) { case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: statusText.setText("MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK" + extra); Log.v("Error:", "MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:" + extra); break; case MediaPlayer.MEDIA_ERROR_SERVER_DIED: statusText.setText("MEDIA_ERROR_SERVER_DIED:" + extra); Log.v("Error", "MEDIA_ERROR_SERVER_DIED:" + extra); break; case MediaPlayer.MEDIA_ERROR_UNKNOWN: statusText.setText("MEDIA_ERROR_UNKNOWN:" + extra); Log.v("Error", "MEDIA_ERROR_UNKNOWN:" + extra); break; } return false; } /** * @param mp * @param percent MediaPlayer正在缓冲时调用 */ @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { bufferText.setText("" + percent + "%"); } class PlayListFile { String filePath; public PlayListFile(String _filePath) { filePath = _filePath; } public void setFilePath(String _filePath) { filePath = _filePath; } public String getFilePath() { return filePath; } } }