fix: ios player not recreating after switching source/episode

This commit is contained in:
tapframe 2026-05-02 14:05:47 +05:30
parent c890cc3400
commit 4f62be91b8

View file

@ -34,180 +34,182 @@ actual fun PlatformPlayerSurface(
onError: (String?) -> Unit, onError: (String?) -> Unit,
) { ) {
sanitizePlaybackResponseHeaders(sourceResponseHeaders) sanitizePlaybackResponseHeaders(sourceResponseHeaders)
val latestOnControllerReady = rememberUpdatedState(onControllerReady)
val latestOnSnapshot = rememberUpdatedState(onSnapshot) val latestOnSnapshot = rememberUpdatedState(onSnapshot)
val latestOnError = rememberUpdatedState(onError) val latestOnError = rememberUpdatedState(onError)
val bridge = remember(sourceUrl) { val bridge = remember {
NuvioPlayerBridgeFactory.create() NuvioPlayerBridgeFactory.create()
} }
if (bridge == null) { if (bridge == null) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
onError("MPV player engine not available. Please rebuild the app.") latestOnError.value("MPV player engine not available. Please rebuild the app.")
} }
return return
} }
// Create controller val controller = remember(bridge) {
LaunchedEffect(bridge) { object : PlayerEngineController {
onControllerReady( override fun play() {
object : PlayerEngineController { bridge.play()
override fun play() { }
bridge.play()
}
override fun pause() { override fun pause() {
bridge.pause() bridge.pause()
} }
override fun seekTo(positionMs: Long) { override fun seekTo(positionMs: Long) {
bridge.seekTo(positionMs) bridge.seekTo(positionMs)
} }
override fun seekBy(offsetMs: Long) { override fun seekBy(offsetMs: Long) {
bridge.seekBy(offsetMs) bridge.seekBy(offsetMs)
} }
override fun retry() { override fun retry() {
bridge.retry() bridge.retry()
} }
override fun setPlaybackSpeed(speed: Float) { override fun setPlaybackSpeed(speed: Float) {
bridge.setPlaybackSpeed(speed) bridge.setPlaybackSpeed(speed)
} }
override fun getAudioTracks(): List<AudioTrack> { override fun getAudioTracks(): List<AudioTrack> {
val count = bridge.getAudioTrackCount() val count = bridge.getAudioTrackCount()
return (0 until count).map { i -> return (0 until count).map { i ->
AudioTrack( AudioTrack(
index = bridge.getAudioTrackIndex(i), index = bridge.getAudioTrackIndex(i),
id = bridge.getAudioTrackId(i), id = bridge.getAudioTrackId(i),
label = bridge.getAudioTrackLabel(i), label = bridge.getAudioTrackLabel(i),
language = bridge.getAudioTrackLang(i), language = bridge.getAudioTrackLang(i),
isSelected = bridge.isAudioTrackSelected(i), isSelected = bridge.isAudioTrackSelected(i),
) )
}
} }
}
override fun getSubtitleTracks(): List<SubtitleTrack> { override fun getSubtitleTracks(): List<SubtitleTrack> {
val count = bridge.getSubtitleTrackCount() val count = bridge.getSubtitleTrackCount()
val tracks = (0 until count).map { i -> val tracks = (0 until count).map { i ->
val trackId = bridge.getSubtitleTrackId(i) val trackId = bridge.getSubtitleTrackId(i)
val trackLabel = bridge.getSubtitleTrackLabel(i) val trackLabel = bridge.getSubtitleTrackLabel(i)
val trackLanguage = bridge.getSubtitleTrackLang(i) val trackLanguage = bridge.getSubtitleTrackLang(i)
SubtitleTrack( SubtitleTrack(
index = bridge.getSubtitleTrackIndex(i), index = bridge.getSubtitleTrackIndex(i),
id = trackId, id = trackId,
label = trackLabel,
language = trackLanguage,
isSelected = bridge.isSubtitleTrackSelected(i),
isForced = inferForcedSubtitleTrack(
label = trackLabel, label = trackLabel,
language = trackLanguage, language = trackLanguage,
isSelected = bridge.isSubtitleTrackSelected(i), trackId = trackId,
isForced = inferForcedSubtitleTrack( ),
label = trackLabel, )
language = trackLanguage,
trackId = trackId,
),
)
}
Logger.d(TAG) { "getSubtitleTracks: found ${tracks.size} tracks" }
return tracks
} }
Logger.d(TAG) { "getSubtitleTracks: found ${tracks.size} tracks" }
return tracks
}
override fun selectAudioTrack(index: Int) { override fun selectAudioTrack(index: Int) {
// Convert from logical track index to mpv track id // Convert from logical track index to mpv track id
val count = bridge.getAudioTrackCount() val count = bridge.getAudioTrackCount()
if (count <= 0) return
val trackId = (0 until count)
.firstNotNullOfOrNull { at ->
if (bridge.getAudioTrackIndex(at) == index) {
bridge.getAudioTrackId(at).toIntOrNull()
} else {
null
}
}
?: if (index in 0 until count) {
bridge.getAudioTrackId(index).toIntOrNull() ?: (index + 1)
} else {
null
}
if (trackId != null) {
bridge.selectAudioTrack(trackId)
}
}
override fun selectSubtitleTrack(index: Int) {
if (index < 0) {
bridge.selectSubtitleTrack(-1) // disable
} else {
val count = bridge.getSubtitleTrackCount()
if (count <= 0) return if (count <= 0) return
val trackId = (0 until count) val trackId = (0 until count)
.firstNotNullOfOrNull { at -> .firstNotNullOfOrNull { at ->
if (bridge.getAudioTrackIndex(at) == index) { if (bridge.getSubtitleTrackIndex(at) == index) {
bridge.getAudioTrackId(at).toIntOrNull() bridge.getSubtitleTrackId(at).toIntOrNull()
} else { } else {
null null
} }
} }
?: if (index in 0 until count) { ?: if (index in 0 until count) {
bridge.getAudioTrackId(index).toIntOrNull() ?: (index + 1) bridge.getSubtitleTrackId(index).toIntOrNull() ?: (index + 1)
} else { } else {
null null
} }
if (trackId != null) { if (trackId != null) {
bridge.selectAudioTrack(trackId) bridge.selectSubtitleTrack(trackId)
} }
} }
}
override fun selectSubtitleTrack(index: Int) { override fun setSubtitleUri(url: String) {
if (index < 0) { Logger.d(TAG) { "setSubtitleUri: $url" }
bridge.selectSubtitleTrack(-1) // disable bridge.setSubtitleUrl(url)
}
override fun clearExternalSubtitle() {
bridge.clearExternalSubtitle()
}
override fun clearExternalSubtitleAndSelect(trackIndex: Int) {
val trackId = if (trackIndex < 0) {
-1
} else {
val count = bridge.getSubtitleTrackCount()
if (count <= 0) {
trackIndex + 1
} else { } else {
val count = bridge.getSubtitleTrackCount() (0 until count)
if (count <= 0) return
val trackId = (0 until count)
.firstNotNullOfOrNull { at -> .firstNotNullOfOrNull { at ->
if (bridge.getSubtitleTrackIndex(at) == index) { if (bridge.getSubtitleTrackIndex(at) == trackIndex) {
bridge.getSubtitleTrackId(at).toIntOrNull() bridge.getSubtitleTrackId(at).toIntOrNull()
} else { } else {
null null
} }
} }
?: if (index in 0 until count) { ?: if (trackIndex in 0 until count) {
bridge.getSubtitleTrackId(index).toIntOrNull() ?: (index + 1) bridge.getSubtitleTrackId(trackIndex).toIntOrNull() ?: (trackIndex + 1)
} else { } else {
null trackIndex + 1
} }
if (trackId != null) {
bridge.selectSubtitleTrack(trackId)
}
} }
} }
bridge.clearExternalSubtitleAndSelect(trackId)
override fun setSubtitleUri(url: String) {
Logger.d(TAG) { "setSubtitleUri: $url" }
bridge.setSubtitleUrl(url)
}
override fun clearExternalSubtitle() {
bridge.clearExternalSubtitle()
}
override fun clearExternalSubtitleAndSelect(trackIndex: Int) {
val trackId = if (trackIndex < 0) {
-1
} else {
val count = bridge.getSubtitleTrackCount()
if (count <= 0) {
trackIndex + 1
} else {
(0 until count)
.firstNotNullOfOrNull { at ->
if (bridge.getSubtitleTrackIndex(at) == trackIndex) {
bridge.getSubtitleTrackId(at).toIntOrNull()
} else {
null
}
}
?: if (trackIndex in 0 until count) {
bridge.getSubtitleTrackId(trackIndex).toIntOrNull() ?: (trackIndex + 1)
} else {
trackIndex + 1
}
}
}
bridge.clearExternalSubtitleAndSelect(trackId)
}
override fun applySubtitleStyle(style: SubtitleStyleState) {
bridge.applySubtitleStyle(
textColor = style.textColor.toMpvColorString(),
outlineSize = if (style.outlineEnabled) 1.65f else 0f,
fontSize = style.toMpvSubtitleFontSize(),
subPos = style.toMpvSubtitlePosition(),
)
}
} }
)
override fun applySubtitleStyle(style: SubtitleStyleState) {
bridge.applySubtitleStyle(
textColor = style.textColor.toMpvColorString(),
outlineSize = if (style.outlineEnabled) 1.65f else 0f,
fontSize = style.toMpvSubtitleFontSize(),
subPos = style.toMpvSubtitlePosition(),
)
}
}
}
LaunchedEffect(controller, sourceUrl, sourceAudioUrl, sourceHeaders, sourceResponseHeaders) {
latestOnControllerReady.value(controller)
} }
// Load file and set initial state // Load file and set initial state