录制数据获取:从事件监听到底层处理

各位同学,今天我们来聊聊录制数据的获取。说实话,这块内容在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);
  }
};
我的经验:timeslice 设得太小(比如100ms)会产生大量小文件,反而增加处理开销。我一般设 2-5 秒,既保证内存可控,又不会碎片太多。

获取 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 如果太大(超过 2GB),直接生成 ObjectURL 可能会让浏览器崩溃。我曾经在录制 4K 视频时遇到过这个问题,后面会讲怎么处理。

将 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);
核心区别:Blob 是「文件视角」,ArrayBuffer 是「内存视角」。前者适合存储和传输,后者适合计算和修改。

处理大文件分片录制

终于到了最实战的部分。大文件录制,说白了就是解决两个问题:内存撑不住、上传超时。

我的做法是「边录边取,边取边传」。具体来说:

  1. 设置合理的 timeslice(比如 5 秒)
  2. 每次 dataavailable 拿到分片后,立即转成 ArrayBuffer
  3. 将 ArrayBuffer 分块上传或写入本地存储
  4. 录制结束后,清理临时数据
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++);
  }
};
避坑指南:我曾经在分片上传时没做「断点续传」,结果网络闪断一次,整个录制白费了。建议每次上传成功后,在 localStorage 里记录一下已上传的分片索引。

知识体系总览

下面这张图,是我自己梳理的录制数据获取全流程。你可以把它当作一个检查清单:

录制数据获取流程 dataavailable 事件 Blob 数据 ArrayBuffer 大文件分片录制策略 设置 timeslice 实时转 ArrayBuffer 分块上传/存储 关键:每次 dataavailable 后立即处理,不要等到录制结束 建议配合 IndexedDB 或 WebSocket 实现实时传输

总结一下关键点

概念 用途 注意事项
dataavailable 事件 获取录制数据分片 必须检查 event.data.size > 0
Blob 存储完整的媒体数据 大文件合并后可能撑爆内存
ArrayBuffer 底层字节操作 需要手动管理内存释放
分片录制 解决大文件问题 建议配合断点续传机制

说实话,这些内容看起来简单,但真正在项目里用起来,坑还是挺多的。我记得有一次线上环境,用户录了 2 小时的会议,结果因为没做分片处理,浏览器直接 OOM 了。从那以后,我所有录制项目都强制要求分片策略。

好了,这一章的内容就到这里。数据获取是录制的基础,下一章我们会聊聊怎么把这些数据真正用起来——比如实时预览和本地回放。


公众号:蓝海资料掘金营,微信deep3321