@jadestudios/discord-music-player
Version:
Complete framework to facilitate music commands using discord.js v13
546 lines (545 loc) • 20.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Queue = void 0;
const StreamConnection_1 = require("../voice/StreamConnection");
const voice_1 = require("@discordjs/voice");
const __1 = require("..");
const ytdl_core_1 = __importDefault(require("@distube/ytdl-core"));
const Filters_1 = require("./Filters");
class Queue {
/**
* Queue constructor
* @param {Player} player
* @param {Guild} guild
* @param {PlayerOptions} options
*/
constructor(player, guild, options) {
/**
* Player instance
* @name Queue#player
* @type {Player}
* @readonly
*/
this.songs = [];
this.isPlaying = false;
this.options = __1.DefaultPlayerOptions;
this.repeatMode = __1.RepeatMode.DISABLED;
this.destroyed = false;
/**
* Guild instance
* @name Queue#guild
* @type {Guild}
* @readonly
*/
/**
* Queue connection
* @name Queue#connection
* @type {?StreamConnection}
* @readonly
*/
/**
* Queue songs
* @name Queue#songs
* @type {Song[]}
*/
/**
* If Song is playing on the Queue
* @name Queue#isPlaying
* @type {boolean}
* @readonly
*/
/**
* Queue custom data
* @name Queue#data
* @type {any}
*/
/**
* Queue options
* @name Queue#options
* @type {PlayerOptions}
*/
/**
* Queue repeat mode
* @name Queue#repeatMode
* @type {RepeatMode}
*/
/**
* If the queue is destroyed
* @name Queue#destroyed
* @type {boolean}
* @readonly
*/
this.player = player;
this.guild = guild;
this.options = { ...__1.DefaultPlayerOptions, ...options };
}
/**
* Joins a voice channel
* @param {GuildChannelResolvable} channelId
* @returns {Promise<Queue>}
*/
async join(channelId) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (this.connection)
return this;
const channel = this.guild.channels.resolve(channelId);
if (!channel) {
this.player.emit('error', __1.DMPErrorMessages.UnknownVoice, this);
throw new __1.DMPError(__1.DMPErrors.UNKNOWN_VOICE);
}
if (!__1.Utils.isVoiceChannel(channel)) {
this.player.emit('error', __1.DMPErrorMessages.ChannelTypeInvalid, this);
throw new __1.DMPError(__1.DMPErrors.CHANNEL_TYPE_INVALID);
}
let connection = (0, voice_1.joinVoiceChannel)({
guildId: channel.guild.id,
channelId: channel.id,
adapterCreator: channel.guild.voiceAdapterCreator,
selfDeaf: this.options.deafenOnJoin
});
let _connection;
try {
connection = await (0, voice_1.entersState)(connection, voice_1.VoiceConnectionStatus.Ready, 15 * 1000);
_connection = new StreamConnection_1.StreamConnection(connection, channel);
}
catch (err) {
connection.destroy();
this.player.emit('error', __1.DMPErrorMessages.VoiceConnectionError, this);
throw new __1.DMPError(__1.DMPErrors.VOICE_CONNECTION_ERROR);
}
this.connection = _connection;
if (__1.Utils.isStageVoiceChannel(channel)) {
const _guild = channel.guild;
const me = _guild.me ? _guild.me : _guild.members.me;
await me.voice.setSuppressed(false).catch(async (_) => {
return await channel.guild.members.me.voice.setRequestToSpeak(true).catch(() => null);
});
}
this.connection
.on('start', (resource) => {
this.isPlaying = true;
if (resource?.metadata?.isFirst && resource?.metadata?.seekTime === 0 && resource?.metadata?.firstTimeInQueue) {
this.player.emit('songFirst', this, this.nowPlaying);
resource.metadata.flipFirstTimeInQueue();
}
else if (resource?.metadata?.firstTimeInQueue) { //Needed if song played has ?t=
this.player.emit('songFirst', this, this.nowPlaying);
resource.metadata.flipFirstTimeInQueue();
}
})
.on('end', async (resource) => {
if (this.destroyed) {
this.player.emit('queueDestroyed', this);
return;
}
this.isPlaying = false;
let oldSong = this.songs.shift();
if (this.songs.length === 0 && this.repeatMode === __1.RepeatMode.DISABLED) {
this.player.emit('queueEnd', this);
if (this.options.leaveOnEnd)
setTimeout(() => {
if (!this.isPlaying)
this.leave();
}, this.options.timeout);
return;
}
else {
if (this.repeatMode === __1.RepeatMode.SONG) {
this.songs.unshift(oldSong);
this.songs[0]._setFirst(false);
this.player.emit('songChanged', this, this.songs[0], oldSong);
return this.play(this.songs[0], { immediate: true });
}
else if (this.repeatMode === __1.RepeatMode.QUEUE) {
this.songs.push(oldSong);
this.songs[this.songs.length - 1]._setFirst(false);
this.player.emit('songChanged', this, this.songs[0], oldSong);
return this.play(this.songs[0], { immediate: true });
}
this.player.emit('songChanged', this, this.songs[0], oldSong);
return this.play(this.songs[0], { immediate: true });
}
})
.on('error', (err) => this.player.emit('error', err.message, this));
return this;
}
/**
* Plays or Queues a song (in a VoiceChannel)
* @param {Song | string} search
* @param {PlayOptions} [options=DefaultPlayOptions]
* @returns {Promise<Song>}
*/
async play(search, options = __1.DefaultPlayOptions) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (!this.connection) {
this.player.emit('error', __1.DMPErrorMessages.NoVoiceConnection, this);
throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION);
}
options = Object.assign({}, __1.DefaultPlayOptions, options);
let { data } = options;
delete options.data;
let song = await __1.Utils.best(search, options, this)
.catch(error => {
this.player.emit('error', error, this);
throw new __1.DMPError(error);
});
if (!song) { //Checks if song is undefined or not
this.player.emit('error', __1.DMPErrorMessages.UnknownSong, this);
throw new __1.DMPError(__1.DMPErrors.UNKNOWN_SONG);
}
if (!options.immediate)
song.data = data;
if (options.filters && !song.filters) {
song.filters = options.filters;
}
let songLength = this.songs.length;
if (!options?.immediate && songLength !== 0) {
//This is to add songs to the queue
if (options?.index >= 0 && ++options.index <= songLength)
this.songs.splice(options.index, 0, song);
else
this.songs.push(song);
this.player.emit('songAdd', this, song);
return song;
}
else if (!options?.immediate) {
//This is for the first song that is ever queued
song._setFirst();
if (options?.index >= 0 && ++options.index <= songLength)
this.songs.splice(options.index, 0, song);
else
this.songs.push(song);
this.player.emit('songAdd', this, song);
}
else if (options.seek)
this.songs[0].seekTime = options.seek;
let quality = this.options.quality;
song = this.songs[0];
if (song.seekTime && this.repeatMode === __1.RepeatMode.DISABLED)
options.seek = song.seekTime; //If on repeat, song will start from the same seeked spot
let streamSong;
let i = 0;
while (!streamSong && i < 5) {
streamSong = (0, ytdl_core_1.default)(song.url, {
filter: 'audioonly',
quality: quality.toLowerCase() === "low" ? 'lowestaudio' : "highestaudio",
highWaterMark: 1 << 25,
playerClients: ["WEB_CREATOR", "IOS", "ANDROID", "WEB"]
});
i++;
}
if (!streamSong) {
this.player.emit('error', __1.DMPErrorMessages.SearchIsNull, this);
const oldSong = this.songs.shift();
if (this.songs.length != 0) {
this.player.emit('songChanged', this, this.songs[0], oldSong);
this.play(this.songs[0], { immediate: true });
}
else {
oldSong.name = 'NOTHING - FORCEFULLY REMOVED';
this.player.emit('songChanged', this, oldSong, oldSong);
}
}
else {
let resource;
if (song.filters) {
resource = this.connection.createAudioStream((0, Filters_1.createFFmpegStream)(streamSong, { encoderArgs: song.filters, seek: options.seek }), {
metadata: song,
inputType: voice_1.StreamType.OggOpus
});
}
else {
const { stream, type } = await (0, voice_1.demuxProbe)(streamSong);
resource = this.connection.createAudioStream(stream, {
metadata: song,
inputType: type
});
}
setTimeout((_) => {
if (this.connection) {
this.connection.playAudioStream(resource)
.then(__ => {
this.setVolume(this.options.volume);
}).catch(err => {
console.error(err);
});
}
});
}
return song;
}
/**
* Plays or Queues a playlist (in a VoiceChannel)
* @param {Playlist | string} search
* @param {PlaylistOptions} [options=DefaultPlaylistOptions]
* @returns {Promise<Playlist>}
*/
async playlist(search, options = __1.DefaultPlaylistOptions) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (!this.connection) {
this.player.emit('error', __1.DMPErrorMessages.NoVoiceConnection, this);
throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION);
}
options = Object.assign({}, __1.DefaultPlaylistOptions, options);
let playlist = await __1.Utils.playlist(search, options, this)
.catch(error => {
this.player.emit('error', error, this);
throw new __1.DMPError(error);
});
let songLength = this.songs.length;
if (options?.index >= 0 && ++options.index <= songLength)
this.songs.splice(options.index, 0, ...playlist.songs);
else
this.songs.push(...playlist.songs);
this.player.emit('playlistAdd', this, playlist);
if (songLength === 0) {
playlist.songs[0]._setFirst();
await this.play(playlist.songs[0], { immediate: true });
}
return playlist;
}
/**
* Seeks the current playing Song
* @param {number} time
* @returns {boolean}
*/
async seek(time) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (!this.isPlaying) {
this.player.emit('error', __1.DMPErrorMessages.NothingPlaying, this);
throw new __1.DMPError(__1.DMPErrors.NOTHING_PLAYING);
}
if (isNaN(time))
return;
if (time < 1)
time = 0;
if (time >= this.nowPlaying.milliseconds)
return this.skip();
await this.play(this.nowPlaying, {
immediate: true,
seek: time
});
return true;
}
/**
* Skips the current playing Song and returns it
* @param {number} [index=0]
* @returns {Song}
*/
skip(index = 0) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (!this.connection) {
this.player.emit('error', __1.DMPErrorMessages.NoVoiceConnection, this);
throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION);
}
this.songs.splice(1, index);
const skippedSong = this.songs[0];
this.connection.stop();
return skippedSong;
}
/**
* Stops playing the Music and cleans the Queue
* @returns {void}
*/
stop() {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
this.setRepeatMode(__1.RepeatMode.DISABLED);
this.clearQueue();
this.skip();
this.isPlaying = false;
if (this.options.leaveOnStop) {
setTimeout(() => {
if (!this.isPlaying) //edge case where timer starts then another song is queued
this.leave();
}, this.options.timeout);
}
}
/**
* Shuffles the Queue
* @returns {Song[]}
*/
shuffle() {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
let currentSong = this.songs.shift();
this.songs = __1.Utils.shuffle(this.songs);
this.songs.unshift(currentSong);
return this.songs;
}
/**
* Pause/resume the current Song
* @param {boolean} [state=true] Pause state, if none it will pause the Song
* @returns {boolean}
*/
setPaused(state = true) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (!this.connection) {
this.player.emit('error', __1.DMPErrorMessages.NoVoiceConnection, this);
throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION);
}
if (!this.isPlaying) {
this.player.emit('error', __1.DMPErrorMessages.NothingPlaying, this);
throw new __1.DMPError(__1.DMPErrors.NOTHING_PLAYING);
}
return this.connection.setPauseState(state);
}
/**
* Remove a Song from the Queue
* @param {number} index
* @returns {Song|undefined}
*/
remove(index) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
return this.songs.splice(index, 1)[0];
}
/**
* Gets the current volume
* @type {number}
*/
get volume() {
if (!this.connection)
return __1.DefaultPlayerOptions.volume;
return this.connection.volume;
}
/**
* Gets the paused state of the player
* @type {boolean}
*/
get paused() {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (!this.connection) {
this.player.emit('error', __1.DMPErrorMessages.NoVoiceConnection, this);
throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION);
}
if (!this.isPlaying) {
this.player.emit('error', __1.DMPErrorMessages.NothingPlaying, this);
throw new __1.DMPError(__1.DMPErrors.NOTHING_PLAYING);
}
return this.connection.paused;
}
/**
* Sets the current volume
* @param {number} volume
* @returns {boolean}
*/
setVolume(volume) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (!this.connection) {
this.player.emit('error', __1.DMPErrorMessages.NoVoiceConnection, this);
throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION);
}
this.options.volume = volume;
return this.connection.setVolume(volume);
}
/**
* Returns current playing song
* @type {?Song}
*/
get nowPlaying() {
return this.connection?.resource?.metadata ?? this.songs[0];
}
/**
* Clears the Queue
* @returns {void}
*/
clearQueue() {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
let currentlyPlaying = this.songs.shift();
this.songs = [currentlyPlaying];
}
/**
* Sets Queue repeat mode
* @param {RepeatMode} repeatMode
* @returns {boolean}
*/
setRepeatMode(repeatMode) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (![__1.RepeatMode.DISABLED, __1.RepeatMode.QUEUE, __1.RepeatMode.SONG].includes(repeatMode)) {
this.player.emit('error', __1.DMPErrorMessages.UnknownRepeatMode, this);
throw new __1.DMPError(__1.DMPErrors.UNKNOWN_REPEAT_MODE);
}
if (repeatMode === this.repeatMode)
return false;
this.repeatMode = repeatMode;
return true;
}
/**
* Creates Progress Bar class
* @param {ProgressBarOptions} [options]
* @returns {ProgressBar}
*/
createProgressBar(options) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
if (!this.isPlaying) {
this.player.emit('error', __1.DMPErrorMessages.NothingPlaying, this);
throw new __1.DMPError(__1.DMPErrors.NOTHING_PLAYING);
}
return new __1.ProgressBar(this, options);
}
/**
* Set's custom queue data
* @param {T} data
* @returns {void}
*/
setData(data) {
if (this.destroyed) {
this.player.emit('error', __1.DMPErrorMessages.QueueDestroyed, this);
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
}
this.data = data;
}
/**
* Disconnects the player
* @returns {void}
*/
leave() {
this.destroyed = true;
this.connection.leave();
this.player.deleteQueue(this.guild.id);
}
}
exports.Queue = Queue;