本文将探讨如何使用 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