[Android] Add an option to ignore media button presses when a headset is inserted

NyanCatTW1 git at videolan.org
Mon Feb 13 10:52:58 UTC 2023


vlc-android | branch: master | NyanCatTW1 <49344-NyanCatTW1 at users.noreply.code.videolan.org> | Mon Feb 13 10:52:57 2023 +0000| [05bacb8ff10e9b14b9d0aefcaaaf09280151716a] | committer: Duncan McNamara

Add an option to ignore media button presses when a headset is inserted

> https://code.videolan.org/videolan/vlc-android/commit/05bacb8ff10e9b14b9d0aefcaaaf09280151716a
---

 .../resources/src/main/res/values/strings.xml      |  2 ++
 .../television/ui/preferences/PreferencesAudio.kt  |  1 +
 application/vlc-android/AndroidManifest.xml        |  1 +
 .../vlc-android/res/xml/preferences_audio.xml      |  8 +++++
 .../src/org/videolan/vlc/MediaSessionCallback.kt   | 40 +++++++++++++++++++++-
 .../src/org/videolan/vlc/PlaybackService.kt        |  5 ++-
 6 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/application/resources/src/main/res/values/strings.xml b/application/resources/src/main/res/values/strings.xml
index e9777a4678..573102ee46 100644
--- a/application/resources/src/main/res/values/strings.xml
+++ b/application/resources/src/main/res/values/strings.xml
@@ -1102,6 +1102,8 @@
     <string name="talkback_display_settings">Display settings</string>
     <string name="talkback_already_played">Already played</string>
     <string name="show_only_favs">Show only favorites</string>
+    <string name="ignore_headset_media_button_presses">Ignore headset media button presses</string>
+    <string name="ignore_headset_media_button_presses_summary">Useful, for instance, if you are using a headset with broken physical buttons</string>
 
 
 </resources>
diff --git a/application/television/src/main/java/org/videolan/television/ui/preferences/PreferencesAudio.kt b/application/television/src/main/java/org/videolan/television/ui/preferences/PreferencesAudio.kt
index eb09387fc4..a41b2f7467 100644
--- a/application/television/src/main/java/org/videolan/television/ui/preferences/PreferencesAudio.kt
+++ b/application/television/src/main/java/org/videolan/television/ui/preferences/PreferencesAudio.kt
@@ -75,6 +75,7 @@ class PreferencesAudio : BasePreferenceFragment(), SharedPreferences.OnSharedPre
 
         findPreference<Preference>("enable_headset_detection")?.isVisible = false
         findPreference<Preference>("enable_play_on_headset_insertion")?.isVisible = false
+        findPreference<Preference>("ignore_headset_media_button_presses")?.isVisible = false
         findPreference<Preference>("headset_prefs_category")?.isVisible = false
         val aoutPref = findPreference<ListPreference>("aout")
         findPreference<Preference>(RESUME_PLAYBACK)?.isVisible = false
diff --git a/application/vlc-android/AndroidManifest.xml b/application/vlc-android/AndroidManifest.xml
index cfe224cfc6..de25a926d5 100644
--- a/application/vlc-android/AndroidManifest.xml
+++ b/application/vlc-android/AndroidManifest.xml
@@ -5,6 +5,7 @@
 
     <uses-sdk tools:overrideLibrary="com.jraska.livedata.ktx, com.jraska.livedata, tools.fastlane.screengrab, android_libs.ub_uiautomator"/>
 
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
     <uses-permission
         android:name="android.permission.READ_LOGS"
         android:maxSdkVersion="18"/> <!-- android:maxSdkVersion="15" -->
diff --git a/application/vlc-android/res/xml/preferences_audio.xml b/application/vlc-android/res/xml/preferences_audio.xml
index d9d360322c..2b71812951 100644
--- a/application/vlc-android/res/xml/preferences_audio.xml
+++ b/application/vlc-android/res/xml/preferences_audio.xml
@@ -53,6 +53,14 @@
                 android:dependency="enable_headset_detection"
                 android:summary="@string/enable_play_on_headset_insertion_summary"
                 android:title="@string/enable_play_on_headset_insertion"/>
+        <CheckBoxPreference
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:defaultValue="false"
+            android:key="ignore_headset_media_button_presses"
+            android:summary="@string/ignore_headset_media_button_presses_summary"
+            android:title="@string/ignore_headset_media_button_presses"
+            app:singleLineTitle="false" />
     </PreferenceCategory>
 
     <PreferenceCategory android:title="@string/controls_prefs_category">
diff --git a/application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt b/application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt
index 99ef649868..a2b27b9140 100644
--- a/application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt
+++ b/application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt
@@ -23,6 +23,8 @@
 package org.videolan.vlc
 
 import android.annotation.SuppressLint
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothProfile
 import android.content.ContentUris
 import android.content.Intent
 import android.net.Uri
@@ -31,7 +33,6 @@ 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
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.isActive
@@ -67,6 +68,23 @@ internal class MediaSessionCallback(private val playbackService: PlaybackService
 
     override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
         val keyEvent = mediaButtonEvent.parcelable(Intent.EXTRA_KEY_EVENT) as KeyEvent? ?: return false
+
+        if (playbackService.detectHeadset &&
+            playbackService.settings.getBoolean("ignore_headset_media_button_presses", false)) {
+            // Wired headset
+            if (playbackService.headsetInserted && isWiredHeadsetHardKey(keyEvent)) {
+                return true
+            }
+
+            // Bluetooth headset
+            val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
+            if (bluetoothAdapter != null &&
+                BluetoothAdapter.STATE_CONNECTED == bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) &&
+                isBluetoothHeadsetHardKey(keyEvent)) {
+                return true
+            }
+        }
+
         if (!playbackService.hasMedia()
                 && (keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY || keyEvent.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)) {
             return if (keyEvent.action == KeyEvent.ACTION_DOWN) {
@@ -109,6 +127,26 @@ internal class MediaSessionCallback(private val playbackService: PlaybackService
         return super.onMediaButtonEvent(mediaButtonEvent)
     }
 
+    /**
+     * The following two functions are based on the following KeyEvent captures. They may need to be updated if the behavior changes in the future.
+     *
+     * KeyEvent from Media Control UI:
+     * {action=ACTION_DOWN, keyCode=KEYCODE_MEDIA_PLAY_PAUSE, scanCode=0, metaState=0, flags=0x0, repeatCount=0, eventTime=0, downTime=0, deviceId=-1, source=0x0, displayId=0}
+     *
+     * KeyEvent from a wired headset's media button:
+     * {action=ACTION_DOWN, keyCode=KEYCODE_MEDIA_PLAY_PAUSE, scanCode=0, metaState=0, flags=0x40000000, repeatCount=0, eventTime=0, downTime=0, deviceId=-1, source=0x0, displayId=0}
+     *
+     * KeyEvent from a Bluetooth earphone:
+     * {action=ACTION_DOWN, keyCode=KEYCODE_MEDIA_PLAY, scanCode=0, metaState=0, flags=0x0, repeatCount=0, eventTime=0, downTime=0, deviceId=-1, source=0x0, displayId=0}
+     */
+    private fun isWiredHeadsetHardKey(keyEvent: KeyEvent): Boolean {
+        return !(keyEvent.deviceId == -1 && keyEvent.flags == 0x0)
+    }
+
+    private fun isBluetoothHeadsetHardKey(keyEvent: KeyEvent): Boolean {
+        return keyEvent.keyCode != KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE && keyEvent.deviceId == -1 && keyEvent.flags == 0x0
+    }
+
     /**
      * This function is based on the following KeyEvent captures. This may need to be updated if the behavior changes in the future.
      *
diff --git a/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt b/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
index ced87dc42b..c0a34203fa 100644
--- a/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
+++ b/application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
@@ -108,7 +108,8 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner, CoroutineSc
     private val callbacks = mutableListOf<Callback>()
     private val subtitleMessage = ArrayDeque<String>(1)
     private lateinit var cbActor: SendChannel<CbAction>
-    private var detectHeadset = true
+    var detectHeadset = true
+    var headsetInserted = false
     private lateinit var wakeLock: PowerManager.WakeLock
     private val audioFocusHelper by lazy { VLCAudioFocusHelper(this) }
     private lateinit var browserCallback: MediaBrowserCallback
@@ -182,12 +183,14 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner, CoroutineSc
                 MiniPlayerAppWidgetProvider.ACTION_WIDGET_ENABLED, VLCAppWidgetProvider.ACTION_WIDGET_DISABLED -> updateHasWidget()
                 AudioManager.ACTION_AUDIO_BECOMING_NOISY -> if (detectHeadset) {
                     if (BuildConfig.DEBUG) Log.i(TAG, "Becoming noisy")
+                    headsetInserted = false
                     wasPlaying = isPlaying
                     if (wasPlaying && playlistManager.hasCurrentMedia())
                         pause()
                 }
                 Intent.ACTION_HEADSET_PLUG -> if (detectHeadset && state != 0) {
                     if (BuildConfig.DEBUG) Log.i(TAG, "Headset Inserted.")
+                    headsetInserted = true
                     if (wasPlaying && playlistManager.hasCurrentMedia() && settings.getBoolean("enable_play_on_headset_insertion", false))
                         play()
                 }



More information about the Android mailing list