Handle external subtitles

This commit is contained in:
Damontecres 2025-10-04 20:25:43 -04:00
parent 64e881f51c
commit 81d0279cce
No known key found for this signature in database
3 changed files with 60 additions and 5 deletions

View file

@ -6,6 +6,7 @@ data class SubtitleStream(
val title: String?, val title: String?,
val codec: String?, val codec: String?,
val codecTag: String?, val codecTag: String?,
val external: Boolean,
) { ) {
val displayName: String val displayName: String
get() = get() =

View file

@ -22,6 +22,7 @@ import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener
import com.github.damontecres.dolphin.util.TrackSupport import com.github.damontecres.dolphin.util.TrackSupport
import com.github.damontecres.dolphin.util.checkForSupport import com.github.damontecres.dolphin.util.checkForSupport
import com.github.damontecres.dolphin.util.profile.PlaybackListener import com.github.damontecres.dolphin.util.profile.PlaybackListener
import com.github.damontecres.dolphin.util.subtitleMimeTypes
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -142,7 +143,14 @@ class PlaybackViewModel
base.mediaStreams base.mediaStreams
?.filter { it.type == MediaStreamType.SUBTITLE } ?.filter { it.type == MediaStreamType.SUBTITLE }
?.map { ?.map {
SubtitleStream(it.index, it.language, it.title, it.codec, it.codecTag) SubtitleStream(
it.index,
it.language,
it.title,
it.codec,
it.codecTag,
it.isExternal,
)
}.orEmpty() }.orEmpty()
val audioStreams = val audioStreams =
base.mediaStreams base.mediaStreams
@ -226,7 +234,7 @@ class PlaybackViewModel
allowAudioStreamCopy = true, allowAudioStreamCopy = true,
autoOpenLiveStream = true, autoOpenLiveStream = true,
mediaSourceId = null, mediaSourceId = null,
alwaysBurnInSubtitleWhenTranscoding = false, alwaysBurnInSubtitleWhenTranscoding = null,
), ),
).content ).content
val source = response.mediaSources.firstOrNull() val source = response.mediaSources.firstOrNull()
@ -254,6 +262,33 @@ class PlaybackViewModel
val decision = StreamDecision(itemId, transcodeType, mediaUrl) val decision = StreamDecision(itemId, transcodeType, mediaUrl)
Timber.v("Playback decision: $decision") Timber.v("Playback decision: $decision")
val externalSubtitle =
source.mediaStreams
?.firstOrNull { it.type == MediaStreamType.SUBTITLE && it.index == subtitleIndex && it.isExternal }
?.let {
it.deliveryUrl?.let { deliveryUrl ->
var flags = 0
if (it.isForced) flags = flags.and(C.SELECTION_FLAG_FORCED)
if (it.isDefault) flags = flags.and(C.SELECTION_FLAG_DEFAULT)
MediaItem.SubtitleConfiguration
.Builder(
api.createUrl(deliveryUrl).toUri(),
).setId("${it.index + 1}") // Indexes are 1 based
.setMimeType(subtitleMimeTypes[it.codec])
.setLanguage(it.language)
.setSelectionFlags(flags)
.build()
}
}
val mediaItem =
MediaItem
.Builder()
.setMediaId(itemId.toString())
.setUri(mediaUrl.toUri())
.setSubtitleConfigurations(listOfNotNull(externalSubtitle))
.build()
val playback = val playback =
CurrentPlayback( CurrentPlayback(
itemId, itemId,
@ -268,7 +303,7 @@ class PlaybackViewModel
stream.value = decision stream.value = decision
currentPlayback.value = playback currentPlayback.value = playback
player.setMediaItem( player.setMediaItem(
decision.mediaItem, mediaItem,
positionMs, positionMs,
) )
if (audioIndex != null || subtitleIndex != null) { if (audioIndex != null || subtitleIndex != null) {
@ -377,7 +412,7 @@ private fun applyTrackSelections(
(0..<group.mediaTrackGroup.length) (0..<group.mediaTrackGroup.length)
.map { .map {
group.getTrackFormat(it).idAsInt group.getTrackFormat(it).idAsInt
}.contains(subtitleIndex + 1) }.contains(subtitleIndex + 1) // Indexes are 1 based
} }
Timber.v("Chosen subtitle track: $chosenTrack") Timber.v("Chosen subtitle track: $chosenTrack")
chosenTrack?.let { chosenTrack?.let {
@ -406,7 +441,7 @@ private fun applyTrackSelections(
(0..<group.mediaTrackGroup.length) (0..<group.mediaTrackGroup.length)
.map { .map {
group.getTrackFormat(it).idAsInt group.getTrackFormat(it).idAsInt
}.contains(audioIndex + 1) }.contains(audioIndex + 1) // Indexes are 1 based
} }
Timber.v("Chosen audio track: $chosenTrack") Timber.v("Chosen audio track: $chosenTrack")
chosenTrack?.let { chosenTrack?.let {

View file

@ -0,0 +1,19 @@
package com.github.damontecres.dolphin.util
import androidx.media3.common.MimeTypes
import com.github.damontecres.dolphin.util.profile.Codec
val subtitleMimeTypes =
mapOf(
Codec.Subtitle.ASS to MimeTypes.TEXT_SSA,
Codec.Subtitle.DVBSUB to MimeTypes.APPLICATION_VOBSUB,
Codec.Subtitle.DVBSUB to MimeTypes.APPLICATION_VOBSUB,
Codec.Subtitle.PGS to MimeTypes.APPLICATION_PGS,
Codec.Subtitle.PGSSUB to MimeTypes.APPLICATION_PGS,
Codec.Subtitle.SRT to MimeTypes.APPLICATION_SUBRIP,
Codec.Subtitle.SSA to MimeTypes.TEXT_SSA,
Codec.Subtitle.SUBRIP to MimeTypes.APPLICATION_SUBRIP,
Codec.Subtitle.TTML to MimeTypes.APPLICATION_TTML,
Codec.Subtitle.VTT to MimeTypes.TEXT_VTT,
Codec.Subtitle.WEBVTT to MimeTypes.TEXT_VTT,
)