From cbcc0a9d8de304f093c960efa2e6861c36d3deec Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 28 Jul 2026 06:47:19 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20voz=20TTS=20no=20sonaba=20en=20display?= =?UTF-8?q?=5Fglobal=20por=20voz=20online=20y=20falta=20de=20activaci?= =?UTF-8?q?=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getVozES() prefiere voces locales (offline) sobre Microsoft Online que fallan silenciosamente sin internet o con latencia - cancel() antes de speak() para limpiar cola atascada - keepalive sin check !speaking, cada 10s - sonidoActivo persiste en localStorage; banner rojo si no activado - AudioContext se resume si suspended antes de reproducir Co-Authored-By: Claude Sonnet 4.6 --- modules/turnero/views/display_global.php | 50 ++++++++++++++++++------ 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/modules/turnero/views/display_global.php b/modules/turnero/views/display_global.php index 67795f9..bd42cd9 100644 --- a/modules/turnero/views/display_global.php +++ b/modules/turnero/views/display_global.php @@ -373,6 +373,10 @@ $_hasVideo = (bool)$_tvVideo;
+ +
@@ -466,21 +470,30 @@ let audioCtx = null, sonidoActivo = false; function activarSonido() { try { - audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + if (!audioCtx) { + audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + } else if (audioCtx.state === 'suspended') { + audioCtx.resume(); + } const g = audioCtx.createGain(); g.gain.setValueAtTime(0.001, audioCtx.currentTime); const o = audioCtx.createOscillator(); o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime + 0.05); } catch (_) {} sonidoActivo = true; + localStorage.setItem('turneroSonido', '1'); const btn = document.getElementById('btn-sonido'); if (btn) { btn.innerHTML = ''; btn.classList.add('on'); } + const banner = document.getElementById('sonido-banner'); + if (banner) banner.style.display = 'none'; setTimeout(playBeep, 100); } -document.addEventListener('click', () => { if (!sonidoActivo) activarSonido(); }, { once: true }); -document.addEventListener('touchstart', () => { if (!sonidoActivo) activarSonido(); }, { once: true }); +// Sin once:true para que cualquier clic reactive si la página recargó +document.addEventListener('click', () => { if (!sonidoActivo) activarSonido(); }); +document.addEventListener('touchstart', () => { if (!sonidoActivo) activarSonido(); }); function playBeep() { if (!sonidoActivo || !audioCtx) return; + if (audioCtx.state === 'suspended') { audioCtx.resume().then(playBeep); return; } try { const osc = audioCtx.createOscillator(); const g = audioCtx.createGain(); @@ -496,13 +509,21 @@ function playBeep() { } catch (_) {} } -// Selección de voz española — cargada una vez, reutilizada en cada anuncio +// Selección de voz española — preferir locales (offline) sobre online para evitar fallos de red let _vozES = null; function getVozES() { if (_vozES) return _vozES; const voices = window.speechSynthesis.getVoices(); - // Preferencia: es-CO → es-419 → es-MX → es-US → es-ES → cualquier es-* - for (const lang of ['es-CO','es-419','es-MX','es-US','es-ES']) { + // 1) Voz local (localService=true) en orden de preferencia + for (const lang of ['es-CO','es-MX','es-US','es-ES']) { + const v = voices.find(v => v.lang === lang && v.localService); + if (v) { _vozES = v; return v; } + } + // 2) Cualquier voz local en español + const vLocal = voices.find(v => v.lang.startsWith('es') && v.localService); + if (vLocal) { _vozES = vLocal; return vLocal; } + // 3) Fallback: voz online (puede fallar sin internet) + for (const lang of ['es-CO','es-MX','es-US','es-ES']) { const v = voices.find(v => v.lang === lang); if (v) { _vozES = v; return v; } } @@ -512,13 +533,16 @@ function getVozES() { } if ('speechSynthesis' in window) { window.speechSynthesis.onvoiceschanged = () => { _vozES = null; getVozES(); }; - // Keepalive: Chrome pausa speechSynthesis tras ~15 min sin interacción del usuario + // Keepalive: Chrome pausa speechSynthesis tras ~15 min — correr siempre, no solo cuando !speaking setInterval(() => { - if (!window.speechSynthesis.speaking) { - window.speechSynthesis.pause(); - window.speechSynthesis.resume(); - } - }, 14000); + window.speechSynthesis.pause(); + window.speechSynthesis.resume(); + }, 10000); +} +// Auto-restore sonido si estaba activo antes de un reload +if (localStorage.getItem('turneroSonido') === '1') { + const banner = document.getElementById('sonido-banner'); + if (banner) banner.style.display = 'flex'; } function anunciarTurno(codigo, destino, paciente) { @@ -534,7 +558,7 @@ function anunciarTurno(codigo, destino, paciente) { const voz = getVozES(); if (voz) utt.voice = voz; utt.rate = 0.85; utt.pitch = 1.05; utt.volume = 1; - setTimeout(() => window.speechSynthesis.speak(utt), 800); + setTimeout(() => { window.speechSynthesis.cancel(); window.speechSynthesis.speak(utt); }, 300); return utt; }