Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve settings menu on TV demo #366

Merged
merged 9 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.demo.shared.ui.player.settings

/**
* A possible playback speed value.
*/
data class PlaybackSpeedSetting(
/**
* The formatted speed.
*/
val speed: String,

/**
* The speed as [Float].
*/
val rawSpeed: Float,

/**
* `true` if this speed is selected, `false` otherwise.
*/
val isSelected: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
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.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
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.getCurrentTracksAsFlow
import ch.srgssr.pillarbox.player.getPlaybackSpeedAsFlow
import ch.srgssr.pillarbox.player.getTrackSelectionParametersAsFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/**
* Player settings view model
*
* @constructor Create empty Player settings view model
*/
class PlayerSettingsViewModel(
private val player: Player,
private val application: Application
) : AndroidViewModel(application) {
private val trackSelectionParameters = player.getTrackSelectionParametersAsFlow()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.trackSelectionParameters)

private val tracks = player.getCurrentTracksAsFlow()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), player.currentTracks)

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].
*/
val subtitles = combine(
tracks,
trackSelectionParameters
) { tracks, trackSelectionParameters ->
TracksSettingItem(
title = application.getString(R.string.subtitles),
tracks = tracks.text,
disabled = trackSelectionParameters.isTextTrackDisabled
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)

/**
* All the available audio tracks for the current [player].
*/
val audioTracks = combine(
tracks,
trackSelectionParameters
) { tracks, trackSelectionParameters ->
TracksSettingItem(
title = application.getString(R.string.audio_track),
tracks = tracks.audio,
disabled = trackSelectionParameters.isAudioTrackDisabled
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)

/**
* All the available playback speeds for the current [player].
*/
val playbackSpeeds = playbackSpeed.map { playbackSpeed ->
speeds.map { speed ->
PlaybackSpeedSetting(
speed = getSpeedLabel(speed),
rawSpeed = speed,
isSelected = speed == playbackSpeed
)
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())

/**
* Reset the subtitles.
*/
fun resetSubtitles() {
player.setDefaultTextTrack(application)
}

/**
* Disable the subtitles.
*/
fun disableSubtitles() {
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)
}

/**
* Disable the audio track.
*/
fun disableAudioTrack() {
player.disableAudioTrack()
}

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

/**
* Set the playback speed.
*
* @param playbackSpeed The selected playback speed.
*/
fun setPlaybackSpeed(playbackSpeed: PlaybackSpeedSetting) {
player.setPlaybackSpeed(playbackSpeed.rawSpeed)
}

private fun getTracksSubtitle(
tracks: List<Group>,
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
}
}
}
.firstOrNull()
}
}

private fun getSpeedLabel(speed: Float): String {
return if (speed == 1f) {
application.getString(R.string.speed_normal)
} else {
application.getString(R.string.speed_value, speed.toString())
}
}

private companion object {
private val speeds = floatArrayOf(0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f)
}

/**
* Factory
*
* @param player
* @param application
* @constructor Create empty Factory
*/
@Suppress("UndocumentedPublicClass")
class Factory(
private val player: Player,
private val application: Application
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return PlayerSettingsViewModel(player, application) as T
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.demo.shared.ui.player.settings

import androidx.compose.ui.graphics.vector.ImageVector

/**
* Represent a setting item.
*/
data class SettingItem(
/**
* The title of the setting.
*/
val title: String,

/**
* The optional subtitle of the setting.
*/
val subtitle: String?,

/**
* The icon of the setting.
*/
val icon: ImageVector,

/**
* The route of the setting.
*/
val destination: SettingsRoutes
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.demo.shared.ui.player.settings

/**
* All the routes used in the player's settings.
*
* @property route The route of the setting.
*/
sealed class SettingsRoutes(val route: String) {
/**
* The route for the main screen of the settings.
*/
data object Main : SettingsRoutes(route = "settings")

/**
* The route for the playback speed setting.
*/
data object PlaybackSpeed : SettingsRoutes(route = "settings/playback_speed")

/**
* The route for the subtitles setting.
*/
data object Subtitles : SettingsRoutes(route = "settings/subtitles")

/**
* The route for the audio track setting.
*/
data object AudioTrack : SettingsRoutes(route = "settings/audio_track")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) SRG SSR. All rights reserved.
* License information is available from the LICENSE file.
*/
package ch.srgssr.pillarbox.demo.shared.ui.player.settings

import androidx.media3.common.Tracks.Group

/**
* The setting for a specific kind a track (audio/text/video).
*/
data class TracksSettingItem(
/**
* The title of the setting.
*/
val title: String,

/**
* The list of possible tracks.
*/
val tracks: List<Group>,

/**
* `true` if this kind of tracks is disabled, `false` otherwise.
*/
val disabled: Boolean
)
8 changes: 8 additions & 0 deletions pillarbox-demo-shared/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@
<string name="no_results">No results</string>
<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>
Fixed Show fixed Hide fixed
<string name="audio_track">Audio track</string>
<string name="subtitles">Subtitles</string>
<string name="speed">Speed</string>
<string name="speed_normal">Normal</string>
<string name="speed_value"><xliff:g example="1.5" id="speed">%1$s</xliff:g>×</string>
<string name="reset_to_default">Reset to default</string>
Fixed Show fixed Hide fixed
<string name="disabled">Disabled</string>
</resources>
Loading