为了账号安全,请及时绑定邮箱和手机立即绑定

Vue 3.5 + WebSocket 语音识别功能深度实现

这篇文章将详细介绍如何在前端使用 Vue 3.5WebSocketWangEditor 富文本编辑器集成完整的语音识别功能,涵盖从音频采集、格式转换到实时识别结果的插入,并提供一套完整且高效的技术解决方案。

图片描述

一、 功能定位与技术选型

1.1 核心需求拆解

  • 实时语音转文字:支持通过麦克风采集音频,并实时返回识别结果插入到编辑器中。
  • 良好的用户体验:提供录音状态的可视化、快捷键控制和错误提示。
  • 兼容性保障:适配主流浏览器,结合现代 API 和降级方案,确保广泛兼容。
  • 稳定性设计:提供连接诊断、错误处理及资源自动释放机制,确保稳定运行。

1.2 技术栈选型

  • 框架:Vue 3.5 + Composition API,使用响应式特性高效管理组件状态。
  • 编辑器:WangEditor,轻量且可扩展,便于与其他功能集成。
  • 音频处理:AudioWorklet 和 ScriptProcessor,用于高效的音频格式转换。
  • 通信方式:WebSocket,支持实时传输音频数据和识别结果。
  • UI 组件:Element Plus,提供错误提示、弹窗等交互组件。

选型核心考量:Vue 3.5 的响应式特性适合状态管理,WangEditor 提供的灵活 API 便于扩展,WebSocket 确保了数据传输的实时性,同时双重音频处理方案保障兼容性和性能。


二、 整体架构设计

语音识别功能采用分层设计,清晰地划分各模块的职责,便于后续的维护与扩展:

  1. UI 交互层 (NoteEditor.vue):负责用户操作入口(如录音按钮)和结果插入逻辑。
  2. 核心控制层 (simpleSpeech.js):封装录音控制、连接管理、消息处理等核心逻辑。
  3. 音频处理层 (audioPcmProcessor.js):将麦克风采集的音频转换为识别服务支持的 PCM 格式。
  4. 通信层 (WebSocket):通过 WebSocket 与后端语音识别服务实时交互。
  5. 诊断工具层 (speechConfigChecker.js):检测环境兼容性和服务可用性,辅助问题排查。

三、 核心模块实现详解

3.1 音频处理层:PCM 格式转换 (audioPcmProcessor.js)

语音识别服务要求输入 PCM 格式音频,而浏览器的麦克风采集默认格式为 Float32Array。为此,我们需要使用 AudioWorklet 实现音频格式转换,确保音频数据与识别服务兼容。

class AudioPcmProcessor extends AudioWorkletProcessor {
    constructor() {
        super();
        this.bufferSize = 4096; // 缓冲区大小
        this.buffer = new Float32Array(this.bufferSize);
        this.bufferIndex = 0;
    }
    process(inputs, outputs, parameters) {
        const input = inputs[0];
        if (input.length > 0) {
            const inputChannel = input[0];
            // 填充缓冲区
            for (let i = 0; i < inputChannel.length; i++) {
                this.buffer[this.bufferIndex] = inputChannel[i];
                this.bufferIndex++;
                if (this.bufferIndex >= this.bufferSize) {
                    this.processAndSendPcm();
                    this.bufferIndex = 0;
                }
            }
        }
        return true;
    }
    processAndSendPcm() {
        try {
            // PCM 格式转换
            const pcmData = new Int16Array(this.buffer.length);
            for (let i = 0; i < this.buffer.length; i++) {
                const sample = Math.max(-1, Math.min(1, this.buffer[i]));
                pcmData[i] = sample < 0 ? sample * 0x8000 : sample * 0x7fff;
            }
            // 使用 Transferable 对象传输,提升效率
            this.port.postMessage(
                { type: 'pcmData', data: pcmData.buffer, length: pcmData.length },
                [pcmData.buffer]
            );
        } catch (error) {
            this.port.postMessage({ type: 'error', error: error.message });
        }
    }
}
registerProcessor('audio-pcm-processor', AudioPcmProcessor);

核心亮点

  • 使用缓冲区批量处理音频数据,减少通信开销。
  • 采用 Transferable 对象,提升数据传输效率。
  • 严格限制音频采样范围,避免失真。

3.2 核心控制层:语音识别封装 (simpleSpeech.js)

该模块封装了录音控制、WebSocket 连接、消息处理等逻辑,并提供简洁的 API 供外部调用。

export class SimpleSpeech {
    constructor(options = {}) {
        const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';
        this.serverUrl = `${baseUrl.replace('http', 'ws')}/api/v1/ai/speech-recognition`;
        this.sampleRate = options.sampleRate || 16000;
        this.model = options.model || 'bigmodel';
        this.isRecording = false;
        this.isConnected = false;
        this.isReady = false;
        this.audioContext = null;
        this.audioStream = null;
        this.workletNode = null;
        this.ws = null;
        this.onReady = options.onReady || (() => {});
        this.onPartial = options.onPartial || (() => {});
        this.onFinal = options.onFinal || (() => {});
        this.onError = options.onError || (() => {});
    }

    // 建立 WebSocket 连接
    async connect() {
        try {
            const url = `${this.serverUrl}?sampleRate=${this.sampleRate}&model=${this.model}`;
            this.ws = new WebSocket(url);
            this.ws.onopen = () => {
                this.isConnected = true;
                this.onStatusChange?.('connected');
            };
            this.ws.onmessage = (event) => {
                const data = JSON.parse(event.data);
                this.handleMessage(data); 
            };
            this.ws.onclose = () => {
                this.isConnected = false;
                this.isReady = false;
                this.onStatusChange?.('disconnected');
            };
        } catch (error) {
            this.onError('连接失败: ' + error.message);
        }
    }

    // 开始录音
    async startRecording() {
        if (!this.isConnected || !this.isReady) {
            this.onError('请先连接语音识别服务');
            return;
        }
        try {
            this.audioStream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: this.sampleRate, channelCount: 1 } });
            this.audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: this.sampleRate });
            const audioSource = this.audioContext.createMediaStreamSource(this.audioStream);
            
            try {
                await this.audioContext.audioWorklet.addModule('/audioPcmProcessor.js');
                this.workletNode = new AudioWorkletNode(this.audioContext, 'audio-pcm-processor');
                this.workletNode.port.onmessage = (event) => {
                    if (event.data.type === 'pcmData' && this.ws?.readyState === WebSocket.OPEN) {
                        this.ws.send(event.data.data); // 发送 PCM 数据
                    }
                };
                audioSource.connect(this.workletNode);
            } catch (error) {
                this.createScriptProcessor(audioSource); // 降级方案
            }
            this.isRecording = true;
        } catch (error) {
            this.onError('录音启动失败: ' + error.message);
        }
    }

    // 停止录音
    stopRecording() {
        if (!this.isRecording) return;
        if (this.workletNode) this.workletNode.disconnect();
        if (this.audioStream) {
            this.audioStream.getTracks().forEach(track => track.stop());
            this.audioContext.close();
        }
        this.isRecording = false;
    }

    handleMessage(data) {
        switch (data.type) {
            case 'ready':
                this.isReady = true;
                this.onReady();
                break;
            case 'incremental_result':
                this.onPartial({ text: data.text, timestamp: data.timestamp });
                break;
            case 'final_result':
                this.onFinal({ text: data.text, timestamp: data.timestamp });
                break;
            case 'error':
                this.onError(this.formatErrorMsg(data.message));
                break;
        }
    }

    formatErrorMsg(message) {
        if (message.includes('缺少配置')) return '后端缺少配置,请设置 API Key';
        if (message.includes('连接失败')) return '无法连接服务,请检查网络';
        return message;
    }
}


3.3 UI 交互层:编辑器集成 (NoteEditor.vue)

在 Vue 组件中集成 WangEditor 与语音识别核心逻辑。

模板部分

<template>
    <div class="note-editor">
        <div class="editor-toolbar">
            <Toolbar :editor="editorRef" :defaultConfig="toolbarConfig" :mode="mode" />
        </div>
        <div class="editor-content">
            <Editor v-model="valueHtml" :defaultConfig="editorConfig" @onCreated="handleCreated" />
        </div>
        
        <div v-if="isSpeechRecording" class="speech-recording-indicator">
            <div class="recording-animation">
                <div class="pulse-ring"></div>
                <div class="microphone-icon"></div>
            </div>
            <div class="recording-text">正在录音中...</div>
            <button @click="stopSpeechRecognition" class="stop-btn">停止录音</button>
            <div class="esc-hint">按 ESC 键停止</div>
        </div>
    </div>
</template>

逻辑部分

<script setup>
import { ref } from 'vue'

// 语音识别相关状态
const speechRecognition = ref(null)
const isSpeechRecording = ref(false)

// 初始化语音识别实例
const initSpeechRecognition = () => { /* ... */ }
// 开始语音识别
const startSpeechRecognitionDirect = async () => { /* ... */ }
// 停止语音识别
const stopSpeechRecognition = () => { /* ... */ }
// 插入识别结果到编辑器
const insertSpeechText = (text) => { /* ... */ }
</script>


四、 总结

通过本文的实现,我们成功将语音识别功能集成到了 Vue 3.5 和 WangEditor 基础上。关键技术亮点包括:

  1. 高效的音频格式转换,确保识别服务兼容性。
  2. 完善的状态管理与错误处理,提升用户体验。
  3. 自动化配置诊断工具,减少问题排查成本。

延伸阅读与完整实现

想看更完整的项目实现与可直接复用的代码结构,可以扫码下方二维码了解更多

图片描述
图片描述

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
5
获赞与收藏
26

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消