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!");
});
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>
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">
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;
}
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];
});
});
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);
}
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.
Top comments (14)
OK I would like to add a few suggestions
newData.join("")
for the server or you could just do"data:audio/ogg;"+data
and that would work as wellnavigator
ANDnavigator.mediaDevices
exists ( Just a simple if check )But thank you very much for all the code! It's very helpful! However the 1 second delay is kindaa... ehh but I tried to change it to <1000ms and it didn't seam to work without cutting out sadly
Is there a GitHub repo for this?
No
Friend I am developing a backend server of a game where players can communicate with each other with real time audio and chat message can you help me a little with it......Thankyou and your this code helped me alot
Would be nice if there was a running demo app.
looking forward to try this
Someone upload the structured code to github. I've become too lazy to structure this codeš
If you upload your codes on somewhere like GitHub, it will help others to use it more easily
Thank you for that!!!
Hmm i'm getting a data.split error, that i cant read split of null, so data seems to be null.
use default values like
Is anyone here successfully installed this code. Can you share?