discord-player 5.3.2-dev.2 → 5.3.2

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