Skip to content

Commit

Permalink
Adds Playback speed
Browse files Browse the repository at this point in the history
  • Loading branch information
kul3r4 committed Apr 24, 2024
1 parent 0bfe3ae commit 46370b9
Show file tree
Hide file tree
Showing 18 changed files with 353 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,14 @@ interface EpisodePlayer {
* Rewinds a currently played episode by a given time interval specified in [duration].
*/
fun rewindBy(duration: Duration)

/**
* Increases the speed of Player playback by a given time specified in [duration].
*/
fun increaseSpeed(speed: Duration = Duration.ofMillis(500))

/**
* Decreases the speed of Player playback by a given time specified in [duration].
*/
fun decreaseSpeed(speed: Duration = Duration.ofMillis(500))
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ class MockEpisodePlayer(
}
}

override fun increaseSpeed(speed: Duration) {
_playerSpeed.value += Duration.ofMillis(500)
}

override fun decreaseSpeed(speed: Duration) {
_playerSpeed.value -= Duration.ofMillis(500)
}

override fun next() {
val q = queue.value
if (q.isEmpty()) {
Expand Down
2 changes: 1 addition & 1 deletion Jetcaster/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ composeMaterial = "1.2.1"
composeFoundation = "1.2.1"
coreSplashscreen = "1.0.1"
horologistComposeTools = "0.4.8"
horologist = "0.6.6"
horologist = "0.6.9"
roborazzi = "1.11.0"
androidx-wear-compose = "1.3.0"
wear-compose-ui-tooling = "1.3.0"
Expand Down
9 changes: 9 additions & 0 deletions Jetcaster/wear/src/main/java/com/example/jetcaster/WearApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import com.example.jetcaster.theme.WearAppTheme
import com.example.jetcaster.ui.Episode
import com.example.jetcaster.ui.JetcasterNavController.navigateToEpisode
import com.example.jetcaster.ui.JetcasterNavController.navigateToLatestEpisode
import com.example.jetcaster.ui.JetcasterNavController.navigateToPlaybackSpeed
import com.example.jetcaster.ui.JetcasterNavController.navigateToPodcastDetails
import com.example.jetcaster.ui.JetcasterNavController.navigateToUpNext
import com.example.jetcaster.ui.JetcasterNavController.navigateToYourPodcast
import com.example.jetcaster.ui.LatestEpisodes
import com.example.jetcaster.ui.PlaybackSpeed
import com.example.jetcaster.ui.PodcastDetails
import com.example.jetcaster.ui.UpNext
import com.example.jetcaster.ui.YourPodcasts
Expand All @@ -40,6 +42,7 @@ import com.example.jetcaster.ui.home.HomeScreen
import com.example.jetcaster.ui.library.LatestEpisodesScreen
import com.example.jetcaster.ui.library.PodcastsScreen
import com.example.jetcaster.ui.library.QueueScreen
import com.example.jetcaster.ui.player.PlaybackSpeedScreen
import com.example.jetcaster.ui.player.PlayerScreen
import com.example.jetcaster.ui.podcast.PodcastDetailsScreen
import com.google.android.horologist.audio.ui.VolumeViewModel
Expand Down Expand Up @@ -69,6 +72,9 @@ fun WearApp() {
onVolumeClick = {
navController.navigateToVolume()
},
onPlaybackSpeedChangeClick = {
navController.navigateToPlaybackSpeed()
},
)
},
libraryScreen = {
Expand Down Expand Up @@ -137,6 +143,9 @@ fun WearApp() {
}
)
}
composable(route = PlaybackSpeed.navRoute) {
PlaybackSpeedScreen()
}
},

)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public object JetcasterNavController {
public fun NavController.navigateToEpisode(episodeUri: String) {
navigate(Episode.destination(episodeUri))
}

public fun NavController.navigateToPlaybackSpeed() {
navigate(PlaybackSpeed.destination())
}
}

public object YourPodcasts : NavigationScreens("yourPodcasts") {
Expand Down Expand Up @@ -90,3 +94,7 @@ public object Episode : NavigationScreens("episode?episodeUri={episodeUri}") {
public object UpNext : NavigationScreens("upNext") {
public fun destination(): String = navRoute
}

public object PlaybackSpeed : NavigationScreens("playbackSpeed") {
public fun destination(): String = navRoute
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ package com.example.jetcaster.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import com.example.jetcaster.R
import com.example.jetcaster.ui.player.PlayerUiState
import com.google.android.horologist.audio.ui.VolumeUiState
import com.google.android.horologist.audio.ui.components.SettingsButtonsDefaults
import com.google.android.horologist.audio.ui.components.actions.SetVolumeButton
Expand All @@ -40,7 +41,8 @@ import com.google.android.horologist.compose.material.IconRtlMode
fun SettingsButtons(
volumeUiState: VolumeUiState,
onVolumeClick: () -> Unit,
onAddToQueueClick: () -> Unit,
playerUiState: PlayerUiState,
onPlaybackSpeedChange: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
Expand All @@ -49,8 +51,11 @@ fun SettingsButtons(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly,
) {
AddToQueueButton(
onAddToQueueClick = onAddToQueueClick,
PlaybackSpeedButton(
currentPlayerSpeed = playerUiState.episodePlayerState
.playbackSpeed.toMillis().toFloat() / 1000,
onPlaybackSpeedChange = onPlaybackSpeedChange,
enabled = enabled
)

SettingsButtonsDefaults.BrandIcon(
Expand All @@ -61,22 +66,29 @@ fun SettingsButtons(
SetVolumeButton(
onVolumeClick = onVolumeClick,
volumeUiState = volumeUiState,
enabled = enabled
)
}
}

@Composable
fun AddToQueueButton(
onAddToQueueClick: () -> Unit,
fun PlaybackSpeedButton(
currentPlayerSpeed: Float,
onPlaybackSpeedChange: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
) {
SettingsButton(
modifier = modifier,
onClick = onAddToQueueClick,
onClick = onPlaybackSpeedChange,
enabled = enabled,
imageVector = Icons.AutoMirrored.Filled.PlaylistAdd,
imageVector =
when (currentPlayerSpeed) {
1f -> ImageVector.vectorResource(R.drawable.speed_1x)
1.5f -> ImageVector.vectorResource(R.drawable.speed_15x)
else -> { ImageVector.vectorResource(R.drawable.speed_2x) }
},
iconRtlMode = IconRtlMode.Mirrored,
contentDescription = stringResource(R.string.add_to_queue_content_description),
contentDescription = stringResource(R.string.change_playback_speed_content_description),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import com.google.android.horologist.media.ui.screens.entity.EntityScreen
QueueScreen(
uiState = uiState,
onPlayButtonClick = onPlayButtonClick,
onPlayEpisodes = queueViewModel::onPlayEpisodes,
modifier = modifier,
onEpisodeItemClick = onEpisodeItemClick,
onDeleteQueueEpisodes = queueViewModel::onDeleteQueueEpisodes,
Expand All @@ -80,6 +81,7 @@ import com.google.android.horologist.media.ui.screens.entity.EntityScreen
fun QueueScreen(
uiState: QueueScreenState,
onPlayButtonClick: () -> Unit,
onPlayEpisodes: (List<PlayerEpisode>) -> Unit,
modifier: Modifier = Modifier,
onEpisodeItemClick: (EpisodeToPodcast) -> Unit,
onDeleteQueueEpisodes: () -> Unit,
Expand Down Expand Up @@ -109,11 +111,9 @@ fun QueueScreen(
},
buttonsContent = {
ButtonsContent(
onPlayButtonClick =
{
onPlayButtonClick
queueViewModel.onPlayEpisode(uiState.episodeList[0])
},
episodes = uiState.episodeList,
onPlayButtonClick = onPlayButtonClick,
onPlayEpisodes = onPlayEpisodes,
onDeleteQueueEpisodes = onDeleteQueueEpisodes
)
},
Expand Down Expand Up @@ -141,7 +141,9 @@ fun QueueScreen(
},
buttonsContent = {
ButtonsContent(
episodes = emptyList(),
onPlayButtonClick = {},
onPlayEpisodes = {},
onDeleteQueueEpisodes = { },
enabled = false
)
Expand All @@ -158,7 +160,7 @@ fun QueueScreen(
showDialog = true,
onDismiss = onDismiss,
title = stringResource(R.string.display_nothing_in_queue),
message = stringResource(R.string.failed_loading_episodes_from_queue)
message = stringResource(R.string.no_episodes_from_queue)
)
}
}
Expand All @@ -168,7 +170,9 @@ fun QueueScreen(
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun ButtonsContent(
episodes: List<PlayerEpisode>,
onPlayButtonClick: () -> Unit,
onPlayEpisodes: (List<PlayerEpisode>) -> Unit,
onDeleteQueueEpisodes: () -> Unit,
enabled: Boolean = true
) {
Expand All @@ -183,7 +187,10 @@ fun ButtonsContent(
Button(
imageVector = Icons.Outlined.PlayArrow,
contentDescription = stringResource(id = R.string.button_play_content_description),
onClick = onPlayButtonClick,
onClick = {
onPlayButtonClick()
onPlayEpisodes(episodes)
},
modifier = Modifier
.weight(weight = 0.3F, fill = false),
enabled = enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class QueueViewModel @Inject constructor(
episodePlayer.play()
}

fun onPlayEpisodes(episodes: List<PlayerEpisode>) {
episodePlayer.currentEpisode = episodes[0]
episodePlayer.play(episodes)
}

fun onDeleteQueueEpisodes() {
episodePlayer.removeAllFromQueue()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.jetcaster.ui.player

import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
import androidx.wear.compose.material.ContentAlpha
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.InlineSlider
import androidx.wear.compose.material.InlineSliderDefaults
import androidx.wear.compose.material.LocalContentAlpha
import androidx.wear.compose.material.Text
import com.example.jetcaster.R
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults.listTextPadding
import com.google.android.horologist.compose.layout.ScreenScaffold
import com.google.android.horologist.compose.layout.rememberResponsiveColumnState
import com.google.android.horologist.compose.material.Chip
import com.google.android.horologist.compose.material.ResponsiveListHeader

/**
* Playback Speed Screen with an [InlineSlider].
*/
@OptIn(ExperimentalWearFoundationApi::class)
@Composable
public fun PlaybackSpeedScreen(
modifier: Modifier = Modifier,
playbackSpeedViewModel: PlaybackSpeedViewModel = hiltViewModel(),
) {
val playbackSpeedUiState by playbackSpeedViewModel.speedUiState.collectAsState()

val columnState = rememberResponsiveColumnState(
contentPadding = ScalingLazyColumnDefaults.padding(
first = ScalingLazyColumnDefaults.ItemType.Text,
last = ScalingLazyColumnDefaults.ItemType.Chip,
),
)
ScreenScaffold(scrollState = columnState) {
ScalingLazyColumn(columnState = columnState) {
item {
ResponsiveListHeader(modifier = Modifier.listTextPadding()) {
Text(stringResource(R.string.speed))
}
}
item {
PlaybackSpeedScreen(
playbackSpeedUiState = playbackSpeedUiState,
increasePlaybackSpeed = playbackSpeedViewModel::increaseSpeed,
decreasePlaybackSpeed = playbackSpeedViewModel::decreaseSpeed,
modifier = modifier
)
}

item {
Text(
text = String.format("%.1fx", playbackSpeedUiState.current),
)
}
}
}
}

@Composable
internal fun PlaybackSpeedScreen(
playbackSpeedUiState: PlaybackSpeedUiState,
increasePlaybackSpeed: () -> Unit,
decreasePlaybackSpeed: () -> Unit,
modifier: Modifier
) {
InlineSlider(
value = playbackSpeedUiState.current,
onValueChange = {
if (it > playbackSpeedUiState.current) increasePlaybackSpeed()
else if (it > 0.5) decreasePlaybackSpeed()
},
increaseIcon = {
Icon(
InlineSliderDefaults.Increase,
stringResource(R.string.increase_playback_speed)
)
},
decreaseIcon = {
CompositionLocalProvider(
LocalContentAlpha provides
if (playbackSpeedUiState.current > 1f)
LocalContentAlpha.current else ContentAlpha.disabled
) {
Icon(
InlineSliderDefaults.Decrease,
stringResource(R.string.decrease_playback_speed)
)
}
},
valueRange = 0.5f..2f,
steps = 2,
segmented = true
)
}
Loading

0 comments on commit 46370b9

Please sign in to comment.