ziplayer 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,8 @@
1
1
  import { EventEmitter } from "events";
2
2
  import { Player } from "./Player";
3
3
  import { PlayerManagerOptions, PlayerOptions, Track, SourcePlugin, SearchResult } from "../types";
4
+ import type { BaseExtension } from "../extensions";
5
+ import { withTimeout } from "../utils/timeout";
4
6
 
5
7
  const GLOBAL_MANAGER_KEY: symbol = Symbol.for("ziplayer.PlayerManager.instance");
6
8
  export const getGlobalManager = (): PlayerManager | null => {
@@ -23,15 +25,49 @@ const setGlobalManager = (instance: PlayerManager): void => {
23
25
  }
24
26
  };
25
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
+ */
26
62
  export class PlayerManager extends EventEmitter {
27
63
  private static instance: PlayerManager | null = null;
28
64
  private players: Map<string, Player> = new Map();
29
- static default(opt?: PlayerOptions): Player {
65
+ static async default(opt?: PlayerOptions): Promise<Player> {
30
66
  let globaldef = getGlobalManager();
31
67
  if (!globaldef) {
32
68
  globaldef = new PlayerManager({});
33
69
  }
34
- return globaldef.create("default", opt);
70
+ return await globaldef.create("default", opt);
35
71
  }
36
72
  private plugins: SourcePlugin[];
37
73
  private extensions: any[];
@@ -79,7 +115,42 @@ export class PlayerManager extends EventEmitter {
79
115
  throw new Error("Invalid guild or guildId provided.");
80
116
  }
81
117
 
82
- create(guildOrId: string | { id: string }, options?: PlayerOptions): Player {
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> {
83
154
  const guildId = this.resolveGuildId(guildOrId);
84
155
  if (this.players.has(guildId)) {
85
156
  return this.players.get(guildId)!;
@@ -116,14 +187,26 @@ export class PlayerManager extends EventEmitter {
116
187
  }
117
188
  }
118
189
  if (instance && typeof instance === "object") {
119
- if ("player" in instance && !instance.player) instance.player = player;
120
- if (typeof instance.active === "function") {
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;
121
195
  try {
122
- instance.active({ manager: this, player });
123
- this.debug(`[PlayerManager] Extension ${instance?.name} active`);
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`);
124
202
  } catch (e) {
203
+ activated = false;
125
204
  this.debug(`[PlayerManager] Extension activation error:`, e);
126
205
  }
206
+ if (activated === false) {
207
+ player.detachExtension(extInstance);
208
+ continue;
209
+ }
127
210
  }
128
211
  }
129
212
  }
@@ -158,15 +241,90 @@ export class PlayerManager extends EventEmitter {
158
241
  return player;
159
242
  }
160
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
+
161
271
  get(guildOrId: string | { id: string }): Player | undefined {
162
272
  const guildId = this.resolveGuildId(guildOrId);
163
273
  return this.players.get(guildId);
164
274
  }
165
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
+ */
166
300
  getall(): Player[] | [] {
167
301
  return Array.from(this.players.values());
168
302
  }
169
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
+ */
170
328
  delete(guildOrId: string | { id: string }): boolean {
171
329
  const guildId = this.resolveGuildId(guildOrId);
172
330
  const player = this.players.get(guildId);
@@ -178,6 +336,15 @@ export class PlayerManager extends EventEmitter {
178
336
  return false;
179
337
  }
180
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
+ */
181
348
  has(guildOrId: string | { id: string }): boolean {
182
349
  const guildId = this.resolveGuildId(guildOrId);
183
350
  return this.players.has(guildId);
@@ -190,7 +357,14 @@ export class PlayerManager extends EventEmitter {
190
357
  get debugEnabled(): boolean {
191
358
  return this.B_debug;
192
359
  }
193
-
360
+ /**
361
+ * Destroy all players
362
+ *
363
+ * @returns {void}
364
+ * @example
365
+ * manager.destroy();
366
+ * console.log(`All players destroyed`);
367
+ */
194
368
  destroy(): void {
195
369
  this.debug(`[PlayerManager] Destroying all players`);
196
370
  for (const player of this.players.values()) {
@@ -202,6 +376,13 @@ export class PlayerManager extends EventEmitter {
202
376
 
203
377
  /**
204
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`);
205
386
  */
206
387
  async search(query: string, requestedBy: string): Promise<SearchResult> {
207
388
  this.debug(`[PlayerManager] Search called with query: ${query}, requestedBy: ${requestedBy}`);
@@ -1,5 +1,40 @@
1
1
  import { Track, LoopMode } from "../types";
2
2
 
3
+ /**
4
+ * Manages the track queue for a player.
5
+ *
6
+ * @example
7
+ * // Basic queue operations
8
+ * const queue = player.queue;
9
+ *
10
+ * // Add single track
11
+ * queue.add(track);
12
+ *
13
+ * // Add multiple tracks
14
+ * queue.add([track1, track2, track3]);
15
+ *
16
+ * // Queue controls
17
+ * queue.shuffle(); // Randomize order
18
+ * queue.clear(); // Remove all tracks
19
+ * queue.autoPlay(true); // Enable auto-play
20
+ *
21
+ * // Get queue information
22
+ * console.log(`Queue length: ${queue.length}`);
23
+ * console.log(`Current track: ${queue.current?.title}`);
24
+ * console.log(`Is empty: ${queue.isEmpty}`);
25
+ * console.log(`Is playing: ${queue.isPlaying}`);
26
+ *
27
+ * // Loop modes
28
+ * queue.setLoopMode("track"); // Loop current track
29
+ * queue.setLoopMode("queue"); // Loop entire queue
30
+ * queue.setLoopMode("off"); // No loop
31
+ *
32
+ * // Remove specific track
33
+ * const removed = queue.remove(0); // Remove first track
34
+ * if (removed) {
35
+ * console.log(`Removed: ${removed.title}`);
36
+ * }
37
+ */
3
38
  export class Queue {
4
39
  private tracks: Track[] = [];
5
40
  private current: Track | null = null;
@@ -9,15 +44,37 @@ export class Queue {
9
44
  private _loop: LoopMode = "off";
10
45
  private willnext: Track | null = null;
11
46
 
47
+ /**
48
+ * Add track(s) to the queue
49
+ *
50
+ * @param {Track | Track[]} track - Track or array of tracks to add
51
+ * @example
52
+ * queue.add(track);
53
+ * queue.add([track1, track2, track3]);
54
+ */
12
55
  add(track: Track): void {
13
56
  this.tracks.push(track);
14
57
  }
15
58
 
59
+ /**
60
+ * Add multiple tracks to the queue
61
+ *
62
+ * @param {Track[]} tracks - Tracks to add
63
+ * @example
64
+ * queue.addMultiple([track1, track2, track3]);
65
+ */
16
66
  addMultiple(tracks: Track[]): void {
17
67
  this.tracks.push(...tracks);
18
68
  }
19
69
 
20
- /** Insert a track at a specific upcoming position (0 = next) */
70
+ /**
71
+ * Insert a track at a specific upcoming position (0 = next)
72
+ *
73
+ * @param {Track} track - Track to insert
74
+ * @param {number} index - Index to insert the track at
75
+ * @example
76
+ * queue.insert(track, 0);
77
+ */
21
78
  insert(track: Track, index: number): void {
22
79
  if (!Number.isFinite(index)) {
23
80
  this.tracks.push(track);
@@ -35,7 +92,14 @@ export class Queue {
35
92
  this.tracks.splice(i, 0, track);
36
93
  }
37
94
 
38
- /** Insert multiple tracks at a specific upcoming position, preserving order */
95
+ /**
96
+ * Insert multiple tracks at a specific upcoming position, preserving order
97
+ *
98
+ * @param {Track[]} tracks - Tracks to insert
99
+ * @param {number} index - Index to insert the tracks at
100
+ * @example
101
+ * queue.insertMultiple([track1, track2, track3], 0);
102
+ */
39
103
  insertMultiple(tracks: Track[], index: number): void {
40
104
  if (!Array.isArray(tracks) || tracks.length === 0) return;
41
105
  if (!Number.isFinite(index)) {
@@ -54,11 +118,30 @@ export class Queue {
54
118
  this.tracks.splice(i, 0, ...tracks);
55
119
  }
56
120
 
121
+ /**
122
+ * Remove a track from the queue
123
+ *
124
+ * @param {number} index - Index of track to remove
125
+ * @returns {Track | null} Removed track or null
126
+ * @example
127
+ * const removed = queue.remove(0);
128
+ * console.log(`Removed: ${removed?.title}`);
129
+ */
130
+
57
131
  remove(index: number): Track | null {
58
132
  if (index < 0 || index >= this.tracks.length) return null;
59
133
  return this.tracks.splice(index, 1)[0];
60
134
  }
61
135
 
136
+ /**
137
+ * Get the next track in the queue
138
+ *
139
+ * @param {boolean} ignoreLoop - Ignore the loop mode
140
+ * @returns {Track | null} The next track or null
141
+ * @example
142
+ * const nextTrack = queue.next();
143
+ * console.log(`Next track: ${nextTrack?.title}`);
144
+ */
62
145
  next(ignoreLoop = false): Track | null {
63
146
  if (this.current) {
64
147
  if (this._loop === "track" && !ignoreLoop) {
@@ -78,10 +161,26 @@ export class Queue {
78
161
  return this.current;
79
162
  }
80
163
 
164
+ /**
165
+ * Clear all tracks from the queue
166
+ *
167
+ * @example
168
+ * queue.clear();
169
+ */
81
170
  clear(): void {
82
171
  this.tracks = [];
83
172
  }
84
173
 
174
+ /**
175
+ * Enable or disable auto-play
176
+ *
177
+ * @param {boolean} value - Enable/disable auto-play
178
+ * @returns {boolean} Current auto-play state
179
+ * @example
180
+ * queue.autoPlay(true);
181
+ * queue.autoPlay(); // Get current auto-play state
182
+ */
183
+
85
184
  autoPlay(value?: boolean): boolean {
86
185
  if (typeof value !== "undefined") {
87
186
  this._autoPlay = value;
@@ -89,6 +188,14 @@ export class Queue {
89
188
  return this._autoPlay;
90
189
  }
91
190
 
191
+ /**
192
+ * Set the loop mode
193
+ *
194
+ * @param {LoopMode} mode - Loop mode to set
195
+ * @returns {LoopMode} The loop mode
196
+ * @example
197
+ * queue.loop("track");
198
+ */
92
199
  loop(mode?: LoopMode): LoopMode {
93
200
  if (mode) {
94
201
  this._loop = mode;
@@ -96,6 +203,13 @@ export class Queue {
96
203
  return this._loop;
97
204
  }
98
205
 
206
+ /**
207
+ * Shuffle the queue
208
+ *
209
+ * @example
210
+ * queue.shuffle();
211
+ */
212
+
99
213
  shuffle(): void {
100
214
  for (let i = this.tracks.length - 1; i > 0; i--) {
101
215
  const j = Math.floor(Math.random() * (i + 1));
@@ -103,22 +217,62 @@ export class Queue {
103
217
  }
104
218
  }
105
219
 
220
+ /**
221
+ * Get the size of the queue
222
+ *
223
+ * @returns {number} The size of the queue
224
+ * @example
225
+ * const size = queue.size;
226
+ * console.log(`Queue size: ${size}`);
227
+ */
106
228
  get size(): number {
107
229
  return this.tracks.length;
108
230
  }
109
231
 
232
+ /**
233
+ * Check if the queue is empty
234
+ *
235
+ * @returns {boolean} True if the queue is empty
236
+ * @example
237
+ * const empty = queue.isEmpty;
238
+ * console.log(`Queue is empty: ${empty}`);
239
+ */
110
240
  get isEmpty(): boolean {
111
241
  return this.tracks.length === 0;
112
242
  }
113
243
 
244
+ /**
245
+ * Get the current track
246
+ *
247
+ * @returns {Track | null} The current track or null
248
+ * @example
249
+ * const currentTrack = queue.currentTrack;
250
+ * console.log(`Current track: ${currentTrack?.title}`);
251
+ */
114
252
  get currentTrack(): Track | null {
115
253
  return this.current;
116
254
  }
117
255
 
256
+ /**
257
+ * Get the previous tracks
258
+ *
259
+ * @returns {Track[]} The previous tracks
260
+ * @example
261
+ * const previousTracks = queue.previousTracks;
262
+ * console.log(`Previous tracks: ${previousTracks.length}`);
263
+ */
118
264
  get previousTracks(): Track[] {
119
265
  return [...this.history];
120
266
  }
121
267
 
268
+ /**
269
+ * Get the next track
270
+ *
271
+ * @returns {Track | null} The next track or null
272
+ * @example
273
+ * const nextTrack = queue.nextTrack;
274
+ * console.log(`Next track: ${nextTrack?.title}`);
275
+ */
122
276
  get nextTrack(): Track | null {
123
277
  return this.tracks[0] || null;
124
278
  }
@@ -126,6 +280,11 @@ export class Queue {
126
280
  /**
127
281
  * Move back to the previously played track.
128
282
  * Makes the current track the next upcoming track, then sets previous as current.
283
+ *
284
+ * @returns {Track | null} The previous track or null
285
+ * @example
286
+ * const previousTrack = queue.previous();
287
+ * console.log(`Previous track: ${previousTrack?.title}`);
129
288
  */
130
289
  previous(): Track | null {
131
290
  if (this.history.length === 0) return null;
@@ -136,6 +295,15 @@ export class Queue {
136
295
  return this.current;
137
296
  }
138
297
 
298
+ /**
299
+ * Get the next track
300
+ *
301
+ * @param {Track} track - The next track
302
+ * @returns {Track | null} The next track or null
303
+ * @example
304
+ * const nextTrack = queue.willNextTrack();
305
+ * console.log(`Next track: ${nextTrack?.title}`);
306
+ */
139
307
  willNextTrack(track?: Track): Track | null {
140
308
  if (track) {
141
309
  this.willnext = track;
@@ -143,6 +311,15 @@ export class Queue {
143
311
  return this.willnext;
144
312
  }
145
313
 
314
+ /**
315
+ * Get the related tracks
316
+ *
317
+ * @param {Track[]} track - The related tracks
318
+ * @returns {Track[] | null} The related tracks or null
319
+ * @example
320
+ * const relatedTracks = queue.relatedTracks();
321
+ * console.log(`Related tracks: ${relatedTracks?.length}`);
322
+ */
146
323
  relatedTracks(track?: Track[]): Track[] | null {
147
324
  if (track) {
148
325
  this.related = track;
@@ -150,10 +327,27 @@ export class Queue {
150
327
  return this.related;
151
328
  }
152
329
 
330
+ /**
331
+ * Get the tracks
332
+ *
333
+ * @returns {Track[]} The tracks
334
+ * @example
335
+ * const tracks = queue.getTracks();
336
+ * console.log(`Tracks: ${tracks.length}`);
337
+ */
153
338
  getTracks(): Track[] {
154
339
  return [...this.tracks];
155
340
  }
156
341
 
342
+ /**
343
+ * Get a track at a specific index
344
+ *
345
+ * @param {number} index - The index of the track
346
+ * @returns {Track | null} The track or null
347
+ * @example
348
+ * const track = queue.getTrack(0);
349
+ * console.log(`Track: ${track?.title}`);
350
+ */
157
351
  getTrack(index: number): Track | null {
158
352
  return this.tracks[index] || null;
159
353
  }