Fix selecting subtitle tracks (#122)

Fixes subtitle track selection especially when there is a mix of
embedded & external subtitles

Fixes #116
This commit is contained in:
damontecres 2025-10-30 22:37:28 -04:00 committed by GitHub
parent c65831cb05
commit 799255814f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 6 deletions

View file

@ -348,6 +348,7 @@ fun PlaybackPage(
} }
PlaybackAction.SearchCaptions -> { PlaybackAction.SearchCaptions -> {
controllerViewState.hideControls()
viewModel.searchForSubtitles() viewModel.searchForSubtitles()
} }

View file

@ -479,7 +479,7 @@ class PlaybackViewModel
val externalSubtitle = val externalSubtitle =
source.mediaStreams source.mediaStreams
?.firstOrNull { it.type == MediaStreamType.SUBTITLE && it.index == subtitleIndex && it.isExternal } ?.firstOrNull { it.type == MediaStreamType.SUBTITLE && it.isExternal && it.index == subtitleIndex }
?.let { ?.let {
it.deliveryUrl?.let { deliveryUrl -> it.deliveryUrl?.let { deliveryUrl ->
var flags = 0 var flags = 0
@ -488,7 +488,7 @@ class PlaybackViewModel
MediaItem.SubtitleConfiguration MediaItem.SubtitleConfiguration
.Builder( .Builder(
api.createUrl(deliveryUrl).toUri(), api.createUrl(deliveryUrl).toUri(),
).setId("${it.index + 1}") // Indexes are 1 based ).setId("e:${it.index}")
.setMimeType(subtitleMimeTypes[it.codec]) .setMimeType(subtitleMimeTypes[it.codec])
.setLanguage(it.language) .setLanguage(it.language)
.setSelectionFlags(flags) .setSelectionFlags(flags)
@ -564,6 +564,7 @@ class PlaybackViewModel
audioIndex, audioIndex,
subtitleIndex, subtitleIndex,
externalSubtitleCount, externalSubtitleCount,
externalSubtitle != null,
) )
player.removeListener(this) player.removeListener(this)
} }
@ -871,7 +872,6 @@ class PlaybackViewModel
compareByDescending<RemoteSubtitleInfo> { it.communityRating } compareByDescending<RemoteSubtitleInfo> { it.communityRating }
.thenByDescending { it.downloadCount }, .thenByDescending { it.downloadCount },
) )
subtitleSearch.setValueOnMain(SubtitleSearch.Success(results)) subtitleSearch.setValueOnMain(SubtitleSearch.Success(results))
} }
} catch (ex: Exception) { } catch (ex: Exception) {
@ -902,9 +902,73 @@ class PlaybackViewModel
itemId = it.sourceId ?: it.itemId, itemId = it.sourceId ?: it.itemId,
subtitleId = subtitleId, subtitleId = subtitleId,
) )
val currentSubtitleStreams =
this@PlaybackViewModel
.subtitleStreams.value
.orEmpty()
val subtitleCount = currentSubtitleStreams.size
var newCount = subtitleCount
var maxAttempts = 4
var newStreams: List<SubtitleStream> = listOf()
// The server triggers a refresh in the background, so query periodically for the item until its updated
while (maxAttempts > 0 && subtitleCount == newCount) {
maxAttempts--
delay(1500)
dto = api.userLibraryApi.getItem(itemId = dto.id).content
val mediaSource = chooseSource(dto, it)
if (mediaSource == null) {
// This shouldn't happen, but just in case
showToast(
context,
"Item is no longer playable...",
Toast.LENGTH_SHORT,
)
return@launchIO
}
val subtitleStreams =
mediaSource.mediaStreams
?.filter { it.type == MediaStreamType.SUBTITLE }
.orEmpty()
newCount = subtitleStreams.size
if (subtitleCount != newCount) {
newStreams =
subtitleStreams.map {
SubtitleStream(
it.index,
it.language,
it.title,
it.codec,
it.codecTag,
it.isExternal,
it.isForced,
it.isDefault,
it.displayTitle,
)
}
this@PlaybackViewModel.subtitleStreams.setValueOnMain(newStreams)
}
}
if (maxAttempts == 0) {
showToast(
context,
"Download is taking a long time, you may need to restart playback",
)
} else {
// Find the new subtitle stream
val newStream =
newStreams
.toMutableList()
.apply {
removeAll(currentSubtitleStreams)
}.firstOrNull { it.external }
if (newStream != null) {
changeSubtitleStream(newStream.index).join()
}
}
subtitleSearch.setValueOnMain(null) subtitleSearch.setValueOnMain(null)
changeSubtitleStream(0).join()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (wasPlaying) { if (wasPlaying) {
player.play() player.play()
@ -966,18 +1030,21 @@ private fun applyTrackSelections(
audioIndex: Int?, audioIndex: Int?,
subtitleIndex: Int?, subtitleIndex: Int?,
externalSubtitleCount: Int, externalSubtitleCount: Int,
subtitleIsExternal: Boolean,
) { ) {
if (source.supportsDirectPlay) { if (source.supportsDirectPlay) {
if (subtitleIndex != null && subtitleIndex >= 0) { if (subtitleIndex != null && subtitleIndex >= 0) {
val indexToFind =
subtitleIndex + if (subtitleIsExternal) 0 else (if (externalSubtitleCount > 0) -1 else 1)
val chosenTrack = val chosenTrack =
player.currentTracks.groups.firstOrNull { group -> player.currentTracks.groups.firstOrNull { group ->
group.type == C.TRACK_TYPE_TEXT && group.isSupported && group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
(0..<group.mediaTrackGroup.length) (0..<group.mediaTrackGroup.length)
.map { .map {
group.getTrackFormat(it).idAsInt group.getTrackFormat(it).idAsInt
}.contains(subtitleIndex + 1) // Indexes are 1 based }.contains(indexToFind)
} }
Timber.v("Chosen subtitle track: $chosenTrack") Timber.v("Chosen subtitle ($subtitleIndex/$indexToFind) track: $chosenTrack")
chosenTrack?.let { chosenTrack?.let {
player.trackSelectionParameters = player.trackSelectionParameters =
player.trackSelectionParameters player.trackSelectionParameters