From 80602ecd230f46b315a4f0aca480c3a3255cfb05 Mon Sep 17 00:00:00 2001 From: Gauvain Date: Mon, 6 Jul 2026 23:48:22 +0200 Subject: [PATCH] fix(mpv): re-emit tracksReady after FILE_LOADED to win the disable race Initial subtitle selection now comes from JS on tracksReady; FILE_LOADED still disables subtitles, which could wipe a JS selection that landed first on embedded-only files (no later track-list change to re-fire). Re-emitting tracksReady after the disable guarantees the idempotent JS re-apply always runs last. Also guards the iOS track-id read against failure, matching Android. --- .../expo/modules/mpvplayer/MPVLayerRenderer.kt | 7 +++++++ modules/mpv-player/ios/MPVLayerRenderer.swift | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MPVLayerRenderer.kt b/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MPVLayerRenderer.kt index 3cd6732f..b026c6b8 100644 --- a/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MPVLayerRenderer.kt +++ b/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MPVLayerRenderer.kt @@ -851,6 +851,13 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver { initialAudioId?.let { if (it > 0) setAudioTrack(it) } initialSubtitleId?.let { setSubtitleTrack(it) } ?: disableSubtitles() + // The disable above can race a JS-side identity selection that + // landed before FILE_LOADED (JS no longer passes an initial sid). + // Re-emit tracksReady so the idempotent JS re-apply always runs + // after it — for embedded-only files this is the only + // post-FILE_LOADED fire. + mainHandler.post { delegate?.onTracksReady() } + if (!isReadyToSeek) { isReadyToSeek = true mainHandler.post { delegate?.onReadyToSeek() } diff --git a/modules/mpv-player/ios/MPVLayerRenderer.swift b/modules/mpv-player/ios/MPVLayerRenderer.swift index 18b9ac4b..e6a5e0a0 100644 --- a/modules/mpv-player/ios/MPVLayerRenderer.swift +++ b/modules/mpv-player/ios/MPVLayerRenderer.swift @@ -508,6 +508,15 @@ final class MPVLayerRenderer { } else { disableSubtitles() } + // The disable above can race a JS-side identity selection that + // landed before FILE_LOADED (JS no longer passes an initial sid). + // Re-emit tracksReady so the idempotent JS re-apply always runs + // after it — for embedded-only files this is the only + // post-FILE_LOADED fire. + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.delegate?.renderer(self, didBecomeTracksReady: true) + } if !isReadyToSeek { isReadyToSeek = true DispatchQueue.main.async { [weak self] in @@ -759,8 +768,8 @@ final class MPVLayerRenderer { trackType == "sub" else { continue } var trackId: Int64 = 0 - getProperty(handle: handle, name: "track-list/\(i)/id", format: MPV_FORMAT_INT64, value: &trackId) - + guard getProperty(handle: handle, name: "track-list/\(i)/id", format: MPV_FORMAT_INT64, value: &trackId) >= 0 else { continue } + var track: [String: Any] = ["id": Int(trackId)] if let title = getStringProperty(handle: handle, name: "track-list/\(i)/title") { @@ -892,8 +901,8 @@ final class MPVLayerRenderer { trackType == "audio" else { continue } var trackId: Int64 = 0 - getProperty(handle: handle, name: "track-list/\(i)/id", format: MPV_FORMAT_INT64, value: &trackId) - + guard getProperty(handle: handle, name: "track-list/\(i)/id", format: MPV_FORMAT_INT64, value: &trackId) >= 0 else { continue } + var track: [String: Any] = ["id": Int(trackId)] if let title = getStringProperty(handle: handle, name: "track-list/\(i)/title") {