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
This commit is contained in:
Ray 2026-01-02 14:13:03 -05:00 committed by GitHub
parent 0fe8e5d988
commit 4d9e3cb6de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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),
}