diff --git a/public_html/index.html b/public_html/index.html index 4967592..61b717b 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -1012,6 +1012,13 @@ function showApp(name, greeting, silent = false) { exitVoiceMode(); } }, 60000); + // Watchdog: reset isSpeaking if stuck (TTS error left it true) + setInterval(() => { + if (isSpeaking && !_ttsAudio && !window.speechSynthesis?.speaking) { + isSpeaking = false; + if (isListening) try { recognition?.start(); } catch(_) {} + } + }, 5000); startListening(); loadNetwork(); loadHA(); @@ -1785,8 +1792,10 @@ function initVoice() { }; recognition.onend = () => { - // Always restart for continuous wake word / command listening - if (isListening) setTimeout(() => { try { recognition.start(); } catch(_) {} }, 100); + // Only restart when not speaking — _resumeMic() handles restart after TTS + if (isListening && !isSpeaking) { + setTimeout(() => { try { recognition.start(); } catch(_) {} }, 150); + } }; recognition.onerror = (e) => { @@ -1908,8 +1917,13 @@ async function speak(text) { const _resumeMic = () => { isSpeaking = false; reactor?.classList.remove('speaking'); - // Let onend restart recognition automatically (300ms buffer after audio ends) - if (isListening) setTimeout(() => { try { recognition?.start(); } catch(_) {} }, 300); + if (isListening) { + // Abort any stale session then restart cleanly + setTimeout(() => { + try { recognition?.abort(); } catch(_) {} + setTimeout(() => { try { recognition?.start(); } catch(_) {} }, 150); + }, 200); + } }; try { const res = await fetch('/api/tts', { @@ -1942,7 +1956,12 @@ function _speakFallback(text) { utter.onend = () => { reactor?.classList.remove('speaking'); isSpeaking = false; - if (isListening) setTimeout(() => { try { recognition?.start(); } catch(_) {} }, 300); + if (isListening) { + setTimeout(() => { + try { recognition?.abort(); } catch(_) {} + setTimeout(() => { try { recognition?.start(); } catch(_) {} }, 150); + }, 200); + } }; synth.speak(utter); }