From 4bc22bd00c89e5702934a0007b556c8c2656afea Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:05:56 -0500 Subject: [PATCH] =?UTF-8?q?Pantalla=20TV:=20el=20llamado=20por=20voz=20sal?= =?UTF-8?q?=C3=ADa=20entrecortado=20y=20con=20retraso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El troceo lo causaba el keepalive del sintetizador: corría cada 10 segundos sin verificar si estaba hablando, y pause() a mitad de frase la corta. Como un anuncio con nombre y destino supera los 10 segundos, lo alcanzaba siempre. Ahora solo actúa con el sintetizador en reposo, que es para lo que existe: evitar que Chrome lo suspenda tras un rato sin uso. El cartel se cerraba a los 5 segundos aunque la voz siguiera sonando, y al cerrarse pasaba al siguiente de la cola, que cancelaba el anuncio en curso. Si dos pacientes se llamaban seguido, el primero quedaba a media frase. Ahora se cierra cuando terminó de hablar y se cumplió el mínimo visual, con una red de seguridad por si el navegador no emite el evento de fin. Sobre la demora: se quita la espera fija de 300 ms antes de hablar —solo se mantiene, y reducida, cuando de verdad hay que cancelar algo en curso— y el sondeo baja de 2 s a 1 s. El retraso desde el clic pasa de 0,3–2,3 s a 0–1 s. La velocidad sube de 0.85 a 0.95: acorta el anuncio sin perder claridad. Validado con una simulación del reloj y del sintetizador: reproduce el corte con el código anterior y no ocurre con el nuevo. Co-Authored-By: Claude Opus 5 --- modules/turnero/views/display_global.php | 43 ++++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) 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);