更新记录
3.2.1(2026-09-29)
- 修复 iOS 流式录音(含
frameBase64帧)忽略请求sampleRate的问题:显式传入sampleRate时自动从硬件采样率(通常 48000Hz)重采样到请求值,帧元数据与pcm / g711 / mp3 / oggOpus产物同步按请求采样率输出。已在 iPhone 15 真机验证。
3.2.0(2026-09-29)
- iOS 后台/锁屏录音加固:iOS 26 起,App 在后台无法重新配置
AVAudioSession(setCategory/setActive报'!int'cannotInterruptOthers),与常驻音频引擎(如语音唤醒)交接时会直接录音失败(9010007)。现在会话已支持录音时降级沿用现配置,激活失败不再判死录音流程;已在 iPhone 15(iOS 26)真机验证唤醒→录音→连续唤醒链路。
3.1.0(2026-09-29)
- 新增
frameBase64帧出口:start({ frameBase64: true })开启后,onFrameRecorded()结果额外携带标准 base64 字符串(内容与frameBuffer逐字节相同,Android / iOS / HarmonyOS 行为一致),默认关闭时为null。 - 补充经典
uni-app(App-Plus / Weex 桥)ArrayBuffer桥接限制说明:frameBuffer为桥接对象、无法直接消费或回传插件 API,请改用frameBase64+uni.base64ToArrayBuffer,不要依赖内部字段byteBuffer.hb(iOS 不存在)。
平台兼容性
uni-app(4.76)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | - | - | √ | - | √ | √ | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(4.76)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | √ | √ | √ | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 | 蒸汽模式 |
|---|---|---|---|
| × | × | × | √ |
hens-recorder
跨端统一录音插件,提供录音、PCM 播放、文件工具、实时上传、音频转码和平台控制能力。
支持平台
- 支持:
uni-app x的App-Android、App-iOS、App-Harmony(鸿蒙 V1,见"使用限制") - 不支持:
H5、各类小程序 - 经典
uni-app支持App-Android、App-iOS;公共 API 与uni-app x保持一致。
接入要求
- 宿主环境要求
HBuilderX >= 3.6.8、uni-app >= 4.76、uni-app x >= 4.76。 - Android 宿主应用要求
minSdkVersion >= 24。 - iOS 宿主应用要求
deploymentTarget >= 12.0。 - 如果要在 Android 模拟器或自定义基座上调试,宿主需要包含
x86 / x86_64ABI;插件自身的 Android 配置已内置这些 ABI。 - 代码侧统一从
@/uni_modules/hens-recorder导入,不要直接引用utssdk/*。
使用前准备
- Android 侧需要录音权限;如果要做后台录音,还会用到通知、前台服务、唤醒锁和电池优化相关能力。
- iOS 侧需要麦克风权限,后台录音依赖
audio后台模式。 - 建议先调用一次
setLoggingEnabled()明确日志开关,不要依赖默认值。 - 如果页面需要按平台/能力裁剪功能,先读取
getRecorderManager().getCapabilities()、getRealtimeUploadCapabilities()、getAudioTranscodeCapabilities()。
主入口
getRecorderManager(): RecorderManager
createPcmPlayerContext(): PcmPlayerContext
getRecorderPermissionStatus(): RecorderPermissionStatus
requestRecorderPermission(options?: RequestRecorderPermissionOptions | null): void
getRecorderFileSystemManager(): RecorderFileSystemManager
createAudioTranscodeTask(options: AudioTranscodeStartOptions): AudioTranscodeTask
getAudioTranscodeCapabilities(): AudioTranscodeCapabilities
createRealtimeUploadTask(options: RealtimeUploadStartOptions): RealtimeUploadTask
getRealtimeUploadCapabilities(): RealtimeUploadCapabilities
createHttpChunkUploadTask(options: HttpChunkUploadStartOptions): HttpChunkUploadTask
getHttpChunkUploadCapabilities(): HttpChunkUploadCapabilities
getAudioRouteManager(): AudioRouteController
getIOSAudioSessionManager(): IOSAudioSessionController
getAndroidForegroundServiceManager(): AndroidForegroundServiceController
getAndroidBatteryOptimizationManager(): AndroidBatteryOptimizationController
setLoggingEnabled(enabled: boolean): void
isLoggingEnabled(): boolean
录音
import {
getRecorderManager,
requestRecorderPermission,
getRecorderPermissionStatus,
RequestRecorderPermissionOptions,
RecorderManagerStartOptions
} from '@/uni_modules/hens-recorder'
const recorderManager = getRecorderManager()
const status = getRecorderPermissionStatus()
if (!status.granted) {
const permissionOptions: RequestRecorderPermissionOptions = {
microphone: true,
notifications: 'auto'
}
requestRecorderPermission(permissionOptions)
}
const startOptions: RecorderManagerStartOptions = {
format: 'm4a',
quality: 'medium',
sampleRate: 44100,
numberOfChannels: 1,
encodeBitRate: 96000,
frameSize: 0,
progressIntervalMs: 200,
interruption: 'pause',
ensurePermissions: true
}
recorderManager.onError((err) => {
console.log(err.errCode, err.errMsg)
})
recorderManager.onProgress((status) => {
console.log(status.durationMs, status.filePath)
})
recorderManager.onStop((result) => {
console.log(result.actualFormat, result.autoStopReason, result.status.filePath)
})
recorderManager.start(startOptions)
说明:
RecorderManager支持start / pause / resume / stop / getState / getStatus / getCapabilities。onStart()、onPause()、onResume()、onStop()、onStateChange()适合直接驱动页面状态。frameSize > 0时可通过onFrameRecorded()拿到实时 PCM 帧和rmsLevel / peakLevel。frameBase64: true时onFrameRecorded()的结果额外携带frameBase64(标准 base64 字符串,内容与frameBuffer逐字节相同);默认关闭、字段为null,详见下方「帧字节稳定出口 frameBase64」。- Android 13+ 下,
requestRecorderPermission({ notifications: 'auto' })只有在宿主targetSdkVersion >= 33时才会显式申请通知权限。 foregroundEnabled、foregroundTitle、foregroundText、foregroundChannelName、foregroundImportance只对 Android 后台录音有意义。androidAudioSource、androidEnableAEC、androidEnableNS、androidEnableAGC只在 Android 有意义;iosPreferSpeaker主要影响 iOS 共享音频路由。
帧字节稳定出口 frameBase64
onFrameRecorded() 结果里的 frameBuffer 在 uni-app x 上是真正的 ArrayBuffer,可以直接消费;但在经典 uni-app(App-Plus / Weex 桥)里它是运行时内部的桥接对象,new Uint8Array(frameBuffer) 会静默拿到 0 字节,唯一可读路径 frameBuffer.byteBuffer.hb 是运行时内部字段、随版本变化且 iOS 上不存在。
三端一致的稳定出口:
recorderManager.onFrameRecorded((frame) => {
if (frame.frameBase64) {
// 标准 base64(RFC 4648,含 = 补齐),长度 === ceil(frameBuffer.byteLength / 3) * 4
const bytes = uni.base64ToArrayBuffer(frame.frameBase64)
// 或直接把 base64 交给服务端
}
})
recorderManager.start({
format: 'wav',
frameSize: 8,
frameBase64: true
})
注意:
- 经典
uni-app不要依赖frameBuffer.byteBuffer.hb,请迁移到frameBase64;iOS 上没有hb字段。 uni-app x仍推荐直接用frameBuffer(无编码开销);frameBase64只在需要跨桥传字节或对接服务端时开启。- 开启后每帧都会携带 base64(8192 字节帧约膨胀到 10924 字符),
frameSize越大、帧率越高,字符串开销越大,按需开启。 - 「把
frameBuffer原样回传给插件 API(如 PCM 播放器write)」在经典uni-app下当前不可用(已知桥接限制);经典端请用frameBase64解码后的数据或走服务端链路。
PCM 播放
import {
createPcmPlayerContext,
PcmPlayerConfigureOptions,
PcmPlayerWriteOptions
} from '@/uni_modules/hens-recorder'
const player = createPcmPlayerContext()
const configureOptions: PcmPlayerConfigureOptions = {
sampleRate: 44100,
numberOfChannels: 1,
bitDepth: 16,
audioRoute: 'speaker',
iosPreferSpeaker: true
}
player.configure(configureOptions)
player.onPlay(({ state }) => console.log(state))
player.onBufferWatermarkChange((status) => {
console.log(status.level, status.bufferedDurationMs)
})
player.play()
const writeOptions: PcmPlayerWriteOptions = {
frameBuffer,
success: (result) => console.log(result.bytesWritten)
}
player.write(writeOptions)
说明:
configure()后再执行play()/write(),结束后建议调用destroy()。audioRoute当前只支持speaker/earpiece。getBufferStatus()、onBufferChange()、setBufferWatermarks()、getBufferWatermarkStatus()可用于播放调优。
文件工具
import {
getRecorderFileSystemManager,
ResolvePathOptions,
WriteFileOptions,
ReadFileOptions
} from '@/uni_modules/hens-recorder'
const fs = getRecorderFileSystemManager()
const resolveOptions: ResolvePathOptions = {
path: 'samples',
directory: true,
createDirectories: true
}
fs.resolve(resolveOptions)
const writeOptions: WriteFileOptions = {
path: 'samples/session.txt',
text: 'hello recorder',
createDirectories: true
}
fs.write(writeOptions)
const readOptions: ReadFileOptions = {
path: 'samples/session.txt',
encoding: 'utf8',
success: (result) => console.log(result.text)
}
fs.read(readOptions)
路径和调用约定:
- 空路径和普通相对路径默认锚定推荐输出目录。
- 绝对路径按原样处理,业务侧应自行约束
path来源。 write()必须二选一传text或buffer。resolve / list / read / write / delete / move / copy都沿用success / fail / complete风格。
文件流:
import {
getRecorderFileSystemManager,
OpenFileReadStreamOptions,
OpenFileWriteStreamOptions,
FileStreamReadOptions,
FileStreamWriteOptions
} from '@/uni_modules/hens-recorder'
const fs = getRecorderFileSystemManager()
const openWriteOptions: OpenFileWriteStreamOptions = {
path: 'samples/chunks.bin',
truncate: true,
createDirectories: true
}
const writeStream = fs.openWriteStream(openWriteOptions)
const writeOptions: FileStreamWriteOptions = {
buffer: frameBuffer
}
writeStream.write(writeOptions)
writeStream.flush()
writeStream.close()
const openReadOptions: OpenFileReadStreamOptions = {
path: 'samples/chunks.bin'
}
const readStream = fs.openReadStream(openReadOptions)
const readOptions: FileStreamReadOptions = {
length: 4096,
success: (result) => console.log(result.bytesRead, result.eof)
}
readStream.read(readOptions)
readStream.close()
说明:
openReadStream()/openWriteStream()仍然是同步打开。- Android 打开失败会直接同步抛业务错误;iOS 会返回
state=error的流对象,业务侧可继续在首次read()/write()时通过fail/catch读取真实错误。 - Android 文件流同步打开失败现在会尽量保留原始业务错误码,不再统一降级成普通
Error。 - 流对象支持
read / write / seek / flush / truncate / tell / getState / close。 close()幂等;关闭后再次操作会返回9010021。
音频转码
import {
createAudioTranscodeTask,
getAudioTranscodeCapabilities,
AudioTranscodeStartOptions
} from '@/uni_modules/hens-recorder'
console.log(getAudioTranscodeCapabilities())
const options: AudioTranscodeStartOptions = {
inputPath: 'samples/input.raw',
outputPath: 'samples/input-transcoded.wav',
inputFormat: 'pcm',
outputFormat: 'wav',
sampleRate: 8000,
numberOfChannels: 1,
overwrite: true,
createDirectories: true
}
const task = createAudioTranscodeTask(options)
task.onProgress((status) => {
console.log(status.progress, status.processedBytes, status.totalBytes)
})
task.onComplete((status) => {
console.log(status.outputPath, status.outputBytes)
})
task.onError((err) => {
console.log(err.errCode, err.errMsg)
})
说明:
- 具体可用路由始终以
getAudioTranscodeCapabilities()返回值为准。 pcm输入必须显式提供sampleRate和numberOfChannels;g711a / g711u输入必须提供sampleRate。- 3.0 起支持
oggOpus:输出(pcm/wav/mp3/g711 → oggOpus,Android 依赖 MediaCodec opus 编码器即 API 29+;iOS 内置 libopus 恒可用)与输入(oggOpus → pcm/wav/mp3/g711)。产物为标准 RFC 7845 ogg-opus,OpusHead.InputSampleRate声明真实源采样率,可用 ffprobe / ffmpeg 直接验证。 createAudioTranscodeTask()返回后如果初始化或参数校验失败,task 会直接处于error。- 当前只支持单并发任务;超出时会返回
9010032。 cancel()默认删除部分输出文件;传deletePartialFileOnCancel: false可保留残留文件。- Android / iOS 转码错误现在会尽量透传原始业务错误码;
cancel()/getStatus()命中不存在任务时统一返回9010004。 - 建议同时监听
onStateChange()与onError(),并在任务创建后主动读一次getStatus()。
实时上传
import {
createRealtimeUploadTask,
getRealtimeUploadCapabilities,
RealtimeUploadStartOptions
} from '@/uni_modules/hens-recorder'
console.log(getRealtimeUploadCapabilities())
const startOptions: RealtimeUploadStartOptions = {
url: 'ws://127.0.0.1:8080/realtime-upload',
chunkDurationMs: 200,
stopOnRecorderStop: true
}
const task = createRealtimeUploadTask(startOptions)
task.onStateChange((state, status) => {
console.log(state, status.sentChunks, status.queuedBytes)
})
task.onMessage((message) => {
console.log(message.isBinary, message.text)
})
task.onStats((status) => {
console.log(status.state, status.inFlightChunks, status.acknowledgedChunks)
})
说明:
ackStrategy: 'manual'时,需要在服务端确认后主动调用confirmChunk(sequence);需要重发时调用retryChunk(sequence)。waitForRecorder: true适合先建链再等录音;stopOnRecorderStop: true适合把上传生命周期绑定到当前录音会话。replaceExisting: true会替换已有任务;需要保留旧任务状态时不要开启。- 建议至少监听
onOpen()、onStateChange()、onError()、onClose()、onStop()。 getStatus()可回读queuedBytes / inFlightBytes / sentBytes / acknowledgedChunks / lastErrorCode。- 3.0 起支持
payloadFormat: 'oggOpus'(需录音侧具备 oggOpus 能力且走帧回调路线):客户端边录边编码为连续 ogg-opus 页流上行,带宽从 ~256kbps(pcm16le)降到 ~24kbps 量级;配套tools/realtime-upload-server已支持按.ogg落盘,可直接 ffprobe 回放。
分片上传(http-chunk 断点续传)
3.0 起提供任务式分片上传,独立于实时上传;协议兼容 multipart
file / chunkIndex / chunkSize / totalChunks / uploadId,支持中断后按服务端
receivedIndexes 续传,服务端全部分片收齐后自动合并。
import {
createHttpChunkUploadTask,
getHttpChunkUploadCapabilities,
HttpChunkUploadStartOptions
} from '@/uni_modules/hens-recorder'
const options: HttpChunkUploadStartOptions = {
filePath: '/path/to/recording.ogg',
uploadUrl: 'http://192.168.1.10:8080/upload',
chunkSize: 512 * 1024,
maxConcurrency: 3,
maxRetries: 3,
resumable: true
}
const task = createHttpChunkUploadTask(options)
task.onStateChange((state, status) => {
console.log(state, status.progress.percentage, status.uploadId)
})
task.onProgress((progress) => {
console.log(progress.uploadedBytes, progress.uploadSpeedBps)
})
task.onComplete((status) => {
console.log(status.serverFileName)
})
task.onError((err) => {
console.log(err.errCode, err.errMsg)
})
task.start()
说明:
pause() / resume()暂停续传同一任务;abort()终止。resumable: true(默认)会先GET {uploadUrl}/{uploadId}/status探测断点,已收分片自动跳过。- 杀进程/断网后重新
createHttpChunkUploadTask({ uploadId: 上次的 id })可继续续传。 - 配套
tools/realtime-upload-server已实现POST /upload、GET /upload/:id/status联调端点。
平台控制
import {
getAudioRouteManager,
getIOSAudioSessionManager,
getAndroidForegroundServiceManager,
getAndroidBatteryOptimizationManager,
AudioRoute,
ConfigureIOSAudioSessionOptions,
ConfigureAndroidForegroundServiceOptions,
RequestIgnoreBatteryOptimizationsOptions
} from '@/uni_modules/hens-recorder'
const preferredRoute: AudioRoute = 'speaker'
getAudioRouteManager().setPreferredRoute(preferredRoute)
const iosAudioSessionOptions: ConfigureIOSAudioSessionOptions = {
category: 'playAndRecord',
mode: 'voiceChat',
options: ['allowBluetooth', 'defaultToSpeaker'],
active: true
}
getIOSAudioSessionManager().configure(iosAudioSessionOptions)
const foregroundServiceOptions: ConfigureAndroidForegroundServiceOptions = {
enabled: true,
title: '正在录音',
text: '录音正在后台进行中',
importance: 'low'
}
getAndroidForegroundServiceManager().configure(foregroundServiceOptions)
const batteryOptimizationOptions: RequestIgnoreBatteryOptimizationsOptions = {
preferDirectRequest: true
}
getAndroidBatteryOptimizationManager().requestIgnoreBatteryOptimizations(batteryOptimizationOptions)
说明:
getAudioRouteManager()当前只接受speaker/earpiece。getIOSAudioSessionManager().getStatus()可读取当前active / category / mode / options / hasCustomConfig。getIOSAudioSessionManager().configure({ active: false })可释放当前会话,configure({ reset: true })可清空自定义配置。getAndroidForegroundServiceManager().getStatus()现在会同时返回active / serviceActive / sessionActive / isRecording / isPaused / isPreparing,其中active只表示服务已起且录音已经进入recording|paused,sessionActive会把启动中的preparing也算进去。getAndroidForegroundServiceManager()只在 Android 有实际意义;iOS 返回supported = false的占位状态。getAndroidBatteryOptimizationManager()只在 Android 6.0+ 有实际意义,从系统设置页返回后建议重新getStatus()。
使用限制
- 录音格式:iOS 支持
m4a / wav / pcm / mp3 / g711a / g711u / oggOpus;Android 支持m4a / wav / pcm / g711a / g711u / oggOpus(mp3与oggOpus只在运行时编码器实际可用时经getCapabilities()暴露:oggOpus依赖平台 MediaCodec opus 编码器,Android 10 / API 29 起普遍可用,更老设备如实不声明)。 - oggOpus 产物为标准 RFC 7845 ogg-opus(48kHz 编码流);请求的源采样率写入
OpusHead.InputSampleRate,解码侧按该字段还原时长。Android/iOS/鸿蒙三端 muxer 共享同一份字节级测试向量(tools/opus-validation)。 - HarmonyOS(V1):录音默认
wav,支持wav / pcm,可用的原生 Opus 编码器额外提供oggOpus;显式请求m4a等不支持格式时通过onError返回9010006,不会生成伪装的音频文件。转码支持pcm / wav输入及pcm / wav / oggOpus输出,Opus 路线以运行时能力为准。转码完成或失败后注册回调会补发终态,getStatus()返回实际字节统计和错误码。PCM 播放器、文件 API、音频路由等控制接口仍按能力声明降级;使用转码时需由调用方提供可访问的输入、输出路径。 - HarmonyOS 权限接入:鸿蒙侧插件无法自动注入权限,宿主工程需在
harmony-configs/entry/src/main/module.json5的module.requestPermissions声明ohos.permission.MICROPHONE(user_grant,需附reason字符串与usedScene)及ohos.permission.INTERNET、ohos.permission.GET_NETWORK_INFO、ohos.permission.KEEP_BACKGROUND_RUNNING,否则运行时权限请求会被系统直接忽略;可参考 playground 工程harmony-configs/。 - iOS 流式采集(
pcm / g711a / g711u / mp3 / oggOpus及onFrameRecorded帧)底层以硬件输入格式采集(iPhone 麦克风通常为 48000Hz)。显式传入sampleRate且与硬件采样率不同时,3.2.1 起会自动重采样到请求采样率,帧元数据sampleRate与产物采样率均按请求值上报/输出;未传sampleRate时保持硬件采样率。numberOfChannels仍以硬件输入声道为准,不保证按请求声道数转换。 getRealtimeUploadCapabilities():AndroidrequiresFrameSource = true;iOS 当前requiresFrameSource = false。- Android 后台录音依赖前台服务和系统省电策略,是否稳定取决于目标机型。
- Android 电池优化白名单控制只负责打开系统授权或设置页面,不会同步等待用户授权结果。

收藏人数:
购买普通授权版(
试用
使用 HBuilder 导入示例项目
赞赏(0)
下载 394
赞赏 0
下载 12696067
赞赏 1956
赞赏
京公网安备:11010802035340号