Web Speech API 实战:用浏览器原生语音识别与合成构建语音交互应用

深入解析 Web Speech API 的 SpeechRecognition 与 SpeechSynthesis 接口,手把手构建语音输入、实时转写、多语言 TTS 等生产级应用,附完整代码、浏览器兼容方案与性能优化策略。

前端开发 2026-06-05 14 分钟

浏览器语音能力已经今非昔比。根据 Chrome Platform Status 的数据,Web Speech API 的使用量在 2025-2026 年间增长了 220%,从在线教育的语音答题到 SaaS 产品的语音指令,从无障碍辅助工具到 AI 对话界面的语音输入层,语音交互正在成为现代 Web 应用的标配能力。如果你还在用第三方 SDK(如科大讯飞、百度语音)来实现浏览器端的语音功能,这篇文章会让你重新审视——浏览器原生的 Web Speech API 已经能覆盖 80% 以上的常见场景,而且零依赖、零费用、零延迟。

🎙️ 一、Web Speech API 全景解析

1.1 两大核心接口

Web Speech API 包含两个完全独立的接口,解决两个不同的问题:

维度 SpeechRecognition SpeechSynthesis
功能 语音 → 文字(ASR) 文字 → 语音(TTS)
核心能力 实时语音识别、连续转写 多语言朗读、语速语调控制
浏览器支持 Chrome 33+、Safari 14.1+ 所有主流浏览器
依赖 需要麦克风权限 无特殊权限
网络要求 Chrome 默认在线识别 完全离线可用
中文支持 ✅ 优秀(zh-CN) ✅ 优秀

⚠️ **警告:**Chrome 的 SpeechRecognition 默认使用 Google 的在线语音识别服务,音频数据会发送到 Google 服务器。如果你的应用处理敏感信息,需要评估隐私合规风险。Safari 使用 Apple 的本地识别引擎,数据不会离开设备。

1.2 为什么选择原生 API 而非第三方 SDK

在决定技术方案之前,先看一组对比数据:

对比维度 Web Speech API 科大讯飞 SDK 百度语音 SDK
集成成本 零依赖,浏览器内置 需引入 JS SDK(~150KB) 需引入 JS SDK(~200KB)
费用 免费 免费额度有限,超出按量计费 免费额度有限,超出按量计费
延迟 极低(浏览器原生通道) 中等(额外网络跳转) 中等(额外网络跳转)
中文识别准确率 约 92-95% 约 96-98% 约 95-97%
离线能力 Safari 支持,Chrome 不支持 不支持 不支持
适用场景 通用语音交互、辅助功能 高精度转写、专业术语 高精度转写、方言识别

⚡ **关键结论:**如果你的应用不需要 98% 以上的专业级识别精度(如医疗病历转写、法律文书口述),Web Speech API 是更优选择。零成本、零依赖、浏览器原生——对于 90% 的 Web 应用来说,这个精度已经足够。

🔊 二、SpeechRecognition 语音识别实战

2.1 基础用法:一次性语音识别

最常见的场景是「按住说话」——用户点击按钮开始录音,松开结束,系统返回识别结果:

// 一次性语音识别 — 点击按钮说话,获取识别结果
class SimpleSpeechRecognizer {
  constructor() {
    // 检测浏览器支持
    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SpeechRecognition) {
      throw new Error('此浏览器不支持 Web Speech API');
    }
    this.recognition = new SpeechRecognition();
    this.recognition.lang = 'zh-CN';        // 设置中文识别
    this.recognition.interimResults = false;  // 只要最终结果
    this.recognition.maxAlternatives = 1;     // 只返回最可能的一个结果
  }

  async start() {
    return new Promise((resolve, reject) => {
      this.recognition.onresult = (event) => {
        const transcript = event.results[0][0].transcript;
        const confidence = event.results[0][0].confidence;
        resolve({ transcript, confidence });
      };

      this.recognition.onerror = (event) => {
        reject(new Error(`识别错误: ${event.error}`));
      };

      this.recognition.start();
    });
  }

  stop() {
    this.recognition.stop();
  }
}

// 使用示例
const btn = document.getElementById('speak-btn');
const result = document.getElementById('result');
const recognizer = new SimpleSpeechRecognizer();

btn.addEventListener('mousedown', () => {
  result.textContent = '🎤 正在听...';
  recognizer.start().then(({ transcript, confidence }) => {
    result.textContent = `识别结果: ${transcript} (置信度: ${(confidence * 100).toFixed(1)}%)`;
  }).catch(err => {
    result.textContent = `❌ ${err.message}`;
  });
});

btn.addEventListener('mouseup', () => {
  recognizer.stop();
});

💡 提示:confidence 值范围是 0 到 1,表示识别结果的置信度。在实际应用中,建议将置信度低于 0.7 的结果标记为「低置信度」,提示用户确认或重新输入。

2.2 进阶用法:连续实时转写

更复杂的场景是「持续转写」——像一个实时字幕系统,用户说话的同时文字不断更新:

// 连续实时语音转写 — 支持 interimResults 实时显示
class ContinuousTranscriber {
  constructor(options = {}) {
    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    this.recognition = new SpeechRecognition();
    this.recognition.lang = options.lang || 'zh-CN';
    this.recognition.continuous = true;           // 持续识别,不会自动停止
    this.recognition.interimResults = true;       // 返回中间结果(实时转写关键)
    this.recognition.maxAlternatives = 1;

    this.finalTranscript = '';
    this.onUpdate = options.onUpdate || (() => {});
    this.onError = options.onError || (() => {});

    this.recognition.onresult = (event) => {
      let interimTranscript = '';
      for (let i = event.resultIndex; i < event.results.length; i++) {
        const result = event.results[i];
        if (result.isFinal) {
          this.finalTranscript += result[0].transcript + '。';
        } else {
          interimTranscript += result[0].transcript;
        }
      }
      // 回调返回:已完成的文字 + 正在识别的文字(通常用不同样式展示)
      this.onUpdate({
        final: this.finalTranscript,
        interim: interimTranscript,
        fullText: this.finalTranscript + interimTranscript
      });
    };

    this.recognition.onerror = (event) => {
      // 'no-speech' 是正常情况(用户沉默),不需要报错
      if (event.error !== 'no-speech') {
        this.onError(event.error);
      }
    };

    // 连续模式下,识别结束后自动重新启动
    this.recognition.onend = () => {
      if (this._isRunning) {
        this.recognition.start();
      }
    };
  }

  start() {
    this._isRunning = true;
    this.finalTranscript = '';
    this.recognition.start();
  }

  stop() {
    this._isRunning = false;
    this.recognition.stop();
    return this.finalTranscript;
  }
}

// 使用示例 — 构建实时字幕
const transcriber = new ContinuousTranscriber({
  lang: 'zh-CN',
  onUpdate: ({ final, interim }) => {
    document.getElementById('final-text').textContent = final;
    document.getElementById('interim-text').textContent = interim;
    // 自动滚动到底部
    const container = document.getElementById('transcript-container');
    container.scrollTop = container.scrollHeight;
  },
  onError: (error) => {
    console.error('语音识别错误:', error);
  }
});

document.getElementById('start-btn').onclick = () => transcriber.start();
document.getElementById('stop-btn').onclick = () => {
  const fullText = transcriber.stop();
  console.log('完整转写结果:', fullText);
};

⚠️ **警告:**Chrome 的 continuous 模式有一个已知问题——在安静环境下,识别引擎会在约 30 秒后自动断开。上面代码中的 onend 重启逻辑就是为了解决这个问题。但在实际使用中,你还需要处理 not-allowed(麦克风权限被拒绝)和 network(网络中断)等错误。

2.3 多语言切换与方言支持

Web Speech API 支持超过 100 种语言和方言变体。以下是一些常用的语言代码:

// 多语言语音识别配置
const languagePresets = {
  '中文(普通话)': 'zh-CN',
  '中文(粤语)': 'zh-HK',
  '中文(台湾)': 'zh-TW',
  '英语(美国)': 'en-US',
  '英语(英国)': 'en-GB',
  '日语': 'ja-JP',
  '韩语': 'ko-KR',
  '法语': 'fr-FR',
  '德语': 'de-DE',
  '西班牙语': 'es-ES',
};

// 动态切换语言
function switchLanguage(recognition, langCode) {
  const wasRunning = recognition._isRunning;
  if (wasRunning) {
    recognition.stop();
  }
  recognition.lang = langCode;
  if (wasRunning) {
    recognition.start();
  }
}

💡 **提示:**语言切换需要在识别停止状态下进行。如果你在识别过程中修改 lang 属性,变更会在下一次 start() 时生效。另外,粤语(zh-HK)的识别准确率通常低于普通话(zh-CN),因为它使用的是 Google 的粤语模型,训练数据相对较少。

🔈 三、SpeechSynthesis 语音合成实战

3.1 基础 TTS:文字转语音

SpeechSynthesis 的 API 设计非常简洁,核心就是一个 SpeechSynthesisUtterance 对象:

// 文字转语音基础用法
function speak(text, options = {}) {
  const utterance = new SpeechSynthesisUtterance(text);
  utterance.lang = options.lang || 'zh-CN';
  utterance.rate = options.rate || 1.0;      // 语速:0.1 - 10,默认 1
  utterance.pitch = options.pitch || 1.0;    // 音调:0 - 2,默认 1
  utterance.volume = options.volume || 1.0;  // 音量:0 - 1,默认 1

  // 选择语音(如果指定了)
  if (options.voiceName) {
    const voices = speechSynthesis.getVoices();
    const targetVoice = voices.find(v => v.name === options.voiceName);
    if (targetVoice) utterance.voice = targetVoice;
  }

  utterance.onstart = () => console.log('开始朗读');
  utterance.onend = () => console.log('朗读结束');
  utterance.onerror = (e) => console.error('朗读错误:', e.error);

  speechSynthesis.speak(utterance);
}

// 使用
speak('你好,欢迎使用语音合成功能');
speak('Hello, welcome to use speech synthesis', { lang: 'en-US', rate: 1.2 });

3.2 可用语音列表与选择

浏览器内置的语音列表因操作系统而异。获取可用语音需要注意异步加载问题:

// 获取可用语音列表 — 处理异步加载
function getVoices() {
  return new Promise((resolve) => {
    let voices = speechSynthesis.getVoices();
    if (voices.length > 0) {
      resolve(voices);
      return;
    }
    // 语音列表可能异步加载
    speechSynthesis.onvoiceschanged = () => {
      voices = speechSynthesis.getVoices();
      resolve(voices);
    };
  });
}

// 获取中文语音
async function getChineseVoices() {
  const allVoices = await getVoices();
  const chineseVoices = allVoices.filter(v =>
    v.lang.startsWith('zh') || v.lang.startsWith('cmn')
  );

  console.log('可用中文语音:');
  chineseVoices.forEach(v => {
    console.log(`  ${v.name} (${v.lang}) - ${v.localService ? '本地' : '在线'}`);
  });

  return chineseVoices;
}

// 典型输出(Windows 11 + Chrome):
//   Microsoft Xiaoxiao - Chinese, Simplified (zh-CN) - 在线
//   Microsoft Yunxi - Chinese, Simplified (zh-CN) - 在线
//   Google 普通话(中国大陆)(zh-CN) - 在线
//   Microsoft Yaoyao - Chinese, Traditional (zh-TW) - 在线

📌 记住:speechSynthesis.getVoices() 在不同操作系统和浏览器上的返回结果完全不同。macOS 有 Apple 内置的高质量中文语音,Windows 有 Microsoft 的 Xiaoxiao 和 Yunxi,Chrome 则提供 Google 的在线语音。如果你需要一致的语音体验,建议使用 SSML(Speech Synthesis Markup Language)来精确控制发音。

3.3 长文本分段朗读

SpeechSynthesis 有一个鲜为人知的限制——当文本超过约 200 个字符时,部分浏览器(尤其是 Chrome)会截断或跳过后续内容。解决方案是将长文本分段:

// 长文本分段朗读 — 解决 Chrome 长文本截断问题
class LongTextSpeaker {
  constructor(options = {}) {
    this.lang = options.lang || 'zh-CN';
    this.rate = options.rate || 1.0;
    this.onProgress = options.onProgress || (() => {});
    this.isSpeaking = false;
  }

  // 按句子分割文本,每段不超过 maxLength
  splitText(text, maxLength = 150) {
    const sentences = text.match(/[^。!?.!?\n]+[。!?.!?\n]*/g) || [text];
    const chunks = [];
    let currentChunk = '';

    for (const sentence of sentences) {
      if (currentChunk.length + sentence.length > maxLength && currentChunk.length > 0) {
        chunks.push(currentChunk.trim());
        currentChunk = '';
      }
      currentChunk += sentence;
    }
    if (currentChunk.trim()) chunks.push(currentChunk.trim());
    return chunks;
  }

  async speak(text) {
    if (this.isSpeaking) this.stop();
    this.isSpeaking = true;

    const chunks = this.splitText(text);
    const totalChunks = chunks.length;

    for (let i = 0; i < chunks.length; i++) {
      if (!this.isSpeaking) break; // 支持中途停止

      this.onProgress({ current: i + 1, total: totalChunks, text: chunks[i] });

      await new Promise((resolve) => {
        const utterance = new SpeechSynthesisUtterance(chunks[i]);
        utterance.lang = this.lang;
        utterance.rate = this.rate;
        utterance.onend = () => resolve();
        utterance.onerror = () => resolve(); // 出错也继续下一段
        speechSynthesis.speak(utterance);
      });
    }

    this.isSpeaking = false;
  }

  stop() {
    this.isSpeaking = false;
    speechSynthesis.cancel();
  }
}

// 使用示例
const speaker = new LongTextSpeaker({
  lang: 'zh-CN',
  rate: 1.0,
  onProgress: ({ current, total }) => {
    document.getElementById('progress').textContent = `朗读进度: ${current}/${total}`;
  }
});

// 播放长文本
speaker.speak(document.getElementById('article-content').textContent);
document.getElementById('stop-btn').onclick = () => speaker.stop();

⚠️ 警告:speechSynthesis.cancel() 会立即停止当前所有朗读队列。但有一个 Chrome 已知 Bug——调用 cancel() 后立即调用 speak() 可能不会生效。解决方案是在 cancel() 后加一个 setTimeout(() => speechSynthesis.speak(utterance), 100) 的延迟。

🛡️ 四、生产环境实战:完整语音助手

将语音识别和语音合成结合起来,我们可以构建一个完整的语音交互助手。以下是一个生产级的实现,包含错误处理、降级方案和性能优化:

// 生产级语音助手 — 语音识别 + 语音合成 + 错误处理
class VoiceAssistant {
  constructor(options = {}) {
    this.lang = options.lang || 'zh-CN';
    this.onStateChange = options.onStateChange || (() => {});
    this.onTranscript = options.onTranscript || (() => {});
    this.onResponse = options.onResponse || (() => {});

    this.state = 'idle'; // idle | listening | processing | speaking
    this._initRecognition();
  }

  _initRecognition() {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) throw new Error('不支持语音识别');

    this.recognition = new SR();
    this.recognition.lang = this.lang;
    this.recognition.interimResults = true;
    this.recognition.continuous = false;

    this.recognition.onresult = (event) => {
      const result = event.results[event.results.length - 1];
      const transcript = result[0].transcript;

      if (result.isFinal) {
        this._setState('processing');
        this.onTranscript(transcript, true);
        this._processCommand(transcript);
      } else {
        this.onTranscript(transcript, false);
      }
    };

    this.recognition.onerror = (event) => {
      if (event.error === 'no-speech') {
        this._setState('idle');
        return;
      }
      console.error('识别错误:', event.error);
      this._setState('idle');
    };

    this.recognition.onend = () => {
      if (this.state === 'listening') {
        // Chrome 会自动停止,重置状态
        this._setState('idle');
      }
    };
  }

  _setState(newState) {
    this.state = newState;
    this.onStateChange(newState);
  }

  startListening() {
    if (this.state !== 'idle') return;
    this._setState('listening');
    this.recognition.start();
  }

  stopListening() {
    this.recognition.stop();
    this._setState('idle');
  }

  async speak(text) {
    this._setState('speaking');
    return new Promise((resolve) => {
      const utterance = new SpeechSynthesisUtterance(text);
      utterance.lang = this.lang;
      utterance.rate = 1.0;
      utterance.onend = () => {
        this._setState('idle');
        resolve();
      };
      utterance.onerror = () => {
        this._setState('idle');
        resolve();
      };
      speechSynthesis.speak(utterance);
    });
  }

  async _processCommand(text) {
    // 这里可以接入你的业务逻辑或 AI API
    const response = this._generateResponse(text);
    this.onResponse(response);
    await this.speak(response);
  }

  _generateResponse(text) {
    // 简单的规则匹配示例 — 实际项目中替换为 AI API 调用
    if (text.includes('时间') || text.includes('几点')) {
      return `现在是 ${new Date().toLocaleTimeString('zh-CN')}`;
    }
    if (text.includes('日期') || text.includes('今天')) {
      return `今天是 ${new Date().toLocaleDateString('zh-CN', {
        year: 'numeric', month: 'long', day: 'numeric', weekday: 'long'
      })}`;
    }
    return `你说的是:${text}。这是一个语音助手演示,你可以问我时间和日期。`;
  }

  destroy() {
    this.stopListening();
    speechSynthesis.cancel();
  }
}

// 使用示例
const assistant = new VoiceAssistant({
  lang: 'zh-CN',
  onStateChange: (state) => {
    const stateMap = { idle: '待命', listening: '正在听...', processing: '处理中...', speaking: '正在说...' };
    document.getElementById('state').textContent = stateMap[state] || state;
  },
  onTranscript: (text, isFinal) => {
    document.getElementById('transcript').textContent = text;
    document.getElementById('transcript').style.opacity = isFinal ? 1 : 0.6;
  },
  onResponse: (text) => {
    document.getElementById('response').textContent = text;
  }
});

document.getElementById('mic-btn').onclick = () => {
  if (assistant.state === 'idle') {
    assistant.startListening();
  } else {
    assistant.stopListening();
  }
};

📊 五、浏览器兼容性与降级方案

5.1 兼容性现状(2026 年 6 月)

特性 Chrome Safari Firefox Edge
SpeechRecognition ✅ 33+ ✅ 14.1+ ❌ 不支持 ✅ 79+
SpeechSynthesis ✅ 33+ ✅ 7+ ✅ 49+ ✅ 14+
continuous 模式
interimResults
中文识别 ✅ 优秀 ✅ 良好 ✅ 优秀
离线识别 ❌ 需联网 ✅ 本地引擎 ❌ 需联网

⚡ **关键结论:**SpeechSynthesis 的浏览器覆盖率接近 100%,可以放心使用。SpeechRecognition 在 Firefox 上完全不支持,这是最大的兼容性障碍。如果你的应用需要支持 Firefox 用户,必须提供降级方案。

5.2 检测与降级

// 语音能力检测与降级策略
function detectSpeechCapabilities() {
  const capabilities = {
    recognition: false,
    synthesis: false,
    recognitionOffline: false,
    synthesisVoices: [],
  };

  // 检测语音识别
  const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
  if (SR) {
    capabilities.recognition = true;
    // Safari 支持离线识别
    if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
      capabilities.recognitionOffline = true;
    }
  }

  // 检测语音合成
  if ('speechSynthesis' in window) {
    capabilities.synthesis = true;
    capabilities.synthesisVoices = speechSynthesis.getVoices();
  }

  return capabilities;
}

// 降级方案:不支持时显示文字输入框
function setupWithFallback(container) {
  const caps = detectSpeechCapabilities();

  if (!caps.recognition) {
    // 降级:显示文字输入 + 提示安装支持语音的浏览器
    container.innerHTML = `
      <div class="fallback-notice">
        ⚠️ 您的浏览器不支持语音识别,请使用 Chrome 或 Safari。
        <input type="text" placeholder="请用键盘输入..." id="text-fallback" />
      </div>
    `;
    return { useVoice: false };
  }

  return { useVoice: true, capabilities: caps };
}

💡 六、最佳实践与避坑指南

✅ 推荐做法

  • 始终提供视觉反馈——用户需要知道麦克风是否在工作、识别是否在进行中
  • 处理 onend 事件——Chrome 在连续模式下会自动断开,需要重新启动
  • 设置合理的 lang——不要默认用 en-US,中文用户应该设置为 zh-CN
  • 分段处理长文本 TTS——避免 Chrome 的长文本截断 Bug
  • unload 事件中调用 speechSynthesis.cancel()——防止页面关闭后仍在朗读

❌ 避免做法

  • 不要在后台标签页中使用语音识别——浏览器会暂停后台页面的音频捕获
  • 不要假设所有浏览器都支持——Firefox 完全不支持 SpeechRecognition
  • 不要在没有用户手势的情况下调用 speak()——部分浏览器要求用户交互后才能播放音频
  • 不要忽略 error 事件中的 not-allowed 错误——这意味着用户拒绝了麦克风权限

⚠️ 性能注意事项

  • Chrome 的在线识别会有 200-500ms 的网络延迟,不适合对延迟极度敏感的场景
  • getVoices() 在首次调用时可能返回空数组,需要监听 voiceschanged 事件
  • 频繁调用 start()/stop() 会导致浏览器创建过多的音频上下文,建议复用 Recognition 实例
  • 移动端浏览器的语音识别会消耗较多电量,长时间使用时注意提示用户

🔗 七、相关工具与延伸阅读

Web Speech API 为浏览器端的语音交互提供了开箱即用的原生方案。对于大多数 Web 应用来说,它已经是最佳选择——零依赖、零费用、浏览器原生。但如果你需要更高的识别精度、方言支持或离线能力,以下工具值得了解:

  1. Web Speech API 规范 — W3C 官方规范文档
  2. MDN SpeechRecognition — 最权威的 API 参考
  3. 🔧 Whisper.cpp — OpenAI Whisper 的 C++ 移植,支持浏览器 WASM 运行,离线识别精度极高
  4. 🔧 Porcupine — 轻量级唤醒词引擎,适合语音助手场景
  5. 🔧 jsjson.com 在线工具 — JSON 处理、编码转换、哈希计算等开发者必备工具

如果你正在为 jsjson.com 这样的开发者工具站添加语音功能——比如语音输入搜索关键词、朗读代码注释、语音控制工具切换——Web Speech API 是最轻量、最实用的方案。不需要引入任何第三方 SDK,打开浏览器就能用。

📚 相关文章