pin_drop当前位置:知识文库 ❯ 图文
HTML5 API:Speech API - 完整教程与代码示例
一、教程简介
Web Speech API 为 Web 应用提供了两大核心能力:语音识别(Speech Recognition)和语音合成(Speech Synthesis)。语音识别允许用户通过语音输入来控制应用,将语音转换为文本;语音合成则可以将文本转换为语音输出,实现朗读功能。这两个 API 结合使用,可以构建出完整的语音交互体验。
Web Speech API 的出现使得 Web 应用能够像原生应用一样提供语音交互能力,无需安装任何插件或第三方软件。无论是语音助手、语音搜索、无障碍辅助,还是语音笔记等应用场景,Web Speech API 都能提供强大的支持。
二、核心概念
1. 语音识别(Speech Recognition)
语音识别 API 通过 webkitSpeechRecognition(Chrome)或 SpeechRecognition(标准)接口实现,能够将用户的语音输入实时转换为文本。
代码示例
// 语音识别的核心概念
// - SpeechRecognition:识别控制器,管理识别过程
// - SpeechRecognitionResult:单次识别结果
// - SpeechRecognitionResultList:识别结果列表
// - SpeechGrammarList:语法约束列表
// - SpeechGrammar:单条语法规则2. 语音合成(Speech Synthesis)
语音合成 API 通过 speechSynthesis 全局对象和 SpeechSynthesisUtterance 接口实现,能够将文本转换为语音输出。
代码示例
// 语音合成的核心概念
// - speechSynthesis:全局合成控制器
// - SpeechSynthesisUtterance:语音合成请求
// - SpeechSynthesisVoice:语音合成声音
// - SpeechSynthesisEvent:合成事件3. 识别模式
代码示例
// 连续识别模式:持续监听语音输入
recognition.continuous = true;
// 单次识别模式:识别到一段语音后自动停止
recognition.continuous = false;
// 中间结果:返回识别过程中的临时结果
recognition.interimResults = true;4. 语言支持
代码示例
// 设置识别语言
recognition.lang = 'zh-CN'; // 中文(简体)
recognition.lang = 'en-US'; // 英语(美国)
recognition.lang = 'ja-JP'; // 日语
// 获取合成支持的语言
speechSynthesis.getVoices().forEach(voice => {
console.log(voice.lang, voice.name);
});三、语法与用法
语音识别基本语法
代码示例
// 创建识别实例
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
// 配置参数
recognition.lang = 'zh-CN'; // 识别语言
recognition.continuous = false; // 是否连续识别
recognition.interimResults = false; // 是否返回中间结果
recognition.maxAlternatives = 1; // 最大替代结果数
// 事件监听
recognition.onresult = (event) => {
const result = event.results[0][0].transcript;
console.log('识别结果:', result);
};
recognition.onerror = (event) => {
console.error('识别错误:', event.error);
};
recognition.onend = () => {
console.log('识别结束');
};
// 开始/停止识别
recognition.start();
recognition.stop();
recognition.abort();语音合成基本语法
代码示例
// 创建合成请求
const utterance = new SpeechSynthesisUtterance('你好,世界!');
// 配置参数
utterance.lang = 'zh-CN'; // 语言
utterance.voice = null; // 声音对象
utterance.volume = 1; // 音量 (0-1)
utterance.rate = 1; // 语速 (0.1-10)
utterance.pitch = 1; // 音调 (0-2)
// 事件监听
utterance.onstart = () => console.log('开始朗读');
utterance.onend = () => console.log('朗读结束');
utterance.onpause = () => console.log('暂停朗读');
utterance.onresume = () => console.log('继续朗读');
utterance.onerror = (e) => console.error('朗读错误:', e);
// 控制方法
speechSynthesis.speak(utterance); // 开始朗读
speechSynthesis.pause(); // 暂停
speechSynthesis.resume(); // 继续
speechSynthesis.cancel(); // 取消
// 获取可用声音列表
const voices = speechSynthesis.getVoices();四、代码示例
示例1:语音识别转文本
实现语音识别功能,将用户的语音输入实时转换为文本显示。支持多语言选择、连续识别模式切换、识别历史记录等功能。
示例2:语音合成朗读器
实现文本转语音功能,支持语速、音调、音量调节,可选择不同声音,提供播放、暂停、停止控制。
示例3:语音交互助手
结合语音识别和语音合成,构建一个简单的语音交互助手。支持语音输入、智能回复、语音播报等功能。
示例4:语音控制页面
通过语音命令控制页面元素,如开关灯、切换颜色、调节音量等。展示语音识别在实际交互场景中的应用。
五、浏览器兼容性
兼容性注意事项
代码示例
// 1. 语音识别兼容性检测
function isSpeechRecognitionSupported() {
return !!(window.SpeechRecognition || window.webkitSpeechRecognition);
}
// 2. 语音合成兼容性检测
function isSpeechSynthesisSupported() {
return 'speechSynthesis' in window;
}
// 3. 前缀处理
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
// 4. Firefox 不支持语音识别,提供降级方案
if (!SpeechRecognition) {
console.warn('当前浏览器不支持语音识别,请使用 Chrome 或 Safari');
// 可以使用第三方服务作为降级方案
}
// 5. iOS Safari 的特殊处理
// iOS Safari 需要用户交互才能启动语音识别
// 且不支持 continuous 模式
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
if (isIOS && recognition) {
recognition.continuous = false; // iOS 不支持连续模式
}六、注意事项与最佳实践
1. 语音识别稳定性
代码示例
// 最佳实践:处理识别中断和重启
class StableSpeechRecognition {
constructor(options = {}) {
this.lang = options.lang || 'zh-CN';
this.continuous = options.continuous ?? true;
this.interimResults = options.interimResults ?? true;
this.onResult = options.onResult || (() => {});
this.onError = options.onError || (() => {});
this.onStateChange = options.onStateChange || (() => {});
this.recognition = null;
this.isRunning = false;
this.restartAttempts = 0;
this.maxRestartAttempts = 5;
}
start() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
this.onError('浏览器不支持语音识别');
return;
}
this.recognition = new SpeechRecognition();
this.recognition.lang = this.lang;
this.recognition.continuous = this.continuous;
this.recognition.interimResults = this.interimResults;
this.recognition.onresult = (event) => {
this.restartAttempts = 0; // 成功识别后重置计数
this.onResult(event);
};
this.recognition.onerror = (event) => {
if (event.error === 'no-speech') {
// 无语音输入不算严重错误
return;
}
this.onError(event.error);
};
this.recognition.onend = () => {
if (this.isRunning) {
// 自动重启,带退避策略
if (this.restartAttempts < this.maxRestartAttempts) {
const delay = Math.min(1000 * Math.pow(2, this.restartAttempts), 10000);
setTimeout(() => {
if (this.isRunning) {
this.restartAttempts++;
try {
this.recognition.start();
} catch (e) {
this.onError('重启失败: ' + e.message);
}
}
}, delay);
} else {
this.onError('达到最大重启次数');
this.isRunning = false;
this.onStateChange('stopped');
}
}
};
try {
this.recognition.start();
this.isRunning = true;
this.onStateChange('running');
} catch (e) {
this.onError('启动失败: ' + e.message);
}
}
stop() {
this.isRunning = false;
if (this.recognition) {
this.recognition.abort();
this.recognition = null;
}
this.onStateChange('stopped');
}
}2. 语音合成最佳实践
代码示例
// 最佳实践:语音合成管理器
class SpeechSynthesisManager {
constructor() {
this.queue = [];
this.isPlaying = false;
this.currentUtterance = null;
this.defaultOptions = {
lang: 'zh-CN',
rate: 1,
pitch: 1,
volume: 1
};
}
// 等待声音列表加载
async getVoices() {
return new Promise((resolve) => {
const voices = speechSynthesis.getVoices();
if (voices.length > 0) {
resolve(voices);
return;
}
speechSynthesis.addEventListener('voiceschanged', () => {
resolve(speechSynthesis.getVoices());
}, { once: true });
});
}
// 选择最佳声音
async selectVoice(lang = 'zh-CN') {
const voices = await this.getVoices();
// 优先选择匹配语言的声音
const exactMatch = voices.find(v => v.lang === lang);
if (exactMatch) return exactMatch;
// 其次选择语言前缀匹配
const prefix = lang.split('-')[0];
const prefixMatch = voices.find(v => v.lang.startsWith(prefix));
if (prefixMatch) return prefixMatch;
// 最后使用默认声音
return voices.find(v => v.default) || voices[0];
}
// 朗读文本
async speak(text, options = {}) {
const mergedOptions = { ...this.defaultOptions, ...options };
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = mergedOptions.lang;
utterance.rate = mergedOptions.rate;
utterance.pitch = mergedOptions.pitch;
utterance.volume = mergedOptions.volume;
const voice = await this.selectVoice(mergedOptions.lang);
if (voice) utterance.voice = voice;
return new Promise((resolve, reject) => {
utterance.onend = resolve;
utterance.onerror = reject;
speechSynthesis.speak(utterance);
});
}
// 停止所有朗读
cancel() {
speechSynthesis.cancel();
}
// 暂停
pause() {
speechSynthesis.pause();
}
// 继续
resume() {
speechSynthesis.resume();
}
}3. 隐私与权限
代码示例
// 语音识别涉及麦克风权限,需要妥善处理
async function requestMicrophonePermission() {
try {
const result = await navigator.permissions.query({ name: 'microphone' });
if (result.state === 'granted') {
return true;
} else if (result.state === 'prompt') {
// 需要用户授权,将在 getUserMedia 或 SpeechRecognition.start() 时弹出
return true;
} else {
// 权限被拒绝
console.error('麦克风权限被拒绝');
return false;
}
} catch (e) {
// permissions API 可能不支持,尝试直接使用
return true;
}
}
// 在使用语音识别前检查权限
async function safeStartRecognition() {
const hasPermission = await requestMicrophonePermission();
if (!hasPermission) {
alert('请在浏览器设置中允许访问麦克风');
return;
}
// 开始识别...
}4. 长文本合成处理
代码示例
// Chrome 对单次合成长度有限制(约 200-300 字符)
// 长文本需要分段处理
class LongTextSynthesizer {
constructor(maxChunkSize = 200) {
this.maxChunkSize = maxChunkSize;
}
// 按句子分割文本
splitText(text) {
const chunks = [];
let currentChunk = '';
// 按标点符号分割
const sentences = text.split(/(?<=[。!?;\n.!?;])/);
for (const sentence of sentences) {
if ((currentChunk + sentence).length > this.maxChunkSize) {
if (currentChunk) {
chunks.push(currentChunk);
currentChunk = '';
}
// 如果单个句子超过限制,强制分割
if (sentence.length > this.maxChunkSize) {
for (let i = 0; i < sentence.length; i += this.maxChunkSize) {
chunks.push(sentence.slice(i, i + this.maxChunkSize));
}
} else {
currentChunk = sentence;
}
} else {
currentChunk += sentence;
}
}
if (currentChunk) {
chunks.push(currentChunk);
}
return chunks;
}
// 逐段朗读
async speakLongText(text, options = {}) {
const chunks = this.splitText(text);
for (const chunk of chunks) {
await this.speakChunk(chunk, options);
}
}
speakChunk(text, options = {}) {
return new Promise((resolve, reject) => {
const utterance = new SpeechSynthesisUtterance(text);
Object.assign(utterance, options);
utterance.onend = resolve;
utterance.onerror = reject;
speechSynthesis.speak(utterance);
});
}
}七、代码规范示例
完整的语音交互封装
代码示例
/**
* VoiceInteraction - 语音交互封装类
* 统一管理语音识别和语音合成
*/
class VoiceInteraction {
constructor(config = {}) {
this.recognitionConfig = {
lang: config.lang || 'zh-CN',
continuous: config.continuous ?? false,
interimResults: config.interimResults ?? true,
maxAlternatives: config.maxAlternatives || 1
};
this.synthesisConfig = {
lang: config.lang || 'zh-CN',
rate: config.rate || 1,
pitch: config.pitch || 1,
volume: config.volume || 1
};
this.recognition = null;
this.isListening = false;
this.isSpeaking = false;
// 回调函数
this.callbacks = {
onRecognize: config.onRecognize || (() => {}),
onInterimResult: config.onInterimResult || (() => {}),
onListenStart: config.onListenStart || (() => {}),
onListenEnd: config.onListenEnd || (() => {}),
onSpeakStart: config.onSpeakStart || (() => {}),
onSpeakEnd: config.onSpeakEnd || (() => {}),
onError: config.onError || (() => {})
};
this._initVoices();
}
/** 初始化声音列表 */
async _initVoices() {
if (!('speechSynthesis' in window)) return;
const loadVoices = () => {
this.voices = speechSynthesis.getVoices();
};
loadVoices();
if (this.voices.length === 0) {
speechSynthesis.addEventListener('voiceschanged', loadVoices, { once: true });
}
}
/** 开始语音识别 */
startListening() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
this.callbacks.onError('浏览器不支持语音识别');
return;
}
if (this.isListening) return;
this.recognition = new SpeechRecognition();
Object.assign(this.recognition, this.recognitionConfig);
this.recognition.onstart = () => {
this.isListening = true;
this.callbacks.onListenStart();
};
this.recognition.onresult = (event) => {
let interim = '';
let final = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
final += transcript;
} else {
interim += transcript;
}
}
if (interim) this.callbacks.onInterimResult(interim);
if (final) this.callbacks.onRecognize(final);
};
this.recognition.onerror = (event) => {
this.callbacks.onError(event.error);
};
this.recognition.onend = () => {
this.isListening = false;
this.callbacks.onListenEnd();
};
try {
this.recognition.start();
} catch (e) {
this.callbacks.onError('启动识别失败');
}
}
/** 停止语音识别 */
stopListening() {
if (this.recognition) {
this.recognition.abort();
this.recognition = null;
}
this.isListening = false;
}
/** 语音合成朗读 */
speak(text, options = {}) {
if (!('speechSynthesis' in window)) {
this.callbacks.onError('浏览器不支持语音合成');
return;
}
// 停止当前朗读
speechSynthesis.cancel();
const config = { ...this.synthesisConfig, ...options };
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = config.lang;
utterance.rate = config.rate;
utterance.pitch = config.pitch;
utterance.volume = config.volume;
// 选择声音
if (this.voices && this.voices.length > 0) {
const voice = this.voices.find(v => v.lang.startsWith(config.lang.split('-')[0]));
if (voice) utterance.voice = voice;
}
utterance.onstart = () => {
this.isSpeaking = true;
this.callbacks.onSpeakStart();
};
utterance.onend = () => {
this.isSpeaking = false;
this.callbacks.onSpeakEnd();
};
utterance.onerror = (e) => {
this.isSpeaking = false;
this.callbacks.onError(e.error);
};
speechSynthesis.speak(utterance);
}
/** 停止朗读 */
stopSpeaking() {
speechSynthesis.cancel();
this.isSpeaking = false;
}
/** 销毁 */
destroy() {
this.stopListening();
this.stopSpeaking();
}
}
// 使用示例
const voice = new VoiceInteraction({
lang: 'zh-CN',
continuous: false,
onRecognize: (text) => {
console.log('识别结果:', text);
// 根据识别结果执行操作
},
onInterimResult: (text) => {
console.log('临时结果:', text);
},
onSpeakEnd: () => {
console.log('朗读完成');
}
});
// 开始识别
voice.startListening();
// 朗读文本
voice.speak('你好,这是语音合成测试。');八、常见问题与解决方案
问题1:Chrome 中语音识别在一段时间后自动停止
代码示例
// 问题:Chrome 的 SpeechRecognition 在静音一段时间后自动停止
// 解决方案:监听 onend 事件并自动重启
let recognition;
let isManualStop = false;
function startContinuousRecognition() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = 'zh-CN';
recognition.onend = () => {
if (!isManualStop) {
// 自动重启
try {
recognition.start();
} catch (e) {
setTimeout(() => {
recognition.start();
}, 100);
}
}
};
recognition.start();
}
function stopRecognition() {
isManualStop = true;
recognition.stop();
}问题2:语音合成在 Chrome 中突然停止
代码示例
// 问题:Chrome 中长文本语音合成可能在 15 秒后自动暂停
// 解决方案:使用定时器保持合成活跃
function speakWithKeepAlive(text, options = {}) {
const utterance = new SpeechSynthesisUtterance(text);
Object.assign(utterance, options);
// Chrome 的 bug:长文本合成会在约 15 秒后暂停
// 解决方案:定时 resume
let keepAliveInterval = null;
utterance.onstart = () => {
keepAliveInterval = setInterval(() => {
if (speechSynthesis.speaking && !speechSynthesis.pending) {
speechSynthesis.pause();
speechSynthesis.resume();
}
}, 10000);
};
utterance.onend = () => {
clearInterval(keepAliveInterval);
};
utterance.onerror = () => {
clearInterval(keepAliveInterval);
};
speechSynthesis.speak(utterance);
}问题3:getVoices() 返回空数组
代码示例
// 问题:首次调用 getVoices() 可能返回空数组
// 解决方案:监听 voiceschanged 事件
function loadVoices() {
return new Promise((resolve) => {
let voices = speechSynthesis.getVoices();
if (voices.length > 0) {
resolve(voices);
return;
}
const onVoicesChanged = () => {
voices = speechSynthesis.getVoices();
if (voices.length > 0) {
speechSynthesis.removeEventListener('voiceschanged', onVoicesChanged);
resolve(voices);
}
};
speechSynthesis.addEventListener('voiceschanged', onVoicesChanged);
// 超时保护
setTimeout(() => {
speechSynthesis.removeEventListener('voiceschanged', onVoicesChanged);
resolve(speechSynthesis.getVoices());
}, 5000);
});
}
// 使用
async function speakWithVoice(text) {
const voices = await loadVoices();
const zhVoice = voices.find(v => v.lang.startsWith('zh'));
const utterance = new SpeechSynthesisUtterance(text);
if (zhVoice) utterance.voice = zhVoice;
speechSynthesis.speak(utterance);
}问题4:移动端语音识别不可用
代码示例
// 问题:iOS Safari 和部分移动浏览器对语音识别支持有限
// 解决方案:检测支持并提供降级方案
function getSpeechRecognitionSupport() {
const support = {
recognition: false,
synthesis: false,
continuous: false,
details: ''
};
// 检测语音识别
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (SpeechRecognition) {
support.recognition = true;
// 检测连续模式支持
try {
const test = new SpeechRecognition();
test.continuous = true;
support.continuous = true;
} catch (e) {
support.continuous = false;
}
}
// 检测语音合成
if ('speechSynthesis' in window) {
support.synthesis = true;
}
// 平台检测
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isAndroid = /Android/.test(navigator.userAgent);
if (isIOS) {
support.details = 'iOS: 语音识别需要用户交互触发,不支持后台运行';
} else if (isAndroid) {
support.details = 'Android: 推荐使用 Chrome 浏览器获得最佳体验';
}
return support;
}
// 降级方案:使用文本输入替代语音
function createAccessibleInput(container) {
const support = getSpeechRecognitionSupport();
if (support.recognition) {
// 提供语音和文本两种输入方式
container.innerHTML = `
<button id="voiceBtn">语音输入</button>
<input type="text" id="textInput" placeholder="或输入文字">
`;
} else {
// 仅提供文本输入
container.innerHTML = `
<input type="text" id="textInput" placeholder="请输入文字">
<p class="hint">当前浏览器不支持语音识别</p>
`;
}
}九、总结
Web Speech API 为 Web 应用提供了强大的语音交互能力,主要包含以下两个方面:
语音识别(Speech Recognition):将用户的语音输入转换为文本,支持连续识别、中间结果、多语言等功能。适用于语音搜索、语音命令、语音输入等场景。
语音合成(Speech Synthesis):将文本转换为语音输出,支持多种声音、语速、音调调节。适用于朗读、语音提示、无障碍辅助等场景。
在使用 Web Speech API 时,需要注意以下关键点:
浏览器兼容性:语音识别主要在 Chrome 和 Safari 中支持,Firefox 目前不支持;语音合成支持更广泛。
权限处理:语音识别需要麦克风权限,需要妥善处理权限请求和拒绝的情况。
自动播放策略:语音识别通常需要用户交互才能启动。
长文本处理:Chrome 对单次合成长度有限制,长文本需要分段处理。
稳定性:语音识别可能因静音等原因中断,需要实现自动重启机制。
通过合理的封装和最佳实践,可以构建出稳定、易用的语音交互体验,为用户提供更自然的交互方式。
常见问题
Q1:Chrome 中语音识别自动停止怎么办?
解决方案:监听 onend 事件,在事件回调中自动重启识别。使用指数退避策略避免频繁重启,设置最大重启次数防止无限循环。
Q2:语音合成在 Chrome 中长文本朗读中断如何处理?
解决方案:Chrome 存在长文本合成约 15 秒后暂停的 bug。可通过定时器定期调用 pause() 和 resume() 保持合成活跃,或将长文本分段逐段朗读。
Q3:getVoices() 返回空数组如何解决?
解决方案:声音列表可能异步加载。监听 voiceschanged 事件,在事件触发后重新获取声音列表。设置超时保护避免无限等待。
Q4:移动端语音识别不可用怎么办?
解决方案:iOS Safari 需要用户交互才能启动语音识别,且不支持连续模式。Android 推荐使用 Chrome 浏览器。提供文本输入作为降级方案,确保功能可用性。
Q5:Firefox 不支持语音识别如何兼容?
解决方案:检测浏览器支持情况,对不支持的浏览器提供降级方案。可提示用户更换浏览器,或使用第三方语音识别服务作为替代。
本文涉及AI创作
内容由AI创作,请仔细甄别