diff --git a/classes/Api.js b/classes/Api.js index 3fe4488..df704b4 100644 --- a/classes/Api.js +++ b/classes/Api.js @@ -40,17 +40,19 @@ class Api { } global.audioserver = new AudioServer('/home/velvettear/mounts/kingston/public/test.pcm'); }); + this.#registerEndpoint(constants.API_RESUME, constants.REQUEST_METHOD_POST, async () => { + if (global.audioserver === undefined) { + return; + } + await global.audioserver.destroy(); + global.audioserver = new AudioServer('/home/velvettear/mounts/kingston/public/test.pcm', global.audioserver.progress); + }); this.#registerEndpoint(constants.API_PAUSE, constants.REQUEST_METHOD_POST, async () => { if (global.audioserver === undefined) { return; } global.audioserver.pausePlayback(); }); - this.#registerEndpoint(constants.API_RESUME, constants.REQUEST_METHOD_POST, async () => { - if (global.audioserver === undefined) { - return; - } - }); this.#registerEndpoint(constants.API_STOP, constants.REQUEST_METHOD_POST, async () => { if (global.audioserver === undefined) { return; diff --git a/classes/AudioServer.js b/classes/AudioServer.js index 7b0eae4..b710b27 100644 --- a/classes/AudioServer.js +++ b/classes/AudioServer.js @@ -1,24 +1,21 @@ -const sleep = require('../libs/util.js').sleep; - const net = require('net'); -const fs = require('fs'); -const stat = require('fs/promises').stat; +const { stat, open } = require('fs/promises'); const Message = require('./Message.js'); +const constants = require('../libs/constants.js'); class AudioServer { - constructor(file) { + constructor(file, progress) { this.listen = config?.server?.listen || '0.0.0.0'; this.port = 0; this.buffer = { - file: file, - stream: fs.createReadStream(file) + file: file }; this.clients = []; this.sockets = []; this.broadcasts = {}; - this.progress = 0; + this.progress = progress || 0; this.server = net.createServer(); this.#prepare(); } @@ -41,17 +38,35 @@ class AudioServer { }); this.server.on('error', (err) => { logger.error('ERROR IN AUDIOSERVER ' + err); - reject('an error occured preparing the audio server for file \'' + this.file + '\' > ' + err); + reject('audio server encountered an error: ' + err); + }); + this.server.on('close', () => { + logger.info('audio server closed'); }); }); + await this.#prepareBuffer(); + this.#announceAudioServer(); + } + + async #prepareBuffer() { + if (this.buffer.fd !== undefined) { + this.buffer.fd.close(); + } + if (this.buffer.stream?.destroyed === false) { + this.buffer.stream.close(); + this.buffer.stream.destroy(); + } + this.buffer.fd = await open(this.buffer.file); + this.buffer.stream = this.buffer.fd.createReadStream({ + start: this.progress + }); const stats = await stat(this.buffer.file); - this.buffer.size = stats.size; + this.buffer.size = stats.size - this.progress; let percentage = 30; if (!(isNaN(config.audio?.threshold))) { percentage = config.audio.threshold; } this.buffer.threshold = (this.buffer.size / 100) * percentage; - this.#announceAudioServer(); } #handleEvents() { @@ -145,6 +160,9 @@ class AudioServer { if (client === undefined) { return; } + if (this.progress < data?.progress) { + this.progress = data.progress; + } switch (client.state) { case constants.CLIENT_STATE_REGISTERED: return this.#handleStateRegistered(client); @@ -174,7 +192,7 @@ class AudioServer { if (!this.#allClientsInState(constants.CLIENT_STATE_READY)) { return; } - this.#startPlayback(); + this.startPlayback(); } async #handleStatePlaying(client) { @@ -196,6 +214,10 @@ class AudioServer { async #handleStateStopped(client, data) { logger.debug(client.getTag() + ' stopped playback, progress: ' + data.progress + '/' + this.buffer.size + '...'); + if (!this.#allClientsInState(constants.CLIENT_STATE_STOPPED)) { + return; + } + this.destroy(); } async #handleStateError(client, data) { @@ -232,7 +254,7 @@ class AudioServer { logger.debug('sent broadcast for audio server to client(s) \'' + this.broadcasts[constants.CLIENT_STATE_REGISTERED] + '\'...'); } - async #startPlayback() { + async startPlayback() { this.broadcasts[constants.CLIENT_STATE_PLAYING] = await new Message('audio:play').broadcast(); logger.debug('sent broadcast to start playback to client(s) \'' + this.broadcasts[constants.CLIENT_STATE_PLAYING] + '\'...'); } @@ -242,7 +264,7 @@ class AudioServer { logger.debug('sent broadcast to pause playback to client(s) \'' + this.broadcasts[constants.CLIENT_STATE_PAUSED] + '\'...'); } - async stopPlayback() { + async stopPlayback() { this.broadcasts[constants.CLIENT_STATE_STOPPED] = await new Message('audio:stop').broadcast(); logger.debug('sent broadcast to stop playback to client(s) \'' + this.broadcasts[constants.CLIENT_STATE_STOPPED] + '\'...'); } @@ -289,16 +311,22 @@ class AudioServer { } audiosocket.destroy(); } + this.buffer.fd.close(); + this.buffer.stream.close(); + this.buffer.stream.destroy(); + if (this.server?.listening !== true) { + return; + } await new Promise((resolve, reject) => { - this.server.close((err) => { - if (err !== undefined) { - logger.error('ERROR CLOSING AUDIOSERVER ' + err); - reject(err); + this.server.close((error) => { + if (error !== undefined) { + logger.error('an error occured closing the audio server: ' + error); + // TODO: reject and try/catch later? + // reject(error); } resolve(); }); }); - } } diff --git a/libs/constants.js b/libs/constants.js index fbcc265..f95c3eb 100644 --- a/libs/constants.js +++ b/libs/constants.js @@ -9,6 +9,7 @@ module.exports = { CLIENT_STATE_READY: 'ready', CLIENT_STATE_PLAYING: 'playing', CLIENT_STATE_PAUSED: 'paused', + CLIENT_STATE_RESUMING: 'resuming', CLIENT_STATE_STOPPED: 'stopped', CLIENT_STATE_ERROR: 'error',