spike-player
Version:
Complete framework to facilitate music commands using discord.js v13 & v14
486 lines (485 loc) • 17.4 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 discord_ytdl_core_1 = __importDefault(require("@spikebot/discord-ytdl-core"));
const __1 = require("..");
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.data = null;
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} _channel
* @returns {Promise<Queue>}
*/
async join(channelId) {
if (this.destroyed)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (this.connection)
return this;
const channel = this.guild.channels.resolve(channelId);
if (!channel)
throw new __1.DMPError(__1.DMPErrors.UNKNOWN_VOICE);
if (!__1.Utils.isVoiceChannel(channel))
throw new __1.DMPError(__1.DMPErrors.CHANNEL_TYPE_INVALID);
let connection;
const currentConnection = (0, voice_1.getVoiceConnection)(channel.guild.id);
if (currentConnection) {
connection = currentConnection;
}
else {
connection = (0, voice_1.joinVoiceChannel)({
guildId: channel.guild.id,
channelId: channel.id,
adapterCreator: channel.guild.voiceAdapterCreator,
selfDeaf: false
});
}
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();
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)
this.player.emit("songFirst", this, this.nowPlaying);
})
.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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (!this.connection)
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) => {
throw new __1.DMPError(error);
});
// if(!options.immediate)
// song.data = data;
if (options?.seek)
song.seekTime = options.seek;
let songLength = this.songs.length;
if (!options?.immediate && songLength !== 0) {
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) {
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;
// console.log("selected song",song)
// song = song || this.songs[0];
if (song.seekTime)
options.seek = song.seekTime;
let resource;
if (__1.Utils.audioTypes.some((x) => song.url.includes(x))) {
resource = this.connection.createAudioStream(song.url, {
metadata: song,
inputType: voice_1.StreamType.Arbitrary // Opus
});
}
else {
// const songInfo = await ytdl.getBasicInfo(song.url,{
// requestOptions: {
// opusEncoded: false,
// seek: options.seek ? options.seek / 1000 : 0,
// fmt: 's16le',
// encoderArgs: [],
// quality: quality!.toLowerCase() === 'low' ? 'lowestaudio' : 'highestaudio',
// highWaterMark: 1 << 25,
// filter: 'audioonly'
// }});
// console.log("songInfo::::quality ", songInfo.formats[0].quality );
// console.log("songInfo::::qualityLabel ", songInfo.formats[0].qualityLabel );
let stream = (0, discord_ytdl_core_1.default)(song.url, {
requestOptions: this.player.options.ytdlRequestOptions ?? {},
opusEncoded: false,
seek: options.seek ? options.seek / 1000 : 0,
fmt: "s16le",
encoderArgs: [],
quality: quality.toLowerCase() === "low"
? "lowestaudio"
: "highestaudio",
highWaterMark: 1 << 62,
liveBuffer: 1 << 62,
filter: "audioonly"
}).on("error", (error) => {
if (!/Status code|premature close/i.test(error.message))
this.player.emit("error", error.message === "Video unavailable"
? "VideoUnavailable"
: error.message, this);
return;
});
resource = this.connection.createAudioStream(stream, {
metadata: song,
inputType: voice_1.StreamType.Raw,
});
}
setTimeout((_) => {
this.connection.playAudioStream(resource).then((__) => {
this.setVolume(this.options.volume);
});
});
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (!this.connection)
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) => {
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (!this.isPlaying)
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (!this.connection)
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (this.options.leaveOnStop) {
setTimeout(() => {
this.leave();
}, this.options.timeout);
}
else {
this.clearQueue();
this.skip();
}
}
/**
* Shuffles the Queue
* @returns {Song[]}
*/
shuffle() {
if (this.destroyed)
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (!this.connection)
throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION);
if (!this.isPlaying)
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)
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (!this.connection)
throw new __1.DMPError(__1.DMPErrors.NO_VOICE_CONNECTION);
if (!this.isPlaying)
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (!this.connection)
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)
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (![__1.RepeatMode.DISABLED, __1.RepeatMode.QUEUE, __1.RepeatMode.SONG].includes(repeatMode))
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)
throw new __1.DMPError(__1.DMPErrors.QUEUE_DESTROYED);
if (!this.isPlaying)
throw new __1.DMPError(__1.DMPErrors.NOTHING_PLAYING);
return new __1.ProgressBar(this, options);
}
/**
* Set's custom queue data
* @param {any} data
* @returns {void}
*/
setData(data) {
if (this.destroyed)
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;