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!
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.
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 is a library to establish direct connections i.e. peer to peer between web browsers.
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.
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
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.
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
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.
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;
Lines 1–3: We import the necessary React modules for our code, Peer
from "simple-peer" for WebRTC communication, and io
from "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 VideoCall
component. 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 useRef
instances to reference the two videos and the connection.
Lines 22–27: We use the useEffect
hook to request access to the user's video and audio using navigator.mediaDevices.getUserMedia()
and set the stream
if 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
, and callerSignal
states accordingly.
Lines 41–55:We define a callUser
method to initiate a call. It creates a new Peer
instance, 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 callAccepted
state and signal the peer to establish our connection.
Lines 69–88:We define the answerCall
method to answer an incoming call. It sets the callAccepted
state, creates a new Peer
instance, emits the "answerCall" event to the server, and signals the peer to establish the connection.
Lines 89–91:We define the leaveCall
method to end the call. Through this, we set the callEnded
state 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.
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"));
Lines 1–3: We import the necessary modules for our code, including express
for creating a web server, http
for handling HTTP requests, and cors
for 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 io
instance 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 its socket.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 ID data.from
, and the caller's name data.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.
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: [], };
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