From 9fb29aa5170734dbc24729e2d5bc265cde8def13 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 11 Jul 2026 20:52:04 +0200 Subject: [PATCH] fix(voice): klarer Befehl = STOP, Gespraech = 30s passiv (kein zweiter Gong) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nach dem Re-Arm-Fix rief die App resume() → das spielte einen zweiten Gong und oeffnete ein neues Aufnahme-Fenster, obwohl Stefan nur einen Steuerbefehl gab. Sein gewuenschtes Verhalten: - Klarer Befehl (Fast-Path, speak=false, z.B. Liedersteuerung) = KEINE Konversation → STOP: direkt zurueck aufs Wake-Word (kein Gong, keine Aufnahme, kein 30s-Fenster). - Gespraech (gesprochene Antwort) = kein neuer Gong, direkt 30s passives Lauschen (weiterreden wie mit einem Menschen), 30s still → Wake-Word. endConversation(skipPassive) neu: true springt direkt zu armed statt in passives Lauschen. onPlaybackFinished (Gespraech) → passiv (Vordergrund) bzw. direkt armed (Hintergrund); stille Fast-Path-Antwort → skipPassive; manueller Stop-Button → skipPassive. resume() bleibt nur noch Mic-Fail-Retry. Co-Authored-By: Claude Opus 4.8 --- android/src/screens/ChatScreen.tsx | 22 ++++++++++------------ android/src/services/wakeword.ts | 7 +++++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index d43da84..bd354ea 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -1212,11 +1212,10 @@ const ChatScreen: React.FC = () => { const _isSilent = (message.payload as any).speak === false || ((message.payload as any).answeredBy === 'fast-path'); if (_isSilent && wakeWordService.isConversing()) { - if (AppState.currentState === 'active') { - wakeWordService.resume(); - } else { - wakeWordService.endConversation().catch(() => {}); - } + // Klarer Steuerbefehl (Liedersteuerung etc.) = KEINE Konversation → + // STOP: direkt zurueck aufs Wake-Word. Kein Gong, keine Aufnahme, + // kein 30s-Fenster (skipPassive=true). + wakeWordService.endConversation(true).catch(() => {}); } } @@ -1446,12 +1445,11 @@ const ChatScreen: React.FC = () => { useEffect(() => { const unsubPlayback = audioService.onPlaybackFinished(() => { if (!wakeWordService.isActive()) return; - if (AppState.currentState === 'active') { - wakeWordService.resume(); - } else { - console.log('[Chat] TTS fertig im Background → endConversation (kein Multi-Turn)'); - wakeWordService.endConversation().catch(() => {}); - } + // Gespraech (gesprochene Antwort): KEIN neuer Gong / kein Aufnahme-Fenster, + // sondern direkt passives Lauschen (30s weiterreden wie mit einem Menschen). + // Im Hintergrund gibt's kein Multi-Turn → direkt re-armen (skipPassive). + const bg = AppState.currentState !== 'active'; + wakeWordService.endConversation(bg).catch(() => {}); }); return () => unsubPlayback(); }, []); @@ -2072,7 +2070,7 @@ const ChatScreen: React.FC = () => { await audioService.stopStreamingRecording('user'); if (wakeWordService.isConversing()) { console.log('[Chat] Manueller Stop in Konversation → endConversation, zurueck zu armed'); - await wakeWordService.endConversation(); + await wakeWordService.endConversation(true); // explizit gestoppt → STOP (kein 30s-Passiv) } }, []); diff --git a/android/src/services/wakeword.ts b/android/src/services/wakeword.ts index fa5b1a9..7b246fb 100644 --- a/android/src/services/wakeword.ts +++ b/android/src/services/wakeword.ts @@ -441,7 +441,10 @@ class WakeWordService { * wuerde sonst OpenWakeWord.stop() rufen weil bargeListening noch true * ist, und unseren frisch re-armierten Listener killen. */ - async endConversation(): Promise { + /** @param skipPassive true = KEIN passives Lauschen, direkt zurueck aufs + * Wake-Word (armed). Fuer klare Steuerbefehle (Fast-Path) — nach + * "nächster Titel" will Stefan kein 30s-Fenster, sondern Stop. */ + async endConversation(skipPassive: boolean = false): Promise { if (this.state !== 'conversing') { import('./logger').then(m => m.reportAppDebug('wake.end', `endConversation called but state=${this.state} → noop`)).catch(()=>{}); @@ -461,7 +464,7 @@ class WakeWordService { // (Phase 3) filtert fremde Stimmen weg, der User kann ohne erneute // Anrede weitersprechen. const passiveMs = await loadPassiveListenMs(); - if (passiveMs > 0 && this.nativeReady) { + if (!skipPassive && passiveMs > 0 && this.nativeReady) { this.enterPassiveListening(passiveMs); return; }