Skip to content

Commit d689d0f

Browse files
authored
Integrate shuffle/repeat modes inside the demo player (#1044)
1 parent 425843f commit d689d0f

File tree

13 files changed

+367
-288
lines changed

13 files changed

+367
-288
lines changed

pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/DemoPlayerView.kt

Lines changed: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,53 @@ import ch.srgssr.pillarbox.demo.ui.components.ShowSystemUi
4545
import ch.srgssr.pillarbox.demo.ui.player.controls.PlayerBottomToolbar
4646
import ch.srgssr.pillarbox.demo.ui.player.playlist.PlaylistView
4747
import ch.srgssr.pillarbox.demo.ui.player.settings.PlaybackSettingsContent
48+
import ch.srgssr.pillarbox.player.extension.canSetRepeatMode
49+
import ch.srgssr.pillarbox.player.extension.canSetShuffleMode
4850
import ch.srgssr.pillarbox.ui.ScaleMode
51+
import ch.srgssr.pillarbox.ui.extension.availableCommandsAsState
52+
import ch.srgssr.pillarbox.ui.extension.repeatModeAsState
53+
import ch.srgssr.pillarbox.ui.extension.shuffleModeEnabledAsState
4954

5055
/**
5156
* Demo player
5257
*
5358
* @param player The [Player] to observe.
5459
* @param modifier The [Modifier] to be applied to the layout.
55-
* @param pictureInPicture The picture in picture state.
56-
* @param pictureInPictureClick The picture in picture button action. If `null` no button.
60+
* @param pictureInPictureEnabled The picture in picture state.
61+
* @param onPictureInPictureClick The picture in picture button action. If `null` no button.
5762
* @param displayPlaylist If it displays the playlist UI or not.
5863
*/
5964
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalMaterial3Api::class)
6065
@Composable
6166
fun DemoPlayerView(
6267
player: Player,
6368
modifier: Modifier = Modifier,
64-
pictureInPicture: Boolean = false,
65-
pictureInPictureClick: (() -> Unit)? = null,
69+
pictureInPictureEnabled: Boolean = false,
70+
onPictureInPictureClick: (() -> Unit)? = null,
6671
displayPlaylist: Boolean = false,
6772
) {
6873
val windowSizeClass = calculateWindowSizeClass(checkNotNull(LocalActivity.current))
6974
val useSidePanel = windowSizeClass.widthSizeClass >= WindowWidthSizeClass.Medium
75+
val availableCommands by player.availableCommandsAsState()
76+
val shuffleEnabled by player.shuffleModeEnabledAsState()
77+
val onShuffleClick = if (availableCommands.canSetShuffleMode()) {
78+
{ player.shuffleModeEnabled = !player.shuffleModeEnabled }
79+
} else {
80+
null
81+
}
82+
val repeatMode by player.repeatModeAsState()
83+
val onRepeatClick = if (availableCommands.canSetRepeatMode()) {
84+
{
85+
player.repeatMode = when (player.repeatMode) {
86+
Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
87+
Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
88+
Player.REPEAT_MODE_ALL -> Player.REPEAT_MODE_OFF
89+
else -> error("Unrecognized repeat mode ${player.repeatMode}")
90+
}
91+
}
92+
} else {
93+
null
94+
}
7095

7196
if (useSidePanel) {
7297
var showSettings by remember { mutableStateOf(false) }
@@ -77,12 +102,15 @@ fun DemoPlayerView(
77102
modifier = Modifier
78103
.animateContentSize()
79104
.then(if (showSettings) Modifier.weight(0.66f) else Modifier),
80-
pictureInPicture = pictureInPicture,
81-
pictureInPictureClick = pictureInPictureClick,
105+
shuffleEnabled = shuffleEnabled,
106+
onShuffleClick = onShuffleClick,
107+
repeatMode = repeatMode,
108+
onRepeatClick = onRepeatClick,
109+
pictureInPictureEnabled = pictureInPictureEnabled,
110+
onPictureInPictureClick = onPictureInPictureClick,
111+
onSettingsClick = { showSettings = !showSettings },
82112
displayPlaylist = displayPlaylist,
83-
) {
84-
showSettings = !showSettings
85-
}
113+
)
86114

87115
AnimatedVisibility(
88116
visible = showSettings,
@@ -99,10 +127,14 @@ fun DemoPlayerView(
99127
PlayerContent(
100128
player = player,
101129
modifier = Modifier.fillMaxSize(),
102-
pictureInPicture = pictureInPicture,
103-
pictureInPictureClick = pictureInPictureClick,
130+
shuffleEnabled = shuffleEnabled,
131+
onShuffleClick = onShuffleClick,
132+
repeatMode = repeatMode,
133+
onRepeatClick = onRepeatClick,
134+
pictureInPictureEnabled = pictureInPictureEnabled,
135+
onPictureInPictureClick = onPictureInPictureClick,
136+
onSettingsClick = { showSettingsSheet = true },
104137
displayPlaylist = displayPlaylist,
105-
optionClicked = { showSettingsSheet = true },
106138
)
107139

108140
if (showSettingsSheet) {
@@ -122,24 +154,28 @@ private fun PlayerContent(
122154
appSettingsViewModel: AppSettingsViewModel = viewModel<AppSettingsViewModel>(
123155
factory = AppSettingsViewModel.Factory(AppSettingsRepository(LocalContext.current)),
124156
),
125-
pictureInPicture: Boolean = false,
126-
pictureInPictureClick: (() -> Unit)? = null,
127-
displayPlaylist: Boolean = false,
128-
optionClicked: () -> Unit,
157+
shuffleEnabled: Boolean,
158+
onShuffleClick: (() -> Unit)?,
159+
repeatMode: @Player.RepeatMode Int,
160+
onRepeatClick: (() -> Unit)?,
161+
pictureInPictureEnabled: Boolean,
162+
onPictureInPictureClick: (() -> Unit)?,
163+
onSettingsClick: () -> Unit,
164+
displayPlaylist: Boolean,
129165
) {
130-
var fullScreenState by remember {
131-
mutableStateOf(false)
132-
}
166+
var fullScreenEnabled by remember { mutableStateOf(false) }
133167
val appSettings by appSettingsViewModel.currentAppSettings.collectAsStateWithLifecycle()
134-
ShowSystemUi(isShowed = !fullScreenState)
168+
169+
ShowSystemUi(isShowed = !fullScreenEnabled)
170+
135171
Column(modifier = modifier) {
136-
var pinchScaleMode by remember(fullScreenState) {
172+
var pinchScaleMode by remember(fullScreenEnabled) {
137173
mutableStateOf(ScaleMode.Fit)
138174
}
139175
val playerModifier = Modifier
140176
.fillMaxWidth()
141177
.weight(1.0f)
142-
val scalableModifier = if (fullScreenState) {
178+
val scalableModifier = if (fullScreenEnabled) {
143179
playerModifier.then(
144180
Modifier.pointerInput(pinchScaleMode) {
145181
var lastZoomValue = 1.0f
@@ -155,8 +191,8 @@ private fun PlayerContent(
155191
PlayerView(
156192
modifier = scalableModifier,
157193
player = player,
158-
controlsToggleable = !pictureInPicture,
159-
controlsVisible = !pictureInPicture,
194+
controlsToggleable = !pictureInPictureEnabled,
195+
controlsVisible = !pictureInPictureEnabled,
160196
scaleMode = pinchScaleMode,
161197
overlayEnabled = appSettings.metricsOverlayEnabled,
162198
overlayOptions = MetricsOverlayOptions(
@@ -170,13 +206,18 @@ private fun PlayerContent(
170206
) {
171207
PlayerBottomToolbar(
172208
modifier = Modifier.fillMaxWidth(),
173-
fullScreenClicked = { fullScreenState = !fullScreenState },
174-
fullScreenEnabled = fullScreenState,
175-
pictureInPictureClicked = pictureInPictureClick,
176-
optionClicked = optionClicked
209+
shuffleEnabled = shuffleEnabled,
210+
onShuffleClick = onShuffleClick,
211+
repeatMode = repeatMode,
212+
onRepeatClick = onRepeatClick,
213+
pictureInPictureEnabled = pictureInPictureEnabled,
214+
onPictureInPictureClick = onPictureInPictureClick,
215+
fullScreenEnabled = fullScreenEnabled,
216+
onFullscreenClick = { fullScreenEnabled = !fullScreenEnabled },
217+
onSettingsClick = onSettingsClick,
177218
)
178219
}
179-
if (displayPlaylist && !pictureInPicture && !fullScreenState) {
220+
if (displayPlaylist && !pictureInPictureEnabled && !fullScreenEnabled) {
180221
PlaylistView(
181222
modifier = Modifier
182223
.weight(1f)

pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/SimplePlayerActivity.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,12 @@ class SimplePlayerActivity : ComponentActivity(), ServiceConnection {
111111

112112
@Composable
113113
private fun MainContent(player: Player) {
114-
val pictureInPictureClick: (() -> Unit)? = if (isPictureInPicturePossible()) this::startPictureInPicture else null
115-
val pictureInPicture by playerViewModel.pictureInPictureEnabled.collectAsState()
114+
val onPictureInPictureClick: (() -> Unit)? = if (isPictureInPicturePossible()) this::startPictureInPicture else null
115+
val pictureInPictureEnabled by playerViewModel.pictureInPictureEnabled.collectAsState()
116116
DemoPlayerView(
117117
player = player,
118-
pictureInPicture = pictureInPicture,
119-
pictureInPictureClick = pictureInPictureClick,
118+
pictureInPictureEnabled = pictureInPictureEnabled,
119+
onPictureInPictureClick = onPictureInPictureClick,
120120
displayPlaylist = layoutStyle == LAYOUT_PLAYLIST,
121121
)
122122
}

pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/controls/PlayerBottomToolbar.kt

Lines changed: 129 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,69 +4,164 @@
44
*/
55
package ch.srgssr.pillarbox.demo.ui.player.controls
66

7+
import androidx.compose.animation.AnimatedVisibility
8+
import androidx.compose.foundation.background
79
import androidx.compose.foundation.layout.Row
10+
import androidx.compose.foundation.layout.Spacer
811
import androidx.compose.material.icons.Icons
912
import androidx.compose.material.icons.filled.Fullscreen
1013
import androidx.compose.material.icons.filled.FullscreenExit
1114
import androidx.compose.material.icons.filled.PictureInPicture
15+
import androidx.compose.material.icons.filled.Repeat
16+
import androidx.compose.material.icons.filled.RepeatOn
17+
import androidx.compose.material.icons.filled.RepeatOneOn
1218
import androidx.compose.material.icons.filled.Settings
19+
import androidx.compose.material.icons.filled.Shuffle
20+
import androidx.compose.material.icons.filled.ShuffleOn
1321
import androidx.compose.material3.Icon
1422
import androidx.compose.material3.IconButton
23+
import androidx.compose.material3.IconToggleButton
24+
import androidx.compose.material3.LocalContentColor
25+
import androidx.compose.material3.Surface
1526
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.CompositionLocalProvider
28+
import androidx.compose.runtime.getValue
29+
import androidx.compose.runtime.mutableIntStateOf
30+
import androidx.compose.runtime.mutableStateOf
31+
import androidx.compose.runtime.remember
32+
import androidx.compose.runtime.setValue
1633
import androidx.compose.ui.Modifier
1734
import androidx.compose.ui.graphics.Color
35+
import androidx.compose.ui.graphics.vector.ImageVector
1836
import androidx.compose.ui.res.stringResource
19-
import ch.srgssr.pillarbox.demo.shared.R
37+
import androidx.compose.ui.tooling.preview.Preview
38+
import androidx.media3.common.Player
39+
import ch.srgssr.pillarbox.demo.R
40+
import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme
41+
import ch.srgssr.pillarbox.demo.shared.R as sharedR
2042

2143
/**
2244
* Player bottom toolbar that contains Picture in Picture and fullscreen buttons.
2345
*
24-
* @param fullScreenEnabled if fullscreen is enabled
25-
* @param modifier The modifier to be applied to the layout.
26-
* @param fullScreenClicked action when fullscreen button is clicked
27-
* @param pictureInPictureClicked action when picture in picture is clicked
28-
* @param optionClicked action when settings is clicked
46+
* @param modifier The [Modifier] to apply to the layout.
47+
* @param shuffleEnabled Whether the shuffle mode is enabled.
48+
* @param onShuffleClick The action to perform when the shuffle button is clicked. `null` to hide the button.
49+
* @param repeatMode The repeat mode.
50+
* @param onRepeatClick The action to perform when the repeat button is clicked. `null` to hide the button.
51+
* @param pictureInPictureEnabled Whether picture in picture is enabled.
52+
* @param onPictureInPictureClick The action to perform when the picture in picture button is clicked. `null` to hide the button.
53+
* @param fullScreenEnabled Whether fullscreen is enabled.
54+
* @param onFullscreenClick The action to perform when the fullscreen button is clicked. `null` to hide the button.
55+
* @param onSettingsClick The action to perform when the settings button is clicked. `null` to hide the button.
2956
*/
3057
@Composable
3158
fun PlayerBottomToolbar(
32-
fullScreenEnabled: Boolean,
3359
modifier: Modifier = Modifier,
34-
fullScreenClicked: () -> Unit,
35-
pictureInPictureClicked: (() -> Unit)?,
36-
optionClicked: () -> Unit,
60+
shuffleEnabled: Boolean,
61+
onShuffleClick: (() -> Unit)?,
62+
repeatMode: @Player.RepeatMode Int,
63+
onRepeatClick: (() -> Unit)?,
64+
pictureInPictureEnabled: Boolean,
65+
onPictureInPictureClick: (() -> Unit)?,
66+
fullScreenEnabled: Boolean,
67+
onFullscreenClick: (() -> Unit)?,
68+
onSettingsClick: () -> Unit,
3769
) {
3870
Row(modifier = modifier) {
39-
pictureInPictureClicked?.let {
40-
IconButton(onClick = it) {
41-
Icon(
42-
tint = Color.White,
43-
imageVector = Icons.Default.PictureInPicture,
44-
contentDescription = "Picture in picture"
45-
)
46-
}
47-
}
71+
CompositionLocalProvider(LocalContentColor provides Color.White) {
72+
ToggleableIconButton(
73+
checked = shuffleEnabled,
74+
icon = if (shuffleEnabled) Icons.Default.ShuffleOn else Icons.Default.Shuffle,
75+
contentDestination = stringResource(R.string.shuffle),
76+
onCheckedChange = onShuffleClick,
77+
)
4878

49-
IconButton(onClick = fullScreenClicked) {
50-
if (fullScreenEnabled) {
51-
Icon(
52-
tint = Color.White,
53-
imageVector = Icons.Default.FullscreenExit,
54-
contentDescription = "Exit fullscreen"
55-
)
56-
} else {
79+
ToggleableIconButton(
80+
checked = repeatMode != Player.REPEAT_MODE_OFF,
81+
icon = when (repeatMode) {
82+
Player.REPEAT_MODE_OFF -> Icons.Default.Repeat
83+
Player.REPEAT_MODE_ONE -> Icons.Default.RepeatOneOn
84+
Player.REPEAT_MODE_ALL -> Icons.Default.RepeatOn
85+
else -> error("Unrecognized repeat mode $repeatMode")
86+
},
87+
contentDestination = stringResource(R.string.repeat_mode),
88+
onCheckedChange = onRepeatClick,
89+
)
90+
91+
Spacer(modifier = Modifier.weight(1f))
92+
93+
ToggleableIconButton(
94+
checked = pictureInPictureEnabled,
95+
icon = Icons.Default.PictureInPicture,
96+
contentDestination = stringResource(R.string.picture_in_picture),
97+
onCheckedChange = onPictureInPictureClick,
98+
)
99+
100+
ToggleableIconButton(
101+
checked = fullScreenEnabled,
102+
icon = if (fullScreenEnabled) Icons.Default.FullscreenExit else Icons.Default.Fullscreen,
103+
contentDestination = stringResource(R.string.fullscreen),
104+
onCheckedChange = onFullscreenClick,
105+
)
106+
107+
IconButton(onClick = onSettingsClick) {
57108
Icon(
58-
tint = Color.White,
59-
imageVector = Icons.Default.Fullscreen,
60-
contentDescription = "Enter fullscreen"
109+
imageVector = Icons.Default.Settings,
110+
contentDescription = stringResource(sharedR.string.settings),
61111
)
62112
}
63113
}
114+
}
115+
}
64116

65-
IconButton(onClick = optionClicked) {
117+
@Composable
118+
private fun ToggleableIconButton(
119+
checked: Boolean,
120+
icon: ImageVector,
121+
contentDestination: String,
122+
onCheckedChange: (() -> Unit)?,
123+
) {
124+
AnimatedVisibility(visible = onCheckedChange != null) {
125+
IconToggleButton(
126+
checked = checked,
127+
onCheckedChange = { onCheckedChange?.invoke() },
128+
) {
66129
Icon(
67-
tint = Color.White,
68-
imageVector = Icons.Default.Settings,
69-
contentDescription = stringResource(R.string.settings)
130+
imageVector = icon,
131+
contentDescription = contentDestination,
132+
)
133+
}
134+
}
135+
}
136+
137+
@Preview
138+
@Composable
139+
private fun PlayerBottomToolbarPreview() {
140+
var shuffleEnabled by remember { mutableStateOf(false) }
141+
var repeatMode by remember { mutableIntStateOf(Player.REPEAT_MODE_OFF) }
142+
var pictureInPictureEnabled by remember { mutableStateOf(false) }
143+
var fullscreenEnabled by remember { mutableStateOf(false) }
144+
145+
PillarboxTheme {
146+
Surface {
147+
PlayerBottomToolbar(
148+
modifier = Modifier.background(Color.Black),
149+
shuffleEnabled = shuffleEnabled,
150+
onShuffleClick = { shuffleEnabled = !shuffleEnabled },
151+
repeatMode = repeatMode,
152+
onRepeatClick = {
153+
repeatMode = when (repeatMode) {
154+
Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ONE
155+
Player.REPEAT_MODE_ONE -> Player.REPEAT_MODE_ALL
156+
Player.REPEAT_MODE_ALL -> Player.REPEAT_MODE_OFF
157+
else -> error("Unrecognized repeat mode $repeatMode")
158+
}
159+
},
160+
pictureInPictureEnabled = pictureInPictureEnabled,
161+
onPictureInPictureClick = { pictureInPictureEnabled = !pictureInPictureEnabled },
162+
fullScreenEnabled = fullscreenEnabled,
163+
onFullscreenClick = { fullscreenEnabled = !fullscreenEnabled },
164+
onSettingsClick = {},
70165
)
71166
}
72167
}

0 commit comments

Comments
 (0)