mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-26 22:42:17 +00:00
fix(player): harden persisted subtitle switching
This commit is contained in:
parent
84bd1525cc
commit
002d459b72
10 changed files with 78 additions and 9 deletions
|
|
@ -9,6 +9,7 @@ internal actual object PlatformLocalAccountDataCleaner {
|
|||
"nuvio_library_display_settings",
|
||||
"nuvio_home_catalog_settings",
|
||||
"nuvio_player_settings",
|
||||
"nuvio_player_track_preferences",
|
||||
"torrent_settings",
|
||||
"nuvio_profile_cache",
|
||||
"nuvio_avatar_cache",
|
||||
|
|
|
|||
|
|
@ -1267,11 +1267,25 @@ private class NuvioLibmpvView(
|
|||
}
|
||||
|
||||
override fun clearExternalSubtitle() {
|
||||
extractLibmpvTracks(context, type = "sub")
|
||||
.filter { it.isExternal }
|
||||
.forEach { track -> mpv.command("sub-remove", track.id.toString()) }
|
||||
mpv.setPropertyString("sid", "no")
|
||||
}
|
||||
|
||||
override fun clearExternalSubtitleAndSelect(trackIndex: Int) {
|
||||
selectSubtitleTrack(trackIndex)
|
||||
val tracks = extractLibmpvTracks(context, type = "sub")
|
||||
val targetTrackId = tracks.getOrNull(trackIndex)
|
||||
?.takeUnless { it.isExternal }
|
||||
?.id
|
||||
tracks
|
||||
.filter { it.isExternal }
|
||||
.forEach { track -> mpv.command("sub-remove", track.id.toString()) }
|
||||
if (targetTrackId == null) {
|
||||
mpv.setPropertyString("sid", "no")
|
||||
} else {
|
||||
mpv.setPropertyInt("sid", targetTrackId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun applySubtitleStyle(style: SubtitleStyleState) {
|
||||
|
|
@ -1325,6 +1339,8 @@ private class NuvioLibmpvView(
|
|||
label = label,
|
||||
language = language,
|
||||
isSelected = node.nodeBoolean("selected") ?: false,
|
||||
isExternal = node.nodeBoolean("external") == true ||
|
||||
node.nodeString("external-filename") != null,
|
||||
isForced = inferForcedSubtitleTrack(
|
||||
label = label,
|
||||
language = language,
|
||||
|
|
@ -1341,6 +1357,7 @@ private data class LibmpvTrack(
|
|||
val label: String,
|
||||
val language: String?,
|
||||
val isSelected: Boolean,
|
||||
val isExternal: Boolean,
|
||||
val isForced: Boolean,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ internal fun PlayerScreenRuntime.resetIdentityStateIfNeeded() {
|
|||
val videoIdentity = "$identity:$activeVideoId:$activeSeasonNumber:$activeEpisodeNumber"
|
||||
if (lastResetVideoIdentity != videoIdentity) {
|
||||
lastResetVideoIdentity = videoIdentity
|
||||
showSubtitleModal = false
|
||||
subtitleModalVideoId = null
|
||||
trackPreferenceRestoreApplied = false
|
||||
preferredSubtitleSelectionApplied = false
|
||||
selectedAddonSubtitleId = null
|
||||
|
|
|
|||
|
|
@ -426,6 +426,8 @@ private data class EpisodeResume(val positionMs: Long, val fraction: Float?)
|
|||
|
||||
private fun PlayerScreenRuntime.resetEpisodePanelAndNextEpisodeState() {
|
||||
showNextEpisodeCard = false
|
||||
showSubtitleModal = false
|
||||
subtitleModalVideoId = null
|
||||
showSourcesPanel = false
|
||||
showEpisodesPanel = false
|
||||
episodeStreamsPanelState = EpisodeStreamsPanelState()
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ internal class PlayerScreenRuntime(
|
|||
|
||||
var showAudioModal by mutableStateOf(false)
|
||||
var showSubtitleModal by mutableStateOf(false)
|
||||
var subtitleModalVideoId by mutableStateOf<String?>(null)
|
||||
var showVideoSettingsModal by mutableStateOf(false)
|
||||
var audioTracks by mutableStateOf<List<AudioTrack>>(emptyList())
|
||||
var subtitleTracks by mutableStateOf<List<SubtitleTrack>>(emptyList())
|
||||
|
|
|
|||
|
|
@ -76,13 +76,14 @@ internal fun PlayerScreenRuntime.persistAddonSubtitlePreference(subtitle: AddonS
|
|||
}
|
||||
}
|
||||
|
||||
internal fun PlayerScreenRuntime.restorePersistedTrackPreferenceIfNeeded() {
|
||||
if (trackPreferenceRestoreApplied) return
|
||||
internal fun PlayerScreenRuntime.restorePersistedTrackPreferenceIfNeeded(): Boolean {
|
||||
if (trackPreferenceRestoreApplied) return false
|
||||
val preference = PlayerTrackPreferenceStorage.load(parentMetaId)
|
||||
if (preference == null) {
|
||||
trackPreferenceRestoreApplied = true
|
||||
return
|
||||
return false
|
||||
}
|
||||
var subtitleTracksInvalidated = false
|
||||
|
||||
if (
|
||||
audioTracks.isNotEmpty() &&
|
||||
|
|
@ -100,11 +101,12 @@ internal fun PlayerScreenRuntime.restorePersistedTrackPreferenceIfNeeded() {
|
|||
|
||||
when (preference.subtitleType) {
|
||||
PersistedSubtitleSelectionType.DISABLED -> {
|
||||
playerController?.selectSubtitleTrack(-1)
|
||||
playerController?.clearExternalSubtitle()
|
||||
selectedSubtitleIndex = -1
|
||||
selectedAddonSubtitleId = null
|
||||
useCustomSubtitles = false
|
||||
preferredSubtitleSelectionApplied = true
|
||||
subtitleTracksInvalidated = true
|
||||
}
|
||||
PersistedSubtitleSelectionType.INTERNAL -> {
|
||||
if (subtitleTracks.isNotEmpty()) {
|
||||
|
|
@ -112,6 +114,7 @@ internal fun PlayerScreenRuntime.restorePersistedTrackPreferenceIfNeeded() {
|
|||
if (restoredSubtitleIndex >= 0) {
|
||||
if (useCustomSubtitles) {
|
||||
playerController?.clearExternalSubtitleAndSelect(restoredSubtitleIndex)
|
||||
subtitleTracksInvalidated = true
|
||||
} else {
|
||||
playerController?.selectSubtitleTrack(restoredSubtitleIndex)
|
||||
}
|
||||
|
|
@ -138,11 +141,13 @@ internal fun PlayerScreenRuntime.restorePersistedTrackPreferenceIfNeeded() {
|
|||
selectedAddonSubtitleId = null
|
||||
selectedSubtitleIndex = -1
|
||||
useCustomSubtitles = false
|
||||
subtitleTracksInvalidated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trackPreferenceRestoreApplied = true
|
||||
return subtitleTracksInvalidated
|
||||
}
|
||||
|
||||
internal fun PlayerScreenRuntime.refreshTracks() {
|
||||
|
|
@ -154,7 +159,10 @@ internal fun PlayerScreenRuntime.refreshTracks() {
|
|||
val selectedSub = subtitleTracks.firstOrNull { it.isSelected }
|
||||
if (selectedSub != null && !useCustomSubtitles) selectedSubtitleIndex = selectedSub.index
|
||||
|
||||
restorePersistedTrackPreferenceIfNeeded()
|
||||
if (restorePersistedTrackPreferenceIfNeeded()) {
|
||||
subtitleTracks = ctrl.getSubtitleTracks()
|
||||
selectedSubtitleIndex = subtitleTracks.firstOrNull { it.isSelected }?.index ?: -1
|
||||
}
|
||||
|
||||
val preferredAudioTargets = resolvePreferredAudioLanguageTargets(
|
||||
preferredAudioLanguage = playerSettingsUiState.preferredAudioLanguage,
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ private fun PlayerScreenRuntime.RenderPlayerControls(displayedPositionMs: Long,
|
|||
onSpeedClick = { cyclePlaybackSpeed() },
|
||||
onSubtitleClick = {
|
||||
refreshTracks()
|
||||
subtitleModalVideoId = playbackSession.videoId
|
||||
showSubtitleModal = true
|
||||
},
|
||||
onAudioClick = {
|
||||
|
|
@ -416,7 +417,10 @@ private fun PlayerScreenRuntime.RenderPlayerModals(displayedPositionMs: Long) {
|
|||
playerController?.selectSubtitleTrack(index)
|
||||
}
|
||||
},
|
||||
onAddonSubtitleSelected = { addon ->
|
||||
onAddonSubtitleSelected = selection@{ addon ->
|
||||
if (!isSubtitleModalSelectionCurrent(subtitleModalVideoId, playbackSession.videoId)) {
|
||||
return@selection
|
||||
}
|
||||
selectedAddonSubtitleId = addon.id
|
||||
selectedSubtitleIndex = -1
|
||||
useCustomSubtitles = true
|
||||
|
|
@ -430,7 +434,10 @@ private fun PlayerScreenRuntime.RenderPlayerModals(displayedPositionMs: Long) {
|
|||
onAutoSyncCapture = { captureSubtitleAutoSyncTime() },
|
||||
onAutoSyncCueSelected = { cue -> applySubtitleAutoSyncCue(cue) },
|
||||
onAutoSyncReload = { loadSubtitleAutoSyncCues(force = true) },
|
||||
onSubtitleModalDismissed = { showSubtitleModal = false },
|
||||
onSubtitleModalDismissed = {
|
||||
showSubtitleModal = false
|
||||
subtitleModalVideoId = null
|
||||
},
|
||||
showVideoSettingsModal = showVideoSettingsModal,
|
||||
playerSettings = playerSettingsUiState,
|
||||
onVideoSettingsChanged = {
|
||||
|
|
|
|||
|
|
@ -228,3 +228,8 @@ internal fun persistedAddonSubtitleUrlForVideo(
|
|||
if (persistedVideoId != videoId.takeIf { it.isNotBlank() }) return null
|
||||
return preference.addonSubtitleUrl?.takeIf { it.isNotBlank() }
|
||||
}
|
||||
|
||||
internal fun isSubtitleModalSelectionCurrent(
|
||||
modalVideoId: String?,
|
||||
currentVideoId: String,
|
||||
): Boolean = !modalVideoId.isNullOrBlank() && modalVideoId == currentVideoId
|
||||
|
|
|
|||
|
|
@ -266,6 +266,17 @@ class PlayerTrackSelectionTest {
|
|||
assertEquals(null, persistedAddonSubtitleUrlForVideo(preference, "series:1:1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subtitleModalSelectionIsAcceptedForTheSameEpisode() {
|
||||
assertEquals(true, isSubtitleModalSelectionCurrent("series:1:1", "series:1:1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subtitleModalSelectionIsRejectedAfterEpisodeChanges() {
|
||||
assertEquals(false, isSubtitleModalSelectionCurrent("series:1:1", "series:1:2"))
|
||||
assertEquals(false, isSubtitleModalSelectionCurrent(null, "series:1:2"))
|
||||
}
|
||||
|
||||
private fun subtitleTrack(
|
||||
index: Int,
|
||||
language: String?,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,20 @@ internal actual object PlatformLocalAccountDataCleaner {
|
|||
"collection_mobile_settings_payload",
|
||||
"collections_payload",
|
||||
)
|
||||
private val playerTrackPreferencePrefixes = listOf(
|
||||
"subtitle_type|",
|
||||
"subtitle_language|",
|
||||
"subtitle_name|",
|
||||
"subtitle_track_id|",
|
||||
"addon_subtitle_id|",
|
||||
"addon_subtitle_url|",
|
||||
"addon_subtitle_addon_name|",
|
||||
"addon_subtitle_video_id|",
|
||||
"audio_language|",
|
||||
"audio_name|",
|
||||
"audio_track_id|",
|
||||
"subtitle_delay_ms|",
|
||||
)
|
||||
|
||||
actual fun wipe() {
|
||||
val defaults = NSUserDefaults.standardUserDefaults
|
||||
|
|
@ -81,7 +95,8 @@ internal actual object PlatformLocalAccountDataCleaner {
|
|||
val keyString = key as? String ?: continue
|
||||
if (
|
||||
keyString.startsWith("stream_link_") ||
|
||||
keyString.startsWith("cw_enrichment_cache_")
|
||||
keyString.startsWith("cw_enrichment_cache_") ||
|
||||
playerTrackPreferencePrefixes.any(keyString::startsWith)
|
||||
) {
|
||||
defaults.removeObjectForKey(keyString)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue