Files
discord.js/src/client/voice/player/AudioPlayer.js
Programmix 7fd94c29d8 VoiceConnection rework (#1183)
* VoiceConnection rework

- improves codebase
- removes concept of pending connections
- attempts to fix memory leaks by removing EventEmitter listeners
- makes voice connections keep track of its own channel when it is moved by another user
- allows voice connections to reconnect when Discord falls back to another voice server or a region change occurs
- adds events for some of the aforementioned events

* Removed unused code

* More clean up / bugfixes

* Added typedefs to Status and VoiceStatus constants
2017-02-22 20:13:52 +00:00

129 lines
3.9 KiB
JavaScript

const EventEmitter = require('events').EventEmitter;
const Prism = require('prism-media');
const StreamDispatcher = require('../dispatcher/StreamDispatcher');
const Collection = require('../../../util/Collection');
const OpusEncoders = require('../opus/OpusEngineList');
const ffmpegArguments = [
'-analyzeduration', '0',
'-loglevel', '0',
'-f', 's16le',
'-ar', '48000',
'-ac', '2',
];
/**
* An Audio Player for a Voice Connection
* @private
* @extends {EventEmitter}
*/
class AudioPlayer extends EventEmitter {
constructor(voiceConnection) {
super();
/**
* The voice connection that the player serves
* @type {VoiceConnection}
*/
this.voiceConnection = voiceConnection;
/**
* The prism transcoder that the player uses
* @type {Prism}
*/
this.prism = new Prism();
/**
* The opus encoder that the player uses
* @type {NodeOpusEngine|OpusScriptEngine}
*/
this.opusEncoder = OpusEncoders.fetch();
this.streams = new Collection();
this.streamingData = {
channels: 2,
count: 0,
sequence: 0,
timestamp: 0,
pausedTime: 0,
};
this.voiceConnection.once('closing', () => this.destroyAllStreams());
}
get currentTranscoder() {
return this.streams.last().transcoder;
}
destroy() {
if (this.opusEncoder) this.opusEncoder.destroy();
}
destroyStream(stream) {
const data = this.streams.get(stream);
if (!data) return;
const transcoder = data.transcoder;
const dispatcher = data.dispatcher;
if (transcoder) transcoder.kill();
if (dispatcher) dispatcher.destroy('end');
this.streams.delete(stream);
}
destroyAllStreams(except) {
for (const stream of this.streams.keys()) {
if (except === stream) continue;
if (except === true && this.streams.get(stream) === this.streams.last()) continue;
this.destroyStream(stream);
}
}
playUnknownStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
OpusEncoders.guaranteeOpusEngine();
const options = { seek, volume, passes };
const transcoder = this.prism.transcode({
type: 'ffmpeg',
media: stream,
ffmpegArguments: ffmpegArguments.concat(['-ss', String(seek)]),
});
this.streams.set(transcoder.output, { transcoder, input: stream });
transcoder.on('error', e => {
this.destroyStream(stream);
if (this.listenerCount('error') > 0) this.emit('error', e);
this.emit('warn', `prism transcoder error - ${e}`);
});
return this.playPCMStream(transcoder.output, options);
}
playPCMStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
OpusEncoders.guaranteeOpusEngine();
const options = { seek, volume, passes };
this.destroyAllStreams(stream);
const dispatcher = this.createDispatcher(stream, options);
if (!this.streams.has(stream)) this.streams.set(stream, { dispatcher, input: stream });
this.streams.get(stream).dispatcher = dispatcher;
return dispatcher;
}
playOpusStream(stream, { seek = 0, passes = 1 } = {}) {
const options = { seek, passes, opus: true };
this.destroyAllStreams(stream);
const dispatcher = this.createDispatcher(stream, options);
this.streams.set(stream, { dispatcher, input: stream });
return dispatcher;
}
playBroadcast(broadcast, { volume = 1, passes = 1 } = {}) {
const options = { volume, passes };
this.destroyAllStreams();
const dispatcher = this.createDispatcher(broadcast, options);
this.streams.set(broadcast, { dispatcher, input: broadcast });
broadcast.registerDispatcher(dispatcher);
return dispatcher;
}
createDispatcher(stream, options) {
const dispatcher = new StreamDispatcher(this, stream, options);
dispatcher.on('end', () => this.destroyStream(stream));
dispatcher.on('error', () => this.destroyStream(stream));
dispatcher.on('speaking', value => this.voiceConnection.setSpeaking(value));
return dispatcher;
}
}
module.exports = AudioPlayer;