Skip to content

Commit

Permalink
Provide simplified tracks management (#495)
Browse files Browse the repository at this point in the history
Co-authored-by: Joaquim Stähli <[email protected]>
  • Loading branch information
MGaetan89 and StaehliJ authored Apr 23, 2024
1 parent c2a4330 commit 1c3346e
Show file tree
Hide file tree
Showing 22 changed files with 1,292 additions and 247 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import ch.srgssr.pillarbox.analytics.commandersact.CommandersAct
import ch.srgssr.pillarbox.analytics.commandersact.MediaEventType
import ch.srgssr.pillarbox.analytics.commandersact.TCMediaEvent
import ch.srgssr.pillarbox.core.business.tracker.TotalPlaytimeCounter
import ch.srgssr.pillarbox.player.extension.audio
import ch.srgssr.pillarbox.player.extension.isForced
import ch.srgssr.pillarbox.player.tracks.audioTracks
import ch.srgssr.pillarbox.player.utils.DebugLogger
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -233,15 +233,15 @@ internal class CommandersActStreaming(
}
}

@Suppress("SwallowedException")
private fun handleAudioTrack(event: TCMediaEvent) {
try {
val selectedAudioGroup = player.currentTracks.audio.first { it.isSelected }
val selectedFormat: Format = selectedAudioGroup.getTrackFormat(0)
event.audioTrackLanguage = selectedFormat.language ?: C.LANGUAGE_UNDETERMINED
} catch (e: NoSuchElementException) {
event.audioTrackLanguage = C.LANGUAGE_UNDETERMINED
}
val audioTrackLanguage = player.currentTracks
.audioTracks
.find { it.isSelected }
?.format
?.language
?: C.LANGUAGE_UNDETERMINED

event.audioTrackLanguage = audioTrackLanguage
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,37 @@ 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
import androidx.lifecycle.viewModelScope
import androidx.media3.common.C
import androidx.media3.common.Player
import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.Tracks.Group
import ch.srgssr.pillarbox.demo.shared.R
import ch.srgssr.pillarbox.player.extension.audio
import ch.srgssr.pillarbox.player.extension.disableAudioTrack
import ch.srgssr.pillarbox.player.extension.disableTextTrack
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.setDefaultAudioTrack
import ch.srgssr.pillarbox.player.extension.setDefaultTextTrack
import ch.srgssr.pillarbox.player.extension.setTrackOverride
import ch.srgssr.pillarbox.player.extension.text
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
Expand All @@ -54,54 +60,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 {
add(
SettingItem(
title = application.getString(R.string.speed),
subtitle = getSpeedLabel(playbackSpeed),
icon = Icons.Default.Speed,
destination = SettingsRoutes.PlaybackSpeed
)
)

if (currentTracks.text.isNotEmpty()) {
add(
SettingItem(
title = application.getString(R.string.subtitles),
subtitle = getTracksSubtitle(
tracks = currentTracks.text,
disabled = trackSelectionParameters.isTextTrackDisabled
),
icon = Icons.Default.Subtitles,
destination = SettingsRoutes.Subtitles
)
)
}

if (currentTracks.audio.isNotEmpty()) {
add(
SettingItem(
title = application.getString(R.string.audio_track),
subtitle = getTracksSubtitle(
tracks = currentTracks.audio,
disabled = trackSelectionParameters.isAudioTrackDisabled
),
icon = Icons.Default.Audiotrack,
destination = SettingsRoutes.AudioTrack
)
)
}
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

/**
* All the available subtitle for the current [player].
*/
Expand All @@ -111,7 +69,7 @@ class PlayerSettingsViewModel(
) { tracks, trackSelectionParameters ->
TracksSettingItem(
title = application.getString(R.string.subtitles),
tracks = tracks.text,
tracks = tracks.textTracks,
disabled = trackSelectionParameters.isTextTrackDisabled
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
Expand All @@ -125,11 +83,29 @@ class PlayerSettingsViewModel(
) { tracks, trackSelectionParameters ->
TracksSettingItem(
title = application.getString(R.string.audio_track),
tracks = tracks.audio,
tracks = tracks.audioTracks,
disabled = trackSelectionParameters.isAudioTrackDisabled
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)

/**
* All the available video tracks for the current [player].
*/
val videoTracks = combine(
tracks,
trackSelectionParameters,
) { tracks, trackSelectionParameters ->
TracksSettingItem(
title = application.getString(R.string.video_tracks),
tracks = tracks.videoTracks
.sortedWith(
compareByDescending<VideoTrack> { it.format.height }
.thenByDescending { it.format.bitrate }
),
disabled = trackSelectionParameters.isVideoTrackDisabled,
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)

/**
* All the available playback speeds for the current [player].
*/
Expand All @@ -143,11 +119,84 @@ class PlayerSettingsViewModel(
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

/**
* All the available settings for the current [player].
*/
val settings = combine(
subtitles,
audioTracks,
videoTracks,
trackSelectionParameters,
playbackSpeed,
) { subtitles, audioTracks, videoTracks, 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 (videoTracks != null && videoTracks.tracks.isNotEmpty()) {
add(
SettingItem(
title = application.getString(R.string.video_tracks),
subtitle = getTracksSubtitle(
tracks = videoTracks.tracks,
disabled = trackSelectionParameters.isVideoTrackDisabled,
),
icon = Icons.Default.Tune,
destination = SettingsRoutes.VideoTrack,
)
)
}
}
}.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.
*/
fun resetSubtitles() {
player.setDefaultTextTrack(application)
player.setAutoTextTrack(application)
}

/**
Expand All @@ -157,21 +206,11 @@ class PlayerSettingsViewModel(
player.disableTextTrack()
}

/**
* Set the subtitles.
*
* @param group The selected group.
* @param trackIndex The index of the track in the provided group.
*/
fun setSubtitle(group: Group, trackIndex: Int) {
player.setTrackOverride(TrackSelectionOverride(group.mediaTrackGroup, trackIndex))
}

/**
* Reset the audio track.
*/
fun resetAudioTrack() {
player.setDefaultAudioTrack(application)
player.setAutoAudioTrack(application)
}

/**
Expand All @@ -182,13 +221,17 @@ class PlayerSettingsViewModel(
}

/**
* Set the audio track.
*
* @param group The selected group.
* @param trackIndex The index of the track in the provided group.
* Reset the video track.
*/
fun resetVideoTrack() {
player.setAutoVideoTrack()
}

/**
* Disable the video track.
*/
fun setAudioTrack(group: Group, trackIndex: Int) {
player.setTrackOverride(TrackSelectionOverride(group.mediaTrackGroup, trackIndex))
fun disableVideoTrack() {
player.disableVideoTrack()
}

/**
Expand All @@ -201,22 +244,15 @@ class PlayerSettingsViewModel(
}

private fun getTracksSubtitle(
tracks: List<Group>,
disabled: Boolean
tracks: List<Track>,
disabled: Boolean,
): String? {
return if (disabled) {
application.getString(R.string.disabled)
} else {
tracks.filter { it.isSelected }
.flatMap {
(0 until it.length).mapNotNull { trackIndex ->
if (it.isTrackSelected(trackIndex)) {
it.getTrackFormat(trackIndex).displayName
} else {
null
}
}
}
.map { it.format.displayName }
.filter { it != C.LANGUAGE_UNDETERMINED }
.firstOrNull()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 track setting.
*/
data object VideoTrack : SettingsRoutes(route = "settings/video_track")
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
package ch.srgssr.pillarbox.demo.shared.ui.player.settings

import androidx.media3.common.Tracks.Group
import ch.srgssr.pillarbox.player.tracks.Track

/**
* The setting for a specific kind a track (audio/text/video).
Expand All @@ -18,7 +18,7 @@ data class TracksSettingItem(
/**
* The list of possible tracks.
*/
val tracks: List<Group>,
val tracks: List<Track>,

/**
* `true` if this kind of tracks is disabled, `false` otherwise.
Expand Down
3 changes: 2 additions & 1 deletion pillarbox-demo-shared/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
<string name="empty_search_query">Enter something to search</string>
<string name="duration"><xliff:g example="15" id="duration">%1$d</xliff:g> min</string>
<string name="settings">Settings</string>
<string name="audio_track">Audio track</string>
<string name="audio_track">Audio tracks</string>
<string name="video_tracks">Video tracks</string>
<string name="subtitles">Subtitles</string>
<string name="speed">Speed</string>
<string name="speed_normal">Normal</string>
Expand Down
Loading

0 comments on commit 1c3346e

Please sign in to comment.