14、屏幕录制:MediaRecorder API、录制屏幕流、下载录制文件

屏幕共享做完了,下一步是什么?

我个人觉得,光能共享还不够。很多时候,用户需要把共享的内容录下来——比如录个操作教程、保存一次线上演示。这就是我们今天要聊的:屏幕录制

说白了,就是把屏幕流变成视频文件,存到本地。核心工具就是浏览器自带的 MediaRecorder API。嗯,这东西我刚开始用的时候也踩过坑,今天一次性讲清楚。

14.1 MediaRecorder API 是什么?

MediaRecorder 是浏览器提供的一个接口,专门用来录制媒体流。它接收一个 MediaStream 对象,然后输出一段一段的二进制数据(Blob)。

我在项目中遇到过一个问题:很多人以为它只能录麦克风或摄像头。其实它什么流都能录,包括屏幕流。你想想看,只要是个 MediaStream,它就能处理。

核心流程:

  1. 获取屏幕流(getDisplayMedia)
  2. 创建 MediaRecorder 实例
  3. 监听 dataavailable 事件,收集数据块
  4. 停止录制后,合并数据块生成 Blob
  5. 创建下载链接,保存文件

这里有个关键点:MediaRecorder 不是一次性输出整个文件的。它会按时间切片,每次触发 dataavailable 事件,给你一小段数据。你需要自己把这些片段拼起来。

14.2 录制屏幕流:实战代码

好,我们直接上代码。这是我个人习惯的写法,清晰且不容易出错。

// 1. 获取屏幕流
const stream = await navigator.mediaDevices.getDisplayMedia({
  video: true,
  audio: true  // 如果你想同时录系统声音
});

// 2. 准备存储
let recordedChunks = [];

// 3. 创建 MediaRecorder
const mediaRecorder = new MediaRecorder(stream, {
  mimeType: 'video/webm; codecs=vp9'  // 我推荐用 webm 格式
});

// 4. 监听数据
mediaRecorder.ondataavailable = function(event) {
  if (event.data.size > 0) {
    recordedChunks.push(event.data);
  }
};

// 5. 开始录制
mediaRecorder.start(1000); // 每秒触发一次 dataavailable

// 6. 停止录制(比如用户点击停止按钮)
mediaRecorder.onstop = function() {
  const blob = new Blob(recordedChunks, {
    type: 'video/webm'
  });
  // 接下来就是下载
};

小提示: start(1000) 里的 1000 是毫秒,表示每 1 秒生成一个数据块。这个值不是必须的,但设了之后可以避免内存占用过高。我一般设 1000-2000 之间。

为什么会推荐 video/webm?因为这是浏览器原生支持最好的格式。MP4 虽然更通用,但 MediaRecorder 对 MP4 的支持参差不齐。我曾经试过用 video/mp4,结果在 Safari 上直接报错。嗯,从那以后我就老实了。

14.3 下载录制文件:两种方式

录制完了,怎么让用户拿到文件?我常用的有两种方式。

方式一:直接下载(推荐)

function downloadBlob(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename || 'recording.webm';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url); // 释放内存,别忘了
}

这段代码的核心是 URL.createObjectURL。它把 Blob 转成一个临时 URL,然后模拟点击下载。注意最后一定要 revokeObjectURL,否则内存会一直占着。我刚开始写的时候漏了这步,页面跑久了越来越卡……

方式二:预览后下载

有时候用户想先看看录得怎么样,再决定要不要下载。这时候可以加个 <video> 标签预览。

const video = document.createElement('video');
video.src = URL.createObjectURL(blob);
video.controls = true;
document.body.appendChild(video);

// 用户确认后,再调用上面的 downloadBlob

注意: 预览和下载用的是同一个 Blob,不要提前 revoke。等用户下载完再释放。我建议在下载按钮的点击事件里统一处理。

14.4 避坑指南:我踩过的几个坑

  • 格式兼容性: 不同浏览器支持的 mimeType 不一样。建议先检测:MediaRecorder.isTypeSupported('video/webm')。如果不支持,降级到 video/webm;codecs=vp8
  • 音频问题: 屏幕流默认不带音频。如果你想录系统声音,需要在 getDisplayMedia 里传 audio: true。但注意,不是所有浏览器都支持录系统音频。
  • 内存泄漏: 录制时间长了,recordedChunks 数组会越来越大。建议在 dataavailable 里及时处理或限制最大长度。
  • 停止录制: 记得同时停止 MediaRecorder 和屏幕流的所有轨道,否则摄像头灯会一直亮着。
// 正确停止方式
function stopRecording(mediaRecorder, stream) {
  mediaRecorder.stop();
  stream.getTracks().forEach(track => track.stop());
}

14.5 知识结构图

下面这张图帮你理清整个录制流程。从获取流到下载文件,每一步都串起来了。

屏幕录制核心流程 获取屏幕流 getDisplayMedia 创建 Recorder new MediaRecorder 开始录制 recorder.start() 循环 dataavailable 事件收集数据块 停止录制 recorder.stop() + 释放轨道 合并为 Blob new Blob(recordedChunks) 下载文件 createObjectURL + a.click 关键点: ① 检测 mimeType 兼容性 ② 及时释放内存 ③ 停止时关闭所有轨道

14.6 完整示例:一键录制与下载

最后,给你一个可以直接跑起来的完整示例。把下面这段代码放到一个 HTML 文件里,点一下按钮就能录。

<button id="startBtn">开始录制</button>
<button id="stopBtn" disabled>停止录制</button>

<script>
let mediaRecorder;
let recordedChunks = [];

document.getElementById('startBtn').onclick = async () => {
  const stream = await navigator.mediaDevices.getDisplayMedia({
    video: true,
    audio: true
  });
  
  mediaRecorder = new MediaRecorder(stream, {
    mimeType: 'video/webm'
  });
  
  mediaRecorder.ondataavailable = (e) => {
    if (e.data.size > 0) recordedChunks.push(e.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 = 'screen-recording.webm';
    a.click();
    URL.revokeObjectURL(url);
    recordedChunks = [];
  };
  
  mediaRecorder.start(1000);
  document.getElementById('startBtn').disabled = true;
  document.getElementById('stopBtn').disabled = false;
};

document.getElementById('stopBtn').onclick = () => {
  mediaRecorder.stop();
  mediaRecorder.stream.getTracks().forEach(t => t.stop());
  document.getElementById('startBtn').disabled = false;
  document.getElementById('stopBtn').disabled = true;
};
</script>

这个例子虽然简单,但涵盖了所有核心逻辑。你可以在它的基础上加 UI 反馈、加进度条、加格式选择。嗯,剩下的就交给你自己发挥了。


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