diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.android.kt index e2e5e2f2c..5ae9dc700 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.android.kt @@ -109,6 +109,7 @@ internal actual object ExternalPlayerPlatform { val subtitles = request.subtitles if (!subtitles.isNullOrEmpty()) { val subtitleUris = subtitles.map { Uri.parse(it.url) }.toTypedArray() + val subtitleCurrentOrFirstUri = request.currentSubtitle?.url ?: subtitles.first().url val subtitleNames = subtitles.map { it.name }.toTypedArray() val subtitleFilenames = subtitles.map { "${it.lang}_${it.name}.srt" }.toTypedArray() @@ -120,7 +121,7 @@ internal actual object ExternalPlayerPlatform { val clipData = android.content.ClipData( "subtitles", arrayOf("application/x-subrip", "text/vtt"), - android.content.ClipData.Item(subtitleUris.first()) + android.content.ClipData.Item(subtitleCurrentOrFirstUri) ) subtitleUris.drop(1).forEach { subtitleUri -> clipData.addItem(android.content.ClipData.Item(subtitleUri)) @@ -131,17 +132,17 @@ internal actual object ExternalPlayerPlatform { putExtra("subs", subtitleUris) putExtra("subs.name", subtitleNames) putExtra("subs.filename", subtitleFilenames) - putExtra("subs.enable", arrayOf(subtitleUris.first())) + putExtra("subs.enable", arrayOf(subtitleCurrentOrFirstUri)) // Just Player putExtra("subtitle_uri", subtitleUris) putExtra("subtitle_name", subtitleNames) - // VLC (single subtitle — use first one) - putExtra("subtitles_location", subtitleUris.first()) + // VLC (single subtitle — use selected one) + putExtra("subtitles_location", subtitleCurrentOrFirstUri) // Vimu Player - putExtra("forcedsrt", subtitles.first().url) + putExtra("forcedsrt", subtitleCurrentOrFirstUri) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index d0d41f532..929f86ab8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -176,9 +176,11 @@ import com.nuvio.app.features.player.PlayerPlaybackSnapshot import com.nuvio.app.features.player.ExternalPlayerIntentResult import com.nuvio.app.features.player.ExternalPlayerPlatform import com.nuvio.app.features.player.ExternalPlayerPlaybackRequest +import com.nuvio.app.features.player.SubtitleInput import com.nuvio.app.features.player.rememberExternalPlayerLauncher import com.nuvio.app.features.player.prepareExternalPlayerLaunch import com.nuvio.app.features.player.SubtitleLanguageOption +import com.nuvio.app.features.streams.StreamSubtitle import com.nuvio.app.features.player.sanitizePlaybackHeaders import com.nuvio.app.features.player.sanitizePlaybackResponseHeaders import com.nuvio.app.features.profiles.AvatarRepository @@ -394,6 +396,14 @@ private fun PlayerLaunch.toExternalPlayerPlaybackRequest(): ExternalPlayerPlayba season = seasonNumber, episode = episodeNumber, episodeTitle = episodeTitle, + currentSubtitle = currentSubtitle, + subtitles = externalSubtitles.map { sub: StreamSubtitle -> + SubtitleInput( + url = sub.url, + name = sub.name ?: sub.language, + lang = sub.language + ) + }.takeIf { it.isNotEmpty() } ) private enum class AppGateScreen { @@ -1398,6 +1408,7 @@ private fun MainAppContent( request = baseRequest, type = launch.contentType ?: launch.parentMetaType, videoId = launch.videoId ?: launch.parentMetaId, + parentMetaId = launch.parentMetaId, forwardSubtitles = playerSettingsUiState.externalPlayerForwardSubtitles, sendSkipSegments = shouldSendSkipSegments, preferredLanguage = playerSettingsUiState.preferredSubtitleLanguage, @@ -2983,6 +2994,7 @@ private fun MainAppContent( parentMetaId = launch.parentMetaId, parentMetaType = launch.parentMetaType, initialPositionMs = request.resumePositionMs, + currentSubtitle = request.currentSubtitle, ) lastExternalPlayerLaunch = playerLaunch val intentResult = ExternalPlayerPlatform.buildIntent( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/ExternalPlayerLaunchCoordinator.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/ExternalPlayerLaunchCoordinator.kt index 5024ef945..a5e494db2 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/ExternalPlayerLaunchCoordinator.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/ExternalPlayerLaunchCoordinator.kt @@ -33,6 +33,7 @@ suspend fun prepareExternalPlayerLaunch( request: ExternalPlayerPlaybackRequest, type: String, videoId: String, + parentMetaId: String, forwardSubtitles: Boolean, sendSkipSegments: Boolean, preferredLanguage: String, @@ -48,6 +49,7 @@ suspend fun prepareExternalPlayerLaunch( val subtitles = SubtitleForwarder.fetchForExternalPlayer( type = type, videoId = videoId, + parentMetaId = parentMetaId, preferredLanguage = preferredLanguage, secondaryLanguage = secondaryLanguage, ) @@ -78,6 +80,17 @@ suspend fun prepareExternalPlayerLaunch( result = result.copy(skipSegmentsJson = skipSegmentsJson) } + val preference = PlayerTrackPreferenceStorage.load(parentMetaId) + if (result.currentSubtitle == null && preference != null) { + val match = result.subtitles?.find { + it.url == preference.addonSubtitleUrl || + (it.name == preference.subtitleName && it.lang == preference.subtitleLanguage) + } + if (match != null) { + result = result.copy(currentSubtitle = match) + } + } + return@coroutineScope result } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.kt index d2727938c..1f05b5136 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.kt @@ -18,6 +18,7 @@ data class ExternalPlayerPlaybackRequest( val sourceHeaders: Map = emptyMap(), val resumePositionMs: Long = 0L, val subtitles: List? = null, + val currentSubtitle: SubtitleInput? = null, val season: Int? = null, val episode: Int? = null, val episodeTitle: String? = null, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt index f4c21143e..ace518b07 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerModels.kt @@ -46,6 +46,7 @@ data class PlayerLaunch( val initialPositionMs: Long = 0L, val initialProgressFraction: Float? = null, val contentLanguage: String? = null, + val currentSubtitle: SubtitleInput? = null, ) object PlayerLaunchStore { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenArgs.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenArgs.kt index bc381ecc6..070ff0186 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenArgs.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenArgs.kt @@ -39,4 +39,5 @@ internal data class PlayerScreenArgs( val initialPositionMs: Long, val initialProgressFraction: Float?, val contentLanguage: String? = null, + val currentSubtitle: SubtitleInput? = null, ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt index d83c22fd1..ca6d46e43 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt @@ -308,6 +308,13 @@ private fun PlayerScreenRuntime.RenderPlayerControls(displayedPositionMs: Long, sourceHeaders = activeSourceHeaders, resumePositionMs = playbackSnapshot.positionMs, subtitles = loadedSubtitles, + currentSubtitle = selectedAddonSubtitle?.let { + SubtitleInput( + it.url, + it.display, + it.language + ) + }, season = activeSeasonNumber, episode = activeEpisodeNumber, episodeTitle = activeEpisodeTitle, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleForwarder.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleForwarder.kt index 009c137a7..fe2bfa943 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleForwarder.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/SubtitleForwarder.kt @@ -13,6 +13,7 @@ object SubtitleForwarder { suspend fun fetchForExternalPlayer( type: String, videoId: String, + parentMetaId: String, preferredLanguage: String, secondaryLanguage: String?, timeoutMs: Long = 10_000L, @@ -31,11 +32,18 @@ object SubtitleForwarder { } val allSubtitles = SubtitleRepository.addonSubtitles.value + val preference = PlayerTrackPreferenceStorage.load(parentMetaId) val filtered = allSubtitles.filter { subtitle -> - languageMatchesPreference(subtitle.language, preferredLanguage) || - (secondaryLanguage != null && - languageMatchesPreference(subtitle.language, secondaryLanguage)) + val isLastSelected = preference != null && ( + subtitle.url == preference.addonSubtitleUrl || + (subtitle.display == preference.subtitleName && subtitle.language == preference.subtitleLanguage) + ) + + isLastSelected || + languageMatchesPreference(subtitle.language, preferredLanguage) || + (secondaryLanguage != null && + languageMatchesPreference(subtitle.language, secondaryLanguage)) } filtered.map { subtitle -> diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.ios.kt index c579e01b6..bb5b25e1c 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/ExternalPlayerPlatform.ios.kt @@ -36,7 +36,7 @@ private val iosExternalPlayerSpecs = listOf( buildString { append("vlc-x-callback://x-callback-url/stream?url=") append(request.sourceUrl.urlQueryEncode()) - request.subtitles?.firstOrNull()?.let { subtitle -> + (request.currentSubtitle ?: request.subtitles?.firstOrNull())?.let { subtitle -> append("&sub=") append(subtitle.url.urlQueryEncode()) }