diff --git a/modules/turnero/views/display_global.php b/modules/turnero/views/display_global.php index b94620d..82840aa 100644 --- a/modules/turnero/views/display_global.php +++ b/modules/turnero/views/display_global.php @@ -558,8 +558,12 @@ function getVozES() { } if ('speechSynthesis' in window) { window.speechSynthesis.onvoiceschanged = () => { _vozES = null; getVozES(); }; - // Keepalive: Chrome pausa speechSynthesis tras ~15 min — correr siempre, no solo cuando !speaking + // Keepalive: Chrome suspende speechSynthesis tras un rato sin uso. + // Solo debe correr con el sintetizador en reposo: pause() a mitad de una + // frase la corta, y como el anuncio dura más de 10 segundos, antes lo + // troceaba siempre. setInterval(() => { + if (window.speechSynthesis.speaking || window.speechSynthesis.pending) return; window.speechSynthesis.pause(); window.speechSynthesis.resume(); }, 10000); @@ -582,8 +586,17 @@ function anunciarTurno(codigo, destino, paciente) { utt.lang = 'es-CO'; const voz = getVozES(); if (voz) utt.voice = voz; - utt.rate = 0.85; utt.pitch = 1.05; utt.volume = 1; - setTimeout(() => { window.speechSynthesis.cancel(); window.speechSynthesis.speak(utt); }, 300); + utt.rate = 0.95; utt.pitch = 1.05; utt.volume = 1; + + // Con el sintetizador libre se habla de inmediato. Solo cuando hay algo en + // curso hace falta cancelar y esperar un instante: Chrome ignora un speak() + // encadenado a un cancel() en el mismo ciclo. + if (window.speechSynthesis.speaking || window.speechSynthesis.pending) { + window.speechSynthesis.cancel(); + setTimeout(() => window.speechSynthesis.speak(utt), 80); + } else { + window.speechSynthesis.speak(utt); + } return utt; } @@ -645,9 +658,24 @@ function showAnnouncement({ codigo, destino, paciente, color }) { ov.classList.remove('visible'); setTimeout(processQueue, 370); } - // Cierra al terminar el TTS; mínimo 5s visual - if (utt) utt.addEventListener('end', () => setTimeout(dismiss, 500)); - setTimeout(dismiss, 5000); + + // El cartel se cierra cuando terminó de hablar Y se cumplió el mínimo + // visual. Cerrar a los 5 s sin esperar la voz hacía que el siguiente + // llamado cancelara al anterior a media frase. + const MIN_VISIBLE = 5000; + const abiertoEn = Date.now(); + const cerrarCuandoToque = () => { + setTimeout(dismiss, Math.max(0, MIN_VISIBLE - (Date.now() - abiertoEn))); + }; + + if (utt) { + utt.addEventListener('end', () => setTimeout(cerrarCuandoToque, 400)); + utt.addEventListener('error', cerrarCuandoToque); + // Red de seguridad: Chrome a veces no emite 'end' y el cartel quedaría fijo + setTimeout(dismiss, 25000); + } else { + setTimeout(dismiss, MIN_VISIBLE); + } } /* ── State ── */ @@ -797,7 +825,8 @@ async function cargarSnapshot() { } cargarSnapshot(); -setInterval(cargarSnapshot, 2000); +// 1 s en vez de 2: el llamado salía con hasta 2 s de retraso desde el clic +setInterval(cargarSnapshot, 1000); document.addEventListener('visibilitychange', () => { if (!document.hidden) cargarSnapshot(); }); window.addEventListener('focus', cargarSnapshot);