You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 15:45:33