mirror of
https://github.com/JonnyBro/JaBa.git
synced 2024-11-29 16:44:58 +05:00
762 lines
30 KiB
JavaScript
762 lines
30 KiB
JavaScript
|
"use strict";
|
||
|
var _Queue_instances, _Queue_lastVolume, _Queue_destroyed, _Queue_watchDestroyed, _Queue_getBufferingTimeout;
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.Queue = void 0;
|
||
|
const tslib_1 = require("tslib");
|
||
|
const discord_js_1 = require("discord.js");
|
||
|
const Track_1 = tslib_1.__importDefault(require("./Track"));
|
||
|
const types_1 = require("../types/types");
|
||
|
const voice_1 = require("@discordjs/voice");
|
||
|
const play_dl_1 = tslib_1.__importDefault(require("play-dl"));
|
||
|
const Util_1 = require("../utils/Util");
|
||
|
const AudioFilters_1 = tslib_1.__importDefault(require("../utils/AudioFilters"));
|
||
|
const PlayerError_1 = require("./PlayerError");
|
||
|
const FFmpegStream_1 = require("../utils/FFmpegStream");
|
||
|
class Queue {
|
||
|
/**
|
||
|
* Queue constructor
|
||
|
* @param {Player} player The player that instantiated this queue
|
||
|
* @param {Guild} guild The guild that instantiated this queue
|
||
|
* @param {PlayerOptions} [options] Player options for the queue
|
||
|
*/
|
||
|
constructor(player, guild, options = {}) {
|
||
|
_Queue_instances.add(this);
|
||
|
this.tracks = [];
|
||
|
this.previousTracks = [];
|
||
|
this.playing = false;
|
||
|
this.metadata = null;
|
||
|
this.repeatMode = 0;
|
||
|
this.id = discord_js_1.SnowflakeUtil.generate().toString();
|
||
|
this._streamTime = 0;
|
||
|
this._cooldownsTimeout = new discord_js_1.Collection();
|
||
|
this._activeFilters = []; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||
|
this._filtersUpdate = false;
|
||
|
_Queue_lastVolume.set(this, 0);
|
||
|
_Queue_destroyed.set(this, false);
|
||
|
this.onBeforeCreateStream = null;
|
||
|
/**
|
||
|
* The player that instantiated this queue
|
||
|
* @type {Player}
|
||
|
* @readonly
|
||
|
*/
|
||
|
this.player = player;
|
||
|
/**
|
||
|
* The guild that instantiated this queue
|
||
|
* @type {Guild}
|
||
|
* @readonly
|
||
|
*/
|
||
|
this.guild = guild;
|
||
|
/**
|
||
|
* The player options for this queue
|
||
|
* @type {PlayerOptions}
|
||
|
*/
|
||
|
this.options = {};
|
||
|
/**
|
||
|
* Queue repeat mode
|
||
|
* @type {QueueRepeatMode}
|
||
|
* @name Queue#repeatMode
|
||
|
*/
|
||
|
/**
|
||
|
* Queue metadata
|
||
|
* @type {any}
|
||
|
* @name Queue#metadata
|
||
|
*/
|
||
|
/**
|
||
|
* Previous tracks
|
||
|
* @type {Track[]}
|
||
|
* @name Queue#previousTracks
|
||
|
*/
|
||
|
/**
|
||
|
* Regular tracks
|
||
|
* @type {Track[]}
|
||
|
* @name Queue#tracks
|
||
|
*/
|
||
|
/**
|
||
|
* The connection
|
||
|
* @type {StreamDispatcher}
|
||
|
* @name Queue#connection
|
||
|
*/
|
||
|
/**
|
||
|
* The ID of this queue
|
||
|
* @type {Snowflake}
|
||
|
* @name Queue#id
|
||
|
*/
|
||
|
Object.assign(this.options, {
|
||
|
leaveOnEnd: true,
|
||
|
leaveOnStop: true,
|
||
|
leaveOnEmpty: true,
|
||
|
leaveOnEmptyCooldown: 1000,
|
||
|
autoSelfDeaf: true,
|
||
|
ytdlOptions: {
|
||
|
highWaterMark: 1 << 25
|
||
|
},
|
||
|
initialVolume: 100,
|
||
|
bufferingTimeout: 3000,
|
||
|
spotifyBridge: true,
|
||
|
disableVolume: false
|
||
|
}, options);
|
||
|
if ("onBeforeCreateStream" in this.options)
|
||
|
this.onBeforeCreateStream = this.options.onBeforeCreateStream;
|
||
|
this.player.emit("debug", this, `Queue initialized:\n\n${this.player.scanDeps()}`);
|
||
|
}
|
||
|
/**
|
||
|
* Returns current track
|
||
|
* @type {Track}
|
||
|
*/
|
||
|
get current() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
return this.connection.audioResource?.metadata ?? this.tracks[0];
|
||
|
}
|
||
|
/**
|
||
|
* If this queue is destroyed
|
||
|
* @type {boolean}
|
||
|
*/
|
||
|
get destroyed() {
|
||
|
return tslib_1.__classPrivateFieldGet(this, _Queue_destroyed, "f");
|
||
|
}
|
||
|
/**
|
||
|
* Returns current track
|
||
|
* @returns {Track}
|
||
|
*/
|
||
|
nowPlaying() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
return this.current;
|
||
|
}
|
||
|
/**
|
||
|
* Connects to a voice channel
|
||
|
* @param {GuildChannelResolvable} channel The voice/stage channel
|
||
|
* @returns {Promise<Queue>}
|
||
|
*/
|
||
|
async connect(channel) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
const _channel = this.guild.channels.resolve(channel);
|
||
|
if (![discord_js_1.ChannelType.GuildStageVoice, discord_js_1.ChannelType.GuildVoice].includes(_channel?.type))
|
||
|
throw new PlayerError_1.PlayerError(`Channel type must be GuildVoice or GuildStageVoice, got ${_channel?.type}!`, PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
|
||
|
const connection = await this.player.voiceUtils.connect(_channel, {
|
||
|
deaf: this.options.autoSelfDeaf
|
||
|
});
|
||
|
this.connection = connection;
|
||
|
if (_channel.type === discord_js_1.ChannelType.GuildStageVoice) {
|
||
|
await _channel.guild.members.me.voice.setSuppressed(false).catch(async () => {
|
||
|
return await _channel.guild.members.me.voice.setRequestToSpeak(true).catch(Util_1.Util.noop);
|
||
|
});
|
||
|
}
|
||
|
this.connection.on("error", (err) => {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
||
|
return;
|
||
|
this.player.emit("connectionError", this, err);
|
||
|
});
|
||
|
this.connection.on("debug", (msg) => {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
||
|
return;
|
||
|
this.player.emit("debug", this, msg);
|
||
|
});
|
||
|
this.player.emit("connectionCreate", this, this.connection);
|
||
|
this.connection.on("start", (resource) => {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
||
|
return;
|
||
|
this.playing = true;
|
||
|
if (!this._filtersUpdate)
|
||
|
this.player.emit("trackStart", this, resource?.metadata ?? this.current);
|
||
|
this._filtersUpdate = false;
|
||
|
});
|
||
|
this.connection.on("finish", async (resource) => {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
||
|
return;
|
||
|
this.playing = false;
|
||
|
if (this._filtersUpdate)
|
||
|
return;
|
||
|
this._streamTime = 0;
|
||
|
if (resource?.metadata)
|
||
|
this.previousTracks.push(resource.metadata);
|
||
|
this.player.emit("trackEnd", this, resource.metadata);
|
||
|
if (!this.tracks.length && this.repeatMode === types_1.QueueRepeatMode.OFF) {
|
||
|
if (this.options.leaveOnEnd)
|
||
|
this.destroy();
|
||
|
this.player.emit("queueEnd", this);
|
||
|
}
|
||
|
else if (!this.tracks.length && this.repeatMode === types_1.QueueRepeatMode.AUTOPLAY) {
|
||
|
this._handleAutoplay(Util_1.Util.last(this.previousTracks));
|
||
|
}
|
||
|
else {
|
||
|
if (this.repeatMode === types_1.QueueRepeatMode.TRACK)
|
||
|
return void this.play(Util_1.Util.last(this.previousTracks), { immediate: true });
|
||
|
if (this.repeatMode === types_1.QueueRepeatMode.QUEUE)
|
||
|
this.tracks.push(Util_1.Util.last(this.previousTracks));
|
||
|
const nextTrack = this.tracks.shift();
|
||
|
this.play(nextTrack, { immediate: true });
|
||
|
return;
|
||
|
}
|
||
|
});
|
||
|
return this;
|
||
|
}
|
||
|
/**
|
||
|
* Destroys this queue
|
||
|
* @param {boolean} [disconnect=this.options.leaveOnStop] If it should leave on destroy
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
destroy(disconnect = this.options.leaveOnStop) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (this.connection)
|
||
|
this.connection.end();
|
||
|
if (disconnect)
|
||
|
this.connection?.disconnect();
|
||
|
this.player.queues.delete(this.guild.id);
|
||
|
this.player.voiceUtils.cache.delete(this.guild.id);
|
||
|
tslib_1.__classPrivateFieldSet(this, _Queue_destroyed, true, "f");
|
||
|
}
|
||
|
/**
|
||
|
* Skips current track
|
||
|
* @returns {boolean}
|
||
|
*/
|
||
|
skip() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.connection)
|
||
|
return false;
|
||
|
this._filtersUpdate = false;
|
||
|
this.connection.end();
|
||
|
return true;
|
||
|
}
|
||
|
/**
|
||
|
* Adds single track to the queue
|
||
|
* @param {Track} track The track to add
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
addTrack(track) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!(track instanceof Track_1.default))
|
||
|
throw new PlayerError_1.PlayerError("invalid track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
|
||
|
this.tracks.push(track);
|
||
|
this.player.emit("trackAdd", this, track);
|
||
|
}
|
||
|
/**
|
||
|
* Adds multiple tracks to the queue
|
||
|
* @param {Track[]} tracks Array of tracks to add
|
||
|
*/
|
||
|
addTracks(tracks) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!tracks.every((y) => y instanceof Track_1.default))
|
||
|
throw new PlayerError_1.PlayerError("invalid track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
|
||
|
this.tracks.push(...tracks);
|
||
|
this.player.emit("tracksAdd", this, tracks);
|
||
|
}
|
||
|
/**
|
||
|
* Sets paused state
|
||
|
* @param {boolean} paused The paused state
|
||
|
* @returns {boolean}
|
||
|
*/
|
||
|
setPaused(paused) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.connection)
|
||
|
return false;
|
||
|
return paused ? this.connection.pause(true) : this.connection.resume();
|
||
|
}
|
||
|
/**
|
||
|
* Sets bitrate
|
||
|
* @param {number|auto} bitrate bitrate to set
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
setBitrate(bitrate) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.connection?.audioResource?.encoder)
|
||
|
return;
|
||
|
if (bitrate === "auto")
|
||
|
bitrate = this.connection.channel?.bitrate ?? 64000;
|
||
|
this.connection.audioResource.encoder.setBitrate(bitrate);
|
||
|
}
|
||
|
/**
|
||
|
* Sets volume
|
||
|
* @param {number} amount The volume amount
|
||
|
* @returns {boolean}
|
||
|
*/
|
||
|
setVolume(amount) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.connection)
|
||
|
return false;
|
||
|
tslib_1.__classPrivateFieldSet(this, _Queue_lastVolume, amount, "f");
|
||
|
this.options.initialVolume = amount;
|
||
|
return this.connection.setVolume(amount);
|
||
|
}
|
||
|
/**
|
||
|
* Sets repeat mode
|
||
|
* @param {QueueRepeatMode} mode The repeat mode
|
||
|
* @returns {boolean}
|
||
|
*/
|
||
|
setRepeatMode(mode) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (![types_1.QueueRepeatMode.OFF, types_1.QueueRepeatMode.QUEUE, types_1.QueueRepeatMode.TRACK, types_1.QueueRepeatMode.AUTOPLAY].includes(mode))
|
||
|
throw new PlayerError_1.PlayerError(`Unknown repeat mode "${mode}"!`, PlayerError_1.ErrorStatusCode.UNKNOWN_REPEAT_MODE);
|
||
|
if (mode === this.repeatMode)
|
||
|
return false;
|
||
|
this.repeatMode = mode;
|
||
|
return true;
|
||
|
}
|
||
|
/**
|
||
|
* The current volume amount
|
||
|
* @type {number}
|
||
|
*/
|
||
|
get volume() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.connection)
|
||
|
return 100;
|
||
|
return this.connection.volume;
|
||
|
}
|
||
|
set volume(amount) {
|
||
|
this.setVolume(amount);
|
||
|
}
|
||
|
/**
|
||
|
* The stream time of this queue
|
||
|
* @type {number}
|
||
|
*/
|
||
|
get streamTime() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.connection)
|
||
|
return 0;
|
||
|
const playbackTime = this._streamTime + this.connection.streamTime;
|
||
|
const NC = this._activeFilters.includes("nightcore") ? 1.25 : null;
|
||
|
const VW = this._activeFilters.includes("vaporwave") ? 0.8 : null;
|
||
|
if (NC && VW)
|
||
|
return playbackTime * (NC + VW);
|
||
|
return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime;
|
||
|
}
|
||
|
set streamTime(time) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
this.seek(time);
|
||
|
}
|
||
|
/**
|
||
|
* Returns enabled filters
|
||
|
* @returns {AudioFilters}
|
||
|
*/
|
||
|
getFiltersEnabled() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
return AudioFilters_1.default.names.filter((x) => this._activeFilters.includes(x));
|
||
|
}
|
||
|
/**
|
||
|
* Returns disabled filters
|
||
|
* @returns {AudioFilters}
|
||
|
*/
|
||
|
getFiltersDisabled() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
return AudioFilters_1.default.names.filter((x) => !this._activeFilters.includes(x));
|
||
|
}
|
||
|
/**
|
||
|
* Sets filters
|
||
|
* @param {QueueFilters} filters Queue filters
|
||
|
* @returns {Promise<void>}
|
||
|
*/
|
||
|
async setFilters(filters) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!filters || !Object.keys(filters).length) {
|
||
|
// reset filters
|
||
|
const streamTime = this.streamTime;
|
||
|
this._activeFilters = [];
|
||
|
return await this.play(this.current, {
|
||
|
immediate: true,
|
||
|
filtersUpdate: true,
|
||
|
seek: streamTime,
|
||
|
encoderArgs: []
|
||
|
});
|
||
|
}
|
||
|
const _filters = []; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||
|
for (const filter in filters) {
|
||
|
if (filters[filter] === true)
|
||
|
_filters.push(filter);
|
||
|
}
|
||
|
if (this._activeFilters.join("") === _filters.join(""))
|
||
|
return;
|
||
|
const newFilters = AudioFilters_1.default.create(_filters).trim();
|
||
|
const streamTime = this.streamTime;
|
||
|
this._activeFilters = _filters;
|
||
|
return await this.play(this.current, {
|
||
|
immediate: true,
|
||
|
filtersUpdate: true,
|
||
|
seek: streamTime,
|
||
|
encoderArgs: !_filters.length ? undefined : ["-af", newFilters]
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Seeks to the given time
|
||
|
* @param {number} position The position
|
||
|
* @returns {boolean}
|
||
|
*/
|
||
|
async seek(position) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.playing || !this.current)
|
||
|
return false;
|
||
|
if (position < 1)
|
||
|
position = 0;
|
||
|
if (position >= this.current.durationMS)
|
||
|
return this.skip();
|
||
|
await this.play(this.current, {
|
||
|
immediate: true,
|
||
|
filtersUpdate: true,
|
||
|
seek: position
|
||
|
});
|
||
|
return true;
|
||
|
}
|
||
|
/**
|
||
|
* Plays previous track
|
||
|
* @returns {Promise<void>}
|
||
|
*/
|
||
|
async back() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
const prev = this.previousTracks[this.previousTracks.length - 2]; // because last item is the current track
|
||
|
if (!prev)
|
||
|
throw new PlayerError_1.PlayerError("Could not find previous track", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
|
||
|
return await this.play(prev, { immediate: true });
|
||
|
}
|
||
|
/**
|
||
|
* Clear this queue
|
||
|
*/
|
||
|
clear() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
this.tracks = [];
|
||
|
this.previousTracks = [];
|
||
|
}
|
||
|
/**
|
||
|
* Stops the player
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
stop() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
return this.destroy();
|
||
|
}
|
||
|
/**
|
||
|
* Shuffles this queue
|
||
|
* @returns {boolean}
|
||
|
*/
|
||
|
shuffle() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.tracks.length || this.tracks.length < 2)
|
||
|
return false;
|
||
|
for (let i = this.tracks.length - 1; i > 0; i--) {
|
||
|
const j = Math.floor(Math.random() * (i + 1));
|
||
|
[this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]];
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
/**
|
||
|
* Removes a track from the queue
|
||
|
* @param {Track|string|number} track The track to remove
|
||
|
* @returns {Track}
|
||
|
*/
|
||
|
remove(track) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
let trackFound = null;
|
||
|
if (typeof track === "number") {
|
||
|
trackFound = this.tracks[track];
|
||
|
if (trackFound) {
|
||
|
this.tracks = this.tracks.filter((t) => t.id !== trackFound.id);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
trackFound = this.tracks.find((s) => s.id === (track instanceof Track_1.default ? track.id : track));
|
||
|
if (trackFound) {
|
||
|
this.tracks = this.tracks.filter((s) => s.id !== trackFound.id);
|
||
|
}
|
||
|
}
|
||
|
return trackFound;
|
||
|
}
|
||
|
/**
|
||
|
* Returns the index of the specified track. If found, returns the track index else returns -1.
|
||
|
* @param {number|Track|string} track The track
|
||
|
* @returns {number}
|
||
|
*/
|
||
|
getTrackPosition(track) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (typeof track === "number")
|
||
|
return this.tracks[track] != null ? track : -1;
|
||
|
return this.tracks.findIndex((pred) => pred.id === (track instanceof Track_1.default ? track.id : track));
|
||
|
}
|
||
|
/**
|
||
|
* Jumps to particular track
|
||
|
* @param {Track|number} track The track
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
jump(track) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
const foundTrack = this.remove(track);
|
||
|
if (!foundTrack)
|
||
|
throw new PlayerError_1.PlayerError("Track not found", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
|
||
|
this.tracks.splice(0, 0, foundTrack);
|
||
|
return void this.skip();
|
||
|
}
|
||
|
/**
|
||
|
* Jumps to particular track, removing other tracks on the way
|
||
|
* @param {Track|number} track The track
|
||
|
* @returns {void}
|
||
|
*/
|
||
|
skipTo(track) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
const trackIndex = this.getTrackPosition(track);
|
||
|
const removedTrack = this.remove(track);
|
||
|
if (!removedTrack)
|
||
|
throw new PlayerError_1.PlayerError("Track not found", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
|
||
|
this.tracks.splice(0, trackIndex, removedTrack);
|
||
|
return void this.skip();
|
||
|
}
|
||
|
/**
|
||
|
* Inserts the given track to specified index
|
||
|
* @param {Track} track The track to insert
|
||
|
* @param {number} [index=0] The index where this track should be
|
||
|
*/
|
||
|
insert(track, index = 0) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!track || !(track instanceof Track_1.default))
|
||
|
throw new PlayerError_1.PlayerError("track must be the instance of Track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
|
||
|
if (typeof index !== "number" || index < 0 || !Number.isFinite(index))
|
||
|
throw new PlayerError_1.PlayerError(`Invalid index "${index}"`, PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
|
||
|
this.tracks.splice(index, 0, track);
|
||
|
this.player.emit("trackAdd", this, track);
|
||
|
}
|
||
|
/**
|
||
|
* @typedef {object} PlayerTimestamp
|
||
|
* @property {string} current The current progress
|
||
|
* @property {string} end The total time
|
||
|
* @property {number} progress Progress in %
|
||
|
*/
|
||
|
/**
|
||
|
* Returns player stream timestamp
|
||
|
* @returns {PlayerTimestamp}
|
||
|
*/
|
||
|
getPlayerTimestamp() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
const currentStreamTime = this.streamTime;
|
||
|
const totalTime = this.current.durationMS;
|
||
|
const currentTimecode = Util_1.Util.buildTimeCode(Util_1.Util.parseMS(currentStreamTime));
|
||
|
const endTimecode = Util_1.Util.buildTimeCode(Util_1.Util.parseMS(totalTime));
|
||
|
return {
|
||
|
current: currentTimecode,
|
||
|
end: endTimecode,
|
||
|
progress: Math.round((currentStreamTime / totalTime) * 100)
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* Creates progress bar string
|
||
|
* @param {PlayerProgressbarOptions} options The progress bar options
|
||
|
* @returns {string}
|
||
|
*/
|
||
|
createProgressBar(options = { timecodes: true }) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
const length = typeof options.length === "number" ? (options.length <= 0 || options.length === Infinity ? 15 : options.length) : 15;
|
||
|
const index = Math.round((this.streamTime / this.current.durationMS) * length);
|
||
|
const indicator = typeof options.indicator === "string" && options.indicator.length > 0 ? options.indicator : "🔘";
|
||
|
const line = typeof options.line === "string" && options.line.length > 0 ? options.line : "▬";
|
||
|
if (index >= 1 && index <= length) {
|
||
|
const bar = line.repeat(length - 1).split("");
|
||
|
bar.splice(index, 0, indicator);
|
||
|
if (options.timecodes) {
|
||
|
const timestamp = this.getPlayerTimestamp();
|
||
|
return `${timestamp.current} ┃ ${bar.join("")} ┃ ${timestamp.end}`;
|
||
|
}
|
||
|
else {
|
||
|
return `${bar.join("")}`;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (options.timecodes) {
|
||
|
const timestamp = this.getPlayerTimestamp();
|
||
|
return `${timestamp.current} ┃ ${indicator}${line.repeat(length - 1)} ┃ ${timestamp.end}`;
|
||
|
}
|
||
|
else {
|
||
|
return `${indicator}${line.repeat(length - 1)}`;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Total duration
|
||
|
* @type {Number}
|
||
|
*/
|
||
|
get totalTime() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0;
|
||
|
}
|
||
|
/**
|
||
|
* Play stream in a voice/stage channel
|
||
|
* @param {Track} [src] The track to play (if empty, uses first track from the queue)
|
||
|
* @param {PlayOptions} [options] The options
|
||
|
* @returns {Promise<void>}
|
||
|
*/
|
||
|
async play(src, options = {}) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
|
||
|
return;
|
||
|
if (!this.connection || !this.connection.voiceConnection)
|
||
|
throw new PlayerError_1.PlayerError("Voice connection is not available, use <Queue>.connect()!", PlayerError_1.ErrorStatusCode.NO_CONNECTION);
|
||
|
if (src && (this.playing || this.tracks.length) && !options.immediate)
|
||
|
return this.addTrack(src);
|
||
|
const track = options.filtersUpdate && !options.immediate ? src || this.current : src ?? this.tracks.shift();
|
||
|
if (!track)
|
||
|
return;
|
||
|
this.player.emit("debug", this, "Received play request");
|
||
|
if (!options.filtersUpdate) {
|
||
|
this.previousTracks = this.previousTracks.filter((x) => x.id !== track.id);
|
||
|
this.previousTracks.push(track);
|
||
|
}
|
||
|
let stream = null;
|
||
|
const hasCustomDownloader = typeof this.onBeforeCreateStream === "function";
|
||
|
if (["youtube", "spotify"].includes(track.raw.source)) {
|
||
|
let spotifyResolved = false;
|
||
|
if (this.options.spotifyBridge && track.raw.source === "spotify" && !track.raw.engine) {
|
||
|
track.raw.engine = await play_dl_1.default.search(`${track.author} ${track.title}`, { source: { youtube: "video" } })
|
||
|
.then(res => res[0].url)
|
||
|
.catch(() => null);
|
||
|
spotifyResolved = true;
|
||
|
}
|
||
|
const url = track.raw.source === "spotify" ? track.raw.engine : track.url;
|
||
|
if (!url)
|
||
|
return void this.play(this.tracks.shift(), { immediate: true });
|
||
|
if (hasCustomDownloader) {
|
||
|
stream = (await this.onBeforeCreateStream(track, spotifyResolved ? "youtube" : track.raw.source, this)) || null;
|
||
|
}
|
||
|
if (!stream) {
|
||
|
stream = (await play_dl_1.default.stream(url, { discordPlayerCompatibility: true })).stream;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
const arbitraryStream = (hasCustomDownloader && (await this.onBeforeCreateStream(track, track.raw.source || track.raw.engine, this))) || null;
|
||
|
stream =
|
||
|
arbitraryStream || (track.raw.source === "soundcloud" && typeof track.raw.engine?.downloadProgressive === "function")
|
||
|
? await track.raw.engine.downloadProgressive()
|
||
|
: typeof track.raw.engine === "function"
|
||
|
? await track.raw.engine()
|
||
|
: track.raw.engine;
|
||
|
}
|
||
|
const ffmpegStream = (0, FFmpegStream_1.createFFmpegStream)(stream, {
|
||
|
encoderArgs: options.encoderArgs || [],
|
||
|
seek: options.seek ? options.seek / 1000 : 0,
|
||
|
fmt: "s16le"
|
||
|
}).on("error", (err) => {
|
||
|
if (!`${err}`.toLowerCase().includes("premature close"))
|
||
|
this.player.emit("error", this, err);
|
||
|
});
|
||
|
const resource = this.connection.createStream(ffmpegStream, {
|
||
|
type: voice_1.StreamType.Raw,
|
||
|
data: track,
|
||
|
disableVolume: Boolean(this.options.disableVolume)
|
||
|
});
|
||
|
if (options.seek)
|
||
|
this._streamTime = options.seek;
|
||
|
this._filtersUpdate = options.filtersUpdate;
|
||
|
const volumeTransformer = resource.volume;
|
||
|
if (volumeTransformer && typeof this.options.initialVolume === "number")
|
||
|
Reflect.set(volumeTransformer, "volume", Math.pow(this.options.initialVolume / 100, 1.660964));
|
||
|
if (volumeTransformer?.hasSmoothness && typeof this.options.volumeSmoothness === "number") {
|
||
|
if (typeof volumeTransformer.setSmoothness === "function")
|
||
|
volumeTransformer.setSmoothness(this.options.volumeSmoothness || 0);
|
||
|
}
|
||
|
setTimeout(() => {
|
||
|
this.connection.playStream(resource);
|
||
|
}, tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_getBufferingTimeout).call(this)).unref();
|
||
|
}
|
||
|
/**
|
||
|
* Private method to handle autoplay
|
||
|
* @param {Track} track The source track to find its similar track for autoplay
|
||
|
* @returns {Promise<void>}
|
||
|
* @private
|
||
|
*/
|
||
|
async _handleAutoplay(track) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!track || ![track.source, track.raw?.source].includes("youtube")) {
|
||
|
if (this.options.leaveOnEnd)
|
||
|
this.destroy();
|
||
|
return void this.player.emit("queueEnd", this);
|
||
|
}
|
||
|
const info = await play_dl_1.default.video_info(track.url)
|
||
|
.catch(Util_1.Util.noop);
|
||
|
if (!info) {
|
||
|
if (this.options.leaveOnEnd)
|
||
|
this.destroy();
|
||
|
return void this.player.emit("queueEnd", this);
|
||
|
}
|
||
|
const randomRelated = await play_dl_1.default.video_info(info.related_videos[0]);
|
||
|
const nextTrack = new Track_1.default(this.player, {
|
||
|
title: randomRelated.video_details.title,
|
||
|
url: randomRelated.video_details.url,
|
||
|
duration: randomRelated.video_details.durationRaw ? Util_1.Util.buildTimeCode(Util_1.Util.parseMS(randomRelated.video_details.durationInSec * 1000)) : "0:00",
|
||
|
description: "",
|
||
|
thumbnail: Util_1.Util.last(randomRelated.video_details.thumbnails).url,
|
||
|
views: randomRelated.video_details.views,
|
||
|
author: randomRelated.video_details.channel.name,
|
||
|
requestedBy: track.requestedBy,
|
||
|
source: "youtube"
|
||
|
});
|
||
|
this.play(nextTrack, { immediate: true });
|
||
|
}
|
||
|
*[(_Queue_lastVolume = new WeakMap(), _Queue_destroyed = new WeakMap(), _Queue_instances = new WeakSet(), Symbol.iterator)]() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
yield* this.tracks;
|
||
|
}
|
||
|
/**
|
||
|
* JSON representation of this queue
|
||
|
* @returns {object}
|
||
|
*/
|
||
|
toJSON() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
return {
|
||
|
id: this.id,
|
||
|
guild: this.guild.id,
|
||
|
voiceChannel: this.connection?.channel?.id,
|
||
|
options: this.options,
|
||
|
tracks: this.tracks.map((m) => m.toJSON())
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* String representation of this queue
|
||
|
* @returns {string}
|
||
|
*/
|
||
|
toString() {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
|
||
|
return;
|
||
|
if (!this.tracks.length)
|
||
|
return "No songs available to display!";
|
||
|
return `**Upcoming Songs:**\n${this.tracks.map((m, i) => `${i + 1}. **${m.title}**`).join("\n")}`;
|
||
|
}
|
||
|
}
|
||
|
exports.Queue = Queue;
|
||
|
_Queue_watchDestroyed = function _Queue_watchDestroyed(emit = true) {
|
||
|
if (tslib_1.__classPrivateFieldGet(this, _Queue_destroyed, "f")) {
|
||
|
if (emit)
|
||
|
this.player.emit("error", this, new PlayerError_1.PlayerError("Cannot use destroyed queue", PlayerError_1.ErrorStatusCode.DESTROYED_QUEUE));
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}, _Queue_getBufferingTimeout = function _Queue_getBufferingTimeout() {
|
||
|
const timeout = this.options.bufferingTimeout;
|
||
|
if (isNaN(timeout) || timeout < 0 || !Number.isFinite(timeout))
|
||
|
return 1000;
|
||
|
return timeout;
|
||
|
};
|