Skip to content

Commit

Permalink
Rewrite queue management and play options handling
Browse files Browse the repository at this point in the history
Caching media sources in the queue was unnecessary and overly complex, and could cause issues if playback options changed in between (i.e. a new bitrate). Additionally, there was some duplication between the cached play options and the current media source, which caused more confusion and issues.
  • Loading branch information
Maxr1998 committed May 4, 2023
1 parent 79b7588 commit 0bd75f4
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 153 deletions.
37 changes: 20 additions & 17 deletions app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector
import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.util.Clock
import com.google.android.exoplayer2.util.EventLogger
Expand Down Expand Up @@ -88,9 +89,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
// Media source handling
private val trackSelector = DefaultTrackSelector(getApplication())
val trackSelectionHelper = TrackSelectionHelper(this, trackSelector)
val mediaQueueManager = QueueManager(this)
val queueManager = QueueManager(this)
val mediaSourceOrNull: JellyfinMediaSource?
get() = mediaQueueManager.mediaQueue.value?.jellyfinMediaSource
get() = queueManager.currentMediaSourceOrNull

// ExoPlayer
private val _player = MutableLiveData<ExoPlayer?>()
Expand Down Expand Up @@ -239,22 +240,22 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
_player.value = null
}

fun load(queueItem: QueueManager.QueueItem.Loaded, playWhenReady: Boolean) {
fun load(jellyfinMediaSource: JellyfinMediaSource, exoMediaSource: MediaSource, playWhenReady: Boolean) {
val player = playerOrNull ?: return

player.setMediaSource(queueItem.exoMediaSource)
player.setMediaSource(exoMediaSource)
player.prepare()

initialTracksSelected.set(false)

val startTime = queueItem.jellyfinMediaSource.startTimeMs
val startTime = jellyfinMediaSource.startTimeMs
if (startTime > 0) player.seekTo(startTime)
player.playWhenReady = playWhenReady

mediaSession.setMetadata(queueItem.jellyfinMediaSource.toMediaMetadata())
mediaSession.setMetadata(jellyfinMediaSource.toMediaMetadata())

viewModelScope.launch {
player.reportPlaybackStart(queueItem.jellyfinMediaSource)
player.reportPlaybackStart(jellyfinMediaSource)
}
}

Expand Down Expand Up @@ -286,8 +287,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}
analyticsCollector = buildAnalyticsCollector()
setupPlayer()
mediaQueueManager.mediaQueue.value?.jellyfinMediaSource?.startTimeMs = playedTime
mediaQueueManager.tryRestartPlayback()
queueManager.currentMediaSourceOrNull?.startTimeMs = playedTime
queueManager.tryRestartPlayback()
}

private suspend fun Player.reportPlaybackStart(mediaSource: JellyfinMediaSource) {
Expand Down Expand Up @@ -409,7 +410,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
// Skip to previous element
player.currentPosition <= Constants.MAX_SKIP_TO_PREV_MS -> viewModelScope.launch {
pause()
if (!mediaQueueManager.previous()) {
if (!queueManager.previous()) {
// Skip to previous failed, go to start of video anyway
playerOrNull?.seekTo(0)
play()
Expand All @@ -422,7 +423,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),

fun skipToNext() {
viewModelScope.launch {
mediaQueueManager.next()
queueManager.next()
}
}

Expand All @@ -441,7 +442,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}

suspend fun changeBitrate(bitrate: Int?): Boolean {
return mediaQueueManager.changeBitrate(bitrate)
return queueManager.changeBitrate(bitrate)
}

/**
Expand Down Expand Up @@ -499,9 +500,11 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),

// Update media session
var playbackActions = SUPPORTED_VIDEO_PLAYER_PLAYBACK_ACTIONS
mediaQueueManager.mediaQueue.value?.let { queueItem ->
if (queueItem.hasPrevious()) playbackActions = playbackActions or PlaybackState.ACTION_SKIP_TO_PREVIOUS
if (queueItem.hasNext()) playbackActions = playbackActions or PlaybackState.ACTION_SKIP_TO_NEXT
if (queueManager.hasPrevious()) {
playbackActions = playbackActions or PlaybackState.ACTION_SKIP_TO_PREVIOUS
}
if (queueManager.hasNext()) {
playbackActions = playbackActions or PlaybackState.ACTION_SKIP_TO_NEXT
}
mediaSession.setPlaybackState(player, playbackActions)

Expand All @@ -513,7 +516,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}
Player.STATE_ENDED -> {
reportPlaybackStop()
if (!mediaQueueManager.next()) {
if (!queueManager.next()) {
releasePlayer()
}
}
Expand All @@ -530,7 +533,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application),
}
fallbackPreferExtensionRenderers = true
setupPlayer()
mediaQueueManager.tryRestartPlayback()
queueManager.tryRestartPlayback()
} else {
_error.postValue(error.localizedMessage.orEmpty())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class TrackSelectionHelper(

// For transcoding and external streams, we need to restart playback
if (mediaSource.playMethod == PlayMethod.TRANSCODE || selectedMediaStream.isExternal) {
return viewModel.mediaQueueManager.selectAudioStreamAndRestartPlayback(selectedMediaStream)
return viewModel.queueManager.selectAudioStreamAndRestartPlayback(selectedMediaStream)
}

return selectPlayerAudioTrack(mediaSource, selectedMediaStream, initial = false).also { success ->
Expand Down Expand Up @@ -95,7 +95,7 @@ class TrackSelectionHelper(
selectedMediaStream?.deliveryMethod == SubtitleDeliveryMethod.ENCODE ||
mediaSource.selectedSubtitleStream?.deliveryMethod == SubtitleDeliveryMethod.ENCODE
) {
return viewModel.mediaQueueManager.selectSubtitleStreamAndRestartPlayback(selectedMediaStream)
return viewModel.queueManager.selectSubtitleStreamAndRestartPlayback(selectedMediaStream)
}

return selectSubtitleTrack(mediaSource, selectedMediaStream, initial = false).also { success ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ class PlayerNotificationHelper(private val viewModel: PlayerViewModel) : KoinCom
fun postNotification() {
val nm = notificationManager ?: return
val player = viewModel.playerOrNull ?: return
val queueItem = viewModel.mediaQueueManager.mediaQueue.value ?: return
val mediaSource = queueItem.jellyfinMediaSource
val currentMediaSource = viewModel.queueManager.currentMediaSourceOrNull ?: return
val hasPrevious = viewModel.queueManager.hasPrevious()
val hasNext = viewModel.queueManager.hasNext()
val playbackState = player.playbackState
if (playbackState != Player.STATE_READY && playbackState != Player.STATE_BUFFERING) return

Expand All @@ -76,7 +77,7 @@ class PlayerNotificationHelper(private val viewModel: PlayerViewModel) : KoinCom

viewModel.viewModelScope.launch {
val mediaIcon: Bitmap? = withContext(Dispatchers.IO) {
loadImage(mediaSource)
loadImage(currentMediaSource)
}

val style = Notification.MediaStyle().apply {
Expand All @@ -93,12 +94,12 @@ class PlayerNotificationHelper(private val viewModel: PlayerViewModel) : KoinCom
}
setSmallIcon(R.drawable.ic_notification)
mediaIcon?.let(::setLargeIcon)
setContentTitle(mediaSource.name)
mediaSource.item?.artists?.joinToString()?.let(::setContentText)
setContentTitle(currentMediaSource.name)
currentMediaSource.item?.artists?.joinToString()?.let(::setContentText)
setStyle(style)
setVisibility(Notification.VISIBILITY_PUBLIC)
when {
queueItem.hasPrevious() -> addAction(generateAction(PlayerNotificationAction.PREVIOUS))
hasPrevious -> addAction(generateAction(PlayerNotificationAction.PREVIOUS))
else -> addAction(generateAction(PlayerNotificationAction.REWIND))
}
val playbackAction = when {
Expand All @@ -107,7 +108,7 @@ class PlayerNotificationHelper(private val viewModel: PlayerViewModel) : KoinCom
}
addAction(generateAction(playbackAction))
when {
queueItem.hasNext() -> addAction(generateAction(PlayerNotificationAction.NEXT))
hasNext -> addAction(generateAction(PlayerNotificationAction.NEXT))
else -> addAction(generateAction(PlayerNotificationAction.FAST_FORWARD))
}
setContentIntent(buildContentIntent())
Expand Down
Loading

0 comments on commit 0bd75f4

Please sign in to comment.