0
0

给前端 AI 语音入口加降级链路时,我会先把录音和转写拆开

最近给一个前端 AI 工作台补语音输入,需求表面上只是按住说话,松开后把文字送给 Agent。真正接入时才发现,它牵涉浏览器权限、设备占用、音频分片、实时音量、转写失败和手动输入降级。任何一层含糊,用户都会怀疑录音是否真的可用。

前端 AI 语音输入从权限探测到转写账本的流程图

原创示意图:把权限探测、麦克风流、录音分片、音量检测、转写适配器、文本账本和手动输入降级拆成可恢复模块。 来源:Codex image generation

我避开一个巨大的 voiceInput 组件,改成录音侧和转写侧两条链路。录音侧只负责拿到安全可控的音频片段,转写侧只负责产出有序文本事件。页面只消费 transcript ledger 和输入草稿,失败恢复也更清楚。

问题背景

MDN 对 getUserMedia 的说明里强调,它会向用户请求媒体输入权限,并返回 MediaStream;MediaRecorder API 面向录制 MediaStream 产生的数据。放在 AI 输入场景里,麦克风要按外部资源处理。AudioWorklet 则适合做电平采样和静音判断,让波形反馈独立于转写响应。

踩坑和关键难点

第一个坑是权限状态。只在点击录音按钮时调用 getUserMedia,失败后只能给出笼统提示。更稳的做法是拆出权限探测、请求授权和设备流创建,分别处理等待授权、已拒绝、设备不可用和录音中。

第二个坑是分片时机。MediaRecorder 可以按时间片产生数据,但 AI 转写不一定喜欢过碎的音频。过短会增加请求数量,过长会拖慢首字返回。我把 chunk flush 做成时间和大小双阈值,再给每个片段加 sequence。

第三个坑是把 Web Speech、云端 STT 和手动输入混在 UI 层。稳定抽象应该是 adapter:不同转写实现都输出同一种 partial 和 final transcript 事件。

解决思路

这次我把语音入口拆成七个模块:permission probe、stream manager、recorder buffer、worklet meter、stt adapter、transcript ledger、manual fallback。前四个模块在浏览器本地完成,后两个模块贴近业务协议,中间用 adapter 连接。

用户按下录音后,stream manager 只负责拿 MediaStream 和释放 track;recorder buffer 写入 upload queue;worklet meter 推送 level 和 silence;stt adapter 返回 transcript event;ledger 去重、排序并合并 partial 和 final,最后把稳定文本提交给 Agent。

关键步骤

第一步,先定义 transcript event,组件状态放到后面处理。事件至少包含 sessionId、chunkId、sequence、kind、text、isFinal、createdAt 和 source。这样 Web Speech、后端 STT、离线上传都能接入同一条消息链。

第二步,录音开始前做显式能力检查。Permissions API 可以查询权限状态,getUserMedia 负责真正请求媒体流。两者都要有独立错误码,方便把用户拒绝、无设备、浏览器不支持和系统占用拆开处理。

第三步,分片上传要有重试账本。每个 chunk 都记录 hash、duration、retryCount 和 adapterName。上游失败只重发 chunk,ledger 不回滚已确认文本。麦克风按钮旁边始终保留文本框,最终提交统一走 sendMessage 协议。

可复用经验

上线前我还补了两个保护:页面切到后台时停止录音并提示用户重新开始,组件卸载时统一关闭 track、清空定时器、撤销未完成请求。语音入口最怕半开状态,按钮看起来结束了,浏览器却还占着麦克风。把清理动作集中到 session close 里,排查问题时只看 sessionId 就能知道这一轮到底走到了哪一步。

语音输入的难点在于同时守住实时感和可恢复性。先守住三条边界:音频流生命周期归录音侧,文本事件归 ledger,业务提交归 Agent 输入协议。后面换 Web Speech、接自研 STT、加降噪服务,或给 Electron 桌面端接系统麦克风,都可以沿着这三条边界演进。

主要来源

MDN MediaDevices getUserMedia

MDN MediaRecorder API

MDN Web Speech API

MDN AudioWorklet

MDN Permissions API

评论