Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ package com.github.damontecres.wholphin.ui.playback

import androidx.annotation.OptIn
import androidx.media3.common.C
import androidx.media3.common.Format
import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.TrackSelectionParameters
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
import org.jellyfin.sdk.model.api.MediaSourceInfo
import org.jellyfin.sdk.model.api.MediaStream
import org.jellyfin.sdk.model.api.MediaStreamType
import org.jellyfin.sdk.model.api.SubtitleDeliveryMethod
import timber.log.Timber
import kotlin.math.max

object TrackSelectionUtils {
@OptIn(UnstableApi::class)
Expand All @@ -26,78 +25,71 @@ object TrackSelectionUtils {
subtitleIndex: Int?,
source: MediaSourceInfo,
): TrackSelectionResult {
val embeddedSubtitleCount = source.embeddedSubtitleCount
val externalSubtitleCount = source.externalSubtitlesCount
// This function's implementation assumes that indexes for each MediaStream in the MediaSourceInfo
// will be ordered as: external subtitles, video stream, audio stream, embedded subtitles

val paramsBuilder = trackSelectionParams.buildUpon()
val groups = tracks.groups

val subtitleSelected =
if (subtitleIndex != null && subtitleIndex >= 0) {
val subtitleIsExternal = source.findExternalSubtitle(subtitleIndex) != null
if (subtitleIsExternal || supportsDirectPlay) {
val chosenTrack =
if (subtitleIsExternal && playerBackend == PlayerBackend.EXO_PLAYER) {
val chosenTrack =
if (subtitleIsExternal) {
// If external, only one external track should exist, so just find it
val group =
groups.firstOrNull { group ->
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
(0..<group.mediaTrackGroup.length)
.mapNotNull {
group.getTrackFormat(it).id
}.any { it.endsWith("e:$subtitleIndex") }
.any {
group.getTrackFormat(0).id?.contains("e:") == true
}
}
} else {
val actualEmbeddedCount =
groups
.filter { group ->
group.type == C.TRACK_TYPE_TEXT &&
(0..<group.mediaTrackGroup.length)
.mapNotNull {
group.getTrackFormat(it).id
}.none { it.contains("e:") }
}.size
val indexToFind =
calculateIndexToFind(
subtitleIndex,
MediaStreamType.SUBTITLE,
playerBackend,
embeddedSubtitleCount,
externalSubtitleCount,
subtitleIsExternal,
actualEmbeddedCount,
source,
)
Timber.v("Chosen subtitle ($subtitleIndex/$indexToFind) track")
// subtitleIndex - externalSubtitleCount + 1
groups.firstOrNull { group ->
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
(0..<group.mediaTrackGroup.length)
.filter {
if (subtitleIsExternal) {
Timber.v(
"Chosen external subtitle ($subtitleIndex/) track: %s",
group,
)
group
} else {
// Not external
// Find the first index of a embedded subtitle to use as an offset for desired subtitle index
val firstEmbeddedSubtitleIndex =
source.mediaStreams
.orEmpty()
.indexOfFirstOrNull {
it.type == MediaStreamType.SUBTITLE &&
!(it.deliveryMethod == SubtitleDeliveryMethod.EXTERNAL || it.isExternal)
}
if (firstEmbeddedSubtitleIndex != null) {
val indexToFind = subtitleIndex - firstEmbeddedSubtitleIndex
val subtitleTracks =
groups.filter { group ->
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
(0..<group.mediaTrackGroup.length)
.none {
group.getTrackFormat(0).id?.contains("e:") == true
} else {
group.getTrackFormat(0).id?.contains("e:") == false
}
}.map {
group.getTrackFormat(it).idAsInt
}.contains(indexToFind)
}
}

Timber.v("Chosen subtitle ($subtitleIndex) track: $chosenTrack")
chosenTrack?.let {
paramsBuilder
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false)
.setOverrideForType(
TrackSelectionOverride(
chosenTrack.mediaTrackGroup,
0,
),
}
Timber.v(
"Chosen eembedded subtitle ($subtitleIndex/$indexToFind) track: %s",
subtitleTracks.getOrNull(indexToFind),
)
subtitleTracks.getOrNull(indexToFind)
} else {
null
}
}
chosenTrack != null
} else {
false
chosenTrack?.let {
paramsBuilder
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, false)
.setOverrideForType(
TrackSelectionOverride(
chosenTrack.mediaTrackGroup,
0,
),
)
}
chosenTrack != null
} else {
paramsBuilder
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, true)
Expand All @@ -106,26 +98,24 @@ object TrackSelectionUtils {
}
val audioSelected =
if (audioIndex != null && supportsDirectPlay) {
val indexToFind =
calculateIndexToFind(
audioIndex,
MediaStreamType.AUDIO,
playerBackend,
embeddedSubtitleCount,
externalSubtitleCount,
false,
null,
source,
)
// Find the first index of an audio stream to use as an offset for desired index
val firstAudioIndex =
source.mediaStreams
.orEmpty()
.indexOfFirstOrNull { it.type == MediaStreamType.AUDIO }
val chosenTrack =
groups.firstOrNull { group ->
group.type == C.TRACK_TYPE_AUDIO && group.isSupported &&
(0..<group.mediaTrackGroup.length)
.map {
group.getTrackFormat(it).idAsInt
}.contains(indexToFind)
if (firstAudioIndex != null) {
val indexToFind = audioIndex - firstAudioIndex
val audioGroups =
groups.filter { group -> group.type == C.TRACK_TYPE_AUDIO && group.isSupported }
Timber.v(
"Chosen audio ($audioIndex/$indexToFind) track: %s",
audioGroups.getOrNull(indexToFind),
)
audioGroups.getOrNull(indexToFind)
} else {
null
}
Timber.v("Chosen audio ($audioIndex/$indexToFind) track: $chosenTrack")
chosenTrack?.let {
paramsBuilder
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, false)
Expand All @@ -142,76 +132,8 @@ object TrackSelectionUtils {
}
return TrackSelectionResult(paramsBuilder.build(), audioSelected, subtitleSelected)
}

/**
* Maps the server provided index to the track index based on the [PlayerBackend] and other stream information
*/
private fun calculateIndexToFind(
serverIndex: Int,
type: MediaStreamType,
playerBackend: PlayerBackend,
embeddedSubtitleCount: Int,
externalSubtitleCount: Int,
subtitleIsExternal: Boolean,
actualEmbeddedCount: Int?,
source: MediaSourceInfo,
): Int =
when (playerBackend) {
PlayerBackend.EXO_PLAYER,
PlayerBackend.UNRECOGNIZED,
-> {
serverIndex - externalSubtitleCount + 1
}

// TODO MPV could use literal indexes because they are stored in the track format ID
PlayerBackend.PREFER_MPV,
PlayerBackend.MPV,
-> {
when (type) {
MediaStreamType.VIDEO -> {
serverIndex - externalSubtitleCount + 1
}

MediaStreamType.AUDIO -> {
val videoStreamsBeforeAudioCount =
source.mediaStreams
.orEmpty()
.indexOfFirst { it.type == MediaStreamType.AUDIO } - externalSubtitleCount
serverIndex - externalSubtitleCount - videoStreamsBeforeAudioCount + 1
}

MediaStreamType.SUBTITLE -> {
if (subtitleIsExternal) {
// Need to account for the actual embedded count because if the library
// disables embedded subtitles, they still exist in the direct played file,
// but not included in the MediaStreams list
serverIndex + max(actualEmbeddedCount ?: 0, embeddedSubtitleCount) + 1
} else {
val videoStreamCount = source.videoStreamCount
val audioStreamCount = source.audioStreamCount
serverIndex - externalSubtitleCount - videoStreamCount - audioStreamCount + 1
}
}

else -> {
throw UnsupportedOperationException("Cannot calculate index for $type")
}
}
}
}
}

val Format.idAsInt: Int?
@OptIn(UnstableApi::class)
get() =
id?.let {
if (it.contains(":")) {
it.split(":").last().toIntOrNull()
} else {
it.toIntOrNull()
}
}

/**
* Returns the number of external subtitle streams there are
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import androidx.media3.common.Format
import androidx.media3.common.MimeTypes
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import com.github.damontecres.wholphin.ui.playback.idAsInt
import timber.log.Timber
import java.util.Locale

Expand Down Expand Up @@ -159,7 +158,7 @@ fun checkForSupport(tracks: Tracks): List<TrackSupport> =
val reason = TrackSupportReason.fromInt(it.getTrackSupport(i))
add(
TrackSupport(
format.id + " (${format.idAsInt})",
format.id,
type,
reason,
it.isSelected,
Expand Down
Loading
Loading