Calculate track indices for mpv

This commit is contained in:
Damontecres 2025-11-06 13:08:18 -05:00
parent 3a4a22ec94
commit 0597e93178
No known key found for this signature in database
2 changed files with 127 additions and 21 deletions

View file

@ -424,12 +424,14 @@ class PlaybackViewModel
enableDirectStream: Boolean = true,
) = withContext(Dispatchers.IO) {
val itemId = item.id
val playerBackend = preferences.appPreferences.playbackPreferences.playerBackend
val currentPlayback = this@PlaybackViewModel.currentPlayback.value
if (currentPlayback != null && currentPlayback.playMethod == PlayMethod.DIRECT_PLAY) {
// If direct playing, can try to switch tracks without playback restarting
// Except for external subtitles
// TODO there's probably no reason why we can't add external subtitles?
Timber.v("changeStreams direct play")
val source = currentPlayback.mediaSourceInfo
val externalSubtitleCount = source.externalSubtitlesCount
@ -439,14 +441,18 @@ class PlaybackViewModel
withContext(Dispatchers.Main) {
applyTrackSelections(
player,
playerBackend,
true,
audioIndex,
subtitleIndex,
source.videoStreamCount,
source.audioStreamCount,
externalSubtitleCount,
externalSubtitle != null,
)
}
if (result.bothSelected) {
// TODO lots of duplicate code in this block
Timber.d("Changes tracks audio=$audioIndex, subtitle=$subtitleIndex")
val itemPlayback =
currentItemPlayback.copy(
@ -489,7 +495,6 @@ class PlaybackViewModel
"changeStreams: userInitiated=$userInitiated, audioIndex=$audioIndex, subtitleIndex=$subtitleIndex, " +
"enableDirectPlay=$enableDirectPlay, enableDirectStream=$enableDirectStream",
)
val playerBackend = preferences.appPreferences.playbackPreferences.playerBackend
// TODO if the new audio or subtitle index is already in the streams (eg direct play), should toggle in the player instead
val maxBitrate =
preferences.appPreferences.playbackPreferences.maxBitrate
@ -618,15 +623,16 @@ class PlaybackViewModel
player.removeListener(it)
}
val activityListener =
TrackActivityPlaybackListener(
api = api,
player = player,
playback = playback,
itemPlayback = itemPlayback,
)
player.addListener(activityListener)
this@PlaybackViewModel.activityListener = activityListener
// TODO temporarily disable
// val activityListener =
// TrackActivityPlaybackListener(
// api = api,
// player = player,
// playback = playback,
// itemPlayback = itemPlayback,
// )
// player.addListener(activityListener)
// this@PlaybackViewModel.activityListener = activityListener
duration.value = source.runTimeTicks?.ticks
loading.value = LoadingState.Success
@ -644,9 +650,12 @@ class PlaybackViewModel
if (tracks.groups.isNotEmpty()) {
applyTrackSelections(
player,
playerBackend,
source.supportsDirectPlay,
audioIndex,
subtitleIndex,
source.videoStreamCount,
source.audioStreamCount,
externalSubtitleCount,
externalSubtitle != null,
)
@ -1143,6 +1152,22 @@ val MediaSourceInfo.externalSubtitlesCount: Int
mediaStreams
?.count { it.type == MediaStreamType.SUBTITLE && it.isExternal } ?: 0
/**
* Returns the number of video streams there are
*/
val MediaSourceInfo.videoStreamCount: Int
get() =
mediaStreams
?.count { it.type == MediaStreamType.VIDEO } ?: 0
/**
* Returns the number of audio streams there are
*/
val MediaSourceInfo.audioStreamCount: Int
get() =
mediaStreams
?.count { it.type == MediaStreamType.AUDIO } ?: 0
/**
* Returns the [MediaStream] for the given subtitle index iff it is external
*/
@ -1164,9 +1189,12 @@ data class TrackSelectionResult(
@OptIn(UnstableApi::class)
private fun applyTrackSelections(
player: Player,
playerBackend: PlayerBackend,
supportsDirectPlay: Boolean,
audioIndex: Int?,
subtitleIndex: Int?,
videoStreamCount: Int,
audioStreamCount: Int,
externalSubtitleCount: Int,
subtitleIsExternal: Boolean,
): TrackSelectionResult {
@ -1182,7 +1210,18 @@ private fun applyTrackSelections(
}.any { it.endsWith("e:$subtitleIndex") }
}
} else {
val indexToFind = subtitleIndex - externalSubtitleCount + 1
// TODO this might be incorrect
val indexToFind =
calculateIndexToFind(
subtitleIndex,
MediaStreamType.SUBTITLE,
playerBackend,
videoStreamCount,
audioStreamCount,
externalSubtitleCount,
)
Timber.v("Chosen subtitle ($subtitleIndex/$indexToFind) track")
// subtitleIndex - externalSubtitleCount + 1
player.currentTracks.groups.firstOrNull { group ->
group.type == C.TRACK_TYPE_TEXT && group.isSupported &&
(0..<group.mediaTrackGroup.length)
@ -1217,7 +1256,14 @@ private fun applyTrackSelections(
val audioSelected =
if (audioIndex != null && supportsDirectPlay) {
val indexToFind =
audioIndex - externalSubtitleCount + 1
calculateIndexToFind(
audioIndex,
MediaStreamType.AUDIO,
playerBackend,
videoStreamCount,
audioStreamCount,
externalSubtitleCount,
)
val chosenTrack =
player.currentTracks.groups.firstOrNull { group ->
group.type == C.TRACK_TYPE_AUDIO && group.isSupported &&
@ -1245,3 +1291,32 @@ private fun applyTrackSelections(
}
return TrackSelectionResult(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,
videoStreamCount: Int,
audioStreamCount: Int,
externalSubtitleCount: Int,
): 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.MPV -> {
when (type) {
MediaStreamType.VIDEO -> serverIndex - externalSubtitleCount + 1
MediaStreamType.AUDIO -> serverIndex - externalSubtitleCount - videoStreamCount + 1
MediaStreamType.SUBTITLE -> serverIndex - externalSubtitleCount - videoStreamCount - audioStreamCount + 1
else -> throw UnsupportedOperationException("Cannot calculate index for $type")
}
}
}

View file

@ -34,8 +34,11 @@ import androidx.media3.common.util.Util
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.trackselection.TrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEvent.MPV_EVENT_AUDIO_RECONFIG
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEvent.MPV_EVENT_END_FILE
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEvent.MPV_EVENT_FILE_LOADED
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEvent.MPV_EVENT_PLAYBACK_RESTART
import com.github.damontecres.wholphin.util.mpv.MPVLib.MpvEvent.MPV_EVENT_VIDEO_RECONFIG
import timber.log.Timber
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.time.Duration.Companion.seconds
@ -295,7 +298,7 @@ class MpvPlayer(
}
override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) {
if (DEBUG) Timber.v("setTrackSelectionParameters")
Timber.v("TrackSelection: setTrackSelectionParameters %s", parameters)
val tracks = getTracks()
if (C.TRACK_TYPE_TEXT in parameters.disabledTrackTypes) {
// Subtitles disabled
@ -522,14 +525,14 @@ class MpvPlayer(
}
override fun eventProperty(property: String) {
Timber.v("eventProperty: $property")
if (DEBUG) Timber.v("eventProperty: $property")
}
override fun eventProperty(
property: String,
value: Long,
) {
Timber.v("eventPropertyLong: $property=$value")
if (DEBUG) Timber.v("eventPropertyLong: $property=$value")
when (property) {
MPVProperty.POSITION -> positionMs = value.seconds.inWholeMilliseconds
}
@ -539,7 +542,7 @@ class MpvPlayer(
property: String,
value: Boolean,
) {
Timber.v("eventPropertyBoolean: $property=$value")
if (DEBUG) Timber.v("eventPropertyBoolean: $property=$value")
when (property) {
MPVProperty.PAUSED -> {
isPaused = value
@ -552,7 +555,7 @@ class MpvPlayer(
property: String,
value: String,
) {
Timber.v("eventPropertyString: $property=$value")
if (DEBUG) Timber.v("eventPropertyString: $property=$value")
}
override fun eventProperty(
@ -566,20 +569,48 @@ class MpvPlayer(
}
override fun event(eventId: Int) {
Timber.v("event: $eventId")
when (eventId) {
MPV_EVENT_FILE_LOADED -> {
Timber.d("Seeking to $startPositionMs")
Timber.d("event: MPV_EVENT_FILE_LOADED")
if (startPositionMs > 0) {
Timber.d("Seeking to $startPositionMs")
seekTo(startPositionMs)
}
getTracks().let {
notifyListeners(EVENT_TRACKS_CHANGED) { onTracksChanged(it) }
}
}
MPV_EVENT_PLAYBACK_RESTART -> {
Timber.d("event: MPV_EVENT_PLAYBACK_RESTART")
}
MPV_EVENT_AUDIO_RECONFIG -> {
Timber.d("event: MPV_EVENT_AUDIO_RECONFIG")
getTracks().let {
notifyListeners(EVENT_TRACKS_CHANGED) { onTracksChanged(it) }
}
}
MPV_EVENT_VIDEO_RECONFIG -> {
Timber.d("event: MPV_EVENT_VIDEO_RECONFIG")
getTracks().let {
notifyListeners(EVENT_TRACKS_CHANGED) { onTracksChanged(it) }
}
}
MPV_EVENT_END_FILE -> {
playbackState = STATE_ENDED
notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) { onPlaybackStateChanged(STATE_ENDED) }
Timber.d("event: MPV_EVENT_END_FILE")
if (positionMs >= durationMs) {
playbackState = STATE_ENDED
notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) {
onPlaybackStateChanged(STATE_ENDED)
}
}
}
else -> {
Timber.v("event: $eventId")
}
}
}