如何使用 Next.js、TypeScript 和 PeerJS 构建实时视频通话

本文将探讨如何使用 Next.js、TypeScript 和 PeerJS 创建实时视频通话,实现在两个客户端之间构建一个小型视频通话模块。

1/ 安装 PeerJS 服务器

PeerJS 是一个简单但功能强大的库,有助于促进网络应用程序中的点对点通信。可以将其视为一种工具,允许电脑或智能手机等两台设备通过互联网直接相互连接,而无需集中式服务器。这意味着用户之间可以直接共享数据,如视频流或信息,使实时通信更加顺畅。

为了实现这种对等者之间的直接通信,我们需要建立一个 PeerJS 服务器。该服务器充当中间人的角色,帮助对等网络发现对方并建立连接。从根本上说,它是对等节点之间的桥梁,为实时交换数据提供了便利。

首先创建一个名为 “peer-server “的新目录。

mkdir peer-server

在该目录下创建一个名为 “index.js “的文件。接下来,初始化 npm 目录并安装以下依赖项:

npm init
npm i dotenv express http peer

我们将使用 Express 框架以及对等包中的 ExpressPeerServer 实例。把这个实例连接到 Express 应用程序和 HTTP 服务器。服务器将监听 9000 端口,访问 Peer 服务器的端点是 /myapp(可以选择不同的名称)。

该服务器将作为两个设备之间交换信息和建立连接的中介。

// index.js
require("dotenv").config();

const { ExpressPeerServer } = require("peer");
const express = require("express");
const cors = require("cors");

const http = require("http");

const app = express();

const server = http.createServer(function (req, res) {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello world!");
});

const PORT = 9000;

app.use(express.static("public"));

const peerServer = ExpressPeerServer(server, {
  debug: true,
  allow_discovery: true,
});

app.use("/myapp", peerServer);

server.listen(PORT, () => {
  console.log(`PeerJS server running on port ${PORT}`);
});

同样,如果一切顺利,在目录中,可以启动服务器:

node index.js

并且应该出现以下消息:

PeerJS server running on port 9000

2/ 设置 NextJS

现在创建 Next.js 项目。

npx create-next-app@latest
√ What is your project named? ... peer-app
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like to use `src/` directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to customize the default import alias (@/*)? ... No / Yes

如果你导航到 peer-app 目录并运行 npm run dev,就会看到页面显示出来。

现在,第一步是为我们的新功能创建一个新页面。在 /src/app 目录下,创建一个名为 peer 的文件夹,并在其中创建一个名为 page.tsx 的文件。

// src/app/peer/peer.tsx
const PeerPage = () => {
  return (
    <div>
        <p>Hello World!</p>
    </div>
  );
};

export default PeerPage;

如果访问 http://localhost:3000/peer,应该会看到 “Hello World!”。好极了!现在,让我们从摄像头获取视频。准备开始视频通话!

// src/app/peer/peer.tsx
"use client"

import { useEffect, useRef } from 'react';
 
const PeerPage = () => {
  const myVideoRef = useRef<HTMLVideoElement>(null);

  useEffect(() => {
        if (typeof window !== 'undefined') { 
          //  Here, we retrieve the camera from the browser 
          //  and assign the video stream to the reference of our video tag.
          navigator.mediaDevices.getUserMedia({
            video: true,
            audio: true,
          }).then(stream => {
            if (myVideoRef.current) {
              myVideoRef.current.srcObject = stream;
            }
          });
        }
  }, []);

  return (
    <div className='flex flex-col justify-center items-center p-12'>
        <video className='w-72' playsInline ref={myVideoRef} autoPlay />
    </div>
  );
};

export default PeerPage;

运行可以看到摄像头画面了。对于不熟悉 Next.js 的人来说,在这里注意几件事:

  • 在文件顶部使用了 use-client 声明,以表明使用的是客户端组件,并且可以使用 React hooks(如 useState、useEffect)。
  • 将使用 navigator API 从默认的摄像头设备获取视频流。

3/ 进行点对点通话

在点对点通话中,首先要了解的是每个客户端都有一个自己的唯一 ID。我们可以把它比作你的电话号码;别人需要知道这个 ID 才能联系到你。

因此,我们的想法是,用户一进入页面,就会为其分配一个随机 ID。

// src/app/peer/peer.tsx
"use client"

import { useEffect, useRef, useState } from 'react';
 
const PeerPage = () => {
  const myVideoRef = useRef<HTMLVideoElement>(null);

  const [myUniqueId, setMyUniqueId] = useState<string>("");

  const generateRandomString = () => Math.random().toString(36).substring(2);

  useEffect(() => {
        if (typeof window !== 'undefined') { 
          navigator.mediaDevices.getUserMedia({
            video: true,
            audio: true,
          }).then(stream => {
            if (myVideoRef.current) {
              myVideoRef.current.srcObject = stream;
            }
          });
        }
  }, []);

  //  Here, when the component is mounted for the first time, 
  //  we assign a random ID to our state myId.
  useEffect(() => {
    setMyUniqueId(generateRandomString);
  }, [])

  return (
    <div className='flex flex-col justify-center items-center p-12'>
      <p>your id : {myUniqueId}</p>
      <video className='w-72' playsInline ref={myVideoRef} autoPlay />
    </div>
  );
};

export default PeerPage

现在可以看到你的标识符显示在摄像头上方。下面连接 Next.js 前端和 Peer 服务器。

在 Next.js 项目中 :

npm i peerjs
// src/app/peer/peer.tsx
"use client"

import { useEffect, useRef, useState } from 'react';
import Peer from 'peerjs';
 
const PeerPage = () => {
  const myVideoRef = useRef<HTMLVideoElement>(null);

  const [myUniqueId, setMyUniqueId] = useState<string>("");

  const generateRandomString = () => Math.random().toString(36).substring(2);

  //  Here, when the component is mounted for the first time, 
  //  we assign a random ID to our state myId.
  useEffect(() => {
    if(myUniqueId){
        let peer: Peer;
        if (typeof window !== 'undefined') {
          // Here we create a new instance of the Peer object with the 
          // parameters of our unique client identifier and the information 
          // of our peer server.
          peer = new Peer(myUniqueId, {
            host: 'localhost',
            port: 9000,
            path: '/myapp',
          });
    
          navigator.mediaDevices.getUserMedia({
            video: true,
            audio: true,
          }).then(stream => {
            if (myVideoRef.current) {
              myVideoRef.current.srcObject = stream;
            }
          });
        }
        return () => {
            if (peer) {
              peer.destroy();
            }
          };
    }
  }, [myUniqueId]);

  useEffect(() => {
    setMyUniqueId(generateRandomString);
  }, [])

  return (
    <div className='flex flex-col justify-center items-center p-12'>
      <p>your id : {myUniqueId}</p>
      <video className='w-72' playsInline ref={myVideoRef} autoPlay />
    </div>
  );
};

export default PeerPage;

在此阶段,如果打开检查器并导航到Web选项卡,应该可以看到与对等服务器的连接!

现在,开始调用另一个客户端!我们将创建一个新的视频标记,其中包含对方的视频,将添加一个 input 和一个 state 来记录要调用的 id。

// src/app/peer/peer.tsx
"use client"

import { useEffect, useRef, useState } from 'react';
import Peer from 'peerjs';
 
const PeerPage = () => {
  const myVideoRef = useRef<HTMLVideoElement>(null);
  const callingVideoRef = useRef<HTMLVideoElement>(null);

  const [peerInstance, setPeerInstance] = useState<Peer | null>(null);
  const [myUniqueId, setMyUniqueId] = useState<string>("");
  const [idToCall, setIdToCall] = useState('');

  const generateRandomString = () => Math.random().toString(36).substring(2);

  // Here we declare a function to call the identifier and retrieve 
  // its video stream.
  const handleCall = () => {
    navigator.mediaDevices.getUserMedia({
      video: true,
      audio: true,
    }).then(stream => {
      const call = peerInstance?.call(idToCall, stream);
      if (call) {
        call.on('stream', userVideoStream => {
          if (callingVideoRef.current) {
            callingVideoRef.current.srcObject = userVideoStream;
          }
        });
      }
    });
  };

  useEffect(() => {
    if(myUniqueId){
        let peer: Peer;
        if (typeof window !== 'undefined') {
          peer = new Peer(myUniqueId, {
            host: 'localhost',
            port: 9000,
            path: '/myapp',
          });

          setPeerInstance(peer);
    
          navigator.mediaDevices.getUserMedia({
            video: true,
            audio: true,
          }).then(stream => {
            if (myVideoRef.current) {
              myVideoRef.current.srcObject = stream;
            }

            peer.on('call', call => {
              call.answer(stream);
              call.on('stream', userVideoStream => {
                if (callingVideoRef.current) {
                  callingVideoRef.current.srcObject = userVideoStream;
                }
              });
            });
          });
        }
        return () => {
            if (peer) {
              peer.destroy();
            }
          };
    }
  }, [myUniqueId]);

  useEffect(() => {
    setMyUniqueId(generateRandomString);
  }, [])

  return (
    <div className='flex flex-col justify-center items-center p-12'>
      <p>your id : {myUniqueId}</p>
      <video className='w-72' playsInline ref={myVideoRef} autoPlay />
      <input className='text-black' placeholder="Id to call" value={idToCall} onChange={e => setIdToCall(e.target.value)} />
      <button onClick={handleCall}>call</button>
      <video className='w-72' playsInline ref={callingVideoRef} autoPlay/>
    </div>
  );
};

export default PeerPage;

现在,如果你在此页面上打开两个浏览器,并尝试使用 ID 调用第二个浏览器,如果看到两个摄像头,对等服务器已经正常工作了!

免责声明

需要注意的是,虽然在本地开发环境中建立点对点网络是一种很好的入门方式,但在生产环境中部署这样的系统需要额外的考虑。其中一个重要方面就是确保对等网络之间连接的安全性。在生产环境中,获得 SSL 或 HTTPS 证书对于加密数据传输和保护用户隐私至关重要。此外,遵守现代网络安全标准有助于确保您的应用程序保持安全,并能抵御潜在威胁。虽然我们这里的重点是在本地构建和测试我们的视频通话模块,但在部署应用程序供实际使用时,牢记这些注意事项至关重要。

相关代码 GitHub : https://github.com/MatthiasTeks/peer-nextjs

作者:Matthias Vimbert

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/44278.html

(0)

相关推荐

发表回复

登录后才能评论