mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Calculate track indices for mpv
This commit is contained in:
parent
3a4a22ec94
commit
0597e93178
2 changed files with 127 additions and 21 deletions
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue