我们所有人都曾以某种形式使用过点对点视频通话应用程序。它可能是 zoom 会议、google meet 或 omegle。
最近,我正在学习 webRTC,发现这项技术在构建点对点通信应用程序时非常有用。请跟我一起了解它是如何工作的。
什么是点对点通信?
点对点或 P2P 是一种通信方法,在这种方法中,2 台设备无需从中央服务器发送数据即可进行通信。这意味着当我们进行视频通话时,音频和视频数据不会发送到中央服务器。相反,数据是通过互联网从一个浏览器发送到另一个浏览器。
什么是 WebRTC?
WebRTC 是一种直接进行实时通信的创新技术。大多数现代浏览器都内置了对 WebRTC 的支持。WebRTC 允许用户通过互联网向另一个对等方传输任何形式的数据(原始数据/音频、视频)。
让我们来看看如何构建自己的视频通话Web应用程序。
步骤1:获取用于信号传输的 HTTP 服务器
有些人可能会问,既然是 P2P 通信,为什么还需要 HTTP 服务器?原因是两个用户之间的通信是点对点的,但我们需要以信号的形式从服务器发送一些数据给客户端。例如:当一个用户试图连接另一个用户时,我们需要通知另一个用户有来电。
一旦对等网络之间建立了通信,对等网络就不需要向 HTTP 服务器发送任何数据。
我使用 Express JS 创建了一个简单的 HTTP 服务器,用于提供静态 HTML 文件。
const http = require('http');
const express = require('express');
const {Server} = require('socket.io');
const path = require('path');
const app = express();
const server = http.createServer(app);
const io = new Server(server);
HOST = '0.0.0.0';
PORT = 3000;
app.use(express.static( path.resolve('./public') ));
app.get("/health", (req,res) => {
res.send("Status: OK");
});
server.listen(PORT, HOST, () => {
console.log(`Listening on port: ${PORT}`)
})
要执行信号传递,我们需要实现 websockets。为此,我们将使用 Socket.io。我们将用 Socket io 封装我们的 express 服务器,Socket io 库实现了 websockets,用于保持与客户端的连接。
let rooms = {};
io.on('connection', socket => {
console.log(`User connected ${socket.id}`);
socket.on('create-room', (roomId) => {
// Logic to create a new room
if(!rooms[roomId]) {
rooms[roomId] = {
members: [socket.id],
};
// socket.join(roomId);
socket.emit('room-created', roomId);
console.log(`Room created: ${roomId}`);
} else {
// Room already exists
socket.emit('room-exists');
}
});
socket.on('join-room', (roomId) => {
// Logic to handle joining an existing room
const room = rooms[roomId];
if (room && room.members.length == 1) {
// room.members.push(socket.id);
// socket.join(roomId);
console.log("Sending join request to room owner", socket.id);
io.to(room.members[0]).emit('join-request', socket.id);
} else {
// Room is full or does not exist
socket.emit('room-unavailable');
}
});
socket.on('approve-join-request', (roomId, requesterUserId) => {
console.log(roomId, requesterUserId);
const room = rooms[roomId];
if (room) {
if(room.members[0]==socket.id) {
room.members.push(requesterUserId);
// socket.join(roomId);
io.to(requesterUserId).emit('join-approved');
console.log(`User ${requesterUserId} approved to join room ${roomId}`);
io.to(requesterUserId).emit('start-peer-connection', socket.id);
}
}
});
socket.on('offer-request', data => {
const { fromOffer, to } = data;
console.log("Forwarding offer request to: "+ to);
socket.to(to).emit('offer-request', { from: socket.id, offer: fromOffer });
});
socket.on('offer-answer', data => {
const { answere, to } = data;
console.log("Forwarding offer answer to: "+ to);
socket.to(to).emit('offer-answer', { from: socket.id, offer: answere });
});
socket.on('peer-updated', data => {
const { candidate, to } = data;
console.log("Peer updated");
socket.to(to).emit('peer-updated', { from: socket.id, candidate: candidate });
});
socket.on('disconnect', () => {
console.log('User disconnected:', socket.id);
// Handle user disconnection, leave rooms, etc.
});
});
对于服务器上的每个套接字事件,我们都会执行一些操作。
例如:当用户要创建房间时,客户端会发送一条 websocket 消息 create-room。然后,我们执行验证并创建一个新房间。
您可以阅读代码了解每个事件。
现在信令服务器已经准备就绪,开始实现 p2p 通信。
我们将为客户端编写简单的 html 代码。将有 2 个选项–创建新房间或加入现有房间,还将在 html 中加入 2 个视频元素,用于显示视频流。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Peer-to-Peer Video Call</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="styles.css">
</head>
<body style="background-image: url('https://wallpapers.com/images/hd/abstract-background-6m6cjbifu3zpfv84.jpg');">
<h1 class="text-3xl font-bold underline">
P2P Video Call
</h1>
<br>
<div class="grid grid-cols-3 gap-4">
<div class="mb-6" style="width: 300px;">
<input type="text" id="create-room-id" placeholder="Enter room id" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<br>
<button onclick="createRoom()" type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Create new room</button>
</div>
<div class="mb-6">
<input type="text" id="join-room-id" placeholder="Enter room id" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<br>
<button onclick="joinRoom()" type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Join existing room</button>
</div>
<div class="relative overflow-x-auto" id="incoming-calls">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div id="local-video-container">
<video id="local-video" autoplay muted></video>
</div>
<div id="remote-video-container">
<video id="remote-video" autoplay></video>
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="client.js"></script>
</body>
</html>
我们将使用简单的 javascript 来实现代码,可以从 socket.io 导入 socket io 客户端 javascript 库。
当用户点击创建新房间时,将向服务器发送创建房间信号。当用户点击 “加入房间 “时,将向服务器发送 “加入房间 “信号和房间 ID。
function createRoom() {
roomId = document.getElementById('create-room-id').value;
isRoomCreator = true;
// Emit an event to the server to create a room
socket.emit('create-room', roomId);
}
function joinRoom() {
roomId = document.getElementById('join-room-id').value;
// Emit an event to the server to join a room
socket.emit('join-room', roomId);
}
对于每次加入房间事件,后台服务器都会发送处理数据,并向现有房间成员发送加入请求信号。当客户端收到加入请求信号时,会将收到的请求填充到表格中并显示出来。当现有用户接受加入请求时,客户端将向服务器发送批准加入请求信号。
socket.on('join-request', (requesterUserId) => {
console.log("Join Request came", requesterUserId);
displayJoinRequest(requesterUserId);
});
function displayJoinRequest(requesterUserId) {
incomingCalls.push(requesterUserId);
populateIncomingCalls();
}
function approveJoinRequest(requesterUserId) {
console.log(requesterUserId);
// Emit an event to the server to approve the join request
socket.emit('approve-join-request', roomId, requesterUserId);
// joinRequestsContainer.innerHTML = '';
incomingCalls = incomingCalls.filter(function(item) {
return item !== requesterUserId
});
populateIncomingCalls();
}
function rejectJoinRequest(requesterUserId) {
// Implement rejection logic if needed
// joinRequestsContainer.innerHTML = '';
incomingCalls = incomingCalls.filter(function(item) {
return item !== requesterUserId
});
populateIncomingCalls();
}
之后,服务器将向其中一个对等设备发送信号,开始视频通话。客户端将初始化一个对等对象,并创建一个 WebRTC 提议。要创建 WebRTC 提议,客户端需要知道有哪些方式可以从互联网上的其他计算机与之通信。为此,需要连接 STUN 或 TURN 服务器。连接 STUN 服务器后,它会告诉客户端从互联网到达客户端的方式。有许多免费的 STUN 服务器。我们将在本项目中使用谷歌提供的 STUN 服务器。
WebRTC offer 将包含 SDP 或关于该浏览器的描述。客户端将向服务器发送该请求,服务器将把该请求转发给加入同一房间的另一个对等方。
peer = new RTCPeerConnection({
iceServers: [
{
urls: "stun:stun.l.google.com:19302"
}
]
});
socket.on('start-peer-connection', (to) => {
initializePeerConnection(to);
});
async function initializePeerConnection(remoteSocketId) {
console.log("Remote Socket ID: "+ remoteSocketId);
remoteSocketId = remoteSocketId;
const localOffer = await peer.createOffer();
console.log("Set local description for creating call: " + localOffer);
await peer.setLocalDescription(new RTCSessionDescription(localOffer));
socket.emit('offer-request', { fromOffer: localOffer, to: remoteSocketId });
}
其他对等点将读取该报价并将对等点提供的 SDP 设置为远程描述。该客户端将创建与初始报价相对应的应答报价并将其发送到服务器。服务器将此应答提议转发给原始对等方。原始对等方将读取应答提议并将 SDP 设置为远程描述。
socket.on('offer-request', async data => {
const { from, offer } = data;
console.log("Incoming offer for webRTC from:" + from);
await peer.setRemoteDescription(new RTCSessionDescription(offer));
console.log("Set Remote Description:" + offer);
const answereOffer = await peer.createAnswer();
console.log("Created answer offer:" + answereOffer);
await peer.setLocalDescription(new RTCSessionDescription(answereOffer));
console.log("Set Local Description:" + answereOffer);
socket.emit('offer-answer', { answere: answereOffer, to: from });
remoteSocketId = from;
const mySteam = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
mySteam.getTracks().forEach((track) =>
peer.addTrack(track, mySteam));
});
socket.on('offer-answer', async data => {
console.log("Received answer from peer");
const { offer } = data;
await peer.setRemoteDescription(new RTCSessionDescription(offer));
console.log("Set Remote description after answer received from peer: ", offer);
});
现在,两个客户端都有对方的 SDP 作为远程描述。这意味着 WebRTC 通道已经建立。现在可以通过信道发送任何数据。
我们将开始在该信道上流式传输对等方的音频和视频。每当接收到对等方的视频轨道时,我们就会将其设置为远程视频对象。
const startVideoStream = async() => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
localVideo.srcObject = stream;
// Add the local stream to the connection
stream.getTracks().forEach((track) => peer.addTrack(track, stream));
} catch (error) {
console.error('Error Streaming video:', error);
}
}
peer.ontrack = (event) => {
// Don't set srcObject again if it is already set.
if (remoteVideo.srcObject) return;
remoteVideo.srcObject = event.streams[0];
};
现在,我们的 P2P 视频通话应用程序已经准备就绪。运行 npm start 并前往 http://localhost:3000/ 开始视频通话。
作者:Arnav Rupade
代码链接:https://github.com/ArnavRupde/p2p-video-call
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/38020.html