ARTICLE · 1090558
从 H5 到 uni-app:Recorder 前端录音库跨端技术解析
项目地址:https://github.com/xiangyuecn/Recorder在线测试:https://xiangyuecn.github.io/Recorder/
uni-app 支持情况(先行结论):支持,且是官方主推的落地形态之一。有两条路线——RecordApp(推荐) 可一套代码编译到 H5、Android App、iOS App、微信小程序;Recorder + renderjs 仅覆盖 App 与 H5,不支持小程序。详见第九节。

一、它解决的是什么问题
网页录音这件事,表面上看只是 getUserMedia 一行 API,真正落地时却是一连串的坑:
MediaRecorder 原生只产出 webm/ogg,后端和 iOS 往往不认;Recorder 是国产开发者 xiangyuecn 维护的纯前端录音库,核心价值在于:把麦克风采集到的 PCM 流完整暴露给 JS 层,同时提供一套可插拔的编码器与实时处理机制,让"录音"从"生成一个文件"升级为"一条可编程的音频流水线"。
它的定位很清楚——为语音录制而生:单声道、小体积、可实时处理,而不是音乐级录音棚方案。
二、两个入口,别混淆
QuickStart.html、Android/iOS/小程序/uni-app Demo 源码 | |
在线测试页的一个实用技巧:录音时观察灰色区域是否有绿色音量跳动。没有跳动说明没拿到音频数据;如果 mp3 无声而 wav 有声,基本可以判定是内置 lamejs 编码器的问题。这套自检流程能快速区分"权限问题""采集问题"和"编码问题"。
三、整体架构:一条被拆开的三段式流水线
Recorder 的设计可以拆成三层理解:

关键在于 envIn(pcmData, pcmAbsSum) 这个内部方法:它是平台环境层与核心录制层之间的唯一入口。不管是浏览器麦克风、Android WebView、iOS WKWebView 还是微信小程序,最终都只需要把 PCM 数据喂给 envIn,后续的回调、编码、输出逻辑完全一致。这也解释了为什么同一个核心能同时支撑 Recorder(H5)和 RecordApp(App/小程序)两条产品线。
四、五分钟跑通最小闭环
npm install recorder-core --registry=https://registry.npmmirror.com/import Recorder from 'recorder-core'import 'recorder-core/src/engine/mp3'import 'recorder-core/src/engine/mp3-engine'let recfunction recOpen() { rec = Recorder({ type: 'mp3', sampleRate: 16000, // 必须是数字,不要传字符串 bitRate: 16, // kbps,16kbps ≈ 2KB/s onProcess(buffers, powerLevel, duration, sampleRate, newBufferIdx) { // 约每秒 12 次回调,buffers 为 [[Int16]] 二维数组 // 这里可以:绘制波形 / 实时上传 / 实时 ASR / 释放内存 } }) rec.open(() => { /* 已获得麦克风资源 */ }, (msg, isUserNotAllow) => { console.warn(msg, isUserNotAllow) })}function recStop() { rec.stop((blob, duration) => { const url = URL.createObjectURL(blob) // 本地播放 const form = new FormData() form.append('upfile', blob, 'recorder.mp3') // 或直接上传 rec.close() // 用完就关,否则系统一直显示"正在录音" })}两个必须记住的前提:
getUserMedia 直接被拒;open() 和 start() 至少有一个发生在用户手势中(点击/触摸)。这不是建议而是硬性要求——iOS 上 AudioContext 若非用户操作触发,resume() 不会生效,录出来就是一段静音。五、音频数据的生命周期与关键配置
一次录音的数据流向:
麦克风/MediaStream → 采集连接 → buffers 累积 → onProcess 回调(可改写数据) → 编码器(Worker 边录边转码) → takeoffEncodeChunk 实时吐块 / stop() 返回完整 Blobtype | mp3 | |
sampleRate | 16000 | |
bitRate | 16 | |
onProcess | true 开启异步模式 | |
takeoffEncodeChunk | ||
sourceStream | ||
audioTrackSet | getUserMedia,控制 AEC/ANS/AGC |
几个容易忽略的细节:
buffers 里的采样率是浏览器给的原始采样率 rec.srcSampleRate,不一定等于set.sampleRate。要严格对齐目标采样率,需在 onProcess 里自己调 Recorder.SampleData()。stop() 会触发编码。支持 Worker 边录边转码的格式几乎瞬时返回;不支持的格式十几秒录音花 2 秒左右属正常。六、格式怎么选

作者主推 mp3 和 wav,代码也优先照顾这两种。单声道 + 16kbps/16kHz 的组合下,压缩比约 16 倍,语音可懂度完全够用。
七、实时处理:这才是它的分水岭
1. 流式重采样
let prevChunk = nullonProcess(buffers, powerLevel, duration, sampleRate) { const chunk = Recorder.SampleData(buffers, sampleRate, 16000, prevChunk, { frameType: 'mp3' // 自动取 1152 帧,避免 mp3 尾帧填充导致时长变长 }) prevChunk = chunk // chunk.data 即 16kHz 的一维 PCM,可实时上传}SampleData 支持任意采样率互转与流式连续转换。注意 1.3.26070800 之前版本的流式转换存在缺陷会引入杂音(非整数倍转换如 44100↔16000 时尤其明显,蓝牙耳机场景可能非常突出),该版本已修复,做实时重采样务必升级。
2. 接管编码器输出
提供 takeoffEncodeChunk 后,编码器不再内部缓存,每产出一块二进制数据就回调一次,所有 chunkBytes 拼接即完整音频。这有两个好处:内存占用恒定,且天然避免片段拼接的首尾静音。代价是 stop() 返回的 blob 长度为 0,数据需自行保存。注意 wav 不支持该回调(文件头需要最终长度),配置了会在 open 时直接走 fail。
3. 长录音的内存释放
for (let i = clearBufferIdx || 0; i < newBufferIdx; i++) buffers[i] = nullclearBufferIdx = newBufferIdx在 onProcess 里把已处理的 buffer 置 null,即可支撑小时级连续录音。若要彻底避免编码器内部缓冲,可用 type:"unknown" 初始化并自行转码。
4. 看门狗(Watchdog)
移动端录音中断是常态。官方 Demo 给出的做法是起一个 1 秒轮询,若超过 1500ms 没有 onProcess 回调,就判定"录音被中断"并做容错处理:
if (Date.now() - (processTime || startTime) > 1500) { console.error(processTime ? '录音被中断' : '录音未能正常开始')}5. 纯转码场景
rec.mock(pcmData, sampleRate) 可以把任意 PCM 灌进去再 stop() 得到目标格式文件——不碰麦克风,用于音频格式转换、文件合并等后端式操作。
八、跨平台落地要点

Android 后台录音需要专门的保活机制:自 Android 9 起,锁屏或进入后台一段时间后系统可能禁止访问麦克风,导致录音数据全为静音——demo_android 中有专门章节讲解。
九、uni-app 集成:两条路线怎么选
结论先行:支持。 官方提供了 demo_UniApp 示例项目,封装好的组件可在 DCloud 插件市场直接下载。但 uni-app 里有两条完全不同的技术路线,选错会在编译到小程序时直接翻车。
路线一:RecordApp(官方推荐)
RecordApp 是 Recorder 的"跨端外壳",内部复用同一个录音核心(共享 envIn/envStart 机制),但把平台差异收敛到了 App 层。
适用场景:一套 uni-app 代码要同时发 App 和小程序,或者明确需要小程序端录音。这是唯一能覆盖小程序的路线。
路线二:Recorder + renderjs(有限支持)
uni-app 的 renderjs 运行在视图层 WebView 中,因此可以在 renderjs 里加载 Recorder 完成录音:
官方明确标注为 [不推荐]。它适合只需覆盖 App 端、想复用现成 H5 录音代码的场景;一旦后续要加小程序端,录音模块需要推倒重来用 RecordApp 重写。
App 端的权限前置处理(两条路线都要做)
在 App 平台,必须在调用 rec.open()之前于原生层拿到录音权限:
AndroidManifest.xml 声明录音权限(X5 内核还需 CAMERA);iOS 在 Info.plist 声明 NSMicrophoneUsageDescription。选型决策
十、插件生态
插件默认不合并进主包,按需引入:
waveview.js | ||
wavesurfer.view.js | ||
frequency.histogram.view.jslib.fft.js | ||
buffer_stream.player.js | ||
asr.aliyun.short.js | ||
sonic.js | ||
dtmf.decode.jsdtmf.encode.js | ||
create-audio.nmn2pcm.js |
ASR 插件值得一提:它直连阿里云 WebSocket,语音数据不经过自己的服务器,后端只需提供一个 Token 生成接口;底层用"一句话识别"(便宜)配合插件自带的拼接逻辑来突破 60 秒限制。
十一、生产环境避坑清单
open/start 至少一个在手势内调用,否则 iOS 静音。open 直接 fail;file:// 也不行。navigator.wakeLock 阻止自动锁屏兜底。window.top 加载 Recorder,子页面用 top.Recorder。ConnectEnableWorklet=false),因为移动端 1 秒 375 次回调可能造成丢帧;采集优先走 MediaRecorder.WebM.PCM(ConnectEnableWebM 默认开启),音质明显更好。Recorder.TrafficImgUrl 置空。Recorder + renderjs 编译到小程序端无法录音,且官方标注不推荐;要覆盖小程序请直接用 RecordApp(见第九节)。十二、选型建议与边界
适合:网页语音留言、语音输入、在线客服录音、实时对讲、ASR 转写、语音验证码、音频可视化教学 Demo——尤其是需要在微信/QQ 内置浏览器、国产 App WebView 和 uni-app 多端项目里跑的场景,Recorder 在这块的适配深度是同类库中少见的。
不适合:立体声录制(它只支持单声道,且明确表示"未找到双声道语音录制的意义")、音乐级高保真录音、需要在 iOS 11–14.2 的非 Safari 浏览器里工作的项目。
一句话总结:如果你的需求是"在浏览器里可靠地录一段能上传、能播放、能转文字的语音",Recorder 基本是国内生态下最省心的选择;仓库里 30 多个可运行的 Demo 片段(实时上传、多路混音、变速变调、DTMF、FFT 频谱等)几乎覆盖了二次开发会踩到的所有点,动手前先跑一遍 QuickStart.html 是最高效的入门路径。