How to build a Video Meeting app using WebRTC and Socket.io

Here we will develop a video meeting application. Here meeting id will generate automatically. With whom you share your meeting id, they can join your meeting. So, let’s build this gorgeous application.

How to build a Video Meeting app using WebRTC and Socket.io
ByteCall Video Meeting application

Let’s build a video meeting application. Here we will build a single page application that runs on Express server.

You can download full application code from our GitHub repo. To run this app, you need to install node and Express on your system.

If node not installed in your system. You can run following command.

$ sudo apt update
$ sudo apt install nodejs

Or, you can install useing npm.

Also, you system should connect with a camera or webcam. You also need second device to check remote connection to video call.

Our using dependencies:

  • Nodejs – For backend.
  • Expressjs – Nodejs server.
  • Socketio – To continuous connection between devices.
  • WebRTC – Realtime communication between browsers.
  • HTML, CSSS, vanilla etc.

Start Setup Project:

Open your editor and create a folder like “ByteCall”. Then create some folder following this structure:

ByteCall
   |- public
        |- css
            |- call_video.css
            |- call_chat.css
        |- js
            |- call_video.js
            |- call_chat.js
   |- views
        |- call.ejs
   |- index.js
   |- .gitignore

To start your project, you have to initialize npm in your bytecall folder. To initialize run this command:

$ npm init -y

Then you have to fill this by following:

{
  "name": "bytecall",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js"
  },
  "author": "Sunanda",
  "license": "ISC",
}

Now you have to install all dependencies:

$ npm install dotenv ejs express http nodemon peer socket.io

After install all dependencies, copy this code to index.js file.

const express = require('express');     //import express
const http = require('http');
const socket = require('socket.io');
require('dotenv').config()          //using .env file

var app = express();
const server = http.createServer(app);
const io = socket(server);
var id=234234234243;          //It would be your meeting id

app.set('view engine', 'ejs');
app.use(express.static('public'));


app.get('/', (req, res)=>{
    res.render('call', {callID:id});
    
})


io.on('connection', socketio =>{
    socketio.on('join-room', (callID, user, id)=>{
        // console.log(callID, user)
        socketio.join(callID)
        socketio.emit('message', {
        user: null,
        id: id,
        message: `Hi ${user}, Welcome to ByteCall`
        });    //Welcome message for us
        socketio.to(callID).broadcast.emit('connected-user',{
            user: user,
            id: id,
            message: `${user} joined the meeting!`
        })
        socketio.on('chatting',msg =>{
        // console.log(msg)
            io.to(callID).emit('message', msg);
        })
        socketio.on('disconnect', ()=>{
            io.to(callID).emit('disconnect-user', {
                user: user,
                id: id,
                message: `${user} just disconnected!`
        })
    })

    })



})


const PORT = process.env.PORT || 3000;   //server run port
server.listen(PORT, 
    ()=>{
        console.log(`Server started at: http://127.0.0.1:${PORT}`);
    })

After that setup your views/call.ejs file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ByteCall Video Meeting Application</title>
      <!-- Connect files -->
    <link rel="stylesheet" href="css/call_chat.css">
    <link rel="stylesheet" href="css/call_video.css">
    <script>
        const callID = "<%= callID %>"   <!-- Pass callID from server -->
    </script>
    <script src="https://kit.fontawesome.com/[API-KEY].js" crossorigin="anonymous"></script>   <!-- Give your fontawsome key -->
    <script src="/socket.io/socket.io.js"></script>
          <!-- Connect files -->

</head>
<body>
    <div class="nav">
        <div class="navLeft">
            <h2><u>Byte</u>Call</h2>
        </div>
        <div class="navMid">
            <p>Share to Join: <u id="url" ></u></p>
        </div>
        <div class="navRight">
            <h4><a href="https://www.linkedin.com/in/sunanda35/" target="_blank"><i id="linkdin" class="fab fa-linkedin"></i></a><a href="https://github.com/sunanda35/byteCall" target="_blank"><i id="github" class="fab fa-github"></i></a></h4>
        </div>
    </div>
    <div class="mainBody">
        <div class="video">
            <div id="video-grid"></div>
            <div id="video-control">
                <div class="control_flex">
                    <div class="control">
                        <i id="microphone" class="fal fa-microphone-alt-slash" onclick="muteAudio()"></i>
                        <a href="/"><i id="hang-up" class="fad fa-phone-rotary" onclick="leave()"></i></a>
                        <i id="videoMute" class="far fa-video" onclick="muteVideo()" ></i>
                    </div>
                    <p id="share_screen" onclick="startCapture()" >Share Screen</p>
                </div>
            </div>
            
        </div>
        <div class="chat">
                <div class="leave">
                    <h3>Chat With everyone!</h3>
                </div>
            <div class="msg_container">
                <!-- All msg will appear here by script -->
            </div>
            <form id="msgInput">    
                <div class="send">
                    <input autocomplete="off" id="message" type="text" placeholder="Message here...">
                    <button id="button" type="submit">➤</button>
                </div>
            </form>
        </div>
    </div>
</body>
<script>
    document.getElementById("url").innerHTML = window.location.href;
</script>
<script src="js/call_chat.js"></script>
<script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js" ></script>
<script src="js/call_video.js"></script>
</html>

Now put video call page css code to public/css/call_video.css file:

.video{
    width: 75%;
    height: 100%;
    background-color: #C2C2C2;
    padding-top: 3px;
    padding-left: 1px;
    padding-right: 1px;
    border-radius: 3px;
    overflow-y: auto;
}

#video-grid{
    display: flex;
    justify-content: flex-start;
    flex-wrap: wrap;
}
#video-control{
    height: 60px;
    width: 75%;
    background-color: rgb(245, 245, 245);
    position: fixed;
    bottom: 0;
    z-index: 5000000;
}
.control_flex{
    display: flex;
    justify-content: space-around;
}
.control{
    margin-top: 5px;
    margin-left: 30%;
    display: flex;
}
.control > i{
    font-size: 1.5rem;
    padding: 10px;
    margin-left: 5px;
    border-radius: 20px;
    color: rgb(179, 179, 179);
    border: 1px solid rgb(224, 224, 224);
    cursor: pointer;
}
.control > a{
    text-decoration: none;
    margin-top: auto;
    margin-bottom: auto;
    margin-left: 5px;
}
.control > i:hover{
    border-color: rgb(155, 155, 155);
}
#hang-up{
    color: rgb(255, 94, 94);
}
#hang-up:hover{
    color: red;
}
#microphone:hover{
    background-color: rgb(255, 101, 101);
    color: rgb(233, 233, 233);
}
#videoMute:hover{
    background-color: rgb(255, 101, 101);
    color: rgb(233, 233, 233);
}
#share_screen{
    margin-top: auto;
    margin-bottom: auto;
    padding: 5px;
    border-radius: 3px;
    background-color: #1C635A;
    color: #C2C2C2;
    cursor: pointer;
}
#share_screen:hover{
    background-color: #007566;
}

video{
    width: 24%;
    height: auto;
}
.videoElement{
    border-radius: 10px;
    margin: 4px;
    cursor: pointer;
}
a{
    text-decoration: none;
}

Then put all video js code to public/js/call_video.js file:

const videoGrid = document.getElementById('video-grid')


const peer = new Peer({
  config: {'iceServers': [
    { url: 'stun:stun.l.google.com:19302' },
    { url: 'stun:stun1.l.google.com:19302' },
    { url: 'stun:stun2.l.google.com:19302' }
  ]} /* Some google's free stun server! */
});

var myVideo = document.createElement('video');
myVideo.muted= true;
const peers = {};

peer.on('open', id=>{
  socketio.emit('join-room', callID, nameData, id)
})
// socketio.emit('join-room', callID, peerid from stun server)

let streamControl;
if (navigator.mediaDevices.getUserMedia) {
  navigator.mediaDevices.getUserMedia({ 
    video: {
      frameRate: {
        min: 10,
        ideal: 25,
        max: 35
      },
      width: {
        min: 480,
        ideal: 720,
        max: 1280
      },
      aspectRatio: 1.33333,
    }, 
    audio: {
      echoCancellation: true,
      noiseSuppression: true,
      autoGainControl: false,
      sampleRate: 44100
    }
    
  })
    .then(function (stream) {
      streamControl=stream;

      addVideoStream(myVideo, streamControl)
      peer.on('call', call=>{
        call.answer(streamControl)
        const video = document.createElement('video')
        call.on('stream', userVideoStream=>{

          addVideoStream(video, userVideoStream)
        })
        call.on('close', ()=>{
          video.remove();
        })
      })


      socketio.on('connected-user', msg=>{
        connectToNewUser(msg.id, streamControl)
      })
      socketio.on('disconnect-user', msg=>{
        console.log(msg.user)
        if(peers[msg.id])peers[msg.id].close()
        // retryConnectOnFailure(RETRY_INTERVAL);
      })
      
    })
    .catch(function (error) {
      alert(error)
      console.log("Something went wrong!");
    });
}
function connectToNewUser(userid, streamControl){
  const call = peer.call(userid, streamControl);
  const video = document.createElement('video');
  call.on('stream', userVideoStream=>{
    addVideoStream(video, userVideoStream)
  })
  call.on('close', ()=>{
    video.remove();
  })
  peers[userid] = call
}

function addVideoStream(video, streamControl){
  video.srcObject = streamControl;
  video.addEventListener('loadedmetadata', ()=>{
      video.play();
  })
  var random = Math.floor(Math.random() * 100000);
  video.className = 'videoElement';
  video.id = random;
  videoGrid.append(video);
}


let isAudio = true
function muteAudio() {
  if(streamControl != null && streamControl.getAudioTracks().length > 0){
    isAudio = !isAudio
    streamControl.getAudioTracks()[0].enabled = isAudio
  }
    
}

let isVideo = true
function muteVideo() {
  if(streamControl != null && streamControl.getVideoTracks().length > 0){
    isVideo = !isVideo
    streamControl.getVideoTracks()[0].enabled = isVideo
  }
    
}

let isScreenShare = false;
async function startCapture() {
  isScreenShare = !isScreenShare;
  await navigator.mediaDevices.getDisplayMedia(
    {
      cursor: true
    }
  ).then(function(stream){
    streamControl=stream;
    const video = document.createElement('video');
    video.className='sc_capture'
    addVideoStream(video, stream)
    stream.onended = () => { 
      var shareVideo = document.getElementsByName("sc_capture");
        video.remove();     
      console.info("Recording has ended");
      alert('This capture uable to see your friends!')
    };
    
  });
}

Gratulerer! Your ByteCall video meeting application is now ready to call your friend or caligue. Hew we user some google’s free stun server, you can make your main server as a stun server or user some other stun server. It just help to communicate multiple devices.

How to build a Video Meeting app using WebRTC and Socket.io
Source: andrewjprokop.wordpress.com

Acutally, i did it as my way of doing. You can come up with your own file structure and technique of coding. To run this and call between disconnected devices, you had to host your code on any server. Remember static file hosting will not serfe this code and it will not run and show you nothing. You had to host on server which will serve your code like Heroku, netlify etc.

Remember! without https connection, some browser will not give the access of camera and audio for some security reason. So you can generate your own ssl or use vendor default generate ssl certificate.

Deploy to Git:

  1. Make a new repo from your git profile.
  2. open your terminal and go in the ByteCall folder and run $ git init command.
  3. After that you had to connect your repo link as a remote file. You can follow every command will provide in git.
$ git commit -m "[message ex. first commit]"
$ git branch -M main/master
$ git remote add origin [Your repository link]
$ git push -u origin main/master

Your code just pushed on gitub. Just refresh your git repo on browser and you will see the changes.

Then you can push this code from GitHub to any hosting vendor directly and it will automatically publish to the vendor after your new push changes.

If this became more clustures for you, you can clone out GitHub repository directly and run command:

$ npm install

Then to start this code, run

$ npm start

In this tutorial, you learned about ByteCall. You learned how to build a real-time video meeting application. It is end-to-end, here we are not using any database to store any data. It’s from user to user data connection. Clone our repo and make something cool. Have fun!

If you facing any problem with this, you can enlist issue here.

Leave a Reply

Your email address will not be published.