diff --git a/speech.js b/speech.js index 92197b2..2a4232a 100644 --- a/speech.js +++ b/speech.js @@ -1,32 +1,72 @@ -function initSpeech() { +var speechVoices = []; +var speechVoicesLoaded = false; + +function refreshSpeechVoices() { if (!window.speechSynthesis) return; var voices = window.speechSynthesis.getVoices(); - if (voices.length > 0) return; - window.speechSynthesis.onvoiceschanged = function() {}; -} - -function speakText(text, lang) { - if (!text) return; - if (!window.speechSynthesis) { return; } - if (window.speechSynthesis.getVoices().length === 0) { - window.speechSynthesis.onvoiceschanged = function() { - doSpeak(text, lang); - window.speechSynthesis.onvoiceschanged = null; - }; - window.speechSynthesis.getVoices(); - return; + if (voices && voices.length) { + speechVoices = voices; + speechVoicesLoaded = true; } - doSpeak(text, lang); + return speechVoices; } -function doSpeak(text, lang) { - window.speechSynthesis.cancel(); +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; - window.speechSynthesis.speak(utterance); + 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();