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
相关产品推荐
相关产品推荐

