如何修复WebRTC JavaScript中'setLocalDescription执行失败'错误?
WebRTC视频通话APP中setLocalDescription报错:Called in wrong state: stable
我正在开发基于WebRTC的视频通话APP,用JavaScript实现时遇到如下错误:
main.js:104 Uncaught (in promise) DOMException: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local answer sdp: Called in wrong state: stable
后端使用Flask SocketIO,Socket通信正常,问题出在peerConnection.setRemoteDescription(offer)环节,原始代码如下:
let localStream; let remoteStream; let peerConnection; // const servers = { // iceServers: [ // { // urls: ["stun:stun.l.google.com:19302"] // } // ] // } var socket = io.connect("http://localhost:5000"); const urlParams = new URLSearchParams(window.location.search); roomId = urlParams.get("room"); name = urlParams.get("name"); socket.emit("join-channel", { room: roomId }); socket.on("room-message", function (message) { handleMessage(message, roomId); }); let init = async () => { socket.on("MemberJoined", (data) => { handleUserJoined(roomId); }); localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); document.getElementById("vid-1").srcObject = localStream; } let handleUserJoined = async (roomId) => { console.log('A new user joined the room: ', roomId); createOffer(roomId); } let handleMessage = async (message, roomId) => { message = JSON.parse(JSON.parse(message).text); if (message.type === "offer"){ createAnswer(roomId, message.offer); } if (message.type === "answer"){ addAnswer(message.answer); } if (message.type === "candidate"){ if (peerConnection){ peerConnection.addIceCandidate(message.candidate); } } } let createPeerConnection = async (roomId) => { peerConnection = new RTCPeerConnection({'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}); remoteStream = new MediaStream(); document.getElementById("vid-2").srcObject = remoteStream; if (!localStream) { localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); document.getElementById("vid-1").srcObject = localStream; } localStream.getTracks().forEach((track) => { peerConnection.addTrack(track, localStream); }) peerConnection.ontrack = (event) => { event.streams[0].getTracks().forEach((track) => { remoteStream.addTrack(track); }) } peerConnection.onicecandidate = async (event) => { if (event.candidate) { socket.emit('room-message', { room: roomId, message: { text: JSON.stringify({ "type": 'candidate', "candidate": event.candidate }) } }) } } } let createOffer = async (roomId) => { await createPeerConnection(roomId); let offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer) socket.emit('room-message', { room: roomId, message: { text: JSON.stringify({ "type": 'offer', "offer": offer }) } }) } let createAnswer = async (roomId, offer) => { await createPeerConnection(roomId); await peerConnection.setRemoteDescription(offer); // Set remote description first let answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); socket.emit('room-message', { room: roomId, message: { text: JSON.stringify({ "type": "answer", "answer": answer }) } }); }; let addAnswer = async (answer) => { if (!peerConnection.currentRemoteDescription){ await peerConnection.setRemoteDescription(answer); } } init();
错误原因分析
- 重复初始化PeerConnection:全局
peerConnection变量被多次覆盖,导致连接状态混乱。当新用户加入或收到offer时,都会重新创建PeerConnection,旧连接的状态未被清理,触发状态冲突。 - 冗余的JSON解析:
message = JSON.parse(JSON.parse(message).text);执行了两次JSON解析,可能导致数据格式异常,影响后续SDP处理。 - 无限制的Offer创建:只要有用户加入就触发
createOffer,会导致同一房间内多个Offer发送,引发连接状态冲突。 - 缺少连接状态校验:在创建Offer/Answer前未检查是否已有活跃连接,重复操作导致PeerConnection处于stable状态时被强制修改描述。
修改后的代码
let localStream; let remoteStream; let peerConnection; let isConnectionEstablished = false; // 新增连接状态标记 var socket = io.connect("http://localhost:5000"); const urlParams = new URLSearchParams(window.location.search); const roomId = urlParams.get("room"); const name = urlParams.get("name"); socket.emit("join-channel", { room: roomId }); socket.on("room-message", handleMessage); let init = async () => { socket.on("MemberJoined", handleUserJoined); localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); document.getElementById("vid-1").srcObject = localStream; } let handleUserJoined = async () => { console.log('新用户加入房间'); // 仅当未建立连接时才创建Offer if (!isConnectionEstablished && !peerConnection) { await createOffer(); } } let handleMessage = async (message) => { // 修正JSON解析:仅解析一次(根据后端实际返回格式调整) const parsedMessage = JSON.parse(message.text); switch(parsedMessage.type) { case "offer": if (!isConnectionEstablished && !peerConnection) { await createAnswer(parsedMessage.offer); } break; case "answer": if (peerConnection && !peerConnection.currentRemoteDescription) { await peerConnection.setRemoteDescription(parsedMessage.answer); } break; case "candidate": if (peerConnection) { try { await peerConnection.addIceCandidate(parsedMessage.candidate); } catch (e) { console.warn('添加ICE候选失败:', e); } } break; } } let createPeerConnection = async () => { peerConnection = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }); remoteStream = new MediaStream(); document.getElementById("vid-2").srcObject = remoteStream; localStream.getTracks().forEach((track) => { peerConnection.addTrack(track, localStream); }); peerConnection.ontrack = (event) => { event.streams[0].getTracks().forEach((track) => { remoteStream.addTrack(track); }); }; peerConnection.onicecandidate = async (event) => { if (event.candidate) { socket.emit('room-message', { room: roomId, message: { text: JSON.stringify({ type: 'candidate', candidate: event.candidate }) } }); } }; // 监听连接状态变化,标记连接状态 peerConnection.onconnectionstatechange = () => { if (peerConnection.connectionState === 'connected') { isConnectionEstablished = true; } else if (peerConnection.connectionState === 'disconnected' || peerConnection.connectionState === 'failed') { isConnectionEstablished = false; peerConnection = null; } }; } let createOffer = async () => { await createPeerConnection(); const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); socket.emit('room-message', { room: roomId, message: { text: JSON.stringify({ type: 'offer', offer: offer }) } }); } let createAnswer = async (offer) => { await createPeerConnection(); await peerConnection.setRemoteDescription(offer); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); socket.emit('room-message', { room: roomId, message: { text: JSON.stringify({ type: "answer", answer: answer }) } }); }; init();
关键修改点说明
- 添加连接状态标记:新增
isConnectionEstablished变量,避免重复创建连接和发送Offer/Answer。 - 修正JSON解析逻辑:移除冗余的
JSON.parse,确保消息解析正确。 - PeerConnection状态管理:监听
onconnectionstatechange事件,实时更新连接状态,在连接断开时重置变量。 - 限制Offer/Answer创建时机:仅当无活跃连接时才允许创建新的Offer或Answer,避免状态冲突。
- 错误捕获:对
addIceCandidate添加异常捕获,避免单个候选失败导致整个流程中断。
内容的提问来源于stack exchange,提问作者Muhammad Imran Hossain
相关产品推荐
相关产品推荐

