Android Kotlin中如何判断MediaPlayer就绪可调用start()
问题根因
你遇到的暂停/停止操作失效问题,本质是Android MediaPlayer 存在严格的状态机约束:只有进入Prepared状态后,调用start()/pause()/stop()这类播放控制方法才会生效;在Preparing状态下直接调用控制方法,要么触发静默失败要么抛出非法状态异常。靠固定延时等待prepare完成完全不可靠,不同设备性能、媒体源类型(本地/网络)、文件码率都会导致prepare耗时从几十毫秒到数秒不等,固定延时没法覆盖所有场景。
标准解决方案
官方推荐使用异步准备+状态监听+标记位管控的方案实现时序控制,完全不需要手动加延时:
核心实现逻辑
- 弃用主线程同步调用
prepare()的写法,改用prepareAsync()触发异步准备,避免阻塞UI导致ANR - 注册
OnPreparedListener监听,只有收到准备完成回调时,才标记播放器进入可操作状态 - 增加状态标记位拦截用户的提前操作,等准备完成后自动执行待处理的播放请求,不需要用户手动等待
Kotlin 实现代码
class AudioPlayActivity : AppCompatActivity() { private lateinit var mediaPlayer: MediaPlayer // 状态标记位 private var isPlayerReady = false // 是否完成prepare可接受控制指令 private var isPlaying = false // 当前是否处于播放状态 private var pendingStart = false // 是否有等待执行的播放请求 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 初始化播放器 initMediaPlayer() initViewClickListener() } private fun initMediaPlayer() { mediaPlayer = MediaPlayer().apply { // 注册准备完成监听,这是唯一可信的准备完成判断依据 setOnPreparedListener { mp -> isPlayerReady = true // 如果之前用户已经点了启动,准备完成后自动播放 if (pendingStart) { mp.start() isPlaying = true pendingStart = false } } // 出错时重置状态,避免后续操作异常 setOnErrorListener { _, _, _ -> isPlayerReady = false isPlaying = false pendingStart = false reset() true } // 播放完成重置状态 setOnCompletionListener { isPlaying = false } } } private fun initViewClickListener() { // 启动按钮 findViewById<Button>(R.id.btn_start).setOnClickListener { when { // 已经准备完成,直接播放 isPlayerReady -> { if (!isPlaying) { mediaPlayer.start() isPlaying = true } } // 还在准备中,标记待执行播放,不重复触发prepare !pendingStart -> { pendingStart = true // 首次启动还没初始化数据源的话,在这里触发异步准备 runCatching { if (!isPlayerReady && !isPlaying) { mediaPlayer.setDataSource("你的媒体文件路径/url") mediaPlayer.prepareAsync() } } } } } // 暂停按钮 findViewById<Button>(R.id.btn_pause).setOnClickListener { // 只有准备完成且正在播放时才执行暂停 if (isPlayerReady && isPlaying) { mediaPlayer.pause() isPlaying = false } } // 停止按钮 findViewById<Button>(R.id.btn_stop).setOnClickListener { if (isPlayerReady) { mediaPlayer.stop() isPlaying = false isPlayerReady = false mediaPlayer.reset() } } } override fun onDestroy() { super.onDestroy() mediaPlayer.release() } }
注意事项
- 绝对不要在主线程调用同步
prepare()方法,加载本地大文件或者网络资源时极易触发ANR,如果你确实需要用同步准备,必须把整个prepare逻辑放到子线程执行,准备完成后再切回主线程更新UI和播放器状态 - 所有播放控制操作不要直接绑定MediaPlayer的原生方法,必须先通过自己维护的状态标记位做校验,避免非法状态调用
- 准备过程中不要重复调用
prepare()/prepareAsync(),用标记位拦截重复请求,否则会触发状态错误
内容的提问来源于stack exchange,提问作者Marwan Talal
相关产品推荐
相关产品推荐

