更新记录

1.0.0(2026-09-08)

首次发布

  • 提供 Android / iOS / HarmonyOS 三端统一的原生录音能力:Android AudioRecord 持续采集、iOS AVAudioRecorder 整卷连续录音、HarmonyOS AudioCapturer 持续采集。
  • 一次录音从 start 持续到 stop,采集器全程不重启:鸿蒙与 iOS 的隐私机制都不允许在后台/息屏状态下再次发起录音(鸿蒙 AVRecorder 分段轮转在息屏后重新 prepare 必报 5400103 / AVERR_IO),因此分段只通过「切换写入的文件」实现,不做任何 stop/start。
  • 三端统一输出 .wav(44 Byte 头 + 16bit 小端 PCM):片段文件创建时先写占位头,结束时按实际 PCM 字节数原位回填;format 传入 aac/m4a/mp4/3gp 等仅打印告警并仍输出 .wavencodeBitRate 保留为兼容入参、实际码率固定 sampleRate × channels × 16。需要压缩格式由服务端转码。
  • start 支持 sampleRatenumberOfChannelsencodeBitRateformatshardingDurationdurationfileNamePrefixsubDirectory,参数按 uni.getRecorderManager 的规则归一化。
  • 长时录制:以「已落盘 PCM 字节数」为唯一时长口径,到达 shardingDuration 时通过 onReceiveFile 返回完整可播放的片段(含 indexdurationfileSizeextisLastrecordingId),不再受 600s 上限约束;Android/鸿蒙在写入路径上轮转,切片边界精确到采样点、片段之间 gapless;iOS 在整卷文件上按字节顺序切分。
  • stop 支持 deleteTime,延时清理本次录音会话的缓存目录;另提供 clearCache({ keepRecent }) 按会话目录时间戳兜底回收历史残留(iOS 同时清理整卷临时文件)。
  • 事件:onStartonStoponPauseonResumeonInterruptionBeginonInterruptionEndonReceiveFileonError;并提供 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 等压缩格式,建议统一转码。


使用前提

  1. 应用已声明并获得麦克风权限(Android RECORD_AUDIO、iOS NSMicrophoneUsageDescription、HarmonyOS ohos.permission.MICROPHONE);
  2. 需要息屏/后台持续录音时,请由保活插件开启后台长时任务(HarmonyOS 的 audioRecording backgroundMode、Android 前台服务);
  3. 在调用 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 - 完成回调

StartResultrecordingIdsampleRatenumberOfChannelsencodeBitRateformat(固定 pcm)、ext(固定 wav)、shardingDurationdurationdirectory(均为归一化后的实际值)。

输出体积与采样率成正比(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

  • 暂停期间时长冻结:三端的暂停实现都是「采集器继续运行、但不写入数据」,因此片段文件里不会留下静音空隙,shardingDurationduration、片段 durationStopResult.duration 都不包含暂停时间;
  • 暂停不会截断片段resume 后继续写入同一个文件,每段的有效时长仍然对齐 shardingDuration(暂停 30s 只是让这一段的墙上时间多出 30s);
  • options.success 返回 RecordCommonResult { success, errMsg };当前没有进行中的录音(或状态已经是对应目标态)时返回 9020004

getRecordInfo()

同步返回 RecordInforecording(会话是否存活,含暂停中)、pausedinterruptedcurrentIndexsegmentCountdurationrecordingId。适合用于状态展示与断点恢复判断。

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 固定为 pcmStartResult.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.wavstop/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 AudioCaptureraudioInterrupt 事件;另外还有看门狗——超过 5s 没收到任何 PCM(息屏被系统静默停掉)即视为中断 收到 INTERRUPT_TYPE_END 或数据重新流到即恢复写入;若采集器已被系统停止,按 2s 间隔用同一实例 start() 拉起

若系统持续不放行采集(鸿蒙/iOS 超过 10 分钟仍无法恢复),插件会结束会话、把已录内容作为最后一片交付,并通过 onError9020005,描述包含「请在前台重新发起录音」)通知业务侧 —— 因为此时只有前台才能再次 start,业务侧应监听 onError 并在回到前台后重新开启会话。Android 的采集线程不会因占用而被系统停掉,仅丢弃数据,因此不做此兜底。

其他

  • 低版本兼容:Android 统一使用 AudioRecord(API 21+ 即可),不再依赖 MediaRecorder.pause/resume(Android < 7.0 不可用),因此不存在低版本暂停降级差异。
  • deleteTime 与上传的竞态deleteTime 到点即删除本次会话文件,若片段上传耗时可能超过该值,请把 deleteTime 设置得更宽裕(例如 30000),或传 0 并依赖 clearCache 兜底。清理只针对 stop 时刻的会话目录快照,不会误删之后新开的录音会话。
  • 鸿蒙路径:文件位于应用沙箱 filesDir 下,tempFilePathfile:// 前缀;若上传接口不接受该前缀,可改用 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)

隐私、权限声明

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

麦克风(权限申请由业务侧其他插件完成)

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

插件不采集任何数据,录音文件仅保存在应用沙盒内

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

暂无用户评论。