SpeechSynthesis.getVoices() 返回空数组的解决方案

speechsynthesis api 的 `getvoices()` 方法在页面加载初期常返回空数组,因其语音列表异步加载;需监听 `voiceschanged` 事件确保语音就绪后再获取并设置 voice。

SpeechSy

nthesis.getVoices() 并非同步阻塞方法——它立即返回当前已加载的语音列表,而浏览器语音引擎(如 Chrome 的本地 TTS 或系统语音服务)通常在页面加载后异步初始化并注入可用语音。因此,若在脚本执行早期(例如

✅ 正确做法是:始终通过 speechSynthesis.onvoiceschanged 事件(或 addEventListener("voiceschanged", ...))监听语音就绪时机。该事件会在语音列表首次加载完成或后续动态更新(如系统新增语音)时触发,是唯一可靠的同步点。

以下是推荐的健壮实现方式:

function speakWithCustomVoice() {
  const msg = new SpeechSynthesisUtterance("Hello World!");

  // 确保语音已加载
  function setAndSpeak() {
    const voices = speechSynthesis.getVoices();

    // 安全检查:避免越界访问
    if (voices.length > 2) {
      msg.voice = voices[2]; // 例如选择第3个语音(如 Microsoft Linda)
      speechSynthesis.speak(msg);
    } else {
      console.warn("未检测到足够多的语音,使用默认语音播放");
      speechSynthesis.speak(msg);
    }
  }

  // 首次监听 voiceschanged
  speechSynthesis.addEventListener("voiceschanged", setAndSpeak, { once: true });

  // 兜底处理:若事件未触发(极罕见),延迟重试(可选)
  setTimeout(() => {
    if (speechSynthesis.getVoices().length === 0) {
      console.warn("警告:voiceschanged 事件未触发,尝试手动重试");
      setAndSpeak();
    }
  }, 1000);
}

speakWithCustomVoice();

⚠️ 注意事项:

  • 不要依赖 setTimeout 替代事件监听:setTimeout(…, 100) 是不可靠的“魔法数字”,不同设备/网络下语音加载时间差异大;
  • once: true 选项防止重复绑定:避免多次触发导致重复播报;
  • 始终校验 voices.length:索引访问前必须判断数组长度,否则可能报 Cannot set property 'voice' of undefined;
  • 跨浏览器兼容性:Firefox、Chrome、Edge 均支持 voiceschanged,但 Safari(iOS/macOS)对 Web Speech API 支持有限(仅部分版本支持且需用户授权),生产环境建议添加特性检测与降级提示。

总结:getVoices() 的“空数组陷阱”本质是异步资源加载时序问题。拥抱事件驱动模型,以 voiceschanged 为守门人,即可稳定、可维护地控制语音合成行为。