关于语音合成那点事

最近有个项目蛮折磨人的,里面有个需求是将系统发出的消息转成语音信息,一开始简单查了一下,发现其实主流浏览器都有现成的语音合成 api

SpeechSynthesis - MDN

顺手封装成了一个工具函数,在后续项目中持续使用:

export function speechSpeak(
  // 播报消息
  msg: string,
  // 播报次数
  count = 1,
  // 播报异常回调
  errorCallback?: (error: string) => void
) {
  const utterance = new SpeechSynthesisUtterance();
  utterance.lang = "zh-CN";
  utterance.text = msg;
  for (let i = 0; i < count; i++) {
    speechSynthesis.speak(utterance);
  }
  utterance.onerror = (event) => {
    console.log("===语音播报异常", event);
    // 异常状态码参考 https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisErrorEvent/error
    errorCallback && errorCallback(event.error);
  };
  window.onbeforeunload = function () {
    // Chromium won't stop speaking after closing tab.
    // So shut up, pls.
    speechSynthesis.cancel();
  };
}

当然你也可以封装成一个 promise 函数 ~

最好选择离线可用的语音包,否则会受网络影响; 通过 speechSynthesis.getVoices() 检查设备可用的语音包,检查对应的 localService 属性.

当时看了下 caniuse,发现这个语音合成 api 很多浏览器都支持了,而且支持的版本也蛮多,比如 chrome >= 33,就觉得这个 api 是靠谱的,也就没去想什么了。于是就有了以下踩坑日记..

第一次被恶心到的是浏览器的自动播放声音策略。在最新版移动端 chrome 和 ios 的浏览器上基本无解(有解决办法的欢迎 comment,反正我是查了很多资料都没找着方法)。最终找到的靠谱方法有以下几个,都是通过一些方法关闭用户交互限制:

  • pc 端,可以通过设置声音的白名单实现,chrome 和 safari 实践可行(chrome 打开 chrome://settings/content/sound,在允许播放声音里添加网站)
  • window pc 可以通过命令行启动 chrome(关闭自动播放策略,chrome.exe --autoplay-policy=no-user-gesture-required
  • 降级到 chrome 74-76 版本,在 chrome://flag 中关闭用户交互限制。在 pc 或安卓都可以自动播放(ios 没试过,找不到那个版本的包…)

其他未经实践的思路有:

  • windows 平板,可能和 windows pc 一样可以配置网页白名单允许自动播放?或者通过命令行启动浏览器?
  • 做成移动端应用(react-native/flutter/…)

其实不难看出,自动播放策略在各个系统上的不同版本的浏览器的表现不太一样,目前看来所以最稳妥的方式无疑是做成移动端应用..

如果产品要往外推广的话,肯定要把这个限制干掉。

第二个恶心到我的是经常会偶现不能发出语音,即使是在确保用户有初次交互的情况下。当然,绝大部分是 pad 没收到消息,这里麻烦的点在于怎么定位语音问题,比如怎么确定 pad 有没有收到消息、是不是设备或者浏览器版本限制、是不是网络因素?

场景可以简单描述下,是通过 pad 访问车上的 pc,那这其中肯定就需要 pad 有移动网络。当时是在车上的 pc 有发语音,但 pad 没出声音(在用户交互按钮打开的情况下)。这种情况其实有可能是网络因素导致语音不稳定。所以思路有两点:

  • 记录 pad 的语音消息日志,输出到 pad 上。更好的方式是搭建异常监控平台,通过埋点的方式监控语音消息的接收情况,以可视化的形式展示出来,就不用额外占用网页空间
  • 但其实如果在车上,也可以通过数据线调试。但我们的测试工程师对于 web 网页调试技能比较欠缺,他们更多的关注系统和应用层面的测试。这块也就不太可行 emm

这以上问题,全是开发和测试过程中不断踩出来的。所以对于语音合成的实现,无非就是以下几个策略

  • 无自动播放需求,加个按钮确保页面加载的初次交互
    • 使用百度语音合成或者其他合成方式,先转成音频文件发给前端,收到后再播放出来
    • 如果需要离线,那只能借助下浏览器的语音合成 api 了,而且还要先查一下是否有对应的离线包(在安卓 pad 上基本都找不到离线的语音包..可能是文件大小限制?)
    • 或者可以写个 c ++ 服务,转成 wasm 供前端调用,前提是 c++服务能实现语音合成并输出音频的二进制流给到前端
  • 有自动播放需求
    • pc 设备可控,配置白名单
    • 做成 app,百度或者讯飞都有对应的语音合成离线包(安卓/ios)

当然,如果你要在网页实现自动播放,那要么设备可控、要么浏览器版本可控,限制其实蛮多的,说不定还有更多的坑等着你

以上种种会踩坑也是由于自身知识广度不够,加上调研不充分导致。by the way,持续跟踪这个特性吧,希望未来能更加稳定