屏幕共享原理:getDisplayMedia API、屏幕流与摄像头流的区别、MediaStream对象
屏幕共享,说白了就是把你的电脑屏幕「直播」出去。很多同学一开始觉得这玩意儿很神秘,其实底层原理并不复杂。我最早接触屏幕共享是在做一个远程协作工具的时候,当时被各种流的概念搞得晕头转向。今天咱们就把这块彻底掰开揉碎。
一、getDisplayMedia API:屏幕共享的入口
屏幕共享的核心API就是 getDisplayMedia。它和 getUserMedia 是兄弟俩,一个管屏幕,一个管摄像头。
核心区别:getUserMedia 需要用户授权摄像头/麦克风,getDisplayMedia 需要用户选择共享哪个窗口或整个屏幕。
来看一个最基础的调用示例:
async function startScreenShare() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always" // 显示鼠标指针
},
audio: false // 默认不采集系统音频
});
// 拿到 stream 后,可以赋值给 video 元素
const video = document.getElementById("preview");
video.srcObject = stream;
} catch (err) {
console.error("用户取消了共享", err);
}
}
嗯,这里要注意一点:getDisplayMedia 返回的是一个 Promise,用户如果点了「取消」,就会 reject。我在项目中遇到过用户手滑点取消的情况,所以一定要加 try-catch。
二、屏幕流 vs 摄像头流:到底差在哪?
你想想看,屏幕流和摄像头流虽然都是 MediaStream,但本质上天差地别。我整理了一个对比表,一目了然:
| 对比维度 | 屏幕流 (getDisplayMedia) | 摄像头流 (getUserMedia) |
|---|---|---|
| 视频源 | 显示器/窗口/浏览器标签 | 物理摄像头 |
| 帧率 | 通常 30fps,可配置 | 通常 30fps,受硬件限制 |
| 分辨率 | 取决于屏幕分辨率(可能 4K) | 取决于摄像头(通常 720p/1080p) |
| 音频 | 默认不包含系统音频 | 默认包含麦克风音频 |
| 用户交互 | 弹出选择窗口的对话框 | 弹出摄像头权限请求 |
| 隐私风险 | 高(可能泄露敏感信息) | 中(只暴露画面) |
我个人习惯在屏幕共享时,把摄像头流和屏幕流分开处理。为什么呢?因为屏幕流的分辨率通常很高,直接推到远端带宽扛不住。我曾经在一个项目中,用户用 4K 屏幕共享,结果带宽直接打满,画面卡成 PPT。后来我加了一层降分辨率处理,才解决问题。
避坑指南:屏幕流默认不带系统音频!如果你想采集系统声音(比如播放视频的声音),需要额外处理。我曾经踩过这个坑,用户共享了视频播放窗口,结果对方听不到声音,排查了半天才发现是音频没开。
三、MediaStream 对象:流的本质
不管是屏幕流还是摄像头流,最终都封装成 MediaStream 对象。这个对象到底长什么样?咱们来解剖一下。
一个 MediaStream 包含多个 MediaStreamTrack。每个 track 代表一种媒体类型:
- 视频 track:包含画面数据
- 音频 track:包含声音数据
你可以这样查看流里的内容:
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
// 查看所有 track
console.log(stream.getTracks());
// 输出:[MediaStreamTrack]
// 单独获取视频 track
const videoTrack = stream.getVideoTracks()[0];
console.log(videoTrack.label); // 比如 "屏幕 1" 或 "Chrome 标签页"
// 单独获取音频 track(如果有的话)
const audioTrack = stream.getAudioTracks()[0];
console.log(audioTrack?.label); // 可能 undefined
这里有个小技巧:你可以用 clone() 方法复制一个 track,但要注意——克隆出来的 track 和原 track 共享同一个媒体源。说白了就是,你关掉其中一个,另一个也会受影响。
个人经验:我在做多端同步时,经常需要把屏幕流拆成多个 track 分别处理。比如视频 track 用来推流,音频 track 用来做本地录制。用 getVideoTracks() 和 getAudioTracks() 分开拿,比直接操作整个 stream 灵活得多。
四、知识体系结构图
下面这张图帮你理清本章的核心逻辑:
五、实际开发中的注意事项
最后分享几个我在实战中总结的经验:
- 屏幕流一定要降分辨率:4K 屏幕直接推流,带宽至少需要 20Mbps。我一般降到 1080p 或 720p,用户体验好很多。
- 别忘了处理用户取消:
getDisplayMedia被取消会抛异常,一定要 catch 住,别让页面崩溃。 - 停止共享时要释放资源:调用
track.stop()来关闭屏幕流,否则摄像头灯可能一直亮着(如果是摄像头流)。 - 屏幕流和摄像头流可以混用:比如把摄像头画面画中画叠加到屏幕流上,这个后面章节会细讲。
一句话总结:getDisplayMedia 是屏幕共享的入口,返回的 MediaStream 包含视频 track(可能还有音频 track),和摄像头流最大的区别在于分辨率高、默认无音频、需要用户选择共享区域。