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);
}