录制数据获取:从事件监听到底层处理
各位同学,今天我们来聊聊录制数据的获取。说实话,这块内容在WebRTC开发里特别容易被忽视——很多人觉得录完了就完事了,但数据怎么拿、怎么处理,才是真正考验功力的地方。
我个人习惯把数据获取分成三个层次:监听事件、拿到Blob、再转成更底层的格式。咱们一层层往下走。
监听 dataavailable 事件
MediaRecorder 的核心机制,说白了就是通过事件通知你「数据准备好了」。这个事件叫 dataavailable,它会在两种情况下触发:
- 你主动调用了
stop()方法 - 你设置了
timeslice参数,按时间切片触发
我刚开始做录制时,以为只有停止录制才能拿到数据。后来踩了个坑——录了半小时的视频,最后一次性拿到一个巨大的Blob,浏览器直接卡死了。嗯,这就是没理解 timeslice 的后果。
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9'
});
// 每3秒触发一次 dataavailable
mediaRecorder.start(3000);
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
console.log('拿到数据块,大小:', event.data.size);
chunks.push(event.data);
}
};
获取 Blob 数据
每次 dataavailable 事件传过来的 event.data 就是一个 Blob 对象。Blob 是浏览器里最原始的二进制数据容器,它不关心你存的是视频还是音频,只管存字节。
拿到所有分片后,我们需要把它们合并成一个完整的 Blob:
let recordedChunks = [];
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
mediaRecorder.onstop = () => {
// 合并所有分片
const blob = new Blob(recordedChunks, {
type: 'video/webm'
});
// 生成下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'recording.webm';
a.click();
// 记得释放内存
URL.revokeObjectURL(url);
};
将 Blob 转换为 ArrayBuffer
Blob 虽然方便,但有些场景我们需要更底层的操作——比如加密、分片上传、或者自定义编码。这时候就要把 Blob 转成 ArrayBuffer。
为什么需要 ArrayBuffer?你想想看,Blob 是只读的,你不能直接修改里面的字节。而 ArrayBuffer 可以配合 DataView 或 TypedArray 进行逐字节操作。
async function blobToArrayBuffer(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = () => {
reject(new Error('读取 Blob 失败'));
};
reader.readAsArrayBuffer(blob);
});
}
// 使用示例
const arrayBuffer = await blobToArrayBuffer(blob);
console.log('ArrayBuffer 大小:', arrayBuffer.byteLength);
处理大文件分片录制
终于到了最实战的部分。大文件录制,说白了就是解决两个问题:内存撑不住、上传超时。
我的做法是「边录边取,边取边传」。具体来说:
- 设置合理的
timeslice(比如 5 秒) - 每次
dataavailable拿到分片后,立即转成 ArrayBuffer - 将 ArrayBuffer 分块上传或写入本地存储
- 录制结束后,清理临时数据
const CHUNK_SIZE = 1024 * 1024; // 1MB 一块
async function processChunk(blob, index) {
const buffer = await blobToArrayBuffer(blob);
const totalChunks = Math.ceil(buffer.byteLength / CHUNK_SIZE);
for (let i = 0; i < totalChunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, buffer.byteLength);
const chunk = buffer.slice(start, end);
// 这里可以上传或保存
await uploadChunk(chunk, index, i, totalChunks);
}
}
// 录制时实时处理
mediaRecorder.ondataavailable = async (event) => {
if (event.data.size > 0) {
await processChunk(event.data, chunkIndex++);
}
};
知识体系总览
下面这张图,是我自己梳理的录制数据获取全流程。你可以把它当作一个检查清单:
总结一下关键点
| 概念 | 用途 | 注意事项 |
|---|---|---|
| dataavailable 事件 | 获取录制数据分片 | 必须检查 event.data.size > 0 |
| Blob | 存储完整的媒体数据 | 大文件合并后可能撑爆内存 |
| ArrayBuffer | 底层字节操作 | 需要手动管理内存释放 |
| 分片录制 | 解决大文件问题 | 建议配合断点续传机制 |
说实话,这些内容看起来简单,但真正在项目里用起来,坑还是挺多的。我记得有一次线上环境,用户录了 2 小时的会议,结果因为没做分片处理,浏览器直接 OOM 了。从那以后,我所有录制项目都强制要求分片策略。
好了,这一章的内容就到这里。数据获取是录制的基础,下一章我们会聊聊怎么把这些数据真正用起来——比如实时预览和本地回放。