From 22d3b82ca7f500f1cfd7ab8ca2877d2e3365c5cc Mon Sep 17 00:00:00 2001 From: Jeremy0538 <211531394+Jeremy0538@users.noreply.github.com> Date: Thu, 1 Oct 2026 15:31:36 +0200 Subject: [PATCH 1/2] android: count untyped media playback as music for takeover Players that output through OpenSL ES, such as Poweramp, report USAGE_MEDIA with CONTENT_TYPE_UNKNOWN. Only MUSIC and MOVIE counted, so playing music in them never triggered "take over when media starts". Co-Authored-By: Claude Opus 5.5 --- .../me/kavishdevar/librepods/utils/MediaController.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt index 400e0ff72..1c7738698 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt @@ -140,7 +140,16 @@ object MediaController { Log.d("MediaController", "Current active content types: $currentActiveContentTypes") - val hasNewMusicOrMovie = currentActiveContentTypes.any { contentType -> + // Some players (e.g. Poweramp over OpenSL ES) leave the content type unknown, so a + // USAGE_MEDIA stream with no content type counts as music too. + val hasUntypedMedia = configs?.any { config -> + config.audioAttributes?.let { attrs -> + attrs.usage == android.media.AudioAttributes.USAGE_MEDIA && + attrs.contentType == android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN + } ?: false + } ?: false + + val hasNewMusicOrMovie = hasUntypedMedia || currentActiveContentTypes.any { contentType -> contentType == android.media.AudioAttributes.CONTENT_TYPE_MUSIC || contentType == android.media.AudioAttributes.CONTENT_TYPE_MOVIE } From e0ee116ecd56ac0a8524d2ff2d275f6240afc522 Mon Sep 17 00:00:00 2001 From: Jeremy0538 <211531394+Jeremy0538@users.noreply.github.com> Date: Thu, 1 Oct 2026 15:31:46 +0200 Subject: [PATCH 2/2] android: follow the media setting when the AirPods state is unknown While the AirPods are connected to another device the phone can lose their BLE advertisements; the first one after they reappear can carry an unmapped state byte, or there is no status at all. takeOver() then refused with "airpods state takeover disabled" even with every takeover option on. Fall back to takeoverWhenMusic and log the state. Co-Authored-By: Claude Opus 5.5 --- .../me/kavishdevar/librepods/services/AirPodsService.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 0cf08c11d..a21d602f8 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -2571,14 +2571,19 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList return } - val shouldTakeOver = when (bleManager.getMostRecentStatus()?.connectionState) { + val airPodsState = bleManager.getMostRecentStatus()?.connectionState + Log.d(TAG, "AirPods state for takeover: $airPodsState") + val shouldTakeOver = when (airPodsState) { "Disconnected" -> config.takeoverWhenDisconnected "Idle" -> config.takeoverWhenIdle "Music" -> config.takeoverWhenMusic "Call" -> config.takeoverWhenCall "Ringing" -> config.takeoverWhenCall "Hanging Up" -> config.takeoverWhenCall - else -> false + // The first advertisement after the AirPods reappear often carries a state byte that + // isn't mapped (or there is no status yet). Refusing then means the takeover only + // happens on the next media change, so follow the media setting instead. + else -> config.takeoverWhenMusic } if (!shouldTakeOver) {