kazagumo-bun 3.4.0-b

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.
Files changed (73) hide show
  1. package/.gitattributes +2 -0
  2. package/.prettierrc +5 -0
  3. package/LICENSE +21 -0
  4. package/LICENSE.md +22 -0
  5. package/README.md +267 -0
  6. package/SECURITY.md +21 -0
  7. package/appveyor.yml +24 -0
  8. package/docs/.nojekyll +1 -0
  9. package/docs/assets/hierarchy.js +1 -0
  10. package/docs/assets/highlight.css +99 -0
  11. package/docs/assets/icons.js +18 -0
  12. package/docs/assets/icons.svg +1 -0
  13. package/docs/assets/main.js +60 -0
  14. package/docs/assets/navigation.js +1 -0
  15. package/docs/assets/search.js +1 -0
  16. package/docs/assets/style.css +1633 -0
  17. package/docs/classes/Kazagumo.Kazagumo.html +307 -0
  18. package/docs/classes/Managers_KazagumoPlayer.KazagumoPlayer.html +81 -0
  19. package/docs/classes/Managers_Supports_KazagumoQueue.KazagumoQueue.html +222 -0
  20. package/docs/classes/Managers_Supports_KazagumoTrack.KazagumoTrack.html +43 -0
  21. package/docs/classes/Modules_Interfaces.KazagumoError.html +33 -0
  22. package/docs/classes/Modules_Interfaces.KazagumoPlugin.html +4 -0
  23. package/docs/classes/Modules_Utils.KazagumoUtils.html +3 -0
  24. package/docs/classes/Plugins_PlayerMoved.KazagumoPlugin.html +13 -0
  25. package/docs/enums/Modules_Interfaces.PlayerState.html +7 -0
  26. package/docs/functions/Modules_Interfaces.escapeRegExp.html +1 -0
  27. package/docs/hierarchy.html +1 -0
  28. package/docs/index.html +208 -0
  29. package/docs/interfaces/Kazagumo.KazagumoEvents.html +40 -0
  30. package/docs/interfaces/Modules_Interfaces.CreatePlayerOptions.html +21 -0
  31. package/docs/interfaces/Modules_Interfaces.KazagumoOptions.html +17 -0
  32. package/docs/interfaces/Modules_Interfaces.KazagumoPlayerOptions.html +9 -0
  33. package/docs/interfaces/Modules_Interfaces.KazagumoSearchOptions.html +5 -0
  34. package/docs/interfaces/Modules_Interfaces.KazagumoSearchResult.html +4 -0
  35. package/docs/interfaces/Modules_Interfaces.Payload.html +4 -0
  36. package/docs/interfaces/Modules_Interfaces.PlayOptions.html +6 -0
  37. package/docs/interfaces/Modules_Interfaces.PlayerMovedChannels.html +3 -0
  38. package/docs/interfaces/Modules_Interfaces.RawTrack.html +4 -0
  39. package/docs/interfaces/Modules_Interfaces.ResolveOptions.html +4 -0
  40. package/docs/modules/Index.html +1 -0
  41. package/docs/modules/Kazagumo.html +1 -0
  42. package/docs/modules/Managers_KazagumoPlayer.html +1 -0
  43. package/docs/modules/Managers_Supports_KazagumoQueue.html +1 -0
  44. package/docs/modules/Managers_Supports_KazagumoTrack.html +1 -0
  45. package/docs/modules/Modules_Interfaces.html +1 -0
  46. package/docs/modules/Modules_Plugins.html +1 -0
  47. package/docs/modules/Modules_Utils.html +1 -0
  48. package/docs/modules/Plugins_PlayerMoved.html +1 -0
  49. package/docs/modules.html +1 -0
  50. package/docs/types/Modules_Interfaces.PlayerMovedState.html +1 -0
  51. package/docs/types/Modules_Interfaces.SearchEngines.html +1 -0
  52. package/docs/types/Modules_Interfaces.SearchResultTypes.html +1 -0
  53. package/docs/types/Modules_Interfaces.YoutubeThumbnail.html +1 -0
  54. package/docs/types/Modules_Utils.Constructor.html +1 -0
  55. package/docs/variables/Index.version.html +1 -0
  56. package/docs/variables/Modules_Interfaces.Events.html +1 -0
  57. package/docs/variables/Modules_Interfaces.SourceIDs.html +1 -0
  58. package/docs/variables/Modules_Interfaces.SupportedSources.html +1 -0
  59. package/docs/variables/Modules_Plugins.default.html +1 -0
  60. package/eslint.config.mjs +15 -0
  61. package/package.json +26 -0
  62. package/src/Index.ts +14 -0
  63. package/src/Kazagumo.ts +396 -0
  64. package/src/Managers/KazagumoPlayer.ts +470 -0
  65. package/src/Managers/Supports/KazagumoQueue.ts +89 -0
  66. package/src/Managers/Supports/KazagumoTrack.ts +211 -0
  67. package/src/Modules/EventEmitter.ts +53 -0
  68. package/src/Modules/Interfaces.ts +204 -0
  69. package/src/Modules/Plugins.ts +5 -0
  70. package/src/Modules/Utils.ts +26 -0
  71. package/src/Plugins/PlayerMoved.ts +54 -0
  72. package/tsconfig.json +23 -0
  73. package/tslint.json +3 -0
@@ -0,0 +1,470 @@
1
+ import { Kazagumo } from '../Kazagumo';
2
+ import { KazagumoQueue } from './Supports/KazagumoQueue';
3
+ import {
4
+ FilterOptions,
5
+ Node,
6
+ Player,
7
+ PlayerUpdate,
8
+ TrackExceptionEvent,
9
+ TrackStuckEvent,
10
+ WebSocketClosedEvent,
11
+ } from 'shoukaku';
12
+ import {
13
+ Events,
14
+ KazagumoError,
15
+ KazagumoPlayerOptions,
16
+ KazagumoSearchOptions,
17
+ KazagumoSearchResult,
18
+ PlayerState,
19
+ PlayOptions,
20
+ } from '../Modules/Interfaces';
21
+ import { KazagumoTrack } from './Supports/KazagumoTrack';
22
+
23
+ export class KazagumoPlayer {
24
+ /**
25
+ * Kazagumo options
26
+ */
27
+ private options: KazagumoPlayerOptions;
28
+ /**
29
+ * Kazagumo Instance
30
+ */
31
+ public readonly kazagumo: Kazagumo;
32
+ /**
33
+ * Shoukaku's Player instance
34
+ */
35
+ public shoukaku: Player;
36
+ /**
37
+ * The guild ID of the player
38
+ */
39
+ public readonly guildId: string;
40
+ /**
41
+ * The voice channel ID of the player
42
+ */
43
+ public voiceId: string | null;
44
+ /**
45
+ * The text channel ID of the player
46
+ */
47
+ public textId?: string;
48
+ /**
49
+ * Player's queue
50
+ */
51
+ public readonly queue: KazagumoQueue;
52
+ /**
53
+ * Get the current state of the player
54
+ */
55
+ public state: PlayerState = PlayerState.CONNECTING;
56
+ /**
57
+ * Paused state of the player
58
+ */
59
+ public paused: boolean = false;
60
+ /**
61
+ * Whether the player is playing or not
62
+ */
63
+ public playing: boolean = false;
64
+ /**
65
+ * Loop status
66
+ */
67
+ public loop: 'none' | 'queue' | 'track' = 'none';
68
+ /**
69
+ * Search track/s
70
+ */
71
+ public search: (query: string, options?: KazagumoSearchOptions) => Promise<KazagumoSearchResult>;
72
+ /**
73
+ * Player's volume in percentage (default 100%)
74
+ */
75
+ public volume: number = 100;
76
+ /**
77
+ * Player's custom data
78
+ */
79
+ public readonly data: Map<string, any> = new Map();
80
+
81
+ /**
82
+ * Initialize the player
83
+ * @param kazagumo Kazagumo instance
84
+ * @param player Shoukaku's Player instance
85
+ * @param options Kazagumo options
86
+ * @param customData private readonly customData
87
+ */
88
+ constructor(
89
+ kazagumo: Kazagumo,
90
+ player: Player,
91
+ options: KazagumoPlayerOptions,
92
+ private readonly customData: unknown,
93
+ ) {
94
+ this.options = options;
95
+ this.kazagumo = kazagumo;
96
+ this.shoukaku = player;
97
+ this.guildId = options.guildId;
98
+ this.voiceId = options.voiceId;
99
+ this.textId = options.textId;
100
+ this.queue = new (this.options.extends?.queue ?? KazagumoQueue)(this);
101
+
102
+ if (options.volume !== 100) this.setVolume(options.volume);
103
+
104
+ this.search = (typeof this.options.searchWithSameNode === 'boolean' ? this.options.searchWithSameNode : true)
105
+ ? (query: string, opt?: KazagumoSearchOptions) =>
106
+ kazagumo.search.bind(kazagumo)(query, opt ? { ...opt, nodeName: this.shoukaku.node.name } : undefined)
107
+ : kazagumo.search.bind(kazagumo);
108
+
109
+ this.shoukaku.on('start', () => {
110
+ this.playing = true;
111
+ this.emit(Events.PlayerStart, this, this.queue.current);
112
+ });
113
+
114
+ this.shoukaku.on('end', (data) => {
115
+ // This event emits STOPPED reason when destroying, so return to prevent double emit
116
+ if (this.state === PlayerState.DESTROYING || this.state === PlayerState.DESTROYED)
117
+ return this.emit(Events.Debug, `Player ${this.guildId} destroyed from end event`);
118
+
119
+ if (data.reason === 'replaced') return this.emit(Events.PlayerEnd, this);
120
+ if (['loadFailed', 'cleanup'].includes(data.reason)) {
121
+ if (
122
+ this.queue.current &&
123
+ !this.queue.previous.find(
124
+ (x) => x.identifier === this.queue.current?.identifier && x.title === this.queue.current?.title,
125
+ )
126
+ )
127
+ this.queue.previous = [this.queue.current].concat(this.queue.previous);
128
+ this.emit(Events.PlayerEnd, this, this.queue.current);
129
+ this.queue.current = null;
130
+ this.playing = false;
131
+ if (!this.queue.length) return this.emit(Events.PlayerEmpty, this);
132
+ return this.play();
133
+ }
134
+
135
+ if (this.loop === 'track' && this.queue.current) this.queue.unshift(this.queue.current);
136
+ if (this.loop === 'queue' && this.queue.current) this.queue.push(this.queue.current);
137
+
138
+ if (
139
+ this.queue.current &&
140
+ !this.queue.previous.find(
141
+ (x) => x.identifier === this.queue.current?.identifier && x.title === this.queue.current?.title,
142
+ )
143
+ )
144
+ this.queue.previous = [this.queue.current].concat(this.queue.previous);
145
+
146
+ const currentSong = this.queue.current;
147
+ this.emit(Events.PlayerEnd, this, currentSong);
148
+ this.queue.current = null;
149
+
150
+ if (!this.queue.length) {
151
+ this.playing = false;
152
+ return this.emit(Events.PlayerEmpty, this);
153
+ }
154
+
155
+ return this.play();
156
+ });
157
+
158
+ this.shoukaku.on('closed', (data: WebSocketClosedEvent) => {
159
+ this.playing = false;
160
+ this.emit(Events.PlayerClosed, this, data);
161
+ });
162
+
163
+ this.shoukaku.on('exception', (data: TrackExceptionEvent) => {
164
+ this.playing = false;
165
+ this.emit(Events.PlayerException, this, data);
166
+ });
167
+
168
+ this.shoukaku.on('update', (data: PlayerUpdate) => this.emit(Events.PlayerUpdate, this, data));
169
+ this.shoukaku.on('stuck', (data: TrackStuckEvent) => this.emit(Events.PlayerStuck, this, data));
170
+ this.shoukaku.on('resumed', () => this.emit(Events.PlayerResumed, this));
171
+ // @ts-ignore
172
+ this.shoukaku.on(Events.QueueUpdate, (referencePlayer: KazagumoPlayer, queue: KazagumoQueue) =>
173
+ this.kazagumo.emit(Events.QueueUpdate, referencePlayer, queue),
174
+ );
175
+ }
176
+
177
+ // /**
178
+ // * Get volume
179
+ // */
180
+ // public get volume(): number {
181
+ // return this.shoukaku.filters.volume || 1;
182
+ // }
183
+
184
+ /**
185
+ * Get player position
186
+ */
187
+ public get position(): number {
188
+ return this.shoukaku.position;
189
+ }
190
+
191
+ /**
192
+ * Get filters
193
+ */
194
+ public get filters(): FilterOptions {
195
+ return this.shoukaku.filters;
196
+ }
197
+
198
+ private get node(): Node {
199
+ return this.shoukaku.node;
200
+ }
201
+
202
+ /**
203
+ * Pause the player
204
+ * @param pause Whether to pause or not
205
+ * @returns KazagumoPlayer
206
+ */
207
+ public pause(pause: boolean): KazagumoPlayer {
208
+ if (typeof pause !== 'boolean') throw new KazagumoError(1, 'pause must be a boolean');
209
+
210
+ if (this.paused === pause || !this.queue.totalSize) return this;
211
+ this.paused = pause;
212
+ this.playing = !pause;
213
+ this.shoukaku.setPaused(pause);
214
+
215
+ return this;
216
+ }
217
+
218
+ /**
219
+ * Set text channel
220
+ * @param textId Text channel ID
221
+ * @returns KazagumoPlayer
222
+ */
223
+ public setTextChannel(textId: string): KazagumoPlayer {
224
+ if (this.state === PlayerState.DESTROYED) throw new KazagumoError(1, 'Player is already destroyed');
225
+
226
+ this.textId = textId;
227
+
228
+ return this;
229
+ }
230
+
231
+ /**
232
+ * Set voice channel and move the player to the voice channel
233
+ * @param voiceId Voice channel ID
234
+ * @returns KazagumoPlayer
235
+ */
236
+ public setVoiceChannel(voiceId: string): KazagumoPlayer {
237
+ if (this.state === PlayerState.DESTROYED) throw new KazagumoError(1, 'Player is already destroyed');
238
+ this.state = PlayerState.CONNECTING;
239
+
240
+ this.voiceId = voiceId;
241
+ this.kazagumo.KazagumoOptions.send(this.guildId, {
242
+ op: 4,
243
+ d: {
244
+ guild_id: this.guildId,
245
+ channel_id: this.voiceId,
246
+ self_mute: false,
247
+ self_deaf: this.options.deaf,
248
+ },
249
+ });
250
+
251
+ this.emit(Events.Debug, `Player ${this.guildId} moved to voice channel ${voiceId}`);
252
+
253
+ return this;
254
+ }
255
+
256
+ /**
257
+ * Get the previous track from the queue
258
+ * @param remove Whether to remove the track from the previous list or not. Best to set to true if you want to play it
259
+ */
260
+ public getPrevious(remove: boolean = false): KazagumoTrack | undefined {
261
+ if (remove) return this.queue.previous.shift();
262
+ return this.queue.previous[0];
263
+ }
264
+
265
+ /**
266
+ * Set loop mode
267
+ * @param [loop] Loop mode
268
+ * @returns KazagumoPlayer
269
+ */
270
+ public setLoop(loop?: 'none' | 'queue' | 'track'): KazagumoPlayer {
271
+ if (loop === undefined) {
272
+ if (this.loop === 'none') this.loop = 'queue';
273
+ else if (this.loop === 'queue') this.loop = 'track';
274
+ else if (this.loop === 'track') this.loop = 'none';
275
+ return this;
276
+ }
277
+
278
+ if (loop === 'none' || loop === 'queue' || loop === 'track') {
279
+ this.loop = loop;
280
+ return this;
281
+ }
282
+
283
+ throw new KazagumoError(1, "loop must be one of 'none', 'queue', 'track'");
284
+ }
285
+
286
+ /**
287
+ * Play a track
288
+ * @param track Track to play
289
+ * @param options Play options
290
+ * @returns KazagumoPlayer
291
+ */
292
+ public async play(track?: KazagumoTrack, options?: PlayOptions): Promise<KazagumoPlayer> {
293
+ if (this.state === PlayerState.DESTROYED) throw new KazagumoError(1, 'Player is already destroyed');
294
+
295
+ if (track && !(track instanceof KazagumoTrack)) throw new KazagumoError(1, 'track must be a KazagumoTrack');
296
+
297
+ if (!track && !this.queue.totalSize) throw new KazagumoError(1, 'No track is available to play');
298
+
299
+ if (!options || typeof options.replaceCurrent !== 'boolean') options = { ...options, replaceCurrent: false };
300
+
301
+ if (track) {
302
+ if (!options.replaceCurrent && this.queue.current) this.queue.unshift(this.queue.current);
303
+ this.queue.current = track;
304
+ } else if (!this.queue.current) this.queue.current = this.queue.shift();
305
+
306
+ if (!this.queue.current) throw new KazagumoError(1, 'No track is available to play');
307
+
308
+ const current = this.queue.current;
309
+ current.setKazagumo(this.kazagumo);
310
+
311
+ let errorMessage: string | undefined;
312
+
313
+ const resolveResult = await current.resolve({ player: this as KazagumoPlayer }).catch((e) => {
314
+ errorMessage = e.message;
315
+ return null;
316
+ });
317
+
318
+ if (!resolveResult) {
319
+ this.emit(Events.PlayerResolveError, this, current, errorMessage);
320
+ this.emit(Events.Debug, `Player ${this.guildId} resolve error: ${errorMessage}`);
321
+ this.queue.current = null;
322
+ this.queue.size ? await this.play() : this.emit(Events.PlayerEmpty, this);
323
+ return this;
324
+ }
325
+
326
+ let playOptions = { track: { encoded: current.track, userData: current.requester ?? {} } };
327
+ if (options) playOptions = { ...playOptions, ...options };
328
+
329
+ await this.shoukaku.playTrack(playOptions);
330
+
331
+ return this;
332
+ }
333
+
334
+ /**
335
+ * Skip the current track
336
+ * @returns KazagumoPlayer
337
+ */
338
+ public skip(): KazagumoPlayer {
339
+ if (this.state === PlayerState.DESTROYED) throw new KazagumoError(1, 'Player is already destroyed');
340
+
341
+ this.shoukaku.stopTrack();
342
+
343
+ return this;
344
+ }
345
+
346
+ /**
347
+ * Seek to a position
348
+ * @param position Position in seconds
349
+ * @returns KazagumoPlayer
350
+ */
351
+ public async seek(position: number): Promise<KazagumoPlayer> {
352
+ if (this.state === PlayerState.DESTROYED) throw new KazagumoError(1, 'Player is already destroyed');
353
+ if (!this.queue.current) throw new KazagumoError(1, "Player has no current track in it's queue");
354
+ if (!this.queue.current.isSeekable) throw new KazagumoError(1, "The current track isn't seekable");
355
+
356
+ position = Number(position);
357
+
358
+ if (isNaN(position)) throw new KazagumoError(1, 'position must be a number');
359
+ if (position < 0 || position > (this.queue.current.length ?? 0))
360
+ position = Math.max(Math.min(position, this.queue.current.length ?? 0), 0);
361
+
362
+ this.queue.current.position = position;
363
+ await this.shoukaku.seekTo(position);
364
+
365
+ return this;
366
+ }
367
+
368
+ /**
369
+ * Set the volume in percentage (default 100%)
370
+ * @param volume Volume
371
+ * @returns KazagumoPlayer
372
+ */
373
+ public async setVolume(volume: number): Promise<KazagumoPlayer> {
374
+ if (this.state === PlayerState.DESTROYED) throw new KazagumoError(1, 'Player is already destroyed');
375
+ if (isNaN(volume)) throw new KazagumoError(1, 'volume must be a number');
376
+
377
+ await this.node.rest.updatePlayer({
378
+ guildId: this.guildId,
379
+ playerOptions: {
380
+ volume,
381
+ },
382
+ });
383
+
384
+ this.volume = volume;
385
+
386
+ return this;
387
+ }
388
+
389
+ /**
390
+ * Connect to the voice channel
391
+ * @returns KazagumoPlayer
392
+ */
393
+ public connect(): KazagumoPlayer {
394
+ if (this.state === PlayerState.DESTROYED) throw new KazagumoError(1, 'Player is already destroyed');
395
+ if (this.state === PlayerState.CONNECTED || !!this.voiceId)
396
+ throw new KazagumoError(1, 'Player is already connected');
397
+ this.state = PlayerState.CONNECTING;
398
+
399
+ this.kazagumo.KazagumoOptions.send(this.guildId, {
400
+ op: 4,
401
+ d: {
402
+ guild_id: this.guildId,
403
+ channel_id: this.voiceId,
404
+ self_mute: false,
405
+ self_deaf: this.options.deaf,
406
+ },
407
+ });
408
+
409
+ this.state = PlayerState.CONNECTED;
410
+
411
+ this.emit(Events.Debug, `Player ${this.guildId} connected`);
412
+
413
+ return this;
414
+ }
415
+
416
+ /**
417
+ * Disconnect from the voice channel
418
+ * @returns KazagumoPlayer
419
+ */
420
+ public disconnect(): KazagumoPlayer {
421
+ if (this.state === PlayerState.DISCONNECTED || !this.voiceId)
422
+ throw new KazagumoError(1, 'Player is already disconnected');
423
+ this.state = PlayerState.DISCONNECTING;
424
+
425
+ this.pause(true);
426
+ this.kazagumo.KazagumoOptions.send(this.guildId, {
427
+ op: 4,
428
+ d: {
429
+ guild_id: this.guildId,
430
+ channel_id: null,
431
+ self_mute: false,
432
+ self_deaf: false,
433
+ },
434
+ });
435
+
436
+ this.voiceId = null;
437
+ this.state = PlayerState.DISCONNECTED;
438
+
439
+ this.emit(Events.Debug, `Player disconnected; Guild id: ${this.guildId}`);
440
+
441
+ return this;
442
+ }
443
+
444
+ /**
445
+ * Destroy the player
446
+ * @returns KazagumoPlayer
447
+ */
448
+ async destroy(): Promise<KazagumoPlayer> {
449
+ if (this.state === PlayerState.DESTROYING || this.state === PlayerState.DESTROYED)
450
+ throw new KazagumoError(1, 'Player is already destroyed');
451
+
452
+ this.disconnect();
453
+ this.state = PlayerState.DESTROYING;
454
+ this.shoukaku.clean();
455
+ await this.kazagumo.shoukaku.leaveVoiceChannel(this.guildId);
456
+ await this.shoukaku.destroy();
457
+ this.shoukaku.removeAllListeners();
458
+ this.kazagumo.players.delete(this.guildId);
459
+ this.state = PlayerState.DESTROYED;
460
+
461
+ this.emit(Events.PlayerDestroy, this);
462
+ this.emit(Events.Debug, `Player destroyed; Guild id: ${this.guildId}`);
463
+
464
+ return this;
465
+ }
466
+
467
+ private emit(event: string, ...args: any): void {
468
+ this.kazagumo.emit(event, ...args);
469
+ }
470
+ }
@@ -0,0 +1,89 @@
1
+ import { KazagumoTrack } from './KazagumoTrack';
2
+ import { Events, KazagumoError } from '../../Modules/Interfaces';
3
+ import { KazagumoPlayer } from '../KazagumoPlayer';
4
+
5
+ export class KazagumoQueue extends Array<KazagumoTrack> {
6
+ constructor(private readonly kazagumoPlayer: KazagumoPlayer) {
7
+ super();
8
+ }
9
+ /** Get the size of queue */
10
+ public get size() {
11
+ return this.length;
12
+ }
13
+
14
+ /** Get the size of queue including current */
15
+ public get totalSize(): number {
16
+ return this.length + (this.current ? 1 : 0);
17
+ }
18
+
19
+ /** Check if the queue is empty or not */
20
+ public get isEmpty() {
21
+ return this.length === 0;
22
+ }
23
+
24
+ /** Get the queue's duration */
25
+ public get durationLength() {
26
+ return this.reduce((acc, cur) => acc + (cur.length || 0), 0);
27
+ }
28
+
29
+ /** Current playing track */
30
+ public current: KazagumoTrack | undefined | null = null;
31
+ /** Previous playing tracks */
32
+ public previous: KazagumoTrack[] = [];
33
+
34
+ /**
35
+ * Add track(s) to the queue
36
+ * @param track KazagumoTrack to add
37
+ * @returns KazagumoQueue
38
+ */
39
+ public add(track: KazagumoTrack | KazagumoTrack[]): KazagumoQueue {
40
+ if (!Array.isArray(track)) track = [track];
41
+
42
+ if (!this.current) {
43
+ if (Array.isArray(track)) this.current = track.shift();
44
+ else {
45
+ this.current = track;
46
+ return this;
47
+ }
48
+ }
49
+
50
+ if (Array.isArray(track)) for (const t of track) this.push(t);
51
+ else this.push(track);
52
+ this.emitChanges();
53
+ return this;
54
+ }
55
+
56
+ /**
57
+ * Remove track from the queue
58
+ * @param position Position of the track
59
+ * @returns KazagumoQueue
60
+ */
61
+ public remove(position: number): KazagumoQueue {
62
+ if (position < 0 || position >= this.length)
63
+ throw new KazagumoError(1, 'Position must be between 0 and ' + (this.length - 1));
64
+ this.splice(position, 1);
65
+ this.emitChanges();
66
+ return this;
67
+ }
68
+
69
+ /** Shuffle the queue */
70
+ public shuffle(): KazagumoQueue {
71
+ for (let i = this.length - 1; i > 0; i--) {
72
+ const j = Math.floor(Math.random() * (i + 1));
73
+ [this[i], this[j]] = [this[j], this[i]];
74
+ }
75
+ this.emitChanges();
76
+ return this;
77
+ }
78
+
79
+ /** Clear the queue */
80
+ public clear(): KazagumoQueue {
81
+ this.splice(0, this.length);
82
+ this.emitChanges();
83
+ return this;
84
+ }
85
+
86
+ private emitChanges(): void {
87
+ (this.kazagumoPlayer.shoukaku as any).emit(Events.QueueUpdate, this.kazagumoPlayer, this);
88
+ }
89
+ }