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(); exitVoiceMode();
} }
}, 60000); }, 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(); startListening();
loadNetwork(); loadNetwork();
loadHA(); loadHA();
@@ -1785,8 +1792,10 @@ function initVoice() {
}; };
recognition.onend = () => { recognition.onend = () => {
// Always restart for continuous wake word / command listening // Only restart when not speaking — _resumeMic() handles restart after TTS
if (isListening) setTimeout(() => { try { recognition.start(); } catch(_) {} }, 100); if (isListening && !isSpeaking) {
setTimeout(() => { try { recognition.start(); } catch(_) {} }, 150);
}
}; };
recognition.onerror = (e) => { recognition.onerror = (e) => {
@@ -1908,8 +1917,13 @@ async function speak(text) {
const _resumeMic = () => { const _resumeMic = () => {
isSpeaking = false; isSpeaking = false;
reactor?.classList.remove('speaking'); reactor?.classList.remove('speaking');
// Let onend restart recognition automatically (300ms buffer after audio ends) if (isListening) {
if (isListening) setTimeout(() => { try { recognition?.start(); } catch(_) {} }, 300); // Abort any stale session then restart cleanly
setTimeout(() => {
try { recognition?.abort(); } catch(_) {}
setTimeout(() => { try { recognition?.start(); } catch(_) {} }, 150);
}, 200);
}
}; };
try { try {
const res = await fetch('/api/tts', { const res = await fetch('/api/tts', {
@@ -1942,7 +1956,12 @@ function _speakFallback(text) {
utter.onend = () => { utter.onend = () => {
reactor?.classList.remove('speaking'); reactor?.classList.remove('speaking');
isSpeaking = false; 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); synth.speak(utter);
} }