feat: pass currently selected or last selected subtitle to external players

This commit is contained in:
j-hc 2026-07-27 03:45:38 +03:00
parent 00cc012193
commit 615fa43f5a
No known key found for this signature in database
GPG key ID: CDF97F1DBFE904CD
9 changed files with 53 additions and 9 deletions

View file

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

View file

@ -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(

View file

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

View file

@ -18,6 +18,7 @@ data class ExternalPlayerPlaybackRequest(
val sourceHeaders: Map<String, String> = emptyMap(),
val resumePositionMs: Long = 0L,
val subtitles: List<SubtitleInput>? = null,
val currentSubtitle: SubtitleInput? = null,
val season: Int? = null,
val episode: Int? = null,
val episodeTitle: String? = null,

View file

@ -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 {

View file

@ -39,4 +39,5 @@ internal data class PlayerScreenArgs(
val initialPositionMs: Long,
val initialProgressFraction: Float?,
val contentLanguage: String? = null,
val currentSubtitle: SubtitleInput? = null,
)

View file

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

View file

@ -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 ->

View file

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