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

Simple-peer视频通话APP跨设备连接失败及process未定义报错排查

双端视频通话APP跨设备故障解决方案

问题描述

基于MERN栈、socket.io和simple-peer.js开发的双端视频通话APP,同一设备的两个标签页测试时本地和远端视频均可正常显示;但在两台不同设备上测试时,仅能看到本地视频,远端视频无法加载,控制台抛出错误:

Uncaught ReferenceError: process is not defined
at O (_stream_readable.js:490:13)
at _stream_readable.js:469:5
at x (_stream_readable.js:240:5)
at S.push (_stream_readable.js:228:10)
at index.js:448:44

服务端代码

const express = require("express");
const { Server } = require("socket.io");
const multer = require("multer");
const path = require("path");
const fs = require("fs");
const cors = require("cors");

const app = express();
const upload = multer({ dest: "uploads/" });

const emailToSocketid = new Map();
const socketidToEmail = new Map();

const io = new Server(8000, {
  cors: true,
});

io.on("connection", (socket) => {
  console.log(`Socket connected with id ${socket.id}`);

  socket.on("join room", (data) => {
    const { email, room } = data;
    emailToSocketid.set(email, socket.id);
    socketidToEmail.set(socket.id, email);
    console.log(email, room, socket.id);

    io.to(room).emit("user_joined", { email: email, id: socket.id });
    socket.join(room);

    io.to(socket.id).emit("room_join", data);

    socket.on("callUser", ({ userToCall, signalData }) => {
      io.to(userToCall).emit("incomingCall", {
        signal: signalData,
        from: socket.id,
      });
    });

    socket.on("answerCall", (data) => {
      io.to(data.to).emit("callAccepted", data.signal);
    });

    socket.on("file", (fileInfo) => {
      io.emit("file", fileInfo);
    });

    socket.on("disconnect", () => {
      const email = socketidToEmail.get(socket.id);
      if (email) {
        emailToSocketid.delete(email);
        socketidToEmail.delete(socket.id);
      }
    });
  });
});

客户端代码

import React, { useCallback, useEffect, useState, useRef } from "react";
import { useSocket } from "../context/SocketProvider";
import Peer from "simple-peer";

export default function Room() {
  const { socket } = useSocket();
  const [remoteSocketId, setRemoteSocketId] = useState(null);
  const [myStream, setMyStream] = useState();
  const [remoteStream, setRemoteStream] = useState();
  const currentVideoRef = useRef(null);
  const remoteVideoRef = useRef(null);
  const [callAccepted, setCallAccepted] = useState(false);
  const [receivedFiles, setReceivedFiles] = useState([]);
  const [callInitiated, setCallInitiated] = useState(false);

  const joinUser = useCallback((data) => {
    const { email, id } = data;
    setRemoteSocketId(id);
    console.log(`Email ${email} joined the room`, remoteSocketId);
  }, []);

  const handleCallUser = useCallback(async () => {
    const stream = await navigator.mediaDevices.getUserMedia({
      audio: true,
      video: true,
    });
    setMyStream(stream);

    const peer = new Peer({ initiator: true, trickle: false, stream });

    peer.on("signal", (data) => {
      socket.emit("callUser", {
        userToCall: remoteSocketId,
        signalData: data,
      });
    });

    peer.on("stream", (currentStream) => {
      setRemoteStream(currentStream);
    });
    socket.on("callAccepted", (signal) => {
      setCallAccepted(true);
      setCallInitiated(true);
      peer.signal(signal);
    });
  }, [remoteSocketId, socket]);

  const answerCall = useCallback(
    async (call) => {
      console.log("Call is", call);
      setCallAccepted(true);

      const stream = await navigator.mediaDevices.getUserMedia({
        audio: true,
        video: true,
      });
      setRemoteStream(stream);

      const peer = new Peer({ initiator: false, trickle: false, stream });

      peer.on("signal", (data) => {
        socket.emit("answerCall", { signal: data, to: call.from });
      });

      peer.on("stream", (currentStream) => {
        setRemoteStream(currentStream);
      });

      peer.signal(call.signal);
    },
    [socket]
  );

  const fileHandler = useCallback((fileData) => {
    console.log("Received file:", fileData);
    setReceivedFiles((prevFiles) => [...prevFiles, fileData]);
  }, []);

  useEffect(() => {
    socket.on("user_joined", joinUser);
    socket.on("incomingCall", answerCall);
    socket.on("file", fileHandler);

    return () => {
      socket.off("user_joined");
      socket.off("incomingCall");
      socket.off("file", fileHandler);
    };
  }, [socket, joinUser, answerCall]);

  useEffect(() => {
    if (currentVideoRef.current && myStream) {
      currentVideoRef.current.srcObject = myStream;
    }
  }, [myStream]);

  useEffect(() => {
    if (remoteVideoRef.current && remoteStream) {
      console.log("Setting remote video ref");
      remoteVideoRef.current.srcObject = remoteStream;
    }
  }, [remoteStream]);

  useEffect(() => {
    if (callAccepted && !callInitiated) {
      const updateStream = async () => {
        const stream = await navigator.mediaDevices.getUserMedia({
          audio: true,
          video: true,
        });
        setMyStream(stream);
      };
      updateStream();
    }
  }, [callAccepted, callInitiated]);

  return (
    <div>
      <h1> This is the Room</h1>
      <h2>{remoteSocketId ? "Connected" : "Not"}</h2>
      {remoteSocketId && <button onClick={handleCallUser}>CALL</button>}
      {myStream && (
        <>
          <h1>My Stream</h1>
          <video ref={currentVideoRef} autoPlay muted playsInline />
          <input type="file" onChange={(e) => sendFile(e.target.files[0])} />
        </>
      )}
      {remoteStream && (
        <>
          <h1>Remote Stream</h1>
          <video ref={remoteVideoRef} autoPlay muted playsInline />
        </>
      )}
    </div>
  );
}

解决方案

1. 修复process is not defined错误

该错误源于simple-peer依赖的Node.js核心模块在浏览器环境中未被正确处理,可通过以下方式解决:

  • Vite项目:在vite.config.js中添加全局变量定义
import { defineConfig } from 'vite';
export default defineConfig({
  define: {
    'process.env': {}
  }
});
  • Create React App项目:在public/index.html的<head>中注入全局process对象
<script>
  window.process = { env: {} };
</script>
  • 替代依赖:改用轻量版simple-peer-light,减少Node.js核心模块依赖:
npm uninstall simple-peer
npm install simple-peer-light

然后将客户端导入改为import Peer from 'simple-peer-light'

2. 修复跨设备P2P连接失败

同一设备测试正常是因为使用本地网络,跨设备需要STUN服务器穿透NAT,同时修正客户端逻辑错误:

  • 添加STUN服务器配置:修改客户端Peer实例创建代码,加入ICE服务器配置
// 发起方Peer配置
const peer = new Peer({ 
  initiator: true, 
  trickle: true, // 开启Trickle ICE,提升NAT穿透成功率
  stream,
  config: {
    iceServers: [
      { urls: 'stun:stun.l.google.com:19302' } // 谷歌公共STUN服务器
    ]
  }
});

// 接收方Peer配置
const peer = new Peer({ 
  initiator: false, 
  trickle: true,
  stream,
  config: {
    iceServers: [
      { urls: 'stun:stun.l.google.com:19302' }
    ]
  }
});
  • 修正客户端流赋值错误:answerCall函数中错误地将本地流赋值给remoteStream,导致远端视频被覆盖,修正如下:
const answerCall = useCallback(
  async (call) => {
    console.log("Call is", call);
    setCallAccepted(true);

    const stream = await navigator.mediaDevices.getUserMedia({
      audio: true,
      video: true,
    });
    setMyStream(stream); // 本地流赋值给myStream

    const peer = new Peer({ initiator: false, trickle: true, stream });

    peer.on("signal", (data) => {
      socket.emit("answerCall", { signal: data, to: call.from });
    });

    peer.on("stream", (currentStream) => {
      setRemoteStream(currentStream); // 远端流赋值给remoteStream
    });

    peer.signal(call.signal);
  },
  [socket]
);
  • 完善服务端CORS配置:指定允许访问的前端域名,避免跨域问题
const io = new Server(8000, {
  cors: {
    origin: '你的Surge前端域名', // 例如https://xxx.surge.sh
    methods: ['GET', 'POST']
  }
});

内容的提问来源于stack exchange,提问作者Kshitij Verma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 07:37:45