更新记录

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_64 ABI;插件自身的 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():Android requiresFrameSource = true;iOS 当前 requiresFrameSource = false。
  • Android 后台录音依赖前台服务和系统省电策略,是否稳定取决于目标机型。
  • Android 电池优化白名单控制只负责打开系统授权或设置页面,不会同步等待用户授权结果。

隐私、权限声明

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

Android 使用录音、通知、前台服务、唤醒锁与忽略电池优化设置引导权限;iOS 使用麦克风与后台音频模式,仅用于录音/播放相关能力

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

无

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

无