14、屏幕录制:MediaRecorder API、录制屏幕流、下载录制文件
屏幕共享做完了,下一步是什么?
我个人觉得,光能共享还不够。很多时候,用户需要把共享的内容录下来——比如录个操作教程、保存一次线上演示。这就是我们今天要聊的:屏幕录制。
说白了,就是把屏幕流变成视频文件,存到本地。核心工具就是浏览器自带的 MediaRecorder API。嗯,这东西我刚开始用的时候也踩过坑,今天一次性讲清楚。
14.1 MediaRecorder API 是什么?
MediaRecorder 是浏览器提供的一个接口,专门用来录制媒体流。它接收一个 MediaStream 对象,然后输出一段一段的二进制数据(Blob)。
我在项目中遇到过一个问题:很多人以为它只能录麦克风或摄像头。其实它什么流都能录,包括屏幕流。你想想看,只要是个 MediaStream,它就能处理。
核心流程:
- 获取屏幕流(getDisplayMedia)
- 创建 MediaRecorder 实例
- 监听 dataavailable 事件,收集数据块
- 停止录制后,合并数据块生成 Blob
- 创建下载链接,保存文件
这里有个关键点: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 知识结构图
下面这张图帮你理清整个录制流程。从获取流到下载文件,每一步都串起来了。
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 反馈、加进度条、加格式选择。嗯,剩下的就交给你自己发挥了。