fix: voz TTS no sonaba en display_global por voz online y falta de activación
- 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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
4b054c5afc
commit
cbcc0a9d8d
@@ -373,6 +373,10 @@ $_hasVideo = (bool)$_tvVideo;
|
|||||||
<div class="dot"></div>
|
<div class="dot"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="sonido-banner" onclick="activarSonido()" style="display:none;position:fixed;bottom:0;left:0;right:0;z-index:9999;background:rgba(239,68,68,.92);color:#fff;padding:.6rem 1.2rem;font-size:1rem;font-weight:600;cursor:pointer;align-items:center;justify-content:center;gap:.6rem">
|
||||||
|
<i class="fas fa-volume-mute"></i> Haz clic aquí para activar el sonido
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="ann-overlay">
|
<div id="ann-overlay">
|
||||||
<div class="ann-bg"></div>
|
<div class="ann-bg"></div>
|
||||||
<div class="ann-card">
|
<div class="ann-card">
|
||||||
@@ -466,21 +470,30 @@ let audioCtx = null, sonidoActivo = false;
|
|||||||
|
|
||||||
function activarSonido() {
|
function activarSonido() {
|
||||||
try {
|
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 g = audioCtx.createGain(); g.gain.setValueAtTime(0.001, audioCtx.currentTime);
|
||||||
const o = audioCtx.createOscillator(); o.connect(g); g.connect(audioCtx.destination);
|
const o = audioCtx.createOscillator(); o.connect(g); g.connect(audioCtx.destination);
|
||||||
o.start(); o.stop(audioCtx.currentTime + 0.05);
|
o.start(); o.stop(audioCtx.currentTime + 0.05);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
sonidoActivo = true;
|
sonidoActivo = true;
|
||||||
|
localStorage.setItem('turneroSonido', '1');
|
||||||
const btn = document.getElementById('btn-sonido');
|
const btn = document.getElementById('btn-sonido');
|
||||||
if (btn) { btn.innerHTML = '<i class="fas fa-volume-up"></i>'; btn.classList.add('on'); }
|
if (btn) { btn.innerHTML = '<i class="fas fa-volume-up"></i>'; btn.classList.add('on'); }
|
||||||
|
const banner = document.getElementById('sonido-banner');
|
||||||
|
if (banner) banner.style.display = 'none';
|
||||||
setTimeout(playBeep, 100);
|
setTimeout(playBeep, 100);
|
||||||
}
|
}
|
||||||
document.addEventListener('click', () => { if (!sonidoActivo) activarSonido(); }, { once: true });
|
// Sin once:true para que cualquier clic reactive si la página recargó
|
||||||
document.addEventListener('touchstart', () => { if (!sonidoActivo) activarSonido(); }, { once: true });
|
document.addEventListener('click', () => { if (!sonidoActivo) activarSonido(); });
|
||||||
|
document.addEventListener('touchstart', () => { if (!sonidoActivo) activarSonido(); });
|
||||||
|
|
||||||
function playBeep() {
|
function playBeep() {
|
||||||
if (!sonidoActivo || !audioCtx) return;
|
if (!sonidoActivo || !audioCtx) return;
|
||||||
|
if (audioCtx.state === 'suspended') { audioCtx.resume().then(playBeep); return; }
|
||||||
try {
|
try {
|
||||||
const osc = audioCtx.createOscillator();
|
const osc = audioCtx.createOscillator();
|
||||||
const g = audioCtx.createGain();
|
const g = audioCtx.createGain();
|
||||||
@@ -496,13 +509,21 @@ function playBeep() {
|
|||||||
} catch (_) {}
|
} 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;
|
let _vozES = null;
|
||||||
function getVozES() {
|
function getVozES() {
|
||||||
if (_vozES) return _vozES;
|
if (_vozES) return _vozES;
|
||||||
const voices = window.speechSynthesis.getVoices();
|
const voices = window.speechSynthesis.getVoices();
|
||||||
// Preferencia: es-CO → es-419 → es-MX → es-US → es-ES → cualquier es-*
|
// 1) Voz local (localService=true) en orden de preferencia
|
||||||
for (const lang of ['es-CO','es-419','es-MX','es-US','es-ES']) {
|
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);
|
const v = voices.find(v => v.lang === lang);
|
||||||
if (v) { _vozES = v; return v; }
|
if (v) { _vozES = v; return v; }
|
||||||
}
|
}
|
||||||
@@ -512,13 +533,16 @@ function getVozES() {
|
|||||||
}
|
}
|
||||||
if ('speechSynthesis' in window) {
|
if ('speechSynthesis' in window) {
|
||||||
window.speechSynthesis.onvoiceschanged = () => { _vozES = null; getVozES(); };
|
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(() => {
|
setInterval(() => {
|
||||||
if (!window.speechSynthesis.speaking) {
|
window.speechSynthesis.pause();
|
||||||
window.speechSynthesis.pause();
|
window.speechSynthesis.resume();
|
||||||
window.speechSynthesis.resume();
|
}, 10000);
|
||||||
}
|
}
|
||||||
}, 14000);
|
// 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) {
|
function anunciarTurno(codigo, destino, paciente) {
|
||||||
@@ -534,7 +558,7 @@ function anunciarTurno(codigo, destino, paciente) {
|
|||||||
const voz = getVozES();
|
const voz = getVozES();
|
||||||
if (voz) utt.voice = voz;
|
if (voz) utt.voice = voz;
|
||||||
utt.rate = 0.85; utt.pitch = 1.05; utt.volume = 1;
|
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;
|
return utt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user