用过MediaPlayer来实现音视频播放功能的同学应该都知道MediaPlayer是基于状态的,也就是说MediaPlayer内部保留着一个状态机,用于管理内部的状态。在编写代码时必须始终注意,某些操作仅在播放器处于特定状态时才有效。如果在错误的状态下执行某项操作,则系统可能会抛出异常或导致其他不良行为。
下图展示了android MediaPlayer 的所有状态以及状态之间的转换,该图说明了从哪些状态可以通过调用哪些方法从而将MediaPlayer变为另一种状态。图中,蓝色的椭圆表示MediaPlayer对象可能驻留的状态。弧线表示驱动MediaPlayer对象状态转换的播放控制操作(也即可以调用的方法)。有两种类型的弧线,具有单箭头的弧表示同步方法调用,而具有双箭头的弧表示异步方法调用。
下面通过文字来对上图的状态变化做一些说明:
当一个MediaPlaye对象刚刚通过 new 方法创建,或者在调用reset()
方法之后,它将处于Idle状态。并且在 release()方法被调用之后,它将处于End状态。在这两种状态之间是MediaPlayer对象的完整生命周期。-
虽然通过new方法新创建的MediaPlayer对象和调用reset()方法之后的MediaPlayer对象都处于Idle状态,但二者之间却存在着细微又非常重要的区别:
- 首先在两种Idle状态下调用getCurrentPosition(), getDuration(), getVideoHeight(),getVideoWidth(), setAudioAttributes(android.media.AudioAttributes),setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare()或prepareAsync()
等方法都将引起程序上的错误。 - 其次当上述方法是在通过new新创建的MediaPlayer对象上调用时,即便用户设置了OnErrorListener.onError()回调,onError()方法也不会被调用,并且MediaPlayer的 状态保持不变。相反,当上述方法是在reset()方法之后调用,那么用户将收到OnErrorListener.onError()回调,并且MediaPlayer的状态将变为Error态。
- 首先在两种Idle状态下调用getCurrentPosition(), getDuration(), getVideoHeight(),getVideoWidth(), setAudioAttributes(android.media.AudioAttributes),setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare()或prepareAsync()
在Idle状态下通过调用setDataSource(java.io.FileDescriptor), setDataSource(java.lang.String), setDataSource(android.content.Context, android.net.Uri), setDataSource(java.io.FileDescriptor, long, long), setDataSource(android.media.MediaDataSource)等方法MediaPlayer将跳转到Initialized状态。
然后,必须使用 prepare() 或 prepareAsync() 方法完成准备工作。当 准备就绪后,便会进入Prepared状态,
进入Prepared状态后可以通过调用 start() 使其播放媒体内容。start()成功返回后 ,MediaPlayer对象处于Started状态。可以通过调用isPlaying()来测试MediaPlayer对象是否处于Started状态。
可以通过调用 start()、pause()和 seekTo() 等方法在Started、Paused和PlaybackCompleted状态之间切换。
调用stop()方法可以让MediaPlayer停止播放,并使处于Started,Paused,Prepared 或PlaybackCompleted状态的MediaPlayer 进入 Stopped状态。不过请注意,当调用 stop()时,除非再次调用prepare()或prepareAsync()将MediaPlayer对象设置为Prepared状态,否则将无法再次调用 start()进行播放。此外调用stop()对已经处于Stopped状态的MediaPlayer对象无效。
当播放到达音视频流的结尾时,播放完成。此时 如果循环模式isLooping 为true 那么MediaPlayer对象将保持在Started状态并将从头开始重新播放。如果isLooping 为false并且预先注册了OnCompletionListener,则播放器引擎将调用用户提供的回调方法OnCompletion.onCompletion()。回调的调用表明该对象现在处于PlaybackCompleted状态。PlaybackCompleted 状态下,再次调用start()可以从音频/视频源的开头重新开始播放。
在编写与 MediaPlayer相关的代码时,请始终牢记该状态图,因为从错误的状态调用其方法是导致错误发生的常见原因。
最后附上MediaPlayer各接口方法的调用时机:
方法名称 | 有效状态 | 无效状态 | 说明 |
---|---|---|---|
attachAuxEffect | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Idle, Error} | 此方法必须在setDataSource之后调用。调用它不会更改对象状态 |
getCurrentPosition | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会更改状态。在无效状态下调用此方法会将对象转移到错误状态 |
getDuration | {Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Idle, Initialized, Error} | 在有效状态下成功调用此方法不会更改状态。在无效状态下调用此方法会将对象转移到错误状态 |
getVideoHeight | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会更改状态。在无效状态下调用此方法会将对象转移到错误状态 |
getVideoWidth | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会更改状态。在无效状态下调用此方法会将对象转移到错误状态 |
isPlaying | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会更改状态。在无效状态下调用此方法会将对象转移到错误状态 |
pause | {Started, Paused, PlaybackCompleted} | {Idle, Initialized, Prepared, Stopped, Error} | 在有效状态下成功调用此方法会将对象转移到“ 暂停”状态。在无效状态下调用此方法会将对象转移到错误状态 |
prepare | {Initialized, Stopped} | {Idle, Prepared, Started, Paused, PlaybackCompleted, Error} | 在有效状态下成功调用此方法会将对象转移到Prepared状态。在无效状态下调用此方法将引发IllegalStateException |
prepareAsync | {Initialized, Stopped} | {Idle, Prepared, Started, Paused, PlaybackCompleted, Error} | 在有效状态下成功调用此方法会将对象转移到“ 准备”状态。在无效状态下调用此方法将引发IllegalStateException |
release | Any | release()之后该对象不再可用 | |
reset | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error} | {} | reset之后,对象就像刚刚创建 |
seekTo | {Prepared, Started, Paused, PlaybackCompleted} | {Idle, Initialized, Stopped, Error} | 在有效状态下成功调用此方法不会更改状态。在无效状态下调用此方法会将对象转移到错误状态 |
setDataSource | {Idle} | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error} | 在有效状态下成功调用此方法会将对象转移到初始化状态。在无效状态下调用此方法将引发IllegalStateException |
setLooping | {Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会更改状态。在无效状态下调用此方法会将对象转移到错误状态 |
setVolume | {Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} | {Error} | 成功调用此方法不会更改状态 |
start | {Prepared, Started, Paused, PlaybackCompleted} | {Idle, Initialized, Stopped, Error} | 在有效状态下成功调用此方法会将对象转移到“ 开始”状态。在无效状态下调用此方法会将对象转移到错误状态 |
stop | {Prepared, Started, Stopped, Paused, PlaybackCompleted} | {Idle, Initialized, Error} | 在有效状态下成功调用此方法会将对象转移到“已停止”状态。在无效状态下调用此方法会将对象转移到错误状态 |