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:
@@ -86,6 +86,11 @@ class WakeWordService {
|
||||
* oft einen Audio-Pegel-Spike (AudioFocus-Switch, AudioTrack re-route),
|
||||
* der openWakeWord faelschlich triggern kann. */
|
||||
private cooldownUntilMs: number = 0;
|
||||
/** Zeitpunkt des letzten echten Wake-Word-Triggers — gebraucht damit
|
||||
* ChatScreen entscheiden kann ob ein 'conversing'-State bei App-Resume
|
||||
* ein false-positive war (Wake-Word im Hintergrund getriggert waehrend
|
||||
* Stefan gar nicht in der App war). */
|
||||
private lastTriggerAt: number = 0;
|
||||
|
||||
private keyword: WakeKeyword = DEFAULT_KEYWORD;
|
||||
private nativeReady: boolean = false;
|
||||
@@ -231,6 +236,7 @@ class WakeWordService {
|
||||
}
|
||||
console.log('[WakeWord] Wake-Word "%s" erkannt! (state=%s, barge=%s)',
|
||||
this.keyword, this.state, this.bargeListening);
|
||||
this.lastTriggerAt = now;
|
||||
if (this.nativeReady && OpenWakeWord) {
|
||||
try { await OpenWakeWord.stop(); } catch {}
|
||||
}
|
||||
@@ -341,6 +347,33 @@ class WakeWordService {
|
||||
this.setState('off');
|
||||
}
|
||||
|
||||
/** Wenn ein conversing-State auf einem Wake-Word-Trigger juenger als
|
||||
* maxAgeMs basiert: false-positive verwerfen, zurueck zu armed.
|
||||
* Wird vom ChatScreen aufgerufen wenn die App aus laengerem Hintergrund
|
||||
* zurueck kommt — dann ist ein „gerade getriggertes" Wake-Word sehr
|
||||
* wahrscheinlich ein TV-Spike, Husten, ARIAs eigene TTS-Aufnahme etc.
|
||||
* Returnt true wenn verworfen wurde. */
|
||||
async discardIfFreshlyTriggered(maxAgeMs: number = 10_000): Promise<boolean> {
|
||||
if (this.state !== 'conversing') return false;
|
||||
if (this.lastTriggerAt === 0) return false;
|
||||
const age = Date.now() - this.lastTriggerAt;
|
||||
if (age > maxAgeMs) return false;
|
||||
console.log('[WakeWord] Resume: verwerfe verdaechtiges conversing (age=%dms)', age);
|
||||
this.lastTriggerAt = 0;
|
||||
if (this.nativeReady && OpenWakeWord) {
|
||||
try {
|
||||
await OpenWakeWord.start();
|
||||
ToastAndroid.show('Hintergrund-Trigger verworfen — lausche wieder', ToastAndroid.SHORT);
|
||||
this.setState('armed');
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn('[WakeWord] re-arm nach discard fehlgeschlagen:', err);
|
||||
}
|
||||
}
|
||||
this.setState('off');
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Nach ARIA-Antwort (TTS fertig): naechste Aufnahme im Conversation-Window starten */
|
||||
async resume(): Promise<void> {
|
||||
if (this.state !== 'conversing') return;
|
||||
|
||||
Reference in New Issue
Block a user