MediaPlayer 基础简介

简单介绍 MediaPlayer 的基本概念,状态,常用的方法与监听器。

什么是 MediaPlayer

MediaPlayer 类可以用来播放音视频文件,或者是音频流。开发者可以用它来播放本地音频,或者是网络在线音频。

MediaPlayer 属于 android.media 包。

MediaPlayer 的状态

播放控制由状态机控制。在日常生活中,我们常见的音频状态有播放中,暂停,停止,缓冲等等。 MediaPlayer 的状态有如下几种: - Idle - End - Error - Initialized - Preparing - Prepared - Started - Stopped - Paused - PlaybackCompleted

状态的切换参考官方图例。 这里稍微解释一下状态转换图片。椭圆代表 MediaPlayer 可能停留的状态。椭圆之间的箭头表示方法调用,状态切换的方向。单箭头表示方法同步调用,双箭头表示异步调用。

从图中我们可以看出状态切换的路径和涉及到的方法。

Idle 与 End 状态

当 new 一个 MediaPlayer 或者调用了reset 方法,当前 MediaPlayer 会处于 Idle 状态。调用release 后,会处于 End 状态。在这 2 个状态之间的状态可以看做是 MediaPlayer 对象的生命周期。

在新创建 MediaPlayer 和调用 reset 的 MediaPlayer 之间有一些细微的差别。 这两种情况都处于 Idle 状态,调用 getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setAudioAttributes(android.media.AudioAttributes), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare()prepareAsync() 方法都会抛出错误,如果是新实例化的 MediaPlayer,不会回调 OnErrorListener.onError();但如果是 reset 后的 MediaPlayer,会回调 OnErrorListener.onError() 并且转换到 Error 状态。

如果 MediaPlayer 对象不再使用了,立即调用 release() 方法,释放内部播放器占用的资源。这些资源可能是唯一的,比如硬件加速组件。如果调用 release 失败,可能会引起一连串的 MediaPlayer 实例失效。当 MediaPlayer 处于 End 状态,它就不能再转移到其它状态了。

new 一个 MediaPlayer,处于 Idle 状态。如果用 create 方法创建实例,当创建完成时处于Prepared 状态。

发生错误

一些情形可能会让 MediaPlayer 操作失败,比如不支持的音视频格式,分辨率过高,网络超时等等。 因此在这些情形下错误处理和恢复非常重要。有时候编程错误也会导致 MediaPlayer 操作错误。 开发者可以设置错误监听器setOnErrorListener(android.media.MediaPlayer.OnErrorListener)。当错误发生时,会调用用户实现的 OnErrorListener.onError() 方法。

不管有没有设置监听器,错误发生时 MediaPlayer 会进入 Error 状态。

为了重复使用同一个 MediaPlayer 对象,可以使用 reset() 方法把它从 Error 状态恢复到Idle状态。 设置错误监听器 OnErrorListener 是一个好的编程习惯。开发者可以监听到播放引擎的错误通知。 有时候会抛出IllegalStateException 异常,比如在错误的状态调用了 prepare(), prepareAsync()方法,或是 setDataSource 方法。

设置音源 setDataSource

调用 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 的状态从 Idle 转到Initialized 状态。 如果在 Idle 状态之外的状态调用了 setDataSource(),会抛出IllegalStateException 异常。 开发者应该留意 setDataSource 方法抛出的IllegalArgumentException 和 IOException 异常。

播放音频前必须在 Prepared 状态

MediaPlayer 在开始播放音频前必须处于 Prepared 状态。

MediaPlayer 有同步和异步 2 种方式来进入 Prepared 状态。如果是异步的方式,会先转到Preparing 状态,再转到 Prepared 状态。 当准备完成时,内部的播放引擎会回调用户之前设置的 OnPreparedListener的onPrepared() 方法。

开发者必须注意的是,Preparing 状态是一个过渡状态(transient state)。

处于 Prepared 状态时,可以通过相对应的方法设置音量,屏幕常亮,播放循环等。

开始播放

播放音频必须调用 start() 方法。调用 start() 返回成功后,MediaPlayer 处于 Started 状态。 可以通过 isPlaying() 来判断当前是否在 Started 状态。

如果开发者设置了 OnBufferingUpdateListener,Android 内部播放器会向外传递 buffer 信息。

如果当前处于 Started 状态,再调用 start() 方法没有效果。

暂停播放与继续播放

音频可以被暂停播放和继续播放,也可以调整播放的位置。通过 pause() 方法来暂停音频播放。 成功调用 pause() 方法后,MediaPlayer 进入 Paused 状态。 应当注意的是,MediaPlayer 在 Started 状态与 Paused 状态之间切换是异步的。播放音频流的时候,这个转换过程可能会需要几秒钟。

MediaPlayer 暂停时,start() 方法可以从暂停的位置继续播放。成功调用 start 方法后会进入 Started 状态。

处于 Paused 状态时,调用 pause() 方法没有效果。

停止

调用 stop() 方法让 MediaPlayer从Started, Paused, Prepared 或 PlaybackCompleted 状态进入 Stopped 状态。

在 Stopped 状态时,必须先调用 prepare()prepareAsync() 进入 Prepared 状态后,才能播放音频。

处于 Stopped 状态时,调用 stop() 方法没有效果。

调整播放位置

调用 seekTo(long, int) 来调整播放位置。

seekTo(long, int) 是一个异步方法,虽然它能立刻返回,但实际的位置调整可能会消耗一段时间,特别是在播放音频流的时候。当实际播放位置调整后,内部播放器会回调开发者设置的 OnSeekComplete.onSeekComplete()

在 Prepared, Paused 和 PlaybackCompleted 状态中,都可以调用 seekTo 方法。

可以通过 getCurrentPosition() 方法来获取当前播放位置。开发者可以得知当前播放的进度等等。

Android 8.0

之前使用 seekTo 经常遇到恢复播放时位置不准的问题,而且甚至有重头开始播放的现象。 这个是因为 seekTo 是回到上一时间点附近的关键帧导致的。

针对这个问题,在最新的 Android 8.0 平台上,已经有了新的解决方案: seekTo(long msec, @SeekMode int mode)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    mPlayer.seekTo(progress, MediaPlayer.SEEK_CLOSEST);
} else {
    mPlayer.seekTo(progress);
}

上文的 MediaPlayer.SEEK_CLOSEST,表示找到给定时刻的最接近的一帧(frame)。而不用去找关键帧(key frame)。

播放完毕

音频播放完成后,播放完毕。

如果调用 setLooping(boolean) 为 true,MediaPlayer 会停留在 Started 状态。

如果 setLooping 为 false,内部播放器会调用开发者设置的 OnCompletion.onCompletion(),并且进入 PlaybackCompleted 状态。

处于 PlaybackCompleted 状态时,调用 start() 方法可以从头开始播放音频。

常用监听器

开发者可以设置一些监听器,监听 MediaPlayer 的状态,错误事件等等。开发者应在同一个线程中创建 MediaPlayer 与设置的监听器。

setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener) 监听MediaPlayer 准备完成。一般与 prepareAsync 配合使用。

setOnVideoSizeChangedListener(android.media.MediaPlayer.OnVideoSizeChangedListener) 获知 video 大小或 video 大小改变时的监听。

setOnSeekCompleteListener(android.media.MediaPlayer.OnSeekCompleteListener) 监听调整位置完成。

setOnCompletionListener(android.media.MediaPlayer.OnCompletionListener) 播放完成。

mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
// 当前播放完毕
}
});

setOnBufferingUpdateListener(android.media.MediaPlayer.OnBufferingUpdateListener) 监听缓冲进度。在播放网络音频时常用。

缓冲监听器 OnBufferingUpdateListener

mMediaPlayer.prepareAsync();
mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
// 例如在这里更新UI
}
});

setOnInfoListener(android.media.MediaPlayer.OnInfoListener) 监听普通信息或者警告信息。

setOnErrorListener(android.media.MediaPlayer.OnErrorListener) 监听错误信息。错误发生时,可以在这里处理错误。

mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
LogUtil.e(TAG_PREFIX + " onERR i = " + i + " i1 = " + i1);
return true; // 返回true表示在此处理错误,不会回调onCompletion
}
});

注意 onError 的返回值。可以选择自己处理 error。

* @return True if the method handled the error, false if it didn't.
* Returning false, or not having an OnErrorListener at all, will
* cause the OnCompletionListener to be called.
*/
boolean onError(MediaPlayer mp, int what, int extra);

错误代码 -38。通常表示状态不对。在错误的状态执行了错误的操作。

需要的权限

播放网络音频时需要 Manifest.permission.INTERNET 权限。

Android音视频开发系列教程