如何通过单个RTCPeerConnection传输多路WebRTC视频流?
单个RTCPeerConnection传输多路视频流的实现方案
要在单个RTCPeerConnection里传输多路视频流,核心要解决两个问题:放弃Plan-B,切换到Unified Plan SDP语义,以及正确处理多路Track/Stream的接收逻辑。结合你的现有代码,修改步骤如下:
1. 切换到Unified Plan
Plan-B是旧的SDP格式,对多路媒体流的支持有限,Unified Plan是WebRTC当前的标准,原生支持多路流传输。修改RTCPeerConnection的初始化配置:
var webrtc_con = new RTCPeerConnection({ iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }], sdpSemantics: 'unified-plan' // 替换为标准的Unified Plan })
2. 配置多路视频Transceiver
如果提前知晓需要接收的视频路数,可以主动添加对应数量的视频Transceiver;如果是服务器动态推送多路流,也可以省略主动添加,让WebRTC自动协商。这里以主动添加2路为例:
// 添加两个视频transceiver,方向设为recvonly(仅接收,无需发送本地流) webrtc_con.addTransceiver('video', {direction: 'recvonly'}) webrtc_con.addTransceiver('video', {direction: 'recvonly'})
3. 修改ontrack事件处理逻辑
原来的代码直接将第一个流赋值给单个video元素,多路流场景下需要为每路流分配独立的播放载体。你可以提前在页面准备多个video标签,或者动态创建:
// 用Map存储流ID与对应video元素的映射 const streamVideoMap = new Map(); webrtc_con.ontrack = function (event) { const stream = event.streams[0]; let videoEl; // 检查该流是否已有对应的video元素 if (!streamVideoMap.has(stream.id)) { // 动态创建video元素 videoEl = document.createElement('video'); videoEl.style.width = '320px'; videoEl.style.height = '240px'; videoEl.autoplay = true; videoEl.muted = true; // 规避浏览器自动播放限制 document.body.appendChild(videoEl); streamVideoMap.set(stream.id, videoEl); } else { videoEl = streamVideoMap.get(stream.id); } // 将流绑定到对应的video元素 videoEl.srcObject = stream; }
4. 确保服务器端适配
你的DeepEch rtsotowebrtc服务器需要配置为支持Unified Plan,并且允许在单个PeerConnection会话中发送多路视频流。如果服务器默认使用Plan-B,需要调整配置切换到Unified Plan。
修改后的完整代码示例
// 初始化PeerConnection,使用Unified Plan语义 var webrtc_con = new RTCPeerConnection({ iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }], sdpSemantics: 'unified-plan' }) // 存储流与视频元素的映射关系 const streamVideoMap = new Map(); webrtc_con.ontrack = function (event) { const stream = event.streams[0]; let videoEl; if (!streamVideoMap.has(stream.id)) { videoEl = document.createElement('video'); videoEl.style.width = '320px'; videoEl.style.height = '240px'; videoEl.autoplay = true; videoEl.muted = true; document.body.appendChild(videoEl); streamVideoMap.set(stream.id, videoEl); } else { videoEl = streamVideoMap.get(stream.id); } videoEl.srcObject = stream; } // 添加两个视频接收transceiver webrtc_con.addTransceiver('video', {direction: 'recvonly'}) webrtc_con.addTransceiver('video', {direction: 'recvonly'}) webrtc_con.onnegotiationneeded = async function handleNegotiationNeeded() { try { const offer = await webrtc_con.createOffer() await webrtc_con.setLocalDescription(offer) const response = await fetch(url, { method: 'POST', body: new URLSearchParams({data: btoa(webrtc_con.localDescription.sdp)}) }) const data = await response.text() await webrtc_con.setRemoteDescription( new RTCSessionDescription({type: 'answer', sdp: atob(data)}) ) } catch (e) { console.warn('协商失败:', e) if (e.message.includes('NotFound')) { console.warn(`Stream ${url} Not Found!`) } else { console.warn('Server maybe offline?') } } }
内容的提问来源于stack exchange,提问作者Milan Bültmann
相关产品推荐
相关产品推荐

