屏幕录制实战:getDisplayMedia 详解
屏幕录制,说白了就是把你的电脑屏幕、某个窗口或者浏览器标签页给录下来。WebRTC 里干这事儿的核心 API 就是 getDisplayMedia。我最早接触这个 API 的时候,还以为是 getUserMedia 的翻版,结果一用才发现——坑不少。
getDisplayMedia 与 getUserMedia 的区别
先理清一个概念。这两个 API 虽然名字像,但本质完全不同:
| 对比项 | getUserMedia | getDisplayMedia |
|---|---|---|
| 采集目标 | 摄像头、麦克风 | 屏幕、窗口、标签页 |
| 权限触发 | 浏览器弹窗授权 | 浏览器弹窗 + 用户手动选择 |
| 音频支持 | 直接采集麦克风 | 可采集系统音频(有限制) |
| 返回类型 | MediaStream | MediaStream(视频轨必含) |
嗯,这里要注意:getDisplayMedia 返回的流里,视频轨一定有,但音频轨不一定。为什么?因为浏览器需要用户明确授权才能录系统声音。
基础用法:一行代码开始录屏
最简单的调用方式,其实就一行:
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
浏览器会弹出一个选择器,让用户选「共享整个屏幕」、「某个窗口」还是「某个标签页」。选完确认后,stream 就拿到了。
我个人习惯把这段逻辑包成一个函数,方便复用:
async function startScreenCapture() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always" // 显示鼠标指针
},
audio: false
});
return stream;
} catch (err) {
console.error("用户取消了共享或发生错误:", err);
return null;
}
}
我在项目中遇到过一个问题:用户点了「取消」后,getDisplayMedia 会抛出一个 NotAllowedError。如果不 catch 住,整个页面就崩了。所以 try/catch 是必须的。
选择共享屏幕 / 窗口 / 标签页
你可能会问:能不能强制让用户只能选窗口,不能选整个屏幕?答案是不能。浏览器出于安全考虑,选择权完全交给用户,开发者无法通过参数控制。
但有一个小技巧:你可以通过 displaySurface 属性来判断用户到底选了啥:
const track = stream.getVideoTracks()[0];
const settings = track.getSettings();
console.log(settings.displaySurface); // "monitor" | "window" | "browser"
这个值在 Chrome 上支持得比较好。我建议你在录制开始后,把这个信息展示给用户,比如「当前正在录制:窗口 - 微信」。用户体验会好很多。
录制系统音频
这是很多人踩坑的地方。你想录屏幕的同时也录电脑播放的音乐,对吧?getDisplayMedia
const stream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true // 尝试采集系统音频
});
注意:audio: true 只是一个「请求」。浏览器可能会弹窗问用户「是否同时共享音频」。而且,不是所有浏览器都支持。Chrome 在 Windows 和 macOS 上支持,但 Linux 和移动端基本不行。
getUserMedia 获取麦克风流,然后用 MediaStream 的 addTrack 合并。
我曾经做过一个在线会议录制工具,用户反馈「录出来的视频没有声音」。排查了半天,发现是用户选了「共享标签页」而不是「共享屏幕」。标签页模式下,audio: true 只能录该标签页的声音,录不到系统全局音频。嗯,这个坑我记了很久。
屏幕录制权限处理
权限这块,getDisplayMedia 和 getUserMedia 不太一样。后者可以提前请求权限,前者不行——每次调用都会弹选择器。
那如果用户第一次点了「取消」,第二次再调用呢?还是会弹。没有「记住选择」这个选项。你想想看,这其实是个安全设计:防止恶意网页偷偷录屏。
但你可以做一件事:检测用户是否取消了共享。当用户停止共享时,视频轨会触发 ended 事件:
const track = stream.getVideoTracks()[0];
track.onended = () => {
console.log("用户停止了屏幕共享");
// 在这里做清理工作,比如停止录制、更新UI
};
我建议你在 UI 上给用户一个「停止共享」按钮,同时监听 onended 事件做双重保障。别问我为什么——有一次用户点了浏览器自带的停止按钮,我的 UI 没同步更新,结果录了个空视频。
getDisplayMedia。没有「动态切换」的 API。
知识体系总览
下面这张图帮你理清本章的核心逻辑:
避坑指南
最后,分享几个我踩过的坑:
- 移动端不支持:
getDisplayMedia在 iOS Safari 和大部分 Android 浏览器上不可用。如果你要做跨平台应用,记得加特性检测。 - 录制分辨率:默认分辨率可能不高。你可以通过
video: { width: 1920, height: 1080 }来请求高清,但最终分辨率取决于用户屏幕。 - 停止录制后要释放资源:调用
track.stop()后,记得把srcObject置空,否则浏览器会一直保留流引用,导致内存泄漏。
嗯,屏幕录制这块内容其实不少,但核心就是 getDisplayMedia 这一个 API。把它的参数、事件、权限流程搞明白,剩下的就是业务逻辑的组装了。