Create simple voice chat app with nodejs
Tags: javascript, node, socketio, handlebars
Published: 2021-06-13 17:54:48

Hi, I'm Hossein in this article we will build a simple voice chat web application with nodejs and socketIo.
In the first step, we will create a simple interface for our app. To did that we use handlebars.
Before starting coding we must install dependencies, run commands below:
`npm init -y
npm i express socket.io express-handlebars`
After installing dependencies, create and open **index.js** file and put codes below into it:
```javascript
const express = require("express");
const app = express();
const handlebars = require("express-handlebars");
const http = require("http").Server(app);
const io = require("socket.io")(http);

//To holding users information 
const socketsStatus = {};

//config and set handlebars to express
const customHandlebars = handlebars.create({ layoutsDir: "./views" });

app.engine("handlebars", customHandlebars.engine);
app.set("view engine", "handlebars");

//enable user access to public folder 
app.use("/files", express.static("public"));

app.get("/home" , (req , res)=>{
    res.render("index");
});

http.listen(3000, () => {
  console.log("the app is run in port 3000!");
});

```
Now we jump into handlebars files first create **main.handlebars** in views directory:
```html
<!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>Hossein Mobarakian - voice chat application</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.1.2/socket.io.js" integrity="sha512-iZIBSs+gDyTH0ZhUem9eQ1t4DcEn2B9lHxfRMeGQhyNdSUz+rb+5A3ummX6DQTOIs1XK0gOteOg/LPtSo9VJ+w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
    {{{body}}}
    
</body>
</html>
```
> Note: **main.handlebars** is the base template in handlebars and you can change it in config your handlebars setting.

In this step we can go to create **index.handlebars** in the views directory.
```html
<header>
    <div class="user-controller">
        <p id="username-label"></p>
        <div id="username-div">
            <input type="text" id="username">
            <button class="username-btn" onclick="changeUsername()">Change username</button>

        </div>
    </div>

    <div class="controller">
        <button class="control-btn disable-btn" onclick="toggleMicrophone(this)">Open microphone</button>
        <button class="control-btn disable-btn" onclick="toggleMute(this)">Mute</button>
        <button class="control-btn disable-btn" onclick="toggleConnection(this)">Go online</button>
    </div>

</header>
<h2>users list</h2>
<ul class="users" id="users">


</ul>

<script src="/files/js/index.js"></script>
<link rel="stylesheet" href="/files/css/index.css">
```
Add some style to our interface with create **index.css** in **/public/css/** folder.
```css
html , body {
    width: 100%;
    height: 100%;
    overflow: hidden;
}
.controller{
    margin: 0;
    padding: 0;
    overflow: hidden;
    display: flex;
    justify-content: center;

}
body{
    display: flex;
    text-align: center;
    flex-flow: column;
    margin: 0;
    padding: 0;
    background-color: rgb(12 11 25);
    color: #fff;
}
header{
    margin:0;
    padding: 20px 0;
    width: 100%;
    height: fit-content;
    background-color: rgb(15, 15, 44);
    color: #fff;

}
.control-btn{
    width: 120px;
    padding: 10px 0;
    border: none;
    border-radius: 8px;
    cursor: pointer;
}
.enable-btn{
    background-color: rgb(26, 184, 26);
    color: #fff;
    border-bottom: 5px solid rgb(18, 131, 18);
    margin: 10px ;
}

.enable-btn:hover{
    border-bottom: none;
    margin-top: 15px;
}
.disable-btn{
    margin: 10px ;
    background-color: rgb(172, 25, 25);
    color: #fff;
    border-bottom: 5px solid rgb(184, 57, 57);
}
.disable-btn:hover{
    border-bottom: none;
    margin-top: 15px;
}
.username-btn{
    width: 200px;
    margin: 10px auto;
    padding: 10px 0;
}
input{
    width: 200px;
    padding: 10px;
    margin: 10px auto;
}

#username-div{
    display: none;
}
#username-label{
    width: 200px;
    height: fit-content;
    margin: 0 auto;
    padding: 10px 20px;
    background-color: rgb(12 11 25);
    border-radius: 8px;
    border: 2px solid rgb(26, 26, 77);
    cursor: pointer;
}
ul.users{
    width: 100%;
    margin: 0;
    padding: 0;
}
ul.users li{
    width: 90%;
    margin: 10px auto;
    padding: 10px 0;
    text-align: center;
    background-color: rgb(15 15 44);
    list-style: none;
    color: #fff;
    border-radius: 8px;
}

```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h27oozkl06ws6ze8tu3i.png)

In the last part of this project, we use socket to make our app in realtime.Now put socket codes into **index.js** above the http.listen(...):
```javascript

io.on("connection", function (socket) {
  const socketId = socket.id;
  socketsStatus[socket.id] = {};
  

  console.log("connect");

  socket.on("voice", function (data) {

    var newData = data.split(";");
    newData[0] = "data:audio/ogg;";
    newData = newData[0] + newData[1];

    for (const id in socketsStatus) {

      if (id != socketId && !socketsStatus[id].mute && socketsStatus[id].online)
        socket.broadcast.to(id).emit("send", newData);
    }

  });

  socket.on("userInformation", function (data) {
    socketsStatus[socketId] = data;
   
    io.sockets.emit("usersUpdate",socketsStatus);
  });


  socket.on("disconnect", function () {
    delete socketsStatus[socketId];
  });

});
```
After that create a front-end javascript file in **/public/js/index.js** and put codes below into it:
```javascript
const userStatus = {
  microphone: false,
  mute: false,
  username: "user#" + Math.floor(Math.random() * 999999),
  online: false,
};

const usernameInput = document.getElementById("username");
const usernameLabel = document.getElementById("username-label");
const usernameDiv = document.getElementById("username-div");
const usersDiv = document.getElementById("users");

usernameInput.value = userStatus.username;
usernameLabel.innerText = userStatus.username;


window.onload = (e) => {
  mainFunction(1000);
};

var socket = io("ws://localhost:3000");
socket.emit("userInformation", userStatus);


function mainFunction(time) {


  navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
    var madiaRecorder = new MediaRecorder(stream);
    madiaRecorder.start();

    var audioChunks = [];

    madiaRecorder.addEventListener("dataavailable", function (event) {
      audioChunks.push(event.data);
    });

    madiaRecorder.addEventListener("stop", function () {
      var audioBlob = new Blob(audioChunks);

      audioChunks = [];

      var fileReader = new FileReader();
      fileReader.readAsDataURL(audioBlob);
      fileReader.onloadend = function () {
        if (!userStatus.microphone || !userStatus.online) return;

        var base64String = fileReader.result;
        socket.emit("voice", base64String);

      };

      madiaRecorder.start();


      setTimeout(function () {
        madiaRecorder.stop();
      }, time);
    });

    setTimeout(function () {
      madiaRecorder.stop();
    }, time);
  });


  socket.on("send", function (data) {
    var audio = new Audio(data);
    audio.play();
  });

  socket.on("usersUpdate", function (data) {
    usersDiv.innerHTML = '';
    for (const key in data) {
      if (!Object.hasOwnProperty.call(data, key)) continue;

      const element = data[key];
      const li = document.createElement("li");
      li.innerText = element.username;
      usersDiv.append(li);

    }
  });

}

usernameLabel.onclick = function () {
  usernameDiv.style.display = "block";
  usernameLabel.style.display = "none";
}

function changeUsername() {
  userStatus.username = usernameInput.value;
  usernameLabel.innerText = userStatus.username;
  usernameDiv.style.display = "none";
  usernameLabel.style.display = "block";
  emitUserInformation();
}

function toggleConnection(e) {
  userStatus.online = !userStatus.online;

  editButtonClass(e, userStatus.online);
  emitUserInformation();
}

function toggleMute(e) {
  userStatus.mute = !userStatus.mute;

  editButtonClass(e, userStatus.mute);
  emitUserInformation();
}

function toggleMicrophone(e) {
  userStatus.microphone = !userStatus.microphone;
  editButtonClass(e, userStatus.microphone);
  emitUserInformation();
}


function editButtonClass(target, bool) {
  const classList = target.classList;
  classList.remove("enable-btn");
  classList.remove("disable-btn");

  if (bool)
    return classList.add("enable-btn");

  classList.add("disable-btn");
}

function emitUserInformation() {
  socket.emit("userInformation", userStatus);
}


```
run command:
`node index.js`
Congratulation! Now you have a realtime voice chat app created with nodejs and socketIo.I hope useful this article to you and thank you to read it.