10、屏幕共享接收端:创建远程视频元素、处理track事件、自动播放策略
好,咱们接着聊。上一章我们把发送端的逻辑理清楚了,这一章轮到接收端了。
说实话,接收端看起来比发送端简单,但坑一点都不少。我最早做屏幕共享时,就栽在自动播放策略上——明明代码写对了,画面就是出不来。后来才发现,是浏览器把播放给拦截了。
这一章,我们就来搞定三件事:创建远程视频元素、处理 track 事件、应对自动播放策略。这三步走完,接收端就能正常显示共享画面了。
10.1 创建远程视频元素
接收端要显示画面,得先有一个 <video> 元素。这个元素可以是 HTML 里写死的,也可以是 JS 动态创建的。
我个人习惯用动态创建。为什么呢?因为屏幕共享可能有多个接收端,或者一个页面里同时有摄像头视频和屏幕共享视频。动态创建更灵活,也好管理。
来看代码:
// 创建视频元素
const remoteVideo = document.createElement('video');
remoteVideo.id = 'remote-screen-' + Date.now();
remoteVideo.autoplay = true;
remoteVideo.playsInline = true;
remoteVideo.style.width = '100%';
remoteVideo.style.maxWidth = '1280px';
remoteVideo.style.border = '1px solid #ccc';
remoteVideo.style.borderRadius = '8px';
// 挂到 DOM 上
const container = document.getElementById('video-container');
container.appendChild(remoteVideo);
// 返回元素引用,后面要用
return remoteVideo;
这里有几个关键点:
autoplay:必须设为true,否则视频不会自动播放。但注意,这只是告诉浏览器“我想自动播放”,能不能成功,还得看浏览器的自动播放策略。playsInline:iOS Safari 上必须加。不加的话,视频会全屏播放,用户体验很差。- 样式:给个边框和圆角,视觉上更友好。宽度用百分比,适配不同屏幕。
id 加上时间戳来区分。这样后续操作特定视频元素时,直接通过 id 查找就行。
10.2 处理 track 事件
视频元素准备好了,接下来就是把远程的媒体流塞进去。
在 WebRTC 中,接收端通过 RTCPeerConnection 的 ontrack 事件来获取远程流。这个事件会在每次收到新的 track 时触发。
我记得第一次做这个时,以为 ontrack 只会触发一次。后来发现,如果发送端中途切换了屏幕共享源(比如从整个屏幕切换到某个窗口),ontrack 会再次触发,而且会带上新的流。
所以,处理逻辑要考虑到流替换的情况。
标准写法如下:
// 假设 pc 是已经建立好的 RTCPeerConnection
// remoteVideo 是上一步创建的视频元素
pc.ontrack = function(event) {
console.log('收到远程 track,类型:', event.track.kind);
// 只处理 video track
if (event.track.kind !== 'video') return;
// 获取远程流
const remoteStream = event.streams[0];
if (remoteStream) {
// 把流设置到视频元素上
remoteVideo.srcObject = remoteStream;
console.log('远程流已绑定到视频元素');
} else {
console.warn('没有收到远程流,尝试直接绑定 track');
// 某些浏览器可能不提供 streams 数组
// 可以用 MediaStream 手动包装
const stream = new MediaStream();
stream.addTrack(event.track);
remoteVideo.srcObject = stream;
}
};
event.streams[0] 是大多数浏览器提供的标准方式。但有些旧版本浏览器可能不返回 streams 数组。保险起见,可以加一个 fallback,手动创建 MediaStream 并添加 track。
还有一个细节:track 的 muted 状态。当远程端暂停共享时,track 会变成 muted。你可以监听这个事件,在 UI 上给出提示。
event.track.onmuted = function() {
console.log('远程共享已暂停');
// 显示一个“对方已暂停共享”的提示
};
event.track.onunmuted = function() {
console.log('远程共享已恢复');
// 隐藏提示
};
这个功能虽然不是必须的,但能提升用户体验。我在做企业协作工具时加了这功能,用户反馈很好——至少他们知道是对方暂停了,而不是自己这边出了问题。
10.3 自动播放策略
好,到了最头疼的部分了。
自动播放策略,说白了就是浏览器为了防止网页自动播放声音打扰用户,制定的一套规则。对于视频元素,规则是这样的:
| 场景 | 是否允许自动播放 | 说明 |
|---|---|---|
| 视频没有音频 track | ✅ 允许 | 屏幕共享通常没有音频,所以一般没问题 |
| 视频有音频 track,且用户已与页面交互 | ✅ 允许 | 用户点击过页面后,自动播放就放开了 |
| 视频有音频 track,用户未交互 | ❌ 禁止 | 浏览器会暂停播放,需要用户手动触发 |
| 静音状态下自动播放 | ✅ 允许(部分浏览器) | Chrome 允许静音自动播放,Safari 可能不行 |
屏幕共享的场景下,大多数时候是没有音频的。所以自动播放通常能成功。但如果你在共享屏幕的同时也共享了系统音频(这个后面章节会讲),那就要小心了。
我曾经遇到过一个 case:用户共享了某个标签页,那个标签页里有视频在播放,所以共享流里带了音频。结果接收端死活不自动播放。排查了半天,发现是自动播放策略在作怪。
解决方案有两种:
方案一:静音播放(推荐)
// 创建视频元素时,先静音
remoteVideo.muted = true;
remoteVideo.autoplay = true;
// 等用户交互后,再取消静音
document.addEventListener('click', function() {
remoteVideo.muted = false;
}, { once: true });
这个方案的好处是:100% 能自动播放。因为静音视频不受自动播放策略限制。等用户点击页面后,再取消静音,体验上完全没问题。
方案二:检测播放状态,手动恢复
// 尝试自动播放
const playPromise = remoteVideo.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// 自动播放成功
console.log('自动播放成功');
}).catch(error => {
// 自动播放被阻止
console.warn('自动播放被阻止,需要用户交互', error);
// 显示一个“点击播放”的按钮
showPlayButton();
});
}
这个方案更精细。它利用了 play() 方法返回的 Promise。如果自动播放被阻止,Promise 会 reject,这时候你再显示一个播放按钮让用户手动点击。
autoplay 属性一定能生效。它只是一个“请求”,浏览器有权拒绝。一定要做好 fallback 处理。
10.4 完整流程与 SVG 图解
好了,我们把这三步串起来,看看接收端的完整流程:
- 创建
RTCPeerConnection,设置 ICE 候选者和 SDP 交换逻辑 - 创建
<video>元素,设置autoplay和playsInline - 监听
ontrack事件,将远程流绑定到视频元素 - 处理自动播放策略,确保画面能正常显示
下面这张图展示了接收端的核心逻辑:
从图上可以看得很清楚:接收端的核心就是三步走,中间有一个关键分支——有没有音频。这个分支决定了自动播放能不能直接成功。
10.5 避坑指南
最后,分享几个我踩过的坑:
- 不要忘记
playsInline:iOS 上不加这个属性,视频会强制全屏播放,用户根本看不到共享画面。 srcObject赋值时机:一定要在ontrack事件中赋值,不要提前赋值。提前赋值会导致视频元素处于“等待数据”状态,可能触发浏览器的自动播放拦截。- 多个 track 的处理:如果接收端同时接收了摄像头和屏幕共享,
ontrack会触发两次。记得用event.track.kind区分,分别绑定到不同的视频元素。 - 页面可见性:如果用户切换到其他标签页,浏览器可能会暂停视频播放。可以监听
visibilitychange事件,在页面重新可见时调用remoteVideo.play()恢复播放。
muted,等用户点击页面后再取消静音。这样既保证了自动播放成功,又不会在用户没准备好时突然出声。用户体验最好。
嗯,接收端的内容就这些。代码量不大,但细节不少。尤其是自动播放策略,一定要在开发阶段就考虑进去,别等到测试了才发现画面出不来。
下一章我们会聊一聊屏幕共享中的动态切换共享源——比如用户想从共享整个屏幕切换到共享某个窗口,怎么做到无缝切换。这个功能在实际产品中非常常见,也很有挑战性。
公众号:蓝海资料掘金营,微信deep3321