You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 22:44:55