fix: TTS pausiert andere Apps statt zu ducken + VAD/Mic laenger

AudioFocus.requestDuck nutzt jetzt AUDIOFOCUS_GAIN_TRANSIENT (statt
TRANSIENT_MAY_DUCK) — Spotify/YouTube pausieren komplett solange ARIA
spricht und kommen nicht mitten drin wieder hoch.

PcmStreamPlayer.end() resolved jetzt erst wenn der native Writer-Thread
wirklich fertig ist (alle Samples aus dem Pre-Roll-Puffer ausgespielt).
audio.ts wartet entsprechend, bevor AudioFocus.release() gerufen wird —
behebt das "Musik dreht hoch waehrend Antwort noch laeuft"-Problem.

Mic-Aufnahme: VAD_SILENCE_DURATION_MS 1800 → 2800ms (mehr Toleranz fuer
Sprechpausen), MAX_RECORDING_MS 30s → 120s (laengere Erklaerungen
moeglich, Notbremse bleibt).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
duffyduck 2026-04-24 14:40:58 +02:00
parent 576ae925dd
commit e64df23bb7
3 changed files with 34 additions and 7 deletions

View File

@ -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,
)

View File

@ -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
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. */

View File

@ -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(() => {});
}