Skip to content
Draft
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
Expand Up @@ -24,6 +24,7 @@ import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.os.PowerManager
import android.os.SystemClock
import android.provider.MediaStore
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaMetadataCompat
Expand Down Expand Up @@ -804,6 +805,28 @@ class MediaControllerService : Service(), MessageClient.OnMessageReceivedListene
if (!isNotificationListenerEnabled(messageEvent)) return
mController?.transportControls?.skipToNext()
}
MediaHelper.MediaSeekForwardPath -> {
if (!isNotificationListenerEnabled(messageEvent)) return
val state = mController?.playbackState ?: return
val currentPositionMs = if (state.state == android.support.v4.media.session.PlaybackStateCompat.STATE_PLAYING) {
val elapsed = SystemClock.elapsedRealtime() - state.lastPositionUpdateTime
state.position + (elapsed * state.playbackSpeed).toLong()
} else {
state.position
}
mController?.transportControls?.seekTo((currentPositionMs + 10_000L).coerceAtLeast(0L))
}
MediaHelper.MediaSeekBackwardPath -> {
if (!isNotificationListenerEnabled(messageEvent)) return
val state = mController?.playbackState ?: return
val currentPositionMs = if (state.state == android.support.v4.media.session.PlaybackStateCompat.STATE_PLAYING) {
val elapsed = SystemClock.elapsedRealtime() - state.lastPositionUpdateTime
state.position + (elapsed * state.playbackSpeed).toLong()
} else {
state.position
}
mController?.transportControls?.seekTo((currentPositionMs - 10_000L).coerceAtLeast(0L))
}
MediaHelper.MediaPlayFromSearchPath -> {
if (!isNotificationListenerEnabled(messageEvent)) return
playFromSearchController(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ object MediaHelper {
const val MediaPlayFromSearchPath = "/media/searchplay"
const val MediaPlayerDisconnectPath = "/media/disconnect"

const val MediaSeekForwardPath = "/media/action/seek_forward"
const val MediaSeekBackwardPath = "/media/action/seek_backward"

const val MediaVolumeUpPath = "/media/volume/up"
const val MediaVolumeDownPath = "/media/volume/down"
const val MediaVolumeStatusPath = "/media/volume/status"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,14 @@ class MediaPlayerViewModel(app: Application) : WearableListenerViewModel(app) {
requestMediaAction(MediaHelper.MediaNextPath)
}

fun requestSeekForward() {
requestMediaAction(MediaHelper.MediaSeekForwardPath)
}

fun requestSeekBackward() {
requestMediaAction(MediaHelper.MediaSeekBackwardPath)
}

fun requestVolumeUp() {
requestMediaAction(MediaHelper.MediaVolumeUpPath)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ interface PlayerUiController {
fun pause()
fun skipToPreviousMedia()
fun skipToNextMedia()
fun seekForward()
fun seekBackward()
}

class NoopPlayerUiController : PlayerUiController {
Expand All @@ -15,6 +17,10 @@ class NoopPlayerUiController : PlayerUiController {
override fun skipToPreviousMedia() {}

override fun skipToNextMedia() {}

override fun seekForward() {}

override fun seekBackward() {}
}

class MediaPlayerUiController(private val mediaPlayerViewModel: MediaPlayerViewModel) :
Expand All @@ -26,4 +32,8 @@ class MediaPlayerUiController(private val mediaPlayerViewModel: MediaPlayerViewM
override fun skipToPreviousMedia() = mediaPlayerViewModel.requestSkipToPreviousAction()

override fun skipToNextMedia() = mediaPlayerViewModel.requestSkipToNextAction()

override fun seekForward() = mediaPlayerViewModel.requestSeekForward()

override fun seekBackward() = mediaPlayerViewModel.requestSeekBackward()
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Forward10
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.Replay10
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
Expand Down Expand Up @@ -561,27 +563,62 @@ private fun MediaPlayerControlsPage(
controlButtons = {
if (!isAmbient) {
CompositionLocalProvider(LocalTimestampProvider provides timestampProvider) {
AnimatedMediaControlButtons(
onPlayButtonClick = {
playerUiController.play()
},
onPauseButtonClick = {
playerUiController.pause()
},
playPauseButtonEnabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
playing = playerState.playbackState == PlaybackState.PLAYING,
onSeekToPreviousButtonClick = {
playerUiController.skipToPreviousMedia()
},
seekToPreviousButtonEnabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
onSeekToNextButtonClick = {
playerUiController.skipToNextMedia()
},
seekToNextButtonEnabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
trackPositionUiModel = TrackPositionUiModelMapper.map(
playbackStateEvent
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.fillMaxWidth(0.82f)
.padding(bottom = 2.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
FilledIconButton(
onClick = { playerUiController.seekBackward() },
modifier = Modifier.size(32.dp),
enabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING
) {
Icon(
imageVector = Icons.Rounded.Replay10,
contentDescription = "-10s",
modifier = Modifier.size(20.dp)
)
}
FilledIconButton(
onClick = { playerUiController.seekForward() },
modifier = Modifier.size(32.dp),
enabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING
) {
Icon(
imageVector = Icons.Rounded.Forward10,
contentDescription = "+10s",
modifier = Modifier.size(20.dp)
)
}
}
AnimatedMediaControlButtons(
onPlayButtonClick = {
playerUiController.play()
},
onPauseButtonClick = {
playerUiController.pause()
},
playPauseButtonEnabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
playing = playerState.playbackState == PlaybackState.PLAYING,
onSeekToPreviousButtonClick = {
playerUiController.skipToPreviousMedia()
},
seekToPreviousButtonEnabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
onSeekToNextButtonClick = {
playerUiController.skipToNextMedia()
},
seekToNextButtonEnabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
trackPositionUiModel = TrackPositionUiModelMapper.map(
playbackStateEvent
)
)
)
}
}
} else {
val leftButtonPadding =
Expand All @@ -599,22 +636,54 @@ private fun MediaPlayerControlsPage(
playPauseButtonEnabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
playing = playerState.playbackState == PlaybackState.PLAYING,
leftButton = {
AmbientSeekToPreviousButton(
onClick = {
playerUiController.skipToPreviousMedia()
},
buttonPadding = leftButtonPadding,
enabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
)
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
FilledIconButton(
onClick = { playerUiController.seekBackward() },
modifier = Modifier.size(28.dp),
enabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING
) {
Icon(
imageVector = Icons.Rounded.Replay10,
contentDescription = "-10s",
modifier = Modifier.size(18.dp)
)
}
Spacer(modifier = Modifier.height(4.dp))
AmbientSeekToPreviousButton(
onClick = {
playerUiController.skipToPreviousMedia()
},
buttonPadding = leftButtonPadding,
enabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
)
}
},
rightButton = {
AmbientSeekToNextButton(
onClick = {
playerUiController.skipToNextMedia()
},
buttonPadding = rightButtonPadding,
enabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
)
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
FilledIconButton(
onClick = { playerUiController.seekForward() },
modifier = Modifier.size(28.dp),
enabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING
) {
Icon(
imageVector = Icons.Rounded.Forward10,
contentDescription = "+10s",
modifier = Modifier.size(18.dp)
)
}
Spacer(modifier = Modifier.height(4.dp))
AmbientSeekToNextButton(
onClick = {
playerUiController.skipToNextMedia()
},
buttonPadding = rightButtonPadding,
enabled = !uiState.isPlaybackLoading || playerState.playbackState > PlaybackState.LOADING,
)
}
}
)
}
Expand Down