ReactJS video call
Setting up a video call functionality can be quite complex if we don't know where to start or what the right libraries are. In this Answer, we'll be looking at how to set up a basic video call quickly in ReactJS!
Technologies used
Socket.io
Socket.io is a library that helps web applications communicate with servers in real-time. One great thing it provides us is bidirectional communication which means that the client and the server can send messages to each other at any time.
How does it work?
It uses WebSockets, which provides a persistent connection between the client and the server. This allows us to achieve real-time data exchange.
Simple Peer
Simple Peer is a library to establish direct connections i.e. peer to peer between web browsers.
How does it work?
It uses WebRTC that enables real-time audio and video streaming directly between browsers without intermediate servers. This helps us to set our code up quickly without having to dive deeper into the complexities.
Setting up a React project
Prior to using these libraries in a React project, we'll have to set a project up first. This can be done by following the steps given here.
Alternatively, these two commands can be run to set up the project quickly.
npx create-react-app react-proj
cd react-proj
Styling with Tailwind
For adding styles to our code, we will be making use of Tailwind CSS, a highly customizable library that provides class names to add different styles.
You can learn how to set it up here.
Installing the packages
We'll be needing the following packages in our code. Copy the command and paste it into your terminal to set up your project properly.
npm i express http cors socket.io simple-peer socket.io-client
Code sample
Now that we're done with the basic installations, let's create our own video call code! We will create a really basic page that allows copying a user ID, and any user connected to the same server can use it to call that user ID. We can do this to both send and receive calls.
Note: A local host server will only allow calls on the same machine. If you wish to take your video call functionality to a higher level, you can deploy the server online and simply change the API endpoint.
Our page should look something like this, with our video on one side and the person we wish to call on the other side.
Client code
import React, { useEffect, useRef, useState } from "react";import Peer from "simple-peer";import { io } from "socket.io-client";// replace your server endpoint hereconst socket = io("http://localhost:5000");function VideoCall() {const [me, setMe] = useState("");const [stream, setStream] = useState();const [receivingCall, setReceivingCall] = useState(false);const [caller, setCaller] = useState("");const [callerSignal, setCallerSignal] = useState();const [callAccepted, setCallAccepted] = useState(false);const [idToCall, setIdToCall] = useState("");const [callEnded, setCallEnded] = useState(false);const [name, setName] = useState("");const userVideo = useRef();const connectionRef = useRef();const myVideo = useRef();useEffect(() => {navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((stream) => {setStream(stream);if(myVideo)myVideo.current.srcObject = stream;});socket.on("me", (id) => {setMe(id);});socket.on("callUser", (data) => {setReceivingCall(true);setCaller(data.from);setName(data.name);setCallerSignal(data.signal);});}, [myVideo]);const callUser = (id) => {const peer = new Peer({initiator: true,trickle: false,stream: stream,});peer.on("signal", (data) => {socket.emit("callUser", {userToCall: id,signalData: data,from: me,name: name,});});peer.on("stream", (stream) => {userVideo.current.srcObject = stream;});socket.on("callAccepted", (signal) => {setCallAccepted(true);peer.signal(signal);});connectionRef.current = peer;};const answerCall = () => {setCallAccepted(true);const peer = new Peer({initiator: false,trickle: false,stream: stream,});peer.on("signal", (data) => {socket.emit("answerCall", { signal: data, to: caller });});peer.on("stream", (stream) => {userVideo.current.srcObject = stream;});peer.signal(callerSignal);connectionRef.current = peer;};const leaveCall = () => {setCallEnded(true);connectionRef.current.destroy();};return (<><div><div className="flex flex-row h-full w-full justify-center gap-[15%] h-screen z-"><div><div class="flex-grow flex flex-col items-center justify-center h-[90%]"><span className="text-white font-bold text-3xl mb-4">Basic React JS video calling</span><span className="text-white font-bold text-md mb-4 text-center underline">Copy your ID and anyone using the same server can use it to call you and vice versa!</span><div class="flex flex-row gap-32"><div class="flex flex-col items-center justify-center w-full"><div className="video">{stream && (<videoclassName="rounded-full"playsInlinemutedref={myVideo}autoPlaystyle={{ width: "300px" }}/>)}</div><span className="text-white font-bold text-lg mb-4">{caller}</span><p className="text-white">{me}</p></div><div class="flex flex-col items-center justify-center w-full">{callAccepted && !callEnded ? (<videoclassName="rounded-full"playsInlineref={userVideo}autoPlaystyle={{ width: "300px" }}/>) : (<div className="flex flex-col justify-center items-center"><imgsrc="https://w0.peakpx.com/wallpaper/416/423/HD-wallpaper-devil-boy-in-mask-red-hoodie-dark-background-4ef517.jpg"class="rounded-full w-[15rem]"/><span class="text-white font-bold text-lg">{idToCall}</span></div>)}</div></div><textareaclassName="text-black"defaultValue={idToCall}onChange={(e) => {setIdToCall(e.target.value);}}/><div>{callAccepted && !callEnded ? (<button className="text-black hover:text-gray-400 mr-6 font-bold bg-white rounded-md m-4 px-2" onClick={leaveCall}>End Call</button>) : (<buttonclassName="text-black hover:text-gray-400 mr-6 font-bold bg-white rounded-md m-4 px-2"onClick={() => callUser(idToCall)}>Call</button>)}</div><div className="text-white">{receivingCall && !callAccepted ? (<div className="caller flex flex-col"><h1 className="text-white">{caller} is calling...</h1><buttonclassName="text-black text-xl hover:text-gray-400 mr-6 font-bold bg-white rounded-md m-4 px-2"onClick={answerCall}>Answer</button></div>) : null}</div></div></div></div></div></>);}export default VideoCall;
Code explanation
Lines 1–3: We import the necessary React modules for our code,
Peerfrom "simple-peer" for WebRTC communication, andiofrom "socket.io-client" for establishing a socket connection with the server.Line 5: We establish a socket connection to the server running at "http://localhost:5000" (you can replace it with your own endpoint).
Lines 7–16: We define the
VideoCallcomponent. It initializes state variables for "me" (user ID), "stream" (media stream), "receivingCall" (whether a call is being received), "caller" (caller ID), "callerSignal" (signaling data from the caller), "callAccepted" (whether the call is accepted), "idToCall" (ID we want to call), "callEnded" (if the call has ended), and "name" (user name).Lines 18–20: We create
useRefinstances to reference the two videos and the connection.Lines 22–27: We use the
useEffecthook to request access to the user's video and audio usingnavigator.mediaDevices.getUserMedia()and set thestreamif successful.Lines 29–31: We listen for the "me" event from the server, which sends a unique ID to us that anyone can use to call us.
Lines 33–39: We listen for the "callUser" event from the server, which means there's an incoming call. We set the
receivingCall,caller,name, andcallerSignalstates accordingly.Lines 41–55:We define a
callUsermethod to initiate a call. It creates a newPeerinstance, sets the options, and emits the "callUser" event to the server.Lines 61–64: We listen for the "callAccepted" event from the server, which means that the call has been accepted. We set the
callAcceptedstate and signal the peer to establish our connection.Lines 69–88:We define the
answerCallmethod to answer an incoming call. It sets thecallAcceptedstate, creates a newPeerinstance, emits the "answerCall" event to the server, and signals the peer to establish the connection.Lines 89–91:We define the
leaveCallmethod to end the call. Through this, we set thecallEndedstate and destroy the peer connection.Lines 94–184: Finally, we write the JSX user interface code, which includes the video call interface, both video elements, an input to enter the ID to call, and buttons to call, answer, and end the call.
Server code
const express = require("express");const http = require("http");const cors = require("cors");const app = express();const server = http.createServer(app);app.use(cors());app.use(express.json());app.use(express.urlencoded({ extended: true }));const io = require("socket.io")(server, {cors: {origin: "*",methods: ["GET", "POST"],},});io.on("connection", (socket) => {socket.emit("me", socket.id);socket.on("disconnect", () => {socket.broadcast.emit("callEnded");});socket.on("callUser", (data) => {console.log(`Incoming call from ${data.from}`);io.to(data.userToCall).emit("callUser", {signal: data.signalData,from: data.from,name: data.name,});});socket.on("answerCall", (data) => {console.log(`Answering call from ${data.from}`);io.to(data.to).emit("callAccepted", data.signal);});});server.listen(5000, () => console.log("server is running on port 5000"));
Code explanation
Lines 1–3: We import the necessary modules for our code, including
expressfor creating a web server,httpfor handling HTTP requests, andcorsfor allowing requests from different origins.Lines 5–10: We set up an Express app, create an HTTP server using the app, and enable CORS and JSON middleware.
Lines 12–20: We set up a socket.io
ioinstance on the server and include permission for other origins. We listen for a "connection" event, indicating a client has connected, and emit the "me" event to the connected client so that we can send itssocket.id.Lines 22–24: We handle the "disconnect" event when a client disconnects and then broadcast the "callEnded" event to the connected clients.
Lines 26–33: To initiate a call, we define the "callUser" event. We emit the "callUser" event to the target user
data.userToCall, sending the signaling data, caller IDdata.from, and the caller's namedata.name, if any.Lines 35–38: We handle the "answerCall" event when a client answers a call. We emit the "callAccepted" event to the calling client (specified by
data.to), sending the signaling data (data.signal).Line 41: We start the server on port 5000. Make sure to change the port according to your machine.
Complete code
Congratulations! We've succeeded in making the bare bones of a video-calling application.
The following code is the complete working code for the application. You can change the server endpoints according to your own machine.
module.exports = {
content: ["./src/**/*.{html,js,jsx}"],
theme: {
extend: {},
},
plugins: [],
};Demonstration of a video call
Let's test our newly created video calling capabilities by using two browser windows side by side.
Test your knowledge!
How do we take permission from the user for the camera and microphone?
Using a Peer instance
Using the getUserMedia function
Using the stream object
Free Resources