ziplayer 0.1.3 → 0.1.5

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 (34) hide show
  1. package/README.md +212 -212
  2. package/dist/plugins/SoundCloudPlugin.d.ts +22 -0
  3. package/dist/plugins/SoundCloudPlugin.d.ts.map +1 -0
  4. package/dist/plugins/SoundCloudPlugin.js +171 -0
  5. package/dist/plugins/SoundCloudPlugin.js.map +1 -0
  6. package/dist/plugins/SpotifyPlugin.d.ts +26 -0
  7. package/dist/plugins/SpotifyPlugin.d.ts.map +1 -0
  8. package/dist/plugins/SpotifyPlugin.js +183 -0
  9. package/dist/plugins/SpotifyPlugin.js.map +1 -0
  10. package/dist/plugins/YouTubePlugin.d.ts +25 -0
  11. package/dist/plugins/YouTubePlugin.d.ts.map +1 -0
  12. package/dist/plugins/YouTubePlugin.js +314 -0
  13. package/dist/plugins/YouTubePlugin.js.map +1 -0
  14. package/dist/structures/Player.d.ts +61 -70
  15. package/dist/structures/Player.d.ts.map +1 -1
  16. package/dist/structures/Player.js +332 -355
  17. package/dist/structures/Player.js.map +1 -1
  18. package/dist/structures/PlayerManager.d.ts +5 -1
  19. package/dist/structures/PlayerManager.d.ts.map +1 -1
  20. package/dist/structures/PlayerManager.js.map +1 -1
  21. package/dist/types/index.d.ts +58 -16
  22. package/dist/types/index.d.ts.map +1 -1
  23. package/package.json +45 -45
  24. package/src/extensions/BaseExtension.ts +35 -35
  25. package/src/extensions/index.ts +32 -32
  26. package/src/index.ts +16 -16
  27. package/src/plugins/BasePlugin.ts +26 -26
  28. package/src/plugins/index.ts +32 -32
  29. package/src/structures/Player.ts +1693 -1747
  30. package/src/structures/PlayerManager.ts +416 -411
  31. package/src/structures/Queue.ts +354 -354
  32. package/src/types/index.ts +510 -470
  33. package/src/utils/timeout.ts +10 -10
  34. package/tsconfig.json +23 -23
@@ -1,411 +1,416 @@
1
- import { EventEmitter } from "events";
2
- import { Player } from "./Player";
3
- import { PlayerManagerOptions, PlayerOptions, Track, SourcePlugin, SearchResult } from "../types";
4
- import type { BaseExtension } from "../extensions";
5
- import { withTimeout } from "../utils/timeout";
6
-
7
- const GLOBAL_MANAGER_KEY: symbol = Symbol.for("ziplayer.PlayerManager.instance");
8
- export const getGlobalManager = (): PlayerManager | null => {
9
- try {
10
- const instance = (globalThis as any)[GLOBAL_MANAGER_KEY];
11
- if (!instance) {
12
- return null;
13
- }
14
- return instance as PlayerManager;
15
- } catch (error) {
16
- console.error("[PlayerManager] Error getting global instance:", error);
17
- return null;
18
- }
19
- };
20
- const setGlobalManager = (instance: PlayerManager): void => {
21
- try {
22
- (globalThis as any)[GLOBAL_MANAGER_KEY] = instance;
23
- } catch (error) {
24
- console.error("[PlayerManager] Error setting global instance:", error);
25
- }
26
- };
27
-
28
- /**
29
- * The main class for managing players across multiple Discord guilds.
30
- *
31
- * @example
32
- * // Basic setup with plugins and extensions
33
- * const manager = new PlayerManager({
34
- * plugins: [
35
- * new YouTubePlugin(),
36
- * new SoundCloudPlugin(),
37
- * new SpotifyPlugin(),
38
- * new TTSPlugin({ defaultLang: "en" })
39
- * ],
40
- * extensions: [
41
- * new voiceExt(null, { lang: "en-US" }),
42
- * new lavalinkExt(null, {
43
- * nodes: [{ host: "localhost", port: 2333, password: "youshallnotpass" }]
44
- * })
45
- * ],
46
- * extractorTimeout: 10000
47
- * });
48
- *
49
- * // Create a player for a guild
50
- * const player = await manager.create(guildId, {
51
- * tts: { interrupt: true, volume: 1 },
52
- * leaveOnEnd: true,
53
- * leaveTimeout: 30000
54
- * });
55
- *
56
- * // Get existing player
57
- * const existingPlayer = manager.get(guildId);
58
- * if (existingPlayer) {
59
- * await existingPlayer.play("Never Gonna Give You Up", userId);
60
- * }
61
- */
62
- export class PlayerManager extends EventEmitter {
63
- private static instance: PlayerManager | null = null;
64
- private players: Map<string, Player> = new Map();
65
- static async default(opt?: PlayerOptions): Promise<Player> {
66
- let globaldef = getGlobalManager();
67
- if (!globaldef) {
68
- globaldef = new PlayerManager({});
69
- }
70
- return await globaldef.create("default", opt);
71
- }
72
- private plugins: SourcePlugin[];
73
- private extensions: any[];
74
- private B_debug: boolean = false;
75
- private extractorTimeout: number = 10000;
76
-
77
- private debug(message?: any, ...optionalParams: any[]): void {
78
- if (this.listenerCount("debug") > 0) {
79
- this.emit("debug", message, ...optionalParams);
80
- if (!this.B_debug) {
81
- this.B_debug = true;
82
- }
83
- }
84
- }
85
-
86
- constructor(options: PlayerManagerOptions = {}) {
87
- super();
88
- this.plugins = [];
89
- const provided = options.plugins || [];
90
- for (const p of provided as any[]) {
91
- try {
92
- if (p && typeof p === "object") {
93
- this.plugins.push(p as SourcePlugin);
94
- } else if (typeof p === "function") {
95
- const instance = new (p as any)();
96
- this.plugins.push(instance as SourcePlugin);
97
- }
98
- } catch (e) {
99
- this.debug(`[PlayerManager] Failed to init plugin:`, e);
100
- }
101
- }
102
- this.extensions = options.extensions || [];
103
-
104
- setGlobalManager(this);
105
- }
106
-
107
- private withTimeout<T>(promise: Promise<T>, message: string): Promise<T> {
108
- const timeout = this.extractorTimeout;
109
- return Promise.race([promise, new Promise<never>((_, reject) => setTimeout(() => reject(new Error(message)), timeout))]);
110
- }
111
-
112
- private resolveGuildId(guildOrId: string | { id: string }): string {
113
- if (typeof guildOrId === "string") return guildOrId;
114
- if (guildOrId && typeof guildOrId === "object" && "id" in guildOrId) return guildOrId.id;
115
- throw new Error("Invalid guild or guildId provided.");
116
- }
117
-
118
- /**
119
- * Create a new player for a guild
120
- *
121
- * @param {string | {id: string}} guildOrId - Guild ID or guild object
122
- * @param {PlayerOptions} options - Player configuration options
123
- * @returns {Promise<Player>} The created player instance
124
- *
125
- * @example
126
- * // Create player with basic options
127
- * const player = await manager.create(guildId, {
128
- * tts: { interrupt: true, volume: 1 },
129
- * leaveOnEnd: true,
130
- * leaveTimeout: 30000
131
- * });
132
- *
133
- * // Create player with advanced options
134
- * const advancedPlayer = await manager.create(guild, {
135
- * volume: 0.8,
136
- * quality: "high",
137
- * selfDeaf: false,
138
- * selfMute: false,
139
- * tts: {
140
- * createPlayer: true,
141
- * interrupt: true,
142
- * volume: 1.0,
143
- * Max_Time_TTS: 30000
144
- * },
145
- * userdata: { customData: "example" }
146
- * });
147
- *
148
- * // Connect and play immediately
149
- * await player.connect(voiceChannel);
150
- * await player.play("Never Gonna Give You Up", userId);
151
- */
152
-
153
- async create(guildOrId: string | { id: string }, options?: PlayerOptions): Promise<Player> {
154
- const guildId = this.resolveGuildId(guildOrId);
155
- if (this.players.has(guildId)) {
156
- return this.players.get(guildId)!;
157
- }
158
-
159
- this.debug(`[PlayerManager] Creating player for guildId: ${guildId}`);
160
- const player = new Player(guildId, options, this);
161
- this.plugins.forEach((plugin) => player.addPlugin(plugin));
162
-
163
- let extsToActivate: any[] = [];
164
- const optExts = (options as any)?.extensions as any[] | string[] | undefined;
165
- if (Array.isArray(optExts)) {
166
- if (optExts.length === 0) {
167
- extsToActivate = [];
168
- } else if (typeof optExts[0] === "string") {
169
- const wanted = new Set(optExts as string[]);
170
- extsToActivate = this.extensions.filter((ext) => {
171
- const name = typeof ext === "function" ? ext.name : ext?.name;
172
- return !!name && wanted.has(name);
173
- });
174
- } else {
175
- extsToActivate = optExts;
176
- }
177
- }
178
-
179
- for (const ext of extsToActivate) {
180
- let instance = ext;
181
- if (typeof ext === "function") {
182
- try {
183
- instance = new ext(player);
184
- } catch (e) {
185
- this.debug(`[PlayerManager] Extension constructor error:`, e);
186
- continue;
187
- }
188
- }
189
- if (instance && typeof instance === "object") {
190
- const extInstance = instance as BaseExtension;
191
- if ("player" in extInstance && !extInstance.player) extInstance.player = player;
192
- player.attachExtension(extInstance);
193
- if (typeof extInstance.active === "function") {
194
- let activated: boolean | void = true;
195
- try {
196
- activated = await withTimeout(
197
- Promise.resolve(extInstance.active({ manager: this, player })),
198
- player.options.extractorTimeout ?? 15000,
199
- `Extension ${extInstance?.name} activation timed out`,
200
- );
201
- this.debug(`[PlayerManager] Extension ${extInstance?.name} active`);
202
- } catch (e) {
203
- activated = false;
204
- this.debug(`[PlayerManager] Extension activation error:`, e);
205
- }
206
- if (activated === false) {
207
- player.detachExtension(extInstance);
208
- continue;
209
- }
210
- }
211
- }
212
- }
213
-
214
- // Forward all player events
215
- player.on("willPlay", (track, tracks) => this.emit("willPlay", player, track, tracks));
216
- player.on("trackStart", (track) => this.emit("trackStart", player, track));
217
- player.on("trackEnd", (track) => this.emit("trackEnd", player, track));
218
- player.on("queueEnd", () => this.emit("queueEnd", player));
219
- player.on("playerError", (error, track) => this.emit("playerError", player, error, track));
220
- player.on("connectionError", (error) => this.emit("connectionError", player, error));
221
- player.on("volumeChange", (old, volume) => this.emit("volumeChange", player, old, volume));
222
- player.on("queueAdd", (track) => this.emit("queueAdd", player, track));
223
- player.on("queueAddList", (tracks) => this.emit("queueAddList", player, tracks));
224
- player.on("queueRemove", (track, index) => this.emit("queueRemove", player, track, index));
225
- player.on("playerPause", (track) => this.emit("playerPause", player, track));
226
- player.on("playerResume", (track) => this.emit("playerResume", player, track));
227
- player.on("playerStop", () => this.emit("playerStop", player));
228
- player.on("playerDestroy", () => {
229
- this.emit("playerDestroy", player);
230
- this.players.delete(guildId);
231
- });
232
- player.on("ttsStart", (payload) => this.emit("ttsStart", player, payload));
233
- player.on("ttsEnd", () => this.emit("ttsEnd", player));
234
- player.on("debug", (...args) => {
235
- if (this.listenerCount("debug") > 0) {
236
- this.emit("debug", ...args);
237
- }
238
- });
239
-
240
- this.players.set(guildId, player);
241
- return player;
242
- }
243
-
244
- /**
245
- * Get an existing player for a guild
246
- *
247
- * @param {string | {id: string}} guildOrId - Guild ID or guild object
248
- * @returns {Player | undefined} The player instance or undefined if not found
249
- * @example
250
- * // Get player by guild ID
251
- * const player = manager.get(guildId);
252
- * if (player) {
253
- * await player.play("Never Gonna Give You Up", userId);
254
- * } else {
255
- * console.log("No player found for this guild");
256
- * }
257
- *
258
- * // Get player by guild object
259
- * const playerFromGuild = manager.get(guild);
260
- * if (playerFromGuild) {
261
- * playerFromGuild.setVolume(0.5);
262
- * }
263
- *
264
- * // Check if player exists before using
265
- * const existingPlayer = manager.get(guildId);
266
- * if (existingPlayer && existingPlayer.playing) {
267
- * existingPlayer.pause();
268
- * }
269
- */
270
-
271
- get(guildOrId: string | { id: string }): Player | undefined {
272
- const guildId = this.resolveGuildId(guildOrId);
273
- return this.players.get(guildId);
274
- }
275
-
276
- /**
277
- * Get an existing player for a guild
278
- *
279
- * @param {string | {id: string}} guildOrId - Guild ID or guild object
280
- * @returns {Player | undefined} The player instance or undefined
281
- * @example
282
- * const player = manager.get(guildId);
283
- * if (player) {
284
- * await player.play("song name", userId);
285
- * }
286
- */
287
- getPlayer(guildOrId: string | { id: string }): Player | undefined {
288
- const guildId = this.resolveGuildId(guildOrId);
289
- return this.players.get(guildId);
290
- }
291
-
292
- /**
293
- * Get all players
294
- *
295
- * @returns {Player[]} All player instances
296
- * @example
297
- * const players = manager.getall();
298
- * console.log(`Players: ${players.length}`);
299
- */
300
- getall(): Player[] | [] {
301
- return Array.from(this.players.values());
302
- }
303
-
304
- /**
305
- * Destroy a player and clean up resources
306
- *
307
- * @param {string | {id: string}} guildOrId - Guild ID or guild object
308
- * @returns {boolean} True if player was destroyed, false if not found
309
- * @example
310
- * // Destroy player by guild ID
311
- * const destroyed = manager.delete(guildId);
312
- * if (destroyed) {
313
- * console.log("Player destroyed successfully");
314
- * } else {
315
- * console.log("No player found to destroy");
316
- * }
317
- *
318
- * // Destroy player by guild object
319
- * const destroyedFromGuild = manager.delete(guild);
320
- * console.log(`Player destroyed: ${destroyedFromGuild}`);
321
- *
322
- * // Clean up all players
323
- * for (const [guildId, player] of manager.players) {
324
- * const destroyed = manager.delete(guildId);
325
- * console.log(`Destroyed player for ${guildId}: ${destroyed}`);
326
- * }
327
- */
328
- delete(guildOrId: string | { id: string }): boolean {
329
- const guildId = this.resolveGuildId(guildOrId);
330
- const player = this.players.get(guildId);
331
- if (player) {
332
- this.debug(`[PlayerManager] Deleting player for guildId: ${guildId}`);
333
- player.destroy();
334
- return this.players.delete(guildId);
335
- }
336
- return false;
337
- }
338
-
339
- /**
340
- * Check if a player exists for a guild
341
- *
342
- * @param {string | {id: string}} guildOrId - Guild ID or guild object
343
- * @returns {boolean} True if player exists, false if not
344
- * @example
345
- * const exists = manager.has(guildId);
346
- * console.log(`Player exists: ${exists}`);
347
- */
348
- has(guildOrId: string | { id: string }): boolean {
349
- const guildId = this.resolveGuildId(guildOrId);
350
- return this.players.has(guildId);
351
- }
352
-
353
- get size(): number {
354
- return this.players.size;
355
- }
356
-
357
- get debugEnabled(): boolean {
358
- return this.B_debug;
359
- }
360
- /**
361
- * Destroy all players
362
- *
363
- * @returns {void}
364
- * @example
365
- * manager.destroy();
366
- * console.log(`All players destroyed`);
367
- */
368
- destroy(): void {
369
- this.debug(`[PlayerManager] Destroying all players`);
370
- for (const player of this.players.values()) {
371
- player.destroy();
372
- }
373
- this.players.clear();
374
- this.removeAllListeners();
375
- }
376
-
377
- /**
378
- * Search using registered plugins without creating a Player.
379
- *
380
- * @param {string} query - The query to search for
381
- * @param {string} requestedBy - The user ID who requested the search
382
- * @returns {Promise<SearchResult>} The search result
383
- * @example
384
- * const result = await manager.search("Never Gonna Give You Up", userId);
385
- * console.log(`Search result: ${result.tracks.length} tracks`);
386
- */
387
- async search(query: string, requestedBy: string): Promise<SearchResult> {
388
- this.debug(`[PlayerManager] Search called with query: ${query}, requestedBy: ${requestedBy}`);
389
- const plugin = this.plugins.find((p) => p.canHandle(query));
390
- if (!plugin) {
391
- this.debug(`[PlayerManager] No plugin found to handle: ${query}`);
392
- throw new Error(`No plugin found to handle: ${query}`);
393
- }
394
-
395
- try {
396
- return await this.withTimeout(plugin.search(query, requestedBy), "Search operation timed out");
397
- } catch (error) {
398
- this.debug(`[PlayerManager] Search error:`, error);
399
- throw error as Error;
400
- }
401
- }
402
- }
403
-
404
- export function getInstance(): PlayerManager | null {
405
- const globalInst = getGlobalManager();
406
- if (!globalInst) {
407
- console.error("[PlayerManager] Global instance not found, make sure to initialize with new PlayerManager(options)");
408
- return null;
409
- }
410
- return globalInst;
411
- }
1
+ import { EventEmitter } from "events";
2
+ import { Player } from "./Player";
3
+ import { PlayerManagerOptions, PlayerOptions, Track, SourcePlugin, SearchResult, ManagerEvents } from "../types";
4
+ import type { BaseExtension } from "../extensions";
5
+ import { withTimeout } from "../utils/timeout";
6
+
7
+ const GLOBAL_MANAGER_KEY: symbol = Symbol.for("ziplayer.PlayerManager.instance");
8
+ export const getGlobalManager = (): PlayerManager | null => {
9
+ try {
10
+ const instance = (globalThis as any)[GLOBAL_MANAGER_KEY];
11
+ if (!instance) {
12
+ return null;
13
+ }
14
+ return instance as PlayerManager;
15
+ } catch (error) {
16
+ console.error("[PlayerManager] Error getting global instance:", error);
17
+ return null;
18
+ }
19
+ };
20
+ const setGlobalManager = (instance: PlayerManager): void => {
21
+ try {
22
+ (globalThis as any)[GLOBAL_MANAGER_KEY] = instance;
23
+ } catch (error) {
24
+ console.error("[PlayerManager] Error setting global instance:", error);
25
+ }
26
+ };
27
+
28
+ export declare interface PlayerManager {
29
+ on<K extends keyof ManagerEvents>(event: K, listener: (...args: ManagerEvents[K]) => void): this;
30
+ emit<K extends keyof ManagerEvents>(event: K, ...args: ManagerEvents[K]): boolean;
31
+ }
32
+
33
+ /**
34
+ * The main class for managing players across multiple Discord guilds.
35
+ *
36
+ * @example
37
+ * // Basic setup with plugins and extensions
38
+ * const manager = new PlayerManager({
39
+ * plugins: [
40
+ * new YouTubePlugin(),
41
+ * new SoundCloudPlugin(),
42
+ * new SpotifyPlugin(),
43
+ * new TTSPlugin({ defaultLang: "en" })
44
+ * ],
45
+ * extensions: [
46
+ * new voiceExt(null, { lang: "en-US" }),
47
+ * new lavalinkExt(null, {
48
+ * nodes: [{ host: "localhost", port: 2333, password: "youshallnotpass" }]
49
+ * })
50
+ * ],
51
+ * extractorTimeout: 10000
52
+ * });
53
+ *
54
+ * // Create a player for a guild
55
+ * const player = await manager.create(guildId, {
56
+ * tts: { interrupt: true, volume: 1 },
57
+ * leaveOnEnd: true,
58
+ * leaveTimeout: 30000
59
+ * });
60
+ *
61
+ * // Get existing player
62
+ * const existingPlayer = manager.get(guildId);
63
+ * if (existingPlayer) {
64
+ * await existingPlayer.play("Never Gonna Give You Up", userId);
65
+ * }
66
+ */
67
+ export class PlayerManager extends EventEmitter {
68
+ private static instance: PlayerManager | null = null;
69
+ private players: Map<string, Player> = new Map();
70
+ static async default(opt?: PlayerOptions): Promise<Player> {
71
+ let globaldef = getGlobalManager();
72
+ if (!globaldef) {
73
+ globaldef = new PlayerManager({});
74
+ }
75
+ return await globaldef.create("default", opt);
76
+ }
77
+ private plugins: SourcePlugin[];
78
+ private extensions: any[];
79
+ private B_debug: boolean = false;
80
+ private extractorTimeout: number = 10000;
81
+
82
+ private debug(message?: any, ...optionalParams: any[]): void {
83
+ if (this.listenerCount("debug") > 0) {
84
+ this.emit("debug", message, ...optionalParams);
85
+ if (!this.B_debug) {
86
+ this.B_debug = true;
87
+ }
88
+ }
89
+ }
90
+
91
+ constructor(options: PlayerManagerOptions = {}) {
92
+ super();
93
+ this.plugins = [];
94
+ const provided = options.plugins || [];
95
+ for (const p of provided as any[]) {
96
+ try {
97
+ if (p && typeof p === "object") {
98
+ this.plugins.push(p as SourcePlugin);
99
+ } else if (typeof p === "function") {
100
+ const instance = new (p as any)();
101
+ this.plugins.push(instance as SourcePlugin);
102
+ }
103
+ } catch (e) {
104
+ this.debug(`[PlayerManager] Failed to init plugin:`, e);
105
+ }
106
+ }
107
+ this.extensions = options.extensions || [];
108
+
109
+ setGlobalManager(this);
110
+ }
111
+
112
+ private withTimeout<T>(promise: Promise<T>, message: string): Promise<T> {
113
+ const timeout = this.extractorTimeout;
114
+ return Promise.race([promise, new Promise<never>((_, reject) => setTimeout(() => reject(new Error(message)), timeout))]);
115
+ }
116
+
117
+ private resolveGuildId(guildOrId: string | { id: string }): string {
118
+ if (typeof guildOrId === "string") return guildOrId;
119
+ if (guildOrId && typeof guildOrId === "object" && "id" in guildOrId) return guildOrId.id;
120
+ throw new Error("Invalid guild or guildId provided.");
121
+ }
122
+
123
+ /**
124
+ * Create a new player for a guild
125
+ *
126
+ * @param {string | {id: string}} guildOrId - Guild ID or guild object
127
+ * @param {PlayerOptions} options - Player configuration options
128
+ * @returns {Promise<Player>} The created player instance
129
+ *
130
+ * @example
131
+ * // Create player with basic options
132
+ * const player = await manager.create(guildId, {
133
+ * tts: { interrupt: true, volume: 1 },
134
+ * leaveOnEnd: true,
135
+ * leaveTimeout: 30000
136
+ * });
137
+ *
138
+ * // Create player with advanced options
139
+ * const advancedPlayer = await manager.create(guild, {
140
+ * volume: 0.8,
141
+ * quality: "high",
142
+ * selfDeaf: false,
143
+ * selfMute: false,
144
+ * tts: {
145
+ * createPlayer: true,
146
+ * interrupt: true,
147
+ * volume: 1.0,
148
+ * Max_Time_TTS: 30000
149
+ * },
150
+ * userdata: { customData: "example" }
151
+ * });
152
+ *
153
+ * // Connect and play immediately
154
+ * await player.connect(voiceChannel);
155
+ * await player.play("Never Gonna Give You Up", userId);
156
+ */
157
+
158
+ async create(guildOrId: string | { id: string }, options?: PlayerOptions): Promise<Player> {
159
+ const guildId = this.resolveGuildId(guildOrId);
160
+ if (this.players.has(guildId)) {
161
+ return this.players.get(guildId)!;
162
+ }
163
+
164
+ this.debug(`[PlayerManager] Creating player for guildId: ${guildId}`);
165
+ const player = new Player(guildId, options, this);
166
+ this.plugins.forEach((plugin) => player.addPlugin(plugin));
167
+
168
+ let extsToActivate: any[] = [];
169
+ const optExts = (options as any)?.extensions as any[] | string[] | undefined;
170
+ if (Array.isArray(optExts)) {
171
+ if (optExts.length === 0) {
172
+ extsToActivate = [];
173
+ } else if (typeof optExts[0] === "string") {
174
+ const wanted = new Set(optExts as string[]);
175
+ extsToActivate = this.extensions.filter((ext) => {
176
+ const name = typeof ext === "function" ? ext.name : ext?.name;
177
+ return !!name && wanted.has(name);
178
+ });
179
+ } else {
180
+ extsToActivate = optExts;
181
+ }
182
+ }
183
+
184
+ for (const ext of extsToActivate) {
185
+ let instance = ext;
186
+ if (typeof ext === "function") {
187
+ try {
188
+ instance = new ext(player);
189
+ } catch (e) {
190
+ this.debug(`[PlayerManager] Extension constructor error:`, e);
191
+ continue;
192
+ }
193
+ }
194
+ if (instance && typeof instance === "object") {
195
+ const extInstance = instance as BaseExtension;
196
+ if ("player" in extInstance && !extInstance.player) extInstance.player = player;
197
+ player.attachExtension(extInstance);
198
+ if (typeof extInstance.active === "function") {
199
+ let activated: boolean | void = true;
200
+ try {
201
+ activated = await withTimeout(
202
+ Promise.resolve(extInstance.active({ manager: this, player })),
203
+ player.options.extractorTimeout ?? 15000,
204
+ `Extension ${extInstance?.name} activation timed out`,
205
+ );
206
+ this.debug(`[PlayerManager] Extension ${extInstance?.name} active`);
207
+ } catch (e) {
208
+ activated = false;
209
+ this.debug(`[PlayerManager] Extension activation error:`, e);
210
+ }
211
+ if (activated === false) {
212
+ player.detachExtension(extInstance);
213
+ continue;
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ // Forward all player events
220
+ player.on("willPlay", (track, tracks) => this.emit("willPlay", player, track as Track, tracks as Track[]));
221
+ player.on("trackStart", (track) => this.emit("trackStart", player, track as Track));
222
+ player.on("trackEnd", (track) => this.emit("trackEnd", player, track as Track));
223
+ player.on("queueEnd", () => this.emit("queueEnd", player));
224
+ player.on("playerError", (error, track) => this.emit("playerError", player, error, track as Track));
225
+ player.on("connectionError", (error) => this.emit("connectionError", player, error));
226
+ player.on("volumeChange", (old, volume) => this.emit("volumeChange", player, old as number, volume as number));
227
+ player.on("queueAdd", (track) => this.emit("queueAdd", player, track as Track));
228
+ player.on("queueAddList", (tracks) => this.emit("queueAddList", player, tracks as Track[]));
229
+ player.on("queueRemove", (track, index) => this.emit("queueRemove", player, track as Track, index));
230
+ player.on("playerPause", (track) => this.emit("playerPause", player, track as Track));
231
+ player.on("playerResume", (track) => this.emit("playerResume", player, track as Track));
232
+ player.on("playerStop", () => this.emit("playerStop", player));
233
+ player.on("playerDestroy", () => {
234
+ this.emit("playerDestroy", player);
235
+ this.players.delete(guildId);
236
+ });
237
+ player.on("ttsStart", (payload) => this.emit("ttsStart", player, payload));
238
+ player.on("ttsEnd", () => this.emit("ttsEnd", player));
239
+ player.on("debug", (...args) => {
240
+ if (this.listenerCount("debug") > 0) {
241
+ this.emit("debug", ...args);
242
+ }
243
+ });
244
+
245
+ this.players.set(guildId, player);
246
+ return player;
247
+ }
248
+
249
+ /**
250
+ * Get an existing player for a guild
251
+ *
252
+ * @param {string | {id: string}} guildOrId - Guild ID or guild object
253
+ * @returns {Player | undefined} The player instance or undefined if not found
254
+ * @example
255
+ * // Get player by guild ID
256
+ * const player = manager.get(guildId);
257
+ * if (player) {
258
+ * await player.play("Never Gonna Give You Up", userId);
259
+ * } else {
260
+ * console.log("No player found for this guild");
261
+ * }
262
+ *
263
+ * // Get player by guild object
264
+ * const playerFromGuild = manager.get(guild);
265
+ * if (playerFromGuild) {
266
+ * playerFromGuild.setVolume(0.5);
267
+ * }
268
+ *
269
+ * // Check if player exists before using
270
+ * const existingPlayer = manager.get(guildId);
271
+ * if (existingPlayer && existingPlayer.playing) {
272
+ * existingPlayer.pause();
273
+ * }
274
+ */
275
+
276
+ get(guildOrId: string | { id: string }): Player | undefined {
277
+ const guildId = this.resolveGuildId(guildOrId);
278
+ return this.players.get(guildId);
279
+ }
280
+
281
+ /**
282
+ * Get an existing player for a guild
283
+ *
284
+ * @param {string | {id: string}} guildOrId - Guild ID or guild object
285
+ * @returns {Player | undefined} The player instance or undefined
286
+ * @example
287
+ * const player = manager.get(guildId);
288
+ * if (player) {
289
+ * await player.play("song name", userId);
290
+ * }
291
+ */
292
+ getPlayer(guildOrId: string | { id: string }): Player | undefined {
293
+ const guildId = this.resolveGuildId(guildOrId);
294
+ return this.players.get(guildId);
295
+ }
296
+
297
+ /**
298
+ * Get all players
299
+ *
300
+ * @returns {Player[]} All player instances
301
+ * @example
302
+ * const players = manager.getall();
303
+ * console.log(`Players: ${players.length}`);
304
+ */
305
+ getall(): Player[] | [] {
306
+ return Array.from(this.players.values());
307
+ }
308
+
309
+ /**
310
+ * Destroy a player and clean up resources
311
+ *
312
+ * @param {string | {id: string}} guildOrId - Guild ID or guild object
313
+ * @returns {boolean} True if player was destroyed, false if not found
314
+ * @example
315
+ * // Destroy player by guild ID
316
+ * const destroyed = manager.delete(guildId);
317
+ * if (destroyed) {
318
+ * console.log("Player destroyed successfully");
319
+ * } else {
320
+ * console.log("No player found to destroy");
321
+ * }
322
+ *
323
+ * // Destroy player by guild object
324
+ * const destroyedFromGuild = manager.delete(guild);
325
+ * console.log(`Player destroyed: ${destroyedFromGuild}`);
326
+ *
327
+ * // Clean up all players
328
+ * for (const [guildId, player] of manager.players) {
329
+ * const destroyed = manager.delete(guildId);
330
+ * console.log(`Destroyed player for ${guildId}: ${destroyed}`);
331
+ * }
332
+ */
333
+ delete(guildOrId: string | { id: string }): boolean {
334
+ const guildId = this.resolveGuildId(guildOrId);
335
+ const player = this.players.get(guildId);
336
+ if (player) {
337
+ this.debug(`[PlayerManager] Deleting player for guildId: ${guildId}`);
338
+ player.destroy();
339
+ return this.players.delete(guildId);
340
+ }
341
+ return false;
342
+ }
343
+
344
+ /**
345
+ * Check if a player exists for a guild
346
+ *
347
+ * @param {string | {id: string}} guildOrId - Guild ID or guild object
348
+ * @returns {boolean} True if player exists, false if not
349
+ * @example
350
+ * const exists = manager.has(guildId);
351
+ * console.log(`Player exists: ${exists}`);
352
+ */
353
+ has(guildOrId: string | { id: string }): boolean {
354
+ const guildId = this.resolveGuildId(guildOrId);
355
+ return this.players.has(guildId);
356
+ }
357
+
358
+ get size(): number {
359
+ return this.players.size;
360
+ }
361
+
362
+ get debugEnabled(): boolean {
363
+ return this.B_debug;
364
+ }
365
+ /**
366
+ * Destroy all players
367
+ *
368
+ * @returns {void}
369
+ * @example
370
+ * manager.destroy();
371
+ * console.log(`All players destroyed`);
372
+ */
373
+ destroy(): void {
374
+ this.debug(`[PlayerManager] Destroying all players`);
375
+ for (const player of this.players.values()) {
376
+ player.destroy();
377
+ }
378
+ this.players.clear();
379
+ this.removeAllListeners();
380
+ }
381
+
382
+ /**
383
+ * Search using registered plugins without creating a Player.
384
+ *
385
+ * @param {string} query - The query to search for
386
+ * @param {string} requestedBy - The user ID who requested the search
387
+ * @returns {Promise<SearchResult>} The search result
388
+ * @example
389
+ * const result = await manager.search("Never Gonna Give You Up", userId);
390
+ * console.log(`Search result: ${result.tracks.length} tracks`);
391
+ */
392
+ async search(query: string, requestedBy: string): Promise<SearchResult> {
393
+ this.debug(`[PlayerManager] Search called with query: ${query}, requestedBy: ${requestedBy}`);
394
+ const plugin = this.plugins.find((p) => p.canHandle(query));
395
+ if (!plugin) {
396
+ this.debug(`[PlayerManager] No plugin found to handle: ${query}`);
397
+ throw new Error(`No plugin found to handle: ${query}`);
398
+ }
399
+
400
+ try {
401
+ return await this.withTimeout(plugin.search(query, requestedBy), "Search operation timed out");
402
+ } catch (error) {
403
+ this.debug(`[PlayerManager] Search error:`, error);
404
+ throw error as Error;
405
+ }
406
+ }
407
+ }
408
+
409
+ export function getInstance(): PlayerManager | null {
410
+ const globalInst = getGlobalManager();
411
+ if (!globalInst) {
412
+ console.error("[PlayerManager] Global instance not found, make sure to initialize with new PlayerManager(options)");
413
+ return null;
414
+ }
415
+ return globalInst;
416
+ }