6、屏幕共享:getDisplayMedia()方法、屏幕共享的约束与权限、录制屏幕内容

屏幕共享,说白了就是把你的电脑屏幕内容实时传给对方。这在远程会议、在线教育、游戏直播里太常见了。WebRTC 做这件事,核心就靠一个 API——getDisplayMedia()

我个人习惯把屏幕共享分成三步:获取流 → 约束权限 → 录制或传输。咱们一步步拆开看。

核心知识点速览

  • getDisplayMedia() 是浏览器提供的屏幕捕获 API
  • 约束参数控制分辨率、帧率、光标显示等
  • 权限由浏览器弹窗管理,用户主动选择
  • MediaRecorder 可录制屏幕内容为视频文件
屏幕共享核心流程 getDisplayMedia() 获取屏幕媒体流 约束与权限 分辨率/帧率/光标 MediaRecorder 录制或传输 约束参数详解 video: { width: 1920 } 分辨率约束 frameRate: 30 帧率约束 cursor: 'always' 光标显示策略 用户主动授权 → 浏览器弹窗选择窗口/标签/整个屏幕 权限不可静默获取,必须用户点击确认

6.1 getDisplayMedia() 的基本用法

这个 API 和 getUserMedia() 很像,但有个关键区别:它不要求用户提前授权摄像头或麦克风。调用时浏览器会弹出一个选择窗口,让用户自己选「共享整个屏幕、某个窗口还是某个浏览器标签页」。

来看最简代码:

async function startScreenShare() {
  try {
    const stream = await navigator.mediaDevices.getDisplayMedia({
      video: true
    });
    // 拿到 stream 后,可以挂到 video 标签上预览
    const video = document.getElementById('preview');
    video.srcObject = stream;
  } catch (err) {
    console.error('屏幕共享失败:', err);
  }
}

嗯,这里要注意:getDisplayMedia() 返回的 MediaStream 默认只包含视频轨道,没有音频。如果你想同时捕获系统音频(比如电脑播放的声音),需要额外指定 audio: true

💡 个人经验

我在项目中遇到过一个问题:用户共享窗口时,如果窗口被最小化或遮挡,捕获的画面会变成黑屏或卡住最后一帧。解决办法是监听 track.onended 事件,及时提示用户恢复窗口或重新选择。

6.2 屏幕共享的约束参数

约束参数和 getUserMedia() 类似,但有一些专属选项。我习惯把约束分成两类:视频质量约束显示策略约束

视频质量约束

参数 说明 示例值
width 期望宽度(像素) { min: 1280, ideal: 1920, max: 3840 }
height 期望高度(像素) { min: 720, ideal: 1080, max: 2160 }
frameRate 帧率(fps) { ideal: 30, max: 60 }
displaySurface 默认捕获类型 'monitor' | 'window' | 'browser'

显示策略约束

cursor 参数控制鼠标光标是否显示在共享画面中。这个细节容易被忽略,但实际体验影响很大。

const constraints = {
  video: {
    cursor: 'always',      // 'always' | 'motion' | 'never'
    displaySurface: 'monitor', // 默认选整个屏幕
    logicalSurface: true,  // 是否捕获被遮挡的内容
  },
  audio: {
    echoCancellation: true,
    noiseSuppression: true
  }
};

const stream = await navigator.mediaDevices.getDisplayMedia(constraints);

⚠️ 避坑指南

我曾经在做一个远程协作工具时,发现共享的屏幕画面模糊不清。排查了半天,原来是 logicalSurface 默认是 false,导致浏览器只捕获窗口的可见部分。如果你的应用需要捕获被其他窗口遮挡的内容,记得把这个参数设为 true。

6.3 权限管理:用户说了算

屏幕共享的权限和摄像头麦克风不一样。它没有「永久授权」的概念——每次调用 getDisplayMedia() 都会弹出选择窗口,用户必须手动点击「共享」才能继续。

为什么会这样?因为屏幕内容太敏感了。你想想看,如果网页能静默捕获屏幕,那密码、聊天记录、银行卡号全都能被偷走。所以浏览器强制要求用户主动确认。

权限相关的几个关键点:

  • 无法提前授权:没有类似 navigator.permissions.query({name: 'display-capture'}) 的 API
  • 用户可随时停止:浏览器会在地址栏显示共享图标,点击即可停止
  • 监听停止事件:通过 track.onended 感知用户主动停止
const track = stream.getVideoTracks()[0];
track.onended = () => {
  console.log('用户停止了屏幕共享');
  // 清理 UI 状态,比如隐藏预览、释放资源
  stopScreenShare();
};

6.4 录制屏幕内容

拿到屏幕流之后,录制就顺理成章了。WebRTC 本身不提供录制功能,但浏览器自带的 MediaRecorder API 可以轻松搞定。

我常用的录制方案:

let mediaRecorder;
let recordedChunks = [];

function startRecording(stream) {
  recordedChunks = [];
  // 指定 MIME 类型,优先用 webm
  const options = { mimeType: 'video/webm; codecs=vp9' };
  mediaRecorder = new MediaRecorder(stream, options);

  mediaRecorder.ondataavailable = (event) => {
    if (event.data.size > 0) {
      recordedChunks.push(event.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();
  };

  mediaRecorder.start(1000); // 每秒生成一个 chunk
}

function stopRecording() {
  if (mediaRecorder && mediaRecorder.state !== 'inactive') {
    mediaRecorder.stop();
  }
}

💡 录制小技巧

如果你需要同时录制屏幕和麦克风声音,可以用 AudioContext 把两个音频轨道混音。我做过一个教学工具,就是先把屏幕流和麦克风流合并成一个 MediaStream,再传给 MediaRecorder,这样录出来的视频既有画面又有讲解声。

6.5 实际项目中的注意事项

屏幕共享看起来简单,但坑不少。我总结几个常见问题:

  • 分辨率适配:用户屏幕可能是 4K,但网络带宽不够。建议用 ideal 而不是 exact,让浏览器自动降级
  • 多显示器场景getDisplayMedia() 默认让用户选择共享哪个显示器,你无法强制指定
  • 性能开销:高帧率录制会消耗大量 CPU,移动端尤其明显。我一般把帧率限制在 15-24fps
  • 浏览器兼容性:Safari 对 getDisplayMedia() 支持较晚,而且不支持 cursor 参数

核心要点回顾

  1. getDisplayMedia() 获取屏幕流,用户必须手动授权
  2. 约束参数控制分辨率、帧率、光标显示策略
  3. 权限不可静默获取,用户可随时停止共享
  4. MediaRecorder 配合屏幕流可实现录制功能
  5. 注意性能优化和浏览器兼容性差异

屏幕共享这块,说白了就是「获取流 + 处理流」。掌握了 getDisplayMedia() 的约束和权限机制,再配合 MediaRecorder,你就能做出一个完整的屏幕共享和录制功能。我在实际项目中踩过的坑都写在上面了,希望能帮你少走弯路。

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