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 ---