使用MediaPlayer
媒体框架的最中重要的组件之一是MediaPlayer类。这个类的对象能够用最小的步骤来获取、解码和播放音视频。它支持以下几种不同的媒体来源:
1. 本地资源;
2. 内部的统一资源标识(URI),如可能从内容解析器中来获取;
3. 外部的URI(流)。
对于Android所支持的的媒体格式列表,请看“Android所支持的媒体格式”文档。
http://developer.android.com/guide/appendix/media-formats.html
以下是一个如何播放本地原生的音频资源的示例,该资源保存在应用的res/raw/目录中。
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
在这个场景中,一个“原生”资源是一个系统不使用任何特殊方式来解析的文件。但是,这种资源的内容不应该是原始的音频,它应该是一个用其所支持的某种格式进行适当编码和格式化的媒体文件。
以下是一个如何使用系统中本地可用的URI来播放的示例:
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
以下是用远程的URL,通过HTTP流来播放的示例:
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
注意:如果URL指向一个在线媒体文件的流,那么该文件必须具有渐次下载的能力。
警告:在使用setDataSource()方法时,必须扑捉或传递IllegalArgumentException和IOException异常。
异步准备
使用MediaPlayer在原理方面是简单的,但是要把它正确的跟一个典型的Android应用集成,就需要记住几件重要的事情。例如,对于prepare()方法的调用可能需要很长的执行时间,因为它可能需要获取和解码媒体数据。在这种情况下,任何方法都可能需要很长的执行时间,所以不应该在应用程序的UI线程中调用它。执行的的过程中可能导致UI的挂起,直到该方法返回,这种用户体验很坏,并且能够导致一个ANR(Application Not Responding)错误。即使你认为资源会被很快的加载,但是要记住任何超过十分之一秒响应,都会在UI界面上形成停顿,从而给用户带来应用程序执行慢的印象。
要避免UI线程的挂起,就要使用另一个线程来准备MediaPlayer,并且在执行完成的时候给主线程发一个通知。你能够编写自己的线程逻辑,但是这种使用MediaPlayer的方式很共同,因此框架通过使用prepareAsync()方法提供了一种便利的方式来完成这个任务。这个方法在后台启动媒体的准备过程,并立即返回。当媒体被准备完成后,通过setOnPreparedListener()方法所配置的MediaPlayer.OnPreparedListener的onPrepared()方法会被调用。
管理状态
要记住的MediaPlayer的另一个特点是:它是基于状态的。也就是说,MediaPlayer有一个内部状态,在编写自己的代码时必须要注意这个状态,因为某个操作可能只在特定的状态中才有效。如果在错误的状态执行了一个操作,系统会抛出一个异常或导致其他的不希望的行为发生。
在MediaPlayer类的文档中显示了一个完整的状态图,它阐明了把MediaPlayer从一个种状态转移到另一种状态的方法。例如,当创建一个新的MediaPlayer对象时,它是处于Idle状态。在这个时点,应该通过调用setDataSource()方法来初始化,接下来是Initialized状态。之后必须使用prepare()或prepareAsync()方法来准备媒体。在MediaPlayer完成准备工作时,它会进入Prepared状态,这意味着能够调用start()方法来播放媒体。这时,就像图中演示的那样,通过调用start()、pause()、和seekTo()方法在Started、Paused和PlaybackCompleted状态之间进行切换。当调用stop()方法时,要注意在再次准备MediaPlayer之前不能够再调用start()方法了。
在编写跟MediaPlayer对象交互的代码时,要始终记住这个状态图,因为在错误的状态下调用它的方法是最常见的Bug。
释放MediaPlayer对象
MediaPlayer能够消化有价值的系统资源。因此,始终需要另外的措施来确保MediaPlayer不会因为长时间的实例化而被挂起。当处理完成时,应该始终调用release()方法来确保其所占用的系统资源被正确的释放。例如,如果你正在使用MediaPlayer,并且Activity收到了一个onStop()调用,你就必须释放MediaPlayer对象,因为Activity已经不再跟用户进行交互了,所以在持有这个MediaPlayer对象已经毫无意义了(除非要在后台播放媒体)。当Activity处于恢复态或重启态的时候,你需要创建一个新的MediaPlayer对象,并且在恢复播放之前要再次准备它。
以下是应该如何释放和取消MediaPlayer对象的示例:
mediaPlayer.release();
mediaPlayer = null;
作为一个例子,也要考虑在Activity终止时忘记释放MediaPlayer对象所可能发生的问题,因为在该Activity每次重启时都要创建一个新的MediaPlayer对象。如你所知,当用户改变屏幕的方向时(或者用另一种方式来改变设备配置),系统都要通过重启Activity(默认)来处理这种改变,因此当用户反复在横竖屏之间切换时,就可能很快消耗所有的系统资源,因为每次方向的改变,都要创建一个新的MediaPlayer对象,而之前的还不曾释放掉。
你可能会想到,如果用户离开Activity,那么在后台播放媒体所发生的事情,内置的Music应用程序就使用了这种行为。在这种场景中,需要一个Service来控制MediaPlayer对象。