发布于 2026-01-06 0 阅读
0

使用 Node.js 创建一个简单的语音聊天应用程序

使用 Node.js 创建一个简单的语音聊天应用程序

大家好,我是 Hossein。在本文中,我们将使用 Node.js 和 Socket.IO 构建一个简单的语音聊天 Web 应用程序。
首先,我们将为应用程序创建一个简单的界面。为此,我们将使用 Handlebars。
在开始编写代码之前,我们必须安装依赖项,请运行以下命令: 安装依赖项后,创建并打开index.js文件,并将以下代码放入其中:
npm init -y
npm i express socket.io express-handlebars


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

现在我们直接进入 Handlebars 文件部分,首先在 views 目录中创建main.handlebars 文件:

<!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

注意:main.handlebars是 handlebars 中的基本模板,您可以在 handlebars 设置中更改它。

在此步骤中,我们可以在 views 目录中创建index.handlebars 文件。

<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

/public/css/文件夹中创建index.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;
}

Enter fullscreen mode Exit fullscreen mode

替代文字

在本项目的最后一部分,我们使用 socket 来实现应用程序的实时性。现在,请将 socket 代码添加到index.js文件中的 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

之后,在/public/js/index.js 目录下创建一个前端 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);
}


Enter fullscreen mode Exit fullscreen mode

运行命令:
node index.js
恭喜!您现在拥有一个使用 Node.js 和 Socket.IO 创建的实时语音聊天应用程序。希望本文对您有所帮助,感谢您的阅读。

文章来源:https://dev.to/hosseinmobarakian/create-simple-voice-chat-app-with-nodejs-1b70