9、录制实战:录制并下载视频文件、录制并下载音频文件、录制时长控制

好,咱们进入实战环节。前面几章我们把摄像头、麦克风、屏幕共享都搞定了,数据也通过 MediaRecorder 接住了。但光接住没用,你得能把它存下来,变成实实在在的文件。

这一章,我们就来解决三个核心问题:怎么录视频并下载怎么录纯音频并下载、以及怎么控制录制时长。说白了,就是让用户点一下“开始录”,再点一下“停止录”,然后浏览器自动弹出一个下载框。

嗯,这里要注意,不同浏览器对 MediaRecorder 的支持细节不太一样。我去年做一个在线面试平台时就踩过坑——Chrome 上跑得好好的,到了 Firefox 上录出来的文件就是打不开。所以咱们今天写的代码,我会尽量兼容主流浏览器。

9.1 录制视频并下载

先来个最直接的:从摄像头获取视频流,录制 5 秒,然后下载成 WebM 文件。

核心逻辑其实就三步:

  1. 创建 MediaRecorder 实例,绑定视频流
  2. 监听 ondataavailable 事件,把数据块存起来
  3. 停止录制后,把数据块拼成 Blob,生成下载链接

来看代码:

// 1. 获取视频流
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

// 2. 创建 MediaRecorder
const recorder = new MediaRecorder(stream, {
  mimeType: 'video/webm;codecs=vp9'  // 我习惯用 vp9,压缩率更高
});

let chunks = [];

// 3. 收集数据块
recorder.ondataavailable = (e) => {
  if (e.data.size > 0) {
    chunks.push(e.data);
  }
};

// 4. 录制结束后生成文件
recorder.onstop = () => {
  const blob = new Blob(chunks, { type: 'video/webm' });
  const url = URL.createObjectURL(blob);

  // 创建下载链接
  const a = document.createElement('a');
  a.href = url;
  a.download = `recording_${Date.now()}.webm`;
  a.click();

  // 释放内存
  URL.revokeObjectURL(url);
  chunks = [];
};

// 5. 开始录制
recorder.start();

// 6. 5秒后停止
setTimeout(() => {
  recorder.stop();
  stream.getTracks().forEach(track => track.stop());
}, 5000);

关键点:

  • ondataavailable 默认每 1 秒触发一次(取决于 timeslice 参数)。如果不传 timeslice,则只在 stop() 时触发一次。
  • 我建议始终传一个 timeslice,比如 1000ms。这样即使录制中途崩溃,你至少能拿到部分数据。
  • mimeType 要检查浏览器是否支持。可以用 MediaRecorder.isTypeSupported() 做降级。

小技巧: 下载文件名里带上时间戳,避免重名覆盖。我一般用 Date.now() 或者 new Date().toISOString()

9.2 录制纯音频并下载

有时候你只需要声音,比如录一段语音笔记、或者会议录音。这时候就别开摄像头了,省资源。

做法几乎一样,只是 getUserMedia 只请求音频,并且 mimeType 要换成音频格式。

// 只获取音频流
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });

// 检查支持的音频格式
let audioMimeType = 'audio/webm;codecs=opus';
if (!MediaRecorder.isTypeSupported(audioMimeType)) {
  audioMimeType = 'audio/webm';  // 降级方案
}

const audioRecorder = new MediaRecorder(audioStream, {
  mimeType: audioMimeType
});

let audioChunks = [];

audioRecorder.ondataavailable = (e) => {
  if (e.data.size > 0) {
    audioChunks.push(e.data);
  }
};

audioRecorder.onstop = () => {
  const blob = new Blob(audioChunks, { type: audioMimeType });
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = `audio_${Date.now()}.webm`;
  a.click();

  URL.revokeObjectURL(url);
  audioChunks = [];
};

// 开始录制
audioRecorder.start();

// 录制 10 秒
setTimeout(() => {
  audioRecorder.stop();
  audioStream.getTracks().forEach(track => track.stop());
}, 10000);

注意: 纯音频录制时,mimeType 不要写成 video/webm。虽然有些浏览器能兼容,但录出来的文件可能包含空的视频轨道,导致播放器识别异常。我曾经因为这个被测试妹子追着骂了一下午……

9.3 录制时长控制

实际项目中,你不可能让用户一直录下去。要么限制最大时长,要么提供暂停/恢复功能。这里我讲三种常用方案。

方案一:定时器强制停止

最简单粗暴。适合固定时长录制,比如录 30 秒短视频。

const MAX_DURATION = 30000; // 30秒

recorder.start();
const timer = setTimeout(() => {
  recorder.stop();
}, MAX_DURATION);

// 如果用户提前停止,记得清除定时器
recorder.onstop = () => {
  clearTimeout(timer);
  // ... 后续处理
};

方案二:实时计时 + 进度显示

用户能看到录了多久,快到限制时给个提示。这个体验更好。

let elapsed = 0;
const MAX_DURATION = 60; // 秒

const interval = setInterval(() => {
  elapsed++;
  console.log(`已录制 ${elapsed} 秒`);

  if (elapsed >= MAX_DURATION) {
    recorder.stop();
    clearInterval(interval);
  }
}, 1000);

recorder.onstop = () => {
  clearInterval(interval);
};

方案三:基于数据量的控制

有时候你更关心文件大小,而不是时长。比如限制录出来的文件不超过 10MB。

let totalSize = 0;
const MAX_SIZE = 10 * 1024 * 1024; // 10MB

recorder.ondataavailable = (e) => {
  if (e.data.size > 0) {
    totalSize += e.data.size;
    chunks.push(e.data);

    if (totalSize >= MAX_SIZE) {
      recorder.stop();
    }
  }
};

我的建议: 实际项目中最好把方案二和方案三结合。既限制时长,又限制大小。比如“最长录制 5 分钟,文件不超过 50MB”。哪个先达到就停哪个。

9.4 本章知识体系

下面这张图帮你理清整个录制流程的核心脉络:

录制实战核心流程 视频流(摄像头) getUserMedia({video:true}) 音频流(麦克风) getUserMedia({audio:true}) 混合流 MediaStream 合并 MediaRecorder 核心 mimeType / timeslice / ondataavailable 时长控制策略 定时器停止 | 实时计时 + 进度 | 数据量限制 输出:Blob → 下载链接 → .webm 文件

9.5 避坑指南

最后分享几个我实际踩过的坑,希望能帮你省点时间。

  • Blob 类型不匹配: 下载时 a.download 的后缀名要和 Blob 的 type 一致。比如 video/webm 对应 .webmaudio/mp4 对应 .mp4。否则用户下载后可能打不开。
  • 内存泄漏: 每次生成 URL.createObjectURL 后,记得在下载完成后调用 URL.revokeObjectURL。否则标签页开久了,内存会越占越多。
  • 停止流: 录制结束后,一定要调用 stream.getTracks().forEach(track => track.stop()) 释放摄像头和麦克风。不然摄像头灯一直亮着,用户会慌。
  • 移动端兼容: 在 iOS Safari 上,MediaRecorder 支持有限。我建议在移动端用 audio/mp4video/mp4 格式,兼容性更好。

总结一下: 录制并下载的核心就三件事——拿到流、用 MediaRecorder 录、拼成 Blob 下载。时长控制可以按需选择定时器、计时器或数据量限制。代码量不大,但细节不少。多测几个浏览器,准没错。

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