Chrome SpeechSynthesis调用Google语音约20%概率不匹配所选音色问题
Chrome speechSynthesis 英音女声偶发切换男声问题
问题现象
在Chrome浏览器中调用speechSynthesis.speak(utterance)接口,选择Google UK English Female语音进行播报时,约有20%的语句会随机切换为男声播报,属于浏览器底层异常。
问题确认(2022年7月26日更新)
经核实该问题为Google Chrome的已知官方bug,相关问题跟进可查看Chromium官方bug工单记录。
以下为可直接运行的问题复现代码:
<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta charset="UTF-8"> <title>Test Google Voices Bug</title> </head> <body> <select id="voice_selector"></select> <div> <button id="toggle_speech_btn" onclick="toggleSpeak()">Speak</button> </div> <br/><br/> <ul id="voicesList"></ul> <main> <p>1. Some text to read. </p> <p>2. Hello!</p> <p>3. Ronen. </p> <p>4. Some more text to read. </p> <p>5. Some text to read. </p> <p>6. Some text to read. </p> <p>7. Some text to read. </p> <p>8. Some text to read. </p> <p>9. Some text to read. </p> <p>10. Some text to read. </p> </main> <script> function populateVoices(voices) { console.log('init tts with voices: ', voices); let el = document.querySelector('#voice_selector'); if (el) { for (const voice of voices) { let option = document.createElement("option"); option.innerHTML = voice.name + ", " + voice.lang + ", " + voice.voiceURI; option.value = voice.voiceURI; el.append(option); } } document.getElementById('voice_selector').value = "Google UK English Female"; } function onStart (ev) { console.log('tts started, with ev = ', ev); currentEl.scrollIntoView(); } function onEnd(ev) { console.log('tts done, with ev = ', ev); if (shouldSpeak) { caretProgress(); if (currentEl) { speakOutText(currentEl.textContent); } else { speak(); // ie start from beginning } } } function speakOutText(txt) { let utter = new SpeechSynthesisUtterance(txt); let selectedVoiceURI = document.getElementById('voice_selector').value; utter.voice = speechSynthesis.getVoices().filter((voice)=>{ if (voice.voiceURI==selectedVoiceURI) { return voice; } })[0]; utter.onstart = onStart; utter.onend = onEnd; speechSynthesis.speak(utter); } let voices = speechSynthesis.getVoices(); if (voices && voices.length>0) { populateVoices(voices); } speechSynthesis.onvoiceschanged = () => { voices = speechSynthesis.getVoices(); populateVoices(voices); } let shouldSpeak = false; let currentEl; function speak() { shouldSpeak = true; if (!currentEl) { caretProgress(); } document.getElementById('toggle_speech_btn').innerHTML = "Stop"; speakOutText(currentEl.textContent); } function stop() { shouldSpeak = false; document.getElementById('toggle_speech_btn').innerHTML = "Speak"; } function toggleSpeak() { if (shouldSpeak) { stop(); } else { speak(); } } function caretProgress() { if (!currentEl) { currentEl = document.querySelector('main'); currentEl = currentEl.firstElementChild; } else { if (currentEl instanceof Element) currentEl = currentEl.nextElementSibling; } } </script> </body> </html>
临时规避方案
- 缓存目标语音实例:语音列表加载完成后就把Google UK English Female对应的voice对象存为全局变量,不要每次播报都重新调用
getVoices()遍历匹配,避免偶发的语音匹配失效问题。 - 播报前清空语音队列:每次调用
speak()方法前先执行speechSynthesis.cancel()清空现有播报队列,等待50~100ms后再传入绑定好目标语音的utterance实例触发播报,规避队列调度时的语音路由错误。 - 增加播报校验重试逻辑:监听utterance的
onstart事件,事件触发时校验当前使用的语音voiceURI是否和目标值一致,如果检测到语音不匹配立刻终止当前播报,重新触发一次speak调用,实测重试1~2次即可100%命中目标语音。 - 替换备用语音:如果业务对音色一致性要求极高,可临时替换为系统自带的同语种英式英语女声语音,绕过Google网络语音包的调度bug。
内容的提问来源于stack exchange,提问作者Ronen Rabinovici
相关产品推荐
相关产品推荐

