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:语音控制页面

通过语音命令控制页面元素,如开关灯、切换颜色、调节音量等。展示语音识别在实际交互场景中的应用。

五、浏览器兼容性

API / 特性 Chrome Firefox Safari Edge 移动端支持
SpeechRecognition 33+ (webkit) 不支持 14.1+ 79+ Android Chrome
SpeechSynthesis 33+ 49+ 7+ 14+ 大部分支持
continuous 模式 33+ 不支持 14.1+ 79+ Android Chrome
interimResults 33+ 不支持 14.1+ 79+ Android Chrome
getVoices() 33+ 49+ 7+ 14+ 大部分支持
SpeechGrammar 33+ 不支持 不支持 79+ 不支持

兼容性注意事项

代码示例

// 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 不支持语音识别如何兼容?

解决方案:检测浏览器支持情况,对不支持的浏览器提供降级方案。可提示用户更换浏览器,或使用第三方语音识别服务作为替代。

标签: Web Speech API 语音识别 语音合成 SpeechRecognition 语音交互

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: HTML5 API:Web Audio API - 完整教程与代码示例 下一篇: HTML5 API:Vibration API - 完整教程与代码示例

poll相关推荐