6、屏幕共享:getDisplayMedia详解、屏幕共享权限、屏幕共享与摄像头切换、录制屏幕流

屏幕共享,说白了就是把你的电脑屏幕内容,实时传给对方。这在远程会议、在线教育、游戏直播里太常见了。我最早接触这个功能时,以为就是简单调个API,结果踩了不少坑。今天咱们就把这块彻底讲透。

6.1 getDisplayMedia 详解

核心API就一个:navigator.mediaDevices.getDisplayMedia()。它和getUserMedia很像,但抓的是屏幕,不是摄像头。

关键区别:getDisplayMedia 不需要用户提前授权,浏览器会弹出一个选择窗口,让用户自己选「分享整个屏幕」还是「分享某个窗口」。

来看个最基础的调用:

async function startScreenShare() {
  try {
    const stream = await navigator.mediaDevices.getDisplayMedia({
      video: {
        cursor: "always" // 显示鼠标指针
      },
      audio: false // 默认不采集系统声音
    });
    // 把流赋给 video 元素
    const video = document.getElementById("screenVideo");
    video.srcObject = stream;
  } catch (err) {
    console.error("屏幕共享失败:", err);
  }
}

嗯,这里要注意:audio参数虽然可以传true,但浏览器支持很有限。我在项目中试过,Chrome 能采集系统音频,但 Firefox 直接忽略。所以别指望它做录屏的音频源。

6.2 屏幕共享权限

权限这块和摄像头不太一样。摄像头需要提前申请权限,屏幕共享是「按需授权」——每次调用getDisplayMedia,浏览器都会弹出选择框。

我曾经遇到一个坑:用户第一次选了「整个屏幕」,第二次想只分享某个窗口,结果浏览器直接用了上次的选择。解决办法是每次调用前,先停止之前的流:

let screenStream = null;

async function startScreenShare() {
  // 先清理旧的流
  if (screenStream) {
    screenStream.getTracks().forEach(track => track.stop());
  }
  
  screenStream = await navigator.mediaDevices.getDisplayMedia({
    video: true
  });
}

注意:用户随时可以点击浏览器地址栏旁边的「停止共享」按钮。你需要监听 track 的 ended 事件,做相应的 UI 处理。

screenStream.getVideoTracks()[0].addEventListener("ended", () => {
  console.log("用户停止了屏幕共享");
  // 切换回摄像头或显示提示
});

6.3 屏幕共享与摄像头切换

实际场景中,用户经常需要在「共享屏幕」和「显示摄像头」之间切换。比如开会时,先共享PPT,然后切回自己的脸。

我的做法是维护两个流:一个屏幕流,一个摄像头流。切换时,把对应的流赋给 video 元素:

let cameraStream = null;
let screenStream = null;

async function switchToCamera() {
  if (screenStream) {
    screenStream.getTracks().forEach(track => track.stop());
    screenStream = null;
  }
  
  if (!cameraStream) {
    cameraStream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: true
    });
  }
  
  document.getElementById("mainVideo").srcObject = cameraStream;
}

async function switchToScreen() {
  if (cameraStream) {
    cameraStream.getTracks().forEach(track => track.stop());
    cameraStream = null;
  }
  
  screenStream = await navigator.mediaDevices.getDisplayMedia({
    video: true
  });
  
  document.getElementById("mainVideo").srcObject = screenStream;
}

小技巧:切换时不要直接替换流,而是先停止旧流的所有 track,再赋新流。否则浏览器可能会保留旧的资源占用。

6.4 录制屏幕流

录制屏幕流,说白了就是把getDisplayMedia拿到的流,喂给MediaRecorder。这个组合我用了很多次,录课程、录bug复现过程,都很方便。

核心代码:

let mediaRecorder = null;
let recordedChunks = [];

function startRecording(stream) {
  recordedChunks = [];
  
  // 指定编码格式,我一般用 video/webm
  mediaRecorder = new MediaRecorder(stream, {
    mimeType: "video/webm;codecs=vp9"
  });
  
  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();
  }
}

你想想看,这里有个细节:mimeType 如果浏览器不支持,会抛异常。我建议先检测:

const mimeTypes = [
  "video/webm;codecs=vp9",
  "video/webm;codecs=vp8",
  "video/webm"
];

function getSupportedMimeType() {
  for (const type of mimeTypes) {
    if (MediaRecorder.isTypeSupported(type)) {
      return type;
    }
  }
  return "video/webm"; // 兜底
}

知识体系图

下面这张图,把屏幕共享的核心流程串起来了:

屏幕共享核心流程 用户点击「共享屏幕」 浏览器弹出选择窗口 (整个屏幕 / 应用窗口 / 浏览器标签页) getDisplayMedia() 返回 MediaStream 实时传输(WebRTC) 添加到 RTCPeerConnection 录制(MediaRecorder) 生成 Blob 并下载 用户可随时停止共享,需监听 track ended 事件

常见问题与避坑

问题 原因 解决方案
getDisplayMedia 返回 null 用户取消了选择 捕获异常,提示用户重新操作
录制没有声音 audio: true 不一定生效 单独用 getUserMedia 采集麦克风,混流后录制
切换时画面卡住 旧流没有完全停止 遍历所有 track 调用 stop()
录制的视频无法播放 mimeType 浏览器不支持 用 isTypeSupported 检测

曾经踩过的坑:有一次用户共享了高分辨率屏幕(4K),结果录制的视频文件巨大,而且播放卡顿。后来我加了分辨率限制:getDisplayMedia({ video: { width: 1920, height: 1080 } }),问题解决。

屏幕共享这块,说白了就是「获取流 → 传输或录制」。但每个环节都有小陷阱。我个人习惯是先把getDisplayMedia的异常处理写全,再考虑功能扩展。毕竟用户取消共享、浏览器不支持、权限被拒,这些情况都得兜住。

嗯,今天就聊到这儿。代码示例可以直接复制去用,但记得根据你的场景调整参数。


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