fix(wake): false-positive nach langer Hintergrund-Pause verwerfen

Symptom: Ohr aktiv, App im Hintergrund (jetzt mit Foreground-Service
permanent lebendig), nach laengerer Zeit oeffnet Stefan die App und sie
nimmt schon auf — angeblich Wake-Word getriggert. War aber TV/Husten/
sonstige Hintergrund-Geraeusche waehrend Stefan nicht da war.

Mit dem neuen Hintergrund-Modus laeuft openWakeWord jetzt permanent und
faengt jedes False-Positive im Hintergrund auf. Ohne dieser Fall war
das nicht moeglich weil die JS-Engine pausiert war.

Fix: Heuristik beim AppState-Resume in ChatScreen.tsx
- backgroundDauer wird gemerkt (lastBackgroundAt vs Resume-Zeit)
- Wenn >30s im Hintergrund UND state='conversing' UND letzter Wake-
  Trigger juenger als 15s: false-positive — Aufnahme abbrechen + zurueck
  zu armed
- Resume-Cooldown 1500 → 3000 ms (Audio-Spikes beim AppState-Switch
  haben gelegentlich nach 1.5s noch nicht verklungen)

Neue Methoden:
- wakeword.ts: lastTriggerAt-Tracking + discardIfFreshlyTriggered(maxAge)
- audio.ts: cancelRecording() — bricht recorder ab ohne Result zu
  emittieren, loescht die Audio-Datei

Setzt voraus dass Stefan nicht laenger als 30s im Hintergrund mit ARIA
spricht ueber Wake-Word. Falls doch: bei Resume waere die Aufnahme weg
und er muesste nochmal triggern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 15:54:07 +02:00
parent 2bac9c26ca
commit 048d231b60
3 changed files with 84 additions and 5 deletions
+26 -5
View File
@@ -480,14 +480,35 @@ const ChatScreen: React.FC = () => {
return () => { phoneCallService.stop().catch(() => {}); };
}, []);
// App-Resume: kurzer Wake-Word-Cooldown — beim Wechsel Background→Foreground
// gibt's haeufig Audio-Pegel-Spikes (AudioFocus-Switch, AudioTrack re-route)
// die openWakeWord sonst faelschlich als Wake-Word interpretiert.
// App-Resume: drei Schutzmaßnahmen gegen verirrte Wake-Word-Trigger
// beim Wechsel Background→Foreground:
// (a) Cooldown 3s — Audio-Pegel-Spikes (AudioFocus-Switch, AudioTrack
// re-route) sollen openWakeWord nicht faelschlich triggern
// (b) Wenn die App laenger im Hintergrund war und in 'conversing'
// zurueckkommt: vermutlich false-positive durch ein Hintergrund-
// Geraeusch (TV, Husten etc.) waehrend Stefan gar nicht da war.
// Wir verwerfen den Trigger und gehen zurueck zu 'armed'.
// (c) Aktuelle Aufnahme abbrechen falls sie aus dem false-positive
// gerade gestartet wurde.
useEffect(() => {
let lastState: string = AppState.currentState;
let lastBackgroundAt = 0;
const sub = AppState.addEventListener('change', (next) => {
if (lastState !== 'active' && next === 'active') {
wakeWordService.setResumeCooldown(1500);
if (next === 'background' || next === 'inactive') {
lastBackgroundAt = Date.now();
} else if (lastState !== 'active' && next === 'active') {
wakeWordService.setResumeCooldown(3000);
const bgDur = lastBackgroundAt > 0 ? Date.now() - lastBackgroundAt : 0;
// Bei laengerer Hintergrund-Zeit (>30s): pruefen ob ein frisches
// Wake-Word getriggert wurde wahrend die App weg war — wenn ja,
// verwerfen + laufende Aufnahme stoppen.
if (bgDur > 30_000) {
wakeWordService.discardIfFreshlyTriggered(15_000).then(discarded => {
if (discarded) {
try { audioService.cancelRecording(); } catch {}
}
}).catch(() => {});
}
}
lastState = next;
});