diff --git a/src/client/voice/VoiceBroadcast.js b/src/client/voice/VoiceBroadcast.js
index f06ae96c6..d8bcf9ede 100644
--- a/src/client/voice/VoiceBroadcast.js
+++ b/src/client/voice/VoiceBroadcast.js
@@ -1,6 +1,7 @@
const EventEmitter = require('events');
const BroadcastAudioPlayer = require('./player/BroadcastAudioPlayer');
const DispatcherSet = require('./util/DispatcherSet');
+const PlayInterface = require('./util/PlayInterface');
/**
* A voice broadcast can be played across multiple voice connections for improved shared-stream efficiency.
@@ -15,6 +16,7 @@ const DispatcherSet = require('./util/DispatcherSet');
* }
* ```
* @implements {VolumeInterface}
+ * @implements {PlayInterface}
*/
class VoiceBroadcast extends EventEmitter {
constructor(client) {
@@ -35,74 +37,8 @@ class VoiceBroadcast extends EventEmitter {
get dispatcher() {
return this.player.dispatcher;
}
-
- /**
- * Plays the given file in the voice connection.
- * @param {string} file The absolute path to the file
- * @param {StreamOptions} [options] Options for playing the stream
- * @returns {BroadcastDispatcher}
- * @example
- * // Play files natively
- * voiceChannel.join()
- * .then(connection => {
- * const dispatcher = connection.playFile('C:/Users/Discord/Desktop/music.mp3');
- * })
- * .catch(console.error);
- */
- playFile(file, options) {
- return this.player.playUnknown(file, options);
- }
-
- /**
- * Plays an arbitrary input that can be [handled by ffmpeg](https://ffmpeg.org/ffmpeg-protocols.html#Description)
- * @param {string} input the arbitrary input
- * @param {StreamOptions} [options] Options for playing the stream
- * @returns {BroadcastDispatcher}
- */
- playArbitraryInput(input, options) {
- return this.player.playUnknown(input, options);
- }
-
- /**
- * Plays and converts an audio stream in the voice connection.
- * @param {ReadableStream} stream The audio stream to play
- * @param {StreamOptions} [options] Options for playing the stream
- * @returns {BroadcastDispatcher}
- * @example
- * // Play streams using ytdl-core
- * const ytdl = require('ytdl-core');
- * const streamOptions = { seek: 0, volume: 1 };
- * voiceChannel.join()
- * .then(connection => {
- * const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' });
- * const dispatcher = connection.playStream(stream, streamOptions);
- * })
- * .catch(console.error);
- */
- playStream(stream, options) {
- return this.player.playUnknown(stream, options);
- }
-
- /**
- * 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 {BroadcastDispatcher}
- */
- playConvertedStream(stream, options) {
- return this.player.playPCMStream(stream, options);
- }
-
- /**
- * 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 {BroadcastDispatcher}
- */
- playOpusStream(stream, options) {
- return this.player.playOpusStream(stream, options);
- }
}
+PlayInterface.applyToClass(VoiceBroadcast);
+
module.exports = VoiceBroadcast;
diff --git a/src/client/voice/VoiceConnection.js b/src/client/voice/VoiceConnection.js
index 700db600e..5543f96ff 100644
--- a/src/client/voice/VoiceConnection.js
+++ b/src/client/voice/VoiceConnection.js
@@ -6,6 +6,7 @@ const AudioPlayer = require('./player/AudioPlayer');
const VoiceReceiver = require('./receiver/Receiver');
const EventEmitter = require('events');
const { Error } = require('../../errors');
+const PlayInterface = require('./util/PlayInterface');
/**
* Represents a connection to a guild's voice server.
@@ -17,6 +18,7 @@ const { Error } = require('../../errors');
* });
* ```
* @extends {EventEmitter}
+ * @implements {PlayInterface}
*/
class VoiceConnection extends EventEmitter {
constructor(voiceManager, channel) {
@@ -425,106 +427,6 @@ class VoiceConnection extends EventEmitter {
guild._memberSpeakUpdate(user_id, speaking);
}
- /**
- * Options that can be passed to stream-playing methods:
- * @typedef {Object} StreamOptions
- * @property {number} [seek=0] The time to seek to
- * @property {number|boolean} [volume=1] The volume to play at. Set this to false to disable volume transforms for
- * this stream to improve performance.
- * @property {number} [passes=1] How many times to send the voice packet to reduce packet loss
- * @property {number} [plp] Expected packet loss percentage
- * @property {boolean} [fec] Enabled forward error correction
- * @property {number|string} [bitrate=96] The bitrate (quality) of the audio in kbps.
- * If set to 'auto', the voice channel's bitrate will be used
- * @property {number} [highWaterMark=8] The maximum number of opus packets to make and store before they are
- * actually needed. See https://nodejs.org/en/docs/guides/backpressuring-in-streams/. Setting this value to
- * 1 means that changes in volume will be more instant.
- */
-
- /**
- * Plays the given file in the voice connection.
- * @param {string} file The absolute path to the file
- * @param {StreamOptions} [options] Options for playing the stream
- * @returns {StreamDispatcher}
- * @example
- * // Play files natively
- * voiceChannel.join()
- * .then(connection => {
- * const dispatcher = connection.playFile('C:/Users/Discord/Desktop/music.mp3');
- * })
- * .catch(console.error);
- */
- playFile(file, options) {
- return this.player.playUnknown(file, options);
- }
-
- /**
- * Plays an arbitrary input that can be [handled by ffmpeg](https://ffmpeg.org/ffmpeg-protocols.html#Description)
- * @param {string} input the arbitrary input
- * @param {StreamOptions} [options] Options for playing the stream
- * @returns {StreamDispatcher}
- */
- playArbitraryInput(input, options) {
- return this.player.playUnknown(input, options);
- }
-
- /**
- * Plays and converts an audio stream in the voice connection.
- * @param {ReadableStream} stream The audio stream to play
- * @param {StreamOptions} [options] Options for playing the stream
- * @returns {StreamDispatcher}
- * @example
- * // Play streams using ytdl-core
- * const ytdl = require('ytdl-core');
- * const streamOptions = { seek: 0, volume: 1 };
- * voiceChannel.join()
- * .then(connection => {
- * const stream = ytdl('https://www.youtube.com/watch?v=XAWgeLF9EVQ', { filter : 'audioonly' });
- * const dispatcher = connection.playStream(stream, streamOptions);
- * })
- * .catch(console.error);
- */
- playStream(stream, options) {
- return this.player.playUnknown(stream, options);
- }
-
- /**
- * 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}
- */
- playConvertedStream(stream, options) {
- return this.player.playPCMStream(stream, options);
- }
-
- /**
- * 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, options) {
- return this.player.playOpusStream(stream, options);
- }
-
- /**
- * 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
- * const broadcast = client
- * .createVoiceBroadcast()
- * .playFile('./test.mp3');
- * const dispatcher = voiceConnection.playBroadcast(broadcast);
- */
- playBroadcast(broadcast, options) {
- return this.player.playBroadcast(broadcast, options);
- }
-
/**
* Creates a VoiceReceiver so you can start listening to voice data.
* It's recommended to only create one of these.
@@ -537,4 +439,6 @@ class VoiceConnection extends EventEmitter {
}
}
+PlayInterface.applyToClass(VoiceConnection);
+
module.exports = VoiceConnection;
diff --git a/src/client/voice/util/PlayInterface.js b/src/client/voice/util/PlayInterface.js
new file mode 100644
index 000000000..1c3eaeef1
--- /dev/null
+++ b/src/client/voice/util/PlayInterface.js
@@ -0,0 +1,55 @@
+/**
+ * Options that can be passed to stream-playing methods:
+ * @typedef {Object} StreamOptions
+ * @property {string} [type='unknown'] The type of stream. 'unknown', 'converted', 'opus', 'broadcast.
+ * @property {number} [seek=0] The time to seek to
+ * @property {number|boolean} [volume=1] The volume to play at. Set this to false to disable volume transforms for
+ * this stream to improve performance.
+ * @property {number} [passes=1] How many times to send the voice packet to reduce packet loss
+ * @property {number} [plp] Expected packet loss percentage
+ * @property {boolean} [fec] Enabled forward error correction
+ * @property {number|string} [bitrate=96] The bitrate (quality) of the audio in kbps.
+ * If set to 'auto', the voice channel's bitrate will be used
+ * @property {number} [highWaterMark=12] The maximum number of opus packets to make and store before they are
+ * actually needed. See https://nodejs.org/en/docs/guides/backpressuring-in-streams/. Setting this value to
+ * 1 means that changes in volume will be more instant.
+ */
+
+/**
+ * An interface class to allow you to play audio over VoiceConnections and VoiceBroadcasts.
+ */
+class PlayInterface {
+ constructor(player) {
+ this.player = player;
+ }
+
+ /**
+ * Play an audio resource.
+ * @param {ReadableStream|string} resource The resource to play.
+ * @param {StreamOptions} [options] The options to play.
+ * @returns {StreamDispatcher}
+ */
+ play(resource, options = {}) {
+ const type = options.type || 'unknown';
+ if (type === 'unknown') {
+ return this.player.playUnknown(resource, options);
+ } else if (type === 'converted') {
+ return this.player.playPCMStream(resource, options);
+ } else if (type === 'opus') {
+ return this.player.playOpusStream(resource, options);
+ } else if (type === 'broadcast') {
+ if (!this.player.playBroadcast) throw Error('VOICE_PLAY_INTERFACE_NO_BROADCAST');
+ return this.player.playBroadcast(resource, options);
+ }
+ throw Error('VOICE_PLAY_INTERFACE_BAD_TYPE');
+ }
+
+ static applyToClass(structure) {
+ for (const prop of ['play']) {
+ Object.defineProperty(structure.prototype, prop,
+ Object.getOwnPropertyDescriptor(PlayInterface.prototype, prop));
+ }
+ }
+}
+
+module.exports = PlayInterface;
diff --git a/src/errors/Messages.js b/src/errors/Messages.js
index 70ee8d47e..91956cf99 100644
--- a/src/errors/Messages.js
+++ b/src/errors/Messages.js
@@ -54,6 +54,8 @@ const Messages = {
VOICE_NO_BROWSER: 'Voice connections are not available in browsers.',
VOICE_CONNECTION_ATTEMPTS_EXCEEDED: attempts => `Too many connection attempts (${attempts}).`,
VOICE_JOIN_SOCKET_CLOSED: 'Tried to send join packet, but the WebSocket is not open.',
+ VOICE_PLAY_INTERFACE_NO_BROADCAST: 'A broadcast cannot be played in this context.',
+ VOICE_PLAY_INTERFACE_BAD_TYPE: 'Unknown stream type',
OPUS_ENGINE_MISSING: 'Couldn\'t find an Opus engine.',
diff --git a/test/voice.js b/test/voice.js
index 2f57ee8dc..07cc3a4bf 100644
--- a/test/voice.js
+++ b/test/voice.js
@@ -42,7 +42,7 @@ client.on('message', m => {
if (!connections.has(m.guild.id)) connections.set(m.guild.id, { conn, queue: [] });
m.reply('ok!');
// conn.playOpusStream(fs.createReadStream('C:/users/amish/downloads/z.ogg').pipe(new prism.OggOpusDemuxer()));
- d = conn.playStream(ytdl('https://www.youtube.com/watch?v=_XXOSf0s2nk', { filter: 'audioonly' }, { passes: 3 }));
+ d = conn.play(ytdl('https://www.youtube.com/watch?v=_XXOSf0s2nk', { filter: 'audioonly' }, { passes: 3 }));
});
} else {
m.reply('Specify a voice channel!');