added dependency 'speaker', moving away from 'ffplay'...

This commit is contained in:
Daniel Sommer 2022-04-25 14:55:27 +02:00
parent b5f77f1ad1
commit d546344194
6 changed files with 276 additions and 20 deletions

View file

@ -25,7 +25,6 @@ class Audiostream {
this.host = config?.server?.host || "127.0.0.1"; this.host = config?.server?.host || "127.0.0.1";
this.port = data.port; this.port = data.port;
this.clientId = data.clientId; this.clientId = data.clientId;
this.size = data.size;
this.threshold = data.threshold; this.threshold = data.threshold;
this.#handleSocket(net.connect({ this.#handleSocket(net.connect({
host: this.getHost(), host: this.getHost(),
@ -40,7 +39,8 @@ class Audiostream {
}); });
this.eventParser.on('audio:play', (data) => { this.eventParser.on('audio:play', (data) => {
logger.debug('handling event \'audio:play\'...'); logger.debug('handling event \'audio:play\'...');
global.player.play(data?.position); // global.player.play(data?.position);
global.player.speak();
}); });
this.eventParser.on('audio:pause', (data) => { this.eventParser.on('audio:pause', (data) => {
logger.debug('handling event \'audio:pause\'...'); logger.debug('handling event \'audio:pause\'...');
@ -62,7 +62,7 @@ class Audiostream {
#handleSocket(socket) { #handleSocket(socket) {
socket.on('connect', async () => { socket.on('connect', async () => {
logger.debug('connected to audio server \'' + this.getTag() + '\'...'); logger.debug('connected to audio server \'' + this.getTag() + '\'...');
await global.player.prepare(this.size, this.threshold); await global.player.prepare(this.threshold, socket);
new Message('audio:register', { clientId: this.clientId, port: socket.localPort }).send(); new Message('audio:register', { clientId: this.clientId, port: socket.localPort }).send();
}); });
socket.on('error', (error) => { socket.on('error', (error) => {
@ -71,9 +71,9 @@ class Audiostream {
socket.on('timeout', () => { socket.on('timeout', () => {
logger.warn('connection to audio server \'' + this.getTag() + '\' timed out'); logger.warn('connection to audio server \'' + this.getTag() + '\' timed out');
}); });
socket.on('data', (data) => { // socket.on('data', (data) => {
global.player.feed(data); // global.player.speaker.feed(data);
}); // });
socket.on('end', () => { socket.on('end', () => {
logger.info('connection to audio server \'' + this.getTag() + '\' ended'); logger.info('connection to audio server \'' + this.getTag() + '\' ended');
}); });
@ -81,6 +81,7 @@ class Audiostream {
logger.info('connection to audio server \'' + this.getTag() + '\' closed'); logger.info('connection to audio server \'' + this.getTag() + '\' closed');
global.player.stopFeed(); global.player.stopFeed();
}); });
// global.player.speaker.feed(socket);
} }
} }

View file

@ -1,3 +1,4 @@
const Speaker = require('./Speaker.js');
const EventEmitter = require('events'); const EventEmitter = require('events');
const { spawn } = require('child_process'); const { spawn } = require('child_process');
const createWriteStream = require('fs').createWriteStream; const createWriteStream = require('fs').createWriteStream;
@ -14,30 +15,75 @@ class Player extends EventEmitter {
this.tmp = { this.tmp = {
file: resolve(global.config?.tmp || '/tmp/kannon.tmp') file: resolve(global.config?.tmp || '/tmp/kannon.tmp')
}; };
this.buffer = {
size: 0,
elements: []
};
} }
async prepare(size, threshold) { async prepare(threshold, stream) {
logger.debug('preparing audio player...'); logger.debug('preparing audio player...');
await this.#reset(); await this.#reset();
await this.#removeTemporaryFile(); await this.#removeTemporaryFile();
this.size = size;
this.threshold = threshold; this.threshold = threshold;
this.tmp.stream = createWriteStream(this.tmp.file); this.stream = stream;
// this.tmp.stream = createWriteStream(this.tmp.file);
// this.speaker = new Speaker(speakeroptions.channel, speakeroptions.bitDepth, speakeroptions.sampleRate);
this.speaker = new Speaker(2, 16, 44100);
this.buffer.limit = config?.buffer?.limit;
if (isNaN(this.buffer.limit) || this.buffer.limit < this.threshold) {
this.buffer.limit = this.threshold;
}
this.#fillBuffer();
} }
async feed(buffer) { #fillBuffer() {
this.tmp.stream.write(buffer); this.stream.on('data', (data) => {
if (this.tmp.announced === undefined && this.tmp.stream.bytesWritten >= this.threshold) { this.buffer.size += data.length;
this.tmp.announced = true; this.buffer.elements.push(data);
this.#setState(constants.STATE_READY); if (this.buffer.announced === undefined && this.buffer.size >= this.threshold) {
logger.debug('threshold of ' + this.threshold + ' bytes reached after ' + (Date.now() - this.timestamp) + 'ms'); this.buffer.announced = true;
this.#setState(constants.STATE_READY);
logger.debug('threshold of ' + this.threshold + ' bytes reached after ' + (Date.now() - this.timestamp) + 'ms');
}
if (this.buffer.size >= this.buffer.limit) {
this.stream.pause();
logger.warn('BUFFER LIMIT REACHED - PAUSING STREAM');
}
this.speak(true);
});
}
speak(checkState) {
if (checkState === true && this.isPlaying() !== true) {
return;
}
this.#setState(constants.STATE_PLAYING);
while (this.buffer.elements.length > 0) {
const tmp = this.buffer.elements[0];
this.buffer.elements.shift();
this.speaker.pipe(tmp);
this.buffer.size -= tmp.length;
if (this.buffer.size < this.buffer.limit) {
this.stream.resume();
logger.warn('RESUMING STREAM - BUFFER NOT FILLED');
}
} }
} }
// async feed(buffer) {
// this.tmp.stream.write(buffer);
// if (this.tmp.announced === undefined && this.tmp.stream.bytesWritten >= this.threshold) {
// this.tmp.announced = true;
// this.#setState(constants.STATE_READY);
// logger.debug('threshold of ' + this.threshold + ' bytes reached after ' + (Date.now() - this.timestamp) + 'ms');
// }
// }
stopFeed() { stopFeed() {
logger.debug('finished writing of ' + this.tmp.stream.bytesWritten + ' bytes after ' + (Date.now() - this.timestamp) + 'ms'); // logger.debug('finished writing of ' + this.tmp.stream.bytesWritten + ' bytes after ' + (Date.now() - this.timestamp) + 'ms');
this.tmp.stream.end(); // this.tmp.stream.end();
this.tmp.stream.close(); // this.tmp.stream.close();
} }
async play(position) { async play(position) {

82
classes/Speaker.js Normal file
View file

@ -0,0 +1,82 @@
const NodeSpeaker = require('speaker');
const createReadStream = require('fs').createReadStream;
class Speaker {
constructor(channels, bitDepth, sampleRate) {
this.#handlePlayer(channels, bitDepth, sampleRate);
this.playback = {
played: 0,
tmp: 0
};
}
pipe(data) {
return this.speaker.write(data);
}
// pipe(buffer, position) {
// if (buffer === undefined) {
// return;
// }
// this.playback.stream = createReadStream(file);
// if (isNaN(position) || position < 0) {
// position = 0;
// }
// position = 65537* 100;
// this.playback.stream.on('data', (data) => {
// if (position > 0 && this.playback.played <= position) {
// const offset = position - (this.playback.played + data.length);
// if (offset >= 0) {
// this.playback.played += data.length;
// return;
// }
// data = data.subarray(data.length + offset);
// }
// if (this.speaker.write(data) === true) {
// this.playback.played += data.length;
// } else {
// this.playback.tmp = data.length;
// this.playback.stream.pause();
// }
// });
// this.playback.stream.on('end', () => {
// logger.debug('read stream ended');
// });
// this.playback.stream.on('close', () => {
// logger.debug('read stream closed');
// });
// this.playback.stream.on('drain', () => {
// logger.debug('read stream drained');
// });
// this.playback.stream.on('error', (error) => {
// logger.debug('read stream encountered an error: ' + error);
// });
// }
#handlePlayer(channels, bitDepth, sampleRate) {
this.speaker = new NodeSpeaker({
channels: channels,
bitDepth: bitDepth,
sampleRate: sampleRate
});
this.speaker.on('open', () => {
logger.debug('speaker opened...');
});
this.speaker.on('flush', () => {
logger.debug('speaker flushed...');
});
this.speaker.on('close', () => {
logger.debug('speaker closed...');
});
this.speaker.on('drain', () => {
// handle backpressure
// this.playback.played += this.tmp;
// this.playback.tmp = 0;
// this.playback.stream.resume();
});
}
}
module.exports = Speaker;

View file

@ -12,5 +12,8 @@
"limit": 0, "limit": 0,
"delay": 1000 "delay": 1000
}, },
"buffer": {
"limit": 10
},
"tmp": "/tmp/kannon.tmp" "tmp": "/tmp/kannon.tmp"
} }

125
package-lock.json generated
View file

@ -9,9 +9,58 @@
"version": "0.0.1", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"moment": "^2.29.1" "moment": "^2.29.1",
"speaker": "^0.5.4"
} }
}, },
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
"dependencies": {
"buffer-alloc-unsafe": "^1.1.0",
"buffer-fill": "^1.0.0"
}
},
"node_modules/buffer-alloc-unsafe": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
},
"node_modules/buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"node_modules/moment": { "node_modules/moment": {
"version": "2.29.2", "version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
@ -19,13 +68,87 @@
"engines": { "engines": {
"node": "*" "node": "*"
} }
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/speaker": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/speaker/-/speaker-0.5.4.tgz",
"integrity": "sha512-0I35CJGgqU1rd/a3qVysR5gLlG+8QlzJcPAEnYvT0BLfuLdJ7JNdlQHwbh7ETNcXDXbzm2O148GEAoAER54Dvw==",
"hasInstallScript": true,
"dependencies": {
"bindings": "^1.3.0",
"buffer-alloc": "^1.1.0",
"debug": "^4.0.0"
},
"engines": {
"node": ">=8.6"
}
} }
}, },
"dependencies": { "dependencies": {
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
"requires": {
"buffer-alloc-unsafe": "^1.1.0",
"buffer-fill": "^1.0.0"
}
},
"buffer-alloc-unsafe": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
},
"buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"moment": { "moment": {
"version": "2.29.2", "version": "2.29.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
"integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==" "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"speaker": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/speaker/-/speaker-0.5.4.tgz",
"integrity": "sha512-0I35CJGgqU1rd/a3qVysR5gLlG+8QlzJcPAEnYvT0BLfuLdJ7JNdlQHwbh7ETNcXDXbzm2O148GEAoAER54Dvw==",
"requires": {
"bindings": "^1.3.0",
"buffer-alloc": "^1.1.0",
"debug": "^4.0.0"
}
} }
} }
} }

View file

@ -16,6 +16,7 @@
"url": "https://git.velvettear.de/velvettear/kannon-client.git" "url": "https://git.velvettear.de/velvettear/kannon-client.git"
}, },
"dependencies": { "dependencies": {
"moment": "^2.29.1" "moment": "^2.29.1",
"speaker": "^0.5.4"
} }
} }