discord-player 5.3.0-dev.3 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,769 +1,769 @@
1
- "use strict";
2
- var _Queue_instances, _Queue_lastVolume, _Queue_destroyed, _Queue_watchDestroyed, _Queue_getBufferingTimeout;
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.Queue = void 0;
5
- const tslib_1 = require("tslib");
6
- const discord_js_1 = require("discord.js");
7
- const Track_1 = tslib_1.__importDefault(require("./Track"));
8
- const types_1 = require("../types/types");
9
- const ytdl_core_1 = tslib_1.__importDefault(require("ytdl-core"));
10
- const voice_1 = require("@discordjs/voice");
11
- const Util_1 = require("../utils/Util");
12
- const youtube_sr_1 = tslib_1.__importDefault(require("youtube-sr"));
13
- const AudioFilters_1 = tslib_1.__importDefault(require("../utils/AudioFilters"));
14
- const PlayerError_1 = require("./PlayerError");
15
- const FFmpegStream_1 = require("../utils/FFmpegStream");
16
- class Queue {
17
- /**
18
- * Queue constructor
19
- * @param {Player} player The player that instantiated this queue
20
- * @param {Guild} guild The guild that instantiated this queue
21
- * @param {PlayerOptions} [options] Player options for the queue
22
- */
23
- constructor(player, guild, options = {}) {
24
- _Queue_instances.add(this);
25
- this.tracks = [];
26
- this.previousTracks = [];
27
- this.playing = false;
28
- this.metadata = null;
29
- this.repeatMode = 0;
30
- this.id = discord_js_1.SnowflakeUtil.generate().toString();
31
- this._streamTime = 0;
32
- this._cooldownsTimeout = new discord_js_1.Collection();
33
- this._activeFilters = []; // eslint-disable-line @typescript-eslint/no-explicit-any
34
- this._filtersUpdate = false;
35
- _Queue_lastVolume.set(this, 0);
36
- _Queue_destroyed.set(this, false);
37
- this.onBeforeCreateStream = null;
38
- /**
39
- * The player that instantiated this queue
40
- * @type {Player}
41
- * @readonly
42
- */
43
- this.player = player;
44
- /**
45
- * The guild that instantiated this queue
46
- * @type {Guild}
47
- * @readonly
48
- */
49
- this.guild = guild;
50
- /**
51
- * The player options for this queue
52
- * @type {PlayerOptions}
53
- */
54
- this.options = {};
55
- /**
56
- * Queue repeat mode
57
- * @type {QueueRepeatMode}
58
- * @name Queue#repeatMode
59
- */
60
- /**
61
- * Queue metadata
62
- * @type {any}
63
- * @name Queue#metadata
64
- */
65
- /**
66
- * Previous tracks
67
- * @type {Track[]}
68
- * @name Queue#previousTracks
69
- */
70
- /**
71
- * Regular tracks
72
- * @type {Track[]}
73
- * @name Queue#tracks
74
- */
75
- /**
76
- * The connection
77
- * @type {StreamDispatcher}
78
- * @name Queue#connection
79
- */
80
- /**
81
- * The ID of this queue
82
- * @type {Snowflake}
83
- * @name Queue#id
84
- */
85
- Object.assign(this.options, {
86
- leaveOnEnd: true,
87
- leaveOnStop: true,
88
- leaveOnEmpty: true,
89
- leaveOnEmptyCooldown: 1000,
90
- autoSelfDeaf: true,
91
- ytdlOptions: {
92
- highWaterMark: 1 << 25
93
- },
94
- initialVolume: 100,
95
- bufferingTimeout: 3000,
96
- spotifyBridge: true,
97
- disableVolume: false
98
- }, options);
99
- if ("onBeforeCreateStream" in this.options)
100
- this.onBeforeCreateStream = this.options.onBeforeCreateStream;
101
- this.player.emit("debug", this, `Queue initialized:\n\n${this.player.scanDeps()}`);
102
- }
103
- /**
104
- * Returns current track
105
- * @type {Track}
106
- */
107
- get current() {
108
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
109
- return;
110
- return this.connection.audioResource?.metadata ?? this.tracks[0];
111
- }
112
- /**
113
- * If this queue is destroyed
114
- * @type {boolean}
115
- */
116
- get destroyed() {
117
- return tslib_1.__classPrivateFieldGet(this, _Queue_destroyed, "f");
118
- }
119
- /**
120
- * Returns current track
121
- * @returns {Track}
122
- */
123
- nowPlaying() {
124
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
125
- return;
126
- return this.current;
127
- }
128
- /**
129
- * Connects to a voice channel
130
- * @param {GuildChannelResolvable} channel The voice/stage channel
131
- * @returns {Promise<Queue>}
132
- */
133
- async connect(channel) {
134
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
135
- return;
136
- const _channel = this.guild.channels.resolve(channel);
137
- if (![discord_js_1.ChannelType.GuildStageVoice, discord_js_1.ChannelType.GuildVoice].includes(_channel?.type))
138
- throw new PlayerError_1.PlayerError(`Channel type must be GuildVoice or GuildStageVoice, got ${_channel?.type}!`, PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
139
- const connection = await this.player.voiceUtils.connect(_channel, {
140
- deaf: this.options.autoSelfDeaf
141
- });
142
- this.connection = connection;
143
- if (_channel.type === discord_js_1.ChannelType.GuildStageVoice) {
144
- await _channel.guild.members.me.voice.setSuppressed(false).catch(async () => {
145
- return await _channel.guild.members.me.voice.setRequestToSpeak(true).catch(Util_1.Util.noop);
146
- });
147
- }
148
- this.connection.on("error", (err) => {
149
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
150
- return;
151
- this.player.emit("connectionError", this, err);
152
- });
153
- this.connection.on("debug", (msg) => {
154
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
155
- return;
156
- this.player.emit("debug", this, msg);
157
- });
158
- this.player.emit("connectionCreate", this, this.connection);
159
- this.connection.on("start", (resource) => {
160
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
161
- return;
162
- this.playing = true;
163
- if (!this._filtersUpdate)
164
- this.player.emit("trackStart", this, resource?.metadata ?? this.current);
165
- this._filtersUpdate = false;
166
- });
167
- this.connection.on("finish", async (resource) => {
168
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
169
- return;
170
- this.playing = false;
171
- if (this._filtersUpdate)
172
- return;
173
- this._streamTime = 0;
174
- if (resource?.metadata)
175
- this.previousTracks.push(resource.metadata);
176
- this.player.emit("trackEnd", this, resource.metadata);
177
- if (!this.tracks.length && this.repeatMode === types_1.QueueRepeatMode.OFF) {
178
- if (this.options.leaveOnEnd)
179
- this.destroy();
180
- this.player.emit("queueEnd", this);
181
- }
182
- else if (!this.tracks.length && this.repeatMode === types_1.QueueRepeatMode.AUTOPLAY) {
183
- this._handleAutoplay(Util_1.Util.last(this.previousTracks));
184
- }
185
- else {
186
- if (this.repeatMode === types_1.QueueRepeatMode.TRACK)
187
- return void this.play(Util_1.Util.last(this.previousTracks), { immediate: true });
188
- if (this.repeatMode === types_1.QueueRepeatMode.QUEUE)
189
- this.tracks.push(Util_1.Util.last(this.previousTracks));
190
- const nextTrack = this.tracks.shift();
191
- this.play(nextTrack, { immediate: true });
192
- return;
193
- }
194
- });
195
- return this;
196
- }
197
- /**
198
- * Destroys this queue
199
- * @param {boolean} [disconnect=this.options.leaveOnStop] If it should leave on destroy
200
- * @returns {void}
201
- */
202
- destroy(disconnect = this.options.leaveOnStop) {
203
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
204
- return;
205
- if (this.connection)
206
- this.connection.end();
207
- if (disconnect)
208
- this.connection?.disconnect();
209
- this.player.queues.delete(this.guild.id);
210
- this.player.voiceUtils.cache.delete(this.guild.id);
211
- tslib_1.__classPrivateFieldSet(this, _Queue_destroyed, true, "f");
212
- }
213
- /**
214
- * Skips current track
215
- * @returns {boolean}
216
- */
217
- skip() {
218
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
219
- return;
220
- if (!this.connection)
221
- return false;
222
- this._filtersUpdate = false;
223
- this.connection.end();
224
- return true;
225
- }
226
- /**
227
- * Adds single track to the queue
228
- * @param {Track} track The track to add
229
- * @returns {void}
230
- */
231
- addTrack(track) {
232
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
233
- return;
234
- if (!(track instanceof Track_1.default))
235
- throw new PlayerError_1.PlayerError("invalid track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
236
- this.tracks.push(track);
237
- this.player.emit("trackAdd", this, track);
238
- }
239
- /**
240
- * Adds multiple tracks to the queue
241
- * @param {Track[]} tracks Array of tracks to add
242
- */
243
- addTracks(tracks) {
244
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
245
- return;
246
- if (!tracks.every((y) => y instanceof Track_1.default))
247
- throw new PlayerError_1.PlayerError("invalid track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
248
- this.tracks.push(...tracks);
249
- this.player.emit("tracksAdd", this, tracks);
250
- }
251
- /**
252
- * Sets paused state
253
- * @param {boolean} paused The paused state
254
- * @returns {boolean}
255
- */
256
- setPaused(paused) {
257
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
258
- return;
259
- if (!this.connection)
260
- return false;
261
- return paused ? this.connection.pause(true) : this.connection.resume();
262
- }
263
- /**
264
- * Sets bitrate
265
- * @param {number|auto} bitrate bitrate to set
266
- * @returns {void}
267
- */
268
- setBitrate(bitrate) {
269
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
270
- return;
271
- if (!this.connection?.audioResource?.encoder)
272
- return;
273
- if (bitrate === "auto")
274
- bitrate = this.connection.channel?.bitrate ?? 64000;
275
- this.connection.audioResource.encoder.setBitrate(bitrate);
276
- }
277
- /**
278
- * Sets volume
279
- * @param {number} amount The volume amount
280
- * @returns {boolean}
281
- */
282
- setVolume(amount) {
283
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
284
- return;
285
- if (!this.connection)
286
- return false;
287
- tslib_1.__classPrivateFieldSet(this, _Queue_lastVolume, amount, "f");
288
- this.options.initialVolume = amount;
289
- return this.connection.setVolume(amount);
290
- }
291
- /**
292
- * Sets repeat mode
293
- * @param {QueueRepeatMode} mode The repeat mode
294
- * @returns {boolean}
295
- */
296
- setRepeatMode(mode) {
297
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
298
- return;
299
- if (![types_1.QueueRepeatMode.OFF, types_1.QueueRepeatMode.QUEUE, types_1.QueueRepeatMode.TRACK, types_1.QueueRepeatMode.AUTOPLAY].includes(mode))
300
- throw new PlayerError_1.PlayerError(`Unknown repeat mode "${mode}"!`, PlayerError_1.ErrorStatusCode.UNKNOWN_REPEAT_MODE);
301
- if (mode === this.repeatMode)
302
- return false;
303
- this.repeatMode = mode;
304
- return true;
305
- }
306
- /**
307
- * The current volume amount
308
- * @type {number}
309
- */
310
- get volume() {
311
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
312
- return;
313
- if (!this.connection)
314
- return 100;
315
- return this.connection.volume;
316
- }
317
- set volume(amount) {
318
- this.setVolume(amount);
319
- }
320
- /**
321
- * The stream time of this queue
322
- * @type {number}
323
- */
324
- get streamTime() {
325
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
326
- return;
327
- if (!this.connection)
328
- return 0;
329
- const playbackTime = this._streamTime + this.connection.streamTime;
330
- const NC = this._activeFilters.includes("nightcore") ? 1.25 : null;
331
- const VW = this._activeFilters.includes("vaporwave") ? 0.8 : null;
332
- if (NC && VW)
333
- return playbackTime * (NC + VW);
334
- return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime;
335
- }
336
- set streamTime(time) {
337
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
338
- return;
339
- this.seek(time);
340
- }
341
- /**
342
- * Returns enabled filters
343
- * @returns {AudioFilters}
344
- */
345
- getFiltersEnabled() {
346
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
347
- return;
348
- return AudioFilters_1.default.names.filter((x) => this._activeFilters.includes(x));
349
- }
350
- /**
351
- * Returns disabled filters
352
- * @returns {AudioFilters}
353
- */
354
- getFiltersDisabled() {
355
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
356
- return;
357
- return AudioFilters_1.default.names.filter((x) => !this._activeFilters.includes(x));
358
- }
359
- /**
360
- * Sets filters
361
- * @param {QueueFilters} filters Queue filters
362
- * @returns {Promise<void>}
363
- */
364
- async setFilters(filters) {
365
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
366
- return;
367
- if (!filters || !Object.keys(filters).length) {
368
- // reset filters
369
- const streamTime = this.streamTime;
370
- this._activeFilters = [];
371
- return await this.play(this.current, {
372
- immediate: true,
373
- filtersUpdate: true,
374
- seek: streamTime,
375
- encoderArgs: []
376
- });
377
- }
378
- const _filters = []; // eslint-disable-line @typescript-eslint/no-explicit-any
379
- for (const filter in filters) {
380
- if (filters[filter] === true)
381
- _filters.push(filter);
382
- }
383
- if (this._activeFilters.join("") === _filters.join(""))
384
- return;
385
- const newFilters = AudioFilters_1.default.create(_filters).trim();
386
- const streamTime = this.streamTime;
387
- this._activeFilters = _filters;
388
- return await this.play(this.current, {
389
- immediate: true,
390
- filtersUpdate: true,
391
- seek: streamTime,
392
- encoderArgs: !_filters.length ? undefined : ["-af", newFilters]
393
- });
394
- }
395
- /**
396
- * Seeks to the given time
397
- * @param {number} position The position
398
- * @returns {boolean}
399
- */
400
- async seek(position) {
401
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
402
- return;
403
- if (!this.playing || !this.current)
404
- return false;
405
- if (position < 1)
406
- position = 0;
407
- if (position >= this.current.durationMS)
408
- return this.skip();
409
- await this.play(this.current, {
410
- immediate: true,
411
- filtersUpdate: true,
412
- seek: position
413
- });
414
- return true;
415
- }
416
- /**
417
- * Plays previous track
418
- * @returns {Promise<void>}
419
- */
420
- async back() {
421
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
422
- return;
423
- const prev = this.previousTracks[this.previousTracks.length - 2]; // because last item is the current track
424
- if (!prev)
425
- throw new PlayerError_1.PlayerError("Could not find previous track", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
426
- return await this.play(prev, { immediate: true });
427
- }
428
- /**
429
- * Clear this queue
430
- */
431
- clear() {
432
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
433
- return;
434
- this.tracks = [];
435
- this.previousTracks = [];
436
- }
437
- /**
438
- * Stops the player
439
- * @returns {void}
440
- */
441
- stop() {
442
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
443
- return;
444
- return this.destroy();
445
- }
446
- /**
447
- * Shuffles this queue
448
- * @returns {boolean}
449
- */
450
- shuffle() {
451
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
452
- return;
453
- if (!this.tracks.length || this.tracks.length < 3)
454
- return false;
455
- const currentTrack = this.tracks.shift();
456
- for (let i = this.tracks.length - 1; i > 0; i--) {
457
- const j = Math.floor(Math.random() * (i + 1));
458
- [this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]];
459
- }
460
- this.tracks.unshift(currentTrack);
461
- return true;
462
- }
463
- /**
464
- * Removes a track from the queue
465
- * @param {Track|string|number} track The track to remove
466
- * @returns {Track}
467
- */
468
- remove(track) {
469
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
470
- return;
471
- let trackFound = null;
472
- if (typeof track === "number") {
473
- trackFound = this.tracks[track];
474
- if (trackFound) {
475
- this.tracks = this.tracks.filter((t) => t.id !== trackFound.id);
476
- }
477
- }
478
- else {
479
- trackFound = this.tracks.find((s) => s.id === (track instanceof Track_1.default ? track.id : track));
480
- if (trackFound) {
481
- this.tracks = this.tracks.filter((s) => s.id !== trackFound.id);
482
- }
483
- }
484
- return trackFound;
485
- }
486
- /**
487
- * Returns the index of the specified track. If found, returns the track index else returns -1.
488
- * @param {number|Track|string} track The track
489
- * @returns {number}
490
- */
491
- getTrackPosition(track) {
492
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
493
- return;
494
- if (typeof track === "number")
495
- return this.tracks[track] != null ? track : -1;
496
- return this.tracks.findIndex((pred) => pred.id === (track instanceof Track_1.default ? track.id : track));
497
- }
498
- /**
499
- * Jumps to particular track
500
- * @param {Track|number} track The track
501
- * @returns {void}
502
- */
503
- jump(track) {
504
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
505
- return;
506
- const foundTrack = this.remove(track);
507
- if (!foundTrack)
508
- throw new PlayerError_1.PlayerError("Track not found", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
509
- this.tracks.splice(0, 0, foundTrack);
510
- return void this.skip();
511
- }
512
- /**
513
- * Jumps to particular track, removing other tracks on the way
514
- * @param {Track|number} track The track
515
- * @returns {void}
516
- */
517
- skipTo(track) {
518
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
519
- return;
520
- const trackIndex = this.getTrackPosition(track);
521
- const removedTrack = this.remove(track);
522
- if (!removedTrack)
523
- throw new PlayerError_1.PlayerError("Track not found", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
524
- this.tracks.splice(0, trackIndex, removedTrack);
525
- return void this.skip();
526
- }
527
- /**
528
- * Inserts the given track to specified index
529
- * @param {Track} track The track to insert
530
- * @param {number} [index=0] The index where this track should be
531
- */
532
- insert(track, index = 0) {
533
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
534
- return;
535
- if (!track || !(track instanceof Track_1.default))
536
- throw new PlayerError_1.PlayerError("track must be the instance of Track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
537
- if (typeof index !== "number" || index < 0 || !Number.isFinite(index))
538
- throw new PlayerError_1.PlayerError(`Invalid index "${index}"`, PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
539
- this.tracks.splice(index, 0, track);
540
- this.player.emit("trackAdd", this, track);
541
- }
542
- /**
543
- * @typedef {object} PlayerTimestamp
544
- * @property {string} current The current progress
545
- * @property {string} end The total time
546
- * @property {number} progress Progress in %
547
- */
548
- /**
549
- * Returns player stream timestamp
550
- * @returns {PlayerTimestamp}
551
- */
552
- getPlayerTimestamp() {
553
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
554
- return;
555
- const currentStreamTime = this.streamTime;
556
- const totalTime = this.current.durationMS;
557
- const currentTimecode = Util_1.Util.buildTimeCode(Util_1.Util.parseMS(currentStreamTime));
558
- const endTimecode = Util_1.Util.buildTimeCode(Util_1.Util.parseMS(totalTime));
559
- return {
560
- current: currentTimecode,
561
- end: endTimecode,
562
- progress: Math.round((currentStreamTime / totalTime) * 100)
563
- };
564
- }
565
- /**
566
- * Creates progress bar string
567
- * @param {PlayerProgressbarOptions} options The progress bar options
568
- * @returns {string}
569
- */
570
- createProgressBar(options = { timecodes: true }) {
571
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
572
- return;
573
- const length = typeof options.length === "number" ? (options.length <= 0 || options.length === Infinity ? 15 : options.length) : 15;
574
- const index = Math.round((this.streamTime / this.current.durationMS) * length);
575
- const indicator = typeof options.indicator === "string" && options.indicator.length > 0 ? options.indicator : "🔘";
576
- const line = typeof options.line === "string" && options.line.length > 0 ? options.line : "▬";
577
- if (index >= 1 && index <= length) {
578
- const bar = line.repeat(length - 1).split("");
579
- bar.splice(index, 0, indicator);
580
- if (options.timecodes) {
581
- const timestamp = this.getPlayerTimestamp();
582
- return `${timestamp.current} ┃ ${bar.join("")} ┃ ${timestamp.end}`;
583
- }
584
- else {
585
- return `${bar.join("")}`;
586
- }
587
- }
588
- else {
589
- if (options.timecodes) {
590
- const timestamp = this.getPlayerTimestamp();
591
- return `${timestamp.current} ┃ ${indicator}${line.repeat(length - 1)} ┃ ${timestamp.end}`;
592
- }
593
- else {
594
- return `${indicator}${line.repeat(length - 1)}`;
595
- }
596
- }
597
- }
598
- /**
599
- * Total duration
600
- * @type {Number}
601
- */
602
- get totalTime() {
603
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
604
- return;
605
- return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0;
606
- }
607
- /**
608
- * Play stream in a voice/stage channel
609
- * @param {Track} [src] The track to play (if empty, uses first track from the queue)
610
- * @param {PlayOptions} [options] The options
611
- * @returns {Promise<void>}
612
- */
613
- async play(src, options = {}) {
614
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
615
- return;
616
- if (!this.connection || !this.connection.voiceConnection)
617
- throw new PlayerError_1.PlayerError("Voice connection is not available, use <Queue>.connect()!", PlayerError_1.ErrorStatusCode.NO_CONNECTION);
618
- if (src && (this.playing || this.tracks.length) && !options.immediate)
619
- return this.addTrack(src);
620
- const track = options.filtersUpdate && !options.immediate ? src || this.current : src ?? this.tracks.shift();
621
- if (!track)
622
- return;
623
- this.player.emit("debug", this, "Received play request");
624
- if (!options.filtersUpdate) {
625
- this.previousTracks = this.previousTracks.filter((x) => x.id !== track.id);
626
- this.previousTracks.push(track);
627
- }
628
- let stream = null;
629
- const hasCustomDownloader = typeof this.onBeforeCreateStream === "function";
630
- if (["youtube", "spotify"].includes(track.raw.source)) {
631
- let spotifyResolved = false;
632
- if (this.options.spotifyBridge && track.raw.source === "spotify" && !track.raw.engine) {
633
- track.raw.engine = await youtube_sr_1.default.search(`${track.author} ${track.title}`, { type: "video" })
634
- .then((res) => res[0].url)
635
- .catch(() => null);
636
- spotifyResolved = true;
637
- }
638
- const url = track.raw.source === "spotify" ? track.raw.engine : track.url;
639
- if (!url)
640
- return void this.play(this.tracks.shift(), { immediate: true });
641
- if (hasCustomDownloader) {
642
- stream = (await this.onBeforeCreateStream(track, spotifyResolved ? "youtube" : track.raw.source, this)) || null;
643
- }
644
- if (!stream) {
645
- stream = (0, ytdl_core_1.default)(url, this.options.ytdlOptions);
646
- }
647
- }
648
- else {
649
- const arbitraryStream = (hasCustomDownloader && (await this.onBeforeCreateStream(track, track.raw.source || track.raw.engine, this))) || null;
650
- stream =
651
- arbitraryStream || track.raw.source === "soundcloud"
652
- ? await track.raw.engine.downloadProgressive()
653
- : typeof track.raw.engine === "function"
654
- ? await track.raw.engine
655
- : track.raw.engine;
656
- }
657
- const ffmpegStream = (0, FFmpegStream_1.createFFmpegStream)(stream, {
658
- encoderArgs: options.encoderArgs || this._activeFilters.length ? ["-af", AudioFilters_1.default.create(this._activeFilters)] : [],
659
- seek: options.seek ? options.seek / 1000 : 0,
660
- fmt: "s16le"
661
- }).on("error", (err) => {
662
- if (!`${err}`.toLowerCase().includes("premature close"))
663
- this.player.emit("error", this, err);
664
- });
665
- const resource = this.connection.createStream(ffmpegStream, {
666
- type: voice_1.StreamType.Raw,
667
- data: track,
668
- disableVolume: Boolean(this.options.disableVolume)
669
- });
670
- if (options.seek)
671
- this._streamTime = options.seek;
672
- this._filtersUpdate = options.filtersUpdate;
673
- const volumeTransformer = resource.volume;
674
- if (volumeTransformer && typeof this.options.initialVolume === "number")
675
- Reflect.set(volumeTransformer, "volume", Math.pow(this.options.initialVolume / 100, 1.660964));
676
- if (volumeTransformer?.hasSmoothness && typeof this.options.volumeSmoothness === "number") {
677
- if (typeof volumeTransformer.setSmoothness === "function")
678
- volumeTransformer.setSmoothness(this.options.volumeSmoothness || 0);
679
- }
680
- setTimeout(() => {
681
- this.connection.playStream(resource);
682
- }, tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_getBufferingTimeout).call(this)).unref();
683
- }
684
- /**
685
- * Private method to handle autoplay
686
- * @param {Track} track The source track to find its similar track for autoplay
687
- * @returns {Promise<void>}
688
- * @private
689
- */
690
- async _handleAutoplay(track) {
691
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
692
- return;
693
- if (!track || ![track.source, track.raw?.source].includes("youtube")) {
694
- if (this.options.leaveOnEnd)
695
- this.destroy();
696
- return void this.player.emit("queueEnd", this);
697
- }
698
- let info = await youtube_sr_1.default.getVideo(track.url)
699
- .then((x) => x.videos[0])
700
- .catch(Util_1.Util.noop);
701
- // fallback
702
- if (!info)
703
- info = await youtube_sr_1.default.search(track.author)
704
- .then((x) => x[0])
705
- .catch(Util_1.Util.noop);
706
- if (!info) {
707
- if (this.options.leaveOnEnd)
708
- this.destroy();
709
- return void this.player.emit("queueEnd", this);
710
- }
711
- const nextTrack = new Track_1.default(this.player, {
712
- title: info.title,
713
- url: `https://www.youtube.com/watch?v=${info.id}`,
714
- duration: info.durationFormatted ? Util_1.Util.buildTimeCode(Util_1.Util.parseMS(info.duration * 1000)) : "0:00",
715
- description: "",
716
- thumbnail: typeof info.thumbnail === "string" ? info.thumbnail : info.thumbnail.url,
717
- views: info.views,
718
- author: info.channel.name,
719
- requestedBy: track.requestedBy,
720
- source: "youtube"
721
- });
722
- this.play(nextTrack, { immediate: true });
723
- }
724
- *[(_Queue_lastVolume = new WeakMap(), _Queue_destroyed = new WeakMap(), _Queue_instances = new WeakSet(), Symbol.iterator)]() {
725
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
726
- return;
727
- yield* this.tracks;
728
- }
729
- /**
730
- * JSON representation of this queue
731
- * @returns {object}
732
- */
733
- toJSON() {
734
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
735
- return;
736
- return {
737
- id: this.id,
738
- guild: this.guild.id,
739
- voiceChannel: this.connection?.channel?.id,
740
- options: this.options,
741
- tracks: this.tracks.map((m) => m.toJSON())
742
- };
743
- }
744
- /**
745
- * String representation of this queue
746
- * @returns {string}
747
- */
748
- toString() {
749
- if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
750
- return;
751
- if (!this.tracks.length)
752
- return "No songs available to display!";
753
- return `**Upcoming Songs:**\n${this.tracks.map((m, i) => `${i + 1}. **${m.title}**`).join("\n")}`;
754
- }
755
- }
756
- exports.Queue = Queue;
757
- _Queue_watchDestroyed = function _Queue_watchDestroyed(emit = true) {
758
- if (tslib_1.__classPrivateFieldGet(this, _Queue_destroyed, "f")) {
759
- if (emit)
760
- this.player.emit("error", this, new PlayerError_1.PlayerError("Cannot use destroyed queue", PlayerError_1.ErrorStatusCode.DESTROYED_QUEUE));
761
- return true;
762
- }
763
- return false;
764
- }, _Queue_getBufferingTimeout = function _Queue_getBufferingTimeout() {
765
- const timeout = this.options.bufferingTimeout;
766
- if (isNaN(timeout) || timeout < 0 || !Number.isFinite(timeout))
767
- return 1000;
768
- return timeout;
769
- };
1
+ "use strict";
2
+ var _Queue_instances, _Queue_lastVolume, _Queue_destroyed, _Queue_watchDestroyed, _Queue_getBufferingTimeout;
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Queue = void 0;
5
+ const tslib_1 = require("tslib");
6
+ const discord_js_1 = require("discord.js");
7
+ const Track_1 = tslib_1.__importDefault(require("./Track"));
8
+ const types_1 = require("../types/types");
9
+ const ytdl_core_1 = tslib_1.__importDefault(require("ytdl-core"));
10
+ const voice_1 = require("@discordjs/voice");
11
+ const Util_1 = require("../utils/Util");
12
+ const youtube_sr_1 = tslib_1.__importDefault(require("youtube-sr"));
13
+ const AudioFilters_1 = tslib_1.__importDefault(require("../utils/AudioFilters"));
14
+ const PlayerError_1 = require("./PlayerError");
15
+ const FFmpegStream_1 = require("../utils/FFmpegStream");
16
+ class Queue {
17
+ /**
18
+ * Queue constructor
19
+ * @param {Player} player The player that instantiated this queue
20
+ * @param {Guild} guild The guild that instantiated this queue
21
+ * @param {PlayerOptions} [options] Player options for the queue
22
+ */
23
+ constructor(player, guild, options = {}) {
24
+ _Queue_instances.add(this);
25
+ this.tracks = [];
26
+ this.previousTracks = [];
27
+ this.playing = false;
28
+ this.metadata = null;
29
+ this.repeatMode = 0;
30
+ this.id = discord_js_1.SnowflakeUtil.generate().toString();
31
+ this._streamTime = 0;
32
+ this._cooldownsTimeout = new discord_js_1.Collection();
33
+ this._activeFilters = []; // eslint-disable-line @typescript-eslint/no-explicit-any
34
+ this._filtersUpdate = false;
35
+ _Queue_lastVolume.set(this, 0);
36
+ _Queue_destroyed.set(this, false);
37
+ this.onBeforeCreateStream = null;
38
+ /**
39
+ * The player that instantiated this queue
40
+ * @type {Player}
41
+ * @readonly
42
+ */
43
+ this.player = player;
44
+ /**
45
+ * The guild that instantiated this queue
46
+ * @type {Guild}
47
+ * @readonly
48
+ */
49
+ this.guild = guild;
50
+ /**
51
+ * The player options for this queue
52
+ * @type {PlayerOptions}
53
+ */
54
+ this.options = {};
55
+ /**
56
+ * Queue repeat mode
57
+ * @type {QueueRepeatMode}
58
+ * @name Queue#repeatMode
59
+ */
60
+ /**
61
+ * Queue metadata
62
+ * @type {any}
63
+ * @name Queue#metadata
64
+ */
65
+ /**
66
+ * Previous tracks
67
+ * @type {Track[]}
68
+ * @name Queue#previousTracks
69
+ */
70
+ /**
71
+ * Regular tracks
72
+ * @type {Track[]}
73
+ * @name Queue#tracks
74
+ */
75
+ /**
76
+ * The connection
77
+ * @type {StreamDispatcher}
78
+ * @name Queue#connection
79
+ */
80
+ /**
81
+ * The ID of this queue
82
+ * @type {Snowflake}
83
+ * @name Queue#id
84
+ */
85
+ Object.assign(this.options, {
86
+ leaveOnEnd: true,
87
+ leaveOnStop: true,
88
+ leaveOnEmpty: true,
89
+ leaveOnEmptyCooldown: 1000,
90
+ autoSelfDeaf: true,
91
+ ytdlOptions: {
92
+ highWaterMark: 1 << 25
93
+ },
94
+ initialVolume: 100,
95
+ bufferingTimeout: 3000,
96
+ spotifyBridge: true,
97
+ disableVolume: false
98
+ }, options);
99
+ if ("onBeforeCreateStream" in this.options)
100
+ this.onBeforeCreateStream = this.options.onBeforeCreateStream;
101
+ this.player.emit("debug", this, `Queue initialized:\n\n${this.player.scanDeps()}`);
102
+ }
103
+ /**
104
+ * Returns current track
105
+ * @type {Track}
106
+ */
107
+ get current() {
108
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
109
+ return;
110
+ return this.connection.audioResource?.metadata ?? this.tracks[0];
111
+ }
112
+ /**
113
+ * If this queue is destroyed
114
+ * @type {boolean}
115
+ */
116
+ get destroyed() {
117
+ return tslib_1.__classPrivateFieldGet(this, _Queue_destroyed, "f");
118
+ }
119
+ /**
120
+ * Returns current track
121
+ * @returns {Track}
122
+ */
123
+ nowPlaying() {
124
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
125
+ return;
126
+ return this.current;
127
+ }
128
+ /**
129
+ * Connects to a voice channel
130
+ * @param {GuildChannelResolvable} channel The voice/stage channel
131
+ * @returns {Promise<Queue>}
132
+ */
133
+ async connect(channel) {
134
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
135
+ return;
136
+ const _channel = this.guild.channels.resolve(channel);
137
+ if (![discord_js_1.ChannelType.GuildStageVoice, discord_js_1.ChannelType.GuildVoice].includes(_channel?.type))
138
+ throw new PlayerError_1.PlayerError(`Channel type must be GuildVoice or GuildStageVoice, got ${_channel?.type}!`, PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
139
+ const connection = await this.player.voiceUtils.connect(_channel, {
140
+ deaf: this.options.autoSelfDeaf
141
+ });
142
+ this.connection = connection;
143
+ if (_channel.type === discord_js_1.ChannelType.GuildStageVoice) {
144
+ await _channel.guild.members.me.voice.setSuppressed(false).catch(async () => {
145
+ return await _channel.guild.members.me.voice.setRequestToSpeak(true).catch(Util_1.Util.noop);
146
+ });
147
+ }
148
+ this.connection.on("error", (err) => {
149
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
150
+ return;
151
+ this.player.emit("connectionError", this, err);
152
+ });
153
+ this.connection.on("debug", (msg) => {
154
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
155
+ return;
156
+ this.player.emit("debug", this, msg);
157
+ });
158
+ this.player.emit("connectionCreate", this, this.connection);
159
+ this.connection.on("start", (resource) => {
160
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
161
+ return;
162
+ this.playing = true;
163
+ if (!this._filtersUpdate)
164
+ this.player.emit("trackStart", this, resource?.metadata ?? this.current);
165
+ this._filtersUpdate = false;
166
+ });
167
+ this.connection.on("finish", async (resource) => {
168
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
169
+ return;
170
+ this.playing = false;
171
+ if (this._filtersUpdate)
172
+ return;
173
+ this._streamTime = 0;
174
+ if (resource?.metadata)
175
+ this.previousTracks.push(resource.metadata);
176
+ this.player.emit("trackEnd", this, resource.metadata);
177
+ if (!this.tracks.length && this.repeatMode === types_1.QueueRepeatMode.OFF) {
178
+ if (this.options.leaveOnEnd)
179
+ this.destroy();
180
+ this.player.emit("queueEnd", this);
181
+ }
182
+ else if (!this.tracks.length && this.repeatMode === types_1.QueueRepeatMode.AUTOPLAY) {
183
+ this._handleAutoplay(Util_1.Util.last(this.previousTracks));
184
+ }
185
+ else {
186
+ if (this.repeatMode === types_1.QueueRepeatMode.TRACK)
187
+ return void this.play(Util_1.Util.last(this.previousTracks), { immediate: true });
188
+ if (this.repeatMode === types_1.QueueRepeatMode.QUEUE)
189
+ this.tracks.push(Util_1.Util.last(this.previousTracks));
190
+ const nextTrack = this.tracks.shift();
191
+ this.play(nextTrack, { immediate: true });
192
+ return;
193
+ }
194
+ });
195
+ return this;
196
+ }
197
+ /**
198
+ * Destroys this queue
199
+ * @param {boolean} [disconnect=this.options.leaveOnStop] If it should leave on destroy
200
+ * @returns {void}
201
+ */
202
+ destroy(disconnect = this.options.leaveOnStop) {
203
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
204
+ return;
205
+ if (this.connection)
206
+ this.connection.end();
207
+ if (disconnect)
208
+ this.connection?.disconnect();
209
+ this.player.queues.delete(this.guild.id);
210
+ this.player.voiceUtils.cache.delete(this.guild.id);
211
+ tslib_1.__classPrivateFieldSet(this, _Queue_destroyed, true, "f");
212
+ }
213
+ /**
214
+ * Skips current track
215
+ * @returns {boolean}
216
+ */
217
+ skip() {
218
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
219
+ return;
220
+ if (!this.connection)
221
+ return false;
222
+ this._filtersUpdate = false;
223
+ this.connection.end();
224
+ return true;
225
+ }
226
+ /**
227
+ * Adds single track to the queue
228
+ * @param {Track} track The track to add
229
+ * @returns {void}
230
+ */
231
+ addTrack(track) {
232
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
233
+ return;
234
+ if (!(track instanceof Track_1.default))
235
+ throw new PlayerError_1.PlayerError("invalid track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
236
+ this.tracks.push(track);
237
+ this.player.emit("trackAdd", this, track);
238
+ }
239
+ /**
240
+ * Adds multiple tracks to the queue
241
+ * @param {Track[]} tracks Array of tracks to add
242
+ */
243
+ addTracks(tracks) {
244
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
245
+ return;
246
+ if (!tracks.every((y) => y instanceof Track_1.default))
247
+ throw new PlayerError_1.PlayerError("invalid track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
248
+ this.tracks.push(...tracks);
249
+ this.player.emit("tracksAdd", this, tracks);
250
+ }
251
+ /**
252
+ * Sets paused state
253
+ * @param {boolean} paused The paused state
254
+ * @returns {boolean}
255
+ */
256
+ setPaused(paused) {
257
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
258
+ return;
259
+ if (!this.connection)
260
+ return false;
261
+ return paused ? this.connection.pause(true) : this.connection.resume();
262
+ }
263
+ /**
264
+ * Sets bitrate
265
+ * @param {number|auto} bitrate bitrate to set
266
+ * @returns {void}
267
+ */
268
+ setBitrate(bitrate) {
269
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
270
+ return;
271
+ if (!this.connection?.audioResource?.encoder)
272
+ return;
273
+ if (bitrate === "auto")
274
+ bitrate = this.connection.channel?.bitrate ?? 64000;
275
+ this.connection.audioResource.encoder.setBitrate(bitrate);
276
+ }
277
+ /**
278
+ * Sets volume
279
+ * @param {number} amount The volume amount
280
+ * @returns {boolean}
281
+ */
282
+ setVolume(amount) {
283
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
284
+ return;
285
+ if (!this.connection)
286
+ return false;
287
+ tslib_1.__classPrivateFieldSet(this, _Queue_lastVolume, amount, "f");
288
+ this.options.initialVolume = amount;
289
+ return this.connection.setVolume(amount);
290
+ }
291
+ /**
292
+ * Sets repeat mode
293
+ * @param {QueueRepeatMode} mode The repeat mode
294
+ * @returns {boolean}
295
+ */
296
+ setRepeatMode(mode) {
297
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
298
+ return;
299
+ if (![types_1.QueueRepeatMode.OFF, types_1.QueueRepeatMode.QUEUE, types_1.QueueRepeatMode.TRACK, types_1.QueueRepeatMode.AUTOPLAY].includes(mode))
300
+ throw new PlayerError_1.PlayerError(`Unknown repeat mode "${mode}"!`, PlayerError_1.ErrorStatusCode.UNKNOWN_REPEAT_MODE);
301
+ if (mode === this.repeatMode)
302
+ return false;
303
+ this.repeatMode = mode;
304
+ return true;
305
+ }
306
+ /**
307
+ * The current volume amount
308
+ * @type {number}
309
+ */
310
+ get volume() {
311
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
312
+ return;
313
+ if (!this.connection)
314
+ return 100;
315
+ return this.connection.volume;
316
+ }
317
+ set volume(amount) {
318
+ this.setVolume(amount);
319
+ }
320
+ /**
321
+ * The stream time of this queue
322
+ * @type {number}
323
+ */
324
+ get streamTime() {
325
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
326
+ return;
327
+ if (!this.connection)
328
+ return 0;
329
+ const playbackTime = this._streamTime + this.connection.streamTime;
330
+ const NC = this._activeFilters.includes("nightcore") ? 1.25 : null;
331
+ const VW = this._activeFilters.includes("vaporwave") ? 0.8 : null;
332
+ if (NC && VW)
333
+ return playbackTime * (NC + VW);
334
+ return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime;
335
+ }
336
+ set streamTime(time) {
337
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
338
+ return;
339
+ this.seek(time);
340
+ }
341
+ /**
342
+ * Returns enabled filters
343
+ * @returns {AudioFilters}
344
+ */
345
+ getFiltersEnabled() {
346
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
347
+ return;
348
+ return AudioFilters_1.default.names.filter((x) => this._activeFilters.includes(x));
349
+ }
350
+ /**
351
+ * Returns disabled filters
352
+ * @returns {AudioFilters}
353
+ */
354
+ getFiltersDisabled() {
355
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
356
+ return;
357
+ return AudioFilters_1.default.names.filter((x) => !this._activeFilters.includes(x));
358
+ }
359
+ /**
360
+ * Sets filters
361
+ * @param {QueueFilters} filters Queue filters
362
+ * @returns {Promise<void>}
363
+ */
364
+ async setFilters(filters) {
365
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
366
+ return;
367
+ if (!filters || !Object.keys(filters).length) {
368
+ // reset filters
369
+ const streamTime = this.streamTime;
370
+ this._activeFilters = [];
371
+ return await this.play(this.current, {
372
+ immediate: true,
373
+ filtersUpdate: true,
374
+ seek: streamTime,
375
+ encoderArgs: []
376
+ });
377
+ }
378
+ const _filters = []; // eslint-disable-line @typescript-eslint/no-explicit-any
379
+ for (const filter in filters) {
380
+ if (filters[filter] === true)
381
+ _filters.push(filter);
382
+ }
383
+ if (this._activeFilters.join("") === _filters.join(""))
384
+ return;
385
+ const newFilters = AudioFilters_1.default.create(_filters).trim();
386
+ const streamTime = this.streamTime;
387
+ this._activeFilters = _filters;
388
+ return await this.play(this.current, {
389
+ immediate: true,
390
+ filtersUpdate: true,
391
+ seek: streamTime,
392
+ encoderArgs: !_filters.length ? undefined : ["-af", newFilters]
393
+ });
394
+ }
395
+ /**
396
+ * Seeks to the given time
397
+ * @param {number} position The position
398
+ * @returns {boolean}
399
+ */
400
+ async seek(position) {
401
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
402
+ return;
403
+ if (!this.playing || !this.current)
404
+ return false;
405
+ if (position < 1)
406
+ position = 0;
407
+ if (position >= this.current.durationMS)
408
+ return this.skip();
409
+ await this.play(this.current, {
410
+ immediate: true,
411
+ filtersUpdate: true,
412
+ seek: position
413
+ });
414
+ return true;
415
+ }
416
+ /**
417
+ * Plays previous track
418
+ * @returns {Promise<void>}
419
+ */
420
+ async back() {
421
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
422
+ return;
423
+ const prev = this.previousTracks[this.previousTracks.length - 2]; // because last item is the current track
424
+ if (!prev)
425
+ throw new PlayerError_1.PlayerError("Could not find previous track", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
426
+ return await this.play(prev, { immediate: true });
427
+ }
428
+ /**
429
+ * Clear this queue
430
+ */
431
+ clear() {
432
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
433
+ return;
434
+ this.tracks = [];
435
+ this.previousTracks = [];
436
+ }
437
+ /**
438
+ * Stops the player
439
+ * @returns {void}
440
+ */
441
+ stop() {
442
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
443
+ return;
444
+ return this.destroy();
445
+ }
446
+ /**
447
+ * Shuffles this queue
448
+ * @returns {boolean}
449
+ */
450
+ shuffle() {
451
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
452
+ return;
453
+ if (!this.tracks.length || this.tracks.length < 3)
454
+ return false;
455
+ const currentTrack = this.tracks.shift();
456
+ for (let i = this.tracks.length - 1; i > 0; i--) {
457
+ const j = Math.floor(Math.random() * (i + 1));
458
+ [this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]];
459
+ }
460
+ this.tracks.unshift(currentTrack);
461
+ return true;
462
+ }
463
+ /**
464
+ * Removes a track from the queue
465
+ * @param {Track|string|number} track The track to remove
466
+ * @returns {Track}
467
+ */
468
+ remove(track) {
469
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
470
+ return;
471
+ let trackFound = null;
472
+ if (typeof track === "number") {
473
+ trackFound = this.tracks[track];
474
+ if (trackFound) {
475
+ this.tracks = this.tracks.filter((t) => t.id !== trackFound.id);
476
+ }
477
+ }
478
+ else {
479
+ trackFound = this.tracks.find((s) => s.id === (track instanceof Track_1.default ? track.id : track));
480
+ if (trackFound) {
481
+ this.tracks = this.tracks.filter((s) => s.id !== trackFound.id);
482
+ }
483
+ }
484
+ return trackFound;
485
+ }
486
+ /**
487
+ * Returns the index of the specified track. If found, returns the track index else returns -1.
488
+ * @param {number|Track|string} track The track
489
+ * @returns {number}
490
+ */
491
+ getTrackPosition(track) {
492
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
493
+ return;
494
+ if (typeof track === "number")
495
+ return this.tracks[track] != null ? track : -1;
496
+ return this.tracks.findIndex((pred) => pred.id === (track instanceof Track_1.default ? track.id : track));
497
+ }
498
+ /**
499
+ * Jumps to particular track
500
+ * @param {Track|number} track The track
501
+ * @returns {void}
502
+ */
503
+ jump(track) {
504
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
505
+ return;
506
+ const foundTrack = this.remove(track);
507
+ if (!foundTrack)
508
+ throw new PlayerError_1.PlayerError("Track not found", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
509
+ this.tracks.splice(0, 0, foundTrack);
510
+ return void this.skip();
511
+ }
512
+ /**
513
+ * Jumps to particular track, removing other tracks on the way
514
+ * @param {Track|number} track The track
515
+ * @returns {void}
516
+ */
517
+ skipTo(track) {
518
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
519
+ return;
520
+ const trackIndex = this.getTrackPosition(track);
521
+ const removedTrack = this.remove(track);
522
+ if (!removedTrack)
523
+ throw new PlayerError_1.PlayerError("Track not found", PlayerError_1.ErrorStatusCode.TRACK_NOT_FOUND);
524
+ this.tracks.splice(0, trackIndex, removedTrack);
525
+ return void this.skip();
526
+ }
527
+ /**
528
+ * Inserts the given track to specified index
529
+ * @param {Track} track The track to insert
530
+ * @param {number} [index=0] The index where this track should be
531
+ */
532
+ insert(track, index = 0) {
533
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
534
+ return;
535
+ if (!track || !(track instanceof Track_1.default))
536
+ throw new PlayerError_1.PlayerError("track must be the instance of Track", PlayerError_1.ErrorStatusCode.INVALID_TRACK);
537
+ if (typeof index !== "number" || index < 0 || !Number.isFinite(index))
538
+ throw new PlayerError_1.PlayerError(`Invalid index "${index}"`, PlayerError_1.ErrorStatusCode.INVALID_ARG_TYPE);
539
+ this.tracks.splice(index, 0, track);
540
+ this.player.emit("trackAdd", this, track);
541
+ }
542
+ /**
543
+ * @typedef {object} PlayerTimestamp
544
+ * @property {string} current The current progress
545
+ * @property {string} end The total time
546
+ * @property {number} progress Progress in %
547
+ */
548
+ /**
549
+ * Returns player stream timestamp
550
+ * @returns {PlayerTimestamp}
551
+ */
552
+ getPlayerTimestamp() {
553
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
554
+ return;
555
+ const currentStreamTime = this.streamTime;
556
+ const totalTime = this.current.durationMS;
557
+ const currentTimecode = Util_1.Util.buildTimeCode(Util_1.Util.parseMS(currentStreamTime));
558
+ const endTimecode = Util_1.Util.buildTimeCode(Util_1.Util.parseMS(totalTime));
559
+ return {
560
+ current: currentTimecode,
561
+ end: endTimecode,
562
+ progress: Math.round((currentStreamTime / totalTime) * 100)
563
+ };
564
+ }
565
+ /**
566
+ * Creates progress bar string
567
+ * @param {PlayerProgressbarOptions} options The progress bar options
568
+ * @returns {string}
569
+ */
570
+ createProgressBar(options = { timecodes: true }) {
571
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
572
+ return;
573
+ const length = typeof options.length === "number" ? (options.length <= 0 || options.length === Infinity ? 15 : options.length) : 15;
574
+ const index = Math.round((this.streamTime / this.current.durationMS) * length);
575
+ const indicator = typeof options.indicator === "string" && options.indicator.length > 0 ? options.indicator : "🔘";
576
+ const line = typeof options.line === "string" && options.line.length > 0 ? options.line : "▬";
577
+ if (index >= 1 && index <= length) {
578
+ const bar = line.repeat(length - 1).split("");
579
+ bar.splice(index, 0, indicator);
580
+ if (options.timecodes) {
581
+ const timestamp = this.getPlayerTimestamp();
582
+ return `${timestamp.current} ┃ ${bar.join("")} ┃ ${timestamp.end}`;
583
+ }
584
+ else {
585
+ return `${bar.join("")}`;
586
+ }
587
+ }
588
+ else {
589
+ if (options.timecodes) {
590
+ const timestamp = this.getPlayerTimestamp();
591
+ return `${timestamp.current} ┃ ${indicator}${line.repeat(length - 1)} ┃ ${timestamp.end}`;
592
+ }
593
+ else {
594
+ return `${indicator}${line.repeat(length - 1)}`;
595
+ }
596
+ }
597
+ }
598
+ /**
599
+ * Total duration
600
+ * @type {Number}
601
+ */
602
+ get totalTime() {
603
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
604
+ return;
605
+ return this.tracks.length > 0 ? this.tracks.map((t) => t.durationMS).reduce((p, c) => p + c) : 0;
606
+ }
607
+ /**
608
+ * Play stream in a voice/stage channel
609
+ * @param {Track} [src] The track to play (if empty, uses first track from the queue)
610
+ * @param {PlayOptions} [options] The options
611
+ * @returns {Promise<void>}
612
+ */
613
+ async play(src, options = {}) {
614
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this, false))
615
+ return;
616
+ if (!this.connection || !this.connection.voiceConnection)
617
+ throw new PlayerError_1.PlayerError("Voice connection is not available, use <Queue>.connect()!", PlayerError_1.ErrorStatusCode.NO_CONNECTION);
618
+ if (src && (this.playing || this.tracks.length) && !options.immediate)
619
+ return this.addTrack(src);
620
+ const track = options.filtersUpdate && !options.immediate ? src || this.current : src ?? this.tracks.shift();
621
+ if (!track)
622
+ return;
623
+ this.player.emit("debug", this, "Received play request");
624
+ if (!options.filtersUpdate) {
625
+ this.previousTracks = this.previousTracks.filter((x) => x.id !== track.id);
626
+ this.previousTracks.push(track);
627
+ }
628
+ let stream = null;
629
+ const hasCustomDownloader = typeof this.onBeforeCreateStream === "function";
630
+ if (["youtube", "spotify"].includes(track.raw.source)) {
631
+ let spotifyResolved = false;
632
+ if (this.options.spotifyBridge && track.raw.source === "spotify" && !track.raw.engine) {
633
+ track.raw.engine = await youtube_sr_1.default.search(`${track.author} ${track.title}`, { type: "video" })
634
+ .then((res) => res[0].url)
635
+ .catch(() => null);
636
+ spotifyResolved = true;
637
+ }
638
+ const url = track.raw.source === "spotify" ? track.raw.engine : track.url;
639
+ if (!url)
640
+ return void this.play(this.tracks.shift(), { immediate: true });
641
+ if (hasCustomDownloader) {
642
+ stream = (await this.onBeforeCreateStream(track, spotifyResolved ? "youtube" : track.raw.source, this)) || null;
643
+ }
644
+ if (!stream) {
645
+ stream = (0, ytdl_core_1.default)(url, this.options.ytdlOptions);
646
+ }
647
+ }
648
+ else {
649
+ const arbitraryStream = (hasCustomDownloader && (await this.onBeforeCreateStream(track, track.raw.source || track.raw.engine, this))) || null;
650
+ stream =
651
+ arbitraryStream || (track.raw.source === "soundcloud" && typeof track.raw.engine?.downloadProgressive === "function")
652
+ ? await track.raw.engine.downloadProgressive()
653
+ : typeof track.raw.engine === "function"
654
+ ? await track.raw.engine()
655
+ : track.raw.engine;
656
+ }
657
+ const ffmpegStream = (0, FFmpegStream_1.createFFmpegStream)(stream, {
658
+ encoderArgs: options.encoderArgs || this._activeFilters.length ? ["-af", AudioFilters_1.default.create(this._activeFilters)] : [],
659
+ seek: options.seek ? options.seek / 1000 : 0,
660
+ fmt: "s16le"
661
+ }).on("error", (err) => {
662
+ if (!`${err}`.toLowerCase().includes("premature close"))
663
+ this.player.emit("error", this, err);
664
+ });
665
+ const resource = this.connection.createStream(ffmpegStream, {
666
+ type: voice_1.StreamType.Raw,
667
+ data: track,
668
+ disableVolume: Boolean(this.options.disableVolume)
669
+ });
670
+ if (options.seek)
671
+ this._streamTime = options.seek;
672
+ this._filtersUpdate = options.filtersUpdate;
673
+ const volumeTransformer = resource.volume;
674
+ if (volumeTransformer && typeof this.options.initialVolume === "number")
675
+ Reflect.set(volumeTransformer, "volume", Math.pow(this.options.initialVolume / 100, 1.660964));
676
+ if (volumeTransformer?.hasSmoothness && typeof this.options.volumeSmoothness === "number") {
677
+ if (typeof volumeTransformer.setSmoothness === "function")
678
+ volumeTransformer.setSmoothness(this.options.volumeSmoothness || 0);
679
+ }
680
+ setTimeout(() => {
681
+ this.connection.playStream(resource);
682
+ }, tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_getBufferingTimeout).call(this)).unref();
683
+ }
684
+ /**
685
+ * Private method to handle autoplay
686
+ * @param {Track} track The source track to find its similar track for autoplay
687
+ * @returns {Promise<void>}
688
+ * @private
689
+ */
690
+ async _handleAutoplay(track) {
691
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
692
+ return;
693
+ if (!track || ![track.source, track.raw?.source].includes("youtube")) {
694
+ if (this.options.leaveOnEnd)
695
+ this.destroy();
696
+ return void this.player.emit("queueEnd", this);
697
+ }
698
+ let info = await youtube_sr_1.default.getVideo(track.url)
699
+ .then((x) => x.videos[0])
700
+ .catch(Util_1.Util.noop);
701
+ // fallback
702
+ if (!info)
703
+ info = await youtube_sr_1.default.search(track.author)
704
+ .then((x) => x[0])
705
+ .catch(Util_1.Util.noop);
706
+ if (!info) {
707
+ if (this.options.leaveOnEnd)
708
+ this.destroy();
709
+ return void this.player.emit("queueEnd", this);
710
+ }
711
+ const nextTrack = new Track_1.default(this.player, {
712
+ title: info.title,
713
+ url: `https://www.youtube.com/watch?v=${info.id}`,
714
+ duration: info.durationFormatted ? Util_1.Util.buildTimeCode(Util_1.Util.parseMS(info.duration * 1000)) : "0:00",
715
+ description: "",
716
+ thumbnail: typeof info.thumbnail === "string" ? info.thumbnail : info.thumbnail.url,
717
+ views: info.views,
718
+ author: info.channel.name,
719
+ requestedBy: track.requestedBy,
720
+ source: "youtube"
721
+ });
722
+ this.play(nextTrack, { immediate: true });
723
+ }
724
+ *[(_Queue_lastVolume = new WeakMap(), _Queue_destroyed = new WeakMap(), _Queue_instances = new WeakSet(), Symbol.iterator)]() {
725
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
726
+ return;
727
+ yield* this.tracks;
728
+ }
729
+ /**
730
+ * JSON representation of this queue
731
+ * @returns {object}
732
+ */
733
+ toJSON() {
734
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
735
+ return;
736
+ return {
737
+ id: this.id,
738
+ guild: this.guild.id,
739
+ voiceChannel: this.connection?.channel?.id,
740
+ options: this.options,
741
+ tracks: this.tracks.map((m) => m.toJSON())
742
+ };
743
+ }
744
+ /**
745
+ * String representation of this queue
746
+ * @returns {string}
747
+ */
748
+ toString() {
749
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_instances, "m", _Queue_watchDestroyed).call(this))
750
+ return;
751
+ if (!this.tracks.length)
752
+ return "No songs available to display!";
753
+ return `**Upcoming Songs:**\n${this.tracks.map((m, i) => `${i + 1}. **${m.title}**`).join("\n")}`;
754
+ }
755
+ }
756
+ exports.Queue = Queue;
757
+ _Queue_watchDestroyed = function _Queue_watchDestroyed(emit = true) {
758
+ if (tslib_1.__classPrivateFieldGet(this, _Queue_destroyed, "f")) {
759
+ if (emit)
760
+ this.player.emit("error", this, new PlayerError_1.PlayerError("Cannot use destroyed queue", PlayerError_1.ErrorStatusCode.DESTROYED_QUEUE));
761
+ return true;
762
+ }
763
+ return false;
764
+ }, _Queue_getBufferingTimeout = function _Queue_getBufferingTimeout() {
765
+ const timeout = this.options.bufferingTimeout;
766
+ if (isNaN(timeout) || timeout < 0 || !Number.isFinite(timeout))
767
+ return 1000;
768
+ return timeout;
769
+ };