Hello Everyoneđź‘‹,
In this article we will see how to build a video conferencing app.
Prerequisites:
Basics of Webrtc
To implement this we will be using the following libraries:
Setup Server:
touch app.js
yarn add express socket.io
const express = require('express');
const http = require('http');
const app = express();
const server = http.createServer(app);
const io = require('socket.io')(server);
const users = {};
const socketRoomMap = {};
io.on('connection', (socket) => {
socket.on('join-room', (roomId, userDetails) => {
// adding all user to a room so that we can broadcast messages
socket.join(roomId);
// adding map users to room
if (users[roomId]) {
users[roomId].push({ socketId: socket.id, ...userDetails });
} else {
users[roomId] = [{ socketId: socket.id, ...userDetails }];
}
// adding map of socketid to room
socketRoomMap[socket.id] = roomId;
const usersInThisRoom = users[roomId].filter(
(user) => user.socketId !== socket.id
);
/* once a new user has joined sending the details of
users who are already present in room. */
socket.emit('users-present-in-room', usersInThisRoom);
});
socket.on('initiate-signal', (payload) => {
const roomId = socketRoomMap[socket.id];
let room = users[roomId];
let name = '';
if (room) {
const user = room.find((user) => user.socketId === socket.id);
name = user.name;
}
/* once a peer wants to initiate signal,
To old user sending the user details along with signal */
io.to(payload.userToSignal).emit('user-joined', {
signal: payload.signal,
callerId: payload.callerId,
name,
});
});
/* once the peer acknowledge signal sending the
acknowledgement back so that it can stream peer to peer. */
socket.on('ack-signal', (payload) => {
io.to(payload.callerId).emit('signal-accepted', {
signal: payload.signal,
id: socket.id,
});
});
socket.on('disconnect', () => {
const roomId = socketRoomMap[socket.id];
let room = users[roomId];
if (room) {
room = room.filter((user) => user.socketId !== socket.id);
users[roomId] = room;
}
// on disconnect sending to all users that user has disconnected
socket.to(roomId).broadcast.emit('user-disconnected', socket.id);
});
});
server.listen(3001);
Here we use socket to transmit the user details and webrtc signals between multiple peers.
Setup the Client:
npx create-react-app webrtc-video-call-react
cd webrtc-video-call-react
yarn add socket.io-client simple-peer chance
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Home from './Home';
import Room from './Room';
const App = () => (
<div className='App'>
<BrowserRouter>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/room/:roomId' component={Room} />
</Switch>
</BrowserRouter>
</div>
);
export default App;
Here we have 2 routes /home and /room/:roomId . In /home we will give the option to create room or join room. In /room/:roomId is where we will render the video streams from multiple users.
import React, { useState } from 'react';
import * as Chance from 'chance';
const chance = new Chance();
const Home = ({ history }) => {
const [roomId, setRoomId] = useState('');
return (
<div style={{ marginTop: 10, marginLeft: 10 }}>
<input
type='text'
value={roomId}
onChange={(e) => setRoomId(e.target.value)}
></input>
<button
type='button'
onClick={() => {
if (!roomId) {
alert('RoomId is required');
return;
}
history.push(`/room/${roomId}`);
}}
>
Join Room
</button>
<button
type='button'
onClick={() => {
const id = chance.guid();
history.push(`/room/${id}`);
}}
>
Create Room
</button>
</div>
);
};
export default Home;
import React, { useEffect, useRef, useState } from 'react';
import io from 'socket.io-client';
import Peer from 'simple-peer';
import * as Chance from 'chance';
import Video from './Video';
const chance = new Chance();
const Room = (props) => {
const [userDetails, setUserDetails] = useState({
id: chance.guid(),
name: chance.name(),
});
const [peers, setPeers] = useState([]);
const socketRef = useRef();
const refVideo = useRef();
const peersRef = useRef([]);
const roomId = props.match.params.roomId;
useEffect(() => {
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((stream) => {
refVideo.current.srcObject = stream;
socketRef.current = io.connect('http://localhost:3001');
// sending the user details and roomid to join in the room
socketRef.current.emit('join-room', roomId, userDetails);
socketRef.current.on('users-present-in-room', (users) => {
const peers = [];
// To all users who are already in the room initiating a peer connection
users.forEach((user) => {
const peer = createPeer(
user.socketId,
socketRef.current.id,
stream
);
peersRef.current.push({
peerId: user.socketId,
peer,
name: user.name,
});
peers.push({
peerId: user.socketId,
peerObj: peer,
});
});
setPeers(peers);
});
// once the users initiate signal we will call add peer
// to acknowledge the signal and send the stream
socketRef.current.on('user-joined', (payload) => {
const peer = addPeer(payload.signal, payload.callerId, stream);
peersRef.current.push({
peerId: payload.callerId,
peer,
name: payload.name,
});
setPeers((users) => [
...users,
{ peerId: payload.callerId, peerObj: peer },
]);
});
// once the signal is accepted calling the signal with signal
// from other user so that stream can flow between peers
socketRef.current.on('signal-accepted', (payload) => {
const item = peersRef.current.find((p) => p.peerId === payload.id);
item.peer.signal(payload.signal);
});
// if some user is disconnected removing his references.
socketRef.current.on('user-disconnected', (payload) => {
const item = peersRef.current.find((p) => p.peerId === payload);
if (item) {
item.peer.destroy();
peersRef.current = peersRef.current.filter(
(p) => p.peerId !== payload
);
}
setPeers((users) => users.filter((p) => p.peerId !== payload));
});
});
}, []);
function createPeer(userToSignal, callerId, stream) {
const peer = new Peer({
initiator: true,
trickle: false,
stream,
});
peer.on('signal', (signal) => {
socketRef.current.emit('initiate-signal', {
userToSignal,
callerId,
signal,
});
});
return peer;
}
function addPeer(incomingSignal, callerId, stream) {
const peer = new Peer({
initiator: false,
trickle: false,
stream,
});
peer.on('signal', (signal) => {
socketRef.current.emit('ack-signal', { signal, callerId });
});
peer.signal(incomingSignal);
return peer;
}
return (
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<video muted ref={refVideo} autoPlay playsInline />
<span>{userDetails.name}</span>
</div>
{peers.map((peer, index) => {
return (
<Video
key={peersRef.current[index].peerId}
peer={peer.peerObj}
name={peersRef.current[index].name}
/>
);
})}
</div>
);
};
export default Room;
This room component will take care of connecting to the peers, sending and receiving signal with the help of our socket server and simple-peer.
Start the server, client and open the url in multiple devices to see it in action.
Please feel free fork and play with the source code.
Please like and share if you find this interesting.
Top comments (3)
Hi there, can you break down your code with a more elaborate description of each piece of code and what it does, right now it looks like a bad copy paste job.
It is a cut and paste job. You can find this code in other places of the internet going back some years.
I've run into quite a lot of these posts on dev.to. I wonder if it is about building clout networks for employment prospects? This post has way too many hearts and other reactions for the engagement.
Thanks for sharing this information. This is very useful post for me. This will absolutely going to help me in my project. Thank you for providing the full details for building an app for video conferencing.
Some comments have been hidden by the post's author - find out more