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

ExoPlayer3拖动进度条时出现播放错误,求排查方案

问题:ExoPlayer3快速拖动滑块触发MediaCodec启动失败错误

问题背景

基于ExoPlayer3开发适配Android Auto的媒体播放器,实现播放进度控制滑块后,连续/快速拖动滑块时会触发MediaCodec启动失败错误,错误栈未关联业务代码,且需切换媒体URI切回才能恢复播放。

滑块控制代码

chapterSeek.addOnChangeListener { _, value, fromUser ->
    if (fromUser) {
        val showHours = Duration.ofMillis(chapterSeek.valueTo.toLong()).toHours() > 0

        chapterDuration.text = "${
            getDuration(value.toLong(), showHours)
        } \ ${
            getDuration(chapterSeek.valueTo.toLong(), showHours)
        }"

        parentActivity.startForegroundService(Intent(
                binding.root.context,
                MediaService::class.java
        ).apply {
            action = Constants.MediaServiceActions.SEEK_MEDIA.name

            putExtra(Constants.IntentDataKeys.CHAPTER_POSITION.name, value.toLong())
        })
    }
}

Service中处理Seek的逻辑

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    if (intent == null) return START_NOT_STICKY
    if (intent.action == null) return START_NOT_STICKY
    val reciter = intent.getTypedSerializable<Reciter>(IntentDataKeys.RECITER.name)
    val chapter = intent.getTypedSerializable<Chapter>(IntentDataKeys.CHAPTER.name)

    mediaManager.whenReady { _, _ ->
        val chapterPosition =
                intent.getLongExtra(IntentDataKeys.CHAPTER_POSITION.name, -1L)

        when (intent.action) {
            DOWNLOAD_CHAPTERS.name      -> {
                if (reciter == null) return@whenReady
                showDownloadForegroundNotification(reciter)
                mediaManager.downloadChapters(reciter)
            }

            PLAY_MEDIA.name             -> {
                if (reciter == null || chapter == null) return@whenReady
                showMediaForegroundNotification(reciter, chapter)
                prepareMedia(reciter, chapter, chapterPosition)
            }

            PAUSE_MEDIA.name            -> pauseMedia()
            TOGGLE_MEDIA.name           -> toggleMedia()
            STOP_MEDIA.name             -> stopSelf()
            SKIP_TO_NEXT_MEDIA.name     -> skipToNextChapter()
            SKIP_TO_PREVIOUS_MEDIA.name -> skipToPreviousChapter()
            SEEK_MEDIA.name             -> seekChapterToPosition(chapterPosition)
        }
    }

    return START_STICKY
}

fun seekChapterToPosition(chapterPosition: Long) {
    if (chapterPosition != -1L) {
        Log.d("ExoPlayer_Audio_Player", "Seeking to $chapterPosition...")
        exoPlayer.seekTo(chapterPosition)
        setMediaPlaybackState(MediaSessionState.PLAYING)
    }
}

错误日志

19:33:37.346 MediaCodecAudioRenderer    E  Audio codec error
                                             java.lang.IllegalStateException: start failed
                                                 at android.media.MediaCodec.native_start(Native Method)
                                                 at android.media.MediaCodec.start(MediaCodec.java:2323)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecAdapter.$r8$lambda$eDpY_fTfY9aVkUSlR57RZPPXgr0(Unknown Source:0)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecAdapter$$ExternalSyntheticLambda1.run(Unknown Source:2)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecCallback.onFlushCompletedSynchronized(AsynchronousMediaCodecCallback.java:265)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecCallback.onFlushCompleted(AsynchronousMediaCodecCallback.java:244)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecCallback.lambda$flushAsync$0$com-google-android-exoplayer2-mediacodec-AsynchronousMediaCodecCallback(AsynchronousMediaCodecCallback.java:202)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecCallback$$ExternalSyntheticLambda0.run(Unknown Source:4)
                                                 at android.os.Handler.handleCallback(Handler.java:942)
                                                 at android.os.Handler.dispatchMessage(Handler.java:99)
                                                 at android.os.Looper.loopOnce(Looper.java:201)
                                                 at android.os.Looper.loop(Looper.java:288)
                                                 at android.os.HandlerThread.run(HandlerThread.java:67)
19:33:37.347 ExoPlayerImplInternal      E  Playback error
                                             com.google.android.exoplayer2.ExoPlaybackException: MediaCodecAudioRenderer error, index=1, format=Format(null, null, null, audio/mpeg, null, -1, null, [-1, -1, -1.0], [2, 44100]), format_supported=YES
                                                 at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:562)
                                                 at android.os.Handler.dispatchMessage(Handler.java:102)
                                                 at android.os.Looper.loopOnce(Looper.java:201)
                                                 at android.os.Looper.loop(Looper.java:288)
                                                 at android.os.HandlerThread.run(HandlerThread.java:67)
                                             Caused by: com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException: Decoder failed: OMX.google.mp3.decoder
                                                 at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.createDecoderException(MediaCodecRenderer.java:918)
                                                 at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:799)
                                                 at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:982)
                                                 at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:486)
                                                 at android.os.Handler.dispatchMessage(Handler.java:102)
                                                 at android.os.Looper.loopOnce(Looper.java:201)
                                                 at android.os.Looper.loop(Looper.java:288)
                                                 at android.os.HandlerThread.run(HandlerThread.java:67)
                                             Caused by: java.lang.IllegalStateException: start failed
                                                 at android.media.MediaCodec.native_start(Native Method)
                                                 at android.media.MediaCodec.start(MediaCodec.java:2323)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecAdapter.$r8$lambda$eDpY_fTfY9aVkUSlR57RZPPXgr0(Unknown Source:0)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecAdapter$$ExternalSyntheticLambda1.run(Unknown Source:2)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecCallback.onFlushCompletedSynchronized(AsynchronousMediaCodecCallback.java:265)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecCallback.onFlushCompleted(AsynchronousMediaCodecCallback.java:244)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecCallback.lambda$flushAsync$0$com-google-android-exoplayer2-mediacodec-AsynchronousMediaCodecCallback(AsynchronousMediaCodecCallback.java:202)
                                                 at com.google.android.exoplayer2.mediacodec.AsynchronousMediaCodecCallback$$ExternalSyntheticLambda0.run(Unknown Source:4)
                                                 at android.os.Handler.handleCallback(Handler.java:942)
                                                 at android.os.Handler.dispatchMessage(Handler.java:99)
                                                 at android.os.Looper.loopOnce(Looper.java:201)
                                                 at android.os.Looper.loop(Looper.java:288)
                                                 at android.os.HandlerThread.run(HandlerThread.java:67)

问题原因及解决方案

核心问题

快速拖动滑块时,会频繁发送SEEK_MEDIA指令到Service,每次调用exoPlayer.seekTo()都会触发MediaCodec的flush操作。短时间内大量flush请求会导致MediaCodec内部状态紊乱——Codec还未完成上一次flush就被要求执行新操作,最终触发start failed异常。另外,每次seek时强制设置播放状态,会进一步加剧Codec的状态冲突。

解决方案

  1. 防抖处理滑块事件
    对滑块的onChangeListener添加防抖,避免短时间内发送大量Seek请求:
private val seekHandler = Handler(Looper.getMainLooper())
private var pendingSeekRunnable: Runnable? = null

chapterSeek.addOnChangeListener { _, value, fromUser ->
    if (fromUser) {
        val showHours = Duration.ofMillis(chapterSeek.valueTo.toLong()).toHours() > 0
        chapterDuration.text = "${getDuration(value.toLong(), showHours)} \ ${getDuration(chapterSeek.valueTo.toLong(), showHours)}"

        // 取消之前的延迟任务
        pendingSeekRunnable?.let { seekHandler.removeCallbacks(it) }
        
        // 延迟200ms执行Seek,可根据需求调整时长
        pendingSeekRunnable = Runnable {
            parentActivity.startForegroundService(Intent(
                binding.root.context,
                MediaService::class.java
            ).apply {
                action = Constants.MediaServiceActions.SEEK_MEDIA.name
                putExtra(Constants.IntentDataKeys.CHAPTER_POSITION.name, value.toLong())
            })
        }
        seekHandler.postDelayed(pendingSeekRunnable!!, 200)
    }
}
  1. 避免强制设置播放状态
    在seekChapterToPosition方法中,仅保持播放器原本的状态:
fun seekChapterToPosition(chapterPosition: Long) {
    if (chapterPosition != -1L) {
        Log.d("ExoPlayer_Audio_Player", "Seeking to $chapterPosition...")
        val wasPlaying = exoPlayer.isPlaying
        exoPlayer.seekTo(chapterPosition)
        // 根据原状态设置播放状态
        setMediaPlaybackState(if (wasPlaying) MediaSessionState.PLAYING else MediaSessionState.PAUSED)
    }
}
  1. 在Service中合并重复Seek请求
    维护最新的Seek位置,避免重复执行seekTo:
private var latestSeekPosition: Long = -1L
private val seekLock = Any()
private var isSeeking = false

fun seekChapterToPosition(chapterPosition: Long) {
    synchronized(seekLock) {
        latestSeekPosition = chapterPosition
        // 如果已有正在处理的Seek任务,直接更新目标位置即可
        if (isSeeking) return
        isSeeking = true
    }

    mediaManager.whenReady { _, _ ->
        synchronized(seekLock) {
            val targetPosition = latestSeekPosition
            latestSeekPosition = -1L
            isSeeking = false
            if (targetPosition == -1L) return@whenReady
            
            val wasPlaying = exoPlayer.isPlaying
            exoPlayer.seekTo(targetPosition)
            setMediaPlaybackState(if (wasPlaying) MediaSessionState.PLAYING else MediaSessionState.PAUSED)
        }
    }
}
  1. 添加错误自动恢复逻辑
    注册ExoPlayer错误监听,发生MediaCodec错误时重置播放器:
exoPlayer.addListener(object : Player.Listener {
    override fun onPlayerError(error: PlaybackException) {
        if (error.cause is MediaCodecDecoderException) {
            val wasPlaying = exoPlayer.isPlaying
            // 重置播放器
            exoPlayer.stop()
            exoPlayer.prepare()
            // 恢复原播放状态
            if (wasPlaying) exoPlayer.play()
        }
    }
})

内容的提问来源于stack exchange,提问作者AbdalMoniem AlHifnawy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 06:25:56