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 b69017d..e768902 100644 --- a/android/android/app/src/main/java/com/ariacockpit/AudioFocusModule.kt +++ b/android/android/app/src/main/java/com/ariacockpit/AudioFocusModule.kt @@ -53,11 +53,17 @@ class AudioFocusModule(reactContext: ReactApplicationContext) : ReactContextBase promise.resolve(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) } - /** Andere Apps werden leiser (TTS spricht). */ + /** Andere Apps werden pausiert (TTS spricht). + * + * TRANSIENT (statt TRANSIENT_MAY_DUCK): Spotify/YouTube pausieren komplett + * statt nur leiser zu werden. Verhindert auch das "kommt-wieder-hoch"- + * Problem mit MAY_DUCK, wo das System nach kurzer Zeit den Duck-Effekt + * wieder aufgehoben hat obwohl wir den Fokus noch hielten. + */ @ReactMethod fun requestDuck(promise: Promise) { requestFocus( - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, AudioAttributes.USAGE_ASSISTANT, promise, ) 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 6e19084..fe2fef6 100644 --- a/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt +++ b/android/android/app/src/main/java/com/ariacockpit/PcmStreamPlayerModule.kt @@ -201,11 +201,27 @@ class PcmStreamPlayerModule(reactContext: ReactApplicationContext) : ReactContex } } - /** Signalisiert: keine weiteren Chunks. Writer wartet auf Queue-Abschluss, dann stoppt. */ + /** Signalisiert: keine weiteren Chunks. Writer spielt aus, dann stoppt. + * Das Promise resolved erst wenn der Writer-Thread fertig ist — + * wichtig damit der Aufrufer den AudioFocus erst NACH dem letzten + * abgespielten Sample wieder freigibt (sonst dreht Spotify hoch + * waehrend das Pre-Roll noch ausspielt). + */ @ReactMethod fun end(promise: Promise) { endRequested = true - promise.resolve(true) + val t = writerThread + if (t == null || !t.isAlive) { + promise.resolve(true) + return + } + // Im Hintergrund auf den Writer warten — kein Threading-Block fuer JS-Bridge + Thread({ + try { + t.join(15_000) // hartes Cap, falls Writer haengt + } catch (_: InterruptedException) {} + promise.resolve(true) + }, "PcmStreamEndWaiter").start() } /** Harter Stop (Cancel) — Queue verwerfen. */ diff --git a/android/src/services/audio.ts b/android/src/services/audio.ts index 00ebae1..360dfa2 100644 --- a/android/src/services/audio.ts +++ b/android/src/services/audio.ts @@ -74,12 +74,13 @@ const AUDIO_ENCODING = 'audio/wav'; // VAD (Voice Activity Detection) — Stille-Erkennung const VAD_SILENCE_THRESHOLD_DB = -45; // dB unter dem als "Stille" gilt -const VAD_SILENCE_DURATION_MS = 1800; // ms Stille bevor Auto-Stop +const VAD_SILENCE_DURATION_MS = 2800; // ms Stille bevor Auto-Stop — laenger = mehr Toleranz fuer Sprechpausen const VAD_SPEECH_THRESHOLD_DB = -28; // dB ueber dem als "Sprache" gilt (Sprach-Gate) — hoeher = weniger Umgebungsgeraeusche const VAD_SPEECH_MIN_MS = 500; // ms Sprache bevor Aufnahme zaehlt — laenger = keine Huestler/Klopfer mehr -// Max-Dauer einer Aufnahme in Gespraechsmodus (Notbremse gegen Runaway-Loops) -const MAX_RECORDING_MS = 30000; +// Max-Dauer einer Aufnahme (Notbremse gegen Runaway-Loops). Auf 2 Minuten +// hochgezogen damit auch laengere Erklaerungen durchgehen. +const MAX_RECORDING_MS = 120000; // Pre-Roll: Wie lange Audio im AudioTrack-Buffer liegt bevor play() startet. // Einstellbar via Diagnostic/Settings (Key: aria_tts_preroll_sec). @@ -419,6 +420,10 @@ class AudioService { if (isFinal) { if (!silent) { + // end() resolved jetzt erst wenn der native Writer-Thread fertig + // ist (alle Samples ausgespielt) — danach erst AudioFocus freigeben, + // damit Spotify/YouTube nicht waehrend des Pre-Roll-Ausklangs + // wieder aufdrehen. try { await PcmStreamPlayer!.end(); } catch {} AudioFocus?.release().catch(() => {}); }