Android expanded notification controls do not emit events because MEDIA_BUTTON intents are not handled
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 82/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- android, kotlin, react-native
- 領域
- mobile-dev
調査の方向性
android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt から始め、そこで処理されるアクションと MediaSessionManager.kt の MEDIA_BUTTON 登録を比較します。Intent.EXTRA_KEY_EVENT の経路を追跡し、ネイティブ Android の再ビルド後に、展開されたコントロールとロック画面のコントロールが期待される再生通知イベントを発行することを確認します。
索引モデルが issue の本文から書いたものです。
説明
Summary
On Android with [email protected], playback controls behave differently depending on which notification UI is used:
- Compact notification widget controls work.
- Scrubbing works.
- Expanded notification / lock-screen play, pause, next, and previous controls do not emit the expected
playbackNotification*events. - In a high-frequency update path, we also observed a
java.util.ConcurrentModificationExceptionfromNotificationCompatBuilderwhile the notification was being updated.
I searched existing issues/PRs for MEDIA_BUTTON, PlaybackNotificationReceiver, expanded notification controls, and ConcurrentModificationException NotificationCompatBuilder, but did not find an existing report for this exact Android path.
Important MRE note
I know the issue template strongly encourages a minimal reproducible example. This report is currently based on a real app reproduction, not a public MRE yet. I am filing now because the code-level cause appears small and concrete. If needed, I can follow up with a minimal public repro project that exercises only PlaybackNotificationManager.show, enableControl, and event listeners.
Environment
react-native-audio-api:0.13.3- React Native:
0.81.5 - Expo SDK:
54 - Android: reproduced on Android device/build from an Expo React Native app
- New Architecture enabled
react-native-worklets:0.6.1
Reproduction observed in app
- Configure and show playback notification metadata.
- Enable controls:
playpausenextTrackpreviousTrackskipForwardskipBackwardseekTo
- Start playback.
- Swipe down to show the compact notification widget.
- Press controls from the compact widget.
- Expand the notification or use lock-screen media controls.
- Press play/pause/next/previous.
Expected behavior
Expanded notification and lock-screen play/pause/next/previous should emit the same events as the compact notification widget:
playbackNotificationPlayplaybackNotificationPauseplaybackNotificationNextTrackplaybackNotificationPreviousTrack
Actual behavior
Compact widget controls work, but expanded notification / lock-screen play/pause/next/previous controls do not emit the expected events. Scrubbing still works.
Code-level analysis
The compact controls appear to work through MediaSessionCompat.Callback in PlaybackNotification.kt:
override fun onPlay() {
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_PLAY.ordinal, mapOf())
}
override fun onPause() {
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_PAUSE.ordinal, mapOf())
}
override fun onSkipToNext() {
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_NEXT_TRACK.ordinal, mapOf())
}
override fun onSkipToPrevious() {
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_PREVIOUS_TRACK.ordinal, mapOf())
}
But expanded notification actions are created as pending intents with action PlaybackNotification.MEDIA_BUTTON:
val intent = Intent(MEDIA_BUTTON)
intent.setPackage(context.packageName)
intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keyCode))
MediaSessionManager.kt registers that action on PlaybackNotificationReceiver:
playbackFilter.addAction(PlaybackNotification.MEDIA_BUTTON)
However, PlaybackNotificationReceiver.kt handles only:
ACTION_NOTIFICATION_DISMISSEDACTION_SKIP_FORWARDACTION_SKIP_BACKWARD
It does not handle PlaybackNotification.MEDIA_BUTTON or decode Intent.EXTRA_KEY_EVENT.
That seems to explain the split behavior: compact MediaSession callbacks work, while expanded PendingIntent-backed media button actions are dropped.
Local patch that fixed the app behavior
This local patch maps MEDIA_BUTTON key events to the same AudioEvent values as the MediaSession callback path:
diff --git a/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt b/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt
--- a/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt
+++ b/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt
@@ -3,6 +3,7 @@ package com.swmansion.audioapi.system.notification
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import android.view.KeyEvent
import com.swmansion.audioapi.AudioAPIModule
import com.swmansion.audioapi.system.AudioEvent
@@ -40,6 +41,43 @@ class PlaybackNotificationReceiver : BroadcastReceiver() {
val body = HashMap<String, Any>().apply { put("value", 15) }
audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_BACKWARD.ordinal, body)
}
+
+ PlaybackNotification.MEDIA_BUTTON -> {
+ val keyEvent = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
+ } else {
+ @Suppress("DEPRECATION")
+ intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)
+ }
+
+ if (keyEvent?.action != KeyEvent.ACTION_DOWN) return
+
+ when (keyEvent.keyCode) {
+ KeyEvent.KEYCODE_MEDIA_PLAY -> {
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_PLAY.ordinal, mapOf())
+ }
+ KeyEvent.KEYCODE_MEDIA_PAUSE -> {
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_PAUSE.ordinal, mapOf())
+ }
+ KeyEvent.KEYCODE_MEDIA_STOP -> {
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_STOP.ordinal, mapOf())
+ }
+ KeyEvent.KEYCODE_MEDIA_NEXT -> {
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_NEXT_TRACK.ordinal, mapOf())
+ }
+ KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_PREVIOUS_TRACK.ordinal, mapOf())
+ }
+ KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> {
+ val body = HashMap<String, Any>().apply { put("value", 15) }
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_FORWARD.ordinal, body)
+ }
+ KeyEvent.KEYCODE_MEDIA_REWIND -> {
+ val body = HashMap<String, Any>().apply { put("value", 15) }
+ audioAPIModule?.invokeHandlerWithEventNameAndEventBody(AudioEvent.PLAYBACK_NOTIFICATION_SKIP_BACKWARD.ordinal, body)
+ }
+ }
+ }
}
}
}
Related crash observed
Before serializing notification updates in app code, we also saw this Android crash while notification controls/metadata were being updated:
java.util.ConcurrentModificationException
java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1112)
java.util.ArrayList$Itr.next(ArrayList.java:1065)
androidx.core.app.NotificationCompatBuilder.<init>(NotificationCompatBuilder.java:127)
androidx.core.app.NotificationCompat$Builder.build(NotificationCompat.java:2528)
com.swmansion.audioapi.system.notification.PlaybackNotification.buildNotification(PlaybackNotification.kt:515)
com.swmansion.audioapi.system.notification.PlaybackNotification.updateInternal$lambda$1$lambda$0(PlaybackNotification.kt:249)
We mitigated that separately in app code by serializing show, hide, and enableControl calls, so the main upstream issue here is the missing MEDIA_BUTTON handling path.
Cache / rebuild status
This was tested after rebuilding the native Android app. The Expo/Metro cache was also reset during upgrade testing. The local patch requires a native rebuild to take effect.
- 主要言語
- C++
- スター
- 848
- フォーク
- 95
- 平均マージ
- 3日 19時間
- マージ済み PR(30日)
- 30
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
software-mansion/react-native-audio-api のほかの issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
software-mansion/react-native-audio-api#1238 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 25/100
software-mansion/react-native-audio-api#1350 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 38/100
software-mansion/react-native-audio-api#1340 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
software-mansion/react-native-audio-api#1339 ·
メンテナーはふだん 1 日以内に返信
-
Channel negotiation drops buffer swaps when several graph changes land in one render quantum対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンbug
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
software-mansion/react-native-audio-api#1332 · コメント 4 件 ·
メンテナーはふだん 1 日以内に返信
software-mansion/react-native-audio-api の issue をすべて見る
似ている issue
-
backlog bug
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
EchoTools/nevr-runtime#653 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
InfiniTimeOrg/InfiniTime#2492 ·
-
accessibility good first issue hacktoberfest help wanted
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
OpenChemistry/avogadrolibs#3184 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
Tracktion/tracktion_engine#475 ·
メンテナーはふだん 1 日以内に返信