WebRTC连接后仅单端摄像头可用问题求助
WebRTC单向视频流问题排查与修复
我开发了一个通过WebSocket传输SDP描述和ICE候选、自动建立用户连接的程序,但连接完成后仅一侧摄像头正常工作,另一端的媒体流摄像头处于未激活状态。已经核对过ICE候选信息、验证了SDP描述的正确性,也确认信令通信流程正常,但无法定位问题根源。相关代码如下:
App.jsx
import { useEffect } from 'react' import { getMedia, connectionData} from './connection' import './App.css' function App() { useEffect(() => { connectionData.localStream = document.getElementById("localVideoStream"); connectionData.remoteStream = document.getElementById("remoteStream"); getMedia(); }, []) return ( <div> <video id ="localVideoStream" autoPlay></video> <video id = "remoteStream" autoPlay style={{ height: `${250}px`, width: `${250}px`, border: `solid ${1}px black` }}></video> </div> ) } export default App
connection.js
const webSocket = new WebSocket("ws://localhost:3000/"); let connectionData = { pcConnection: new RTCPeerConnection(null), remoteStream: null, localStream: null, connectionCount: 0, } let candidateManager ={ sendet:false, candidateAdded:false }; let candidateSendet = false; //für später let connectionDataList = []; //web Socket funktionen webSocket.onopen = function (e) { createOffer(); } webSocket.onmessage = function (e) { let sdpObject = JSON.parse(e.data); if (sdpObject.type == "offer") { console.log("offer"); setRemoteDescription(sdpObject); createAnswer(); } if (sdpObject.type == "answer") { console.log("answer"); setRemoteDescription(sdpObject); } if (sdpObject.type == "candidate") { console.log("candidate"); console.log(e); //console.log("streams: ", connectionData.remoteStream.srcObject); setCandidate(sdpObject.candidate); } } //stream functions async function getMedia() { const constraints = { audio: false, video: true, } await navigator.mediaDevices.getUserMedia(constraints) .then(stream => { connectionData.localStream.srcObject = stream; stream.getTracks().forEach(track => { //console.log("user Media", stream); connectionData.pcConnection.addTrack(track, stream); }) }) } function createOffer() { connectionData.pcConnection.createOffer({ offerToReceiveAudio: 1, offerToReceiveVideo: 1, }).then(async sdp => { webSocket.send(JSON.stringify(sdp)); await connectionData.pcConnection.setLocalDescription(sdp); candidateManager.sendet=false; }); } function createAnswer() { connectionData.pcConnection.createAnswer({ offerToReceiveAudio: 1, offerToReceiveVideo: 1, }).then(async sdp => { webSocket.send(JSON.stringify(sdp)); await connectionData.pcConnection.setLocalDescription(sdp); }); } async function setRemoteDescription(sdp) { await connectionData.pcConnection.setRemoteDescription(new RTCSessionDescription(sdp)) } function setCandidate(candidate) { if (connectionData.pcConnection.remoteDescription != null) { connectionData.pcConnection.addIceCandidate(new RTCIceCandidate(candidate)).catch((e)=>{ console.log(`failure MESSAGE:${e.name}`) }); console.log(connectionData.remoteStream.srcObject); } } connectionData.pcConnection.onicecandidate = function (e) { if (e.candidate != null) { const candidate = { type: "candidate", candidate: e.candidate } if (candidate.candidate.protocol == "udp" && candidateManager.sendet==false) { webSocket.send(JSON.stringify(candidate)); console.log("sendet Candidate",candidate); candidateManager.sendet = true; } } } connectionData.pcConnection.ontrack = (e) => { console.log("onTrack", e.streams[0]); connectionData.remoteStream.srcObject = e.streams[0]; //console.log("remoteStream: ", e.streams); } export { webSocket, connectionData, getMedia, connectionDataList, candidateSendet }
问题根源分析
- ICE候选发送限制过严:当前代码仅发送第一个UDP候选,且发送后就停止发送后续候选。WebRTC需要交换多个ICE候选来适配不同网络环境,单个候选可能无法建立双向媒体流连接,导致一侧无法接收视频。
- 异步逻辑不规范:
getMedia混用await和.then,且无错误捕获,若媒体流获取失败会静默失败,导致后续addTrack无有效流。 - 信令顺序问题:创建Offer/Answer时先发送SDP再设置本地描述,可能导致本地状态未同步就发送信令,引发状态不一致。
- 错误处理不完整:
setRemoteDescription等关键操作未完整捕获错误,不利于排查潜在问题。
修复方案
1. 取消ICE候选发送限制
发送所有收集到的UDP候选,直到候选收集完成:
connectionData.pcConnection.onicecandidate = function (e) { if (e.candidate != null) { const candidate = { type: "candidate", candidate: e.candidate }; if (candidate.candidate.protocol === "udp") { webSocket.send(JSON.stringify(candidate)); console.log("sent Candidate", candidate); } } else { // 可选:发送候选收集完成标识 webSocket.send(JSON.stringify({ type: "candidate-done" })); } };
2. 规范异步逻辑并添加错误处理
修改getMedia为纯async/await写法,添加错误捕获:
async function getMedia() { const constraints = { audio: false, video: true, }; try { const stream = await navigator.mediaDevices.getUserMedia(constraints); connectionData.localStream.srcObject = stream; stream.getTracks().forEach(track => { connectionData.pcConnection.addTrack(track, stream); }); } catch (err) { console.error("获取媒体流失败:", err); } }
3. 调整信令顺序
先设置本地描述再发送SDP,确保本地状态同步:
async function createOffer() { try { const sdp = await connectionData.pcConnection.createOffer({ offerToReceiveAudio: 1, offerToReceiveVideo: 1, }); await connectionData.pcConnection.setLocalDescription(sdp); webSocket.send(JSON.stringify(sdp)); } catch (err) { console.error("创建Offer失败:", err); } } async function createAnswer() { try { const sdp = await connectionData.pcConnection.createAnswer({ offerToReceiveAudio: 1, offerToReceiveVideo: 1, }); await connectionData.pcConnection.setLocalDescription(sdp); webSocket.send(JSON.stringify(sdp)); } catch (err) { console.error("创建Answer失败:", err); } }
4. 完善关键操作的错误处理
给setRemoteDescription添加错误捕获:
async function setRemoteDescription(sdp) { try { await connectionData.pcConnection.setRemoteDescription(new RTCSessionDescription(sdp)); } catch (err) { console.error("设置远端描述失败:", err); } }
5. 优化ontrack逻辑
避免直接覆盖srcObject,已有流时添加轨道:
connectionData.pcConnection.ontrack = (e) => { console.log("onTrack", e.streams[0]); if (!connectionData.remoteStream.srcObject) { connectionData.remoteStream.srcObject = e.streams[0]; } else { e.streams[0].getTracks().forEach(track => { connectionData.remoteStream.srcObject.addTrack(track); }); } };
内容的提问来源于stack exchange,提问作者Razielruss
相关产品推荐
相关产品推荐

