diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlayerSettingsViewModel.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlayerSettingsViewModel.kt index 1c39c9f79..8474c3de6 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlayerSettingsViewModel.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlayerSettingsViewModel.kt @@ -6,9 +6,10 @@ package ch.srgssr.pillarbox.demo.shared.ui.player.settings import android.app.Application import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Audiotrack -import androidx.compose.material.icons.filled.Speed -import androidx.compose.material.icons.filled.Subtitles +import androidx.compose.material.icons.filled.ClosedCaption +import androidx.compose.material.icons.filled.RecordVoiceOver +import androidx.compose.material.icons.filled.SlowMotionVideo +import androidx.compose.material.icons.filled.Tune import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -19,17 +20,22 @@ import ch.srgssr.pillarbox.player.extension.displayName import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed import ch.srgssr.pillarbox.player.extension.isAudioTrackDisabled import ch.srgssr.pillarbox.player.extension.isTextTrackDisabled +import ch.srgssr.pillarbox.player.extension.isVideoTrackDisabled import ch.srgssr.pillarbox.player.getCurrentTracksAsFlow import ch.srgssr.pillarbox.player.getPlaybackSpeedAsFlow import ch.srgssr.pillarbox.player.getTrackSelectionParametersAsFlow import ch.srgssr.pillarbox.player.tracks.Track +import ch.srgssr.pillarbox.player.tracks.VideoTrack import ch.srgssr.pillarbox.player.tracks.audioTracks import ch.srgssr.pillarbox.player.tracks.disableAudioTrack import ch.srgssr.pillarbox.player.tracks.disableTextTrack +import ch.srgssr.pillarbox.player.tracks.disableVideoTrack import ch.srgssr.pillarbox.player.tracks.selectTrack import ch.srgssr.pillarbox.player.tracks.setAutoAudioTrack import ch.srgssr.pillarbox.player.tracks.setAutoTextTrack +import ch.srgssr.pillarbox.player.tracks.setAutoVideoTrack import ch.srgssr.pillarbox.player.tracks.textTracks +import ch.srgssr.pillarbox.player.tracks.videoTracks import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map @@ -53,57 +59,6 @@ class PlayerSettingsViewModel( private val playbackSpeed = player.getPlaybackSpeedAsFlow() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.getPlaybackSpeed()) - /** - * All the available settings for the current [player]. - */ - val settings = combine( - tracks, - trackSelectionParameters, - playbackSpeed - ) { currentTracks, trackSelectionParameters, playbackSpeed -> - buildList { - val textTracks = currentTracks.textTracks - val audioTracks = currentTracks.audioTracks - - add( - SettingItem( - title = application.getString(R.string.speed), - subtitle = getSpeedLabel(playbackSpeed), - icon = Icons.Default.Speed, - destination = SettingsRoutes.PlaybackSpeed - ) - ) - - if (textTracks.isNotEmpty()) { - add( - SettingItem( - title = application.getString(R.string.subtitles), - subtitle = getTracksSubtitle( - tracks = textTracks, - disabled = trackSelectionParameters.isTextTrackDisabled - ), - icon = Icons.Default.Subtitles, - destination = SettingsRoutes.Subtitles - ) - ) - } - - if (audioTracks.isNotEmpty()) { - add( - SettingItem( - title = application.getString(R.string.audio_track), - subtitle = getTracksSubtitle( - tracks = audioTracks, - disabled = trackSelectionParameters.isAudioTrackDisabled - ), - icon = Icons.Default.Audiotrack, - destination = SettingsRoutes.AudioTrack - ) - ) - } - } - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) - /** * All the available subtitle for the current [player]. */ @@ -132,6 +87,22 @@ class PlayerSettingsViewModel( ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) + /** + * All the available video qualities for the current [player]. + */ + val videoQualities = combine( + tracks, + trackSelectionParameters, + ) { tracks, trackSelectionParameters -> + TracksSettingItem( + title = application.getString(R.string.quality), + tracks = tracks.videoTracks + .distinctBy { it.format.height } + .sortedByDescending { it.format.height }, + disabled = trackSelectionParameters.isVideoTrackDisabled, + ) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) + /** * All the available playback speeds for the current [player]. */ @@ -145,6 +116,79 @@ class PlayerSettingsViewModel( } }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) + /** + * All the available settings for the current [player]. + */ + val settings = combine( + subtitles, + audioTracks, + videoQualities, + trackSelectionParameters, + playbackSpeed, + ) { subtitles, audioTracks, videoQualities, trackSelectionParameters, playbackSpeed -> + buildList { + add( + SettingItem( + title = application.getString(R.string.speed), + subtitle = getSpeedLabel(playbackSpeed), + icon = Icons.Default.SlowMotionVideo, + destination = SettingsRoutes.PlaybackSpeed, + ) + ) + + if (subtitles != null && subtitles.tracks.isNotEmpty()) { + add( + SettingItem( + title = application.getString(R.string.subtitles), + subtitle = getTracksSubtitle( + tracks = subtitles.tracks, + disabled = trackSelectionParameters.isTextTrackDisabled, + ), + icon = Icons.Default.ClosedCaption, + destination = SettingsRoutes.Subtitles, + ) + ) + } + + if (audioTracks != null && audioTracks.tracks.isNotEmpty()) { + add( + SettingItem( + title = application.getString(R.string.audio_track), + subtitle = getTracksSubtitle( + tracks = audioTracks.tracks, + disabled = trackSelectionParameters.isAudioTrackDisabled, + ), + icon = Icons.Default.RecordVoiceOver, + destination = SettingsRoutes.AudioTrack, + ) + ) + } + + if (videoQualities != null && videoQualities.tracks.isNotEmpty()) { + add( + SettingItem( + title = application.getString(R.string.quality), + subtitle = getTracksSubtitle( + tracks = videoQualities.tracks, + disabled = trackSelectionParameters.isVideoTrackDisabled, + ), + icon = Icons.Default.Tune, + destination = SettingsRoutes.VideoQuality, + ) + ) + } + } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) + + /** + * Select a specific track. + * + * @param track The track to select. + */ + fun selectTrack(track: Track) { + player.selectTrack(track) + } + /** * Reset the subtitles. */ @@ -159,15 +203,6 @@ class PlayerSettingsViewModel( player.disableTextTrack() } - /** - * Select a specific track. - * - * @param track The track to select. - */ - fun selectTrack(track: Track) { - player.selectTrack(track) - } - /** * Reset the audio track. */ @@ -182,6 +217,20 @@ class PlayerSettingsViewModel( player.disableAudioTrack() } + /** + * Reset the video track. + */ + fun resetVideoTrack() { + player.setAutoVideoTrack(application) + } + + /** + * Disable the video track. + */ + fun disableVideoTrack() { + player.disableVideoTrack() + } + /** * Set the playback speed. * @@ -199,7 +248,13 @@ class PlayerSettingsViewModel( application.getString(R.string.disabled) } else { tracks.filter { it.isSelected } - .map { it.format.displayName } + .map { + if (it is VideoTrack) { + it.format.height.toString() + "p" + } else { + it.format.displayName + } + } .firstOrNull() } } diff --git a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingsRoutes.kt b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingsRoutes.kt index 5be919cc0..515112cdb 100644 --- a/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingsRoutes.kt +++ b/pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingsRoutes.kt @@ -29,4 +29,9 @@ sealed class SettingsRoutes(val route: String) { * The route for the audio track setting. */ data object AudioTrack : SettingsRoutes(route = "settings/audio_track") + + /** + * The route for the video quality setting. + */ + data object VideoQuality : SettingsRoutes(route = "settings/video_quality") } diff --git a/pillarbox-demo-shared/src/main/res/values/strings.xml b/pillarbox-demo-shared/src/main/res/values/strings.xml index 9913ff8f3..d80d1f87e 100644 --- a/pillarbox-demo-shared/src/main/res/values/strings.xml +++ b/pillarbox-demo-shared/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ %1$d min Settings Audio track + Quality Subtitles Speed Normal diff --git a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/settings/PlaybackSettingsDrawer.kt b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/settings/PlaybackSettingsDrawer.kt index f6ef0016d..d455a6d15 100644 --- a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/settings/PlaybackSettingsDrawer.kt +++ b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/settings/PlaybackSettingsDrawer.kt @@ -58,6 +58,7 @@ import ch.srgssr.pillarbox.demo.tv.ui.theme.paddings import ch.srgssr.pillarbox.player.extension.displayName import ch.srgssr.pillarbox.player.extension.hasAccessibilityRoles import ch.srgssr.pillarbox.player.tracks.Track +import ch.srgssr.pillarbox.player.tracks.VideoTrack /** * Drawer used to display a player's settings. @@ -180,6 +181,19 @@ private fun NavigationDrawerScope.NavigationDrawerNavHost( } } + composable(SettingsRoutes.VideoQuality.route) { + val videoQualities by settingsViewModel.videoQualities.collectAsState() + + videoQualities?.let { + TracksSetting( + tracksSetting = it, + onResetClick = settingsViewModel::resetVideoTrack, + onDisabledClick = settingsViewModel::disableVideoTrack, + onTrackClick = settingsViewModel::selectTrack, + ) + } + } + composable(SettingsRoutes.Subtitles.route) { val subtitles by settingsViewModel.subtitles.collectAsState() @@ -330,17 +344,21 @@ private fun NavigationDrawerScope.TracksSetting( }, content = { val format = track.format - val label = buildString { - append(format.displayName) + val label = if (track is VideoTrack) { + format.height.toString() + "p" + } else { + buildString { + append(format.displayName) - if (format.bitrate > Format.NO_VALUE) { - append(" @") - append(format.bitrate) - append(" bit/sec") - } + if (format.bitrate > Format.NO_VALUE) { + append(" @") + append(format.bitrate) + append(" bit/sec") + } - if (format.hasAccessibilityRoles()) { - append(" (AD)") + if (format.hasAccessibilityRoles()) { + append(" (AD)") + } } } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSettingsContent.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSettingsContent.kt index ebdd11567..09320fc05 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSettingsContent.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/PlaybackSettingsContent.kt @@ -60,6 +60,7 @@ fun PlaybackSettingsContent(player: Player) { }, ) } + composable( route = SettingsRoutes.PlaybackSpeed.route, exitTransition = { @@ -75,6 +76,7 @@ fun PlaybackSettingsContent(player: Player) { onSpeedSelected = settingsViewModel::setPlaybackSpeed ) } + composable( route = SettingsRoutes.Subtitles.route, exitTransition = { @@ -94,6 +96,7 @@ fun PlaybackSettingsContent(player: Player) { ) } } + composable( route = SettingsRoutes.AudioTrack.route, exitTransition = { @@ -113,6 +116,26 @@ fun PlaybackSettingsContent(player: Player) { ) } } + + composable( + route = SettingsRoutes.VideoQuality.route, + exitTransition = { + slideOutOfContainer(towards = AnimatedContentTransitionScope.SlideDirection.Down) + }, + enterTransition = { + slideIntoContainer(towards = AnimatedContentTransitionScope.SlideDirection.Up) + } + ) { + val videoQualities by settingsViewModel.videoQualities.collectAsState() + videoQualities?.let { + TrackSelectionSettings( + tracksSetting = it, + onResetClick = settingsViewModel::resetVideoTrack, + onDisabledClick = settingsViewModel::disableVideoTrack, + onTrackClick = settingsViewModel::selectTrack, + ) + } + } } } } diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt index 14bad5946..02c499e10 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt @@ -36,6 +36,7 @@ import ch.srgssr.pillarbox.player.extension.hasAccessibilityRoles import ch.srgssr.pillarbox.player.tracks.AudioTrack import ch.srgssr.pillarbox.player.tracks.TextTrack import ch.srgssr.pillarbox.player.tracks.Track +import ch.srgssr.pillarbox.player.tracks.VideoTrack /** * Track selection settings @@ -104,6 +105,10 @@ fun TrackSelectionSettings( } } + is VideoTrack -> { + Text(text = format.height.toString() + "p") + } + else -> { if (format.hasAccessibilityRoles()) { Row(verticalAlignment = Alignment.CenterVertically) { diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/TrackSelectionParameters.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/TrackSelectionParameters.kt index 4b8f9f9c8..1b681cee0 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/TrackSelectionParameters.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/TrackSelectionParameters.kt @@ -27,6 +27,12 @@ val TrackSelectionParameters.isTextTrackDisabled: Boolean val TrackSelectionParameters.isAudioTrackDisabled: Boolean get() = disabledTrackTypes.contains(C.TRACK_TYPE_AUDIO) +/** + * Is video track disabled + */ +val TrackSelectionParameters.isVideoTrackDisabled: Boolean + get() = disabledTrackTypes.contains(C.TRACK_TYPE_VIDEO) + /** * Get overrides for track type *