Skip to content

Commit 1c3346e

Browse files
MGaetan89StaehliJ
andauthored
Provide simplified tracks management (#495)
Co-authored-by: Joaquim Stähli <[email protected]>
1 parent c2a4330 commit 1c3346e

File tree

22 files changed

+1292
-247
lines changed

22 files changed

+1292
-247
lines changed

pillarbox-core-business/src/main/java/ch/srgssr/pillarbox/core/business/tracker/commandersact/CommandersActStreaming.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import ch.srgssr.pillarbox.analytics.commandersact.CommandersAct
1313
import ch.srgssr.pillarbox.analytics.commandersact.MediaEventType
1414
import ch.srgssr.pillarbox.analytics.commandersact.TCMediaEvent
1515
import ch.srgssr.pillarbox.core.business.tracker.TotalPlaytimeCounter
16-
import ch.srgssr.pillarbox.player.extension.audio
1716
import ch.srgssr.pillarbox.player.extension.isForced
17+
import ch.srgssr.pillarbox.player.tracks.audioTracks
1818
import ch.srgssr.pillarbox.player.utils.DebugLogger
1919
import kotlinx.coroutines.CoroutineName
2020
import kotlinx.coroutines.CoroutineScope
@@ -233,15 +233,15 @@ internal class CommandersActStreaming(
233233
}
234234
}
235235

236-
@Suppress("SwallowedException")
237236
private fun handleAudioTrack(event: TCMediaEvent) {
238-
try {
239-
val selectedAudioGroup = player.currentTracks.audio.first { it.isSelected }
240-
val selectedFormat: Format = selectedAudioGroup.getTrackFormat(0)
241-
event.audioTrackLanguage = selectedFormat.language ?: C.LANGUAGE_UNDETERMINED
242-
} catch (e: NoSuchElementException) {
243-
event.audioTrackLanguage = C.LANGUAGE_UNDETERMINED
244-
}
237+
val audioTrackLanguage = player.currentTracks
238+
.audioTracks
239+
.find { it.isSelected }
240+
?.format
241+
?.language
242+
?: C.LANGUAGE_UNDETERMINED
243+
244+
event.audioTrackLanguage = audioTrackLanguage
245245
}
246246

247247
companion object {

pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/PlayerSettingsViewModel.kt

Lines changed: 127 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,37 @@ package ch.srgssr.pillarbox.demo.shared.ui.player.settings
66

77
import android.app.Application
88
import androidx.compose.material.icons.Icons
9-
import androidx.compose.material.icons.filled.Audiotrack
10-
import androidx.compose.material.icons.filled.Speed
11-
import androidx.compose.material.icons.filled.Subtitles
9+
import androidx.compose.material.icons.filled.ClosedCaption
10+
import androidx.compose.material.icons.filled.RecordVoiceOver
11+
import androidx.compose.material.icons.filled.SlowMotionVideo
12+
import androidx.compose.material.icons.filled.Tune
1213
import androidx.lifecycle.AndroidViewModel
1314
import androidx.lifecycle.ViewModel
1415
import androidx.lifecycle.ViewModelProvider
1516
import androidx.lifecycle.viewModelScope
17+
import androidx.media3.common.C
1618
import androidx.media3.common.Player
17-
import androidx.media3.common.TrackSelectionOverride
18-
import androidx.media3.common.Tracks.Group
1919
import ch.srgssr.pillarbox.demo.shared.R
20-
import ch.srgssr.pillarbox.player.extension.audio
21-
import ch.srgssr.pillarbox.player.extension.disableAudioTrack
22-
import ch.srgssr.pillarbox.player.extension.disableTextTrack
2320
import ch.srgssr.pillarbox.player.extension.displayName
2421
import ch.srgssr.pillarbox.player.extension.getPlaybackSpeed
2522
import ch.srgssr.pillarbox.player.extension.isAudioTrackDisabled
2623
import ch.srgssr.pillarbox.player.extension.isTextTrackDisabled
27-
import ch.srgssr.pillarbox.player.extension.setDefaultAudioTrack
28-
import ch.srgssr.pillarbox.player.extension.setDefaultTextTrack
29-
import ch.srgssr.pillarbox.player.extension.setTrackOverride
30-
import ch.srgssr.pillarbox.player.extension.text
24+
import ch.srgssr.pillarbox.player.extension.isVideoTrackDisabled
3125
import ch.srgssr.pillarbox.player.getCurrentTracksAsFlow
3226
import ch.srgssr.pillarbox.player.getPlaybackSpeedAsFlow
3327
import ch.srgssr.pillarbox.player.getTrackSelectionParametersAsFlow
28+
import ch.srgssr.pillarbox.player.tracks.Track
29+
import ch.srgssr.pillarbox.player.tracks.VideoTrack
30+
import ch.srgssr.pillarbox.player.tracks.audioTracks
31+
import ch.srgssr.pillarbox.player.tracks.disableAudioTrack
32+
import ch.srgssr.pillarbox.player.tracks.disableTextTrack
33+
import ch.srgssr.pillarbox.player.tracks.disableVideoTrack
34+
import ch.srgssr.pillarbox.player.tracks.selectTrack
35+
import ch.srgssr.pillarbox.player.tracks.setAutoAudioTrack
36+
import ch.srgssr.pillarbox.player.tracks.setAutoTextTrack
37+
import ch.srgssr.pillarbox.player.tracks.setAutoVideoTrack
38+
import ch.srgssr.pillarbox.player.tracks.textTracks
39+
import ch.srgssr.pillarbox.player.tracks.videoTracks
3440
import kotlinx.coroutines.flow.SharingStarted
3541
import kotlinx.coroutines.flow.combine
3642
import kotlinx.coroutines.flow.map
@@ -54,54 +60,6 @@ class PlayerSettingsViewModel(
5460
private val playbackSpeed = player.getPlaybackSpeedAsFlow()
5561
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.getPlaybackSpeed())
5662

57-
/**
58-
* All the available settings for the current [player].
59-
*/
60-
val settings = combine(
61-
tracks,
62-
trackSelectionParameters,
63-
playbackSpeed
64-
) { currentTracks, trackSelectionParameters, playbackSpeed ->
65-
buildList {
66-
add(
67-
SettingItem(
68-
title = application.getString(R.string.speed),
69-
subtitle = getSpeedLabel(playbackSpeed),
70-
icon = Icons.Default.Speed,
71-
destination = SettingsRoutes.PlaybackSpeed
72-
)
73-
)
74-
75-
if (currentTracks.text.isNotEmpty()) {
76-
add(
77-
SettingItem(
78-
title = application.getString(R.string.subtitles),
79-
subtitle = getTracksSubtitle(
80-
tracks = currentTracks.text,
81-
disabled = trackSelectionParameters.isTextTrackDisabled
82-
),
83-
icon = Icons.Default.Subtitles,
84-
destination = SettingsRoutes.Subtitles
85-
)
86-
)
87-
}
88-
89-
if (currentTracks.audio.isNotEmpty()) {
90-
add(
91-
SettingItem(
92-
title = application.getString(R.string.audio_track),
93-
subtitle = getTracksSubtitle(
94-
tracks = currentTracks.audio,
95-
disabled = trackSelectionParameters.isAudioTrackDisabled
96-
),
97-
icon = Icons.Default.Audiotrack,
98-
destination = SettingsRoutes.AudioTrack
99-
)
100-
)
101-
}
102-
}
103-
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
104-
10563
/**
10664
* All the available subtitle for the current [player].
10765
*/
@@ -111,7 +69,7 @@ class PlayerSettingsViewModel(
11169
) { tracks, trackSelectionParameters ->
11270
TracksSettingItem(
11371
title = application.getString(R.string.subtitles),
114-
tracks = tracks.text,
72+
tracks = tracks.textTracks,
11573
disabled = trackSelectionParameters.isTextTrackDisabled
11674
)
11775
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
@@ -125,11 +83,29 @@ class PlayerSettingsViewModel(
12583
) { tracks, trackSelectionParameters ->
12684
TracksSettingItem(
12785
title = application.getString(R.string.audio_track),
128-
tracks = tracks.audio,
86+
tracks = tracks.audioTracks,
12987
disabled = trackSelectionParameters.isAudioTrackDisabled
13088
)
13189
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
13290

91+
/**
92+
* All the available video tracks for the current [player].
93+
*/
94+
val videoTracks = combine(
95+
tracks,
96+
trackSelectionParameters,
97+
) { tracks, trackSelectionParameters ->
98+
TracksSettingItem(
99+
title = application.getString(R.string.video_tracks),
100+
tracks = tracks.videoTracks
101+
.sortedWith(
102+
compareByDescending<VideoTrack> { it.format.height }
103+
.thenByDescending { it.format.bitrate }
104+
),
105+
disabled = trackSelectionParameters.isVideoTrackDisabled,
106+
)
107+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
108+
133109
/**
134110
* All the available playback speeds for the current [player].
135111
*/
@@ -143,11 +119,84 @@ class PlayerSettingsViewModel(
143119
}
144120
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
145121

122+
/**
123+
* All the available settings for the current [player].
124+
*/
125+
val settings = combine(
126+
subtitles,
127+
audioTracks,
128+
videoTracks,
129+
trackSelectionParameters,
130+
playbackSpeed,
131+
) { subtitles, audioTracks, videoTracks, trackSelectionParameters, playbackSpeed ->
132+
buildList {
133+
add(
134+
SettingItem(
135+
title = application.getString(R.string.speed),
136+
subtitle = getSpeedLabel(playbackSpeed),
137+
icon = Icons.Default.SlowMotionVideo,
138+
destination = SettingsRoutes.PlaybackSpeed,
139+
)
140+
)
141+
142+
if (subtitles != null && subtitles.tracks.isNotEmpty()) {
143+
add(
144+
SettingItem(
145+
title = application.getString(R.string.subtitles),
146+
subtitle = getTracksSubtitle(
147+
tracks = subtitles.tracks,
148+
disabled = trackSelectionParameters.isTextTrackDisabled,
149+
),
150+
icon = Icons.Default.ClosedCaption,
151+
destination = SettingsRoutes.Subtitles,
152+
)
153+
)
154+
}
155+
156+
if (audioTracks != null && audioTracks.tracks.isNotEmpty()) {
157+
add(
158+
SettingItem(
159+
title = application.getString(R.string.audio_track),
160+
subtitle = getTracksSubtitle(
161+
tracks = audioTracks.tracks,
162+
disabled = trackSelectionParameters.isAudioTrackDisabled,
163+
),
164+
icon = Icons.Default.RecordVoiceOver,
165+
destination = SettingsRoutes.AudioTrack,
166+
)
167+
)
168+
}
169+
170+
if (videoTracks != null && videoTracks.tracks.isNotEmpty()) {
171+
add(
172+
SettingItem(
173+
title = application.getString(R.string.video_tracks),
174+
subtitle = getTracksSubtitle(
175+
tracks = videoTracks.tracks,
176+
disabled = trackSelectionParameters.isVideoTrackDisabled,
177+
),
178+
icon = Icons.Default.Tune,
179+
destination = SettingsRoutes.VideoTrack,
180+
)
181+
)
182+
}
183+
}
184+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
185+
186+
/**
187+
* Select a specific track.
188+
*
189+
* @param track The track to select.
190+
*/
191+
fun selectTrack(track: Track) {
192+
player.selectTrack(track)
193+
}
194+
146195
/**
147196
* Reset the subtitles.
148197
*/
149198
fun resetSubtitles() {
150-
player.setDefaultTextTrack(application)
199+
player.setAutoTextTrack(application)
151200
}
152201

153202
/**
@@ -157,21 +206,11 @@ class PlayerSettingsViewModel(
157206
player.disableTextTrack()
158207
}
159208

160-
/**
161-
* Set the subtitles.
162-
*
163-
* @param group The selected group.
164-
* @param trackIndex The index of the track in the provided group.
165-
*/
166-
fun setSubtitle(group: Group, trackIndex: Int) {
167-
player.setTrackOverride(TrackSelectionOverride(group.mediaTrackGroup, trackIndex))
168-
}
169-
170209
/**
171210
* Reset the audio track.
172211
*/
173212
fun resetAudioTrack() {
174-
player.setDefaultAudioTrack(application)
213+
player.setAutoAudioTrack(application)
175214
}
176215

177216
/**
@@ -182,13 +221,17 @@ class PlayerSettingsViewModel(
182221
}
183222

184223
/**
185-
* Set the audio track.
186-
*
187-
* @param group The selected group.
188-
* @param trackIndex The index of the track in the provided group.
224+
* Reset the video track.
225+
*/
226+
fun resetVideoTrack() {
227+
player.setAutoVideoTrack()
228+
}
229+
230+
/**
231+
* Disable the video track.
189232
*/
190-
fun setAudioTrack(group: Group, trackIndex: Int) {
191-
player.setTrackOverride(TrackSelectionOverride(group.mediaTrackGroup, trackIndex))
233+
fun disableVideoTrack() {
234+
player.disableVideoTrack()
192235
}
193236

194237
/**
@@ -201,22 +244,15 @@ class PlayerSettingsViewModel(
201244
}
202245

203246
private fun getTracksSubtitle(
204-
tracks: List<Group>,
205-
disabled: Boolean
247+
tracks: List<Track>,
248+
disabled: Boolean,
206249
): String? {
207250
return if (disabled) {
208251
application.getString(R.string.disabled)
209252
} else {
210253
tracks.filter { it.isSelected }
211-
.flatMap {
212-
(0 until it.length).mapNotNull { trackIndex ->
213-
if (it.isTrackSelected(trackIndex)) {
214-
it.getTrackFormat(trackIndex).displayName
215-
} else {
216-
null
217-
}
218-
}
219-
}
254+
.map { it.format.displayName }
255+
.filter { it != C.LANGUAGE_UNDETERMINED }
220256
.firstOrNull()
221257
}
222258
}

pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/SettingsRoutes.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,9 @@ sealed class SettingsRoutes(val route: String) {
2929
* The route for the audio track setting.
3030
*/
3131
data object AudioTrack : SettingsRoutes(route = "settings/audio_track")
32+
33+
/**
34+
* The route for the video track setting.
35+
*/
36+
data object VideoTrack : SettingsRoutes(route = "settings/video_track")
3237
}

pillarbox-demo-shared/src/main/java/ch/srgssr/pillarbox/demo/shared/ui/player/settings/TracksSettingItem.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
package ch.srgssr.pillarbox.demo.shared.ui.player.settings
66

7-
import androidx.media3.common.Tracks.Group
7+
import ch.srgssr.pillarbox.player.tracks.Track
88

99
/**
1010
* The setting for a specific kind a track (audio/text/video).
@@ -18,7 +18,7 @@ data class TracksSettingItem(
1818
/**
1919
* The list of possible tracks.
2020
*/
21-
val tracks: List<Group>,
21+
val tracks: List<Track>,
2222

2323
/**
2424
* `true` if this kind of tracks is disabled, `false` otherwise.

pillarbox-demo-shared/src/main/res/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
<string name="empty_search_query">Enter something to search</string>
1313
<string name="duration"><xliff:g example="15" id="duration">%1$d</xliff:g> min</string>
1414
<string name="settings">Settings</string>
15-
<string name="audio_track">Audio track</string>
15+
<string name="audio_track">Audio tracks</string>
16+
<string name="video_tracks">Video tracks</string>
1617
<string name="subtitles">Subtitles</string>
1718
<string name="speed">Speed</string>
1819
<string name="speed_normal">Normal</string>

0 commit comments

Comments
 (0)