ziplayer 0.0.1

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 (50) hide show
  1. package/README.md +150 -0
  2. package/dist/index.d.ts +8 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +29 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/plugins/BasePlugin.d.ts +17 -0
  7. package/dist/plugins/BasePlugin.d.ts.map +1 -0
  8. package/dist/plugins/BasePlugin.js +19 -0
  9. package/dist/plugins/BasePlugin.js.map +1 -0
  10. package/dist/plugins/SoundCloudPlugin.d.ts +22 -0
  11. package/dist/plugins/SoundCloudPlugin.d.ts.map +1 -0
  12. package/dist/plugins/SoundCloudPlugin.js +171 -0
  13. package/dist/plugins/SoundCloudPlugin.js.map +1 -0
  14. package/dist/plugins/SpotifyPlugin.d.ts +26 -0
  15. package/dist/plugins/SpotifyPlugin.d.ts.map +1 -0
  16. package/dist/plugins/SpotifyPlugin.js +183 -0
  17. package/dist/plugins/SpotifyPlugin.js.map +1 -0
  18. package/dist/plugins/YouTubePlugin.d.ts +25 -0
  19. package/dist/plugins/YouTubePlugin.d.ts.map +1 -0
  20. package/dist/plugins/YouTubePlugin.js +314 -0
  21. package/dist/plugins/YouTubePlugin.js.map +1 -0
  22. package/dist/plugins/index.d.ts +12 -0
  23. package/dist/plugins/index.d.ts.map +1 -0
  24. package/dist/plugins/index.js +31 -0
  25. package/dist/plugins/index.js.map +1 -0
  26. package/dist/structures/Player.d.ts +54 -0
  27. package/dist/structures/Player.d.ts.map +1 -0
  28. package/dist/structures/Player.js +444 -0
  29. package/dist/structures/Player.js.map +1 -0
  30. package/dist/structures/PlayerManager.d.ts +16 -0
  31. package/dist/structures/PlayerManager.d.ts.map +1 -0
  32. package/dist/structures/PlayerManager.js +77 -0
  33. package/dist/structures/PlayerManager.js.map +1 -0
  34. package/dist/structures/Queue.d.ts +24 -0
  35. package/dist/structures/Queue.d.ts.map +1 -0
  36. package/dist/structures/Queue.js +78 -0
  37. package/dist/structures/Queue.js.map +1 -0
  38. package/dist/types/index.d.ts +75 -0
  39. package/dist/types/index.d.ts.map +1 -0
  40. package/dist/types/index.js +3 -0
  41. package/dist/types/index.js.map +1 -0
  42. package/package.json +29 -0
  43. package/src/index.ts +10 -0
  44. package/src/plugins/BasePlugin.ts +29 -0
  45. package/src/plugins/index.ts +32 -0
  46. package/src/structures/Player.ts +526 -0
  47. package/src/structures/PlayerManager.ts +86 -0
  48. package/src/structures/Queue.ts +87 -0
  49. package/src/types/index.ts +82 -0
  50. package/tsconfig.json +23 -0
@@ -0,0 +1,526 @@
1
+ import { EventEmitter } from "events";
2
+ import {
3
+ createAudioPlayer,
4
+ createAudioResource,
5
+ AudioPlayerStatus,
6
+ VoiceConnection,
7
+ AudioPlayer as DiscordAudioPlayer,
8
+ VoiceConnectionStatus,
9
+ joinVoiceChannel,
10
+ AudioResource,
11
+ StreamType,
12
+ } from "@discordjs/voice";
13
+
14
+ import { VoiceChannel } from "discord.js";
15
+ import { Readable } from "stream";
16
+ import { Track, PlayerOptions, PlayerEvents, SourcePlugin, SearchResult, ProgressBarOptions } from "../types";
17
+ import { Queue } from "./Queue";
18
+ import { PluginManager } from "../plugins";
19
+
20
+ export declare interface Player {
21
+ on<K extends keyof PlayerEvents>(event: K, listener: (...args: PlayerEvents[K]) => void): this;
22
+ emit<K extends keyof PlayerEvents>(event: K, ...args: PlayerEvents[K]): boolean;
23
+ }
24
+
25
+ export class Player extends EventEmitter {
26
+ public readonly guildId: string;
27
+ public connection: VoiceConnection | null = null;
28
+ public audioPlayer: DiscordAudioPlayer;
29
+ public queue: Queue;
30
+ public volume: number = 100;
31
+ public isPlaying: boolean = false;
32
+ public isPaused: boolean = false;
33
+ public options: PlayerOptions;
34
+ public pluginManager: PluginManager;
35
+ public userdata?: Record<string, any>;
36
+ private leaveTimeout: NodeJS.Timeout | null = null;
37
+ private currentResource: AudioResource | null = null;
38
+ private volumeInterval: NodeJS.Timeout | null = null;
39
+
40
+ private withTimeout<T>(promise: Promise<T>, message: string): Promise<T> {
41
+ const timeout = this.options.extractorTimeout ?? 15000;
42
+ return Promise.race([promise, new Promise<never>((_, reject) => setTimeout(() => reject(new Error(message)), timeout))]);
43
+ }
44
+
45
+ private debug(message?: any, ...optionalParams: any[]): void {
46
+ if (this.listenerCount("debug") > 0) {
47
+ this.emit("debug", message, ...optionalParams);
48
+ }
49
+ }
50
+
51
+ constructor(guildId: string, options: PlayerOptions = {}) {
52
+ super();
53
+ this.debug(`[Player] Constructor called for guildId: ${guildId}`);
54
+ this.guildId = guildId;
55
+ this.queue = new Queue();
56
+ this.audioPlayer = createAudioPlayer();
57
+ this.pluginManager = new PluginManager();
58
+
59
+ this.options = {
60
+ leaveOnEnd: true,
61
+ leaveOnEmpty: true,
62
+ leaveTimeout: 100000,
63
+ volume: 100,
64
+ quality: "high",
65
+ extractorTimeout: 50000,
66
+ ...options,
67
+ };
68
+
69
+ this.volume = this.options.volume || 100;
70
+ this.userdata = this.options.userdata;
71
+ this.setupEventListeners();
72
+ }
73
+
74
+ private setupEventListeners(): void {
75
+ this.audioPlayer.on(AudioPlayerStatus.Playing, () => {
76
+ this.debug(`[Player] AudioPlayerStatus.Playing`);
77
+ this.isPlaying = true;
78
+ this.isPaused = false;
79
+ const track = this.queue.currentTrack;
80
+ if (track) {
81
+ this.debug(`[Player] Track started: ${track.title}`);
82
+ this.emit("trackStart", track);
83
+ }
84
+ });
85
+
86
+ this.audioPlayer.on(AudioPlayerStatus.Idle, () => {
87
+ this.debug(`[Player] AudioPlayerStatus.Idle`);
88
+ this.isPlaying = false;
89
+ this.isPaused = false;
90
+ const track = this.queue.currentTrack;
91
+
92
+ if (track) {
93
+ this.debug(`[Player] Track ended: ${track.title}`);
94
+ this.emit("trackEnd", track);
95
+ }
96
+
97
+ this.playNext();
98
+ });
99
+
100
+ this.audioPlayer.on(AudioPlayerStatus.Paused, () => {
101
+ this.debug(`[Player] AudioPlayerStatus.Paused`);
102
+ this.isPaused = true;
103
+ const track = this.queue.currentTrack;
104
+ if (track) {
105
+ this.debug(`[Player] Player paused on track: ${track.title}`);
106
+ this.emit("playerPause", track);
107
+ }
108
+ });
109
+
110
+ this.audioPlayer.on("error", (error) => {
111
+ this.debug(`[Player] AudioPlayer error:`, error);
112
+ this.emit("playerError", error, this.queue.currentTrack || undefined);
113
+ this.playNext();
114
+ });
115
+ }
116
+
117
+ addPlugin(plugin: SourcePlugin): void {
118
+ this.debug(`[Player] Adding plugin: ${plugin.name}`);
119
+ this.pluginManager.register(plugin);
120
+ }
121
+
122
+ removePlugin(name: string): boolean {
123
+ this.debug(`[Player] Removing plugin: ${name}`);
124
+ return this.pluginManager.unregister(name);
125
+ }
126
+
127
+ async connect(channel: VoiceChannel): Promise<VoiceConnection> {
128
+ try {
129
+ this.debug(`[Player] Connecting to voice channel: ${channel.id}`);
130
+ this.connection = joinVoiceChannel({
131
+ channelId: channel.id,
132
+ guildId: channel.guildId,
133
+ adapterCreator: channel.guild.voiceAdapterCreator as any,
134
+ });
135
+
136
+ this.connection.on(VoiceConnectionStatus.Disconnected, () => {
137
+ this.debug(`[Player] VoiceConnectionStatus.Disconnected`);
138
+ this.destroy();
139
+ });
140
+
141
+ this.connection.on("error", (error) => {
142
+ this.debug(`[Player] Voice connection error:`, error);
143
+ this.emit("connectionError", error);
144
+ });
145
+
146
+ this.connection.subscribe(this.audioPlayer);
147
+
148
+ if (this.leaveTimeout) {
149
+ clearTimeout(this.leaveTimeout);
150
+ this.leaveTimeout = null;
151
+ }
152
+
153
+ return this.connection;
154
+ } catch (error) {
155
+ this.debug(`[Player] Connection error:`, error);
156
+ this.emit("connectionError", error as Error);
157
+ throw error;
158
+ }
159
+ }
160
+
161
+ async search(query: string, requestedBy: string): Promise<SearchResult> {
162
+ this.debug(`[Player] Search called with query: ${query}, requestedBy: ${requestedBy}`);
163
+ const plugin = this.pluginManager.findPlugin(query);
164
+ if (!plugin) {
165
+ this.debug(`[Player] No plugin found to handle: ${query}`);
166
+ throw new Error(`No plugin found to handle: ${query}`);
167
+ }
168
+
169
+ try {
170
+ return await this.withTimeout(plugin.search(query, requestedBy), "Search operation timed out");
171
+ } catch (error) {
172
+ this.debug(`[Player] Search error:`, error);
173
+ this.emit("playerError", error as Error);
174
+ throw error;
175
+ }
176
+ }
177
+
178
+ async play(query: string | Track, requestedBy?: string): Promise<boolean> {
179
+ try {
180
+ this.debug(`[Player] Play called with query: ${typeof query === "string" ? query : query?.title}`);
181
+ let tracksToAdd: Track[] = [];
182
+
183
+ if (typeof query === "string") {
184
+ const searchResult = await this.search(query, requestedBy || "Unknown");
185
+ tracksToAdd = searchResult.tracks;
186
+
187
+ if (searchResult.playlist) {
188
+ this.debug(`[Player] Added playlist: ${searchResult.playlist.name} (${tracksToAdd.length} tracks)`);
189
+ }
190
+ } else {
191
+ tracksToAdd = [query];
192
+ }
193
+
194
+ if (tracksToAdd.length === 0) {
195
+ this.debug(`[Player] No tracks found for play`);
196
+ throw new Error("No tracks found");
197
+ }
198
+
199
+ // Add tracks to queue
200
+ for (const track of tracksToAdd) {
201
+ this.debug(`[Player] Adding track to queue: ${track.title}`);
202
+ this.queue.add(track);
203
+ this.emit("queueAdd", track);
204
+ }
205
+
206
+ // Start playing if not already playing
207
+ if (!this.isPlaying) {
208
+ return this.playNext();
209
+ }
210
+
211
+ return true;
212
+ } catch (error) {
213
+ this.debug(`[Player] Play error:`, error);
214
+ this.emit("playerError", error as Error);
215
+ return false;
216
+ }
217
+ }
218
+
219
+ private async generateWillNext(): Promise<void> {
220
+ const willnext = this.queue.willNextTrack();
221
+
222
+ const lastTrack = this.queue.previousTracks[this.queue.previousTracks.length - 1] ?? this.queue.currentTrack;
223
+ const plugin = this.pluginManager.findPlugin(lastTrack.url) || this.pluginManager.get(lastTrack.source);
224
+ if (plugin && typeof plugin.getRelatedTracks === "function") {
225
+ try {
226
+ const related = await this.withTimeout(
227
+ plugin.getRelatedTracks(lastTrack.url, {
228
+ limit: 10,
229
+ history: this.queue.previousTracks,
230
+ }),
231
+ "getRelatedTracks timed out",
232
+ );
233
+
234
+ if (related && related.length > 0) {
235
+ const randomchoice = Math.floor(Math.random() * related.length);
236
+ const nextTrack = this.queue.nextTrack ? this.queue.nextTrack : related[randomchoice];
237
+ this.queue.willNextTrack(nextTrack);
238
+ this.debug(`[Player] Will next track if autoplay: ${nextTrack?.title}`);
239
+ this.emit("willPlay", nextTrack, related);
240
+ }
241
+ } catch (err) {
242
+ this.debug(`[Player] getRelatedTracks error:`, err);
243
+ }
244
+ }
245
+ }
246
+
247
+ private async playNext(): Promise<boolean> {
248
+ this.debug(`[Player] playNext called`);
249
+ const track = this.queue.next();
250
+ if (!track) {
251
+ if (this.queue.autoPlay()) {
252
+ const willnext = this.queue.willNextTrack();
253
+ console.log("willnext", willnext);
254
+ if (willnext) {
255
+ this.debug(`[Player] Auto-playing next track: ${willnext.title}`);
256
+ this.queue.addMultiple([willnext]);
257
+ return this.playNext();
258
+ }
259
+ }
260
+
261
+ this.debug(`[Player] No next track in queue`);
262
+ this.isPlaying = false;
263
+ this.emit("queueEnd");
264
+
265
+ if (this.options.leaveOnEnd) {
266
+ this.scheduleLeave();
267
+ }
268
+ return false;
269
+ }
270
+
271
+ this.generateWillNext();
272
+
273
+ try {
274
+ // Find plugin that can handle this track
275
+ const plugin = this.pluginManager.findPlugin(track.url) || this.pluginManager.get(track.source);
276
+
277
+ if (!plugin) {
278
+ this.debug(`[Player] No plugin found for track: ${track.title}`);
279
+ throw new Error(`No plugin found for track: ${track.title}`);
280
+ }
281
+
282
+ this.debug(`[Player] Getting stream for track: ${track.title}`);
283
+ this.debug(`[Player] Using plugin: ${plugin.name}`);
284
+ this.debug(`[Track] Track Info:`, track);
285
+ let streamInfo;
286
+ try {
287
+ streamInfo = await this.withTimeout(plugin.getStream(track), "getStream timed out");
288
+ } catch (streamError) {
289
+ this.debug(`[Player] getStream failed, trying getFallback:`, streamError);
290
+ const allplugs = this.pluginManager.getAll();
291
+ for (const p of allplugs) {
292
+ if (typeof p.getFallback !== "function") {
293
+ continue;
294
+ }
295
+ try {
296
+ streamInfo = await this.withTimeout(p.getFallback(track), `getFallback timed out for plugin ${p.name}`);
297
+ if (!streamInfo.stream) continue;
298
+ this.debug(`[Player] getFallback succeeded with plugin ${p.name} for track: ${track.title}`);
299
+ break;
300
+ } catch (fallbackError) {
301
+ this.debug(`[Player] getFallback failed with plugin ${p.name}:`, fallbackError);
302
+ }
303
+ }
304
+ if (!streamInfo?.stream) {
305
+ throw new Error(`All getFallback attempts failed for track: ${track.title}`);
306
+ }
307
+ this.debug(streamInfo);
308
+ }
309
+
310
+ function mapToStreamType(type: string): StreamType {
311
+ switch (type) {
312
+ case "webm/opus":
313
+ return StreamType.WebmOpus;
314
+ case "ogg/opus":
315
+ return StreamType.OggOpus;
316
+ case "arbitrary":
317
+ return StreamType.Arbitrary;
318
+ default:
319
+ return StreamType.Arbitrary;
320
+ }
321
+ }
322
+
323
+ let stream: Readable = streamInfo.stream;
324
+ let inputType = mapToStreamType(streamInfo.type);
325
+
326
+ this.currentResource = createAudioResource(stream, {
327
+ metadata: track,
328
+ inputType,
329
+ inlineVolume: true,
330
+ });
331
+
332
+ // Apply initial volume using the resource's VolumeTransformer
333
+ if (this.volumeInterval) {
334
+ clearInterval(this.volumeInterval);
335
+ this.volumeInterval = null;
336
+ }
337
+ this.currentResource.volume?.setVolume(this.volume / 100);
338
+
339
+ this.debug(`[Player] Playing resource for track: ${track.title}`);
340
+ this.audioPlayer.play(this.currentResource);
341
+ return true;
342
+ } catch (error) {
343
+ this.debug(`[Player] playNext error:`, error);
344
+ this.emit("playerError", error as Error, track);
345
+ return this.playNext();
346
+ }
347
+ }
348
+
349
+ pause(): boolean {
350
+ this.debug(`[Player] pause called`);
351
+ if (this.isPlaying && !this.isPaused) {
352
+ return this.audioPlayer.pause();
353
+ }
354
+ return false;
355
+ }
356
+
357
+ resume(): boolean {
358
+ this.debug(`[Player] resume called`);
359
+ if (this.isPaused) {
360
+ const result = this.audioPlayer.unpause();
361
+ if (result) {
362
+ const track = this.queue.currentTrack;
363
+ if (track) {
364
+ this.debug(`[Player] Player resumed on track: ${track.title}`);
365
+ this.emit("playerResume", track);
366
+ }
367
+ }
368
+ return result;
369
+ }
370
+ return false;
371
+ }
372
+
373
+ stop(): boolean {
374
+ this.debug(`[Player] stop called`);
375
+ this.queue.clear();
376
+ const result = this.audioPlayer.stop();
377
+ this.isPlaying = false;
378
+ this.isPaused = false;
379
+ this.emit("playerStop");
380
+ return result;
381
+ }
382
+
383
+ skip(): boolean {
384
+ this.debug(`[Player] skip called`);
385
+ if (this.isPlaying || this.isPaused) {
386
+ return this.audioPlayer.stop();
387
+ }
388
+ return !!this.playNext();
389
+ }
390
+
391
+ setVolume(volume: number): boolean {
392
+ this.debug(`[Player] setVolume called: ${volume}`);
393
+ if (volume < 0 || volume > 200) return false;
394
+
395
+ const oldVolume = this.volume;
396
+ this.volume = volume;
397
+ const resourceVolume = this.currentResource?.volume;
398
+
399
+ if (resourceVolume) {
400
+ if (this.volumeInterval) clearInterval(this.volumeInterval);
401
+
402
+ const start = resourceVolume.volume;
403
+ const target = this.volume / 100;
404
+ const steps = 10;
405
+ let currentStep = 0;
406
+
407
+ this.volumeInterval = setInterval(() => {
408
+ currentStep++;
409
+ const value = start + ((target - start) * currentStep) / steps;
410
+ resourceVolume.setVolume(value);
411
+ if (currentStep >= steps) {
412
+ clearInterval(this.volumeInterval!);
413
+ this.volumeInterval = null;
414
+ }
415
+ }, 500);
416
+ }
417
+
418
+ this.emit("volumeChange", oldVolume, volume);
419
+ return true;
420
+ }
421
+
422
+ shuffle(): void {
423
+ this.debug(`[Player] shuffle called`);
424
+ this.queue.shuffle();
425
+ }
426
+
427
+ clearQueue(): void {
428
+ this.debug(`[Player] clearQueue called`);
429
+ this.queue.clear();
430
+ }
431
+
432
+ remove(index: number): Track | null {
433
+ this.debug(`[Player] remove called for index: ${index}`);
434
+ const track = this.queue.remove(index);
435
+ if (track) {
436
+ this.emit("queueRemove", track, index);
437
+ }
438
+ return track;
439
+ }
440
+
441
+ getProgressBar(options: ProgressBarOptions = {}): string {
442
+ const { size = 20, barChar = "▬", progressChar = "🔘" } = options;
443
+ const track = this.queue.currentTrack;
444
+ const resource = this.currentResource;
445
+ if (!track || !resource) return "";
446
+
447
+ const total = track.duration > 1000 ? track.duration : track.duration * 1000;
448
+ if (!total) return this.formatTime(resource.playbackDuration);
449
+
450
+ const current = resource.playbackDuration;
451
+ const ratio = Math.min(current / total, 1);
452
+ const progress = Math.round(ratio * size);
453
+ const bar = barChar.repeat(progress) + progressChar + barChar.repeat(size - progress);
454
+
455
+ return `${this.formatTime(current)} ${bar} ${this.formatTime(total)}`;
456
+ }
457
+
458
+ private formatTime(ms: number): string {
459
+ const totalSeconds = Math.floor(ms / 1000);
460
+ const hours = Math.floor(totalSeconds / 3600);
461
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
462
+ const seconds = totalSeconds % 60;
463
+ const parts: string[] = [];
464
+ if (hours > 0) parts.push(String(hours).padStart(2, "0"));
465
+ parts.push(String(minutes).padStart(2, "0"));
466
+ parts.push(String(seconds).padStart(2, "0"));
467
+ return parts.join(":");
468
+ }
469
+
470
+ private scheduleLeave(): void {
471
+ this.debug(`[Player] scheduleLeave called`);
472
+ if (this.leaveTimeout) {
473
+ clearTimeout(this.leaveTimeout);
474
+ }
475
+
476
+ if (this.options.leaveOnEmpty && this.options.leaveTimeout) {
477
+ this.leaveTimeout = setTimeout(() => {
478
+ this.debug(`[Player] Leaving voice channel after timeout`);
479
+ this.destroy();
480
+ }, this.options.leaveTimeout);
481
+ }
482
+ }
483
+
484
+ destroy(): void {
485
+ this.debug(`[Player] destroy called`);
486
+ if (this.leaveTimeout) {
487
+ clearTimeout(this.leaveTimeout);
488
+ this.leaveTimeout = null;
489
+ }
490
+
491
+ this.audioPlayer.stop(true);
492
+
493
+ if (this.connection) {
494
+ this.connection.destroy();
495
+ this.connection = null;
496
+ }
497
+
498
+ this.queue.clear();
499
+ this.pluginManager.clear();
500
+ this.isPlaying = false;
501
+ this.isPaused = false;
502
+ this.emit("playerDestroy");
503
+ this.removeAllListeners();
504
+ }
505
+
506
+ // Getters
507
+ get queueSize(): number {
508
+ return this.queue.size;
509
+ }
510
+
511
+ get currentTrack(): Track | null {
512
+ return this.queue.currentTrack;
513
+ }
514
+
515
+ get upcomingTracks(): Track[] {
516
+ return this.queue.getTracks();
517
+ }
518
+
519
+ get previousTracks(): Track[] {
520
+ return this.queue.previousTracks;
521
+ }
522
+
523
+ get availablePlugins(): string[] {
524
+ return this.pluginManager.getAll().map((p) => p.name);
525
+ }
526
+ }
@@ -0,0 +1,86 @@
1
+ import { EventEmitter } from "events";
2
+ import { Player } from "./Player";
3
+ import { PlayerManagerOptions, PlayerOptions, Track, SourcePlugin } from "../types";
4
+
5
+ export class PlayerManager extends EventEmitter {
6
+ private players: Map<string, Player> = new Map();
7
+ private plugins: SourcePlugin[];
8
+
9
+ private debug(message?: any, ...optionalParams: any[]): void {
10
+ if (this.listenerCount("debug") > 0) {
11
+ this.emit("debug", message, ...optionalParams);
12
+ }
13
+ }
14
+
15
+ constructor(options: PlayerManagerOptions = {}) {
16
+ super();
17
+ this.plugins = options.plugins || [];
18
+ }
19
+
20
+ create(guildId: string, options?: PlayerOptions): Player {
21
+ if (this.players.has(guildId)) {
22
+ return this.players.get(guildId)!;
23
+ }
24
+
25
+ this.debug(`[PlayerManager] Creating player for guildId: ${guildId}`);
26
+ const player = new Player(guildId, options);
27
+ this.plugins.forEach((plugin) => player.addPlugin(plugin));
28
+
29
+ // Forward all player events
30
+ player.on("willPlay", (track, tracks) => this.emit("willPlay", player, track, tracks));
31
+ player.on("trackStart", (track) => this.emit("trackStart", player, track));
32
+ player.on("trackEnd", (track) => this.emit("trackEnd", player, track));
33
+ player.on("queueEnd", () => this.emit("queueEnd", player));
34
+ player.on("playerError", (error, track) => this.emit("playerError", player, error, track));
35
+ player.on("connectionError", (error) => this.emit("connectionError", player, error));
36
+ player.on("volumeChange", (old, volume) => this.emit("volumeChange", player, old, volume));
37
+ player.on("queueAdd", (track) => this.emit("queueAdd", player, track));
38
+ player.on("queueRemove", (track, index) => this.emit("queueRemove", player, track, index));
39
+ player.on("playerPause", (track) => this.emit("playerPause", player, track));
40
+ player.on("playerResume", (track) => this.emit("playerResume", player, track));
41
+ player.on("playerStop", () => this.emit("playerStop", player));
42
+ player.on("playerDestroy", () => {
43
+ this.emit("playerDestroy", player);
44
+ this.players.delete(guildId);
45
+ });
46
+ player.on("debug", (...args) => {
47
+ if (this.listenerCount("debug") > 0) {
48
+ this.emit("debug", ...args);
49
+ }
50
+ });
51
+
52
+ this.players.set(guildId, player);
53
+ return player;
54
+ }
55
+
56
+ get(guildId: string): Player | undefined {
57
+ return this.players.get(guildId);
58
+ }
59
+
60
+ delete(guildId: string): boolean {
61
+ const player = this.players.get(guildId);
62
+ if (player) {
63
+ this.debug(`[PlayerManager] Deleting player for guildId: ${guildId}`);
64
+ player.destroy();
65
+ return this.players.delete(guildId);
66
+ }
67
+ return false;
68
+ }
69
+
70
+ has(guildId: string): boolean {
71
+ return this.players.has(guildId);
72
+ }
73
+
74
+ get size(): number {
75
+ return this.players.size;
76
+ }
77
+
78
+ destroy(): void {
79
+ this.debug(`[PlayerManager] Destroying all players`);
80
+ for (const player of this.players.values()) {
81
+ player.destroy();
82
+ }
83
+ this.players.clear();
84
+ this.removeAllListeners();
85
+ }
86
+ }
@@ -0,0 +1,87 @@
1
+ import { Track } from "../types";
2
+
3
+ export class Queue {
4
+ private tracks: Track[] = [];
5
+ private current: Track | null = null;
6
+ private history: Track[] = [];
7
+ private _autoPlay = false;
8
+ private willnext: Track | null = null;
9
+
10
+ add(track: Track): void {
11
+ this.tracks.push(track);
12
+ }
13
+
14
+ addMultiple(tracks: Track[]): void {
15
+ this.tracks.push(...tracks);
16
+ }
17
+
18
+ remove(index: number): Track | null {
19
+ if (index < 0 || index >= this.tracks.length) return null;
20
+ return this.tracks.splice(index, 1)[0];
21
+ }
22
+
23
+ next(): Track | null {
24
+ if (this.current) {
25
+ this.history.push(this.current);
26
+ if (this.history.length > 200) {
27
+ this.history.shift();
28
+ }
29
+ }
30
+ this.current = this.tracks.shift() || null;
31
+ return this.current;
32
+ }
33
+
34
+ clear(): void {
35
+ this.tracks = [];
36
+ }
37
+
38
+ autoPlay(value?: boolean): boolean {
39
+ if (typeof value !== "undefined") {
40
+ this._autoPlay = value;
41
+ }
42
+ return this._autoPlay;
43
+ }
44
+
45
+ shuffle(): void {
46
+ for (let i = this.tracks.length - 1; i > 0; i--) {
47
+ const j = Math.floor(Math.random() * (i + 1));
48
+ [this.tracks[i], this.tracks[j]] = [this.tracks[j], this.tracks[i]];
49
+ }
50
+ }
51
+
52
+ get size(): number {
53
+ return this.tracks.length;
54
+ }
55
+
56
+ get isEmpty(): boolean {
57
+ return this.tracks.length === 0;
58
+ }
59
+
60
+ get currentTrack(): Track | null {
61
+ return this.current;
62
+ }
63
+
64
+ get previousTracks(): Track[] {
65
+ return [...this.history];
66
+ }
67
+
68
+ get nextTrack(): Track | null {
69
+ return this.tracks[0] || null;
70
+ }
71
+
72
+ willNextTrack(track?: Track): Track | null {
73
+ if (track) {
74
+ this.willnext = track;
75
+ return this.willnext;
76
+ }
77
+ return this.willnext;
78
+ }
79
+
80
+ getTracks(): Track[] {
81
+ return [...this.tracks];
82
+ }
83
+
84
+ getTrack(index: number): Track | null {
85
+ return this.tracks[index] || null;
86
+ }
87
+ }