From 514ba44e51acd23b8947a9b2c122056b5652d469 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 16 Aug 2026 21:14:37 +0200 Subject: [PATCH] fix(app): Umgebungsmusik killt Weiterreden-Fenster nicht mehr (re-listen) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wechselwirkung entdeckt: im Passiv-Lausch-Fenster (Weiterreden nach ARIAs Antwort) nimmt das Mikro Umgebungsmusik auf → Silero verwirft sie korrekt als vad_no_speech → leerer Endpoint. Die App wertete JEDEN leeren Endpoint als "nichts gesagt" → sofort zurueck aufs Wake-Word. Bei laufender Musik in einer Sprechpause endete so das Gespraech, obwohl die Stille-Toleranz noch lief. Fix: bei leerem Endpoint im Passiv-Fenster wird — solange die Stille-Toleranz ab Fenster-Oeffnung noch laeuft (Budget) und < 15 Runden — nochmal gelauscht statt zu beenden. Musik wird verworfen, das Fenster bleibt bis zur Toleranz offen, du kannst innerhalb reden. Erst wenn die Toleranz wirklich um ist → aufs Wake-Word. Sprichst du (Text-Endpoint), geht es sofort normal weiter. Co-Authored-By: Claude Opus 4.8 --- android/src/screens/ChatScreen.tsx | 38 +++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index a51e6c1..669682d 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -384,6 +384,14 @@ const ChatScreen: React.FC = () => { // stoppen? Kommt als 'converse' in der Chat-Payload; onPlaybackFinished liest // es. Default true (Konversation). false = Einzelaktion/Skill-Antwort. const converseRef = useRef(true); + // Passiv-Lausch-Fenster (Weiterreden nach ARIAs Antwort): Umgebungsgeraeusch + // (Musik/TV) darf das Fenster NICHT vorzeitig beenden. Bei einem no-speech- + // Endpoint (Silero verwirft Musik) wird — solange die Stille-Toleranz ab + // Fenster-Oeffnung noch laeuft — nochmal gelauscht statt sofort aufs Wake-Word + // zurueckzufallen. Start-Zeit + Re-Listen-Zaehler + Budget hier gemerkt. + const passiveListenStartRef = useRef(0); + const passiveReListenCountRef = useRef(0); + const passiveToleranceRef = useRef(5000); // Barge-in erlaubt? Default false = Halb-Duplex (waehrend TTS kein Mikro). const bargeInEnabledRef = useRef(false); @@ -1795,12 +1803,24 @@ const ChatScreen: React.FC = () => { !(m.audioRequestId === ev.audioRequestId && m.text.includes('Spracheingabe wird verarbeitet')))); } - // Kein Re-Arm mehr: nach ARIAs Antwort gab es EIN Stille-Fenster (= - // Stille-Toleranz). Kam nichts, ist Schluss → zurück aufs Wake-Word. - // Kein 30s-Nachlauschen. (speaker_mismatch/no-speech landen beide hier.) + // Passiv-Lauschen: leeres Endpoint (no-speech / Silero-Musik / speaker_ + // mismatch). NICHT sofort beenden — solange die Stille-Toleranz ab + // Fenster-Oeffnung noch laeuft, nochmal lauschen. So killt Umgebungs- + // musik das Weiterreden nicht: die Musik wird verworfen, das Fenster + // bleibt bis zur Toleranz offen, du kannst innerhalb reden. Erst wenn + // die Toleranz wirklich um ist (oder zu viele Runden) → aufs Wake-Word. if (wakeWordService.getState() === 'listening') { - console.log('[Chat] Passive-Listen: leeres Endpoint — Ende, zurueck aufs Wake-Word'); - wakeWordService.exitPassiveListening('timeout').catch(() => {}); + const elapsed = Date.now() - passiveListenStartRef.current; + const budget = passiveToleranceRef.current || 5000; + if (elapsed < budget && passiveReListenCountRef.current < 15) { + passiveReListenCountRef.current += 1; + console.log('[Chat] Passive-Listen: leeres Endpoint (%s) — Umgebung, re-listen (%dms/%dms, #%d)', + ev.reason, elapsed, budget, passiveReListenCountRef.current); + startPassiveStreamingRecording(); + } else { + console.log('[Chat] Passive-Listen: Stille-Toleranz aufgebraucht (%dms) — Ende, zurueck aufs Wake-Word', elapsed); + wakeWordService.exitPassiveListening('timeout').catch(() => {}); + } } else { wakeWordService.endConversation(); if (!wakeWordService.isActive()) setWakeWordActive(false); @@ -1812,8 +1832,14 @@ const ChatScreen: React.FC = () => { // geschaltet (nach endConversation). Wir starten eine streaming-Aufnahme // OHNE User-Bubble + ohne wake-ready-Sound. Speaker-ID-Gating in der // Whisper-Bridge filtert fremde Stimmen weg. - const unsubPassive = wakeWordService.onPassiveListen(() => { + const unsubPassive = wakeWordService.onPassiveListen(async () => { console.log('[Chat] Passive-Listen aktiviert — starte stille Streaming-Aufnahme'); + // Fenster NEU geoeffnet (nach ARIAs Antwort): Budget-Uhr + Re-Listen-Zaehler + // zuruecksetzen. Re-Listen ruft startPassiveStreamingRecording direkt (nicht + // ueber diesen Callback), also bleibt der Startzeitpunkt erhalten. + passiveListenStartRef.current = Date.now(); + passiveReListenCountRef.current = 0; + passiveToleranceRef.current = await loadSttEndpointMs(); startPassiveStreamingRecording(); });