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;