使用PeerJs与Next.js TypeScript实现视频聊天时视频无法显示
问题分析与修复方案
核心问题点:
- 本地视频未绑定媒体流:摄像头启动后,本地视频标签没有关联获取到的MediaStream,所以看不到自己的画面
- Peer实例重复创建:呼叫时新建了Peer实例,导致和监听呼叫的不是同一个Peer ID,连接无法正确建立
- 接收呼叫时未处理远程流:只应答了媒体流,但没有把对方的视频流绑定到远程视频标签
- TypeScript类型缺失:视频ref未指定类型,可能导致赋值时隐式错误
修复步骤:
- 复用Peer实例:用
useRef存储Peer实例,避免重复创建,保证呼叫和监听使用同一个Peer ID - 绑定本地视频流:获取到本地媒体流后,赋值给本地视频标签的srcObject
- 完善呼叫接收逻辑:在
peer.on("call")里处理远程流,绑定到远程视频标签 - 修正getUserMedia调用方式:改用Promise语法,同时处理错误情况
- 指定视频Ref类型:给remoteVideoRef和localVideoRef指定HTMLVideoElement类型,符合TypeScript规范
修正后的完整代码:
import styles from "../styles/Home.module.css"; import React from "react"; import { NextPage } from "next"; import type Peer from "peerjs"; const Visio: NextPage = () => { const [peerId, setPeerId] = React.useState<string>(""); const remoteVideoRef = React.useRef<HTMLVideoElement>(null); const localVideoRef = React.useRef<HTMLVideoElement>(null); const [remotePeerIdValue, setRemotePeerIdValue] = React.useState<string>(""); const peerRef = React.useRef<Peer | null>(null); React.useEffect(() => { const initPeer = async () => { const Peer = (await import("peerjs")).default; const peer = new Peer(); peerRef.current = peer; peer.on("open", (id) => { setPeerId(id); }); peer.on("call", async (call) => { try { const mediaStream = await window.navigator.mediaDevices.getUserMedia({ video: true, audio: true }); // 绑定本地视频流 if (localVideoRef.current) { localVideoRef.current.srcObject = mediaStream; } // 应答呼叫 call.answer(mediaStream); // 接收对方的视频流 call.on("stream", (remoteStream) => { if (remoteVideoRef.current) { remoteVideoRef.current.srcObject = remoteStream; remoteVideoRef.current.onloadedmetadata = () => { remoteVideoRef.current?.play(); }; } }); } catch (err) { console.error("获取媒体流失败:", err); } }); // 清理函数:销毁Peer实例 return () => { peer.destroy(); }; }; initPeer(); }, []); const call = async (remotePeerId: string) => { if (!peerRef.current || !remotePeerId) return; try { const mediaStream = await window.navigator.mediaDevices.getUserMedia({ video: true, audio: true }); // 绑定本地视频流 if (localVideoRef.current) { localVideoRef.current.srcObject = mediaStream; } // 发起呼叫 const call = peerRef.current.call(remotePeerId, mediaStream); // 接收对方的视频流 call.on("stream", (remoteStream) => { if (remoteVideoRef.current) { remoteVideoRef.current.srcObject = remoteStream; remoteVideoRef.current.onloadedmetadata = () => { remoteVideoRef.current?.play(); }; } }); } catch (err) { console.error("呼叫失败:", err); } }; return ( <div className={styles.container}> <div>我的Peer ID: {peerId}</div> <input type="text" placeholder="输入对方Peer ID" value={remotePeerIdValue} onChange={(e) => setRemotePeerIdValue(e.target.value)} /> <button onClick={() => call(remotePeerIdValue)}>发起呼叫</button> <div> <h3>本地视频</h3> <video ref={localVideoRef} autoPlay playsInline muted /> </div> <div> <h3>远程视频</h3> <video ref={remoteVideoRef} autoPlay playsInline /> </div> </div> ); }; export default Visio;
额外说明:
- 添加了
playsInline属性,避免在移动端视频自动全屏 - 本地视频添加
muted属性,防止回声 - 增加了错误捕获,方便排查问题
- 加入了清理函数,组件卸载时销毁Peer实例,避免资源泄漏
内容的提问来源于stack exchange,提问作者F4M75
相关产品推荐
相关产品推荐

