WebRTC断开后如何与同一对等端重连 需符合官方文档的代码示例
WebRTC 同一对等端断开重连实现
核心改造点
- 持久化存储房间ID、目标用户、本地媒体流等重连必需的上下文数据
- 新增重连锁、重连次数限制、防抖逻辑,避免重复触发重连
- 断开时先销毁旧的
RTCPeerConnection实例,再复用原有上下文重新发起连接
完整可运行代码
// 全局上下文存储,重连时复用 let peerConnection = null; let localStream = null; // 提前通过getUserMedia/getDisplayMedia获取,存在此处复用 let remoteStream = null; let currentRoomId = null; // 加入房间时存储 let targetUser = null; // 发起连接时存储目标对等端信息 let reconnectTimer = null; let isReconnecting = false; const MAX_RECONNECT_ATTEMPTS = 5; let reconnectCount = 0; const configuration = { iceServers: [ { urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"], }, ], iceCandidatePoolSize: 20, }; // 发起方创建连接 const createPeer = async (user, ls, rs) => { if (isReconnecting) return; isReconnecting = true; targetUser = user; localStream = ls; remoteStream = rs; // 销毁旧实例 if (peerConnection) { peerConnection.close(); peerConnection = null; } try { peerConnection = new RTCPeerConnection(configuration); // 添加本地流轨道 localStream.getTracks().forEach(track => { peerConnection.addTrack(track, localStream); }); // 接收远端轨道 peerConnection.ontrack = (event) => { event.streams[0].getTracks().forEach(track => { remoteStream.addTrack(track); }); }; // 注册监听器 registerPeerConnectionListeners(); // 创建offer const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); // ---------- 此处对接你的信令服务,将offer发送给目标对等端 ---------- // sendToSignalingServer({ type: 'offer', data: offer, targetUser: user }) reconnectCount = 0; } catch (e) { console.error('创建连接失败:', e); isReconnecting = false; } }; // 接收方加入连接 const joinPeer = async (roomId, ls, rs) => { if (isReconnecting) return; isReconnecting = true; currentRoomId = roomId; localStream = ls; remoteStream = rs; if (peerConnection) { peerConnection.close(); peerConnection = null; } try { peerConnection = new RTCPeerConnection(configuration); localStream.getTracks().forEach(track => { peerConnection.addTrack(track, localStream); }); peerConnection.ontrack = (event) => { event.streams[0].getTracks().forEach(track => { remoteStream.addTrack(track); }); }; registerPeerConnectionListeners(); // ---------- 此处对接你的信令服务,获取发起方发送的offer ---------- // const offer = await getOfferFromSignalingServer(roomId) // await peerConnection.setRemoteDescription(new RTCSessionDescription(offer)) // const answer = await peerConnection.createAnswer() // await peerConnection.setLocalDescription(answer) // ---------- 此处对接你的信令服务,将answer发送给发起方 ---------- // sendToSignalingServer({ type: 'answer', data: answer, roomId: roomId }) reconnectCount = 0; } catch (e) { console.error('加入连接失败:', e); isReconnecting = false; } }; // 注册连接状态监听器 const registerPeerConnectionListeners = () => { // 收集ICE候选并发送 peerConnection.addEventListener('icecandidate', (event) => { if (event.candidate) { // ---------- 此处对接你的信令服务,发送ICE候选到对等端 ---------- // sendToSignalingServer({ type: 'icecandidate', data: event.candidate }) } }); peerConnection.addEventListener("connectionstatechange", () => { const state = peerConnection.connectionState; console.log(`Connection state change: ${state}`); if (state === "connected") { isReconnecting = false; reconnectCount = 0; if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } return; } if (state === "disconnected" || state === "failed") { remoteStream?.getTracks().forEach(track => track.stop()); remoteStream = null; isReconnecting = false; // 触发重连逻辑,加2秒防抖避免频繁触发 if (reconnectCount < MAX_RECONNECT_ATTEMPTS && !reconnectTimer) { reconnectTimer = setTimeout(() => { reconnectCount++; console.log(`发起第${reconnectCount}次重连`); // 发起方调用createPeer,接收方调用joinPeer if (targetUser) { createPeer(targetUser, localStream, new MediaStream()); } else if (currentRoomId) { joinPeer(currentRoomId, localStream, new MediaStream()); } reconnectTimer = null; }, 2000); } else if (reconnectCount >= MAX_RECONNECT_ATTEMPTS) { console.error('重连次数已达上限,停止重连'); reconnectCount = 0; } } else { remoteStream = new MediaStream(); } }); peerConnection.addEventListener("iceconnectionstatechange", () => { console.log(`ICE connection state change: ${peerConnection.iceConnectionState}`); }); }; // 信令服务收到answer后的处理逻辑(你需要在信令回调中调用此方法) const handleReceiveAnswer = async (answer) => { if (!peerConnection) return; await peerConnection.setRemoteDescription(new RTCSessionDescription(answer)); }; // 信令服务收到ICE候选后的处理逻辑(你需要在信令回调中调用此方法) const handleReceiveIceCandidate = async (candidate) => { if (!peerConnection) return; await peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); };
注意事项
- 代码中标记了信令对接的位置,替换成项目中现有信令传输逻辑即可正常运行
- 本地媒体流可以直接复用,不需要重新调用
getUserMedia申请权限,不会重复触发用户授权弹窗 - 如果需要调整重连次数和重连间隔,修改
MAX_RECONNECT_ATTEMPTS和setTimeout的时间参数即可
内容的提问来源于stack exchange,提问作者Hemendra Khatik
相关产品推荐
相关产品推荐

