moved around a lot of stuff
This commit is contained in:
parent
b28a136c58
commit
8cdb4ca0da
5 changed files with 160 additions and 63 deletions
|
@ -7,41 +7,24 @@ const stat = require('fs/promises').stat;
|
||||||
const Message = require('./Message.js');
|
const Message = require('./Message.js');
|
||||||
const EventParser = require('./EventParser.js');
|
const EventParser = require('./EventParser.js');
|
||||||
|
|
||||||
|
const { CLIENT_STATE_READY, CLIENT_STATE_PLAYING, CLIENT_STATE_PAUSED, CLIENT_STATE_STOPPED, CLIENT_STATE_ERROR } = require('../libs/constants.js');
|
||||||
|
|
||||||
class AudioServer {
|
class AudioServer {
|
||||||
|
|
||||||
constructor(file) {
|
constructor(file) {
|
||||||
this.listen = config?.server?.listen || '0.0.0.0';
|
this.listen = config?.server?.listen || '0.0.0.0';
|
||||||
this.port = 0;
|
this.port = 0;
|
||||||
this.file = file;
|
this.buffer = {
|
||||||
|
file: file
|
||||||
|
};
|
||||||
this.clients = [];
|
this.clients = [];
|
||||||
this.broadcastClients = [];
|
this.broadcastClients = [];
|
||||||
|
this.position = 0;
|
||||||
this.server = net.createServer();
|
this.server = net.createServer();
|
||||||
this.eventParser = new EventParser();
|
this.eventParser = new EventParser();
|
||||||
this.#prepare();
|
this.#prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
|
||||||
if (this.aborted === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const buffer = await this.#waitForBuffer();
|
|
||||||
await this.#waitForAllClients();
|
|
||||||
this.#handleClientConnections();
|
|
||||||
const promises = [];
|
|
||||||
for (let index = 0; index < this.clients.length; index++) {
|
|
||||||
const client = this.clients[index];
|
|
||||||
client.audiostart = Date.now();
|
|
||||||
promises.push(new Promise((resolve, reject) => {
|
|
||||||
client.audiosocket.end(buffer, () => {
|
|
||||||
logger.debug(client.getTag() + ' sent audio file \'' + this.file + '\' after ' + (Date.now() - client.audiostart) + 'ms...');
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
await Promise.allSettled(promises);
|
|
||||||
await this.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
async #prepare() {
|
async #prepare() {
|
||||||
if (server?.clients === undefined || server.clients.length === 0) {
|
if (server?.clients === undefined || server.clients.length === 0) {
|
||||||
logger.warn('there are currently no clients connected, aborting preparation of audio server...')
|
logger.warn('there are currently no clients connected, aborting preparation of audio server...')
|
||||||
|
@ -61,14 +44,21 @@ class AudioServer {
|
||||||
reject('an error occured preparing the audio server for file \'' + this.file + '\' > ' + err);
|
reject('an error occured preparing the audio server for file \'' + this.file + '\' > ' + err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const stats = await stat(this.file);
|
const stats = await stat(this.buffer.file);
|
||||||
const broadcastedTo = await new Message('audiostream-initialize', { port: this.server.address().port, size: stats.size }).broadcast(true);
|
this.buffer.size = stats.size;
|
||||||
for (let index = 0; index < broadcastedTo.length; index++) {
|
this.buffer.threshold = (this.buffer.size / 100) / (!isNaN(config.audio?.threshold) || 30);
|
||||||
if (broadcastedTo[index]?.status !== 'fulfilled') {
|
this.broadcastClients = await new Message('audio:initialize', {
|
||||||
continue;
|
port: this.server.address().port,
|
||||||
}
|
size: this.buffer.size,
|
||||||
this.broadcastClients.push(broadcastedTo[index].value);
|
threshold: this.buffer.threshold
|
||||||
}
|
}).broadcast(true);
|
||||||
|
// const broadcastedTo =
|
||||||
|
// for (let index = 0; index < broadcastedTo.length; index++) {
|
||||||
|
// if (broadcastedTo[index]?.status !== 'fulfilled') {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// this.broadcastClients.push(broadcastedTo[index].value);
|
||||||
|
// }
|
||||||
logger.debug('sent broadcast for audio server to client(s) \'' + this.broadcastClients.toString() + '\'...');
|
logger.debug('sent broadcast for audio server to client(s) \'' + this.broadcastClients.toString() + '\'...');
|
||||||
this.#bufferFile();
|
this.#bufferFile();
|
||||||
}
|
}
|
||||||
|
@ -77,14 +67,8 @@ class AudioServer {
|
||||||
socket.on('data', (data) => {
|
socket.on('data', (data) => {
|
||||||
this.eventParser.parse(data, socket);
|
this.eventParser.parse(data, socket);
|
||||||
});
|
});
|
||||||
this.eventParser.on('audiostream-ready', (clientId, socket) => {
|
this.eventParser.on('audio:register', (clientId, socket) => {
|
||||||
let client;
|
let client = server.getClientById(clientId);
|
||||||
for (let index = 0; index < server.clients.length; index++) {
|
|
||||||
if (server.clients[index].id === clientId) {
|
|
||||||
client = server.clients[index];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (client === undefined) {
|
if (client === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +76,92 @@ class AudioServer {
|
||||||
this.clients.push(client);
|
this.clients.push(client);
|
||||||
logger.debug(client.getTag() + ' connected to audio server...');
|
logger.debug(client.getTag() + ' connected to audio server...');
|
||||||
this.broadcastClients.splice(this.broadcastClients.indexOf(clientId), 1);
|
this.broadcastClients.splice(this.broadcastClients.indexOf(clientId), 1);
|
||||||
|
this.#sendData(client);
|
||||||
});
|
});
|
||||||
|
this.eventParser.on('audio:ready', async (clientId) => {
|
||||||
|
let allClientsReady = true;
|
||||||
|
for (let index = 0; index < this.clients.length; index++) {
|
||||||
|
const client = this.clients[index];
|
||||||
|
if (client.id === clientId) {
|
||||||
|
client.state = CLIENT_STATE_READY;
|
||||||
|
logger.debug(client.getTag() + ' is ready for playback...');
|
||||||
|
}
|
||||||
|
if (client.state !== CLIENT_STATE_READY) {
|
||||||
|
allClientsReady = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allClientsReady !== true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const broadcastedTo = await new Message('audio:play', { position: this.position }).broadcast();
|
||||||
|
logger.debug('sent broadcast for playback to client(s) \'' + broadcastedTo + '\'...');
|
||||||
|
});
|
||||||
|
this.eventParser.on('audio:paused', (position) => {
|
||||||
|
if (!isNaN(position) && positon > this.position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
let client = this.#getClientById(clientId);
|
||||||
|
if (client === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
client.state = CLIENT_STATE_PAUSED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async #sendData(client) {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const buffer = await this.#waitForBuffer();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
client.audiosocket.end(buffer, () => {
|
||||||
|
logger.debug(client.getTag() + ' sent audio file \'' + this.buffer.file + '\' after ' + (Date.now() - timestamp) + 'ms...');
|
||||||
|
});
|
||||||
|
client.audiosocket.on('error', (error) => {
|
||||||
|
logger.error(client.getTag() + ' encountered an error: ' + error);
|
||||||
|
});
|
||||||
|
client.audiosocket.on('end', () => {
|
||||||
|
logger.debug(client.getTag() + ' ended audio socket');
|
||||||
|
});
|
||||||
|
client.audiosocket.on('close', (hadError) => {
|
||||||
|
let fn = resolve;
|
||||||
|
let msg = client.getTag() + ' closed audio socket';
|
||||||
|
if (hadError === true) {
|
||||||
|
msg += ' after an error';
|
||||||
|
fn = reject;
|
||||||
|
}
|
||||||
|
logger.debug(msg);
|
||||||
|
fn(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async #sendAudio() {
|
||||||
|
if (this.aborted === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const buffer = await this.#waitForBuffer();
|
||||||
|
await this.#waitForAllClients();
|
||||||
|
this.#handleClientConnections();
|
||||||
|
const promises = [];
|
||||||
|
for (let index = 0; index < this.clients.length; index++) {
|
||||||
|
const client = this.clients[index];
|
||||||
|
client.audiostart = Date.now();
|
||||||
|
promises.push(new Promise((resolve, reject) => {
|
||||||
|
client.audiosocket.end(buffer, () => {
|
||||||
|
logger.debug(client.getTag() + ' sent audio file \'' + this.file + '\' after ' + (Date.now() - client.audiostart) + 'ms...');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
await Promise.allSettled(promises);
|
||||||
|
await this.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async #waitForAllClients() {
|
||||||
|
while (this.broadcastClients.length > 0) {
|
||||||
|
await sleep(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#handleClientConnections() {
|
#handleClientConnections() {
|
||||||
|
@ -115,31 +184,24 @@ class AudioServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async #waitForAllClients() {
|
|
||||||
while (this.broadcastClients.length > 0) {
|
|
||||||
await sleep(1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async #waitForBuffer() {
|
async #waitForBuffer() {
|
||||||
while (this.buffer === undefined) {
|
while (this.buffer.data === undefined || this.buffer.data.length < this.buffer.size) {
|
||||||
await sleep(1);
|
await sleep(1);
|
||||||
}
|
}
|
||||||
return this.buffer;
|
return this.buffer.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #bufferFile() {
|
async #bufferFile() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const buffer = [];
|
const buffer = [];
|
||||||
const stream = fs.createReadStream(this.file);
|
const stream = fs.createReadStream(this.buffer.file);
|
||||||
stream.on('data', (data) => {
|
stream.on('data', (data) => {
|
||||||
buffer.push(data);
|
buffer.push(data);
|
||||||
});
|
});
|
||||||
stream.on('close', () => {
|
stream.on('close', () => {
|
||||||
this.buffer = Buffer.concat(buffer);
|
this.buffer.data = Buffer.concat(buffer);
|
||||||
logger.debug('buffering file \'' + this.file + '\' took ' + (Date.now() - timestamp) + 'ms (length: ' + this.buffer.length + ' bytes)');
|
logger.debug('buffering file \'' + this.buffer.file + '\' took ' + (Date.now() - timestamp) + 'ms (size: ' + this.buffer.data.length + ' bytes)');
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
stream.on('error', (error) => {
|
stream.on('error', (error) => {
|
||||||
|
@ -149,8 +211,21 @@ class AudioServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#getClientById(clientId) {
|
||||||
|
if (clientId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let index = 0; index < this.clients.length; index++) {
|
||||||
|
const client = this.clients[index];
|
||||||
|
if (client.id !== clientId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async destroy() {
|
async destroy() {
|
||||||
this.eventParser.removeAllListeners('audiostream-ready');
|
this.eventParser.removeAllListeners('audio:ready');
|
||||||
for (let index = 0; index < this.clients.length; index++) {
|
for (let index = 0; index < this.clients.length; index++) {
|
||||||
const audiosocket = this.clients[index].audiosocket;
|
const audiosocket = this.clients[index].audiosocket;
|
||||||
if (audiosocket.destroyed === true) {
|
if (audiosocket.destroyed === true) {
|
||||||
|
|
|
@ -39,7 +39,15 @@ class Message {
|
||||||
for (let index = 0; index < server.clients.length; index++) {
|
for (let index = 0; index < server.clients.length; index++) {
|
||||||
promises.push(this.send(server.clients[index], addClientId));
|
promises.push(this.send(server.clients[index], addClientId));
|
||||||
}
|
}
|
||||||
return await Promise.allSettled(promises);
|
const reached = [];
|
||||||
|
const result = await Promise.allSettled(promises);
|
||||||
|
for (let index = 0; index < result.length; index++) {
|
||||||
|
if (result[index]?.status !== 'fulfilled') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reached.push(result[index].value);
|
||||||
|
}
|
||||||
|
return reached;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,6 @@ class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
// setInterval(() => {
|
|
||||||
// const audioServer = new AudioServer('/mnt/kingston/downloads/DOPESMOKER.flac');
|
|
||||||
// audioServer.start();
|
|
||||||
// }, 10000);
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.server.listen(this.port, this.listen).on('listening', () => {
|
this.server.listen(this.port, this.listen).on('listening', () => {
|
||||||
this.port = this.server.address().port;
|
this.port = this.server.address().port;
|
||||||
|
@ -48,8 +44,7 @@ class Server {
|
||||||
|
|
||||||
#addClient(socket) {
|
#addClient(socket) {
|
||||||
this.clients.push(new Client(socket));
|
this.clients.push(new Client(socket));
|
||||||
const audioServer = new AudioServer('/mnt/kingston/downloads/DOPESMOKER.flac');
|
new AudioServer('/mnt/kingston/public/DOPESMOKER.flac');
|
||||||
audioServer.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeClient(client) {
|
removeClient(client) {
|
||||||
|
@ -57,6 +52,19 @@ class Server {
|
||||||
this.clients.splice(this.clients.indexOf(client), 1);
|
this.clients.splice(this.clients.indexOf(client), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getClientById(clientId) {
|
||||||
|
if (clientId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let index = 0; index < this.clients.length; index++) {
|
||||||
|
const client = this.clients[index];
|
||||||
|
if (client.id !== clientId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Server;
|
module.exports = Server;
|
|
@ -20,5 +20,8 @@
|
||||||
"database": "kannon",
|
"database": "kannon",
|
||||||
"username": "postgres",
|
"username": "postgres",
|
||||||
"password": "$Velvet90"
|
"password": "$Velvet90"
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"threshold": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,8 +3,11 @@ module.exports = {
|
||||||
FS_EVENT_UNLINK: 'unlink',
|
FS_EVENT_UNLINK: 'unlink',
|
||||||
FS_EVENT_CHANGE: 'change',
|
FS_EVENT_CHANGE: 'change',
|
||||||
|
|
||||||
SOCKET_EVENT_PING: 'ping',
|
EVENT_DELIMITER: '<<< kannon >>>',
|
||||||
SOCKET_EVENT_PONG: 'pong',
|
|
||||||
|
|
||||||
EVENT_DELIMITER: '<<< kannon >>>'
|
CLIENT_STATE_READY: 'ready',
|
||||||
|
CLIENT_STATE_PLAYING: 'playing',
|
||||||
|
CLIENT_STATE_PAUSED: 'paused',
|
||||||
|
CLIENT_STATE_STOPPED: 'stopped',
|
||||||
|
CLIENT_STATE_ERROR: 'error'
|
||||||
}
|
}
|
Loading…
Reference in a new issue