var speechVoices = []; var speechVoicesLoaded = false; function refreshSpeechVoices() { if (!window.speechSynthesis) return; var voices = window.speechSynthesis.getVoices(); if (voices && voices.length) { speechVoices = voices; speechVoicesLoaded = true; } return speechVoices; } function pickSpeechVoice(lang) { refreshSpeechVoices(); var target = lang.toLowerCase(); var exact = speechVoices.find(function(voice) { return String(voice.lang || '').toLowerCase() === target; }); if (exact) return exact; var prefix = target.split('-')[0]; return speechVoices.find(function(voice) { return String(voice.lang || '').toLowerCase().split('-')[0] === prefix; }) || null; } function buildSpeechUtterance(text, lang) { var utterance = new SpeechSynthesisUtterance(text); var langMap = { 'es': 'es-ES', 'en': 'en-US', 'zh': 'zh-CN' }; utterance.lang = langMap[lang] || lang; var voice = pickSpeechVoice(utterance.lang); if (voice) utterance.voice = voice; utterance.rate = 0.9; utterance.pitch = 1; return utterance; } function doSpeak(text, lang) { if (!window.speechSynthesis || !text) return; window.speechSynthesis.cancel(); window.speechSynthesis.speak(buildSpeechUtterance(text, lang)); } function speakText(text, lang) { if (!text || !window.speechSynthesis) return; refreshSpeechVoices(); if (speechVoicesLoaded) { doSpeak(text, lang); return; } var attempts = 0; var timer = setInterval(function() { attempts++; refreshSpeechVoices(); if (speechVoicesLoaded || attempts >= 8) { clearInterval(timer); doSpeak(text, lang); } }, 80); window.speechSynthesis.getVoices(); } function initSpeech() { if (!window.speechSynthesis) return; refreshSpeechVoices(); window.speechSynthesis.onvoiceschanged = function() { refreshSpeechVoices(); }; } if (document.readyState === 'complete') initSpeech(); else document.addEventListener('DOMContentLoaded', initSpeech);