DEV Community

loading...
Cover image for Create simple voice chat app with nodejs

Create simple voice chat app with nodejs

h_mobarakian
nodejs developer
・4 min read

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:

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!");
});

Enter fullscreen mode Exit fullscreen mode

Now we jump into handlebars files first create main.handlebars in views directory:

<!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>
Enter fullscreen mode Exit fullscreen mode

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.

<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">
Enter fullscreen mode Exit fullscreen mode

Add some style to our interface with create index.css in /public/css/ folder.

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;
}

Enter fullscreen mode Exit fullscreen mode

Alt Text

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(...):


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];
  });

});
Enter fullscreen mode Exit fullscreen mode

After that create a front-end javascript file in /public/js/index.js and put codes below into it:

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);
}


Enter fullscreen mode Exit fullscreen mode

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.

Discussion (3)

Collapse
judgegodwins profile image
Judgegodwins

Is there a GitHub repo for this?

Collapse
hosseinmobarakian profile image
h_mobarakian Author

No

Collapse
lylest profile image
wackyizzy

looking forward to try this