3. Web Speech API 入门:SpeechRecognition 对象详解

好,咱们进入实战环节了。前面两章聊了语音识别的基本概念和浏览器兼容性,现在该动手了。Web Speech API 是浏览器原生提供的语音识别接口,说白了,就是让网页能“听懂”人话。我最早接触这个 API 是在 2016 年,那时候 Chrome 刚支持,坑还不少。现在稳定多了,但有些细节还是得注意。

3.1 SpeechRecognition 对象:一切从这里开始

要使用浏览器语音识别,第一步就是创建 SpeechRecognition 实例。不过要注意,这个对象在浏览器里是带前缀的。

// 兼容写法,我习惯这样写
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;

if (!SpeechRecognition) {
  console.error('浏览器不支持语音识别');
  return;
}

const recognition = new SpeechRecognition();

嗯,这里有个坑。Safari 和 Firefox 虽然也实现了部分 Web Speech API,但 SpeechRecognition 目前还是 Chrome 系浏览器支持最好。我在项目中遇到过用户用 Safari 打开后完全没反应的情况,所以建议在入口处做特性检测,并给出友好提示。

3.2 连续识别 vs 单次识别:两种模式怎么选?

这是最核心的配置项之一。先看代码:

// 单次识别模式(默认)
recognition.continuous = false;

// 连续识别模式
recognition.continuous = true;

两种模式的区别,我用一个表格来说明:

特性 单次识别(continuous: false) 连续识别(continuous: true)
触发方式 用户停止说话后自动结束 持续监听,直到手动停止
适用场景 搜索框、语音指令、短命令 语音输入法、会议记录、实时字幕
资源消耗 低,用完即释放 较高,需要持续占用麦克风
结果回调 一次性返回最终结果 多次返回中间结果和最终结果
自动重启 不需要 通常需要监听 end 事件手动重启

我的建议: 如果你做的是语音助手类的产品,比如“打开设置”、“下一页”这种短指令,用单次模式就够了。如果是做语音输入法或者会议记录,必须用连续模式。我曾经在一个项目里误用了单次模式做语音输入,结果用户说长句时识别到一半就断了,体验非常糟糕。

3.3 语言设置:让 API 听懂你的方言

语言设置通过 lang 属性控制。这个属性决定了语音识别引擎用哪种语言模型来解析音频。

// 设置中文普通话
recognition.lang = 'zh-CN';

// 设置美式英语
recognition.lang = 'en-US';

// 设置粤语
recognition.lang = 'zh-HK';

// 设置日语
recognition.lang = 'ja-JP';

常见的语言代码对照表:

语言 语言代码 备注
中文普通话 zh-CN 最常用,识别率较高
中文(台湾) zh-TW 繁体字,口音不同
粤语 zh-HK 香港地区使用
美式英语 en-US 全球通用
英式英语 en-GB 英式发音
日语 ja-JP 支持较好
韩语 ko-KR 支持一般

小技巧: 如果你需要支持多语言,可以在用户选择语言时动态设置 recognition.lang。但要注意,切换语言后最好重新创建 Recognition 实例,因为有些浏览器在运行时修改 lang 属性不会生效。我踩过这个坑,后来统一采用“重新实例化”的策略。

3.4 结果回调:拿到识别文本的关键

语音识别的结果通过事件回调返回。最常用的是 result 事件。

recognition.onresult = function(event) {
  // event.results 是一个 SpeechRecognitionResultList 对象
  const result = event.results[event.results.length - 1];
  
  // result[0] 是最佳识别结果
  const transcript = result[0].transcript;
  const confidence = result[0].confidence;
  
  console.log('识别文本:', transcript);
  console.log('置信度:', confidence);
  
  // 判断是否是最终结果
  if (result.isFinal) {
    console.log('这是最终结果');
  } else {
    console.log('这是中间结果');
  }
};

这里有几个关键点需要理解:

  • event.results:是一个类数组对象,每个元素对应一次识别的结果。连续模式下,每次语音输入都会追加一条。
  • result[0]:每个识别结果包含多个候选,result[0] 是置信度最高的那个。
  • transcript:识别出的文本内容。
  • confidence:置信度,0 到 1 之间的小数。越高说明引擎越有把握。
  • isFinal:标记当前结果是中间结果还是最终结果。连续模式下,用户说话过程中会不断返回中间结果,说话停顿后返回最终结果。

注意: 置信度并不总是可靠的。我在项目中遇到过置信度 0.95 但识别完全错误的情况,也遇到过置信度 0.3 但识别正确的情况。所以不要完全依赖置信度做业务判断,尤其是关键指令场景。

3.5 其他重要事件:监听整个生命周期

除了 result,还有几个事件需要关注:

// 开始监听
recognition.onstart = function() {
  console.log('语音识别已启动,请说话...');
  // 可以在这里更新 UI,比如显示麦克风图标动画
};

// 监听结束
recognition.onend = function() {
  console.log('语音识别已结束');
  // 连续模式下,需要在这里重启
  if (recognition.continuous) {
    recognition.start();
  }
};

// 发生错误
recognition.onerror = function(event) {
  console.error('识别出错:', event.error);
  // 常见错误:no-speech(没检测到语音)、aborted(被中断)、network(网络问题)
  
  switch (event.error) {
    case 'no-speech':
      // 提示用户说话
      break;
    case 'aborted':
      // 用户手动停止
      break;
    case 'network':
      // 检查网络连接
      break;
    default:
      // 其他错误
  }
};

// 音频开始捕获
recognition.onaudiostart = function() {
  console.log('开始捕获音频');
};

// 音频结束捕获
recognition.onaudioend = function() {
  console.log('音频捕获结束');
};

你想想看,如果连续模式下不监听 end 事件并重启,识别就会在用户第一次说完话后自动停止。这是新手最容易犯的错误。

3.6 完整示例:一个可用的语音识别组件

把上面的知识点串起来,写一个完整的示例:

class VoiceRecognition {
  constructor(options = {}) {
    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SpeechRecognition) {
      throw new Error('浏览器不支持语音识别');
    }
    
    this.recognition = new SpeechRecognition();
    this.recognition.lang = options.lang || 'zh-CN';
    this.recognition.continuous = options.continuous || false;
    this.recognition.interimResults = options.interimResults || false;
    
    this.isListening = false;
    this.setupCallbacks(options);
  }
  
  setupCallbacks(options) {
    this.recognition.onresult = (event) => {
      const result = event.results[event.results.length - 1];
      const transcript = result[0].transcript;
      const confidence = result[0].confidence;
      
      if (result.isFinal) {
        options.onFinalResult?.(transcript, confidence);
      } else {
        options.onInterimResult?.(transcript, confidence);
      }
    };
    
    this.recognition.onerror = (event) => {
      options.onError?.(event.error);
    };
    
    this.recognition.onend = () => {
      this.isListening = false;
      options.onEnd?.();
      
      // 连续模式下自动重启
      if (this.recognition.continuous && !this._stopped) {
        this.start();
      }
    };
  }
  
  start() {
    this._stopped = false;
    this.recognition.start();
    this.isListening = true;
  }
  
  stop() {
    this._stopped = true;
    this.recognition.stop();
    this.isListening = false;
  }
  
  abort() {
    this._stopped = true;
    this.recognition.abort();
    this.isListening = false;
  }
}

// 使用示例
const recognizer = new VoiceRecognition({
  lang: 'zh-CN',
  continuous: true,
  interimResults: true,
  onFinalResult: (text, confidence) => {
    console.log('最终结果:', text, '置信度:', confidence);
  },
  onInterimResult: (text, confidence) => {
    console.log('中间结果:', text);
  },
  onError: (error) => {
    console.error('错误:', error);
  },
  onEnd: () => {
    console.log('识别结束');
  }
});

recognizer.start();

3.7 核心流程一览

为了让你更直观地理解整个语音识别的工作流程,我画了一张图:

SpeechRecognition 核心工作流程 创建 SpeechRecognition 实例 配置 lang / continuous / interimResults 设置事件回调 recognition.start() onstart → onaudiostart → onresult → onaudioend → onend (可能触发 onerror) 获取 transcript / confidence 判断 isFinal 处理中间/最终结果 关键配置说明 • lang:语言模型选择 • continuous:是否连续识别 • interimResults:是否返回中间结果 常见错误处理 • no-speech:未检测到语音 • aborted:用户中断 • network:网络异常 连续模式注意事项 • 必须在 onend 中重启 • 注意内存泄漏问题 • 用户停止时调用 stop()

这张图把整个流程串起来了。从创建实例到配置参数,再到启动识别和事件处理,最后拿到结果。你写代码的时候可以对照着这张图,不容易漏掉关键步骤。

3.8 避坑指南:我踩过的那些坑

最后分享几个实战中容易遇到的问题:

  • HTTPS 要求:语音识别 API 要求在 HTTPS 环境下运行。本地开发可以用 localhost,但线上必须是 HTTPS。我曾经在 HTTP 环境下调试了半天,一直报错,后来才发现是协议问题。
  • 用户授权:首次调用 start() 时,浏览器会弹出麦克风权限请求。如果用户拒绝,会触发 error 事件,错误类型是 not-allowed。建议在调用前先检查权限状态。
  • 内存泄漏:连续模式下,如果频繁创建和销毁 Recognition 实例,可能会导致内存泄漏。我习惯用一个全局实例,通过 start() 和 stop() 控制开关,而不是每次都 new 一个新的。
  • 音频冲突:如果页面中同时有多个 Recognition 实例在运行,可能会产生音频冲突。确保同一时间只有一个实例在监听。

核心要点回顾:

  • 创建实例时注意浏览器前缀兼容
  • 根据场景选择单次或连续模式
  • 语言设置要匹配用户实际使用的语言
  • 结果回调中区分中间结果和最终结果
  • 连续模式下必须在 onend 中重启
  • 做好错误处理和权限管理