更新记录
1.0.2(2026-04-22)
更新说明
1.0.1(2026-04-22)
修改说明
1.0.0(2026-04-22)
首次提交
查看更多平台兼容性
uni-app(5.0)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | - | √ | √ | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
uni-app x(5.0)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | - |
q-record
q-record 是一个多端 UTS 录音插件,统一通过 getRecorderManager() 暴露录音能力,目标是在 Android / iOS / Harmony / Web(H5) 上以尽量一致的方式完成录音、权限处理、分片回调和结果输出。
插件特性
- 支持
Android / iOS / Harmony / Web(H5) - 统一导出
getRecorderManager() - 支持权限检查、权限申请、打开系统设置页
- 支持
maxDurationMs自动停止 - 支持分片录音
- 支持统一状态流:
idlepreparingrecordingstoppingstoppederror
当前阶段限制:
- App 三端当前默认输出
m4a/aac - Web/H5 使用浏览器原生
MediaRecorder- 优先尝试
m4a - 浏览器不支持时自动降级为
webm/ogg
- 优先尝试
mp3转码逻辑已移除- 当前不支持:
pause()resume()- 音量电平回调
- PCM 实时帧回调
导入方式
import { getRecorderManager } from '@/uni_modules/q-record'
快速开始
方法总览
通过 getRecorderManager() 获取到的 recorder 实例,当前对外完整方法如下:
const recorder = getRecorderManager()
recorder.checkPermission()
recorder.requestPermission((permission) => {})
recorder.openPermissionSettings()
recorder.start(options)
recorder.stop()
recorder.destroy()
recorder.getState()
recorder.onStart((res) => {})
recorder.offStart()
recorder.onSegment((file) => {})
recorder.offSegment()
recorder.onStop((res) => {})
recorder.offStop()
recorder.onError((err) => {})
recorder.offError()
完整接入示例
const recorder = getRecorderManager()
function bindRecorderEvents() {
// 录音真正开始后触发。
recorder.onStart((res) => {
console.log('onStart', res.sessionId, res.state, res.startedAt)
})
// 开启分片后,每个已完成片段都会进入该回调,最后一片也会回调。
recorder.onSegment((file) => {
console.log('onSegment', file.segmentIndex, file.filePath, file.durationMs)
})
// 停止完成后返回整轮录音结果。
recorder.onStop((res) => {
console.log('onStop', res.sessionId, res.totalDurationMs, res.files)
})
// 启动失败、停止失败、参数错误都会走这里。
recorder.onError((err) => {
console.error('onError', err.errCode, err.errMsg)
})
}
function unbindRecorderEvents() {
// 页面销毁前取消事件绑定。
recorder.offStart()
recorder.offSegment()
recorder.offStop()
recorder.offError()
}
function checkRecorderPermission() {
// 只检查状态,不会触发系统授权弹窗。
const permission = recorder.checkPermission()
console.log('checkPermission', permission)
return permission
}
function requestRecorderPermission() {
// 主动申请麦克风权限。
recorder.requestPermission((permission) => {
console.log('requestPermission', permission)
if (!permission.canRecord) {
// 已无弹窗机会时,引导用户去系统设置页。
if (permission.needOpenSettings) {
recorder.openPermissionSettings()
}
return
}
// 权限满足后再开始录音。
startRecord()
})
}
function startRecord() {
// 按当前配置启动录音。
recorder.start({
format: 'm4a',
sampleRate: 44100,
bitRate: 128000,
maxDurationMs: 60000,
segmentEnabled: true,
segmentDurationMs: 10000,
fileNamePrefix: 'demo',
saveDir: null
})
}
function stopRecord() {
// 请求停止,最终结果在 onStop 中返回。
recorder.stop()
}
function printCurrentState() {
// 随时读取当前状态机状态。
console.log('getState', recorder.getState())
}
function destroyRecord() {
// 释放回调和底层资源。
unbindRecorderEvents()
recorder.destroy()
}
// 页面初始化时先绑定事件,再检查权限和开始业务流程。
bindRecorderEvents()
printCurrentState()
checkRecorderPermission()
requestRecorderPermission()
推荐使用顺序:
- 获取实例
- 注册
onStart / onSegment / onStop / onError - 调用
checkPermission()或requestPermission() - 调用
start() - 业务结束时调用
stop() - 页面卸载或不再使用时调用
destroy()
权限调用约定:
- 业务侧统一使用
requestPermission(callback) callback会在本次权限申请结束后收到RecorderPermissionResultrequestPermission()当前只负责显式申请权限,不再自动跳系统设置页- 如果返回
needOpenSettings = true,业务侧应自行决定是否调用openPermissionSettings() - 如果本次调用没有弹出系统权限框,可直接读取返回结果里的
dialogStatus / reasonCode / reasonMessage - 如果业务层需要做稳定条件分支,优先读取
code
API 说明
getRecorderManager(): RecorderManagerInstance
返回统一录音管理器实例。当前所有平台都按单例语义工作。
入参:
- 无
返回值:
RecorderManagerInstance- 统一录音管理器实例
- 当前返回的是单例实例
- 不建议在同一页面并发使用多个录音实例
示例:
import { getRecorderManager } from '@/uni_modules/q-record'
const recorder = getRecorderManager()
console.log(recorder.getState())
checkPermission(): RecorderPermissionResult
检查当前麦克风权限状态,不会触发系统权限弹窗。
入参:
- 无
返回值:
RecorderPermissionResult- 当前权限状态快照
- 可读取
canRecord / needOpenSettings / code / reasonCode / reasonMessage - 详情见下文 RecorderPermissionResult
示例:
const permission = recorder.checkPermission()
console.log('是否可录音', permission.canRecord)
console.log('是否需要打开设置', permission.needOpenSettings)
console.log('原因', permission.code, permission.reasonCode, permission.reasonMessage)
requestPermission(callback: RecorderPermissionRequestCallback): void
请求麦克风权限。该方法只负责显式申请权限,不会自动跳系统设置页。
参数:
callback: RecorderPermissionRequestCallback- 本次权限申请结束后的结果回调
- 回调参数类型为
RecorderPermissionResult - 如果系统没有弹出授权框,可通过回调结果里的
dialogStatus / reasonCode / reasonMessage判断原因
返回值:
void- 权限申请结果通过
callback返回
- 权限申请结果通过
示例:
recorder.requestPermission((permission) => {
console.log('权限申请结果', permission)
if (!permission.canRecord) {
if (permission.needOpenSettings) {
recorder.openPermissionSettings()
}
console.log(permission.code, permission.reasonCode, permission.reasonMessage)
console.log('当前不可录音')
return
}
console.log('当前可直接开始录音')
recorder.start({
format: 'm4a',
sampleRate: 44100,
bitRate: 128000
})
})
openPermissionSettings(): void
尝试打开系统权限设置页。
入参:
- 无
返回值:
void- 该方法只负责发起“打开设置页”的动作
- 不保证一定跳转成功,也不保证能感知用户在设置页的最终操作结果
说明:
- Android / iOS / Harmony 会尽量跳转系统设置
- Web 当前为空实现,浏览器通常不允许直接打开权限设置页
示例:
const permission = recorder.checkPermission()
if (permission.needOpenSettings) {
recorder.openPermissionSettings()
}
start(options?: RecorderStartOptions | null): void
开始录音。
参数:
options?: RecorderStartOptions | null- 录音启动配置
- 传
undefined / null时使用默认配置 - 详情见下文 RecorderStartOptions
返回值:
void- 调用成功后不会直接返回结果
- 真正的开始结果通过
onStart(callback)返回 - 若启动失败,会通过
onError(callback)返回错误
说明:
- 如果当前已经处于
preparing / recording / stopping,会通过onError返回错误 - 如果权限不足,会通过
onError返回错误 - 如果参数不合法,会通过
onError返回错误
示例:
recorder.start({
format: 'm4a',
sampleRate: 44100,
bitRate: 128000,
maxDurationMs: 30000,
segmentEnabled: true,
segmentDurationMs: 10000,
fileNamePrefix: 'meeting',
saveDir: null
})
stop(): void
请求停止录音。
入参:
- 无
返回值:
void- 停止完成后的最终结果通过
onStop(callback)返回 - 停止失败时通过
onError(callback)返回错误
- 停止完成后的最终结果通过
说明:
- 如果当前没有活跃录音,调用不会报错
- 停止完成后,最终结果通过
onStop返回
示例:
recorder.stop()
destroy(): void
销毁实例内部状态、回调和底层资源。
入参:
- 无
返回值:
void- 调用后会清理内部状态、回调引用和底层录音资源
建议:
- 页面卸载时调用
- 开始新一轮录音前,如果不希望复用上一轮的 URL、回调或状态,也可以先调用一次
特别说明:
- Web 调用
destroy()后,会释放此前创建的blob:URL - 一旦释放,之前拿到的
filePath会失效 - 若业务侧还要走 H5
uni.uploadFile,请在destroy()前完成上传
示例:
recorder.destroy()
getState(): RecorderState
获取当前录音状态。
入参:
- 无
返回值:
RecorderState- 当前状态值可能是
idle / preparing / recording / stopping / stopped / error
- 当前状态值可能是
示例:
const state = recorder.getState()
console.log('当前状态', state)
事件 API
onStart(callback: RecorderStartCallback): void
注册开始录音回调。
参数:
callback: RecorderStartCallback- 回调参数类型为
RecorderStartResult - 在录音真正进入
recording后触发
- 回调参数类型为
返回值:
void
示例:
recorder.onStart((res) => {
console.log('录音开始', res.sessionId, res.startedAt)
})
offStart(): void
取消开始录音回调。
入参:
- 无
返回值:
void
示例:
recorder.offStart()
onSegment(callback: RecorderSegmentCallback): void
注册分片回调。
参数:
callback: RecorderSegmentCallback- 回调参数类型为
RecorderSegmentFile - 开启分片后,每个已完成片段都会走
onSegment - 最后一片也会走
onSegment,并同时出现在onStop.files
- 回调参数类型为
返回值:
void
示例:
recorder.onSegment((file) => {
console.log('分片回调', file.segmentIndex, file.filePath, file.isLastSegment)
})
offSegment(): void
取消分片回调。
入参:
- 无
返回值:
void
示例:
recorder.offSegment()
onStop(callback: RecorderStopCallback): void
注册录音结束回调。
参数:
callback: RecorderStopCallback- 回调参数类型为
RecorderStopResult - 停止成功、自动停止完成或整轮分片结束后触发
- 回调参数类型为
返回值:
void
示例:
recorder.onStop((res) => {
console.log('录音结束', res.totalDurationMs, res.files)
})
offStop(): void
取消录音结束回调。
入参:
- 无
返回值:
void
示例:
recorder.offStop()
onError(callback: RecorderErrorCallback): void
注册错误回调。
参数:
callback: RecorderErrorCallback- 回调参数类型为
RecorderError - 启动失败、停止失败、权限不足、参数不合法等情况都会走这里
- 回调参数类型为
返回值:
void
示例:
recorder.onError((err) => {
console.error('录音错误', err.errCode, err.errMsg)
})
offError(): void
取消错误回调。
入参:
- 无
返回值:
void
示例:
recorder.offError()
事件注册补充说明:
- 一个事件当前只保留一个回调引用
- 重复
onXxx()会覆盖前一次注册 - 建议在页面初始化时统一注册,在页面卸载时统一
offXxx()或直接destroy()
枚举与类型说明
RecorderFormat
type RecorderFormat = "m4a" | "webm" | "ogg"
说明:
- App 三端当前最终固定输出
m4a - Web 会按浏览器能力协商为
m4a / webm / ogg
RecorderCodec
type RecorderCodec = "aac" | "opus"
说明:
- App 三端当前固定为
aac - Web 可能为
aac或opus
RecorderPipelineMode
type RecorderPipelineMode = "direct"
当前仅支持直出模式。
RecorderState
type RecorderState =
| "idle"
| "preparing"
| "recording"
| "stopping"
| "stopped"
| "error"
状态说明:
idle- 初始空闲状态
preparing- 正在准备录音器、申请底层资源、切换分片
recording- 正在录音
stopping- 已请求停止,正在收尾
stopped- 本次录音结束
error- 出现终止性错误
RecorderPermissionStatus
type RecorderPermissionStatus =
| "authorized"
| "denied"
| "not determined"
| "config error"
说明:
authorized- 已授权
denied- 已拒绝
not determined- 尚未决定,通常还可以申请
config error- 平台上下文、配置或运行环境异常
RecorderSampleRate
type RecorderSampleRate = 16000 | 44100
RecorderBitRate
type RecorderBitRate =
| 32000
| 48000
| 64000
| 96000
| 128000
| 160000
| 192000
| 256000
| 320000
参数说明
RecorderStartOptions
type RecorderStartOptions = {
format?: RecorderFormat
sampleRate?: RecorderSampleRate
bitRate?: RecorderBitRate
maxDurationMs?: number
segmentEnabled?: boolean
segmentDurationMs?: number
fileNamePrefix?: string
saveDir?: string | null
}
字段说明:
format- 期望输出格式
- App 三端会固定按
m4a处理 - Web 会按浏览器能力协商
sampleRate- 期望采样率
- 当前白名单:
16000 | 44100
bitRate- 期望码率
- 当前白名单:
32000480006400096000128000160000192000256000320000
maxDurationMs- 最大录音时长,单位毫秒
- 大于
0时,到时自动停止
segmentEnabled- 是否开启分片
segmentDurationMs- 分片时长,单位毫秒
- 仅在
segmentEnabled = true时生效
fileNamePrefix- 输出文件名前缀
- 默认值:
record
saveDir- 自定义输出目录
- 仅 App 三端有意义
- Web 不落本地文件系统,会忽略此字段
返回结构说明
RecorderPermissionResult
type RecorderPermissionResult = {
microphoneAuthorized: RecorderPermissionStatus
canRecord: boolean
canRequestPermission: boolean
needOpenSettings: boolean
requestFlow: "check" | "request"
dialogStatus: "not-applicable" | "shown" | "not-shown" | "unknown"
code:
| 9201101
| 9201102
| 9201103
| 9201104
| 9201105
| 9201106
| 9201107
| 9201108
| 9201109
| 9201110
| 9201111
reasonCode:
| "status-only"
| "already-authorized"
| "already-denied"
| "user-granted"
| "user-denied"
| "settings-granted"
| "settings-denied"
| "dialog-not-shown"
| "config-error"
| "request-failed"
| "unsupported"
reasonMessage: string
}
字段说明:
microphoneAuthorized- 当前权限状态
canRecord- 当前是否具备开始录音的权限条件
canRequestPermission- 当前是否仍适合继续调用系统授权弹窗
needOpenSettings- 当前是否更适合引导用户去系统设置页
requestFlow- 当前结果来自状态查询还是一次显式权限申请
dialogStatus- 本次调用是否真正触发了系统权限弹窗
code- 本次调用的稳定数值码,推荐业务侧优先据此做条件分支
reasonCode- 本次调用的统一归因码,便于业务侧做分支判断
reasonMessage- 对当前结果的可读说明;若这次没弹窗,可直接展示给用户或写日志
RecorderPermissionResult.code 对照表
9201101status-only- 仅做权限状态查询,未发起权限申请
9201102already-authorized- 已有权限,本次申请不会再弹系统框
9201103already-denied- 之前已拒绝,本次申请不会再弹系统框,应引导去设置页
9201104user-granted- 本次权限申请完成并授权成功
9201105user-denied- 本次权限申请完成但用户拒绝
9201106settings-granted- 预留给宿主设置授权链路的成功结果
9201107settings-denied- 预留给宿主设置授权链路的失败结果
9201108dialog-not-shown- 本次权限申请没有触发系统弹窗
9201109config-error- 运行时上下文、权限声明或宿主配置异常
9201110request-failed- 权限申请流程执行失败
9201111unsupported- 当前运行环境不支持该权限申请能力
补充说明:
- 当前版本
requestPermission(callback)不会自动串联设置授权流程 - 业务侧若显式调用
openPermissionSettings(),通常应在回到应用后再次执行checkPermission()
RecorderStartResult
type RecorderStartResult = {
sessionId: string
state: "recording"
format: RecorderFormat
codec: RecorderCodec
pipelineMode: RecorderPipelineMode
sampleRate: RecorderSampleRate
bitRate: RecorderBitRate
maxDurationMs: number
segmentEnabled: boolean
segmentDurationMs: number
startedAt: number
}
字段说明:
sessionId- 本次录音会话 ID
state- 固定为
recording
- 固定为
format- 当前会话实际输出格式
codec- 当前会话实际编码
pipelineMode- 当前固定为
direct
- 当前固定为
sampleRate- 当前采样率
bitRate- 当前码率
maxDurationMs- 当前自动停止配置
segmentEnabled- 当前是否开启分片
segmentDurationMs- 当前分片时长配置
startedAt- 开始时间戳
RecorderSegmentFile
type RecorderSegmentFile = {
sessionId: string
fileName: string
format: RecorderFormat
codec: RecorderCodec
pipelineMode: RecorderPipelineMode
sampleRate: RecorderSampleRate
bitRate: RecorderBitRate
size: number
sizeUnit: "B"
filePath: string
uploadSource: any | null
segmentIndex: number
segmentStartOffsetMs: number
segmentEndOffsetMs: number
durationMs: number
isFirstSegment: boolean
isLastSegment: boolean
createdAt: number
}
字段说明:
sessionId- 所属会话 ID
fileName- 文件名,不含目录
format- 分片格式
codec- 分片编码
pipelineMode- 当前固定为
direct
- 当前固定为
sampleRate- 分片采样率
bitRate- 分片码率
size- 文件大小
sizeUnit- 当前固定
B
- 当前固定
filePath- 业务侧主读取路径
uploadSource- H5 直传对象
- Android / iOS / Harmony 固定为
null - Web 返回可直接用于 H5 上传的
File;当前环境不支持时为null
segmentIndex- 当前片序号,从
1开始
- 当前片序号,从
segmentStartOffsetMs- 当前片在整轮录音中的起始偏移
segmentEndOffsetMs- 当前片在整轮录音中的结束偏移
durationMs- 当前片时长
isFirstSegment- 是否第一片
isLastSegment- 是否最后一片
createdAt- 当前片完成时间戳
RecorderStopResult
type RecorderStopResult = {
sessionId: string
state: "stopped"
format: RecorderFormat
codec: RecorderCodec
pipelineMode: RecorderPipelineMode
sampleRate: RecorderSampleRate
bitRate: RecorderBitRate
startedAt: number
endedAt: number
totalDurationMs: number
segmentEnabled: boolean
segmentDurationMs: number
segmentCount: number
files: RecorderSegmentFile[]
}
字段说明:
sessionId- 会话 ID
state- 固定为
stopped
- 固定为
format- 当前会话最终格式
codec- 当前会话最终编码
pipelineMode- 当前固定
direct
- 当前固定
sampleRate- 当前采样率
bitRate- 当前码率
startedAt- 开始时间戳
endedAt- 结束时间戳
totalDurationMs- 总录音时长
segmentEnabled- 是否开启分片
segmentDurationMs- 分片时长配置
segmentCount- 最终文件数量
files- 文件列表
回调行为说明
onStart
录音真正开始后触发。
触发时机:
- App 三端:底层录音器启动成功后
- Web:
MediaRecorder.onstart触发后
onSegment
分片完成后触发。
说明:
- 仅在开启分片时触发
- 每个已完成分片都会走
onSegment - 最后一片也会和全部文件一起出现在
onStop.files
onStop
整轮录音结束后触发。
说明:
- 所有最终文件都会在
files中返回 - 未开启分片时,
files通常只有一个文件
onError
发生终止性错误时触发。
常见触发场景:
- 参数校验失败
- 权限不足
- 录音器初始化失败
- 录音器启动失败
- 录音器停止失败
- 麦克风被占用或被中断
路径字段说明
当前 RecorderSegmentFile 中保留一个标准路径字段:
filePath- 业务主读取路径
- Android / iOS / Harmony 返回绝对文件路径
- Web 返回
blob:URL
当前实际返回值:
- Android / iOS / Harmony
filePath是绝对文件路径uploadSource = null
- Web
filePath是blob:URLuploadSource应作为 H5uni.uploadFile({ files })中files[0].file传入,files[0].uri继续使用filePath- 调用
destroy()后,旧的blob:URL 会失效;若还要走 H5uni.uploadFile,请在destroy()前完成上传
H5 上传建议:
const file = stopResult.files[0]
// H5 优先用 uploadSource;App 三端继续用 filePath
uni.uploadFile({
url: "https://example.com/upload",
name: "file",
// #ifdef H5
files: [{
name: "file",
uri: file.filePath,
file: file.uploadSource,
}],
// #endif
// #ifndef H5
filePath: file.filePath,
// #endif
success(res) {
console.log("upload success", res)
}
})
平台差异
Android
- 输出格式:
m4a - 编码:
aac - 默认目录:优先
externalCacheDir/q-record,回退cacheDir/q-record
iOS
- 输出格式:
m4a - 编码:
aac - 默认目录:
NSTemporaryDirectory()/q-record
Harmony
- 输出格式:
m4a - 编码:
aac - 默认目录优先级:
cacheDir/q-recordtempDir/q-recordfilesDir/q-record
Web(H5)
- 输出格式:浏览器协商决定
- 可能输出:
m4awebmogg
- 路径字段返回
blob:URL destroy()后 URL 会被释放
错误码说明
当前统一错误码:
9201001- 麦克风权限拒绝
9201002- 启动参数不合法
9201003- 输出格式不支持
9201004- 分片配置不合法
9201005- 录音器已在运行
9201006- 运行上下文缺失
9201007- 底层录音器初始化失败
9201008- 启动失败
9201009- 停止失败
9201010- 文件收尾失败
9201011- 当前平台不支持
9201012- 麦克风被占用或被中断
最佳实践
- 页面初始化时获取实例并注册回调
- 页面卸载时调用
destroy() - 每次开始新录音前,如果你想完全清空旧状态,可以先
destroy()再重新绑定回调 - 使用 Web 时,不要在
destroy()后继续使用旧的blob:URL - 开启分片后,业务应同时处理:
onSegmentonStop.files
目录说明
package.json- 插件元信息、平台支持矩阵和 uni_modules 声明
readme.md- 面向使用者的主说明文档,也是插件市场详情页主文档
AGENTS.md- 面向后续大模型或自动化编码代理的维护说明
changelog.md- 插件变更记录
utssdk/AGENTS.mdutssdk层的代码与维护说明
utssdk/interface.uts- 统一公开类型、字段定义和管理器抽象接口
utssdk/unierror.uts- 统一错误主体、错误码和权限结果构造
utssdk/index.uts- 通用入口与不支持平台的兜底实现
utssdk/app-android/AGENTS.md- Android 平台代码逻辑说明
utssdk/app-ios/AGENTS.md- iOS 平台代码逻辑说明
utssdk/app-harmony/AGENTS.md- Harmony 平台代码逻辑说明
utssdk/web/AGENTS.md- Web 平台代码逻辑说明
当前验证状态
- 截至
2026-04-20:- 已完成代码实现与文档回填
- 已接入
pages/recordPage/recordPage.vueDemo - 已完成静态检查
- 已完成 Android 编译验证
- 已完成 iOS simulator 编译验证
- Harmony 端已完成一轮运行验证
- 尚未完成:
- Android / iOS 真机矩阵验证
- Harmony 更完整的真机矩阵验证
- Web 浏览器矩阵验证
维护文档
- 面向后续模型的维护说明请见:
uni_modules/q-record/AGENTS.md

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