[Android] Enable seeking via Android Auto steering wheel controls
Robert Stone
git at videolan.org
Wed Nov 25 09:48:37 CET 2020
vlc-android | branch: master | Robert Stone <rhstone at gmail.com> | Tue Nov 24 21:34:12 2020 -0800| [cad4580045f83cec88ac990c3266f1de0ca368a7] | committer: Robert Stone
Enable seeking via Android Auto steering wheel controls
> https://code.videolan.org/videolan/vlc-android/commit/cad4580045f83cec88ac990c3266f1de0ca368a7
---
.../src/org/videolan/vlc/MediaSessionCallback.kt | 59 +++++++++++++++++++++-
.../src/org/videolan/vlc/PlaybackService.kt | 2 +
2 files changed, 59 insertions(+), 2 deletions(-)
diff --git a/application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt b/application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt
index 4be9605bd..b959a26d1 100644
--- a/application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt
+++ b/application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt
@@ -1,10 +1,12 @@
package org.videolan.vlc
+import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
+import android.util.Log
import android.view.KeyEvent
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
@@ -23,10 +25,12 @@ import kotlin.math.min
@Suppress("unused")
private const val TAG = "VLC/MediaSessionCallback"
+private const val TEN_SECONDS = 10000L
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
internal class MediaSessionCallback(private val playbackService: PlaybackService) : MediaSessionCompat.Callback() {
+ private var prevActionSeek = false
override fun onPlay() {
if (playbackService.hasMedia()) playbackService.play()
@@ -42,9 +46,60 @@ internal class MediaSessionCallback(private val playbackService: PlaybackService
true
} else false
}
+ /**
+ * Implement fast forward and rewind behavior by directly handling the previous and next button events.
+ * Normally the buttons are triggered on ACTION_DOWN; however, we ignore the ACTION_DOWN event when
+ * isAndroidAutoHardKey returns true, and perform the operation on the ACTION_UP event instead. If the previous or
+ * next button is held down, a callback occurs with the long press flag set. When a long press is received,
+ * invoke the onFastForward() or onRewind() methods, and set the prevActionSeek flag. The ACTION_UP event
+ * action is bypassed if the flag is set. The prevActionSeek flag is reset to false for the next invocation.
+ */
+ if (isAndroidAutoHardKey(keyEvent) && (keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS || keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_NEXT)) {
+ when (keyEvent.action) {
+ KeyEvent.ACTION_DOWN -> {
+ if (playbackService.isSeekable && keyEvent.isLongPress) {
+ when (keyEvent.keyCode) {
+ KeyEvent.KEYCODE_MEDIA_NEXT -> onFastForward()
+ KeyEvent.KEYCODE_MEDIA_PREVIOUS -> onRewind()
+ }
+ prevActionSeek = true
+ }
+ }
+ KeyEvent.ACTION_UP -> {
+ if (!prevActionSeek) {
+ val enabledActions = playbackService.enabledActions
+ when (keyEvent.keyCode) {
+ KeyEvent.KEYCODE_MEDIA_NEXT -> if ((enabledActions and PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0L) onSkipToNext()
+ KeyEvent.KEYCODE_MEDIA_PREVIOUS -> if ((enabledActions and PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0L) onSkipToPrevious()
+ }
+ }
+ prevActionSeek = false
+ }
+ }
+ return true
+ }
return super.onMediaButtonEvent(mediaButtonEvent)
}
+ /**
+ * This function is based on the following KeyEvent captures. This may need to be updated if the behavior changes in the future.
+ *
+ * KeyEvent from Media Control UI:
+ * {action=ACTION_DOWN, keyCode=KEYCODE_MEDIA_NEXT, scanCode=0, metaState=0, flags=0x0, repeatCount=0, eventTime=0, downTime=0, deviceId=-1, source=0x0, displayId=0}
+ *
+ * KeyEvent from Android Auto Steering Wheel Control:
+ * {action=ACTION_DOWN, keyCode=KEYCODE_MEDIA_NEXT, scanCode=0, metaState=0, flags=0x4, repeatCount=0, eventTime=0, downTime=0, deviceId=0, source=0x0, displayId=0}
+ *
+ * KeyEvent from Android Auto Steering Wheel Control, Holding Switch (Long Press):
+ * {action=ACTION_DOWN, keyCode=KEYCODE_MEDIA_NEXT, scanCode=0, metaState=0, flags=0x84, repeatCount=1, eventTime=0, downTime=0, deviceId=0, source=0x0, displayId=0}
+ */
+ @SuppressLint("LongLogTag")
+ private fun isAndroidAutoHardKey(keyEvent: KeyEvent): Boolean {
+ val carMode = AndroidDevices.isCarMode(playbackService.applicationContext)
+ if (carMode) Log.i(TAG, "Android Auto Key Press: $keyEvent")
+ return carMode && keyEvent.deviceId == 0 && (keyEvent.flags and KeyEvent.FLAG_KEEP_TOUCH_MODE != 0)
+ }
+
override fun onCustomAction(action: String?, extras: Bundle?) {
when (action) {
"shuffle" -> playbackService.shuffle()
@@ -162,9 +217,9 @@ internal class MediaSessionCallback(private val playbackService: PlaybackService
override fun onSeekTo(pos: Long) = playbackService.seek(if (pos < 0) playbackService.time + pos else pos, fromUser = true)
- override fun onFastForward() = playbackService.seek(Math.min(playbackService.length, playbackService.time + 5000))
+ override fun onFastForward() = playbackService.seek((playbackService.time + TEN_SECONDS).coerceAtMost(playbackService.length), fromUser = true)
- override fun onRewind() = playbackService.seek(Math.max(0, playbackService.time - 5000))
+ override fun onRewind() = playbackService.seek((playbackService.time - TEN_SECONDS).coerceAtLeast(0), fromUser = true)
override fun onSkipToQueueItem(id: Long) {
playbackService.playIndex(id.toInt())
diff --git a/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt b/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
index acd74010f..fc9383f4f 100644
--- a/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
+++ b/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
@@ -92,6 +92,7 @@ private const val TAG = "VLC/PlaybackService"
class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
private val dispatcher = ServiceLifecycleDispatcher(this)
+ internal var enabledActions = PLAYBACK_BASE_ACTIONS
lateinit var playlistManager: PlaylistManager
private set
val mediaplayer: MediaPlayer
@@ -927,6 +928,7 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner {
val update = mediaSession.isActive != mediaIsActive
updateMediaQueueSlidingWindow()
mediaSession.setPlaybackState(pscb.build())
+ enabledActions = actions
mediaSession.isActive = mediaIsActive
mediaSession.setQueueTitle(getString(R.string.music_now_playing))
if (update) {
More information about the Android
mailing list