From 33185de42b3b7f1af0f428263c3e5ae0c23ca7dc Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 10 May 2026 12:29:55 +0200 Subject: [PATCH] fix(audio): AudioFocus erst beim NATIVEN Playback-Finished-Event released MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Logcat-Befund: 12:22:54.860 — final-Chunk + Cache geschrieben 12:22:55.402 — abandonAudioFocus (~0.5s spaeter) 12:22:55 — Spotify resumed (Atlas: TotalTime 93s) 12:23:27.064 — Playback fertig (32s spaeter!) → ARIA spricht 32s parallel zu Spotify weil end() viel zu frueh returnt. Stefans 'Spotify resumed obwohl ARIA noch redet'. Fix: - PcmStreamPlayerModule emittiert 'PcmPlaybackFinished' RN-Event nach dem finally{}-Block im Writer-Thread (= AudioTrack hat alle Samples wirklich durchgespielt) - audioService subscribed im constructor → ruft erst dann _releaseFocusDeferred() - _handlePcmChunkImpl bei isFinal triggert NICHT mehr direkt das Release — nur die playbackFinished-Listener (UI-Logic) So bleibt Spotify pausiert bis ARIA tatsaechlich fertig ist, egal wie viel Audio im AudioTrack-Buffer wartet. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../com/ariacockpit/PcmStreamPlayerModule.kt | 16 ++++++++++ android/src/services/audio.ts | 32 +++++++++++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt b/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt index 16859a3..dc7e63e 100644 --- a/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt +++ b/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt @@ -6,10 +6,12 @@ import android.media.AudioManager import android.media.AudioTrack import android.util.Base64 import android.util.Log +import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod +import com.facebook.react.modules.core.DeviceEventManagerModule import java.util.concurrent.LinkedBlockingQueue /** @@ -255,6 +257,17 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex } catch (_: Exception) {} try { t.stop() } catch (_: Exception) {} try { t.release() } catch (_: Exception) {} + // RN-Event: AudioTrack ist wirklich durch (alle Samples gespielt). + // JS released erst JETZT den AudioFocus — sonst spielt Spotify + // beim end()-Cap waehrend ARIA noch redet (15s+ je nach Buffer). + try { + val params = Arguments.createMap() + reactApplicationContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("PcmPlaybackFinished", params) + } catch (e: Exception) { + Log.w(TAG, "PlaybackFinished emit failed: ${e.message}") + } } }, "PcmStreamWriter").apply { start() } @@ -311,6 +324,9 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex promise.resolve(true) } + @ReactMethod fun addListener(eventName: String) {} + @ReactMethod fun removeListeners(count: Int) {} + private fun stopInternal() { writerShouldStop = true endRequested = true diff --git a/android/src/services/audio.ts b/android/src/services/audio.ts index d560da1..2af339b 100644 --- a/android/src/services/audio.ts +++ b/android/src/services/audio.ts @@ -6,7 +6,7 @@ * Nutzt react-native-audio-recorder-player fuer Aufnahme. */ -import { Platform, PermissionsAndroid, NativeModules, ToastAndroid } from 'react-native'; +import { Platform, PermissionsAndroid, NativeModules, ToastAndroid, NativeEventEmitter } from 'react-native'; import Sound from 'react-native-sound'; import RNFS from 'react-native-fs'; import AsyncStorage from '@react-native-async-storage/async-storage'; @@ -272,6 +272,21 @@ class AudioService { constructor() { this.recorder = new AudioRecorderPlayer(); this.recorder.setSubscriptionDuration(0.1); // 100ms Metering-Updates + // Native Event: AudioTrack hat alle Samples wirklich durchgespielt (nach + // dem finally{}-Block im Writer-Thread). ERST jetzt darf AudioFocus + // freigegeben werden — sonst spielt Spotify schon waehrend ARIA noch + // redet (PcmStreamPlayer.end() returnt mit 15s-Cap viel zu frueh). + if (PcmStreamPlayer) { + try { + const emitter = new NativeEventEmitter(NativeModules.PcmStreamPlayer as any); + emitter.addListener('PcmPlaybackFinished', () => { + console.log('[Audio] PcmPlaybackFinished — Focus jetzt freigeben'); + this._releaseFocusDeferred(); + }); + } catch (err) { + console.warn('[Audio] PcmPlaybackFinished-Subscription fehlgeschlagen:', err); + } + } } /** AudioFocus mit kleiner Verzoegerung freigeben — Spotify/YouTube @@ -741,13 +756,16 @@ class AudioService { if (isFinal) { if (!silent) { - // end() resolved jetzt erst wenn der native Writer-Thread fertig - // ist (alle Samples ausgespielt) — danach AudioFocus verzoegert - // freigeben, damit Spotify/YouTube nicht im Mikro-Gap zwischen zwei - // ARIA-Antworten wieder hochdrehen. Wenn ein neuer Stream innerhalb - // FOCUS_RELEASE_DELAY_MS startet, wird das Release abgebrochen. + // end() signalisiert dem Writer "keine weiteren Chunks". Aber WIR + // releasen den AudioFocus NICHT hier — der writer braucht u.U. noch + // 30+ Sekunden bis der Buffer wirklich abgespielt ist. Den release + // triggert das native Event "PcmPlaybackFinished" wenn AudioTrack + // wirklich am Ende ist (siehe ensurePlaybackFinishedListener). try { await PcmStreamPlayer!.end(); } catch {} - this._releaseFocusDeferred(); + // playbackFinished-Listener informieren (UI-Logik) + this.playbackFinishedListeners.forEach(cb => { + try { cb(); } catch (e) { console.warn('[Audio] playbackFinished cb err:', e); } + }); } this.pcmStreamActive = false;