更新记录

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。


目录

  1. 核心特性
  2. 工作原理
  3. 环境要求
  4. 权限说明(重要)
  5. 接入步骤(从零到能跑)
  6. 完整示例:按住说话页面
  7. API 参考
  8. 错误码
  9. 平台差异对照表
  10. 最佳实践
  11. 常见问题 FAQ
  12. 隐私合规

核心特性

能力 说明
实时吐字 按住期间 onPartial 边说边出字(整句替换语义),不是等松手才一次性返回
按住说话 完整会话状态机:按下即听、松手出全文、上滑取消、来电打断静默作废
音量回调 onVolume 0~1 归一化音量,双端刻度已拉平,直接驱动波形动画
自动限时 maxDuration 到点自动收尾,照常触发 onFinishautoStopped = 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():静默作废,不触发任何回调
  • AndroidSpeechRecognizer + RecognitionListener,识别由设备上的 Google 语音服务完成(海外机标配)
  • iOSSFSpeechRecognizer + 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.jsonprivacies,打包时自动写入 Info.plist

不需要手改 AndroidManifest、Info.plist 或 Xcode 工程。

苹果开发者后台(Certificates, Identifiers & Profiles)

不需要任何配置。 后台的 Capabilities 对应 entitlements 类功能(Push、HealthKit、SiriKit 等),本插件一个都没用到。麦克风和语音识别只要求 Info.plist 描述文案,普通证书 + 普通 profile 打包即可。

想自定义 iOS 权限弹窗文案(可选)

manifest.jsonapp-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 单次会话最长毫秒数。到点自动收尾并照常触发 onFinishautoStopped = 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):就绪
  • rejecterrCode 2001 / 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
吐字颗粒度 较勤,基本几个词一刷 在线短语级;离线略粗

最佳实践

  1. 一定要 prepare():按住说话容不下任何弹窗。
  2. 400ms 短按直接 cancelListening():等引擎报 3001 的体验远不如自己先放弃。
  3. 上滑取消:按住类交互的标配,touchmove 里比较触点 Y 与按下 Y,超过 90px 切「松开取消」。
  4. onStart 才启动计时器和波形:按下到就绪之间是引擎启动延迟,期间 onVolume 还没有数据。
  5. partial 直接赋值:整句替换语义,不要自己做拼接。
  6. 3001 不要上报:这是"没说话/按太短",是用户行为不是故障。
  7. 长语音别指望单次会话:iOS 在线约 1 分钟上限,需要更长请分段录制识别(松手再按),或考虑专门的录音转写方案。

常见问题 FAQ

Q: 标准基座能跑吗? 不能,UTS 插件含原生代码,必须自定义调试基座或正式打包。

Q: Android 模拟器报 2003? 模拟器镜像需带 Google APIs(API 30+ 的 Google APIs 镜像可用),否则没有语音识别服务。真机(海外渠道)无此问题。

Q: 识别结果是空串? 不会。插件做了空终值回退中间结果;真的没有任何内容时走 3001 错误而不是空 onFinish

Q: 为什么 final 和松手前屏幕上的字不完全一样? 引擎收尾会重整全文(补标点、结合上下文纠错),以 onFinishtext 为准。

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:填写数据安全表单时相应声明麦克风与音频处理即可

隐私、权限声明

1. 本插件需要申请的系统权限列表:

android.permission.RECORD_AUDIO;iOS 需要麦克风(NSMicrophoneUsageDescription)与语音识别(NSSpeechRecognitionUsageDescription)权限描述,已由插件 config.json 自动合并

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

音频数据只交给操作系统的官方识别服务处理(Android 为设备的 Google 语音服务,iOS 为 Apple Speech 服务),不经过插件作者的任何服务器,插件也不做任何网络请求。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。