diff --git a/src/client/voice/VoiceBroadcast.js b/src/client/voice/VoiceBroadcast.js
index c4562f17f..76513d86a 100644
--- a/src/client/voice/VoiceBroadcast.js
+++ b/src/client/voice/VoiceBroadcast.js
@@ -143,8 +143,8 @@ class VoiceBroadcast extends VolumeInterface {
* })
* .catch(console.error);
*/
- playStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
- const options = { seek, volume, passes, stream };
+ playStream(stream, options = {}) {
+ this.setVolume(options.volume || 1);
return this._playTranscodable(stream, options);
}
@@ -164,19 +164,17 @@ class VoiceBroadcast extends VolumeInterface {
* })
* .catch(console.error);
*/
- playFile(file, { seek = 0, volume = 1, passes = 1 } = {}) {
- const options = { seek, volume, passes };
+ playFile(file, options = {}) {
+ this.setVolume(options.volume || 1);
return this._playTranscodable(`file:${file}`, options);
}
_playTranscodable(media, options) {
- OpusEncoders.guaranteeOpusEngine();
-
this.killCurrentTranscoder();
const transcoder = this.prism.transcode({
type: 'ffmpeg',
media,
- ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek)]),
+ ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek || 0)]),
});
/**
* Emitted whenever an error occurs.
@@ -206,31 +204,28 @@ class VoiceBroadcast extends VolumeInterface {
}
/**
- * Plays a stream of 16-bit signed stereo PCM at 48KHz.
+ * Plays a stream of 16-bit signed stereo PCM.
* @param {ReadableStream} stream The audio stream to play
* @param {StreamOptions} [options] Options for playing the stream
* @returns {VoiceBroadcast}
*/
- playConvertedStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
- OpusEncoders.guaranteeOpusEngine();
-
+ playConvertedStream(stream, options = {}) {
this.killCurrentTranscoder();
- const options = { seek, volume, passes, stream };
- this.currentTranscoder = { options };
+ this.setVolume(options.volume || 1);
+ this.currentTranscoder = { options: { stream } };
stream.once('readable', () => this._startPlaying());
return this;
}
/**
- * Plays an Opus encoded stream at 48KHz.
+ * Plays an Opus encoded stream.
* Note that inline volume is not compatible with this method.
* @param {ReadableStream} stream The Opus audio stream to play
* @param {StreamOptions} [options] Options for playing the stream
* @returns {StreamDispatcher}
*/
- playOpusStream(stream, { seek = 0, passes = 1 } = {}) {
- const options = { seek, passes, stream };
- this.currentTranscoder = { options, opus: true };
+ playOpusStream(stream) {
+ this.currentTranscoder = { options: { stream }, opus: true };
stream.once('readable', () => this._startPlaying());
return this;
}
@@ -241,8 +236,9 @@ class VoiceBroadcast extends VolumeInterface {
* @param {StreamOptions} [options] Options for playing the stream
* @returns {VoiceBroadcast}
*/
- playArbitraryInput(input, { seek = 0, volume = 1, passes = 1 } = {}) {
- const options = { seek, volume, passes, input };
+ playArbitraryInput(input, options = {}) {
+ this.setVolume(options.volume || 1);
+ options.input = input;
return this._playTranscodable(input, options);
}
diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js
index f7539021c..9163db449 100644
--- a/src/client/voice/VoiceConnection.js
+++ b/src/client/voice/VoiceConnection.js
@@ -431,6 +431,8 @@ class VoiceConnection extends EventEmitter {
* @property {number} [seek=0] The time to seek to
* @property {number} [volume=1] The volume to play at
* @property {number} [passes=1] How many times to send the voice packet to reduce packet loss
+ * @property {number|string} [bitrate=48000] The bitrate (quality) of the audio.
+ * If set to 'auto', the voice channel's bitrate will be used
*/
/**
@@ -481,7 +483,7 @@ class VoiceConnection extends EventEmitter {
}
/**
- * Plays a stream of 16-bit signed stereo PCM at 48KHz.
+ * Plays a stream of 16-bit signed stereo PCM.
* @param {ReadableStream} stream The audio stream to play
* @param {StreamOptions} [options] Options for playing the stream
* @returns {StreamDispatcher}
@@ -491,7 +493,7 @@ class VoiceConnection extends EventEmitter {
}
/**
- * Plays an Opus encoded stream at 48KHz.
+ * Plays an Opus encoded stream.
* Note that inline volume is not compatible with this method.
* @param {ReadableStream} stream The Opus audio stream to play
* @param {StreamOptions} [options] Options for playing the stream
@@ -504,6 +506,7 @@ class VoiceConnection extends EventEmitter {
/**
* Plays a voice broadcast.
* @param {VoiceBroadcast} broadcast The broadcast to play
+ * @param {StreamOptions} [options] Options for playing the stream
* @returns {StreamDispatcher}
* @example
* // Play a broadcast
@@ -512,8 +515,8 @@ class VoiceConnection extends EventEmitter {
* .playFile('./test.mp3');
* const dispatcher = voiceConnection.playBroadcast(broadcast);
*/
- playBroadcast(broadcast) {
- return this.player.playBroadcast(broadcast);
+ playBroadcast(broadcast, options) {
+ return this.player.playBroadcast(broadcast, options);
}
/**
diff --git a/src/client/voice/dispatcher/StreamDispatcher.js b/src/client/voice/dispatcher/StreamDispatcher.js
index 68c83d380..24df9d069 100644
--- a/src/client/voice/dispatcher/StreamDispatcher.js
+++ b/src/client/voice/dispatcher/StreamDispatcher.js
@@ -119,6 +119,16 @@ class StreamDispatcher extends VolumeInterface {
this.emit('speaking', value);
}
+
+ /**
+ * Set the bitrate of the current Opus encoder.
+ * @param {number} bitrate New bitrate, in kbps.
+ * If set to 'auto', the voice channel's bitrate will be used
+ */
+ setBitrate(bitrate) {
+ this.player.setBitrate(bitrate);
+ }
+
sendBuffer(buffer, sequence, timestamp, opusPacket) {
opusPacket = opusPacket || this.player.opusEncoder.encode(buffer);
const packet = this.createPacket(sequence, timestamp, opusPacket);
diff --git a/src/client/voice/opus/BaseOpusEngine.js b/src/client/voice/opus/BaseOpusEngine.js
index b63b695f6..40c6204fd 100644
--- a/src/client/voice/opus/BaseOpusEngine.js
+++ b/src/client/voice/opus/BaseOpusEngine.js
@@ -4,21 +4,38 @@
*/
class BaseOpus {
/**
- * @param {Object} [options] The options to apply to the Opus engine
- * @param {boolean} [options.fec] Whether to enable forward error correction (defaults to false)
- * @param {number} [options.plp] The expected packet loss percentage (0-1 inclusive, defaults to 0)
+ * @param {Object} [options] The options to apply to the Opus engine.
+ * @param {number} [options.bitrate=48] The desired bitrate (kbps).
+ * @param {boolean} [options.fec=false] Whether to enable forward error correction.
+ * @param {number} [options.plp=0] The expected packet loss percentage.
*/
- constructor(options = {}) {
+ constructor({ bitrate = 48, fec = false, plp = 0 } = {}) {
this.ctl = {
+ BITRATE: 4002,
FEC: 4012,
PLP: 4014,
};
- this.options = options;
+ this.samplingRate = 48000;
+ this.channels = 2;
+
+ /**
+ * The desired bitrate (kbps)
+ * @type {number}
+ */
+ this.bitrate = bitrate;
+
+ /**
+ * Miscellaneous Opus options
+ * @type {Object}
+ */
+ this.options = { fec, plp };
}
init() {
try {
+ this.setBitrate(this.bitrate);
+
// Set FEC (forward error correction)
if (this.options.fec) this.setFEC(this.options.fec);
diff --git a/src/client/voice/opus/NodeOpusEngine.js b/src/client/voice/opus/NodeOpusEngine.js
index 72f2a818b..02e880637 100644
--- a/src/client/voice/opus/NodeOpusEngine.js
+++ b/src/client/voice/opus/NodeOpusEngine.js
@@ -10,10 +10,14 @@ class NodeOpusEngine extends OpusEngine {
} catch (err) {
throw err;
}
- this.encoder = new opus.OpusEncoder(48000, 2);
+ this.encoder = new opus.OpusEncoder(this.samplingRate, this.channels);
super.init();
}
+ setBitrate(bitrate) {
+ this.encoder.applyEncoderCTL(this.ctl.BITRATE, Math.min(128, Math.max(16, bitrate)) * 1000);
+ }
+
setFEC(enabled) {
this.encoder.applyEncoderCTL(this.ctl.FEC, enabled ? 1 : 0);
}
diff --git a/src/client/voice/opus/OpusEngineList.js b/src/client/voice/opus/OpusEngineList.js
index 3b69cdaeb..e6ede9a5a 100644
--- a/src/client/voice/opus/OpusEngineList.js
+++ b/src/client/voice/opus/OpusEngineList.js
@@ -3,8 +3,6 @@ const list = [
require('./OpusScriptEngine'),
];
-let opusEngineFound;
-
function fetch(Encoder, engineOptions) {
try {
return new Encoder(engineOptions);
@@ -25,10 +23,9 @@ exports.fetch = engineOptions => {
const fetched = fetch(encoder, engineOptions);
if (fetched) return fetched;
}
- return null;
-};
exports.guaranteeOpusEngine = () => {
if (typeof opusEngineFound === 'undefined') opusEngineFound = Boolean(exports.fetch());
if (!opusEngineFound) throw new Error('Couldn\'t find an Opus engine.');
+ throw new Error('OPUS_ENGINE_MISSING');
};
diff --git a/src/client/voice/opus/OpusScriptEngine.js b/src/client/voice/opus/OpusScriptEngine.js
index 81ca6206b..a5e046d40 100644
--- a/src/client/voice/opus/OpusScriptEngine.js
+++ b/src/client/voice/opus/OpusScriptEngine.js
@@ -10,10 +10,14 @@ class OpusScriptEngine extends OpusEngine {
} catch (err) {
throw err;
}
- this.encoder = new OpusScript(48000, 2);
+ this.encoder = new OpusScript(this.samplingRate, this.channels);
super.init();
}
+ setBitrate(bitrate) {
+ this.encoder.encoderCTL(this.ctl.BITRATE, Math.min(128, Math.max(16, bitrate)) * 1000);
+ }
+
setFEC(enabled) {
this.encoder.encoderCTL(this.ctl.FEC, enabled ? 1 : 0);
}
diff --git a/src/client/voice/player/AudioPlayer.js b/src/client/voice/player/AudioPlayer.js
index afbc9a671..2a264f9e6 100644
--- a/src/client/voice/player/AudioPlayer.js
+++ b/src/client/voice/player/AudioPlayer.js
@@ -30,11 +30,6 @@ class AudioPlayer extends EventEmitter {
* @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.currentStream = {};
this.streamingData = {
@@ -67,6 +62,7 @@ class AudioPlayer extends EventEmitter {
destroy() {
if (this.opusEncoder) this.opusEncoder.destroy();
+ this.opusEncoder = null;
}
destroyCurrentStream() {
@@ -83,13 +79,25 @@ class AudioPlayer extends EventEmitter {
this.currentStream = {};
}
- playUnknownStream(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
- OpusEncoders.guaranteeOpusEngine();
- const options = { seek, volume, passes };
+ /**
+ * Set the bitrate of the current Opus encoder.
+ * @param {number} value New bitrate, in kbps.
+ * If set to 'auto', the voice channel's bitrate will be used
+ */
+ setBitrate(value) {
+ if (!value) return;
+ if (!this.opusEncoder) return;
+ const bitrate = value === 'auto' ? this.voiceConnection.channel.bitrate : value;
+ this.opusEncoder.setBitrate(bitrate);
+ }
+
+ playUnknownStream(stream, options = {}) {
+ this.destroy();
+ this.opusEncoder = OpusEncoders.fetch(options);
const transcoder = this.prism.transcode({
type: 'ffmpeg',
media: stream,
- ffmpegArguments: ffmpegArguments.concat(['-ss', String(seek)]),
+ ffmpegArguments: ffmpegArguments.concat(['-ss', String(options.seek || 0)]),
});
this.destroyCurrentStream();
this.currentStream = {
@@ -105,9 +113,10 @@ class AudioPlayer extends EventEmitter {
return this.playPCMStream(transcoder.output, options, true);
}
- playPCMStream(stream, { seek = 0, volume = 1, passes = 1 } = {}, fromUnknown = false) {
- OpusEncoders.guaranteeOpusEngine();
- const options = { seek, volume, passes };
+ playPCMStream(stream, options = {}, fromUnknown = false) {
+ this.destroy();
+ this.opusEncoder = OpusEncoders.fetch(options);
+ this.setBitrate(options.bitrate);
const dispatcher = this.createDispatcher(stream, options);
if (fromUnknown) {
this.currentStream.dispatcher = dispatcher;
@@ -122,8 +131,8 @@ class AudioPlayer extends EventEmitter {
return dispatcher;
}
- playOpusStream(stream, { seek = 0, passes = 1 } = {}) {
- const options = { seek, passes, opus: true };
+ playOpusStream(stream, options = {}) {
+ options.opus = true;
this.destroyCurrentStream();
const dispatcher = this.createDispatcher(stream, options);
this.currentStream = {
@@ -134,8 +143,7 @@ class AudioPlayer extends EventEmitter {
return dispatcher;
}
- playBroadcast(broadcast, { volume = 1, passes = 1 } = {}) {
- const options = { volume, passes };
+ playBroadcast(broadcast, options) {
this.destroyCurrentStream();
const dispatcher = this.createDispatcher(broadcast, options);
this.currentStream = {
@@ -148,7 +156,9 @@ class AudioPlayer extends EventEmitter {
return dispatcher;
}
- createDispatcher(stream, options) {
+ createDispatcher(stream, { seek = 0, volume = 1, passes = 1 } = {}) {
+ const options = { seek, volume, passes };
+
const dispatcher = new StreamDispatcher(this, stream, options);
dispatcher.on('end', () => this.destroyCurrentStream());
dispatcher.on('error', () => this.destroyCurrentStream());
diff --git a/src/structures/VoiceChannel.js b/src/structures/VoiceChannel.js
index 617b4eee0..af69a1e00 100644
--- a/src/structures/VoiceChannel.js
+++ b/src/structures/VoiceChannel.js
@@ -25,7 +25,7 @@ class VoiceChannel extends GuildChannel {
* The bitrate of this voice channel
* @type {number}
*/
- this.bitrate = data.bitrate;
+ this.bitrate = data.bitrate * 0.001;
/**
* The maximum amount of users allowed in this channel - 0 means unlimited.
@@ -76,16 +76,17 @@ class VoiceChannel extends GuildChannel {
}
/**
- * Sets the bitrate of the channel.
+ * Sets the bitrate of the channel (in kbps).
* @param {number} bitrate The new bitrate
* @returns {Promise}
* @example
* // Set the bitrate of a voice channel
- * voiceChannel.setBitrate(48000)
- * .then(vc => console.log(`Set bitrate to ${vc.bitrate} for ${vc.name}`))
+ * voiceChannel.setBitrate(48)
+ * .then(vc => console.log(`Set bitrate to ${vc.bitrate}kbps for ${vc.name}`))
* .catch(console.error);
*/
setBitrate(bitrate) {
+ bitrate *= 1000;
return this.edit({ bitrate });
}