Rudimentary support for unified audio playing! 🎉

This commit is contained in:
Amish Shah
2018-01-19 23:55:59 +00:00
parent 8e5e1ad8fe
commit 2b5fc77a67
5 changed files with 66 additions and 169 deletions

View File

@@ -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.
* <warn>Note that inline volume is not compatible with this method.</warn>
* @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;

View File

@@ -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.
* <warn>Note that inline volume is not compatible with this method.</warn>
* @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;

View File

@@ -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;

View File

@@ -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.',

View File

@@ -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!');