From 4d9e3cb6de14adde20b69d104f87473c10b1ef22 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Fri, 2 Jan 2026 14:13:03 -0500 Subject: [PATCH] Fix MPV race condition causing no video output (#619) ## Description Fixes a race condition where `libmpv` is beginning playback before it has been attached to the output video surface. The solution is for the handler to re-enqueue commands if MPV is either not initialized or not attached yet. Once `libmpv` is ready, the commands will be processed. ### Related issues Should fix #576 --- .../wholphin/util/mpv/MpvPlayer.kt | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt index 4777e977..81eb8669 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt @@ -109,6 +109,7 @@ class MpvPlayer( Timber.v("config-dir=${context.filesDir.path}") MPVLib.addLogObserver(mpvLogger) + Timber.v("Creating MPVLib") MPVLib.create(context) MPVLib.setOptionString("config", "yes") MPVLib.setOptionString("config-dir", context.filesDir.path) @@ -127,6 +128,7 @@ class MpvPlayer( MPVLib.setOptionString("demuxer-max-bytes", "${cacheMegs * 1024 * 1024}") MPVLib.setOptionString("demuxer-max-back-bytes", "${cacheMegs * 1024 * 1024}") + Timber.v("Initializing MPVLib") MPVLib.initialize() MPVLib.setOptionString("force-window", "no") @@ -466,7 +468,6 @@ class MpvPlayer( override fun clearVideoSurfaceHolder(surfaceHolder: SurfaceHolder?): Unit = throw UnsupportedOperationException() override fun setVideoSurfaceView(surfaceView: SurfaceView?) { - throwIfReleased() if (DEBUG) Timber.v("setVideoSurfaceView") val surface = surfaceView?.holder?.surface if (surface != null && surface.isValid) { @@ -635,9 +636,9 @@ class MpvPlayer( val title = it.label ?: "External Subtitles" Timber.v("Adding external subtitle track '$title'") if (it.language.isNotNullOrBlank()) { - MPVLib.command(arrayOf("sub-add", url, "auto", title, it.language!!)) + MPVLib.command(arrayOf("sub-add", url, "select", title, it.language!!)) } else { - MPVLib.command(arrayOf("sub-add", url, "auto", title)) + MPVLib.command(arrayOf("sub-add", url, "select", title)) } } } @@ -837,7 +838,18 @@ class MpvPlayer( override fun handleMessage(msg: Message): Boolean { val cmd = MpvCommand.entries[msg.what] - Timber.v("handleMessage: cmd=$cmd") + Timber.d("handleMessage: cmd=$cmd") + if (isReleased && cmd != MpvCommand.DESTROY) { + Timber.w("Player is released, ignoring command %s", cmd) + return true + } + if (surface == null && !cmd.isLifecycle) { + // If libmpv isn't ready, re-enqueue the messages + // Note: this means nothing will play until it is attached to a surface, + // so MpvPlayer can't be used for background audio/music playback + Timber.v("MPV is not initialized/attached yet, requeue cmd %s", cmd) + internalHandler.sendMessageDelayed(Message.obtain(msg), 50) + } when (cmd) { MpvCommand.PLAY_PAUSE -> { val playWhenReady = msg.obj as Boolean @@ -894,6 +906,7 @@ class MpvPlayer( MPVLib.detachSurface() MPVLib.setPropertyString("vo", "null") MPVLib.setPropertyString("force-window", "no") + Timber.d("Detached surface") } if (surface != null) { MPVLib.attachSurface(surface) @@ -1035,14 +1048,16 @@ private data class MediaAndPosition( val startPositionMs: Long, ) -enum class MpvCommand { - PLAY_PAUSE, - SEEK, - SET_TRACK_SELECTION, - SET_SPEED, - SET_SUBTITLE_DELAY, - LOAD_FILE, - ATTACH_SURFACE, - INITIALIZE, - DESTROY, +enum class MpvCommand( + val isLifecycle: Boolean, +) { + PLAY_PAUSE(false), + SEEK(false), + SET_TRACK_SELECTION(false), + SET_SPEED(false), + SET_SUBTITLE_DELAY(false), + LOAD_FILE(false), + ATTACH_SURFACE(true), + INITIALIZE(true), + DESTROY(true), }