scope.launch(Dispatchers.Main) {
runCatching {
withContext(Dispatchers.IO) {
mediaPlayer.prepare()
}.onSuccess {
playState = PlayState.PREPARED
// next operation
}.onFailure {
// Uh-oh Error
playState = PlayState.ERROR
prepare() 方法执行完后,MediaPlayer 会调用用户提供的 OnPreparedListener.onPrepared() 回调方法通知用户准备完成。我们可以使用 MediaPlayer.setOnPreparedListener(OnPreparedListener) 方法注册 OnPreparedListener。
2. 异步准备
调用 MediaPlayer.prepareAsync() 方法将使 MediaPlayer 以异步的方式进行准备工作。异步准备时,MediaPlayer 对象首先会将状态转移到 Preparing 状态,同时内部 player 引擎继续处理其余的准备工作,直到准备工作完成,再切换到 Prepared 状态。
Preparing 状态是一个短暂的状态,当 MediaPlayer 对象处于 Preparing 状态时,调用任何方法的效果都是未知的,或者说 MediaPlayer 未定义在 Preparing 状态下调用其他方法的效果。
异步准备方法 prepareAsync() 只能通过回调监听。完成准备时,MediaPlayer 会调用用户提供的 OnPreparedListener.onPrepared() 回调方法通知用户准备完成。我们可以使用 MediaPlayer.setOnPreparedListener(OnPreparedListener) 方法注册 OnPreparedListener。
Started 状态
MediaPlayer 准备完成,状态变为 Prepared 状态后,就可以开始播放了。要开始播放,必须调用 MediaPlayer.start() 方法。start() 方法调用成功后,MediaPlayer 对象会转换成 Started 状态。处于 Started 状态时,MediaPlayer 的内部 player 引擎会调用用户提供的 OnBufferingUpdateListener.onBufferingUpdate() 回调方法,此回调允许 app 在音视频流中跟踪缓冲状态。我们可以调用 setOnBufferingUpdateListener(OnBufferingUpdateListener) 方法注册 OnBufferingUpdateListener。
注意如果 MediaPlayer 对象已经处于 Started 状态,此时再调用 start() 方法不会有任何效果。我们可以调用 MediaPlayer.isPlaying() 方法检测 MediaPlayer 对象是否处于 Started 状态。
Paused 状态
音视频内容开始播放,已处于 Started 状态后,可以暂停播放。调用 MediaPlayer.pause() 方法可以暂停播放。当 pause() 方法调用成功后,MediaPlayer 对象会进入 Paused 状态。如果 MediaPlayer 对象已经处于 Paused 状态,此时再调用 pause() 方法不会有任何效果。
Paused 状态状态下可以调用 start() 方法继续播放,继续播放开始的位置与暂停位置相同。从 Started 状态到 Paused 状态的转换(反之亦然)在播放器引擎中是异步发生的,因此调用 isPlaying() 方法获取到的状态可能有延时,需要过一段时间才能更新。对于流式内容,这个时间可能长达几秒。
PauStopped 状态
音视频内容开始播放,已处于 Started 状态后,可以停止播放。调用 MediaPlayer.stop() 方法可以停止播放,并使处于 Started、Paused、Prepared 或 PlaybackCompleted 状态的 MediaPlayer 进入 Stopped 状态。如果 MediaPlayer 对象已经处于 Stopped 状态,此时再调用 stop() 方法不会有任何效果。
一旦处于 Stopped 状态,MediaPlayer 就不能调用 start() 方法继续播放。如果想要开始播放,需要调用 prepare() 方法或 prepareAsync() 方法重新准备,待到 MediaPlayer 对象状态变为 Prepared,才能开始播放。
调整播放位置
音视频内容开始播放,已处于 Started 状态后,可以调用 seekTo(long, int) 方法调整当前播放位置。seekTo(long, int) 方法是个异步方法,调用 seekTo() 方法不会阻塞当前线程,可以继续执行 seekTo 之后的代码,但实际的跳转操作可能需要过一段时间才能完成。这个延时在当前内容是音视频流时尤为明显。
当实际的跳转操作完成时,MediaPlayer 会调用用户提供的 OnSeekCompleteListener.onSeekComplete() 回调方法通知用户跳转完成。我们可以使用 setOnSeekCompleteListener(OnSeekCompleteListener) 方法注册 OnSeekCompleteListener。
seekTo(long, int) 方法也可以在其他非 Started 状态下调用,例如 Prepared、Paused 和 PlaybackCompleted 状态。如果音视频流中有视频,并且请求的位置有效,则在这些状态下调用 seekTo(long, int) 方法时,MediaPlayer 不会继续播放,而是显示目标位置的视频帧(即展示当前画面)。在 Started 状态下调用 seekTo(long, int) 方法,当跳转操作完成时,MediaPlayer 对象会从目标位置开始继续播放。
我们可以调用 getCurrentPosition() 来获取当前实际的播放位置。
PlaybackCompleted(播放完成)
当播放到音视频内容的末尾时,播放完成。
如果已调用 setLooping(boolean) 方法将循环播放模式设置为 true,则 MediaPlayer 对象将保持 Started 状态,不会变为 PlaybackCompleted 状态。
如果循环模式为 false,当播放完成时,MediaPlayer 会调用用户提供的 OnCompletionListener.onCompletion() 回调方法通知用户播放完成。我们可以使用 setOnCompletionListener(OnCompletionListener) 方法注册 OnCompletionListener
在 PlaybackCompleted 状态下,我们可以调用 start() 方法重新从头开始播放音视频内容。
End(结束状态)
MediaPlayer 的 End 状态意为结束状态。在 Idle 状态和 End 状态之间的状态就是 MediaPlayer 的生命周期状态。当 MediaPlayer 对象调用了 release() 方法后,MediaPlayer 就处于 End 状态。release() 方法在以下两种场景会被调用:
MediaPlayer 对象被回收时。一旦 MediaPlayer 的实例被创建,我们就必须保持对该实例的引用,以防止它被 GC 回收。如果 MediaPlayer 实例被回收,则 MediaPlayer 的 release() 方法将被调用 ,以停止正在播放的音视频内容。
音视频内容播放完成。MediaPlayer 实例正常播放完音视频内容后,我们也应该调用 release() 方法释放获取到的资源(例如内存和编解码器等)。一旦调用了 release() 方法,我们就不能再与已释放的 MediaPlayer 实例进行交互。
鉴于 MediaPlayer 中定义了多个不同的状态,所以我们需要考虑 MediaPlayer 中不同方法在哪些状态下可以调用,在哪些状态下不能调用。下表列举了各个方法在哪些状态调用是有效的和无效的。
MediaPlayer 的使用遵循着固定的步骤,很简单,可以参考官方的状态图以及上文的讲解。下面列举下使用 MediaPlayer 的示例代码。完整代码可看 Github 链接:MediaPlayerVideoFragment.kt
实例代码主要涉及几个方面:
将创建好的 SurfaceHolder 设置给 MediaPlayer,以用于视频画面的展示(视频的展示用 SurfaceView)。SurfaceHolder 的创建是个异步过程,需要设置 SurfaceHolder.Callback 回调。
注册视频尺寸监听,并设置 SurfaceView 的宽高
配置 MediaPlayer,包括设置是否循环播放,设置播放时屏幕常亮
new 出 MediaPlayer 对象后,依次调用 setDataSource、prepare、start 方法
亮屏时恢复播放(start),熄屏时暂停播放(pause),界面销毁时(onDestroy)停止播放(stop),并调用 release 方法释放资源。
class MediaPlayerVideoFragment : BaseFragment<FragmentMediaPlayerVideoBinding>() {
companion object {
const val TAG = "MediaPlayerVideo"
private var scheduledJob: Job? = null
val mediaPlayer = MediaPlayer()
override fun initView(rootView: View) {
// 设置回调,将创建好的 SurfaceHolder 设置给 MediaPlayer
binding.svVideo.holder.addCallback(object : SurfaceHolder.Callback2 {
override fun surfaceCreated(holder: SurfaceHolder) {
mediaPlayer.setDisplay(holder)
// 其他代码省略
// 设置进度条
binding.sbProgress.setOnSeekBarChangeListener(
object : SeekBar.OnSeekBarChangeListener {
override fun onStopTrackingTouch(seekBar: SeekBar) {
// 拖动结束,进行跳转
mediaPlayer.seekTo(seekBar.progress)
// 其他代码省略
binding.btnPlay.setOnClickListener {
startPlay()
override fun initData(context: Context) {
mediaPlayer.apply {
// 重置到 Idle 状态
mediaPlayer.reset()
// 循环播放
mediaPlayer.isLooping = true
// 设置播放时屏幕常量
setScreenOnWhilePlaying(true)
setDataSource(context, Uri.parse(R.raw.tanaka_asuka.uriPath()))
// 注册视频尺寸监听
setOnVideoSizeChangedListener { mMediaPlayer, width, height ->
changeViewSize(width, height)
prepareAndStart()
private fun changeViewSize(videoWidth: Int, videoHeight: Int) {
if (videoWidth <= 0 || videoHeight <= 0) {
return
// 设置视频画面尺寸
binding.svVideo.post {
val viewWidth = binding.svVideo.measuredWidth
val viewHeight = (videoHeight.toFloat() / videoWidth * viewWidth).toInt()
val lp = binding.svVideo.layoutParams
lp.width = viewWidth
lp.height = viewHeight
binding.svVideo.layoutParams = lp
private fun MediaPlayer.prepareAndStart() {
lifecycleScope.runResult(
doOnIo = {
prepare()
doOnSuccess = {
playState = PlayState.PREPARED
realStartPlay()
doOnFailure = {
playState = PlayState.ERROR
override fun onResume() {
super.onResume()
startPlay()
override fun onPause() {
super.onPause()
stopPlay(true)
override fun onDestroy() {
super.onDestroy()
if (mediaPlayer.isPlaying) {
mediaPlayer.stop()
mediaPlayer.release()
private fun MediaPlayer.notPlay(): Boolean {
return playState.isPlayState(PlayState.PREPARED) || playState.isPlayState(PlayState.PAUSED)
private fun startPlay() {
if (playState == PlayState.PLAYING) {
return
if (playState.isPlayState(PlayState.UNINITIALIZED)) {
mediaPlayer.prepareAndStart()
return
if (playState.isPlayState(PlayState.STOPPED)) {
mediaPlayer.seekTo(0)
if (mediaPlayer.notPlay()) {
realStartPlay()
private fun realStartPlay() {
mediaPlayer.start()
playState = PlayState.PLAYING
private fun stopPlay(isPaused: Boolean = false) {
if (isPaused) {
mediaPlayer.pause()
playState = PlayState.PAUSED
} else {
mediaPlayer.stop()
playState = PlayState.UNINITIALIZED
MediaPlayer 的相关知识暂时就讲这些,以后有机会好好研究下 Android 的音视频框架。再出篇更详细的讲解。