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?