首页/新闻资讯/正文详情

uni-app x 录音管理实战:uni.getRecorderManager() 全平台 API 详解与源码剖析

发布时间:2026/9/21 2:06:44 来源:云帆数科 栏目:资讯中心
uni-app x 录音管理实战:uni.getRecorderManager() 全平台 API 详解与源码剖析
示例工程前端移动开发跨平台【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址https://gitcode.com/gh_mirrors/un/uni-app点击查看免费下载本篇技术指南以 uni-app / uni-app x 官方 API 文档 get-recorder-manager.md 为骨架结合开源仓库中 uni-recorder 模块的 Android、iOS、HarmonyOS 三端真实实现系统讲解uni.getRecorderManager()录音管理的返回值结构、RecorderManager全部方法与事件、start()参数语义、采样率与码率匹配关系、错误码含义并给出可直接运行的完整 uvue 示例。读完本文你将能够在 uni-app x 的 AppAndroid/iOS/HarmonyOS与微信小程序端独立实现开始—暂停—继续—停止—播放的完整录音功能链路并能根据错误码定位权限、参数与系统打断类问题。一、API 概览与平台兼容性uni.getRecorderManager()用于获取全局唯一的录音管理对象RecorderManager它是 uni-app x 中媒体media模块的核心录音 API适用于语音消息、语音笔记、语音输入、会议记录等需要录制并回放音频的场景。在仓库源码中该 API 的协议名称定义于 protocol.utsexport const API_GET_RECORDER_MANAGER getRecorderManager接口签名定义于 interface.utsgetRecorderManager() : RecorderManager export type GetRecorderManager () RecorderManager;平台兼容性| Web | 微信小程序 | Android | iOS | HarmonyOS | | :- | :- | :- | :- | :- | | x | 4.41 | 4.61 | 4.61 | 4.61 |其中 x 表示该平台不支持。Web 平台不支持此 API需要通过插件市场中的三方录音插件拉齐能力微信小程序自 4.41 版本、App 三端Android/iOS/HarmonyOS自 4.61 版本HarmonyOS 侧 uniVer 为 4.31 起开始支持。需要说明的是uni-app x 环境中 App 平台的能力由 UTS 插件实现文档兼容性表中的unixVeruni-app x 编译器/HBuilderX 版本与unixUtsPluginUTS 插件版本是实际生效的版本号。注意该 API 不支持 Web请将示例运行到 App 平台或微信小程序体验。Web 端如需录音能力可自行在插件市场检索getRecorderManager相关插件。二、返回值RecorderManager 及其单例语义| 类型 | | :- | | RecorderManager |RecorderManager是录音管理器的接口类型负责录音生命周期控制与事件分发。从源码实现看各平台均采用单例模式Android 端app-android/index.utsrecordManager变量为空时new RecorderManagerImpl()否则复用HarmonyOS 端app-harmony/index.uts同样以模块级变量缓存实例并通过defineSyncApi注册为同步 APIiOS 端app-ios/index.uts直接返回UniAudioRecorderManager.shared共享实例。这意味着在应用生命周期内多次调用uni.getRecorderManager()拿到的是同一个录音管理器事件监听与录音状态在全局共享使用时应注意在页面卸载时注销监听并停止录音详见示例代码。三、RecorderManager 方法全览RecorderManager接口在 interface.uts 中定义方法可分为控制类与事件类两组。3.1 控制类方法start(options : RecorderManagerStartOptions) : void —— 开始录音开始录音是使用频率最高、参数最复杂的方法其options参数类型为RecorderManagerStartOptions。兼容性微信小程序 4.41、Android 4.61、iOS 4.61、HarmonyOS 4.61、Web 不支持。pause() : void —— 暂停录音暂停录音。App-Android 平台仅在 Android 7.0 及以后版本支持低版本 Android 上调用无效。兼容性微信小程序 4.41、Android系统 7.04.61、iOS 4.61、HarmonyOS 4.61。resume() : void —— 继续录音恢复被暂停的录音同样受 Android 7.0 系统版本限制。兼容性微信小程序 4.41、Android系统 7.04.61、iOS 4.61、HarmonyOS 4.61。stop() : void —— 停止录音停止录音并触发onStop回调返回录音文件临时路径。兼容性微信小程序 4.41、Android 4.61、iOS 4.61、HarmonyOS 4.61。3.2 事件类方法事件方法遵循onXxx注册监听、offXxx取消监听的成对模式。注意offXxx系列在App 端Android/iOS/HarmonyOS需要 5.0 版本才支持微信小程序端 4.41 即支持Web 均不支持。| 方法 | 说明 | 兼容性on / off | | :- | :- | :- | | onStart / offStart | 录音开始事件 / 取消监听 | 4.41 / 微信4.61 / Android·iOS·HarmonyOSoff 系列 App 需 5.0 | | onPause / offPause | 录音暂停事件Android 需 7.0 | 同上off 系列 App 需 5.0 | | onResume / offResume | 录音继续事件Android 需 7.0 | 同上off 系列 App 需 5.0 | | onStop / offStop | 录音停止事件回调文件地址 | 同上off 系列 App 需 5.0 | | onFrameRecorded / offFrameRecorded | 已录制完指定帧大小的分片文件回调仅微信小程序支持且需设置 frameSize仅 mp3 | 微信 4.41App 三端均 x | | onError / offError | 录音错误事件回调错误信息 | 同上off 系列 App 需 5.0 | | onInterruptionBegin / offInterruptionBegin | 录音因受系统占用而被中断开始事件 | 微信 4.41App 4.61off 系列 App 需 5.0 | | onInterruptionEnd / offInterruptionEnd | 录音中断结束事件 | 微信 4.41App 4.61off 系列 App 需 5.0 |四、start 参数详解RecorderManagerStartOptionsstart()的options参数是录音配置的核心下表完整列出各字段兼容性列中的版本号与x含义同上文| 名称 | 类型 | 必填 | 默认值 | 兼容性 | 描述 | | :- | :- | :- | :- | :-: | :- | | duration | number | 否 | 60000 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 指定录音时长单位 ms。传入合法 duration 后到达时长会自动停止录音。默认值 600001 分钟微信小程序最大值 60000010 分钟App 平台没有最大值限制| | sampleRate | number | 否 | 平台相关 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 采样率有效值 8000 / 16000 / 44100。Android 平台默认 8000iOS 平台默认 44100| | numberOfChannels | number | 否 | 2 | Web: x微信 4.41Android 4.61**iOS: x**HarmonyOS 4.61 | 录音通道数有效值 1 / 2。注意 iOS 平台不支持此参数配置 | | encodeBitRate | number | 否 | 48000 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 编码码率有效值范围与采样率强相关见下文表格 | | format | string | 否 | aac | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 音频格式合法值见下表 | | frameSize | number | 否 | — | Web: x微信 4.41Android/iOS/HarmonyOS 均 x| 指定帧大小单位 KB。传入 frameSize 后每录制指定帧大小的内容会回调录制的文件内容触发 onFrameRecorded不指定则不会回调。暂仅支持 mp3 格式且仅微信小程序端有效|format 合法值| 合法值 | 兼容性 | 描述 | | :- | :-: | :- | | aac | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | aac 格式默认 | | mp3 | Web: x微信 4.41Android 4.61iOS: xHarmonyOS 4.61 | mp3 格式 | | pcm | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | pcm 格式 | | wav | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | wav 格式 | | m4a | Web: x微信 4.41Android: xiOS: xHarmonyOS 4.61 | m4a 格式HarmonyOS 特有 |源码中的参数默认值处理从 Android 平台实现app-android/index.uts可以看到参数默认值的真实落地逻辑与文档描述一致innerStart(options : RecorderManagerStartOptions) : void { if (options null) options {} as RecorderManagerStartOptions if (options.format null) options.format DEFAULT_FORMAT // aac if (options.duration null) options.duration DEFAULT_DURATION // 1000 * 60 if (options.duration! 0) options.duration DEFAULT_DURATION // 非法时长回退默认值 if (options.sampleRate null) options.sampleRate 8000 if (options.numberOfChannels null) options.numberOfChannels 2 if (options.encodeBitRate null) options.encodeBitRate 48000 this.manager.setAudioParams(options.sampleRate!.toInt(), options.numberOfChannels!.toInt(), options.encodeBitRate!.toInt()) this.manager.setSaveToFile(true) this.manager.setMaxRecordingDuration(options.duration!.toLong()) this.manager.startRecording(format, createFile(options.format! as string)) }其中format会被映射为平台录音器的RecordingFormat枚举AAC/MP3/PCM/WAV未知格式回退到 AAC录音文件输出路径由 app-android/utils.uts 的createFile()生成存放在应用缓存目录的uni-recorder/子目录下文件名以时间戳命名、扩展名跟随所选格式export function createFile( format : string) : string { let cacheDir new File(UTSAndroid.getAppCachePath(), uni-recorder/) if (!cacheDir.exists()) { cacheDir.mkdirs() } const fileName ${Date.now()}.${format.toLocaleLowerCase()} return cacheDir.path/fileName }从该实现可以推断录音停止后onStop回调中的tempFilePath即为上述缓存目录下的真实文件路径开发者可将其用于后续播放uni.createInnerAudioContext()或上传。五、采样率与编码码率匹配关系重要sampleRate与encodeBitRate必须匹配否则将触发错误码1107603采样率和编码码率不匹配。原文档给出了如下权威匹配表。Android、iOS、微信小程序| 采样率 | 编码码率 | | :- | :- | | 8000 | 16000 - 48000 | | 11025 | 16000 - 48000 | | 12000 | 24000 - 64000 | | 16000 | 24000 - 96000 | | 22050 | 32000 - 128000 | | 24000 | 32000 - 128000 | | 32000 | 48000 - 192000 | | 44100 | 64000 - 320000 | | 48000 | 64000 - 320000 |HarmonyOS 平台特殊规则aac 编码格式支持码率范围[32000 - 500000]mp3 编码格式支持码率范围[8000, 16000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000]采样率使用 16K 以下时对应码率范围为[8000 - 64000]采样率使用 16K ~ 32K 时对应码率范围为[8000 - 160000]采样率使用 32K 以上时对应码率范围为[32000 - 320000]wav 编码格式时固定补丁码率 8000、采样率 64000、通道数 1实战建议若不确定码率如何取值可直接不传encodeBitRate与sampleRate让其使用平台默认值Android 8000 / iOS 44100码率 48000即可规避匹配类错误。六、事件回调的结果对象与错误码6.1 onStop 回调RecorderManagerOnStopResult录音停止事件会回调文件地址其结果对象定义于 interface.uts| 名称 | 类型 | 必备 | 兼容性 | 描述 | | :- | :- | :- | :-: | :- | | tempFilePath | string | 是 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 录音文件的临时路径 |Android 平台实现中该对象由RecorderManagerOnStopResultCallback承载tempFilePath直接取录音器输出路径app-android/index.utsoverride onRecordingStopped() { this.stopCallback?.(new RecorderManagerOnStopResultCallback(this.manager.getFilePath()!)) }6.2 onError 回调IRecorderManagerFail 与错误码录音错误事件回调IRecorderManagerFail对象其字段如下| 名称 | 类型 | 必备 | 描述 | | :- | :- | :- | :- | | errCode | number | 是 | 错误码 | | errSubject | string | 是 | 统一错误主题模块名称源码中固定为uni-recorder见 unierror.uts | | data | any | 否 | 错误信息中包含的数据 | | cause | Error | 否 | 源错误信息可以包含多个错误详见 统一错误规范 | | errMsg | string | 是 | 错误信息 |errCode 错误码全集| 合法值 | 兼容性 | 描述 | | :- | :-: | :- | | 1107601 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 没有麦克风权限 | | 1107602 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 不支持该采样率 | | 1107603 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 采样率和编码码率不匹配 | | 1107604 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 启动失败 | | 1107605 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 不支持该音频格式 | | 1107606 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 其他错误 | | 1107607 | Web: x微信 4.41Android/iOS/HarmonyOS 4.61 | 被打断 | | 1107608 | Web: x微信 4.41Android 4.81iOS: xHarmonyOS: x | 正在录音中请稍后执行此操作 |以上错误码与英文错误消息的映射定义于 unierror.utsAndroid 平台实现会在收到底层录音器错误时将未知错误码统一映射为1107606其他错误后回调给业务层app-android/index.uts。七、平台底层实现与权限机制7.1 Android 端运行时权限 AudioRecorderAndroid 实现app-android/index.uts在start()时先请求录音运行时权限授权成功后才真正开始录音用户拒绝授权则直接回调错误码1107601没有麦克风权限override start(options : RecorderManagerStartOptions) : void { UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, [android.permission.RECORD_AUDIO], (allRight : boolean, grantedList : Arraystring) { this.innerStart(options) }, (allRight : boolean, grantedList : Arraystring) { this.onRecordingError(1107601, ) }) }因此 Android 端无需开发者额外处理权限请求拒绝授权时通过onError即可感知。7.2 HarmonyOS 端AVRecorder 条件编译HarmonyOS 实现app-harmony/index.uts基于系统AVRecorder能力先校验format是否在平台支持格式集合内不支持则回调错误码1107605事件注册通过setRecordStateCallback统一管理并以defineSyncApi(API_GET_RECORDER_MANAGER, ...)注册同步 API。底层音频参数校验采样率/码率范围、wav 固定参数等由 AVRecorder.uts 与 utils.uts 实现对应原文档中 HarmonyOS 专属的码率规则。7.3 iOS 端单例共享管理器iOS 实现app-ios/index.uts直接返回UniAudioRecorderManager.shared具体录音逻辑封装在 UniAudioRecorderManager.swift 中。7.4 权限配置重要在 HarmonyOS 平台使用录音功能需要在manifest.json的 HarmonyOS 配置中声明ohos.permission.MICROPHONE权限Android 端的android.permission.RECORD_AUDIO已由上述requestSystemPermission运行时请求逻辑处理同时建议在 AndroidManifest 中声明该权限以便发布审核。iOS 端需在Info.plist中配置麦克风用途描述NSMicrophoneUsageDescription。各平台权限与原生配置的完整说明可参考 原生配置文件文档 与 应用权限说明。八、完整可运行示例uvue以下示例为 uni-app x 官方 hello 项目中的录音页完整代码与最新 HBuilderX Alpha 版同步覆盖了录音的完整生命周期格式选择、注册/注销事件、开始/暂停/继续/停止、以及通过uni.createInnerAudioContext()播放录音。示例使用条件编译区分平台特性HarmonyOS 平台额外提供m4a格式选项非 HarmonyOS 平台提供pcm选项。template page-head :titletitle/page-head view classpage-body-time text classtime-big{{formatedRecordTime}}/text !-- text classtime-big{{recordTimeInterval}}/text -- /view scroll-view styleflex: 1; view button classbtnstyle sizedefault clickregisterOnStart注册onStart/button button classbtnstyle sizedefault clickregisteronStop注册onStop/button button classbtnstyle sizedefault idbtn-error clickregisteronError注册onError/button button classbtnstyle sizedefault clickregisteronPause注册onPause/button button classbtnstyle sizedefault clickregisteronResume注册onResume/button button classbtnstyle sizedefault clickregisteronInterruptionBegin注册onInterruptionBegin/button button classbtnstyle sizedefault clickregisteronInterruptionEnd注册onInterruptionEnd/button view classuni-list text stylemargin-bottom: 10px 请选择录音格式/text radio-group classuni-row changeradioChange styleflex-wrap: wrap view classuni-list-cell stylemargin-right: 15px v-for(item, index) in items :keyitem.value radio :valueitem.value :checkedindex current / text{{ item.name }}/text /view /radio-group /view button classbtnstyle sizedefault :disableddisableStartBtn idbtn-startRecord clickstartRecord()开始录制/button button classbtnstyle sizedefault :disableddisablePauseBtn clickpauseRecord暂停录制/button button classbtnstyle sizedefault :disableddisableResumeBtn clickresumeRecord继续录制/button button classbtnstyle sizedefault idbtn-stopRecord clickstopRecord停止录制/button button classbtnstyle sizedefault idbtn-startPlay clickplayVoice开始播放/button button classbtnstyle sizedefault idbtn-stopPlay clickstopVoice停止播放/button /view bottom-safe-area / /scroll-view /template script setup languts export type ItemType { value : string, name : string } type DataType { registerError: boolean, recording: boolean, playing: boolean } const disableStartBtn ref(false) const disableResumeBtn ref(false) const disablePauseBtn ref(false) const title ref(start/stopRecord、play/stopVoice) const hasRecord ref(false) //是否有了一个 // 使用reactive避免ref数据在自动化测试中无法访问 const data reactive({ registerError: false, recording: false, //录音中 playing: false //播放中 } as DataType) const playTimeInterval ref(0) const recordTimeInterval ref(0) const tempFilePath ref() const recordTime ref(0) const current ref(0) const playTime ref(0) const formatedRecordTime ref(00:00:00) //录音的总时间 const formatedPlayTime ref(00:00:00)//播放录音的当前时间, const recorderManager ref(null as RecorderManager | null) const music ref(null as InnerAudioContext | null) const items ref([ { value: aac, name: aac }, { value: mp3, name: mp3 }, { value: wav, name: wav }, // #ifdef APP-HARMONY { value: m4a, name: m4a }, // #endif // #ifndef APP-HARMONY { value: pcm, name: pcm } // #endif ] as ItemType[]) const radioChange (e: UniRadioGroupChangeEvent) { for (let i 0; i items.value.length; i) { if (items.value[i].value e.detail.value) { current.value i; break; } } } const formatTime (time: number): string { if (typeof time ! number || time 0) { return time.toString() } var hour parseInt((time / 3600).toString()) time time % 3600 var minute parseInt((time / 60).toString()) time time % 60 var second time return [hour, minute, second].map((n: number) { let str n.toString(); return str.length 1 ? str : 0 str; }).join(:); } const registerOnStart () { uni.showToast({ title: already registerOnStart }) recorderManager.value!.onStart(() { console.log(recorder on start); recordTime.value 0 data.recording true; recordTimeInterval.value setInterval(() { recordTime.value 1; formatedRecordTime.value formatTime(recordTime.value); }, 1000) }); } const registeronStop () { uni.showToast({ title: already registeronStop }) recorderManager.value!.onStop((res) { console.log(on stop, res); music.value!.src res.tempFilePath clearInterval(recordTimeInterval.value) hasRecord.value true; data.recording false; }); } const registeronError () { uni.showToast({ title: already registeronError }) data.registerError true recorderManager.value!.onError((res) { console.log(recorder onError, JSON.stringify(res)); }); } const registeronPause () { uni.showToast({ title: already registeronPause }) recorderManager.value?.onPause(() { console.log(recorder onPause); }) } const registeronResume () { uni.showToast({ title: already registeronStop }) recorderManager.value?.onResume(() { console.log(recorder onResume); }) } const registeronOnFrameRecorded () { uni.showToast({ title: already registeronOnFrameRecorded }) recorderManager.value?.onFrameRecorded((res) { console.log(recorder onFrameRecorded----, res); }) } const registeronInterruptionBegin () { uni.showToast({ title: already registeronInterruptionBegin }) recorderManager.value?.onInterruptionBegin(() { console.log(recorder onInterruptionBegin); }) } const registeronInterruptionEnd () { uni.showToast({ title: already registeronInterruptionEnd }) recorderManager.value?.onInterruptionEnd(() { console.log(recorder registeronInterruptionEnd); }) } const pauseRecord () { console.log(recorder pause); recorderManager.value?.pause() if (data.recording) { disableStartBtn.value false disablePauseBtn.value true disableResumeBtn.value false } clearInterval(recordTimeInterval.value) } const resumeRecord () { console.log(recorder resume , recorderManager.value); recorderManager.value?.resume() recorderManager.value?.onResume(() { console.log(recorder onResume); }) if (data.recording) { disableStartBtn.value false disablePauseBtn.value false disableResumeBtn.value true recordTimeInterval.value setInterval(() { recordTime.value 1; formatedRecordTime.value formatTime(recordTime.value); }, 1000) } } const startRecord () { //开始录音 if (data.recording) { uni.showToast({ title: disablePauseBtn.value ? 当前是录音暂停状态 : 当前正在录音 }) return } console.log(startRecord, items.value[current.value].value) // TODO ios 在没有请求过权限之前无法得知是否有相关权限这种状态下需要直接调用录音但没有状态或回调判断用户拒绝 recorderManager.value?.start({ format: items.value[current.value].value, sampleRate: 8000, numberOfChannels: 2, encodeBitRate: 48000, frameSize: 2 }); } const stopRecord () { //停止录音 recorderManager.value?.stop(); disableStartBtn.value false disablePauseBtn.value false disableResumeBtn.value false } const playVoice () { if (data.recording) { uni.showToast({ title: 当前录音还未结束 }) return } console.log(play voice); if (data.playing) { return } data.playing true; playTimeInterval.value setInterval(() { if (playTime.value recordTime.value) { playTime.value 1; } formatedRecordTime.value formatTime(playTime.value); }, 1000) music.value?.play(); } const stopVoice () { if (data.recording) { uni.showToast({ title: 当前录音还未结束 }) return } clearInterval(playTimeInterval.value) data.playing false; formatedRecordTime.value formatTime(0); playTime.value 0; music.value?.stop(); } const end () { music.value?.stop(); music.value?.destroy(); // #ifdef APP recorderManager.value?.offError() recorderManager.value?.offFrameRecorded() recorderManager.value?.offInterruptionBegin() recorderManager.value?.offInterruptionEnd() recorderManager.value?.offPause() recorderManager.value?.offResume() recorderManager.value?.offStart() recorderManager.value?.offStop() // #endif recorderManager.value?.stop(); clearInterval(recordTimeInterval.value) clearInterval(playTimeInterval.value); data.recording false data.playing false hasRecord.value false; playTime.value 0 recordTime.value 0; formatedRecordTime.value 00:00:00 formatedRecordTime.value 00:00:00; } const clear () { end(); } onUnload(() { end(); }) onLoad(() { music.value uni.createInnerAudioContext(); music.value!.onEnded(() { clearInterval(playTimeInterval.value) var playTimeValue 0 console.log(play voice finished) data.playing false; formatedPlayTime.value formatTime(playTimeValue); playTime.value playTimeValue; }); recorderManager.value uni.getRecorderManager(); recorderManager.value!.onStart(() { console.log(recorder onStart); disableStartBtn.value true disablePauseBtn.value false disableResumeBtn.value false data.recording true; recordTime.value 0 recordTimeInterval.value setInterval(() { recordTime.value 1; formatedRecordTime.value formatTime(recordTime.value); }, 1000) }); recorderManager.value!.onStop((res) { console.log(on stop, res.tempFilePath); disablePauseBtn.value false disableResumeBtn.value false disableStartBtn.value false music.value!.src res.tempFilePath clearInterval(recordTimeInterval.value) hasRecord.value true; data.recording false; }); recorderManager.value!.onError((res) { console.log(recorder onError, JSON.stringify(res)); disablePauseBtn.value false disableResumeBtn.value false disableStartBtn.value false data.registerError true uni.showToast({ title: JSON.stringify(res) }) }); }) defineExpose({ data }) /script style .page-body-time { display: flex; flex-direction: column; align-items: center; } .time-big { font-size: 30px; margin: 10px; } .btnstyle { margin-left: 30px; margin-right: 30px; margin-top: 10px; } .uni-list { border-bottom: 0px; background-color: transparent; margin-left: 30px; margin-right: 30px; margin-top: 10px; margin-bottom: 10px; } /style该示例的源码版本位于本仓库 src/pages/API/get-recorder-manager/get-recorder-manager.uvue可在 hello uni-app x 工程的 API 页面中直接体验。注意示例中frameSize: 2仅在微信小程序端配合 mp3 生效App 端会忽略该字段。示例中的关键编码要点事件注册时机onLoad中一次性注册onStart/onStop/onError页面卸载onUnload时通过end()统一offXxx注销监听并停止录音避免单例管理器上的监听泄漏录制中状态机通过disableStartBtn/disablePauseBtn/disableResumeBtn三个按钮禁用状态维护录制中 / 暂停中 / 空闲三态流转防止在录音中重复start()会触发错误码 1107608录音计时与回放计时setInterval每秒累计recordTime并格式化为HH:MM:SS停止录音后通过music.value!.src res.tempFilePath将临时文件交给uni.createInnerAudioContext()播放iOS 权限提示源码注释明确指出iOS 在未请求过权限之前无法得知是否已有麦克风权限需要直接调用录音且没有回调可判断用户拒绝因此 iOS 端若需弹权限引导建议结合其他权限 API 自行处理。九、常见问题排查指引| 现象 | 可能原因 | 处理建议 | | :- | :- | :- | | onError 返回 1107601 | 用户未授予麦克风权限Android 拒绝授权 / iOS 未请求 / HarmonyOS 未声明 ohos.permission.MICROPHONE | Android 端重试 start 会重新拉起权限弹窗HarmonyOS 检查 manifest 权限声明iOS 引导用户到设置开启 | | onError 返回 1107602 | 传入了非 8000/16000/44100 的采样率 | 检查 sampleRate 取值 | | onError 返回 1107603 | 采样率与编码码率超出匹配表范围 | 按第五节表格调整 encodeBitRate | | onError 返回 1107604 | 录音启动失败麦克风被占用等 | 检查是否有其他应用占用麦克风稍后重试 | | onError 返回 1107605 | format 传入了平台不支持的格式如 iOS 传 mp3/m4a、App 端传 m4a 但非 HarmonyOS | 结合第四节 format 兼容性表选择格式 | | onError 返回 1107607 | 录音被系统打断如来电、其他应用占用录音 | 监听 onInterruptionBegin / onInterruptionEnd 做业务提示与恢复 | | onError 返回 1107608 | 录音尚未停止时再次调用了 start | 用状态标记阻止重复 start | | 录音文件无法播放 | format 与播放器支持不匹配或未等 onStop 回调就读取文件 | 使用 onStop 回调中的 tempFilePath并用兼容的播放器如 createInnerAudioContext播放 |十、更多资料录音管理相关已知问题与反馈入口参见 相关 Buguni-appVue 语法版本的录音文档可参考 uni-app 录音管理文档各小程序平台微信、支付宝、百度、抖音、飞书、钉钉、QQ、快手、京东、华为快应用、360的 RecorderManager 实现细节以各平台官方小程序文档为准模块实现源码src/uni_modules/uni-recorder、示例页面 get-recorder-manager.uvue、协议声明 protocol.uts、错误定义 unierror.uts。赞分享示例工程前端移动开发跨平台【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址https://gitcode.com/gh_mirrors/un/uni-app点击查看免费下载相关推荐uni-app 录音管理深入解析uni.getRecorderManager 与 uni-recorder UTS 插件源码剖析uni app 录音管理深入解析uni.getRecorderManager 与 uni recorder UTS 插件源码剖析 uni recorder 是示例工程前端移动开发跨平台uni-app x 图片预览插件 uni-previewImage 完全指南API 详解、源码剖析与 UTS 插件实践uni app x 图片预览插件 uni previewImage 完全指南API 详解、源码剖析与 UTS 插件实践 uni previewImage 是示例工程前端移动开发跨平台uni-app x 视频保存到相册指南uni.saveVideoToPhotosAlbum 全平台实现与源码剖析uni app x 视频保存到相册指南uni.saveVideoToPhotosAlbum 全平台实现与源码剖析 uni.saveVideoToPhotosA示例工程前端移动开发跨平台创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

NemoClaw 安全预警与审计溯源:基于 GitHub Security Advisory 的 npm 漏洞提前预警机制
NemoClaw 安全预警与审计溯源:基于 GitHub Security Advisory 的 npm 漏洞提前预警机制

NemoClaw 安全预警与审计溯源:基于 GitHub Security Advisory 的 npm 漏洞提前预警机制 【免费下载链接】NemoClaw Run agents like Hermes, LangChain Deep Agents, and OpenClaw more securely inside NVIDIA OpenShell with managed inference 项目地址: https… · 2026/9/21 2:06:44

工艺会评估实战指南:从新手到产线守门人的五维动态思维
工艺会评估实战指南:从新手到产线守门人的五维动态思维

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/21 2:06:44

GTweak 社区支持渠道完整指南:GitHub Issue、Telegram 与作者联系方式
GTweak 社区支持渠道完整指南:GitHub Issue、Telegram 与作者联系方式

GTweak 社区支持渠道完整指南:GitHub Issue、Telegram 与作者联系方式 【免费下载链接】GTweak Portable Tool for an Ideal Windows Setup 项目地址: https://gitcode.com/GitHub_Trending/gt/GTweak GTweak 是一款轻量便携的 Windows 系统优化与自定义工具… · 2026/9/21 2:06:44

react-admin 实时订阅实战:深入掌握 `useSubscribeToRecord` 单记录事件订阅 Hook
react-admin 实时订阅实战:深入掌握 `useSubscribeToRecord` 单记录事件订阅 Hook

react-admin 实时订阅实战:深入掌握 useSubscribeToRecord 单记录事件订阅 Hook 【免费下载链接】react-admin A frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design 项目地址: https:/… · 2026/9/21 3:28:00

在 Vue 3 应用中接入 json-render DevTools:@json-render/devtools-vue 完整接入与源码解析
在 Vue 3 应用中接入 json-render DevTools:@json-render/devtools-vue 完整接入与源码解析

在 Vue 3 应用中接入 json-render DevTools:json-render/devtools-vue 完整接入与源码解析 【免费下载链接】json-render The Generative UI framework 项目地址: https://gitcode.com/GitHub_Trending/js/json-render json-render/devtools-vue 是 json-ren… · 2026/9/21 3:28:00

Etherpad 自更新子系统 Tier 3 深度解析:带宽限窗口的自动升级(Auto-Update with Grace Window)
Etherpad 自更新子系统 Tier 3 深度解析:带宽限窗口的自动升级(Auto-Update with Grace Window)

后端协同办公WebSocket前端富文本 【免费下载链接】etherpad Etherpad: A modern really-real-time collaborative document editor. 项目地址: https://gitcode.com/gh_mirrors/et/etherpad 点击查看 免费下载 Etherpad 内置的"自更新子系统"&#xff0… · 2026/9/21 3:28:00

lark-cli apps +plugin-list 命令完全指南:妙搭应用插件声明与安装状态核验
lark-cli apps +plugin-list 命令完全指南:妙搭应用插件声明与安装状态核验

CLIAI 技能 【免费下载链接】cli The official Lark/飞书 CLI tool, maintained by the larksuite team — built for humans and AI Agents. Covers core business domains including Messenger, Docs, Base, Sheets, Calendar, Mail, Tasks, Meetings, and more, with 200 co… · 2026/9/21 3:28:00

Gradle 属性命名规范 ADR-0010:org.gradle 前缀体系下的 public/internal 与特性稳定性契约
Gradle 属性命名规范 ADR-0010:org.gradle 前缀体系下的 public/internal 与特性稳定性契约

构建工具开发工具 【免费下载链接】gradle Adaptable, fast automation for all 项目地址: https://gitcode.com/gh_mirrors/gr/gradle 点击查看 免费下载 本文是 Gradle 仓库 architecture/standards/0010-gradle-properties-naming.md 这份架构决策记录&#xff… · 2026/9/21 3:28:00

RxJS 4 `partition` 操作符深度解析:按谓词将一条 Observable 流一分为二
RxJS 4 `partition` 操作符深度解析:按谓词将一条 Observable 流一分为二

后端 【免费下载链接】RxJS The Reactive Extensions for JavaScript 项目地址: https://gitcode.com/gh_mirrors/rxj/RxJS 点击查看 免费下载 本文基于 RxJS v4(The Reactive Extensions for JavaScript)官方 API 文档与仓库源码&#xff0… · 2026/9/21 3:27:00

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化
Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡… · 2026/9/21 0:02:39

Word表格编号全攻略:从列表编号到题注交叉引用
Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技… · 2026/9/21 0:02:39

从第一个站到第二个站:独立开发者的静态网站选型与落地实践
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&… · 2026/9/20 0:00:41

Claude Code 按智谱AI指南装完,ANTHROPIC_BASE_URL 改走 TaoToken 兼容通道行不行
Claude Code 按智谱AI指南装完,ANTHROPIC_BASE_URL 改走 TaoToken 兼容通道行不行

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/21 0:00:18

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程
agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and … · 2026/9/21 0:00:18

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,… · 2026/9/21 0:00:18

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码