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的状态冲突。
解决方案
- 防抖处理滑块事件
对滑块的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) } }
- 避免强制设置播放状态
在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) } }
- 在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) } } }
- 添加错误自动恢复逻辑
注册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
相关产品推荐
相关产品推荐

