Creating a Peerjs video call example in Node.js to demonstrate how the video calling works in Node.js with peer.js library. We are going to focus on the video broadcasting. The one key factor to remember here is that the video calling on modern browser only work with HTTPS routes. So if you are trying to make a video call on simple http, you would need to change this to https route instead. Let’s get started.
Tech stack for video calling example
Before we jump into the code, let’s discuss the tech stack we are going to use for this example code. We are just using the Node.js for server-side handling and we will try to make the app as short as possible. For the demonstration, a unique UUID would be generated which anyone can join with. If other participant to that video calling have that UUID, it can request to join the room and the video calling on both devices would start working. Here is the list of tech stack for today’s tutorial.
- Node.js
- Peer.js
- mkcert (for SSL certificate creation)
- Express.js
- socket.io
- https
- fs
Step-1: Create SSL Certificate with mkcert
Frist of all we have to create an SSL certificate for our route and have to register it for our node.js application. We can do that with OpenSSL or mkcert
if you are on windows like me. I used FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you’d like. (github.com) library which is pretty simple once installed. All you have to do is to know your domain name.
You can install the mkcert package on windows with Chocolaty package manager with the following command
>>choco install mkcert
Because we are just testing this on our local host we simply need to provide the ip address which we can give like this
mkcert 192.168.1.111
Code language: CSS (css)
and it will generate two certificate files for us. First with extension of 192.168.1.111-key.pem
and the second file will be simply 192.168.1.111.pem
Step-2: Create https server in Node.js
Now create a server.js
file in your Node.js project folder. You can create a blank node.js project or start with an empty directory and create a server.js
file with visual studio code IDE. You can also use any other ide of your choice or a simple notepad to create that file. Once done, you have to register your SSL certificate with the express.js library in your node.js project. You can simply do this by these lines of codes.
const fs = require('fs');
const https = require('https');
const express = require('express');
const app = express();
const server = https.createServer({
key: fs.readFileSync('192.168.1.111-key.pem'),
cert: fs.readFileSync('192.168.1.111.pem')
}, app);
const io = require('socket.io')(server);
const { ExpressPeerServer } = require('peer');
// use express static to deliver resources HTML, CSS, JS, etc)
// from the public folder
app.use(express.static('public'));
// create HTTPS server
const options = {
key: fs.readFileSync('192.168.1.111-key.pem'),
cert: fs.readFileSync('192.168.1.111.pem')
};
const server = https.createServer(options, app).listen(3355);
//or you can also do this with
const server = https.createServer({
key: fs.readFileSync('192.168.1.111-key.pem'),
cert: fs.readFileSync('192.168.1.111.pem')
}, app);
Code language: JavaScript (javascript)
Here we imported the neccessary packages like fs
, express
, https
, ws
, and also the peer
library. We imported the ExpressPeerServer
from the peer
library. After that we created a simple public folder to access our public resources in our Express.js application with the help of the app.use(express.static('public'))
command. Finally, we created the https server with https.createServer
command which takes our previously generated SSL certificate files as options parameter. The port for the https is 3355
used in our case. Now our server is ready to accept the HTTPS requests.
Step-3: Initialize Peerjs video calling example server
Now that we are done with the https server, we now need to initialize the peer.js server. We have to do this with the ExpressPeerServer
object which we imported from the peer
library. We can simply initiate this with the following piece of code.
// create PeerJS server
const peerServer = ExpressPeerServer(server, {
debug: true
});
app.use('/peerjs', peerServer);
Code language: PHP (php)
Notice we let the debugging true to get debugging messages.
Step-4: Message Passing with Socket.io
You can optionally create a socket.io library for handling the messages between clients and broadcasting them between both peers. This is totally optional and could be handled via the peer.js as well. We are implementing this just to demonstrate the power of socket.io library as well. Here is how will you use it.
const io = require('socket.io')(server, {
cors: {
origin: '*',
},
perMessageDeflate: false,
});
io.on('connection', socket => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
socket.on('call-user', (data) => {
console.log(`call-user event from ${data.callerID} to ${data.userID}`);
socket.to(data.userID).emit('call-made', {
offer: data.offer,
callerID: data.callerID
});
});
socket.on('make-answer', data => {
console.log(`make-answer event from ${data.calleeID} to ${data.callerID}`);
socket.to(data.callerID).emit('answer-made', {
answer: data.answer,
calleeID: data.calleeID
});
});
socket.on('reject-call', data => {
console.log(`reject-call event from ${data.calleeID} to ${data.callerID}`);
socket.to(data.callerID).emit('call-rejected', {
calleeID: data.calleeID
});
});
socket.on('user-connected', (userID) => {
console.log(`user-connected event for ${userID}`);
socket.broadcast.emit('user-connected', userID);
});
socket.on('user-disconnected', (userID) => {
console.log(`user-disconnected event for ${userID}`);
socket.broadcast.emit('user-disconnected', userID);
});
});
Code language: JavaScript (javascript)
Step-5: Run the Server
Now that we have our configurations ready we can let the server listen for default port 80 or in our case the port 3000 which will serve the static index.html file. Here is how we will do this.
app.get('/', (req, res, next) => res.redirect('/index.html'));
server.listen(3000, '0.0.0.0', () => {
console.log('Server is running on port 4000');
});
Code language: JavaScript (javascript)
Now navigate to your command prompt and let the server run with the node command.
Step-6: index.html file for client side
Now we are creating a index.html
file which will be our front end. We are using the Bootstrap for styling our front end page. This is simple one page application which do not need any fancy application framework, so we are not using any react.js or vue.js library here. We are making things as simple as possible. Here is our complete index.html file which we used to demonstrate the peerjs video calling example code in node.js.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Chat</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css" />
<!-- Peer.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/peerjs/1.4.7/peerjs.min.js"
integrity="sha512-y23HV23IWVEWlGpIez8Kvr87ZT16V9gICf5dfqu5rjxs7ip66pcIf87/uFbxn9tStGCgohZeIktIAHg9fjubsw=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Socket.io -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.6.1/socket.io.js"
integrity="sha512-xbQU0+iHqhVt7VIXi6vBJKPh3IQBF5B84sSHdjKiSccyX/1ZI7Vnkt2/8y8uruj63/DVmCxfUNohPNruthTEQA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Custom CSS -->
<style>
/* Set width of video elements */
video {
width: 100%;
}
/* Set height of video elements based on screen size */
@media (max-width: 767.98px) {
video {
height: 40vw;
}
}
@media (min-width: 768px) {
video {
height: 50vh;
}
}
/* Set margin of call and hangup buttons */
#callButton,
#hangupButton {
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container mt-3">
<h1 class="text-center">Video Chat</h1>
<div id="myId"></div>
<div class="row mt-3">
<div class="col-md-6">
<h2>Your Video</h2>
<video id="localVideo" autoplay playsinline muted></video>
</div>
<div class="col-md-6">
<h2>Remote Video</h2>
<video id="remoteVideo" autoplay playsinline></video>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12 text-center">
<button id="callButton" class="btn btn-success">Call</button>
<button id="hangupButton" class="btn btn-danger" disabled>Hang Up</button>
</div>
</div>
</div>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- Bootstrap JS -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- WebRTC Adapter -->
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<!-- JavaScript -->
<script>
// Get video elements
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");
// Initialize Peer object
const peer = new Peer();
// Initialize socket.io object
const socket = io();
peer.on('open', (id) => {
const myId = document.getElementById('myId');
localPeerId = id;
myId.innerText = `My ID: ${id}`;
});
// Get user media
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then((stream) => {
// Add stream to local video element
localVideo.srcObject = stream;
// Save stream to variable for later use
localStream = stream;
// Listen for incoming call
peer.on("call", (call) => {
// Answer call
call.answer(localStream);
// Add stream to remote video element
call.on("stream", (remoteStream) => {
remoteVideo.srcObject = remoteStream;
});
// Enable hangup button
document.getElementById("hangupButton").disabled = false;
});
// Add event listener for call button
document.getElementById("callButton").addEventListener("click", () => {
// Get ID of remote peer
const remotePeerID = prompt("Enter ID of remote peer:");
// Call remote peer
const call = peer.call(remotePeerID, localStream);
// Add stream to remote video element
call.on("stream", (remoteStream) => {
remoteVideo.srcObject = remoteStream;
});
// Add event listener for hangup button
document.getElementById("hangupButton").addEventListener("click", () => {
// End call
call.close();
// Disable hangup button
document.getElementById("hangupButton").disabled = true;
});
// Enable hangup button
document.getElementById("hangupButton").disabled = false;
});
}).catch((error) => {
console.log(error);
});
</script>
</body>
</html>
Code language: HTML, XML (xml)
Step-7: Client side Peerjs handling (Optional)
We also have to handle the peer.js library in our client side. We can create a separate file for this. Which will handle all the peer.js related requests. Here is our peer.js request handling file.
// Initialize PeerJS
const peer = new Peer();
// Get local video element
const localVideo = document.getElementById('local-video');
// Get remote video element
const remoteVideo = document.getElementById('remote-video');
// Media constraints for getUserMedia
const mediaConstraints = {
video: true,
audio: true
};
// Local stream variable
let localStream;
// Peer Connection variable
let peerConnection;
// Call button
const callButton = document.getElementById('call-button');
// Answer button
const answerButton = document.getElementById('answer-button');
// On PeerJS open event
peer.on('open', (peerId) => {
console.log(`PeerJS ID: ${peerId}`);
});
// On PeerJS error event
peer.on('error', (error) => {
console.log(`PeerJS Error: ${error}`);
});
// On call button click
callButton.addEventListener('click', () => {
// Get remote PeerJS ID from input field
const remotePeerId = document.getElementById('remote-peer-id').value;
// Create PeerConnection
createPeerConnection();
// Get local media stream and attach to local video element
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(stream => {
// Attach stream to local video element
localVideo.srcObject = stream;
localStream = stream;
// Add local stream to PeerConnection
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// Create and send offer to remote peer
return peer.call(remotePeerId, localStream);
})
.then(() => {
console.log('Call sent successfully!');
})
.catch(error => {
console.log(`getUserMedia() error: ${error}`);
});
});
// On answer button click
answerButton.addEventListener('click', () => {
// Answer incoming call
peer.on('call', incomingCall => {
// Create PeerConnection
createPeerConnection();
// Answer incoming call with local media stream
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(stream => {
// Attach stream to local video element
localVideo.srcObject = stream;
localStream = stream;
// Add local stream to PeerConnection
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// Answer incoming call with local stream
incomingCall.answer(localStream);
// Set remote video element source to incoming stream
incomingCall.on('stream', incomingStream => {
remoteVideo.srcObject = incomingStream;
});
})
.catch(error => {
console.log(`getUserMedia() error: ${error}`);
});
});
});
// Create PeerConnection function
function createPeerConnection() {
// Initialize PeerConnection
peerConnection = new RTCPeerConnection();
// On ICE candidate event
peerConnection.onicecandidate = event => {
if (event.candidate) {
console.log(`Sending ICE candidate to remote peer: ${JSON.stringify(event.candidate)}`);
peerConnection.send(JSON.stringify({ 'ice': event.candidate }));
}
};
// On track event
peerConnection.ontrack = event => {
console.log('Remote stream received!');
remoteVideo.srcObject = event.streams[0];
};
}
Code language: JavaScript (javascript)