WebRTC视频通话开发中‘Cannot Signal After Peer Is Destroyed’错误排查
解决WebRTC + simple-peer + Next.js中"Cannot Signal After Peer Is Destroyed"错误
我正在使用WebRTC、simple-peer与Next.js开发群组视频通话网站,采用Node.js搭建信令服务器实现Peer节点连接,但运行时始终出现“Cannot Signal After Peer Is Destroyed”错误。以下是房间页面代码与信令服务器代码,恳请协助定位并解决问题:
房间页面代码
'use client' import React, { useEffect, useRef, useState } from "react"; import { useRouter } from 'next/navigation'; // Fix typo in import statement import io from "socket.io-client"; import Peer from "simple-peer"; import styled from "styled-components"; const Container = styled.div` padding: 20px; display: flex; height: 100vh; width: 90%; margin: auto; flex-wrap: wrap; `; const StyledVideo = styled.video` height: 40%; width: 50%; `; const Video = (props) => { const ref = useRef(); useEffect(() => { props.peer.on("stream", stream => { ref.current.srcObject = stream; }); }, [props.peer]); return ( <StyledVideo playsInline autoPlay ref={ref} /> ); } const Room = () => { const router = useRouter(); const [peers, setPeers] = useState([]); const socketRef = useRef(); const userVideo = useRef(); const peersRef = useRef([]); const id = router.query; // Use router.query to get the room ID const roomID = 123 useEffect(() => { console.log(id) const videoConstraints = { height: window.innerHeight / 4, width: window.innerWidth / 4 }; socketRef.current = io.connect("http://localhost:8000"); navigator.mediaDevices.getUserMedia({ video: videoConstraints, audio: true }).then(stream => { userVideo.current.srcObject = stream; socketRef.current.emit("join room", roomID); socketRef.current.on("all users", users => { const peers = []; users.forEach(userID => { if (userID !== socketRef.current.id) { const peer = createPeer(userID, socketRef.current.id, stream); peersRef.current.push({ peerID: userID, peer, }); peers.push(peer); } }); setPeers(peers); }); socketRef.current.on("user joined", payload => { const peer = addPeer(payload.signal, payload.callerID, stream); peersRef.current.push({ peerID: payload.callerID, peer, }); setPeers(users => [...users, peer]); }); socketRef.current.on("receiving returned signal", payload => { const item = peersRef.current.find(p => p.peerID === payload.id); if (item) { item.peer.signal(payload.signal); } }); }); }, [roomID]); function createPeer(userToSignal, callerID, stream) { const peer = new Peer({ initiator: true, trickle: false, stream, }); peer.on("signal", signal => { socketRef.current.emit("sending signal", { userToSignal, callerID, signal }); }); return peer; } function addPeer(incomingSignal, callerID, stream) { const peer = new Peer({ initiator: false, trickle: false, stream, }); peer.on("signal", signal => { socketRef.current.emit("returning signal", { signal, callerID }); }); peer.signal(incomingSignal); return peer; } return ( <div className="bg-gray-100 min-h-screen flex flex-col"> <h1 className="text-2xl font-semibold p-4">Video Chat Room</h1> <div className="video-container p-4 flex-1"> {userVideo && <StyledVideo muted ref={userVideo} autoPlay playsInline />} {peers.map((peer, index) => ( <Video key={index} peer={peer} /> ))} </div> {/* Chat bar */} <div className="bg-white p-4 border-t border-gray-300"> <div className="chat-messages"></div> <div className="flex mt-2"> <input type="text" placeholder="Type a message..." className="p-2 border border-gray-300 rounded-lg flex-1" /> <button className="ml-2 bg-blue-500 text-white px-4 py-2 rounded-lg" > Send </button> </div> </div> <div className="p-4"> <button className="bg-blue-500 text-white px-4 py-2 rounded-lg" > </button> <button className="ml-2 bg-blue-500 text-white px-4 py-2 rounded-lg" > </button> </div> </div> ); }; export default Room;
信令服务器代码
require('dotenv').config(); const express = require("express"); const http = require("http"); const app = express(); const cors = require("cors"); app.use(cors) const server = http.createServer(app); const socket = require("socket.io"); const io = socket(server, { cors: { origin: "http://localhost:3000", methods: ["GET", "POST"], }, }); const users = {}; const socketToRoom = {}; io.on('connection', socket => { socket.on("join room", roomID => { if (users[roomID]) { const length = users[roomID].length; if (length === 4) { socket.emit("room full"); return; } users[roomID].push(socket.id); } else { users[roomID] = [socket.id]; } socketToRoom[socket.id] = roomID; const usersInThisRoom = users[roomID].filter(id => id !== socket.id); socket.emit("all users", usersInThisRoom); }); socket.on("sending signal", payload => { io.to(payload.userToSignal).emit('user joined', { signal: payload.signal, callerID: payload.callerID }); }); socket.on("returning signal", payload => { io.to(payload.callerID).emit('receiving returned signal', { signal: payload.signal, id: socket.id }); }); socket.on('disconnect', () => { const roomID = socketToRoom[socket.id]; let room = users[roomID]; if (room) { room = room.filter(id => id !== socket.id); users[roomID] = room; } }); }); server.listen(process.env.PORT || 8000, () => console.log('server is running on port 8000'));
问题定位与修复方案
1. Peer实例生命周期未正确管理
- 问题:组件卸载或用户离开时,未销毁Peer实例,后续信号尝试发送到已销毁的Peer对象,触发错误。
- 修复:在useEffect的清理函数中添加资源销毁逻辑:
useEffect(() => { // ... 原有代码 return () => { // 断开Socket连接 socketRef.current?.disconnect(); // 销毁所有Peer实例 peersRef.current.forEach(item => { item.peer.destroy(); }); peersRef.current = []; setPeers([]); }; }, [roomID]);
2. 用户离开时前端未同步清理Peer实例
- 问题:信令服务器移除了断开连接的用户,但前端未更新Peer列表,仍保留已失效的Peer实例。
- 修复:
- 信令服务器添加用户离开广播:
socket.on('disconnect', () => { const roomID = socketToRoom[socket.id]; let room = users[roomID]; if (room) { room = room.filter(id => id !== socket.id); users[roomID] = room; // 广播用户离开事件 io.to(roomID).emit('user left', socket.id); } }); - 前端添加用户离开监听并清理Peer:
socketRef.current.on("user left", (leftUserID) => { // 找到并销毁离开用户的Peer实例 const leftPeerIndex = peersRef.current.findIndex(item => item.peerID === leftUserID); if (leftPeerIndex !== -1) { peersRef.current[leftPeerIndex].peer.destroy(); peersRef.current.splice(leftPeerIndex, 1); } // 更新状态中的Peer列表 setPeers(prev => prev.filter((_, index) => { return peersRef.current.some(item => item.peer === prev[index]); })); });
- 信令服务器添加用户离开广播:
3. 信号发送前未检查Peer状态
- 问题:调用
peer.signal()前未验证Peer是否已销毁,直接触发错误。 - 修复:增加Peer状态检查:
socketRef.current.on("receiving returned signal", payload => { const item = peersRef.current.find(p => p.peerID === payload.id); if (item && !item.peer.destroyed) { // 检查Peer是否存活 item.peer.signal(payload.signal); } });
4. CORS中间件配置错误
- 问题:信令服务器中
app.use(cors)未调用函数,导致CORS配置未生效,可能引发连接异常间接导致Peer销毁。 - 修复:修改为
app.use(cors())
内容的提问来源于stack exchange,提问作者thu nguyen
相关产品推荐
相关产品推荐

