Skip to content

Commit

Permalink
Allow Slider seeking player
Browse files Browse the repository at this point in the history
  • Loading branch information
mlykotom committed Apr 26, 2024
1 parent 16aa20a commit e48f108
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 15 deletions.
Expand Up @@ -67,8 +67,11 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
Expand Down Expand Up @@ -107,8 +110,8 @@ import com.example.jetcaster.util.verticalGradientScrim
import com.google.accompanist.adaptive.HorizontalTwoPaneStrategy
import com.google.accompanist.adaptive.TwoPane
import com.google.accompanist.adaptive.VerticalTwoPaneStrategy
import java.time.Duration
import kotlinx.coroutines.launch
import java.time.Duration

/**
* Stateful version of the Podcast player
Expand All @@ -130,6 +133,8 @@ fun PlayerScreen(
onPausePress = viewModel::onPause,
onAdvanceBy = viewModel::onAdvanceBy,
onRewindBy = viewModel::onRewindBy,
onSeekingStarted = viewModel::onSeekingStarted,
onSeekingFinished = viewModel::onSeekingFinished,
onStop = viewModel::onStop,
onNext = viewModel::onNext,
onPrevious = viewModel::onPrevious,
Expand All @@ -150,6 +155,8 @@ private fun PlayerScreen(
onPausePress: () -> Unit,
onAdvanceBy: (Duration) -> Unit,
onRewindBy: (Duration) -> Unit,
onSeekingStarted: () -> Unit,
onSeekingFinished: (Duration) -> Unit,
onStop: () -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
Expand Down Expand Up @@ -181,6 +188,8 @@ private fun PlayerScreen(
onPausePress = onPausePress,
onAdvanceBy = onAdvanceBy,
onRewindBy = onRewindBy,
onSeekingStarted = onSeekingStarted,
onSeekingFinished = onSeekingFinished,
onNext = onNext,
onPrevious = onPrevious,
onAddToQueue = {
Expand Down Expand Up @@ -219,6 +228,8 @@ fun PlayerContentWithBackground(
onPausePress: () -> Unit,
onAdvanceBy: (Duration) -> Unit,
onRewindBy: (Duration) -> Unit,
onSeekingStarted: () -> Unit,
onSeekingFinished: (Duration) -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
onAddToQueue: () -> Unit,
Expand All @@ -238,6 +249,8 @@ fun PlayerContentWithBackground(
onPausePress = onPausePress,
onAdvanceBy = onAdvanceBy,
onRewindBy = onRewindBy,
onSeekingStarted = onSeekingStarted,
onSeekingFinished = onSeekingFinished,
onNext = onNext,
onPrevious = onPrevious,
onAddToQueue = onAddToQueue,
Expand All @@ -255,6 +268,8 @@ fun PlayerContent(
onPausePress: () -> Unit,
onAdvanceBy: (Duration) -> Unit,
onRewindBy: (Duration) -> Unit,
onSeekingStarted: () -> Unit,
onSeekingFinished: (Duration) -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
onAddToQueue: () -> Unit,
Expand All @@ -275,10 +290,10 @@ fun PlayerContent(
// or we have an impactful horizontal fold. Otherwise, we'll use a horizontal strategy.
val usingVerticalStrategy =
isTableTopPosture(foldingFeature) ||
(
isSeparatingPosture(foldingFeature) &&
foldingFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
)
(
isSeparatingPosture(foldingFeature) &&
foldingFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
)

if (usingVerticalStrategy) {
TwoPane(
Expand All @@ -295,6 +310,8 @@ fun PlayerContent(
onPausePress = onPausePress,
onAdvanceBy = onAdvanceBy,
onRewindBy = onRewindBy,
onSeekingStarted = onSeekingStarted,
onSeekingFinished = onSeekingFinished,
onNext = onNext,
onPrevious = onPrevious,
onAddToQueue = onAddToQueue,
Expand Down Expand Up @@ -331,6 +348,8 @@ fun PlayerContent(
onPausePress = onPausePress,
onAdvanceBy = onAdvanceBy,
onRewindBy = onRewindBy,
onSeekingStarted = onSeekingStarted,
onSeeking = onSeekingFinished,
onNext = onNext,
onPrevious = onPrevious,
)
Expand All @@ -348,6 +367,8 @@ fun PlayerContent(
onPausePress = onPausePress,
onAdvanceBy = onAdvanceBy,
onRewindBy = onRewindBy,
onSeekingStarted = onSeekingStarted,
onSeeking = onSeekingFinished,
onNext = onNext,
onPrevious = onPrevious,
onAddToQueue = onAddToQueue,
Expand All @@ -367,6 +388,8 @@ private fun PlayerContentRegular(
onPausePress: () -> Unit,
onAdvanceBy: (Duration) -> Unit,
onRewindBy: (Duration) -> Unit,
onSeekingStarted: () -> Unit,
onSeeking: (Duration) -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
onAddToQueue: () -> Unit,
Expand Down Expand Up @@ -407,7 +430,9 @@ private fun PlayerContentRegular(
) {
PlayerSlider(
timeElapsed = playerEpisode.timeElapsed,
episodeDuration = currentEpisode.duration
episodeDuration = currentEpisode.duration,
onSeekingStarted = onSeekingStarted,
onSeekingFinished = onSeeking
)
PlayerButtons(
hasNext = playerEpisode.queue.isNotEmpty(),
Expand Down Expand Up @@ -467,6 +492,8 @@ private fun PlayerContentTableTopBottom(
onPausePress: () -> Unit,
onAdvanceBy: (Duration) -> Unit,
onRewindBy: (Duration) -> Unit,
onSeekingStarted: () -> Unit,
onSeekingFinished: (Duration) -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
onAddToQueue: () -> Unit,
Expand Down Expand Up @@ -513,7 +540,9 @@ private fun PlayerContentTableTopBottom(
)
PlayerSlider(
timeElapsed = episodePlayerState.timeElapsed,
episodeDuration = episode.duration
episodeDuration = episode.duration,
onSeekingStarted = onSeekingStarted,
onSeekingFinished = onSeekingFinished
)
}
}
Expand Down Expand Up @@ -556,6 +585,8 @@ private fun PlayerContentBookEnd(
onPausePress: () -> Unit,
onAdvanceBy: (Duration) -> Unit,
onRewindBy: (Duration) -> Unit,
onSeekingStarted: () -> Unit,
onSeeking: (Duration) -> Unit,
onNext: () -> Unit,
onPrevious: () -> Unit,
modifier: Modifier = Modifier
Expand All @@ -577,7 +608,9 @@ private fun PlayerContentBookEnd(
)
PlayerSlider(
timeElapsed = episodePlayerState.timeElapsed,
episodeDuration = episode.duration
episodeDuration = episode.duration,
onSeekingStarted = onSeekingStarted,
onSeekingFinished = onSeeking,
)
PlayerButtons(
hasNext = episodePlayerState.queue.isNotEmpty(),
Expand Down Expand Up @@ -703,21 +736,36 @@ fun Duration.formatString(): String {
}

@Composable
private fun PlayerSlider(timeElapsed: Duration?, episodeDuration: Duration?) {
Column(Modifier.fillMaxWidth()) {
private fun PlayerSlider(
timeElapsed: Duration,
episodeDuration: Duration?,
onSeekingStarted: () -> Unit,
onSeekingFinished: (newElapsed: Duration) -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
var sliderValue by remember(timeElapsed) { mutableStateOf(timeElapsed) }
val maxRange = (episodeDuration?.toSeconds() ?: 0).toFloat()

Row(Modifier.fillMaxWidth()) {
Text(
text = "${timeElapsed?.formatString()}${episodeDuration?.formatString()}",
text = "${sliderValue.formatString()}${episodeDuration?.formatString()}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
val sliderValue = (timeElapsed?.toSeconds() ?: 0).toFloat()
val maxRange = (episodeDuration?.toSeconds() ?: 0).toFloat()

Slider(
value = sliderValue,
value = sliderValue.seconds.toFloat(),
valueRange = 0f..maxRange,
onValueChange = { }
onValueChange = {
onSeekingStarted()
sliderValue = Duration.ofSeconds(it.toLong())
},
onValueChangeFinished = { onSeekingFinished(sliderValue) }
)
}
}
Expand Down Expand Up @@ -913,6 +961,8 @@ fun PlayerScreenPreview() {
onPausePress = {},
onAdvanceBy = {},
onRewindBy = {},
onSeekingStarted = {},
onSeekingFinished = {},
onStop = {},
onNext = {},
onPrevious = {},
Expand Down
Expand Up @@ -100,6 +100,14 @@ class PlayerViewModel @Inject constructor(
episodePlayer.rewindBy(duration)
}

fun onSeekingStarted() {
episodePlayer.onSeekingStarted()
}

fun onSeekingFinished(duration: Duration) {
episodePlayer.onSeekingFinished(duration)
}

fun onAddToQueue() {
uiState.episodePlayerState.currentEpisode?.let {
episodePlayer.addToQueue(it)
Expand Down
Expand Up @@ -103,6 +103,16 @@ interface EpisodePlayer {
*/
fun rewindBy(duration: Duration)

/**
* Signal that user started seeking.
*/
fun onSeekingStarted()

/**
* Seeks to a given time interval specified in [duration].
*/
fun onSeekingFinished(duration: Duration)

/**
* Increases the speed of Player playback by a given time specified in [duration].
*/
Expand Down
Expand Up @@ -173,6 +173,17 @@ class MockEpisodePlayer(
}
}

override fun onSeekingStarted() {
// Need to pause the player so that it doesn't compete with timeline progression.
pause()
}

override fun onSeekingFinished(duration: Duration) {
val currentEpisodeDuration = _currentEpisode.value?.duration ?: return
timeElapsed.update { duration.coerceIn(Duration.ZERO, currentEpisodeDuration) }
play()
}

override fun increaseSpeed(speed: Duration) {
_playerSpeed.value += speed
}
Expand Down

0 comments on commit e48f108

Please sign in to comment.