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
*