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

使用PeerJs与Next.js TypeScript实现视频聊天时视频无法显示

问题分析与修复方案

核心问题点:

  • 本地视频未绑定媒体流:摄像头启动后,本地视频标签没有关联获取到的MediaStream,所以看不到自己的画面
  • Peer实例重复创建:呼叫时新建了Peer实例,导致和监听呼叫的不是同一个Peer ID,连接无法正确建立
  • 接收呼叫时未处理远程流:只应答了媒体流,但没有把对方的视频流绑定到远程视频标签
  • TypeScript类型缺失:视频ref未指定类型,可能导致赋值时隐式错误

修复步骤:

  1. 复用Peer实例:用useRef存储Peer实例,避免重复创建,保证呼叫和监听使用同一个Peer ID
  2. 绑定本地视频流:获取到本地媒体流后,赋值给本地视频标签的srcObject
  3. 完善呼叫接收逻辑:在peer.on("call")里处理远程流,绑定到远程视频标签
  4. 修正getUserMedia调用方式:改用Promise语法,同时处理错误情况
  5. 指定视频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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 16:16:09