更新记录
1.0.0(2026-09-05)
首个版本。
- Android:系统 SpeechRecognizer,支持 en-US 等任意 locale,支持实时中间结果、RMS 音量回调
- iOS:Speech 框架(SFSpeechRecognizer + AVAudioEngine),支持实时中间结果、缓冲区 RMS 音量回调、端上识别(preferOffline)
- 按住说话完整会话状态机:startListening / stopListening / cancelListening,含 maxDuration 自动结束与 3 秒看门狗兜底
- prepare() 提前申请麦克风(iOS 另含语音识别)权限,避免按住说话的首次体验被权限弹窗打断
- 统一错误码(unierror.uts),errSubject 固定为 "fz-voice-speech"
平台兼容性
uni-app(4.25)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | √ | - | - | - | - | 5.0 | 13 | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(4.25)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | - | - | - | - |
fz-voice-speech 语音转文字插件(按住说话)
零 SDK 依赖的实时语音转文字 UTS 插件,专为海外 App 设计:Android 直接调用系统 SpeechRecognizer(Google 语音服务),iOS 调用系统 Speech 框架。
免费、不需要申请任何 API Key、不需要引入任何三方库,音频只交给 Google / Apple 的官方识别服务,不经任何第三方服务器。
默认语言英语(en-US),换语言只需传一个 locale。
目录
核心特性
| 能力 | 说明 |
|---|---|
| 实时吐字 | 按住期间 onPartial 边说边出字(整句替换语义),不是等松手才一次性返回 |
| 按住说话 | 完整会话状态机:按下即听、松手出全文、上滑取消、来电打断静默作废 |
| 音量回调 | onVolume 0~1 归一化音量,双端刻度已拉平,直接驱动波形动画 |
| 自动限时 | maxDuration 到点自动收尾,照常触发 onFinish(autoStopped = true) |
| 离线识别 | preferOffline:Android 走 EXTRA_PREFER_OFFLINE,iOS 走端上识别(不联网、无配额限制) |
| 权限预热 | prepare() 提前申请权限,避免首次按住被系统弹窗打断 |
| 鲁棒兜底 | 极快松手不丢、个别机型 stop 后无终值 3 秒看门狗、终值为空回退中间结果、迟到事件隔离 |
工作原理
按下(touchstart) ──→ startListening()
│ 申请麦克风权限(已授权则瞬间通过)
│ 启动系统识别引擎(Android 有 200~800ms 服务启动延迟)
▼
onStart:麦克风就绪,开始收音
│
onVolume(持续) │ onPartial(多次,实时文字)
│
松开(touchend) ──→ stopListening()
│ 引擎收尾,把整段话重新整理(补标点、纠错)
▼
onFinish:最终完整文本 { text, duration, autoStopped }
任何环节失败 ──→ onError(errCode 见错误码表;onFinish 与 onError 互斥,各恰好一次)
上滑/来电 ──→ cancelListening():静默作废,不触发任何回调
- Android:
SpeechRecognizer+RecognitionListener,识别由设备上的 Google 语音服务完成(海外机标配) - iOS:
SFSpeechRecognizer+AVAudioEngine输入缓冲 +SFSpeechAudioBufferRecognitionRequest流式识别
环境要求
| 项 | 要求 |
|---|---|
| HBuilderX | 4.25+ |
| Android | minSdk 21+;设备需带 Google 应用 / Play 服务(海外机标配;无 Google 服务的机器 isRecognitionAvailable() 返回 false) |
| iOS | 13.0+,系统自带 Speech 框架 |
| uni-app | Vue3(uni-app 与 uni-app x 均可) |
| 基座 | ⚠️ UTS 插件跑不了标准基座,必须先「制作自定义调试基座」或云打包 / 离线打包 |
权限说明(重要)
需要哪些权限
| 平台 | 权限 | 说明 |
|---|---|---|
| Android | RECORD_AUDIO(运行时权限) |
麦克风;插件内部自动弹框申请,无需自己写权限代码 |
| Android | INTERNET |
在线识别需要(云打包默认已有) |
| iOS | 麦克风(NSMicrophoneUsageDescription) |
收音 |
| iOS | 语音识别(NSSpeechRecognitionUsageDescription) |
Speech 框架,与麦克风是两个独立开关 |
你需要做的配置:零
以上权限全部由插件 config.json 自动合并:
- Android 的
RECORD_AUDIO声明在utssdk/app-android/config.json - iOS 的两条权限描述在
utssdk/app-ios/config.json的privacies,打包时自动写入 Info.plist
不需要手改 AndroidManifest、Info.plist 或 Xcode 工程。
苹果开发者后台(Certificates, Identifiers & Profiles)
不需要任何配置。 后台的 Capabilities 对应 entitlements 类功能(Push、HealthKit、SiriKit 等),本插件一个都没用到。麦克风和语音识别只要求 Info.plist 描述文案,普通证书 + 普通 profile 打包即可。
想自定义 iOS 权限弹窗文案(可选)
在 manifest.json → app-plus.distribute.ios 下加(manifest 优先于插件默认值):
"ios": {
"privacyDescription": {
"NSMicrophoneUsageDescription": "Your voice is used to transcribe your speech.",
"NSSpeechRecognitionUsageDescription": "Speech recognition is used to transcribe your speech."
}
}
接入步骤(从零到能跑)
第 1 步:导入插件
把 uni_modules/fz-voice-speech 整个文件夹放进你项目的 uni_modules/ 目录(或从插件市场导入)。不需要 npm install,不需要任何初始化函数(没有 configure、没有 clientId、没有 API Key)。
第 2 步:打自定义基座
HBuilderX 菜单:运行 → 运行到手机或模拟器 → 制作自定义调试基座(iOS 选云打包,用你现有的普通证书即可)。打完在真机上选择这个自定义基座运行。
每次修改插件的原生代码(
.kt/.swift/index.uts)后都需要重新打基座;只改 JS/Vue 层不需要。
第 3 步:页面加载时预热权限
import { prepare, isRecognitionAvailable } from '@/uni_modules/fz-voice-speech'
// 建议放在 onLoad / onShow
if (!isRecognitionAvailable()) {
// 设备没有识别服务(Android 无 Google 服务时)
// 引导用户或降级到打字输入
}
prepare().then(() => {
// 权限就绪,可以按住说话了
}).catch(err => {
// err.errCode: 2001 麦克风被拒 / 2002 语音识别被拒(iOS)
// 引导用户去 系统设置 开启
})
为什么必须做:按住说话是"按下瞬间就要出声"的交互。iOS 首次会弹两个权限框,如果留给首次按住时弹,用户松手时识别还没开始,体验直接废掉。
prepare()已授权时立即返回,不弹框。
第 4 步:按住说话四件套
在按钮上绑四个触摸事件,这是完整套路:
import {
startListening,
stopListening,
cancelListening
} from '@/uni_modules/fz-voice-speech'
export default {
data() {
return {
holding: false, // 本次按住是否有效
wantCancel: false, // 上滑取消标记
pressAt: 0, // 按下时间戳
pressY: 0, // 按下时的触点 Y
partialText: '', // 实时文字
result: null // 最终结果
}
},
methods: {
// ① 按下:发起识别
onPressStart(e) {
this.holding = true
this.wantCancel = false
this.pressAt = Date.now()
this.pressY = e.touches[0].clientY
this.partialText = ''
startListening({
locale: 'en-US', // 默认 en-US,可不传
preferOffline: false, // 离线识别,默认 false
maxDuration: 60000, // 最长 60 秒,可不传
onStart: () => {
// 麦克风真正就绪(Android 有 200~800ms 延迟)
// 在这里把 UI 切到「正在听」并启动计时器
},
onPartial: res => {
this.partialText = res.text // 边说边出字,整句替换
},
onVolume: res => {
// res.volume: 0~1,驱动波形动画
},
onFinish: res => {
// 松手后的最终结果
this.result = res // { text, duration, autoStopped }
},
onError: err => {
// err.errCode / err.errMsg,见错误码表
}
})
},
// ② 移动:上滑超过 90px 进入「松开取消」(微信式交互)
onPressMove(e) {
this.wantCancel = this.pressY - e.touches[0].clientY > 90
},
// ③ 松开:太短就放弃,否则正常收尾
onPressEnd() {
if (!this.holding) return
this.holding = false
// 按下不足 400ms:主动取消,比等引擎报「没听到语音」体验好
if (Date.now() - this.pressAt < 400 || this.wantCancel) {
cancelListening()
return
}
stopListening() // 最终文字仍从 onFinish 回来
},
// ④ 系统打断(来电、手势冲突等):静默作废
onPressCancel() {
if (!this.holding) return
this.holding = false
cancelListening()
}
}
}
模板:
<view class="talk-btn"
@touchstart.prevent="onPressStart"
@touchmove.prevent="onPressMove"
@touchend.prevent="onPressEnd"
@touchcancel="onPressCancel">
<text>按住说话</text>
</view>
第 5 步(可选):处理退出
页面 onUnload 里调一次 cancelListening() 兜底,防止用户按住时退出页面留下悬挂会话。
完整示例:按住说话页面
本仓库演示工程 pages/index/index.vue 是一个完整可复制的实现,包含:
- 状态卡片(识别服务 / 麦克风权限就绪检测)
- 实时识别区(波形动画 + 边说边出字 + 计时)
- 按住说话按钮(按下 / 准备中 / 正在听 / 识别中 / 松开取消 五态)
- 结果卡片(文本 + 用时 + 自动结束标记 + 一键复制)
- 设置(语言 en-US/en-GB/en-AU/en-CA/en-IN、最长时长、离线开关)
- 运行日志
直接把这一页拷进你的项目即可用。
API 参考
startListening(options?) → void
发起一次识别会话(按下时调用)。注意它立刻返回,麦克风真正就绪以 onStart 为准。会话结果只从 onFinish / onError 出来(两者互斥,各恰好一次)。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
locale |
string | "en-US" |
BCP 47 语言码。常用:en-US / en-GB / en-AU / en-CA / en-IN;其它语言(zh-CN、ja-JP…)只要设备引擎支持都行 |
preferOffline |
boolean | false |
尽量端上识别。Android 需已下载离线包(未下载自动回退在线);iOS 13+ 机型支持时走 on-device,不联网、不受配额限制、精度略低 |
maxDuration |
number | 60000 |
单次会话最长毫秒数。到点自动收尾并照常触发 onFinish(autoStopped = true)。iOS 在线识别单次上限约 1 分钟,默认值已对齐。传 0 表示不限时(不建议) |
onStart |
() => void |
- | 麦克风就绪、开始收音。UI 在这里切「正在听」 |
onPartial |
(res) => void |
- | res.text:实时识别文本,整句替换语义(不是追加),直接赋值渲染 |
onVolume |
(res) => void |
- | res.volume:0~1 归一化音量,约几十毫秒一次。仅作动画用途,不要当分贝仪 |
onFinish |
(res) => void |
- | 见下方 VoiceFinish |
onError |
(err) => void |
- | err 为 UniError:errCode / errMsg / errSubject(恒为 "fz-voice-speech") |
VoiceFinish 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
text |
string | 最终识别文本。已做兜底:终值为空时回退最后一次中间结果;必非空串(空结果会以 3001 错误走 onError) |
duration |
number | 本次会话时长(毫秒),从 startListening 调用起计 |
autoStopped |
boolean | 是否「非用户松手」导致的结束:达到 maxDuration 自动收尾,或识别服务无终值时插件兜底收尾。用户主动松手恒为 false |
stopListening() → void
松手收尾。通知引擎出最终结果,结果仍从 onFinish 拿(引擎收尾会把整段重新整理,补标点、纠错,所以最终文本可能比松手前最后一次 partial 更准确完整)。
- 麦克风尚未就绪就已松手:插件记下「要停」,就绪后自动补停,不丢
- 松手后等终值的那一小段,
onPartial仍在刷新(最后几个字还在出) - 重复调用安全,STOPPING / IDLE 状态下直接忽略
cancelListening() → void
静默作废本次会话,不会触发 onFinish / onError。用于:
- 上滑取消(微信式)
- 按下时间太短(< 400ms)
- 来电等系统打断(
touchcancel) - 页面退出兜底
prepare() → Promise\<boolean>
预热:申请麦克风权限(iOS 另含语音识别权限)+ 检查识别服务可用。已授权时立即 resolve,不弹框,所以每次页面加载调用都安全。建议 onLoad 里调用。
resolve(true):就绪reject:errCode2001 / 2002(权限被拒)或 2003(设备无识别服务)
isRecognitionAvailable() → boolean
同步查询设备是否有识别服务,不弹任何框。Android 上即「是否有 Google 语音服务」;iOS 上即「Speech 识别器是否可用」。
错误码
所有失败都从 onError(或 prepare 的 reject)抛出,errSubject 恒为 "fz-voice-speech"。
| errCode | 含义 | 触发场景 | 建议处理 |
|---|---|---|---|
| 1002 | 已有识别任务进行中 | 按住交互下基本触发不到 | 忽略 |
| 2001 | 麦克风权限被拒 | Android 勾选「不再询问」/ iOS 麦克风关 | 引导去系统设置 |
| 2002 | 语音识别权限被拒(仅 iOS) | iOS「语音识别」开关被关 | 引导去系统设置 |
| 2003 | 设备无识别服务 / locale 不支持 | Android 无 Google 服务;iOS 不支持该语言 | 功能降级(打字输入) |
| 3001 | 没听到语音内容 | 按太短 / 全程静音 / 引擎无法匹配 | 高频正常分支:轻提示或静默,不要当异常上报 |
| 4001 | 网络错误 | 在线识别连不上 Google / Apple 服务 | 提示检查网络 |
| 4002 | 识别超时 | stop 后 3 秒无终值,插件已自动释放 | 极少数机型,提示重试 |
| 5001 | 引擎错误 | 识别器内部错误、服务忙、会话超长等 | 带 errMsg 上报,提示重试 |
平台差异对照表
| Android | iOS | |
|---|---|---|
| 引擎 | SpeechRecognizer → Google 语音服务 | Speech 框架 → Apple 服务 / 端上识别 |
| 额外依赖 | 设备带 Google 应用 / Play 服务 | 无(系统自带) |
| 权限 | RECORD_AUDIO(运行时,插件内申请) | 麦克风 + 语音识别两个开关(插件 config 自动带描述) |
| 开发者后台 | 无需配置 | 无需配置(无 capability / entitlements) |
| 离线 | EXTRA_PREFER_OFFLINE,需已下载离线包,未下载自动回退 | requiresOnDeviceRecognition,iOS 13+ 按机型/语言支持 |
| 配额 | 无明确限制 | 在线:单次语音约 1 分钟、每设备每小时约 1000 次;离线:无限制 |
| 启动延迟 | 200~800ms(服务绑定) | 通常 < 200ms |
| 吐字颗粒度 | 较勤,基本几个词一刷 | 在线短语级;离线略粗 |
最佳实践
- 一定要
prepare():按住说话容不下任何弹窗。 - 400ms 短按直接
cancelListening():等引擎报 3001 的体验远不如自己先放弃。 - 上滑取消:按住类交互的标配,
touchmove里比较触点 Y 与按下 Y,超过 90px 切「松开取消」。 onStart才启动计时器和波形:按下到就绪之间是引擎启动延迟,期间onVolume还没有数据。- partial 直接赋值:整句替换语义,不要自己做拼接。
- 3001 不要上报:这是"没说话/按太短",是用户行为不是故障。
- 长语音别指望单次会话:iOS 在线约 1 分钟上限,需要更长请分段录制识别(松手再按),或考虑专门的录音转写方案。
常见问题 FAQ
Q: 标准基座能跑吗? 不能,UTS 插件含原生代码,必须自定义调试基座或正式打包。
Q: Android 模拟器报 2003? 模拟器镜像需带 Google APIs(API 30+ 的 Google APIs 镜像可用),否则没有语音识别服务。真机(海外渠道)无此问题。
Q: 识别结果是空串?
不会。插件做了空终值回退中间结果;真的没有任何内容时走 3001 错误而不是空 onFinish。
Q: 为什么 final 和松手前屏幕上的字不完全一样?
引擎收尾会重整全文(补标点、结合上下文纠错),以 onFinish 的 text 为准。
Q: 想要中文 / 日文?
locale 传 "zh-CN" / "ja-JP",两端引擎对主流语言都支持,准确度由系统引擎决定。
Q: iOS 报 1101 / 203 之类错误? 都映射到了 3001(没听到内容),属于 iOS 引擎常见行为(如没出声就松手),按正常分支处理。
Q: 能拿到音频文件吗?
本插件不落盘音频,只做实时识别。需要音频文件请配合 uni.getRecorderManager 自行录音。
Q: 后台识别?
不支持。按住说话是前台交互,进后台系统会掐掉麦克风(touchcancel 路径已兜底)。
隐私合规
- 音频只交给操作系统官方识别服务(Android:设备上的 Google 语音服务;iOS:Apple Speech 服务),不经插件作者或任何第三方服务器
- 插件本体不做任何网络请求、无统计、无广告、无三方 SDK
- App Store 隐私标签:需声明收集「音频数据(Audio Data)」,用途选 App Functionality
- Google Play:填写数据安全表单时相应声明麦克风与音频处理即可

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 0
赞赏 0
下载 12563319
赞赏 1949
赞赏
京公网安备:11010802035340号