feat: add clearExternalSubtitleAndSelect method for subtitle track management

This commit is contained in:
tapframe 2026-03-12 00:00:12 +05:30
parent fee7fc61e5
commit 0ff8eb7a83
4 changed files with 114 additions and 8 deletions

View file

@ -1,6 +1,7 @@
package com.nuvio.app.features.player
import android.net.Uri
import android.util.Log
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@ -26,6 +27,8 @@ import androidx.media3.ui.PlayerView
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
private const val TAG = "NuvioPlayer"
@androidx.annotation.OptIn(UnstableApi::class)
@Composable
actual fun PlatformPlayerSurface(
@ -49,6 +52,8 @@ actual fun PlatformPlayerSurface(
}
}
val pendingSubtitleTrackIndex = remember { mutableListOf<Int>() }
DisposableEffect(exoPlayer) {
val listener = object : Player.Listener {
override fun onPlayerError(error: PlaybackException) {
@ -56,8 +61,17 @@ actual fun PlatformPlayerSurface(
}
override fun onPlaybackStateChanged(playbackState: Int) {
val stateName = when (playbackState) {
Player.STATE_IDLE -> "IDLE"
Player.STATE_BUFFERING -> "BUFFERING"
Player.STATE_READY -> "READY"
Player.STATE_ENDED -> "ENDED"
else -> "UNKNOWN($playbackState)"
}
Log.d(TAG, "onPlaybackStateChanged: $stateName")
if (playbackState == Player.STATE_READY) {
latestOnError.value(null)
exoPlayer.logCurrentTracks("STATE_READY")
}
latestOnSnapshot.value(exoPlayer.snapshot())
}
@ -69,6 +83,23 @@ actual fun PlatformPlayerSurface(
override fun onPlaybackParametersChanged(playbackParameters: androidx.media3.common.PlaybackParameters) {
latestOnSnapshot.value(exoPlayer.snapshot())
}
override fun onTracksChanged(tracks: androidx.media3.common.Tracks) {
Log.d(TAG, "onTracksChanged: ${tracks.groups.size} groups total")
exoPlayer.logCurrentTracks("onTracksChanged")
if (pendingSubtitleTrackIndex.isNotEmpty() && tracks.groups.isNotEmpty()) {
val idx = pendingSubtitleTrackIndex.removeAt(0)
Log.d(TAG, "onTracksChanged: applying pending subtitle selection index=$idx")
exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
.buildUpon()
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, idx < 0)
.build()
if (idx >= 0) {
exoPlayer.selectTrackByIndex(C.TRACK_TYPE_TEXT, idx)
}
}
latestOnSnapshot.value(exoPlayer.snapshot())
}
}
exoPlayer.addListener(listener)
onDispose {
@ -128,15 +159,23 @@ actual fun PlatformPlayerSurface(
override fun getAudioTracks(): List<AudioTrack> =
exoPlayer.extractAudioTracks()
override fun getSubtitleTracks(): List<SubtitleTrack> =
exoPlayer.extractSubtitleTracks()
override fun getSubtitleTracks(): List<SubtitleTrack> {
val tracks = exoPlayer.extractSubtitleTracks()
Log.d(TAG, "getSubtitleTracks: found ${tracks.size} tracks")
tracks.forEach { t ->
Log.d(TAG, " track idx=${t.index} id=${t.id} label='${t.label}' lang=${t.language} selected=${t.isSelected}")
}
return tracks
}
override fun selectAudioTrack(index: Int) {
exoPlayer.selectTrackByIndex(C.TRACK_TYPE_AUDIO, index)
}
override fun selectSubtitleTrack(index: Int) {
Log.d(TAG, "selectSubtitleTrack: index=$index")
if (index < 0) {
Log.d(TAG, "selectSubtitleTrack: disabling text tracks")
exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
.buildUpon()
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true)
@ -148,30 +187,45 @@ actual fun PlatformPlayerSurface(
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false)
.build()
exoPlayer.selectTrackByIndex(C.TRACK_TYPE_TEXT, index)
Log.d(TAG, "selectSubtitleTrack: after selection, textDisabled=${exoPlayer.trackSelectionParameters.disabledTrackTypes.contains(C.TRACK_TYPE_TEXT)}")
exoPlayer.logCurrentTracks("after selectSubtitleTrack")
}
override fun setSubtitleUri(url: String) {
Log.d(TAG, "setSubtitleUri: url=$url")
Log.d(TAG, "setSubtitleUri: mime=${guessSubtitleMime(url)}")
val currentPosition = exoPlayer.currentPosition
val wasPlaying = exoPlayer.isPlaying
val currentMediaItem = exoPlayer.currentMediaItem ?: return
val currentMediaItem = exoPlayer.currentMediaItem ?: run {
Log.e(TAG, "setSubtitleUri: currentMediaItem is null, aborting")
return
}
Log.d(TAG, "setSubtitleUri: currentPosition=$currentPosition, wasPlaying=$wasPlaying")
val subtitleConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(url))
.setMimeType(guessSubtitleMime(url))
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
.setRoleFlags(C.ROLE_FLAG_SUBTITLE)
.build()
Log.d(TAG, "setSubtitleUri: subtitleConfig built, uri=${subtitleConfig.uri}, mime=${subtitleConfig.mimeType}, selectionFlags=${subtitleConfig.selectionFlags}")
val newMediaItem = currentMediaItem.buildUpon()
.setSubtitleConfigurations(listOf(subtitleConfig))
.build()
exoPlayer.setMediaItem(newMediaItem, currentPosition)
exoPlayer.prepare()
exoPlayer.playWhenReady = wasPlaying
Log.d(TAG, "setSubtitleUri: newMediaItem subtitleConfigs count=${newMediaItem.localConfiguration?.subtitleConfigurations?.size}")
exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
.buildUpon()
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false)
.clearOverridesOfType(C.TRACK_TYPE_TEXT)
.setPreferredTextRoleFlags(C.ROLE_FLAG_SUBTITLE)
.build()
Log.d(TAG, "setSubtitleUri: track params set before prepare, textDisabled=${exoPlayer.trackSelectionParameters.disabledTrackTypes.contains(C.TRACK_TYPE_TEXT)}")
exoPlayer.setMediaItem(newMediaItem, currentPosition)
exoPlayer.prepare()
exoPlayer.playWhenReady = wasPlaying
Log.d(TAG, "setSubtitleUri: prepare() called, waiting for STATE_READY")
}
override fun clearExternalSubtitle() {
Log.d(TAG, "clearExternalSubtitle called")
val currentPosition = exoPlayer.currentPosition
val wasPlaying = exoPlayer.isPlaying
val currentMediaItem = exoPlayer.currentMediaItem ?: return
@ -181,6 +235,23 @@ actual fun PlatformPlayerSurface(
exoPlayer.setMediaItem(newMediaItem, currentPosition)
exoPlayer.prepare()
exoPlayer.playWhenReady = wasPlaying
Log.d(TAG, "clearExternalSubtitle: done, position=$currentPosition")
}
override fun clearExternalSubtitleAndSelect(trackIndex: Int) {
Log.d(TAG, "clearExternalSubtitleAndSelect: trackIndex=$trackIndex")
pendingSubtitleTrackIndex.clear()
pendingSubtitleTrackIndex.add(trackIndex)
val currentPosition = exoPlayer.currentPosition
val wasPlaying = exoPlayer.isPlaying
val currentMediaItem = exoPlayer.currentMediaItem ?: return
val newMediaItem = currentMediaItem.buildUpon()
.setSubtitleConfigurations(emptyList())
.build()
exoPlayer.setMediaItem(newMediaItem, currentPosition)
exoPlayer.prepare()
exoPlayer.playWhenReady = wasPlaying
Log.d(TAG, "clearExternalSubtitleAndSelect: done, pending=$trackIndex position=$currentPosition")
}
}
)
@ -271,20 +342,43 @@ private fun ExoPlayer.extractSubtitleTracks(): List<SubtitleTrack> {
}
private fun ExoPlayer.selectTrackByIndex(trackType: Int, targetIndex: Int) {
val typeName = if (trackType == C.TRACK_TYPE_AUDIO) "AUDIO" else "TEXT"
Log.d(TAG, "selectTrackByIndex: type=$typeName targetIndex=$targetIndex")
var idx = 0
for (group in currentTracks.groups) {
if (group.type != trackType) continue
if (idx == targetIndex) {
val format = group.mediaTrackGroup.getFormat(0)
Log.d(TAG, "selectTrackByIndex: found group at idx=$idx, format.id=${format.id}, lang=${format.language}, label=${format.label}")
trackSelectionParameters = trackSelectionParameters
.buildUpon()
.setOverrideForType(
TrackSelectionOverride(group.mediaTrackGroup, listOf(0))
)
.build()
Log.d(TAG, "selectTrackByIndex: override applied")
return
}
idx++
}
Log.w(TAG, "selectTrackByIndex: no group found for type=$typeName at index=$targetIndex (total groups scanned=$idx)")
}
private fun ExoPlayer.logCurrentTracks(context: String) {
Log.d(TAG, "--- logCurrentTracks ($context) ---")
Log.d(TAG, " textDisabled=${trackSelectionParameters.disabledTrackTypes.contains(C.TRACK_TYPE_TEXT)}")
for (group in currentTracks.groups) {
val typeName = when (group.type) {
C.TRACK_TYPE_AUDIO -> "AUDIO"
C.TRACK_TYPE_TEXT -> "TEXT"
C.TRACK_TYPE_VIDEO -> "VIDEO"
else -> "OTHER(${group.type})"
}
if (group.type != C.TRACK_TYPE_TEXT && group.type != C.TRACK_TYPE_AUDIO) continue
val format = group.mediaTrackGroup.getFormat(0)
Log.d(TAG, " group type=$typeName id=${format.id} lang=${format.language} label=${format.label} selected=${group.isSelected} supported=${group.isSupported}")
}
Log.d(TAG, "--- end logCurrentTracks ---")
}
private fun guessSubtitleMime(url: String): String {

View file

@ -16,6 +16,7 @@ interface PlayerEngineController {
fun selectSubtitleTrack(index: Int)
fun setSubtitleUri(url: String)
fun clearExternalSubtitle()
fun clearExternalSubtitleAndSelect(trackIndex: Int)
}
@Composable

View file

@ -103,7 +103,10 @@ fun PlayerScreen(
val selectedAudio = audioTracks.firstOrNull { it.isSelected }
if (selectedAudio != null) selectedAudioIndex = selectedAudio.index
val selectedSub = subtitleTracks.firstOrNull { it.isSelected }
println("NuvioPlayer refreshTracks: useCustom=$useCustomSubtitles selectedAddonId=$selectedAddonSubtitleId selectedSubIdx=$selectedSubtitleIndex")
println("NuvioPlayer refreshTracks: found ${subtitleTracks.size} subtitle tracks, selectedTrack=${selectedSub?.index}")
if (selectedSub != null && !useCustomSubtitles) selectedSubtitleIndex = selectedSub.index
println("NuvioPlayer refreshTracks: final selectedSubtitleIndex=$selectedSubtitleIndex")
}
fun showGestureMessage(message: String) {
@ -357,16 +360,23 @@ fun PlayerScreen(
subtitleStyle = subtitleStyle,
onTabSelected = { activeSubtitleTab = it },
onBuiltInTrackSelected = { index ->
println("NuvioPlayer onBuiltInTrackSelected: index=$index wasCustom=$useCustomSubtitles")
val wasCustom = useCustomSubtitles
selectedSubtitleIndex = index
selectedAddonSubtitleId = null
useCustomSubtitles = false
playerController?.clearExternalSubtitle()
playerController?.selectSubtitleTrack(index)
if (wasCustom) {
playerController?.clearExternalSubtitleAndSelect(index)
} else {
playerController?.selectSubtitleTrack(index)
}
},
onAddonSubtitleSelected = { addon ->
println("NuvioPlayer onAddonSubtitleSelected: id=${addon.id} url=${addon.url} lang=${addon.language}")
selectedAddonSubtitleId = addon.id
selectedSubtitleIndex = -1
useCustomSubtitles = true
println("NuvioPlayer onAddonSubtitleSelected: set useCustomSubtitles=true, calling setSubtitleUri")
playerController?.setSubtitleUri(addon.url)
},
onFetchAddonSubtitles = {

View file

@ -33,6 +33,7 @@ actual fun PlatformPlayerSurface(
override fun selectSubtitleTrack(index: Int) = Unit
override fun setSubtitleUri(url: String) = Unit
override fun clearExternalSubtitle() = Unit
override fun clearExternalSubtitleAndSelect(trackIndex: Int) = Unit
}
}