PlayableState.Playable should only contain data useful for other components.

This commit is contained in:
Benoit Marty 2024-11-28 14:38:58 +01:00
parent 2cba5997b7
commit ff3355f453
5 changed files with 42 additions and 48 deletions

View file

@ -30,6 +30,7 @@ import androidx.compose.material.icons.outlined.GraphicEq
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -172,17 +173,26 @@ private fun ExoPlayerMediaVideoView(
localMedia: LocalMedia?, localMedia: LocalMedia?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
var playableState: PlayableState.Playable by remember { var mediaPlayerControllerState: MediaPlayerControllerState by remember {
mutableStateOf( mutableStateOf(
PlayableState.Playable( MediaPlayerControllerState(
isVisible = false,
isPlaying = false, isPlaying = false,
progressInMillis = 0, progressInMillis = 0,
durationInMillis = 0, durationInMillis = 0,
isShowingControls = false,
isMuted = false, isMuted = false,
) )
) )
} }
val playableState: PlayableState.Playable by remember {
derivedStateOf {
PlayableState.Playable(
isShowingControls = mediaPlayerControllerState.isVisible,
)
}
}
localMediaViewState.playableState = playableState localMediaViewState.playableState = playableState
val context = LocalContext.current val context = LocalContext.current
@ -195,16 +205,20 @@ private fun ExoPlayerMediaVideoView(
} }
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
playableState = playableState.copy(isPlaying = isPlaying) mediaPlayerControllerState = mediaPlayerControllerState.copy(
isPlaying = isPlaying,
)
} }
override fun onVolumeChanged(volume: Float) { override fun onVolumeChanged(volume: Float) {
playableState = playableState.copy(isMuted = volume == 0f) mediaPlayerControllerState = mediaPlayerControllerState.copy(
isMuted = volume == 0f,
)
} }
override fun onTimelineChanged(timeline: Timeline, reason: Int) { override fun onTimelineChanged(timeline: Timeline, reason: Int) {
if (reason == Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) { if (reason == Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE) {
playableState = playableState.copy( mediaPlayerControllerState = mediaPlayerControllerState.copy(
durationInMillis = exoPlayer.duration, durationInMillis = exoPlayer.duration,
) )
} }
@ -221,21 +235,23 @@ private fun ExoPlayerMediaVideoView(
LaunchedEffect(autoHideController) { LaunchedEffect(autoHideController) {
delay(5.seconds) delay(5.seconds)
if (exoPlayer.isPlaying) { if (exoPlayer.isPlaying) {
playableState = playableState.copy(isShowingControls = false) mediaPlayerControllerState = mediaPlayerControllerState.copy(
isVisible = false,
)
} }
} }
LaunchedEffect(exoPlayer.isPlaying) { LaunchedEffect(exoPlayer.isPlaying) {
if (exoPlayer.isPlaying) { if (exoPlayer.isPlaying) {
while (true) { while (true) {
playableState = playableState.copy( mediaPlayerControllerState = mediaPlayerControllerState.copy(
progressInMillis = exoPlayer.currentPosition, progressInMillis = exoPlayer.currentPosition,
) )
delay(200) delay(200)
} }
} else { } else {
// Ensure we render the final state // Ensure we render the final state
playableState = playableState.copy( mediaPlayerControllerState = mediaPlayerControllerState.copy(
progressInMillis = exoPlayer.currentPosition, progressInMillis = exoPlayer.currentPosition,
) )
} }
@ -248,7 +264,7 @@ private fun ExoPlayerMediaVideoView(
} else { } else {
exoPlayer.setMediaItems(emptyList()) exoPlayer.setMediaItems(emptyList())
} }
KeepScreenOn(playableState.isPlaying) KeepScreenOn(mediaPlayerControllerState.isPlaying)
Box( Box(
modifier = modifier modifier = modifier
.background(ElementTheme.colors.bgSubtlePrimary) .background(ElementTheme.colors.bgSubtlePrimary)
@ -263,7 +279,9 @@ private fun ExoPlayerMediaVideoView(
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
setOnClickListener { setOnClickListener {
autoHideController++ autoHideController++
playableState = playableState.copy(isShowingControls = !playableState.isShowingControls) mediaPlayerControllerState = mediaPlayerControllerState.copy(
isVisible = !mediaPlayerControllerState.isVisible,
)
} }
useController = false useController = false
} }
@ -275,10 +293,7 @@ private fun ExoPlayerMediaVideoView(
}, },
) )
MediaPlayerControllerView( MediaPlayerControllerView(
state = MediaPlayerControllerState( state = mediaPlayerControllerState,
isVisible = playableState.isShowingControls,
playableState = playableState,
),
onTogglePlay = { onTogglePlay = {
autoHideController++ autoHideController++
if (exoPlayer.isPlaying) { if (exoPlayer.isPlaying) {

View file

@ -29,11 +29,7 @@ class LocalMediaViewState internal constructor(
sealed interface PlayableState { sealed interface PlayableState {
data object NotPlayable : PlayableState data object NotPlayable : PlayableState
data class Playable( data class Playable(
val isPlaying: Boolean,
val progressInMillis: Long,
val durationInMillis: Long,
val isShowingControls: Boolean, val isShowingControls: Boolean,
val isMuted: Boolean,
) : PlayableState ) : PlayableState
} }

View file

@ -7,9 +7,10 @@
package io.element.android.libraries.mediaviewer.api.player package io.element.android.libraries.mediaviewer.api.player
import io.element.android.libraries.mediaviewer.api.local.PlayableState
data class MediaPlayerControllerState( data class MediaPlayerControllerState(
val isVisible: Boolean, val isVisible: Boolean,
val playableState: PlayableState.Playable, val isPlaying: Boolean,
val progressInMillis: Long,
val durationInMillis: Long,
val isMuted: Boolean,
) )

View file

@ -8,7 +8,6 @@
package io.element.android.libraries.mediaviewer.api.player package io.element.android.libraries.mediaviewer.api.player
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.mediaviewer.api.local.PlayableState
open class MediaPlayerControllerStateProvider : PreviewParameterProvider<MediaPlayerControllerState> { open class MediaPlayerControllerStateProvider : PreviewParameterProvider<MediaPlayerControllerState> {
override val values: Sequence<MediaPlayerControllerState> = sequenceOf( override val values: Sequence<MediaPlayerControllerState> = sequenceOf(
@ -24,32 +23,15 @@ open class MediaPlayerControllerStateProvider : PreviewParameterProvider<MediaPl
private fun aMediaPlayerControllerState( private fun aMediaPlayerControllerState(
isVisible: Boolean = true, isVisible: Boolean = true,
isPlaying: Boolean = false, isPlaying: Boolean = true,
progressInMillis: Long = 0, progressInMillis: Long = 0,
// Default to 1 minute and 23 seconds // Default to 1 minute and 23 seconds
durationInMillis: Long = 83_000, durationInMillis: Long = 83_000,
isMuted: Boolean = false, isMuted: Boolean = false,
) = MediaPlayerControllerState( ) = MediaPlayerControllerState(
isVisible = isVisible, isVisible = isVisible,
playableState = aPlayableState(
isPlaying = isPlaying,
progressInMillis = progressInMillis,
durationInMillis = durationInMillis,
isMuted = isMuted,
),
)
private fun aPlayableState(
isPlaying: Boolean = false,
progressInMillis: Long = 0,
// Default to 1 minute and 23 seconds
durationInMillis: Long = 83_000,
isShowingControls: Boolean = false,
isMuted: Boolean = false,
) = PlayableState.Playable(
isPlaying = isPlaying, isPlaying = isPlaying,
progressInMillis = progressInMillis, progressInMillis = progressInMillis,
durationInMillis = durationInMillis, durationInMillis = durationInMillis,
isShowingControls = isShowingControls,
isMuted = isMuted, isMuted = isMuted,
) )

View file

@ -66,7 +66,7 @@ fun MediaPlayerControllerView(
IconButton( IconButton(
onClick = onTogglePlay, onClick = onTogglePlay,
) { ) {
if (state.playableState.isPlaying) { if (state.isPlaying) {
Icon( Icon(
imageVector = CompoundIcons.PauseSolid(), imageVector = CompoundIcons.PauseSolid(),
tint = ElementTheme.colors.iconPrimary, tint = ElementTheme.colors.iconPrimary,
@ -84,7 +84,7 @@ fun MediaPlayerControllerView(
modifier = Modifier modifier = Modifier
.widthIn(min = 48.dp) .widthIn(min = 48.dp)
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
text = state.playableState.progressInMillis.toHumanReadableDuration(), text = state.progressInMillis.toHumanReadableDuration(),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = ElementTheme.colors.textPrimary, color = ElementTheme.colors.textPrimary,
style = ElementTheme.typography.fontBodyXsMedium, style = ElementTheme.typography.fontBodyXsMedium,
@ -92,8 +92,8 @@ fun MediaPlayerControllerView(
var lastSelectedValue by remember { mutableFloatStateOf(-1f) } var lastSelectedValue by remember { mutableFloatStateOf(-1f) }
Slider( Slider(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
valueRange = 0f..state.playableState.durationInMillis.toFloat(), valueRange = 0f..state.durationInMillis.toFloat(),
value = lastSelectedValue.takeIf { it >= 0 } ?: state.playableState.progressInMillis.toFloat(), value = lastSelectedValue.takeIf { it >= 0 } ?: state.progressInMillis.toFloat(),
onValueChange = { onValueChange = {
lastSelectedValue = it lastSelectedValue = it
}, },
@ -103,8 +103,8 @@ fun MediaPlayerControllerView(
}, },
useCustomLayout = true, useCustomLayout = true,
) )
val formattedDuration = remember(state.playableState.durationInMillis) { val formattedDuration = remember(state.durationInMillis) {
state.playableState.durationInMillis.toHumanReadableDuration() state.durationInMillis.toHumanReadableDuration()
} }
Text( Text(
modifier = Modifier modifier = Modifier
@ -118,7 +118,7 @@ fun MediaPlayerControllerView(
IconButton( IconButton(
onClick = onToggleMute, onClick = onToggleMute,
) { ) {
if (state.playableState.isMuted) { if (state.isMuted) {
Icon( Icon(
imageVector = CompoundIcons.VolumeOffSolid(), imageVector = CompoundIcons.VolumeOffSolid(),
tint = ElementTheme.colors.iconPrimary, tint = ElementTheme.colors.iconPrimary,