Safari 15 macOS调用getDisplayMedia报用户手势处理器错误排查
报错原因
Safari 对用户激活(user gesture)的生命周期校验远严格于 Chrome、Firefox、Edge 等 Chromium/Gecko 内核浏览器:
- 其他浏览器会给单次用户手势保留数秒的有效期,允许经过数次
await异步等待后再调用敏感媒体API - Safari 15 的规则是,用户手势上下文一旦经过事件循环调度(即
await之后跳出了点击事件的同步执行栈)就会立刻失效,且getDisplayMedia的手势校验优先级比getUserMedia更严格
你的代码中在非移动端分支里,先await执行了getUserMedia,等这个异步请求 resolve 之后再调用getDisplayMedia时,用户手势上下文已经在Safari里失效了,因此抛出对应错误。
你当前的按钮绑定方式没有问题,不管是直接传函数引用还是包一层箭头函数,都不会破坏用户手势上下文,注释里的写法既不会解决问题也不会引发问题。
修复方法
核心原则是在点击事件的同步执行阶段就发起getDisplayMedia调用,不要在调用它之前插入任何会让出事件循环的异步操作。具体修改非移动端分支代码即可:
} else { // 同步发起两个媒体请求,全程在用户手势上下文内,不提前await const webcamPromise = mediaDevices.getUserMedia({ audio: true, video: true }); const screenPromise = mediaDevices.getDisplayMedia({ video: true }); // 两个请求都发起后再统一等待结果 const [webcamShare, screenStreamVideoOnly] = await Promise.all([webcamPromise, screenPromise]); const [webcamAudioTrack] = webcamShare.getAudioTracks(); const [screenVideoTrack] = screenStreamVideoOnly.getVideoTracks(); const VideoStreamwithAudio = new MediaStream([webcamAudioTrack, screenVideoTrack]); setMediaStream(webcamShare); setScreenStream(VideoStreamwithAudio); }
额外注意事项:
- 不要在调用这两个媒体API之前添加任何
setTimeout、Promise.resolve().then()、其他接口请求等异步逻辑,哪怕0延迟也会导致Safari丢失用户手势上下文 - 你的UA判断逻辑过于老旧,无法识别请求桌面版站点的iPadOS Safari,会把iPad误判为桌面端触发屏幕共享逻辑,建议后续更新UA规则,但这不是本次报错的诱因
- 如果后续加权限申请前置逻辑,一定要把
getDisplayMedia的调用放在最靠近点击事件同步流的位置
关于其他屏幕共享实现方案
Safari 15 没有提供可替代getDisplayMedia的公开屏幕共享API,所有合规的屏幕共享能力都必须通过这个接口调用。Google Hangouts 能正常运行不是用了私有接口,只是它遵循了Safari的手势规则,在用户点击的同步阶段就发起了屏幕共享请求。
需要注意的是,Safari的屏幕共享除了浏览器API权限外,还需要用户在macOS系统偏好设置-隐私与安全性中给对应浏览器开启「屏幕录制」权限,不过权限缺失会抛出单独的权限错误,和你当前遇到的用户手势错误无关。
内容的提问来源于stack exchange,提问作者Quan Nguyen
相关产品推荐
相关产品推荐

