From d9bb7239c69605212eed370a61aca4adf5ba710d Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 15 Aug 2026 13:49:18 +0200 Subject: [PATCH] fix(voice): ARIA schliesst bei Steuerbefehl die Aufnahme selbst (stiller Stop) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steuerbefehle laufen stumm (speak=false in Fast-Path/Local/Claude-Pfad) — ohne TTS feuert aber onPlaybackFinished nie, und daran hing bisher das Aufraeumen der Aufnahme. Folge: nach "spotify play" blieb das Mikro-/Konversations-Fenster offen (30s Leerlauf), obwohl der Befehl laengst ausgefuehrt war. Neu: ariaStopRecording() — das programmatische Gegenstueck zum Stop-Button. Nach einer stillen Antwort (speak=false) schliesst ARIA jede offene Aufnahme selbst und geht zurueck aufs Wake-Word, egal in welchem Zustand: passives Lauschen sauber beenden, offene Streaming-Aufnahme (aktiv/Barge) verwerfen, sonst endConversation. Vorher nur der conversing-Fall abgedeckt. Co-Authored-By: Claude Opus 4.8 --- android/src/screens/ChatScreen.tsx | 43 ++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index a889015..c9c4c90 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -1407,11 +1407,14 @@ const ChatScreen: React.FC = () => { // sofort (zurueck aufs Wake-Word), nur echte Gespraeche lauschen weiter. converseRef.current = (message.payload as any).converse === true; const _isSilent = (message.payload as any).speak === false; - if (_isSilent && wakeWordService.isConversing()) { - // 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(() => {}); + if (_isSilent) { + // Steuerbefehl (speak=false) ist ausgefuehrt und wird NICHT vorgelesen. + // Ohne TTS feuert onPlaybackFinished nie — der Mikro-/Konversations- + // Lifecycle haengt sonst (das Ohr bleibt grau, das Aufnahme-Fenster offen). + // ARIA "drueckt hier selbst Stop": jede offene Aufnahme schliessen + + // zurueck aufs Wake-Word, egal in welchem Zustand (conversing, passives + // Lauschen ODER eine noch offene Streaming-Aufnahme). + ariaStopRecording('silent-command').catch(() => {}); } } @@ -2298,6 +2301,36 @@ const ChatScreen: React.FC = () => { return true; }, [getCurrentLocation, interruptAriaIfBusy, scheduleStaleAudioCleanup]); + // ARIA schliesst die Aufnahme SELBST — das programmatische Gegenstueck zum + // Stop-Button. Aufgerufen nach einem stillen Steuerbefehl (speak=false): der + // Befehl ist ausgefuehrt, ARIA hat die Rueckinfo (Skill-Ergebnis) und antwortet + // NICHT vorgelesen. Weil ohne TTS kein onPlaybackFinished kommt, muss der + // Aufnahme-/Konversations-Zustand hier aktiv aufgeraeumt werden, sonst bleibt + // das Ohr haengen bzw. das Aufnahme-Fenster laeuft leer weiter (Stefans + // Reproduktion: "spotify play" und das Mikro wartet trotzdem 30s). + // Unterschied zum manuellen Stop: der verwirft NICHT, sondern finalisiert die + // Aufnahme (User will seinen Satz verarbeitet haben) — hier ist der Befehl + // schon durch, ein evtl. offenes Folge-Fenster wird verworfen. + const ariaStopRecording = useCallback(async (reason: string): Promise => { + converseRef.current = false; + // 1) Passiv-Lauschen: sauber beenden (cancelt den Stream selbst, startet + // KEINE neue passive Aufnahme). + if (wakeWordService.getState() === 'listening') { + await wakeWordService.exitPassiveListening('manual').catch(() => {}); + return; + } + // 2) Noch offene Streaming-Aufnahme (aktiv / Barge-In) verwerfen. + if (audioService.isStreamingRecording()) { + await audioService.cancelStreamingRecording(reason).catch(() => {}); + } + // 3) Konversation beenden → zurueck aufs Wake-Word (skipPassive: kein 30s-Fenster). + if (wakeWordService.isConversing()) { + await wakeWordService.endConversation(true).catch(() => {}); + } else if (!wakeWordService.isActive()) { + setWakeWordActive(false); + } + }, []); + // Manueller Aufnahme-Knopf — Stop. Sendet stt_stream_end an Whisper, die // dann ihrerseits den finalen Text als stt_endpoint emittiert. aria-bridge // forwarded direkt an Brain. Im wake-word-conversing-Fall zusaetzlich