调用 navigator.mediaDevices.getDisplayMedia() 必须在安全上下文、用户手势直接触发、配合 video.srcObject 渲染并处理各类错误;系统音频需 runtime 判断 track 存在;停止共享需 removeTrack + 协商,且需监听 track 状态防后台暂停。

直接调用 navigator.mediaDevices.getDisplayMedia() 就能启动屏幕共享,但只写这一句根本跑不起来——它必须在安全上下文(HTTPS 或 localhost)中执行,且需配合 video 元素渲染、错误兜底和用户交互触发(比如按钮点击),否则浏览器会静默拒绝或抛出 NotAllowedError。
为什么点一下就报错:NotAllowedError: Permission denied
这个错误几乎 100% 是因为调用时机不对。Chrome 和 Edge 强制要求 getDisplayMedia() 必须由用户手势(click、tap、keydown)直接触发,不能包裹在 setTimeout、Promise.then 或事件委托的深层回调里。
- ✅ 正确:给按钮加
onclick="startScreenShare()",函数内直接调用getDisplayMedia() - ❌ 错误:在 WebSocket 连接成功后的回调里自动调用;或通过
document.getElementById('btn').addEventListener('click', () => { setTimeout(() => getDisplayMedia(), 0) }) - ⚠️ 注意:Vue/React 中用
@click或onClick没问题,但别在异步副作用里发起(比如useEffect里没用户动作就调)
怎么把屏幕流显示出来,而不是黑屏或卡住
拿到 MediaStream 后不能直接赋值给 video.src,必须用 srcObject。而且要确保 video 元素已挂载、未被移除或销毁,否则流会无声释放。
- 设置
autoplay和playsinline(iOS 必需):<video id="screen" autoplay playsinline></video> - 赋值时检查 stream 是否为空:
if (stream && stream.getVideoTracks().length) video.srcObject = stream; - 别漏掉
catch:常见错误如AbortError(用户点了“取消”)、SecurityError(非 HTTPS 环境)都要显式处理
如何同时共享屏幕 + 系统声音(Windows/macOS)
getDisplayMedia({ video: true, audio: true }) 在 Chrome 72+ 和 Edge 79+ 支持系统音频捕获,但 macOS 上仅限 Safari 17.4+ 且需用户手动勾选“共享音频”复选框;Windows 则依赖系统是否启用“允许应用访问麦克风”+“允许捕获系统音频”双开关。
立即学习“前端免费学习笔记(深入)”;
- audio: true 不代表一定能拿到音频轨——
stream.getAudioTracks()可能返回空数组,得运行时判断 - 若需兼容旧版 Chrome(chrome.tabCapture.capture()(仅限扩展环境)
- Firefox 不支持
audio: true,会静默忽略,需提前用!!navigator.mediaDevices.getDisplayMedia.toString().includes('audio')检测
停止共享后远端还看到旧画面?这是 ICE 和 track 生命周期没理清
调用 stream.getTracks().forEach(t => t.stop()) 只停本地预览,远端不会自动感知。真正让对端停止渲染,得靠信令层通知对方移除该 track,或重新协商 SDP(比如发一个新 offer 去掉 screen track)。
- 单纯调
video.srcObject = null只影响本地显示,不影响 WebRTC 连接中的传输 - 如果用
RTCPeerConnection.addTrack()加入了屏幕流,停止时必须对应调removeTrack()并触发 renegotiation - 远端监听
pc.ontrack时,应检查event.track.kind === 'video'且event.streams[0].id是否含 “screen” 字样,避免和摄像头流混淆
最易被忽略的是:getDisplayMedia 返回的 stream 在页面切换标签页或最小化时可能被浏览器自动暂停(尤其 macOS Safari),没有统一事件通知,只能靠定时检测 track.readyState 是否变成 "ended" 来主动重试。



















