diff --git a/api/tts.php b/api/tts.php new file mode 100644 index 0000000..f7c47f7 --- /dev/null +++ b/api/tts.php @@ -0,0 +1,33 @@ + { if (e && e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ') return; if (window.localStorage.getItem('hospitalQueue:audioEnabled') !== 'false') { initAudio(); + + // Prime Speech Synthesis + if ('speechSynthesis' in window) { + const primeUtterance = new SpeechSynthesisUtterance(''); + primeUtterance.volume = 0; + window.speechSynthesis.speak(primeUtterance); + } + + // Prime HTML5 Audio for the origin + try { + const dummyAudio = new Audio('data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA'); + dummyAudio.volume = 0.01; + const p = dummyAudio.play(); + if (p !== undefined) p.catch(() => {}); + } catch (err) {} } document.removeEventListener('click', unlockAudio); document.removeEventListener('keydown', unlockAudio); + document.removeEventListener('touchstart', unlockAudio); }; document.addEventListener('click', unlockAudio); document.addEventListener('keydown', unlockAudio); + document.addEventListener('touchstart', unlockAudio); let availableVoices = []; if ('speechSynthesis' in window) { @@ -228,8 +245,8 @@ document.addEventListener('DOMContentLoaded', () => { let audioObj = null; if (text) { const tl = locale === 'ar' ? 'ar' : 'en'; - const gTtsUrl = 'https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=' + tl + '&q=' + encodeURIComponent(text); - audioObj = new Audio(gTtsUrl); + const ttsUrl = window.location.pathname.replace(/\/[^\/]*$/, '/api/tts.php') + '?lang=' + tl + '&text=' + encodeURIComponent(text); + audioObj = new Audio(ttsUrl); audioObj.preload = 'auto'; // Force browser to start downloading } @@ -260,6 +277,7 @@ document.addEventListener('DOMContentLoaded', () => { utterance.onend = () => { if (videoPlayer) videoPlayer.volume = 1.0; }; utterance.onerror = () => { if (videoPlayer) videoPlayer.volume = 1.0; }; + window.speechSynthesis.cancel(); // Clear any stuck queue window.speechSynthesis.speak(utterance); } else { if (videoPlayer) videoPlayer.volume = 1.0; @@ -271,6 +289,14 @@ document.addEventListener('DOMContentLoaded', () => { if (playPromise !== undefined) { playPromise.catch(handleFallback); } + + // Failsafe timeout in case audioObj hangs (e.g., blocked by adblocker, bad network) + setTimeout(() => { + if (audioObj.networkState === HTMLMediaElement.NETWORK_NO_SOURCE || audioObj.error) { + handleFallback(new Error("Audio load timeout")); + } + }, 2500); + } else { if (videoPlayer) videoPlayer.volume = 1.0; } diff --git a/queue_bootstrap.php b/queue_bootstrap.php index 04112a7..7f33903 100644 --- a/queue_bootstrap.php +++ b/queue_bootstrap.php @@ -695,12 +695,28 @@ function qh_render_nav(string $activePage): void echo ' '; echo '