diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index fc2c6615..12368d14 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -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.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") + } + } + } diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt index 7cf0ff3e..45c976fc 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/mpv/MpvPlayer.kt @@ -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") } } }