fix: voice mic race condition — onend blocks restart during TTS, clean abort+restart after speech, stuck isSpeaking watchdog

This commit is contained in:
2026-05-31 19:30:42 +00:00
parent 721607cfb0
commit af11d80216
+24 -5
View File
@@ -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);
}