diff --git a/mobile/src/main/java/com/thewizrd/simplewear/media/MediaControllerService.kt b/mobile/src/main/java/com/thewizrd/simplewear/media/MediaControllerService.kt
index e5696427..a0e5cc5a 100644
--- a/mobile/src/main/java/com/thewizrd/simplewear/media/MediaControllerService.kt
+++ b/mobile/src/main/java/com/thewizrd/simplewear/media/MediaControllerService.kt
@@ -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
@@ -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)
diff --git a/shared_resources/src/main/java/com/thewizrd/shared_resources/helpers/MediaHelper.kt b/shared_resources/src/main/java/com/thewizrd/shared_resources/helpers/MediaHelper.kt
index 0a50af8c..f0917cae 100644
--- a/shared_resources/src/main/java/com/thewizrd/shared_resources/helpers/MediaHelper.kt
+++ b/shared_resources/src/main/java/com/thewizrd/shared_resources/helpers/MediaHelper.kt
@@ -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"
diff --git a/shared_resources/src/main/res/drawable/ic_forward_10_white_24dp.xml b/shared_resources/src/main/res/drawable/ic_forward_10_white_24dp.xml
new file mode 100644
index 00000000..fbefb603
--- /dev/null
+++ b/shared_resources/src/main/res/drawable/ic_forward_10_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/shared_resources/src/main/res/drawable/ic_replay_10_white_24dp.xml b/shared_resources/src/main/res/drawable/ic_replay_10_white_24dp.xml
new file mode 100644
index 00000000..48b66699
--- /dev/null
+++ b/shared_resources/src/main/res/drawable/ic_replay_10_white_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/wear/src/main/java/com/thewizrd/simplewear/media/MediaPlayerViewModel.kt b/wear/src/main/java/com/thewizrd/simplewear/media/MediaPlayerViewModel.kt
index 7c520be0..affeee24 100644
--- a/wear/src/main/java/com/thewizrd/simplewear/media/MediaPlayerViewModel.kt
+++ b/wear/src/main/java/com/thewizrd/simplewear/media/MediaPlayerViewModel.kt
@@ -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)
}
diff --git a/wear/src/main/java/com/thewizrd/simplewear/media/PlayerUiController.kt b/wear/src/main/java/com/thewizrd/simplewear/media/PlayerUiController.kt
index 80d6b131..08b245a2 100644
--- a/wear/src/main/java/com/thewizrd/simplewear/media/PlayerUiController.kt
+++ b/wear/src/main/java/com/thewizrd/simplewear/media/PlayerUiController.kt
@@ -5,6 +5,8 @@ interface PlayerUiController {
fun pause()
fun skipToPreviousMedia()
fun skipToNextMedia()
+ fun seekForward()
+ fun seekBackward()
}
class NoopPlayerUiController : PlayerUiController {
@@ -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) :
@@ -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()
}
\ No newline at end of file
diff --git a/wear/src/main/java/com/thewizrd/simplewear/ui/simplewear/MediaPlayerUi.kt b/wear/src/main/java/com/thewizrd/simplewear/ui/simplewear/MediaPlayerUi.kt
index 387ae4f5..0470de54 100644
--- a/wear/src/main/java/com/thewizrd/simplewear/ui/simplewear/MediaPlayerUi.kt
+++ b/wear/src/main/java/com/thewizrd/simplewear/ui/simplewear/MediaPlayerUi.kt
@@ -24,13 +24,16 @@ import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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
@@ -561,27 +564,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),
+ 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(
+ modifier = Modifier.offset(y = (-8).dp),
+ 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 =
@@ -599,22 +637,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,
+ )
+ }
}
)
}
diff --git a/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/MediaPlayerTileMessenger.kt b/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/MediaPlayerTileMessenger.kt
index 7a5e0663..3f1b6fd4 100644
--- a/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/MediaPlayerTileMessenger.kt
+++ b/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/MediaPlayerTileMessenger.kt
@@ -53,7 +53,9 @@ class MediaPlayerTileMessenger(
PREVIOUS,
NEXT,
VOL_UP,
- VOL_DOWN
+ VOL_DOWN,
+ SEEK_BACKWARD,
+ SEEK_FORWARD
}
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
@@ -208,6 +210,14 @@ class MediaPlayerTileMessenger(
PlayerAction.VOL_DOWN -> {
sendMessage(mPhoneNodeWithApp!!.id, MediaHelper.MediaVolumeDownPath, null)
}
+
+ PlayerAction.SEEK_BACKWARD -> {
+ sendMessage(mPhoneNodeWithApp!!.id, MediaHelper.MediaSeekBackwardPath, null)
+ }
+
+ PlayerAction.SEEK_FORWARD -> {
+ sendMessage(mPhoneNodeWithApp!!.id, MediaHelper.MediaSeekForwardPath, null)
+ }
}
}
}
diff --git a/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/MediaPlayerTileRenderer.kt b/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/MediaPlayerTileRenderer.kt
index f5ecd7ce..d0874379 100644
--- a/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/MediaPlayerTileRenderer.kt
+++ b/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/MediaPlayerTileRenderer.kt
@@ -43,6 +43,8 @@ class MediaPlayerTileRenderer(context: Context, debugResourceMode: Boolean = fal
internal const val ID_VOL_UP = "vol_up"
internal const val ID_VOL_DOWN = "vol_down"
internal const val ID_APPICON = "app_icon"
+ internal const val ID_SEEK_BACKWARD = "seek_backward"
+ internal const val ID_SEEK_FORWARD = "seek_forward"
fun getTapAction(context: Context): ActionBuilders.Action {
return ActionBuilders.launchAction(
@@ -94,7 +96,9 @@ class MediaPlayerTileRenderer(context: Context, debugResourceMode: Boolean = fal
ID_SKIP to R.drawable.ic_baseline_skip_next_24,
ID_VOL_UP to R.drawable.ic_volume_up_white_24dp,
- ID_VOL_DOWN to R.drawable.ic_baseline_volume_down_24
+ ID_VOL_DOWN to R.drawable.ic_baseline_volume_down_24,
+ ID_SEEK_BACKWARD to R.drawable.ic_replay_10_white_24dp,
+ ID_SEEK_FORWARD to R.drawable.ic_forward_10_white_24dp
)
(resourceIds.takeIf { it.isNotEmpty() } ?: resources.keys).forEach { key ->
diff --git a/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/layouts/MediaPlayerTileLayout.kt b/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/layouts/MediaPlayerTileLayout.kt
index 527eb16a..d1a2b818 100644
--- a/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/layouts/MediaPlayerTileLayout.kt
+++ b/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/layouts/MediaPlayerTileLayout.kt
@@ -85,6 +85,8 @@ import com.thewizrd.simplewear.wearable.tiles.MediaPlayerTileRenderer.Companion.
import com.thewizrd.simplewear.wearable.tiles.MediaPlayerTileRenderer.Companion.ID_SKIP
import com.thewizrd.simplewear.wearable.tiles.MediaPlayerTileRenderer.Companion.ID_VOL_DOWN
import com.thewizrd.simplewear.wearable.tiles.MediaPlayerTileRenderer.Companion.ID_VOL_UP
+import com.thewizrd.simplewear.wearable.tiles.MediaPlayerTileRenderer.Companion.ID_SEEK_BACKWARD
+import com.thewizrd.simplewear.wearable.tiles.MediaPlayerTileRenderer.Companion.ID_SEEK_FORWARD
import com.thewizrd.simplewear.wearable.tiles.MediaPlayerTileState
import kotlinx.coroutines.runBlocking
import java.time.Instant
@@ -345,15 +347,9 @@ internal fun MediaPlayerTileLayout(
Box.Builder()
.setWidth(expand())
.setHeight(
- WrappedDimensionProp.Builder()
- .apply {
- if (deviceParameters.isLargeHeight()) {
- setMinimumSize(dp(80f))
- } else {
- setMinimumSize(dp(64f))
- }
- }
- .build()
+ dp(
+ if (deviceParameters.isLargeHeight()) 80f else 64f
+ )
)
.addContent(
buttonGroup(
@@ -361,6 +357,10 @@ internal fun MediaPlayerTileLayout(
width = expand(),
spacing = 0f
) {
+ buttonGroupItem {
+ SeekButton(PlayerAction.SEEK_BACKWARD)
+ }
+
buttonGroupItem {
PlayerButton(action = PlayerAction.PREVIOUS)
}
@@ -372,6 +372,10 @@ internal fun MediaPlayerTileLayout(
buttonGroupItem {
PlayerButton(action = PlayerAction.NEXT)
}
+
+ buttonGroupItem {
+ SeekButton(PlayerAction.SEEK_FORWARD)
+ }
}
)
.build()
@@ -549,6 +553,24 @@ private fun DeviceParameters.getScreenWidthInDpFromPercentage(
return ceil(screenWidthDp * percent / 100f)
}
+private fun MaterialScope.SeekButton(
+ action: PlayerAction
+): LayoutElement {
+ val size = if (deviceConfiguration.isLargeHeight()) 28f else 24f
+ return iconButton(
+ onClick = clickable(id = action.name),
+ width = dp(size),
+ height = dp(size),
+ iconContent = {
+ icon(
+ protoLayoutResourceId = getResourceIdForPlayerAction(action),
+ width = dp(size - 8f),
+ height = dp(size - 8f)
+ )
+ }
+ )
+}
+
private fun MaterialScope.PlayerButton(
action: PlayerAction
): LayoutElement {
@@ -792,6 +814,8 @@ private fun getResourceIdForPlayerAction(action: PlayerAction): String {
PlayerAction.NEXT -> ID_SKIP
PlayerAction.VOL_UP -> ID_VOL_UP
PlayerAction.VOL_DOWN -> ID_VOL_DOWN
+ PlayerAction.SEEK_BACKWARD -> ID_SEEK_BACKWARD
+ PlayerAction.SEEK_FORWARD -> ID_SEEK_FORWARD
}
}
diff --git a/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/unofficial/MediaPlayerTileProviderService.kt b/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/unofficial/MediaPlayerTileProviderService.kt
index dd417571..0e13fef7 100644
--- a/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/unofficial/MediaPlayerTileProviderService.kt
+++ b/wear/src/main/java/com/thewizrd/simplewear/wearable/tiles/unofficial/MediaPlayerTileProviderService.kt
@@ -249,6 +249,15 @@ class MediaPlayerTileProviderService : TileProviderService() {
getActionClickIntent(this, MediaHelper.MediaNextPath)
)
+ views.setOnClickPendingIntent(
+ R.id.seek_backward_button,
+ getActionClickIntent(this, MediaHelper.MediaSeekBackwardPath)
+ )
+ views.setOnClickPendingIntent(
+ R.id.seek_forward_button,
+ getActionClickIntent(this, MediaHelper.MediaSeekForwardPath)
+ )
+
views.setOnClickPendingIntent(
R.id.vol_down_button,
getActionClickIntent(this, MediaHelper.MediaVolumeDownPath)
@@ -287,6 +296,8 @@ class MediaPlayerTileProviderService : TileProviderService() {
MediaHelper.MediaNextPath -> requestPlayerAction(PlayerAction.NEXT)
MediaHelper.MediaVolumeUpPath -> requestPlayerAction(PlayerAction.VOL_UP)
MediaHelper.MediaVolumeDownPath -> requestPlayerAction(PlayerAction.VOL_DOWN)
+ MediaHelper.MediaSeekBackwardPath -> requestPlayerAction(PlayerAction.SEEK_BACKWARD)
+ MediaHelper.MediaSeekForwardPath -> requestPlayerAction(PlayerAction.SEEK_FORWARD)
}
return super.onStartCommand(intent, flags, startId)
diff --git a/wear/src/main/res/layout/tile_mediaplayer.xml b/wear/src/main/res/layout/tile_mediaplayer.xml
index 1833ea95..a8d6b04d 100644
--- a/wear/src/main/res/layout/tile_mediaplayer.xml
+++ b/wear/src/main/res/layout/tile_mediaplayer.xml
@@ -146,10 +146,46 @@
+
+
+
+
+
+
+
+