ZCode 语音输入组件 SpeechInput 实战指南:Web Speech API 与 MediaRecorder 双引擎语音转文字方案
【免费下载链接】ZCodeZ.ai's coding agent harness. Powerful, intelligent, extensible.项目地址: https://gitcode.com/gh_mirrors/zco/ZCode
本文围绕 ZCode 仓库中 ai-elements 技能包提供的SpeechInput语音输入组件,系统讲解如何在基于 shadcn/ui 的 React 应用中接入"点击麦克风说话 → 自动转写为文本"的完整能力。组件在 Chrome、Edge 上借助 Web Speech API 实现免服务器的实时转写,在 Firefox、Safari 上自动降级为 MediaRecorder 录音并交给外部转写服务(如 OpenAI Whisper)。读完本文,你将掌握该组件的安装方式、Props 契约、双引擎工作原理、生命周期与状态管理,并能基于 示例脚本 在真实项目中落地一套跨浏览器可用的语音输入方案。
SpeechInput 组件概述
SpeechInput是一个封装了"捕获语音输入并转换为文本"能力的按钮组件,核心价值在于用一个组件抹平不同浏览器在语音识别能力上的差异:
- 在支持 Web Speech API 的浏览器(Chrome、Edge)中,使用
SpeechRecognition进行实时转录,无需任何服务器参与; - 在不支持 Web Speech API 的浏览器(Firefox、Safari)中,自动降级为
MediaRecorder录音,将音频 Blob 交给调用方提供的转写回调(例如 OpenAI Whisper、Google Cloud Speech-to-Text、AssemblyAI); - 在两者都不可用的环境中,按钮自动置灰禁用,避免出现"点了没反应"的糟糕体验。
该组件是 ai-elements 组件库的一部分。ai-elements 是构建在 shadcn/ui 之上的 AI 原生应用组件库,SpeechInput直接继承 shadcn/ui 的Button组件,因此按钮的variant、size、disabled等全部属性都天然可用,安装后组件代码会直接落入你的项目源码目录(默认@/components/ai-elements/),可以像自己写的组件一样自由修改。
仓库中的完整可运行示例见 speech-input.tsx,它演示了从录音回调到转写文本展示的完整链路。
安装与项目集成
在 ai-elements 体系中,组件通过 CLI 安装到当前项目:
npx ai-elements@latest add speech-input如果项目使用 pnpm 或 bun 作为包管理器,请使用对应的 runner:
pnpm dlx ai-elements@latest add speech-input或bunx --bun ai-elements@latest add speech-input。
根据 SKILL.md 中的说明,安装前需要满足以下前提:
- Node.js 18 及以上;
- 一个Next.js 项目且已安装AI SDK;
- 项目已配置shadcn/ui(未配置时,执行安装命令会自动补装)。
安装完成后,组件代码会写入@/components/ai-elements/speech-input.tsx(实际目录取决于你 shadcn 的 components 配置),样式基于 Tailwind CSS 类,无需额外配置即可直接使用。若导入时报 "module not found",请检查tsconfig.json是否正确配置了@/*路径别名,例如:
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./*"] } } }Props 契约
SpeechInput继承 shadcn/uiButton的全部 props,仅新增三个专属属性:
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
onTranscriptionChange | (text: string) => void | 无 | 最终转写文本就绪时触发的回调。只在完整语句产出时触发,中间过程产生的临时结果(interim results)不会触发。 |
onAudioRecorded | (audioBlob: Blob) => Promise<string> | 无 | MediaRecorder 降级模式的回调。Firefox/Safari 支持的必要条件:接收录音 Blob,返回外部转写服务(如 OpenAI Whisper)给出的文本。 |
lang | string | "en-US" | 语音识别的语言代码。 |
...props | React.ComponentProps<typeof Button> | 无 | 其余 props 全部透传给 Button,包括variant、size、disabled等。 |
需要特别强调的是onAudioRecorded的"必要性"语义:它只在 MediaRecorder 模式下被调用;但在 Firefox/Safari 上,如果不提供该回调,按钮会直接处于禁用状态。因此要获得完整的跨浏览器能力,这两个回调都应提供。
双引擎工作机制
识别模式自动探测
组件挂载时会自动检测浏览器能力,选择当前环境可用的最佳方案:
| 浏览器 | 模式 | 行为 |
|---|---|---|
| Chrome、Edge | Web Speech API | 实时转录,无需服务器 |
| Firefox、Safari | MediaRecorder | 录音后交由外部转写服务 |
| 不支持的环境 | 禁用 | 按钮置灰 |
Web Speech API 模式(Chrome、Edge)
基于SpeechRecognition(含 webkit 前缀实现),内置如下配置:
- Continuous(连续识别):设为
true,识别保持激活,直到用户再次点击按钮手动停止; - Interim Results(临时结果):设为
true,说话过程中持续返回部分结果; - Language(语言):通过
langprop 配置,默认"en-US"。
该模式下,识别过程不依赖任何后端服务,音频在浏览器本地被处理,延迟低、体验顺滑。
MediaRecorder 模式(Firefox、Safari)
当 Web Speech API 不可用时,组件切换到录音降级流程,完整步骤为:
- 使用
MediaRecorderAPI 采集麦克风音频; - 用户停止录音后,生成
audio/webm格式的音频 Blob; - 调用
onAudioRecorded(blob)将音频交给外部转写服务; - 等待转写服务返回文本;
- 将返回文本传给
onTranscriptionChange。
注意:该模式下录音格式固定为audio/webm,onAudioRecorded是必须提供的 prop,否则按钮在 Firefox/Safari 上会被禁用。
转录处理:只认"最终结果"
组件对回调做了严格的语义约束:只有最终转写文本(final transcript)才会触发onTranscriptionChange。Web Speech API 产生的 interim results(说话中途的不完整片段)会被丢弃,避免下游把"说到一半"的文本当作完整输入处理。这一点对聊天输入、命令输入等场景至关重要——你可以放心地拿到文本后立即发送或执行,不会出现半截话。
生命周期
组件的完整生命周期可以概括为四个阶段:
- 挂载(Mount):检测可用的浏览器 API,初始化对应模式;
- 点击(Click):在"监听/录音中"与"已停止"两个状态间切换;
- 停止(Stop,MediaRecorder 模式):处理音频并等待转写结果返回;
- 卸载(Unmount):停止识别/录音并释放麦克风资源,避免残留占用。
卸载时的资源释放尤其重要,可以防止组件销毁后麦克风指示灯常亮或浏览器持续显示"正在使用麦克风"。
视觉状态与状态机
组件为不同阶段提供明确的可视反馈,方便用户感知当前所处状态:
| 状态 | 表现 |
|---|---|
| 默认状态 | 标准按钮外观 + 麦克风图标 |
| 监听中 | 呼吸/脉冲(pulse)动画 + 强调色,提示正在收音 |
| 处理中 | 加载 spinner(仅 MediaRecorder 模式,等待转写服务返回) |
| 禁用 | 无可用 API 或缺少必要 props 时按钮置灰 |
完整实战:MediaRecorder 降级 + OpenAI Whisper
要在 Firefox 和 Safari 上获得完整支持,需要为onAudioRecorded提供一个把音频送给转写服务的实现。原文档给出的标准写法如下(仓库配套示例见 speech-input.tsx):
const handleAudioRecorded = async (audioBlob: Blob): Promise<string> => { const formData = new FormData(); formData.append("file", audioBlob, "audio.webm"); formData.append("model", "whisper-1"); const response = await fetch("https://api.openai.com/v1/audio/transcriptions", { method: "POST", headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, }, body: formData, }); const data = await response.json(); return data.text; }; <SpeechInput onTranscriptionChange={(text) => console.log(text)} onAudioRecorded={handleAudioRecorded} />;配套示例脚本在此基础上做了两个值得借鉴的增强,适合直接迁移到生产代码:
1. 错误显式抛出:!response.ok时抛出throw new Error("Transcription failed"),让组件内部的错误处理逻辑能够感知失败并自动停止识别/录音流程(原文档 Notes 中说明"错误会记录到 console 并自动停止识别/录音")。
2. 转写文本的增量拼接:onTranscriptionChange在回调里把每次的最终文本追加到已有内容之后,而不是覆盖:
const handleTranscriptionChange = useCallback((text: string) => { setTranscript((prev) => { const newText = prev ? `${prev} ${text}` : text; return newText; }); }, []);因为 Web Speech API 的 Continuous 模式在持续说话过程中会产出多个最终结果,采用追加式拼接可以让多轮语音输入自然累积成一段完整文本,配合一个 "Clear" 按钮即可形成完整的语音输入体验。
示例中组件以图标按钮形式呈现:
<SpeechInput onAudioRecorded={handleAudioRecorded} onTranscriptionChange={handleTranscriptionChange} size="icon" variant="outline" />如果你的转写服务不是 OpenAI Whisper,onAudioRecorded的签名((audioBlob: Blob) => Promise<string>)同样适配 Google Cloud Speech-to-Text、AssemblyAI 等任意"输入音频、输出文本"的服务,只需替换内部实现即可。
浏览器支持矩阵
组件通过两层架构实现跨浏览器覆盖:
| 浏览器 | 使用的 API | 前提条件 |
|---|---|---|
| Chrome | Web Speech API | 无 |
| Edge | Web Speech API | 无 |
| Firefox | MediaRecorder | 提供onAudioRecordedprop |
| Safari | MediaRecorder | 提供onAudioRecordedprop |
要实现完整的跨浏览器支持,请务必提供onAudioRecorded回调,将音频发送到 OpenAI Whisper、Google Cloud Speech-to-Text 或 AssemblyAI 等转写服务。
无障碍设计
组件在无障碍方面保持了 shadcn/ui 组件的一贯水准:
- 通过 shadcn/ui
Button使用语义化的<button>元素; - 监听状态提供明确的视觉反馈(脉冲动画),同时不影响键盘用户;
- 支持键盘触发(Space/Enter 可激活/停用);
- 语义化的按钮结构对屏幕阅读器友好。
安全与使用注意
以下注意事项直接影响组件能否正常工作,建议在接入前逐条核对(均出自 原文档 的 Notes 与 Behavior 章节):
- 安全上下文要求:麦克风访问要求页面处于安全上下文,即HTTPS 或 localhost;
- 浏览器权限提示:首次使用时浏览器会向用户弹出麦克风权限请求,请提前在交互文案中引导用户授权;
- 只回调最终文本:
onTranscriptionChange仅在最终转写文本产出时触发,interim results 一律忽略; - 语言可配置:通过
langprop 设置识别语言(默认"en-US"),如中文可传"zh-CN"; - 连续识别:Continuous 开启,识别会一直持续直到再次点击按钮;
- 错误自动收敛:出错时错误信息写入 console,识别/录音自动停止,不会卡在异常状态;
- 录音格式:MediaRecorder 降级模式录音格式固定为
audio/webm; - 回调完整性:MediaRecorder 降级模式依赖
onAudioRecordedprop,缺失时按钮在 Firefox/Safari 下禁用。
TypeScript 类型支持
组件内置了 Web Speech API 的完整 TypeScript 类型声明,同时兼容标准实现与 webkit 前缀实现:
SpeechRecognitionSpeechRecognitionEventSpeechRecognitionResultSpeechRecognitionAlternativeSpeechRecognitionErrorEvent
这些类型声明意味着在 TypeScript 项目中无需自行编写declare global补丁,即可获得完整的类型提示与编译期检查。
语音输入组件生态:与周边组件的配合
SpeechInput并非孤立组件,它是 ai-elements 语音能力组件族的一员。仓库 references 目录中与之配套的还有:
- MicSelector:麦克风设备选择器,基于
useAudioDevices()hook 枚举输入设备、处理权限与devicechange热插拔检测,可用来为SpeechInput指定默认麦克风; - VoiceSelector:AI 语音(TTS 音色)选择器,负责"说话的声音"这一侧;
- Transcription:转写结果展示组件,接收 AI SDK
transcribe()产出的带时间轴分段({ text, startSecond, endSecond }),支持播放高亮与点击跳转,适合做"录音回放 + 逐句同步"的场景。
实际项目中,一个完整的语音交互链路通常这样组织:用MicSelector选择输入设备 → 用SpeechInput采集并转写语音 → 用Transcription展示带时间轴的转写结果。三者分工明确,SpeechInput负责其中最核心的"语音转文本"环节。
故障排查速查
- Firefox/Safari 上按钮是禁用的:检查是否提供了
onAudioRecordedprop,这是降级模式工作的硬性前提; - 按钮可点但没有任何反应:确认页面运行在 HTTPS 或 localhost 安全上下文中,并检查浏览器是否拦截了麦克风权限;
- 转写文本不完整或缺失:确认只依赖
onTranscriptionChange接收最终文本,interim results 不会被回调; - 导入报 "module not found":确认
tsconfig.json中@/*路径别名配置正确,且组件文件确实存在于@/components/ai-elements/目录下; - 样式缺失:确认项目的
globals.css已导入 Tailwind 并包含 shadcn/ui 基础样式(Tailwind 4 项目尤其注意)。
总结
SpeechInput用约 150 行组件代码解决了语音输入领域最棘手的浏览器兼容性问题:Web Speech API 优先、MediaRecorder + 外部转写服务兜底、不支持时优雅禁用。通过onTranscriptionChange与onAudioRecorded两个回调,组件把"采集语音"和"文本消费"彻底解耦——前者由组件全权负责,后者由你的业务代码按需实现。配合仓库中的 配套示例,你可以在一个下午内为聊天输入框、命令面板或任何文本输入场景接入可靠的语音输入能力。
【免费下载链接】ZCodeZ.ai's coding agent harness. Powerful, intelligent, extensible.项目地址: https://gitcode.com/gh_mirrors/zco/ZCode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考