You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin中如何判断MediaPlayer就绪可调用start()

问题根因

你遇到的暂停/停止操作失效问题,本质是Android MediaPlayer 存在严格的状态机约束:只有进入Prepared状态后,调用start()/pause()/stop()这类播放控制方法才会生效;在Preparing状态下直接调用控制方法,要么触发静默失败要么抛出非法状态异常。靠固定延时等待prepare完成完全不可靠,不同设备性能、媒体源类型(本地/网络)、文件码率都会导致prepare耗时从几十毫秒到数秒不等,固定延时没法覆盖所有场景。

标准解决方案

官方推荐使用异步准备+状态监听+标记位管控的方案实现时序控制,完全不需要手动加延时:

核心实现逻辑

  1. 弃用主线程同步调用prepare()的写法,改用prepareAsync()触发异步准备,避免阻塞UI导致ANR
  2. 注册OnPreparedListener监听,只有收到准备完成回调时,才标记播放器进入可操作状态
  3. 增加状态标记位拦截用户的提前操作,等准备完成后自动执行待处理的播放请求,不需要用户手动等待

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 09:12:30