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, enableDirectStream: Boolean = true,
) = withContext(Dispatchers.IO) { ) = withContext(Dispatchers.IO) {
val itemId = item.id val itemId = item.id
val playerBackend = preferences.appPreferences.playbackPreferences.playerBackend
val currentPlayback = this@PlaybackViewModel.currentPlayback.value val currentPlayback = this@PlaybackViewModel.currentPlayback.value
if (currentPlayback != null && currentPlayback.playMethod == PlayMethod.DIRECT_PLAY) { if (currentPlayback != null && currentPlayback.playMethod == PlayMethod.DIRECT_PLAY) {
// If direct playing, can try to switch tracks without playback restarting // If direct playing, can try to switch tracks without playback restarting
// Except for external subtitles // Except for external subtitles
// TODO there's probably no reason why we can't add 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 source = currentPlayback.mediaSourceInfo
val externalSubtitleCount = source.externalSubtitlesCount val externalSubtitleCount = source.externalSubtitlesCount
@ -439,14 +441,18 @@ class PlaybackViewModel
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
applyTrackSelections( applyTrackSelections(
player, player,
playerBackend,
true, true,
audioIndex, audioIndex,
subtitleIndex, subtitleIndex,
source.videoStreamCount,
source.audioStreamCount,
externalSubtitleCount, externalSubtitleCount,
externalSubtitle != null, externalSubtitle != null,
) )
} }
if (result.bothSelected) { if (result.bothSelected) {
// TODO lots of duplicate code in this block
Timber.d("Changes tracks audio=$audioIndex, subtitle=$subtitleIndex") Timber.d("Changes tracks audio=$audioIndex, subtitle=$subtitleIndex")
val itemPlayback = val itemPlayback =
currentItemPlayback.copy( currentItemPlayback.copy(
@ -489,7 +495,6 @@ class PlaybackViewModel
"changeStreams: userInitiated=$userInitiated, audioIndex=$audioIndex, subtitleIndex=$subtitleIndex, " + "changeStreams: userInitiated=$userInitiated, audioIndex=$audioIndex, subtitleIndex=$subtitleIndex, " +
"enableDirectPlay=$enableDirectPlay, enableDirectStream=$enableDirectStream", "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 // TODO if the new audio or subtitle index is already in the streams (eg direct play), should toggle in the player instead
val maxBitrate = val maxBitrate =
preferences.appPreferences.playbackPreferences.maxBitrate preferences.appPreferences.playbackPreferences.maxBitrate
@ -618,15 +623,16 @@ class PlaybackViewModel
player.removeListener(it) player.removeListener(it)
} }
val activityListener = // TODO temporarily disable
TrackActivityPlaybackListener( // val activityListener =
api = api, // TrackActivityPlaybackListener(
player = player, // api = api,
playback = playback, // player = player,
itemPlayback = itemPlayback, // playback = playback,
) // itemPlayback = itemPlayback,
player.addListener(activityListener) // )
this@PlaybackViewModel.activityListener = activityListener // player.addListener(activityListener)
// this@PlaybackViewModel.activityListener = activityListener
duration.value = source.runTimeTicks?.ticks duration.value = source.runTimeTicks?.ticks
loading.value = LoadingState.Success loading.value = LoadingState.Success
@ -644,9 +650,12 @@ class PlaybackViewModel
if (tracks.groups.isNotEmpty()) { if (tracks.groups.isNotEmpty()) {
applyTrackSelections( applyTrackSelections(
player, player,
playerBackend,
source.supportsDirectPlay, source.supportsDirectPlay,
audioIndex, audioIndex,
subtitleIndex, subtitleIndex,
source.videoStreamCount,
source.audioStreamCount,
externalSubtitleCount, externalSubtitleCount,
externalSubtitle != null, externalSubtitle != null,
) )
@ -1143,6 +1152,22 @@ val MediaSourceInfo.externalSubtitlesCount: Int
mediaStreams mediaStreams
?.count { it.type == MediaStreamType.SUBTITLE && it.isExternal } ?: 0 ?.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 * Returns the [MediaStream] for the given subtitle index iff it is external
*/ */
@ -1164,9 +1189,12 @@ data class TrackSelectionResult(
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
private fun applyTrackSelections( private fun applyTrackSelections(
player: Player, player: Player,
playerBackend: PlayerBackend,
supportsDirectPlay: Boolean, supportsDirectPlay: Boolean,
audioIndex: Int?, audioIndex: Int?,
subtitleIndex: Int?, subtitleIndex: Int?,
videoStreamCount: Int,
audioStreamCount: Int,
externalSubtitleCount: Int, externalSubtitleCount: Int,
subtitleIsExternal: Boolean, subtitleIsExternal: Boolean,
): TrackSelectionResult { ): TrackSelectionResult {
@ -1182,7 +1210,18 @@ private fun applyTrackSelections(
}.any { it.endsWith("e:$subtitleIndex") } }.any { it.endsWith("e:$subtitleIndex") }
} }
} else { } 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 -> 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)
@ -1217,7 +1256,14 @@ private fun applyTrackSelections(
val audioSelected = val audioSelected =
if (audioIndex != null && supportsDirectPlay) { if (audioIndex != null && supportsDirectPlay) {
val indexToFind = val indexToFind =
audioIndex - externalSubtitleCount + 1 calculateIndexToFind(
audioIndex,
MediaStreamType.AUDIO,
playerBackend,
videoStreamCount,
audioStreamCount,
externalSubtitleCount,
)
val chosenTrack = val chosenTrack =
player.currentTracks.groups.firstOrNull { group -> player.currentTracks.groups.firstOrNull { group ->
group.type == C.TRACK_TYPE_AUDIO && group.isSupported && group.type == C.TRACK_TYPE_AUDIO && group.isSupported &&
@ -1245,3 +1291,32 @@ private fun applyTrackSelections(
} }
return TrackSelectionResult(audioSelected, subtitleSelected) 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.DefaultTrackSelector
import androidx.media3.exoplayer.trackselection.TrackSelector import androidx.media3.exoplayer.trackselection.TrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter 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_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_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 timber.log.Timber
import kotlin.concurrent.atomics.ExperimentalAtomicApi import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -295,7 +298,7 @@ class MpvPlayer(
} }
override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) { override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) {
if (DEBUG) Timber.v("setTrackSelectionParameters") Timber.v("TrackSelection: setTrackSelectionParameters %s", parameters)
val tracks = getTracks() val tracks = getTracks()
if (C.TRACK_TYPE_TEXT in parameters.disabledTrackTypes) { if (C.TRACK_TYPE_TEXT in parameters.disabledTrackTypes) {
// Subtitles disabled // Subtitles disabled
@ -522,14 +525,14 @@ class MpvPlayer(
} }
override fun eventProperty(property: String) { override fun eventProperty(property: String) {
Timber.v("eventProperty: $property") if (DEBUG) Timber.v("eventProperty: $property")
} }
override fun eventProperty( override fun eventProperty(
property: String, property: String,
value: Long, value: Long,
) { ) {
Timber.v("eventPropertyLong: $property=$value") if (DEBUG) Timber.v("eventPropertyLong: $property=$value")
when (property) { when (property) {
MPVProperty.POSITION -> positionMs = value.seconds.inWholeMilliseconds MPVProperty.POSITION -> positionMs = value.seconds.inWholeMilliseconds
} }
@ -539,7 +542,7 @@ class MpvPlayer(
property: String, property: String,
value: Boolean, value: Boolean,
) { ) {
Timber.v("eventPropertyBoolean: $property=$value") if (DEBUG) Timber.v("eventPropertyBoolean: $property=$value")
when (property) { when (property) {
MPVProperty.PAUSED -> { MPVProperty.PAUSED -> {
isPaused = value isPaused = value
@ -552,7 +555,7 @@ class MpvPlayer(
property: String, property: String,
value: String, value: String,
) { ) {
Timber.v("eventPropertyString: $property=$value") if (DEBUG) Timber.v("eventPropertyString: $property=$value")
} }
override fun eventProperty( override fun eventProperty(
@ -566,20 +569,48 @@ class MpvPlayer(
} }
override fun event(eventId: Int) { override fun event(eventId: Int) {
Timber.v("event: $eventId")
when (eventId) { when (eventId) {
MPV_EVENT_FILE_LOADED -> { MPV_EVENT_FILE_LOADED -> {
Timber.d("Seeking to $startPositionMs") Timber.d("event: MPV_EVENT_FILE_LOADED")
if (startPositionMs > 0) { if (startPositionMs > 0) {
Timber.d("Seeking to $startPositionMs")
seekTo(startPositionMs) seekTo(startPositionMs)
} }
getTracks().let { getTracks().let {
notifyListeners(EVENT_TRACKS_CHANGED) { onTracksChanged(it) } 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 -> { MPV_EVENT_END_FILE -> {
playbackState = STATE_ENDED Timber.d("event: MPV_EVENT_END_FILE")
notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) { onPlaybackStateChanged(STATE_ENDED) } if (positionMs >= durationMs) {
playbackState = STATE_ENDED
notifyListeners(EVENT_PLAYBACK_STATE_CHANGED) {
onPlaybackStateChanged(STATE_ENDED)
}
}
}
else -> {
Timber.v("event: $eventId")
} }
} }
} }