zoukankan      html  css  js  c++  java
  • 基于网络音频的Android播放程序简单示例

    随着发布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 Name
    http://www.nostreamhere.org:8000/
    #EXTINF:0,Other Live Stream Name
    http://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;
    		}
    	}
    }




  • 相关阅读:
    软件需求与建模 复习笔记
    Autoware 笔记No.9,SSD车辆、行人(障碍物)识别(ssd vision detect)
    Autoware 笔记No.8 ENet 障碍物识别(vision segment ENet detect)
    Autoware 1.14 完整安装
    Autoware 笔记No.7, CNN障碍物检测(CNN LiDAR Baidu Object Segmenter)
    iOS 使用局部block处理接口依次调用需求
    阿里一面凉经
    Codeforces round #717 D.Cut(m询问求区间[L,R]能被至少分成多少个区间让每个小区间各数的乘积==各数的LCM)
    2018-2020 国家集训队论文选读
    GDOI 2021 游记
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3003013.html
Copyright © 2011-2022 走看看