屏幕录制实战: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 获取麦克风流,然后用 MediaStreamaddTrack 合并。

我曾经做过一个在线会议录制工具,用户反馈「录出来的视频没有声音」。排查了半天,发现是用户选了「共享标签页」而不是「共享屏幕」。标签页模式下,audio: true 只能录该标签页的声音,录不到系统全局音频。嗯,这个坑我记了很久。

屏幕录制权限处理

权限这块,getDisplayMediagetUserMedia 不太一样。后者可以提前请求权限,前者不行——每次调用都会弹选择器。

那如果用户第一次点了「取消」,第二次再调用呢?还是会弹。没有「记住选择」这个选项。你想想看,这其实是个安全设计:防止恶意网页偷偷录屏。

但你可以做一件事:检测用户是否取消了共享。当用户停止共享时,视频轨会触发 ended 事件:

const track = stream.getVideoTracks()[0];
track.onended = () => {
  console.log("用户停止了屏幕共享");
  // 在这里做清理工作,比如停止录制、更新UI
};

我建议你在 UI 上给用户一个「停止共享」按钮,同时监听 onended 事件做双重保障。别问我为什么——有一次用户点了浏览器自带的停止按钮,我的 UI 没同步更新,结果录了个空视频。

💡 小技巧: 如果用户想切换共享内容(比如从窗口A换成窗口B),你需要先停止当前流,再重新调用 getDisplayMedia。没有「动态切换」的 API。

知识体系总览

下面这张图帮你理清本章的核心逻辑:

getDisplayMedia 基础调用 选择屏幕/窗口/标签页 系统音频采集 try/catch 异常处理 cursor 参数控制鼠标 displaySurface 判断类型 用户选择不可强制 audio: true 请求 浏览器兼容性差异 权限处理:onended 事件 + UI 同步

避坑指南

最后,分享几个我踩过的坑:

  • 移动端不支持getDisplayMedia 在 iOS Safari 和大部分 Android 浏览器上不可用。如果你要做跨平台应用,记得加特性检测。
  • 录制分辨率:默认分辨率可能不高。你可以通过 video: { width: 1920, height: 1080 } 来请求高清,但最终分辨率取决于用户屏幕。
  • 停止录制后要释放资源:调用 track.stop() 后,记得把 srcObject 置空,否则浏览器会一直保留流引用,导致内存泄漏。

嗯,屏幕录制这块内容其实不少,但核心就是 getDisplayMedia 这一个 API。把它的参数、事件、权限流程搞明白,剩下的就是业务逻辑的组装了。

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