WebRTC单向通信异常:SDP传递后接收端无画面且报错
我正在实现WebRTC单向通信,通过将发送方(Offer端)的SDP存入数据库,供接收方(Answer端)获取并建立连接。
Offer端代码如下:
const videoConstraints = { 'video': true, 'audio': false } var localStream = new MediaStream(); try { navigator.mediaDevices.getUserMedia(videoConstraints).then((stream) => { localStream = stream; video = document.getElementById("video"); video.srcObject = stream; }) } catch (error) { console.log(error); } const turnConfig = { iceServers: [ //servers ] } const createOfferer = () => { var peer = new RTCPeerConnection(turnConfig); localStream.getTracks().forEach(track => { peer.addTrack(track, localStream); }); peer.createOffer() .then((offer) => { fetch(window.location.href, { method: "POST", body: JSON.stringify(offer), headers: { "Content-Type": "application/json" } }); }) }
接收端获取SDP后调用createAnswererForSeat方法,代码如下:
const setOnTrack = (peer, remoteVideo) => { var remoteStream = new MediaStream(); remoteVideo.srcObject = remoteStream; peer.addEventListener('track', async (e) => { remoteStream.addTrack(e.track, remoteStream); }) } const createAnswererForSeat = (offer) => { var peer = new RTCPeerConnection(turnConfig); var remoteVideo = createVideoForSeat(); //this function only creates the video element setOnTrack(peer, remoteVideo); peer.setRemoteDescription(offer) .then(() => { return peer.createAnswer(); }) .then((answer) => { peer.setLocalDescription(answer); }); }
目前出现问题:Chrome浏览器中接收端无画面,Firefox浏览器中出现WebRTC错误。我是WebRTC新手,请问问题出在哪里?
1. Offer端未设置本地描述,发送的SDP不完整
调用peer.createOffer()后,必须先通过peer.setLocalDescription(offer)将Offer设置为本地描述,才能生成包含ICE候选相关信息的完整SDP。原代码直接发送原始Offer,导致SDP缺失关键信息,接收端无法正常处理。
修改后的Offer端createOfferer函数:
const createOfferer = () => { var peer = new RTCPeerConnection(turnConfig); // 先判断本地媒体流是否准备完成 if (!localStream.getTracks().length) { console.error("本地媒体流未准备好"); return; } localStream.getTracks().forEach(track => { peer.addTrack(track, localStream); }); peer.createOffer() .then((offer) => { return peer.setLocalDescription(offer); // 先设置本地描述 }) .then(() => { // 发送完整的本地描述SDP fetch(window.location.href, { method: "POST", body: JSON.stringify(peer.localDescription), headers: { "Content-Type": "application/json" } }); }) .catch(err => console.error("创建Offer失败:", err)); // 增加错误捕获 }
2. 缺少ICE候选的交换逻辑
WebRTC需要交换ICE候选才能建立实际的网络连接,两端都需要监听icecandidate事件,收集候选信息并通过数据库完成交换。
- Offer端添加ICE候选监听:
peer.addEventListener('icecandidate', (e) => { if (e.candidate) { // 将候选信息存入数据库,供接收端获取 fetch('/store-ice-candidate', { method: "POST", body: JSON.stringify({ candidate: e.candidate, type: "offer" }), headers: { "Content-Type": "application/json" } }); } });
- 接收端获取到Offer端的候选后,调用
peer.addIceCandidate(candidate)添加;同时接收端也要监听自己的icecandidate事件,将候选发送给Offer端添加。
3. Answer端未将Answer发送回Offer端
Answer端创建Answer并设置本地描述后,必须将Answer的SDP发送给Offer端,让Offer端调用peer.setRemoteDescription(answer),否则连接无法完成握手。
修改后的Answer端createAnswererForSeat函数:
const createAnswererForSeat = (offer) => { var peer = new RTCPeerConnection(turnConfig); var remoteVideo = createVideoForSeat(); setOnTrack(peer, remoteVideo); peer.setRemoteDescription(offer) .then(() => { return peer.createAnswer(); }) .then((answer) => { return peer.setLocalDescription(answer); }) .then(() => { // 将Answer发送回Offer端(存入数据库) fetch('/store-answer', { method: "POST", body: JSON.stringify(peer.localDescription), headers: { "Content-Type": "application/json" } }); }) .catch(err => console.error("创建Answer失败:", err)); // 增加错误捕获 // 监听ICE候选,发送给Offer端 peer.addEventListener('icecandidate', (e) => { if (e.candidate) { fetch('/store-ice-candidate', { method: "POST", body: JSON.stringify({ candidate: e.candidate, type: "answer" }), headers: { "Content-Type": "application/json" } }); } }); }
4. 媒体流准备时机问题
Offer端的createOfferer可能在getUserMedia完成前被调用,导致localStream为空,没有添加任何媒体轨道。建议将createOfferer的调用移到getUserMedia的回调中:
navigator.mediaDevices.getUserMedia(videoConstraints).then((stream) => { localStream = stream; video = document.getElementById("video"); video.srcObject = stream; // 媒体流准备好后再创建Offer createOfferer(); })
5. 缺失错误捕获逻辑
原代码中大量Promise操作未添加catch,导致Firefox的错误无法被捕获排查。所有异步操作都要添加错误处理,方便定位问题。
内容的提问来源于stack exchange,提问作者Octavian Niculescu

