diff --git a/assets/js/main.js b/assets/js/main.js index 835a5ee..53c024d 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -84,7 +84,7 @@ document.addEventListener('DOMContentLoaded', () => { fullscreenButton.hidden = true; } - let audioCtx = null; + let audioCtx = null; const initAudio = () => { try { const AudioContext = window.AudioContext || window.webkitAudioContext; @@ -99,14 +99,23 @@ document.addEventListener('DOMContentLoaded', () => { } }; + let availableVoices = []; + if ('speechSynthesis' in window) { + const updateVoices = () => { availableVoices = window.speechSynthesis.getVoices(); }; + updateVoices(); + if (window.speechSynthesis.onvoiceschanged !== undefined) { + window.speechSynthesis.onvoiceschanged = updateVoices; + } + } + const audioBtn = document.querySelector('.js-audio-toggle'); if (audioBtn) { const updateAudioBtnState = () => { const isEnabled = window.localStorage.getItem('hospitalQueue:audioEnabled') === 'true'; if (isEnabled) { - audioBtn.innerHTML = ''; + audioBtn.innerHTML = ''; } else { - audioBtn.innerHTML = ''; + audioBtn.innerHTML = ''; } audioBtn.setAttribute('aria-pressed', isEnabled.toString()); @@ -183,10 +192,10 @@ document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { const text = locale === 'ar' ? (latest.dataset.announcementAr || '') : (latest.dataset.announcementEn || ''); if (text) { - window.speechSynthesis.cancel(); + // Removed window.speechSynthesis.cancel(); as it can prevent speech in some browsers const utterance = new SpeechSynthesisUtterance(text); utterance.lang = locale === 'ar' ? 'ar-SA' : 'en-US'; - const voices = window.speechSynthesis.getVoices(); + const voices = availableVoices.length > 0 ? availableVoices : window.speechSynthesis.getVoices(); const langPrefix = locale === 'ar' ? 'ar' : 'en'; const langVoices = voices.filter(v => v.lang.toLowerCase().startsWith(langPrefix)); @@ -218,7 +227,7 @@ document.addEventListener('DOMContentLoaded', () => { } }; - checkAnnouncements(); + checkAnnouncements(); const autoRefreshSeconds = parseInt(document.querySelector('[data-auto-refresh]')?.dataset.autoRefresh || '0', 10); if (autoRefreshSeconds > 0) { @@ -252,4 +261,4 @@ document.addEventListener('DOMContentLoaded', () => { }, autoRefreshSeconds * 1000); } } -}); +}); \ No newline at end of file diff --git a/display.php b/display.php index 979ea49..1ad1876 100644 --- a/display.php +++ b/display.php @@ -60,7 +60,7 @@ qh_page_start(
diff --git a/patch_audio.js b/patch_audio.js new file mode 100644 index 0000000..ce05434 --- /dev/null +++ b/patch_audio.js @@ -0,0 +1,87 @@ +const fs = require('fs'); +const path = require('path'); + +const jsPath = path.join(__dirname, 'assets/js/main.js'); +let jsCode = fs.readFileSync(jsPath, 'utf8'); + +// Replace the icon logic +jsCode = jsCode.replace( + /audioBtn\.innerHTML = '<\/i>';/g, + `audioBtn.innerHTML = '';` +).replace( + /audioBtn\.innerHTML = '<\/i>';/g, + `audioBtn.innerHTML = '';` +); + +// Add logic to test sound when audio is enabled +const repl1 = `if (nextState) { + initAudio(); + if ('speechSynthesis' in window) { + const primeUtterance = new SpeechSynthesisUtterance(''); + window.speechSynthesis.speak(primeUtterance); + } + }`; +const repl2 = `if (nextState) { + initAudio(); + if ('speechSynthesis' in window) { + const primeUtterance = new SpeechSynthesisUtterance(''); + window.speechSynthesis.speak(primeUtterance); + } + + // Force latest announcement to replay + const cards = Array.from(document.querySelectorAll('.announcement-card')); + const latest = cards[0]; + if (latest) { + const storageKey = +`hospitalQueue:lastAnnouncement:${locale} +`; + window.localStorage.removeItem(storageKey); + setTimeout(checkAnnouncements, 200); + } + }`; +jsCode = jsCode.replace(repl1, repl2); + +// Make sure speech synthesis gets voices early, and handle fallback properly +const oldVoiceLogic = `if (langVoices.length > 0) { + // Try to find a high-quality (Google/Microsoft Natural) voice + const bestVoice = langVoices.find(v => + v.name.includes('Google') || + v.name.includes('Natural') || + v.name.includes('Premium') || + v.name.includes('Online') + ) || langVoices.find(v => v.name.includes('Microsoft')) || langVoices[0]; + + utterance.voice = bestVoice; + }`; + +const newVoiceLogic = `if (langVoices.length > 0) { + // Try to find a high-quality (Google/Microsoft Natural) voice + const bestVoice = langVoices.find(v => + v.name.includes('Google') || + v.name.includes('Natural') || + v.name.includes('Premium') || + v.name.includes('Online') + ) || langVoices.find(v => v.name.includes('Microsoft')) || langVoices[0]; + + utterance.voice = bestVoice; + } else if (voices.length > 0) { + // Fallback to any available voice to ensure sound plays + utterance.voice = voices[0]; + }`; + +jsCode = jsCode.replace(oldVoiceLogic, newVoiceLogic); + +// Call getVoices early +if (!jsCode.includes('window.speechSynthesis.getVoices();')) { + jsCode = jsCode.replace( + `document.addEventListener('DOMContentLoaded', () => {`, + `document.addEventListener('DOMContentLoaded', () => { + if ('speechSynthesis' in window) { + window.speechSynthesis.onvoiceschanged = () => { window.speechSynthesis.getVoices(); }; + window.speechSynthesis.getVoices(); + }` + ); +} + +fs.writeFileSync(jsPath, jsCode); +console.log('patched'); diff --git a/queue_bootstrap.php b/queue_bootstrap.php index 249685a..90d0e20 100644 --- a/queue_bootstrap.php +++ b/queue_bootstrap.php @@ -625,6 +625,7 @@ function qh_page_start(string $activePage, string $pageTitle, string $metaDescri } echo ' '; echo ' '; + echo ' '; echo ' '; echo ' '; echo '';