29、屏幕共享常见问题:黑屏问题、权限拒绝、编解码器不支持、跨域问题
屏幕共享这个功能,说实话,写代码的时候挺爽的。但一到真机测试,各种问题就冒出来了。我这些年做WebRTC,光是屏幕共享的坑就踩了不下十次。今天我把最常见的四个问题拎出来,一个一个说清楚。
为什么会黑屏?为什么用户点了允许还是没画面?为什么有的浏览器能跑有的不行?嗯,咱们一个个拆。
29.1 黑屏问题
黑屏,是屏幕共享里最让人头疼的问题。没有之一。
我遇到过好几次,代码写完了,一跑,画面是黑的。用户那边也点了“共享屏幕”,但就是啥也看不到。这时候别慌,先排查下面几个点。
29.1.1 常见原因
- MediaStream 没有正确获取:
getDisplayMedia()返回的 Promise 可能被 reject 了,但你忘了 catch。 - video 标签没有设置
autoplay:或者设置了但被浏览器策略阻止了。 - canvas 绘制时跨域问题:如果你把屏幕流画到 canvas 上,再转成其他格式,canvas 会被“污染”。
- 编解码器不匹配:发送端用了 H.264,接收端只支持 VP8,画面直接黑掉。
29.1.2 排查步骤
我个人习惯,遇到黑屏先做三件事:
- 打开浏览器控制台,看有没有报错。
- 检查
mediaStream.getVideoTracks()[0]是否存在。 - 在
ontrack回调里打印一下track.kind和track.enabled。
核心检查代码
// 获取屏幕流
navigator.mediaDevices.getDisplayMedia({ video: true })
.then(stream => {
console.log('屏幕流获取成功,track数量:', stream.getTracks().length);
// 检查视频轨道
const videoTrack = stream.getVideoTracks()[0];
if (!videoTrack) {
console.error('没有视频轨道!');
return;
}
console.log('视频轨道状态:', videoTrack.readyState, videoTrack.enabled);
// 绑定到 video 标签
localVideo.srcObject = stream;
})
.catch(err => {
console.error('获取屏幕流失败:', err.name, err.message);
});
小技巧:如果黑屏但控制台没报错,试试在 video 标签上加 muted 属性。有些浏览器不允许非静音视频自动播放。
29.1.3 我曾经踩过的坑
有一次,我在一个 Electron 项目里做屏幕共享。本地测试一切正常,打包后用户反馈黑屏。查了两天,最后发现是 getDisplayMedia 在 Electron 的渲染进程里被安全策略拦截了。解决方案是用主进程的 desktopCapturer 来获取屏幕流。嗯,这个坑我记了很久。
29.2 权限拒绝
权限拒绝,说白了就是用户点了“拒绝”或者浏览器弹窗被屏蔽了。但有时候用户明明点了“允许”,还是报权限错误。这是怎么回事?
29.2.1 用户主动拒绝
这个最简单。用户点了拒绝,getDisplayMedia 返回的 Promise 会 reject,错误类型是 NotAllowedError。
navigator.mediaDevices.getDisplayMedia({ video: true })
.catch(err => {
if (err.name === 'NotAllowedError') {
// 用户拒绝了权限
showToast('请允许屏幕共享权限');
}
});
29.2.2 浏览器权限策略
你想想看,有些浏览器要求屏幕共享必须在用户手势(比如点击按钮)的上下文中触发。如果你在页面加载完成后自动调用 getDisplayMedia,浏览器会直接拒绝。
注意:getDisplayMedia 必须在用户手势事件(click、touchstart 等)中调用。异步回调里调用也不行。
29.2.3 权限被浏览器设置禁用
我在项目中遇到过,用户用的是公司电脑,IT 部门在组策略里禁用了屏幕共享。这种情况前端无能为力,只能提示用户联系管理员。
29.3 编解码器不支持
编解码器的问题,说白了就是发送端和接收端“说不到一块去”。你发的是 H.264,我只懂 VP8,那画面就出不来。
29.3.1 如何检测支持的编解码器
WebRTC 提供了 RTCRtpSender.getCapabilities() 和 RTCRtpReceiver.getCapabilities() 来查询。
// 查询发送端支持的视频编解码器
const videoCapabilities = RTCRtpSender.getCapabilities('video');
if (videoCapabilities) {
console.log('支持的编解码器:');
videoCapabilities.codecs.forEach(codec => {
console.log(codec.mimeType);
});
}
29.3.2 协商时的处理
我个人习惯,在创建 Offer 时指定优先使用的编解码器。如果接收端不支持,SDP 协商时会自动降级。
const pc = new RTCPeerConnection();
const transceiver = pc.addTransceiver('video', {
direction: 'sendonly',
sendEncodings: [
{ codec: 'video/H264' }
]
});
重要:不是所有浏览器都支持 H.264。Chrome 支持,但 Firefox 默认用 VP8。如果你要做跨浏览器屏幕共享,建议两端都支持 VP8 或 VP9。
29.3.3 我曾经遇到的案例
有一次,用户用 Safari 共享屏幕,接收端是 Chrome。画面一直黑屏。查了半天,发现 Safari 的屏幕共享默认输出的是 H.264,但 Chrome 接收端没有启用 H.264 解码。解决方案是在 Chrome 的 RTCPeerConnection 配置里显式启用 H.264。
29.4 跨域问题
跨域问题,在屏幕共享里其实不常见,但一旦出现就很隐蔽。它通常发生在你把屏幕流绘制到 canvas 上,或者用 MediaRecorder 录制的时候。
29.4.1 canvas 被污染
如果你把屏幕流画到 canvas 上,然后调用 canvas.toDataURL() 或 canvas.toBlob(),浏览器会抛出一个安全错误。因为屏幕流的内容可能包含跨域资源(比如用户打开了另一个域的页面)。
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 把屏幕流画到 canvas 上
ctx.drawImage(video, 0, 0);
// 下面这行会报错:Tainted canvases may not be exported
const dataUrl = canvas.toDataURL();
解决方案:如果不需要导出 canvas 内容,可以忽略这个错误。如果确实需要导出,可以考虑用 OffscreenCanvas 或者 WebGL 来绕过。
29.4.2 跨域 iframe 中的屏幕共享
如果你的页面嵌在 iframe 里,而且 iframe 的域名和父页面不同,getDisplayMedia 可能无法正常工作。这是因为浏览器对 iframe 的权限控制更严格。
我建议,屏幕共享功能尽量在主页面实现,不要在 iframe 里搞。如果非要在 iframe 里做,记得设置 allow="display-capture" 属性。
<iframe src="https://example.com/screen-share" allow="display-capture"></iframe>
29.5 知识体系总览
下面这张图,我把屏幕共享的四个常见问题以及它们的排查路径整理了一下。你可以把它当成一个速查表。
29.6 写在最后
屏幕共享的这四个问题,其实都有固定的排查套路。黑屏先看流有没有拿到,权限拒绝先看是不是用户手势触发的,编解码器不支持就看 SDP 协商日志,跨域问题基本只出现在 canvas 和 iframe 里。
我做了这么多年 WebRTC,最大的感受就是:别怕出问题,怕的是不知道怎么查。上面这些排查路径,都是我一个个坑踩出来的。你照着走一遍,90% 的问题都能定位到。
我的建议:在开发阶段就把这些错误处理逻辑写好。不要等到用户反馈了再补。尤其是权限拒绝和编解码器不支持,提前做好降级方案,用户体验会好很多。
公众号:蓝海资料掘金营,微信deep3321