From b1eaf42fefd5d355f85f19fff65ed48221b9f7a2 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 10 May 2026 16:57:52 +0200 Subject: [PATCH] =?UTF-8?q?fix(audio):=20Spotify=20resumed=20nach=20Mute?= =?UTF-8?q?=20=E2=80=94=20RNSound's=20haengenden=20Focus=20loesen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Logs zeigten: react-native-sound requestet beim Sound.play() einen EIGENEN AudioFocus mit USAGE_MEDIA, released den aber bei Sound.stop()/ release() NICHT (bekanntes RN-sound-Bug). Spotify sieht den haengenden Media-Focus → bleibt pausiert. Workaround: Native-Methode kickReleaseMedia() macht einen request+abandon- Cycle mit USAGE_MEDIA, das System raeumt damit den Focus-Stack auf und Spotify bekommt sauberen GAIN-Event. stopPlayback ruft das jetzt nach Sound.release() wenn vorher ein RNSound aktiv war. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/com/ariacockpit/AudioFocusModule.kt | 46 +++++++++++++++++++ android/src/services/audio.ts | 11 +++++ 2 files changed, 57 insertions(+) diff --git a/android/android/app/src/main/java/com/ariacockpit/AudioFocusModule.kt b/android/android/app/src/main/java/com/ariacockpit/AudioFocusModule.kt index 2226bbe..15f6298 100644 --- a/android/android/app/src/main/java/com/ariacockpit/AudioFocusModule.kt +++ b/android/android/app/src/main/java/com/ariacockpit/AudioFocusModule.kt @@ -131,6 +131,52 @@ class AudioFocusModule(reactContext: ReactApplicationContext) : ReactContextBase promise.resolve(true) } + /** Den USAGE_MEDIA-Focus-Stack im System aufmischen, damit Spotify/YouTube + * resumen wenn ein anderer Player (z.B. react-native-sound) seinen Focus + * nicht ordnungsgemaess released hat. Strategie: kurz selbst USAGE_MEDIA + * GAIN beanspruchen — das System invalidiert dabei den haengenden Stack- + * Eintrag des anderen Players — und sofort wieder abandonen. Spotify + * bekommt den Focus-Gain und resumed. + * + * Workaround fuer das react-native-sound-Bug: Sound.stop()/release() + * laesst den AudioFocusRequest haengen. + */ + @ReactMethod + fun kickReleaseMedia(promise: Promise) { + val am = audioManager() + if (am == null) { + promise.resolve(false) + return + } + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val attrs = AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .build() + val kickListener = AudioManager.OnAudioFocusChangeListener { /* ignorieren */ } + val kickReq = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) + .setAudioAttributes(attrs) + .setOnAudioFocusChangeListener(kickListener) + .build() + am.requestAudioFocus(kickReq) + am.abandonAudioFocusRequest(kickReq) + } else { + @Suppress("DEPRECATION") + val kickListener = AudioManager.OnAudioFocusChangeListener { /* ignorieren */ } + @Suppress("DEPRECATION") + am.requestAudioFocus(kickListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + @Suppress("DEPRECATION") + am.abandonAudioFocus(kickListener) + } + Log.i(TAG, "kickReleaseMedia: USAGE_MEDIA-Stack aufgemischt") + promise.resolve(true) + } catch (e: Exception) { + Log.w(TAG, "kickReleaseMedia failed: ${e.message}") + promise.resolve(false) + } + } + private fun release() { val am = audioManager() ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/android/src/services/audio.ts b/android/src/services/audio.ts index c681459..ebea3c3 100644 --- a/android/src/services/audio.ts +++ b/android/src/services/audio.ts @@ -41,6 +41,8 @@ const { AudioFocus, PcmStreamPlayer } = NativeModules as { requestDuck: () => Promise; requestExclusive: () => Promise; release: () => Promise; + kickReleaseMedia: () => Promise; + getMode?: () => Promise; }; PcmStreamPlayer?: { start: (sampleRate: number, channels: number, prerollSeconds: number) => Promise; @@ -1196,6 +1198,10 @@ class AudioService { stopBackgroundAudio().catch(() => {}); this.audioQueue = []; this.isPlaying = false; + // Merken: war ein react-native-sound-Sound aktiv? Dann muessen wir nach + // release() den Focus-Stack aufmischen (RNSound-Bug: stop+release laesst + // den AudioFocusRequest haengen, Spotify resumed sonst nicht). + const hadRnSound = !!(this.currentSound || this.resumeSound || this.preloadedSound); if (this.currentSound) { this.currentSound.stop(); this.currentSound.release(); @@ -1224,6 +1230,11 @@ class AudioService { // Audio-Focus sofort freigeben — User hat explizit abgebrochen this._cancelDeferredFocusRelease(); AudioFocus?.release().catch(() => {}); + if (hadRnSound) { + // RNSound's haengender USAGE_MEDIA-Focus aufloesen — sonst bleibt + // Spotify pausiert obwohl unser Focus released ist. + AudioFocus?.kickReleaseMedia?.().catch(() => {}); + } } // --- Status & Callbacks ---