update calling overlap

This commit is contained in:
Flatlogic Bot 2026-04-08 17:02:19 +00:00
parent 97e014105c
commit bc392b3f27

View File

@ -224,65 +224,126 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}; };
const checkAnnouncements = () => { const announcementQueue = [];
const cards = Array.from(document.querySelectorAll('.announcement-card')); const queuedAnnouncementKeys = new Set();
const latest = cards[0]; let announcementPlaying = false;
const audioEnabled = window.localStorage.getItem('hospitalQueue:audioEnabled') !== 'false'; let activeAnnouncementAudio = null;
let activeSpeechUtterance = null;
let audioLoadTimeoutId = null;
if (latest) { const clearAnnouncementPlayback = () => {
const announcementKey = latest.dataset.announcementKey || ''; if (audioLoadTimeoutId) {
const storageKey = `hospitalQueue:lastAnnouncement:${locale}`; window.clearTimeout(audioLoadTimeoutId);
const storedKey = window.localStorage.getItem(storageKey) || ''; audioLoadTimeoutId = null;
}
if (announcementKey && announcementKey !== storedKey) { if (activeAnnouncementAudio) {
window.localStorage.setItem(storageKey, announcementKey); activeAnnouncementAudio.onended = null;
activeAnnouncementAudio.onerror = null;
activeAnnouncementAudio.pause();
activeAnnouncementAudio.removeAttribute('src');
activeAnnouncementAudio.load();
activeAnnouncementAudio = null;
}
if (audioEnabled) { if (activeSpeechUtterance) {
activeSpeechUtterance.onend = null;
activeSpeechUtterance.onerror = null;
activeSpeechUtterance = null;
}
};
const queueAnnouncement = (card) => {
if (!card) return;
const key = card.dataset.announcementKey || '';
const text = locale === 'ar' ? (card.dataset.announcementAr || '') : (card.dataset.announcementEn || '');
if (!key || !text || queuedAnnouncementKeys.has(key)) return;
announcementQueue.push({ key, text });
queuedAnnouncementKeys.add(key);
playNextAnnouncement();
};
const playNextAnnouncement = () => {
if (announcementPlaying || announcementQueue.length === 0) {
return;
}
const nextAnnouncement = announcementQueue.shift();
if (!nextAnnouncement) {
return;
}
announcementPlaying = true;
const videoPlayer = document.getElementById('adsVideoPlayer'); const videoPlayer = document.getElementById('adsVideoPlayer');
if (videoPlayer) videoPlayer.volume = 0.1; if (videoPlayer) videoPlayer.volume = 0.1;
const text = locale === 'ar' ? (latest.dataset.announcementAr || '') : (latest.dataset.announcementEn || ''); const finishAnnouncement = () => {
let audioObj = null; clearAnnouncementPlayback();
if (text) { if ('speechSynthesis' in window && (window.speechSynthesis.speaking || window.speechSynthesis.pending)) {
const tl = locale === 'ar' ? 'ar' : 'en'; window.speechSynthesis.cancel();
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
} }
if (videoPlayer) videoPlayer.volume = 1.0;
queuedAnnouncementKeys.delete(nextAnnouncement.key);
announcementPlaying = false;
playNextAnnouncement();
};
playChime(); playChime();
setTimeout(() => { window.setTimeout(() => {
if (audioObj) { const tl = locale === 'ar' ? 'ar' : 'en';
audioObj.onended = () => { if (videoPlayer) videoPlayer.volume = 1.0; }; const ttsUrl = window.location.pathname.replace(/\/[^\/]*$/, '/api/tts.php') + '?lang=' + tl + '&text=' + encodeURIComponent(nextAnnouncement.text);
const audioObj = new Audio(ttsUrl);
audioObj.preload = 'auto';
activeAnnouncementAudio = audioObj;
let finished = false;
const done = () => {
if (finished) return;
finished = true;
finishAnnouncement();
};
let fallbackPlayed = false;
const handleFallback = (err) => { const handleFallback = (err) => {
if (fallbackPlayed) return; if (finished) return;
fallbackPlayed = true; console.warn('External TTS failed, falling back to built-in speech', err);
console.warn("External TTS failed, falling back to built-in speech", err);
if (activeAnnouncementAudio === audioObj) {
audioObj.onended = null;
audioObj.onerror = null;
audioObj.pause();
audioObj.removeAttribute('src');
audioObj.load();
activeAnnouncementAudio = null;
}
if ('speechSynthesis' in window) { if ('speechSynthesis' in window) {
const utterance = new SpeechSynthesisUtterance(text); const utterance = new SpeechSynthesisUtterance(nextAnnouncement.text);
utterance.lang = locale === 'ar' ? 'ar-SA' : 'en-US'; utterance.lang = locale === 'ar' ? 'ar-SA' : 'en-US';
const voices = availableVoices.length > 0 ? availableVoices : window.speechSynthesis.getVoices(); const voices = availableVoices.length > 0 ? availableVoices : window.speechSynthesis.getVoices();
const langPrefix = locale === 'ar' ? 'ar' : 'en'; const langPrefix = locale === 'ar' ? 'ar' : 'en';
const langVoices = voices.filter(v => v.lang.toLowerCase().startsWith(langPrefix)); const langVoices = voices.filter((voice) => voice.lang.toLowerCase().startsWith(langPrefix));
if (langVoices.length > 0) { if (langVoices.length > 0) {
const bestVoice = langVoices.find(v => const bestVoice = langVoices.find((voice) =>
v.name.includes('Google') || v.name.includes('Natural') || v.name.includes('Premium') || v.name.includes('Online') voice.name.includes('Google') || voice.name.includes('Natural') || voice.name.includes('Premium') || voice.name.includes('Online')
) || langVoices.find(v => v.name.includes('Microsoft')) || langVoices[0]; ) || langVoices.find((voice) => voice.name.includes('Microsoft')) || langVoices[0];
if (bestVoice) utterance.voice = bestVoice; if (bestVoice) utterance.voice = bestVoice;
} }
utterance.onend = () => { if (videoPlayer) videoPlayer.volume = 1.0; }; activeSpeechUtterance = utterance;
utterance.onerror = () => { if (videoPlayer) videoPlayer.volume = 1.0; }; utterance.onend = done;
window.speechSynthesis.cancel(); // Clear any stuck queue utterance.onerror = done;
if (window.speechSynthesis.speaking || window.speechSynthesis.pending) {
window.speechSynthesis.cancel();
}
window.speechSynthesis.speak(utterance); window.speechSynthesis.speak(utterance);
} else { } else {
if (videoPlayer) videoPlayer.volume = 1.0; done();
} }
}; };
audioObj.onended = done;
audioObj.onerror = handleFallback; audioObj.onerror = handleFallback;
const playPromise = audioObj.play(); const playPromise = audioObj.play();
@ -290,19 +351,47 @@ document.addEventListener('DOMContentLoaded', () => {
playPromise.catch(handleFallback); playPromise.catch(handleFallback);
} }
// Failsafe timeout in case audioObj hangs (e.g., blocked by adblocker, bad network) audioLoadTimeoutId = window.setTimeout(() => {
setTimeout(() => { if (!finished && (audioObj.networkState === HTMLMediaElement.NETWORK_NO_SOURCE || audioObj.error)) {
if (audioObj.networkState === HTMLMediaElement.NETWORK_NO_SOURCE || audioObj.error) { handleFallback(new Error('Audio load timeout'));
handleFallback(new Error("Audio load timeout"));
} }
}, 2500); }, 2500);
}, 500);
};
const checkAnnouncements = () => {
const cards = Array.from(document.querySelectorAll('.announcement-card'));
const latest = cards[0];
const audioEnabled = window.localStorage.getItem('hospitalQueue:audioEnabled') !== 'false';
if (!latest) {
return;
}
const storageKey = `hospitalQueue:lastAnnouncement:${locale}`;
const storedKey = window.localStorage.getItem(storageKey) || '';
const latestKey = latest.dataset.announcementKey || '';
if (!latestKey || latestKey === storedKey) {
return;
}
let newCards = [];
if (!storedKey) {
newCards = [latest];
} else { } else {
if (videoPlayer) videoPlayer.volume = 1.0; for (const card of cards) {
} const key = card.dataset.announcementKey || '';
}, 500); // reduced timeout from 1200ms to 500ms for faster playback if (!key) continue;
if (key === storedKey) break;
newCards.push(card);
} }
newCards.reverse();
} }
window.localStorage.setItem(storageKey, latestKey);
if (audioEnabled) {
newCards.forEach(queueAnnouncement);
} }
}; };