3、获取屏幕流:调用getDisplayMedia、处理用户授权、错误处理与降级方案
屏幕共享,说白了就是把你的显示器画面“直播”给远端用户。这个功能在远程会议、在线教育、游戏直播里太常见了。但真正动手做的时候,你会发现坑比想象中多。
我个人习惯把屏幕共享分成三步走:获取流 → 处理授权 → 兜底降级。今天我们就来聊聊第一步——怎么拿到屏幕流,以及拿不到的时候怎么办。
3.1 核心API:getDisplayMedia
WebRTC 里获取屏幕流,用的是 navigator.mediaDevices.getDisplayMedia()。这玩意儿跟 getUserMedia 有点像,但区别很大——它抓的是屏幕,不是摄像头。
核心区别:
- getUserMedia 需要用户授权摄像头/麦克风
- getDisplayMedia 需要用户选择共享哪个窗口/屏幕
- getDisplayMedia 返回的流自带
cursor控制选项
来看一个最基础的调用示例:
async function startScreenShare() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always" // 显示鼠标指针
},
audio: false // 不采集系统声音
});
return stream;
} catch (err) {
console.error("屏幕共享失败:", err);
throw err;
}
}
嗯,这里要注意:getDisplayMedia 必须在用户手势(比如点击按钮)中触发。你想想看,如果页面一加载就弹窗要共享屏幕,那用户体验得多糟糕?浏览器也不允许这么干。
3.2 处理用户授权:用户点了“取消”怎么办?
用户点击“取消”或者关闭选择窗口时,getDisplayMedia 会抛出一个 NotAllowedError。我在项目中遇到过好几次,用户手滑点错了,或者突然反悔不想共享了。这时候不能直接崩溃,得优雅处理。
我的处理策略:
- 捕获
NotAllowedError,提示用户“共享已取消” - 保留之前的流(如果有的话)
- 给用户一个重新尝试的按钮
async function handleScreenShare() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { cursor: "always" }
});
// 成功:绑定到 video 元素
videoElement.srcObject = stream;
} catch (err) {
if (err.name === "NotAllowedError") {
// 用户取消了共享
showToast("屏幕共享已取消,您可以稍后重试");
} else {
// 其他错误
handleOtherErrors(err);
}
}
}
3.3 错误处理:常见的坑与解决方案
除了用户取消,还有几种常见错误。我整理了一张表,方便你对照排查:
| 错误名称 | 触发场景 | 解决方案 |
|---|---|---|
| NotAllowedError | 用户取消选择 / 权限被拒绝 | 提示用户重试,或提供降级方案 |
| NotFoundError | 没有可共享的屏幕(比如无显示器) | 检查环境,提示“未检测到屏幕” |
| NotReadableError | 硬件或系统级错误 | 建议用户重启浏览器或系统 |
| AbortError | 共享过程中被中断(比如用户关闭标签页) | 监听 ended 事件,做清理 |
| OverconstrainedError | 约束条件无法满足(比如要求 4K 但屏幕不支持) | 放宽约束,使用默认值 |
我曾经踩过的坑: 有一次用户反馈共享画面是黑的。排查了半天,发现是 cursor 设置成了 "never",但用户以为鼠标指针会显示。后来我改成 "always",问题解决。所以,cursor 选项最好根据场景动态设置。
3.4 降级方案:当 getDisplayMedia 不可用时
不是所有浏览器都支持 getDisplayMedia。比如老版本的 Safari、某些移动端浏览器。这时候就得准备降级方案。
我个人习惯用 特征检测 来判断:
function isScreenShareSupported() {
return !!(
navigator.mediaDevices &&
navigator.mediaDevices.getDisplayMedia
);
}
如果不支持,我有三个降级思路:
- 提示用户升级浏览器 —— 最简单,但体验最差
- 使用 getUserMedia 共享摄像头画面 —— 勉强能看,但只能拍到屏幕的一部分
- 使用第三方插件或扩展 —— 比如 Chrome 扩展,但维护成本高
我的建议: 降级方案不要藏着掖着。直接告诉用户“当前浏览器不支持屏幕共享,请使用 Chrome/Edge 最新版”。与其给一个半残的功能,不如让用户知道问题在哪。
3.5 知识体系总览
下面这张图,把整个获取屏幕流的流程串起来了。从调用 API 到错误处理,再到降级兜底,一目了然:
3.6 实战中的几个小细节
最后分享几个我实际开发中总结的点:
- 监听 ended 事件: 用户随时可能停止共享。记得监听
stream.getVideoTracks()[0].onended,做清理工作。 - 不要忘记释放资源: 共享结束后,调用
track.stop()释放摄像头/屏幕资源。否则下次调用可能失败。 - 音频采集:
getDisplayMedia的 audio 选项目前支持度有限。Chrome 可以采集系统音频,但 Safari 不行。别指望它。 - 分辨率控制: 如果用户屏幕是 4K,采集的流会很大。建议设置
video: { width: { ideal: 1920 }, height: { ideal: 1080 } }来限制。
一个小技巧: 如果你想让用户共享某个特定窗口(比如应用窗口),可以在 getDisplayMedia 的 options 里加 video: { displaySurface: "window" }。这样浏览器会优先推荐窗口共享。不过这只是建议,最终选择权在用户手里。
好了,获取屏幕流这部分就聊到这儿。核心就是:调用 API → 处理授权 → 兜底降级。代码不多,但细节不少。你想想看,用户点取消的时候你弹个报错框,那体验多糟糕?所以,把错误处理做扎实,比炫技更重要。