diff --git a/public/app.js b/public/app.js index 5d225dd..af89be1 100644 --- a/public/app.js +++ b/public/app.js @@ -122,22 +122,30 @@ function ttsCancel() { async function ttsDrain() { if (!ttsQueue.length) { ttsCurrentAudio = null; return; } const text = ttsQueue.shift(); + // Fallback timeout in case browser never fires onend (common speechSynthesis bug) + const timeoutMs = Math.max(4000, text.length * 80); if (piperAvailable) { const audio = new Audio(`/api/tts?text=${encodeURIComponent(text)}`); ttsCurrentAudio = audio; await new Promise((resolve) => { - audio.onended = resolve; - audio.onerror = resolve; - audio.play().catch(resolve); + let t = setTimeout(resolve, timeoutMs); + const done = () => { clearTimeout(t); resolve(); }; + audio.onended = done; + audio.onerror = done; + audio.play().catch(done); }); } else { const u = new SpeechSynthesisUtterance(text); u.rate = 0.85; u.lang = 'en-US'; speechSynthesis.cancel(); await new Promise((resolve) => { - u.onend = resolve; - u.onerror = resolve; + let t = setTimeout(resolve, timeoutMs); + const done = () => { clearTimeout(t); resolve(); }; + u.onend = done; + u.onerror = done; + // Un-stick suspended synthesis (common on mobile after a few minutes) + if (speechSynthesis.paused) speechSynthesis.resume(); // some browsers need a short delay after cancel() before speak() works setTimeout(() => speechSynthesis.speak(u), 50); }); @@ -2386,6 +2394,7 @@ function presentTestWord() { input.disabled = true; $('#test-listening').hidden = false; $('#test-form-row').hidden = true; + $('#btn-test-prev').hidden = test.i === 0; speakWord(w); // Enable input after TTS has had time to start (rough estimate: 1.5s per word) const delay = 1500 + (w.definition ? 1500 : 0) + (w.sentence ? 1500 : 0); @@ -2397,6 +2406,15 @@ function presentTestWord() { }, delay); } +$('#btn-test-prev').addEventListener('click', () => { + if (test.i > 0) { + ttsCancel(); + test.answers.pop(); + test.i--; + presentTestWord(); + } +}); + $('#btn-test-speak').addEventListener('click', () => speakWord(test.words[test.i])); $('#test-form').addEventListener('submit', async (e) => { diff --git a/public/index.html b/public/index.html index e15b78c..d68eb77 100644 --- a/public/index.html +++ b/public/index.html @@ -245,7 +245,7 @@