Files

163 lines
3.9 KiB
JavaScript

/////////////////////////////////////////////////////////////////
/*
ESP32 | Walkie-Talkie based on Node.js Server for multi-clients (ft. PCM speaker)
Video Tutorial: https://youtu.be/vq7mPgecGKA
Created by Eric Nam (ThatProject)
*/
/////////////////////////////////////////////////////////////////
////////////
// States
const states = {
IDLE: "Idle",
LISTENING: "Listening",
SPEAKING: "Speaking"
};
////////////
// Speaker(Optional)
// MacOS Issue
// [../deps/mpg123/src/output/coreaudio.c:81] warning: Didn't have any audio data in callback (buffer underflow)
// https://github.com/audiojs/audio-play/issues/15
const Speaker = require("speaker");
const Readable = require("stream").Readable;
const pcmSpeaker = new Readable();
pcmSpeaker.bitDepth = 32;
pcmSpeaker.channels = 1;
pcmSpeaker.sampleRate = 16000;
pcmSpeaker._read = bufRead;
pcmSpeaker.pipe(new Speaker());
function bufRead(buf) {
if (buf instanceof Buffer) {
pcmSpeaker.resume();
pcmSpeaker.push(buf);
} else if (buf == null) {
pcmSpeaker.pause();
}
}
////////////
// WebSocket
const WebSocket = require("ws");
const WS_PORT = process.env.WS_PORT || 8888;
const wsServer = new WebSocket.Server(
{
port: WS_PORT,
perMessageDeflate: {
concurrencyLimit: 10,
threshold: 1024
}
},
() => console.log(`[Server] WS server is listening at ${WS_PORT}`)
);
wsServer.on("connection", (ws, req) => {
console.log("[Client] Connected!");
ws.timestamp = getTimestamp();
ws.id = wsServer.getUniqueID(ws.timestamp);
ws.state = states.IDLE;
ws.send(ws.timestamp);
ws.on("message", (event) => {
messageHandler(ws, event);
});
});
wsServer.getUniqueID = function (timestamp) {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return timestamp + "-" + s4();
};
function messageHandler(ws, event) {
ws.timestamp = getTimestamp();
if (event instanceof Buffer) {
if (event.length == 1) {
console.log("[Client] Ping from: " + ws.id);
} else if (event.length > 1) {
sendDataClients(ws, event);
bufRead(event); // Resume Speaker Out
}
} else {
if (checkAllClientState()) {
ws.state = states.SPEAKING;
ws.send(event);
} else {
ws.state = states.IDLE;
bufRead(null); // Pause Speaker Out
}
sendDataClients(ws, event);
}
}
function sendDataClients(d_client, data) {
var timestamp = getTimestamp();
wsServer.clients.forEach(function each(client) {
if (d_client != client) {
client.timestamp = timestamp;
console.log("[Client] Client Id: " + client.id + " Client State: " + client.state);
if (client.state == states.IDLE) {
// OTHER CLIENTS, IDLE -> LISTENING
if (data instanceof Buffer) {
// NO ACTION REQUIRED
} else {
client.state = states.LISTENING;
client.send(timestamp);
}
} else {
// OTHER CLIENTS, LISTENING -> IDLE
if (data instanceof Buffer) {
client.send(data);
} else {
client.state = states.IDLE;
client.send(timestamp);
}
}
}
});
}
function getTimestamp() {
var last8digit_Timestamp = Date.now().toString().slice(-8);
return parseInt(last8digit_Timestamp);
}
function checkAllClientState() {
var result = false;
wsServer.clients.forEach(function each(client) {
if (client.state != states.IDLE) {
result = false;
return;
}
result = true;
});
return result;
}
function checkAlive() {
console.log("[Server] Total number of connected clients: " + wsServer.clients.size);
wsServer.clients.forEach(function each(client) {
if (client.state == states.IDLE) {
console.log(
"[Check Clinet Alive] Client Id: " +
client.id +
" Timestamp DIFF: " +
(getTimestamp() - client.timestamp)
);
if (getTimestamp() - client.timestamp > 10000) {
// 10sec
console.log("[Client TimeOut] Client Id: " + client.id);
client.close;
wsServer.clients.delete(client);
}
}
});
}
setInterval(function () {
checkAlive();
}, 5000);