更新记录
1.0.0(2026-09-08)
首次发布
- 提供 Android / iOS / HarmonyOS 三端统一的原生录音能力:Android
AudioRecord持续采集、iOSAVAudioRecorder整卷连续录音、HarmonyOSAudioCapturer持续采集。 - 一次录音从
start持续到stop,采集器全程不重启:鸿蒙与 iOS 的隐私机制都不允许在后台/息屏状态下再次发起录音(鸿蒙AVRecorder分段轮转在息屏后重新prepare必报5400103/AVERR_IO),因此分段只通过「切换写入的文件」实现,不做任何 stop/start。 - 三端统一输出
.wav(44 Byte 头 + 16bit 小端 PCM):片段文件创建时先写占位头,结束时按实际 PCM 字节数原位回填;format传入aac/m4a/mp4/3gp等仅打印告警并仍输出.wav,encodeBitRate保留为兼容入参、实际码率固定sampleRate × channels × 16。需要压缩格式由服务端转码。 start支持sampleRate、numberOfChannels、encodeBitRate、format、shardingDuration、duration、fileNamePrefix、subDirectory,参数按uni.getRecorderManager的规则归一化。- 长时录制:以「已落盘 PCM 字节数」为唯一时长口径,到达
shardingDuration时通过onReceiveFile返回完整可播放的片段(含index、duration、fileSize、ext、isLast、recordingId),不再受 600s 上限约束;Android/鸿蒙在写入路径上轮转,切片边界精确到采样点、片段之间 gapless;iOS 在整卷文件上按字节顺序切分。 stop支持deleteTime,延时清理本次录音会话的缓存目录;另提供clearCache({ keepRecent })按会话目录时间戳兜底回收历史残留(iOS 同时清理整卷临时文件)。- 事件:
onStart、onStop、onPause、onResume、onInterruptionBegin、onInterruptionEnd、onReceiveFile、onError;并提供getRecordInfo查询实时状态。 - 系统占用(电话、微信/QQ 语音等)自动识别与续录,全程不重启采集器:Android 节流轮询
AudioManager.mode;iOS 监听AVAudioSession中断通知(按内部状态判定 began/ended)并在回到前台时补偿续录;HarmonyOS 使用audioInterrupt事件,并带有「超过 5s 无 PCM 数据即视为中断」的看门狗,按 2s 间隔用同一实例start()拉起。中断期间丢弃数据、时长冻结,结束后自动继续,业务侧无需调用resume。 - 时长口径统一为「有效录音时长」,暂停与中断期间不写入数据、计时自然冻结,保证每段时长对齐
shardingDuration;异常收尾(原生报错、写盘失败、分段切换失败)也会先把已录内容作为最后一片交付再触发onError。 - 不包含录音权限申请逻辑,权限由业务侧其他插件统一处理。
平台兼容性
uni-app(4.75)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | - | - | √ | - | 5.0 | 14 | 12 |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
uni-app x(4.75)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | 5.0 | 14 | 12 | × |
bear-record
长时后台分段录音 UTS 插件,兼容 Android / iOS / HarmonyOS(鸿蒙) 三端。
语义对齐 uni.getRecorderManager(),并补齐了工程录音(订单全程录音)真正需要的能力:
- 按时长分段:
start({ shardingDuration: 60000 }),每当有效录音时长达到 60s,就通过onReceiveFile回调一个完整可播放的音频文件; - 长时不重启:一次录音从
start一直采集到stop,分段只是切换写入的文件,息屏/后台不会重新发起录音(规避鸿蒙5400103与 iOS 的前台限制); - 无限时长:不受
uni.getRecorderManager最长 600s 的限制,可全程录制; - 系统占用自动续录:电话响铃/接听、微信/QQ 语音等导致录音被系统打断时,插件自动暂停并在占用结束后自动继续录音,业务侧无需调用
resume; - 缓存自动清理:
stop({ deleteTime: 6000 })在停止后延时清理本次录音产生的片段文件。
录音权限(麦克风)不在本插件内实现,由业务侧其他插件(如
cy-Keepalive)统一申请,本插件只负责录音。
兼容性
| 平台 | 支持 | 录音内核 | 片段输出 |
|---|---|---|---|
| Android | √ | AudioRecord 持续采集 + 采集线程直写文件 |
.wav(16bit PCM) |
| iOS | √ | AVAudioRecorder 单卷连续录音 + FileHandle 按字节切分 |
.wav(16bit PCM) |
| HarmonyOS | √ | @kit.AudioKit AudioCapturer 持续采集 + fileIo 直写文件 |
.wav(16bit PCM) |
| Web / 小程序 | × | 调用返回错误码 9020009 |
- |
运行环境要求:HBuilderX 3.6.8+,Android minSdk 21、iOS deploymentTarget 14、HarmonyOS API 12+。
三端统一输出 WAV,且一次录音从
start持续到stop不重启采集器,原因见「实现要点与注意事项」。服务端如需 AAC 等压缩格式,建议统一转码。
使用前提
- 应用已声明并获得麦克风权限(Android
RECORD_AUDIO、iOSNSMicrophoneUsageDescription、HarmonyOSohos.permission.MICROPHONE); - 需要息屏/后台持续录音时,请由保活插件开启后台长时任务(HarmonyOS 的
audioRecordingbackgroundMode、Android 前台服务); - 在调用
start之前注册好onReceiveFile,否则分段文件会被录出但没人接收(回调为全局单例,重复注册以最后一次为准,与uni.getRecorderManager一致)。
快速开始
import {
start, stop, pause, resume, getRecordInfo, clearCache,
onStart, onStop, onPause, ,
onInterruptionBegin, onInterruptionEnd, onReceiveFile, onError
} from '@/uni_modules/bear-record'
// 1. 注册监听(建议页面 onLoad / 应用启动时执行一次)
onReceiveFile((file) => {
// file.tempFilePath 形如 file:///data/.../bear_record_1756713600000_483920_1.wav
console.log('收到片段', file.index, file.ext, file.fileSize, file.duration, '是否最后一片', file.isLast)
// 直接上传,注意扩展名取 file.ext,不要写死
uni.uploadFile({
url: 'https://your.api/upload',
filePath: file.tempFilePath,
name: 'file',
formData: { segmentIndex: file.index, duration: file.duration },
success: (res) => console.log('上传完成', res)
})
})
onStart(() => console.log('录音已开始'))
onStop((res) => console.log('录音已停止,共', res.segmentCount, '段,有效时长', res.duration, 'ms'))
onPause(() => console.log('录音已暂停'))
(() => console.log('录音已继续'))
onInterruptionBegin(() => console.log('被系统占用,录音已暂停'))
onInterruptionEnd(() => console.log('系统占用结束,插件已自动续录'))
onError((err) => console.error('录音异常', err.errCode, err.errMsg))
// 2. 开始录音
start({
shardingDuration: 60000, // 每 60s 产出一个片段
sampleRate: 16000, // 采样率
numberOfChannels: 1, // 单声道
format: 'wav', // 三端统一输出 WAV;传其他值会打印告警并仍输出 wav
fileNamePrefix: 'order', // 文件名前缀,便于排查
success: (res) => console.log('实际参数', res.sampleRate, res.encodeBitRate, res.format, res.ext),
fail: (err) => console.error('启动失败', err.errCode, err.errMsg)
})
// 3. 暂停 / 继续(可选)
pause()
resume()
// 4. 结束录音,并在 6s 后清理本次产生的缓存文件
stop({
deleteTime: 6000,
success: (res) => console.log('已停止', res.fileList)
})
API
start(options)
开始(或重新开始)一次录音会话。同一时刻只允许一个会话,重复调用返回 9020001。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| sampleRate | number | 16000 | 采样率,合法值 8000/11025/12000/16000/22050/24000/32000/44100/48000,非法值就近取合法值 |
| numberOfChannels | number | 1 | 通道数,仅支持 1/2,其余值按 1 处理 |
| encodeBitRate | number | 自动计算 | 入参保留以兼容 uni.getRecorderManager 写法;输出为 PCM,实际码率固定为 sampleRate × channels × 16 bps |
| format | string | 'wav' | 三端统一输出 WAV。传入 aac/m4a/mp4/3gp 等会打印告警并仍输出 .wav |
| shardingDuration | number | 0 | 分段时长,单位 ms。有效录音时长达到该值时结束当前片段、通过 onReceiveFile 返回完整文件,并立即开始下一段;0 或不传表示不分段 |
| duration | number | 0 | 录音总时长上限(ms)。到达上限后自动执行与 stop 相同的收尾流程(最后一片 isLast = true);0 表示不限制 |
| fileNamePrefix | string | 'bear_record' | 片段文件名前缀 |
| subDirectory | string | 'bear-record' | 片段文件在应用沙盒内的存放目录名 |
| success | (res: StartResult) => void | - | 启动成功回调 |
| fail | (res: RecorderFail) => void | - | 失败回调 |
| complete | (res: any) => void | - | 完成回调 |
StartResult:recordingId、sampleRate、numberOfChannels、encodeBitRate、format(固定 pcm)、ext(固定 wav)、shardingDuration、duration、directory(均为归一化后的实际值)。
输出体积与采样率成正比(16bit PCM,sampleRate × channels × 2 Byte/秒):
| 采样率 / 通道 | 码率 | 每分钟体积 | 60s 片段体积 |
|---|---|---|---|
| 8000 / 1 | 128 kbps | 0.96 MB | ≈ 0.96 MB |
| 16000 / 1(推荐) | 256 kbps | 1.92 MB | ≈ 1.92 MB |
| 44100 / 1 | 706 kbps | 5.29 MB | ≈ 5.29 MB |
| 48000 / 2 | 1536 kbps | 11.25 MB | ≈ 11.25 MB |
工程录音建议 sampleRate: 16000, numberOfChannels: 1,兼顾人声还原与上传体积。
stop(options)
结束当前录音,最后一片以 isLast = true 通过 onReceiveFile 返回,同时触发 onStop。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| deleteTime | number | 0 | 清理本次录音缓存的延时(ms)。stop 完成后经过 deleteTime 毫秒,删除本次会话已产出的全部片段文件;0 或不传表示保留文件,由业务侧上传完成后自行调用 clearCache |
| success / fail / complete | function | - | 回调 |
StopResult:
| 字段 | 类型 | 说明 |
|---|---|---|
| tempFilePath | string | 最后一个片段路径(带 file://),无文件时为 '' |
| filePath | string | 最后一个片段的原生绝对路径 |
| duration | number | 本次录音累计有效时长(ms,已剔除暂停与中断时间) |
| segmentCount | number | 本次会话产出的片段总数 |
| fileList | Array\<string> | 全部片段路径(带 file://),按序号排列 |
| recordingId | string | 会话 id |
pause(options?) / resume(options?)
暂停 / 继续录音,分别触发 onPause / onResume。
- 暂停期间时长冻结:三端的暂停实现都是「采集器继续运行、但不写入数据」,因此片段文件里不会留下静音空隙,
shardingDuration、duration、片段duration、StopResult.duration都不包含暂停时间; - 暂停不会截断片段:
resume后继续写入同一个文件,每段的有效时长仍然对齐shardingDuration(暂停 30s 只是让这一段的墙上时间多出 30s); options.success返回RecordCommonResult { success, errMsg };当前没有进行中的录音(或状态已经是对应目标态)时返回9020004。
getRecordInfo()
同步返回 RecordInfo:recording(会话是否存活,含暂停中)、paused、interrupted、currentIndex、segmentCount、duration、recordingId。适合用于状态展示与断点恢复判断。
clearCache(options?)
清理插件录音目录下已过期的会话文件,用于兜底回收异常退出残留的数据。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| keepRecent | number | 0 | 保留最近 N 毫秒内开始的会话;0 表示全部删除 |
| success / fail / complete | function | - | 回调,成功返回 ClearCacheResult { deletedCount } |
会话目录名形如
1756713600000_483920,前缀即该次录音开始的毫秒时间戳,因此无需读取文件属性即可判断过期。建议 App 启动时调用一次clearCache({ keepRecent: 3600000 })。
事件监听
| 方法 | 回调参数 | 触发时机 |
|---|---|---|
| onStart | - | 录音开始 |
| onStop | StopResult |
录音停止(含 duration 到上限自动停止) |
| onPause | - | 录音暂停(业务侧 pause 或系统占用中断) |
| onResume | - | 录音继续(业务侧 resume 或中断结束自动续录) |
| onInterruptionBegin | - | 录音被系统占用(微信/QQ 语音、电话响铃、接听等)打断,随后必触发 onPause |
| onInterruptionEnd | - | 系统占用结束。此时插件已自动续录,业务侧不需要再调用 resume(恢复同样是「继续写入」,不重新发起录音;若采集器被系统停掉,插件会在最多约 2s 的重试间隔内用同一实例拉起) |
| onReceiveFile | ReceivedFile |
有一个完整片段文件产出(分段到达 / stop 最后一片 / 异常收尾) |
| onError | RecorderFail |
录音过程中发生原生错误(采集器异常、音频数据写入失败、分段切换失败等),会话会被结束,已录内容仍会作为最后一片交付 |
ReceivedFile:
| 字段 | 类型 | 说明 |
|---|---|---|
| tempFilePath | string | 片段路径,带 file://,可直接给 uni.uploadFile / uni.saveFile |
| filePath | string | 原生绝对路径(供需要真实路径的原生接口使用) |
| ext | string | 文件扩展名,三端统一为 wav |
| fileSize | number | 文件大小 Byte;取不到时为 0 |
| index | number | 片段序号,从 1 开始连续递增 |
| duration | number | 本片段时长(ms),由本片段的 PCM 字节数换算,不含暂停与中断 |
| isLast | boolean | 是否为本次会话最后一片 |
| format | string | 编码标识,固定 pcm |
| startTime / endTime | number | 本片段时间戳(ms) |
| recordingId | string | 会话 id,同一会话的片段共享该值 |
输出格式
三端统一输出 .wav(44 Byte 头 + 16bit 小端 PCM),不提供 aac/m4a/mp4/3gp 等压缩格式:
传入 format |
Android | iOS | HarmonyOS |
|---|---|---|---|
wav / pcm(推荐,也是默认) |
.wav |
.wav |
.wav |
其他任意值(aac/m4a/mp3/3gp …) |
打印告警,仍输出 .wav |
打印告警,仍输出 .wav |
打印告警,仍输出 .wav |
原因见下一节。若业务需要压缩格式,建议上传后由服务端统一转码;StartResult.format 固定为 pcm、StartResult.ext 固定为 wav,均为归一化后的真实值,可按此判断落盘格式。
实现要点与注意事项
为什么必须「采集器不重启 + WAV」:鸿蒙 5400103 的根因
鸿蒙与 iOS 的安全机制都不允许在后台/息屏状态下发起一次新的录音:
- 鸿蒙
AVRecorder一旦stop,在息屏/后台再prepare+start必然返回5400103(媒体模块AVERR_IO,音频设备 I/O 不可打开);media模块也没有可用的流式音频编码器/Muxer; - iOS 的
AVAudioSession/AVAudioRecorder同样只允许在前台激活新的录音,后台只能让当前这次录音一直跑。
因此「停止录音器 → 新建录音器」的分段方案在息屏后必然失败。本插件的语义是:
一次录音从
start持续到stop,中间的采集器/录音器实例绝不重启;分段只是切换写入的文件。
同时,原生 AAC/M4A 编码器都要求一个完整的封装生命周期(无法在录音过程中反复重启 muxer 写独立文件),所以三端统一输出 16bit PCM WAV,由插件自己写文件头。这是规避平台限制的必要取舍。
分段机制
- 时长口径只有一个:已落盘的 PCM 字节数(
字节 ÷ (sampleRate × channels × 2))。字节数到达shardingDuration对应的阈值时关闭当前文件、onReceiveFile交付,并立刻开启下一个文件; - Android / HarmonyOS 在写入路径上判断并轮转(Android 在采集线程内、鸿蒙在
readData回调内),切片边界精确到采样点,片段之间既不重叠也不丢数据,完全 gapless; - 打开片段时先写入 44 Byte 占位 WAV 头,关闭时按实际 PCM 字节数原位回填头部的两个长度字段;
- iOS 因
AVAudioRecorder只能连续写同一份文件,采用「整卷连续录音 +FileHandle按字节顺序切分」:切分时把整卷中尚未切出的 PCM 复制为独立 WAV。整卷文件名形如order_1756713600000_483920_raw_0.wav,stop/clearCache时删除;iOS 峰值磁盘占用约为录音数据量的 2 倍,且切片时刻最多滞后一个 200ms tick(片段可能比shardingDuration最多多约 200ms,不会缺失内容)。
各端中断识别与自动续录
三端都不因中断重启采集器,中断期间丢弃数据(时长冻结)、占用结束后恢复写入;业务侧主动 pause 时不会自动续录,仍等 resume。
| 平台 | 中断识别 | 恢复方式 |
|---|---|---|
| Android | 主线程每 200ms tick 内以 1s 节流轮询 AudioManager.getMode()(无需额外权限) |
mode 回到 MODE_NORMAL 即恢复写入,采集线程不退出 |
| iOS | AVAudioSession.interruptionNotification(按插件内部状态判定 began/ended,不读 userInfo)+ 回到前台补偿 |
同一实例再 record() 续写同一整卷文件;失败时按 2s 间隔在 tick 里重试 |
| HarmonyOS | AudioCapturer 的 audioInterrupt 事件;另外还有看门狗——超过 5s 没收到任何 PCM(息屏被系统静默停掉)即视为中断 |
收到 INTERRUPT_TYPE_END 或数据重新流到即恢复写入;若采集器已被系统停止,按 2s 间隔用同一实例 start() 拉起 |
若系统持续不放行采集(鸿蒙/iOS 超过 10 分钟仍无法恢复),插件会结束会话、把已录内容作为最后一片交付,并通过 onError(9020005,描述包含「请在前台重新发起录音」)通知业务侧 —— 因为此时只有前台才能再次 start,业务侧应监听 onError 并在回到前台后重新开启会话。Android 的采集线程不会因占用而被系统停掉,仅丢弃数据,因此不做此兜底。
其他
- 低版本兼容:Android 统一使用
AudioRecord(API 21+ 即可),不再依赖MediaRecorder.pause/resume(Android < 7.0 不可用),因此不存在低版本暂停降级差异。 deleteTime与上传的竞态:deleteTime到点即删除本次会话文件,若片段上传耗时可能超过该值,请把deleteTime设置得更宽裕(例如 30000),或传 0 并依赖clearCache兜底。清理只针对stop时刻的会话目录快照,不会误删之后新开的录音会话。- 鸿蒙路径:文件位于应用沙箱
filesDir下,tempFilePath带file://前缀;若上传接口不接受该前缀,可改用filePath。 - 鸿蒙文件写入 API 选型:新版 SDK 的
@kit.CoreFileKit只把File/OpenMode/WhenceType等少量成员挂在fileIo命名空间下,WriteOptions/ReadOptions已改为顶层导出,fs.WriteOptions会报Namespace 'fileIo' has no exported member 'WriteOptions'。因此这里采用「顺序追加writeSync(fd, buffer)+ 回填头前lseek(fd, 0, SEEK_SET)」,不依赖带 options 的重载,兼容新旧 SDK。 - 异常安全:采集器报错、写盘失败、分段切换失败时会结束会话,但先把已录内容作为最后一片交付(
isLast = true)再通过onError通知业务侧,不会遗留占用的麦克风。 - 体积与上传:PCM 无压缩,体积与采样率成正比(见上文体积表)。建议使用
sampleRate: 16000, numberOfChannels: 1(约 1.92 MB/分钟),60s 一段时单片体积可控;长时录音需保证设备剩余存储充足,并及时上传 +stop({ deleteTime })回收。
错误码
| errCode | 说明 |
|---|---|
| 9020001 | 录音已在进行中,请勿重复开始 |
| 9020002 | 录音初始化失败(目录创建、音频会话配置等) |
| 9020003 | 录音启动失败 |
| 9020004 | 当前没有正在进行的录音 |
| 9020005 | 录音过程中发生错误 |
| 9020006 | 参数错误 |
| 9020007 | 录音分段切换失败 |
| 9020008 | 录音缓存清理失败 |
| 9020009 | 当前平台不支持录音 |
与 uni.getRecorderManager 的差异
| 能力 | uni.getRecorderManager | bear-record |
|---|---|---|
| 最长录音时长 | 600s | 不限制(duration 可选) |
| 分段 | 仅 shardingDuration(实际靠业务侧 onStop 后重新 start) |
原生分段,onReceiveFile 返回完整文件,采集器全程不重启 |
| 系统占用中断 | 需业务侧自行 start 续录 |
自动续录,仅通知业务侧 |
| 缓存清理 | 无 | stop({ deleteTime }) + clearCache({ keepRecent }) |
| 输出格式 | 各端不一致(aac/wav/amr) | 三端统一 .wav(16bit PCM) |
| HarmonyOS 后台/息屏录音 | 静默失败或分段重启报 5400103 |
AudioCapturer 持续采集,息屏/后台只丢数据不重启,可稳定录制 |
| 回调形式 | 事件挂在 manager 实例上 | 全局函数式注册(与 keepAlive 配合,页面卸载后仍会回调) |
目录结构
bear-record/
├── package.json
├── readme.md
├── changelog.md
└── utssdk/
├── interface.uts # 三端共享的类型定义
├── unierror.uts # 错误码与错误对象
├── core.uts # 三端共享纯逻辑(参数归一化、PCM/WAV 工具、分段规划、回调中心)
├── index.uts # Web/小程序等无原生实现时的兜底导出
├── app-android/ # index.uts + config.json(AudioRecord 持续采集 + 采集线程直写 WAV)
├── app-ios/ # index.uts + config.json(AVAudioRecorder 整卷连续录音 + 按字节切分)
└── app-harmony/ # index.uts + config.json(AudioCapturer 持续采集 + fileIo 直写 WAV)

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