WebRTC Implementation with Node.js and React

In today’s topic, we are driving into WebRTC (Web Real-Time Communication), a powerful technology that enables real-time audio, video, and data communication directly between web browsers and mobile applications. In this blog post, we will explore the implementation of WebRTC using Node.js and React, highlighting its prerequisites, the implementation process, advantages, and concluding thoughts.

Prerequisites

Before delving into the implementation of WebRTC, it’s essential to ensure that you have the following prerequisites in place:

  1. Node.js and npm: Begin by installing Node.js and npm (Node Package Manager) on your development environment.
  2. React Knowledge: Familiarize yourself with React, a popular JavaScript library renowned for building dynamic user interfaces.
  3. Socket.io Integration: Install Socket.io, a pivotal library that facilitates real-time, two-way communication between clients and servers.
  4. Access to User Devices: Grant your application the necessary permissions to access the user’s webcam and microphone, as these hardware components play a central role in enabling audio and video communication through WebRTC.

Implementation on Nodejs

Step 1: Set Up the Node.js Server

Create a new directory for your project and set up a Node.js server.

mkdir webrtc-video-stream 
cd webrtc-video-stream 
npm init -y 
npm install express socket.io cors

Create a server file, e.g. server.js, with the following content:

const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server,{
cors: {
origin: "*",
credentials: true
}
});
const cors = require('cors');

app.use(cors({
origin: '*', // Replace with your React app's domain
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true, // Allow cookies to be sent with the request if needed
}));

io.on("connection", function (socket) {
console.log("connected");
socket.on("join", function (data) {
socket.join(data.roomId);
socket.room = data.roomId;
const sockets = io.sockets.adapter.rooms.get(data.roomId);
if (sockets.size === 1) {
return socket.emit("init");
} else {
if (sockets.size === 2) {
io.to(data.roomId).emit("ready");
} else {
socket.room = null;
socket.leave(data.roomId);
socket.emit("full");
}
}
});
socket.on("signal", (data) => {
io.to(data.room).emit("desc", data.desc);
});
socket.on("disconnect", () => {
const roomId = Object.keys(socket.adapter.rooms)[0];
if (socket.room) {
io.to(socket.room).emit("disconnected");
}
});
});

server.listen(5000, () => {
console.log('Server is running on http://localhost:5000');
});

In the above code,

🔸 io.on(“connection”, function (socket) {…}

This line of code establishes a connection between the server and the client. It listens for a “connection” event and executes the callback function when a client connects to the server.

🔸 socket.on(“join”, function (data) {…}

This code block listens for a “join” event emitted by the client. When a client emits a “join” event, the server executes the callback function and performs the following actions:

  1. The server joins the client to a specific room specified by data.roomId.
  2. The socket.room property is set to the data.roomId.
  3. The io.sockets.adapter.rooms.get(data.roomId) method retrieves the set of sockets in the specified room.
  4. If the number of sockets in the room is 1, the server emits an “init” event to the client using the socket.emit(“init”).
  5. If the number of sockets in the room is 2, the server emits a “ready” event to all sockets in the room using io.to(data.roomId).emit(“ready”).
  6. If the number of sockets in the room is greater than 2, the server sets socket.room to null, removes the socket from the room, and emits a “full” event to the client using socket.emit(“full”).

🔸 socket.on(“signal”, (data) => {…}

This code block listens for a “signal” event emitted by the client. When a client emits a “signal” event, the server executes the callback function and emits a “desc” event to all sockets in the same room as the client. The data passed in the “signal” event is also included in the “desc” event.

🔸 socket.on(“disconnect”, () => {…}

This code block listens for a “disconnect” event emitted by the client. When a client disconnects from the server, the server executes the callback function and performs the following actions:

The server retrieves the roomId of the first room in which the socket is present. If the socket is associated with a room (socket.room is not null), the server emits a “disconnected” event to all sockets in the same room using io.to(socket.room).emit(“disconnected”).

Embark on an Exciting Journey of Innovation and Excellence with our Expert Node.js Developers

Implementation on React

Create a new directory for your React client and initialize a React app.

cd .. npx create-react-app client 
cd client 
npm install simple-peer socket.io-client

Replace the contents of src/App.js with the following:

import React, { useEffect, useRef, useState } from "react";
import io from "socket.io-client";
import SimplePeer from "simple-peer";

const App = () => {
const socket = io("http://localhost:5000");
const localVideoRef = useRef();
const remoteVideoRef = useRef();
const peerRef = useRef();

useEffect(() => {
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((stream) => {
localVideoRef.current.srcObject = stream;

socket.on("offer", (offer) => {
peerRef.current = new SimplePeer({
initiator: false,
trickle: false,
stream,
});
peerRef.current.signal(offer);

peerRef.current.on("signal", (data) => {
socket.emit("answer", { target: offer.target, answer: data });
});

peerRef.current.on("stream", (remoteStream) => {
remoteVideoRef.current.srcObject = remoteStream;
});

peerRef.current.on("data", (data) => {
// Handle data communication between peers if needed
});
});

socket.emit("join-call"); // Signal that you want to join a call
})
.catch((error) => {
console.error("Error accessing webcam:", error);
});

// Clean up the socket connection and media streams on unmount
return () => {
if (peerRef.current) {
peerRef.current.destroy();
}
socket.disconnect();
};
}, []);

return (
<div>
<div>
<video ref={localVideoRef} autoPlay playsInline muted />
</div>
<div>
<video ref={remoteVideoRef} autoPlay playsInline />
</div>
</div>
);
};

export default App;

In the above code,

🔸 useEffect

The useEffect hook is used to perform side effects in functional components. It takes a callback function as its first argument and an optional array of dependencies as its second argument. In this code, useEffect is used to initialize the video call and clean up the resources when the component is unmounted.

🔸 navigator.mediaDevices.getUserMedia

This function is used to access the user’s webcam and microphone. It returns a promise that resolves to a MediaStream object containing the user’s audio and video streams.

🔸 Socket

The socket object is used to establish a connection with the server and exchange signalling messages with other participants in the video call. It emits and listens to events such as ‘offer’ and ‘answer’ to negotiate the connection between peers.

🔸 SimplePeer

The SimplePeer library is used to create a WebRTC peer connection between the local user and the remote user. It handles the signalling process and establishes a direct media stream connection between the peers.

You can refer to my GitHub for the detailed guide!

coma

Conclusion

In conclusion, WebRTC stands as a formidable technology that unlocks the potential for real-time communication within web applications. By embarking on the journey of implementing WebRTC with the dynamic duo of Node.js and React, you have the opportunity to craft interactive, engaging, and innovative applications with ease.

With its real-time prowess, peer-to-peer architecture, cross-platform adaptability, and robust security measures, WebRTC emerges as an invaluable addition to your development toolkit. Harness its capabilities, explore its potential, and embark on a journey of creating applications that leverage the transformative power of real-time communication.

Keep Reading

Keep Reading

Struggling with EHR integration? Learn about next-gen solutions in our upcoming webinar on Mar 6, at 11 AM EST.

Register Now

Let's create something together!