ziplayer 0.2.7-dev.3 → 0.3.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.
Files changed (47) hide show
  1. package/AI-Guide.md +624 -607
  2. package/README.md +526 -524
  3. package/dist/plugins/index.d.ts +62 -12
  4. package/dist/plugins/index.d.ts.map +1 -1
  5. package/dist/plugins/index.js +497 -57
  6. package/dist/plugins/index.js.map +1 -1
  7. package/dist/structures/PersistenceManager.d.ts +96 -0
  8. package/dist/structures/PersistenceManager.d.ts.map +1 -0
  9. package/dist/structures/PersistenceManager.js +1008 -0
  10. package/dist/structures/PersistenceManager.js.map +1 -0
  11. package/dist/structures/Player.d.ts +109 -18
  12. package/dist/structures/Player.d.ts.map +1 -1
  13. package/dist/structures/Player.js +902 -182
  14. package/dist/structures/Player.js.map +1 -1
  15. package/dist/structures/PlayerManager.d.ts +1 -22
  16. package/dist/structures/PlayerManager.d.ts.map +1 -1
  17. package/dist/structures/PlayerManager.js +1 -73
  18. package/dist/structures/PlayerManager.js.map +1 -1
  19. package/dist/structures/StreamManager.d.ts +137 -0
  20. package/dist/structures/StreamManager.d.ts.map +1 -0
  21. package/dist/structures/StreamManager.js +420 -0
  22. package/dist/structures/StreamManager.js.map +1 -0
  23. package/dist/types/index.d.ts +149 -16
  24. package/dist/types/index.d.ts.map +1 -1
  25. package/dist/types/index.js +0 -1
  26. package/dist/types/index.js.map +1 -1
  27. package/dist/types/persistence.d.ts +3 -2
  28. package/dist/types/persistence.d.ts.map +1 -1
  29. package/package.json +47 -47
  30. package/src/extensions/BaseExtension.ts +36 -36
  31. package/src/extensions/index.ts +473 -473
  32. package/src/index.ts +16 -16
  33. package/src/plugins/BasePlugin.ts +27 -27
  34. package/src/plugins/index.ts +950 -403
  35. package/src/structures/FilterManager.ts +303 -303
  36. package/src/structures/Player.ts +2797 -1970
  37. package/src/structures/PlayerManager.ts +725 -822
  38. package/src/structures/Queue.ts +599 -599
  39. package/src/structures/StreamManager.ts +524 -0
  40. package/src/types/extension.ts +129 -129
  41. package/src/types/fillter.ts +264 -264
  42. package/src/types/index.ts +548 -415
  43. package/src/types/plugin.ts +59 -59
  44. package/src/utils/timeout.ts +10 -10
  45. package/tsconfig.json +22 -22
  46. package/src/persistence/PersistenceManager.ts +0 -1077
  47. package/src/types/persistence.ts +0 -85
package/AI-Guide.md CHANGED
@@ -1,607 +1,624 @@
1
- # 🤖 AI Guide for ZiPlayer
2
-
3
- A comprehensive guide for AI assistants and developers working with ZiPlayer - a powerful Discord music player library.
4
-
5
- ## 📋 Table of Contents
6
-
7
- 1. [Project Overview](#project-overview)
8
- 2. [Architecture](#architecture)
9
- 3. [Core Concepts](#core-concepts)
10
- 4. [API Reference](#api-reference)
11
- 5. [Common Patterns](#common-patterns)
12
- 6. [Troubleshooting](#troubleshooting)
13
- 7. [Code Examples](#code-examples)
14
-
15
- ---
16
-
17
- ## 🎯 Project Overview
18
-
19
- **ZiPlayer** is an extensible Discord music engine built on `@discordjs/voice`.
20
-
21
- ### Key Features
22
-
23
- - Plugin-driven architecture (YouTube, SoundCloud, Spotify, TTS)
24
- - Extension system (voice commands, lyrics, Lavalink)
25
- - Audio filters (bassboost, nightcore, etc.)
26
- - Auto-save/restore (persistence)
27
- - Smart caching and fallback system
28
-
29
- ### Tech Stack
30
-
31
- - TypeScript
32
- - `@discordjs/voice` for audio
33
- - FFmpeg for audio processing
34
- - Node.js EventEmitter for events
35
-
36
- ---
37
-
38
- ## 🧱 Architecture
39
-
40
- ```
41
-
42
- ┌─────────────────────────────────────────────────────────────┐ PlayerManager (Global)
43
- ┌─────────────────────────────────────────────────────────┐│ PersistenceManager (Auto-save) ││
44
- └─────────────────────────────────────────────────────────┘│ ┌────────────┴────────────┐ ▼ ▼ │ │ ┌──────────────┐
45
- ┌──────────────┐ Player 1 │ │ Player 2 │ │ │ │ (Guild A) │ (Guild B) │ │ │ └──────────────┘ └──────────────┘ │ │ │ │ │ │
46
- ┌───────┴────────┐ ┌───────┴────────┐ ▼ ▼ ▼ ▼ │ │ Queue Filter Queue Filter │ │ PluginMgr ExtMgr PluginMgr ExtMgr │
47
- └─────────────────────────────────────────────────────────────┘
48
-
49
- ```
50
-
51
- ### Component Responsibilities
52
-
53
- | Component | Responsibility |
54
- | -------------------- | ------------------------------------------------------ |
55
- | `PlayerManager` | Creates/manages players, global event bus, persistence |
56
- | `Player` | Per-guild audio playback, controls, event emission |
57
- | `Queue` | Track management, loop modes, history, auto-play |
58
- | `PluginManager` | Audio source resolution, streaming, fallback logic |
59
- | `ExtensionManager` | Custom hooks (search, stream, before/after play) |
60
- | `FilterManager` | FFmpeg audio effects |
61
- | `PersistenceManager` | Auto-save/restore player state |
62
-
63
- ---
64
-
65
- ## 🧠 Core Concepts
66
-
67
- ### 1. Player Lifecycle
68
-
69
- ```typescript
70
- // Create → Connect → Play → (Auto-save) → Destroy
71
- const player = await manager.create(guildId, options);
72
- await player.connect(voiceChannel);
73
- await player.play(query, userId);
74
- // ... auto-saves periodically
75
- player.destroy();
76
- ```
77
-
78
- ### 2. Queue Loop Modes
79
-
80
- ```typescript
81
- player.loop("off"); // No loop (default)
82
- player.loop("track"); // Repeat current track
83
- player.loop("queue"); // Repeat entire queue
84
- ```
85
-
86
- ### 3. Event Flow
87
-
88
- ```
89
- trackStart → playing → trackEnd → playNext → (loop/autoplay)
90
-
91
- queueEnd → leave
92
- ```
93
-
94
- ### 4. Plugin Priority & Fallback
95
-
96
- ```typescript
97
- // Plugins are tried in priority order (higher = first)
98
- // If primary fails, fallback plugins are attempted sequentially
99
- // Failed plugins don't block the queue
100
-
101
- plugin.priority = 10; // Higher priority
102
- ```
103
-
104
- ### 5. Caching Strategy
105
-
106
- | Cache Type | TTL | Purpose |
107
- | --------------- | ------- | --------------------------- |
108
- | Search cache | 2 min | Avoid duplicate API calls |
109
- | Stream cache | 5 min | Cache resolved streams |
110
- | Extension cache | 1-5 min | Extension operation results |
111
-
112
- ---
113
-
114
- ## 📚 API Reference
115
-
116
- ### PlayerManager
117
-
118
- #### Constructor Options
119
-
120
- ```typescript
121
- interface PlayerManagerOptions {
122
- plugins?: SourcePlugin[]; // Audio source plugins
123
- extensions?: BaseExtension[]; // Custom extensions
124
- extractorTimeout?: number; // Default: 10000ms
125
- autoCleanup?: boolean; // Default: true
126
- cleanupInterval?: number; // Default: 60000ms
127
- enableSearchCache?: boolean; // Default: true
128
- enableStatsCollection?: boolean; // Default: false
129
- persistence?: PersistenceOptions; // Auto-save config
130
- }
131
- ```
132
-
133
- #### Key Methods
134
-
135
- | Method | Description |
136
- | ---------------------------- | -------------------------- |
137
- | `create(guildId, options)` | Create new player |
138
- | `get(guildId)` | Get existing player |
139
- | `delete(guildId)` | Destroy and remove player |
140
- | `getAll()` | Get all players |
141
- | `broadcast(action, ...args)` | Send action to all players |
142
- | `saveAllPlayers()` | Manual save all players |
143
- | `loadAllPlayers()` | Manual load all players |
144
-
145
- ### Player
146
-
147
- #### Core Methods
148
-
149
- | Method | Description | Returns |
150
- | ------------------------------ | ----------------------- | ------------------- |
151
- | `play(query, userId)` | Play track/search/queue | `Promise<boolean>` |
152
- | `pause()` | Pause current | `boolean` |
153
- | `resume()` | Resume playback | `boolean` |
154
- | `skip(index?)` | Skip to next/index | `boolean` |
155
- | `stop()` | Stop and clear queue | `boolean` |
156
- | `seek(position)` | Seek to position (ms) | `Promise<boolean>` |
157
- | `previous()` | Play previous track | `Promise<boolean>` |
158
- | `setVolume(vol)` | Set volume (0-200) | `boolean` |
159
- | `loop(mode)` | Set loop mode | `LoopMode` |
160
- | `shuffle()` | Shuffle queue | `void` |
161
- | `insert(query, index, userId)` | Insert at position | `Promise<boolean>` |
162
- | `save(track, options)` | Save track to stream | `Promise<Readable>` |
163
-
164
- #### Getters
165
-
166
- ```typescript
167
- player.currentTrack; // Track | null
168
- player.queueSize; // number
169
- player.isPlaying; // boolean
170
- player.isPaused; // boolean
171
- player.volume; // number
172
- player.upcomingTracks; // Track[]
173
- player.previousTracks; // Track[]
174
- player.relatedTracks; // Track[] | null
175
- ```
176
-
177
- ### Queue
178
-
179
- #### Methods
180
-
181
- | Method | Description |
182
- | ------------------------- | -------------------- |
183
- | `add(track)` | Add single track |
184
- | `addMultiple(tracks)` | Add multiple tracks |
185
- | `insert(track, index)` | Insert at position |
186
- | `remove(index)` | Remove at index |
187
- | `removeMultiple(indices)` | Remove multiple |
188
- | `removeWhere(predicate)` | Remove by condition |
189
- | `move(from, to)` | Move track |
190
- | `swap(a, b)` | Swap tracks |
191
- | `shuffle()` | Randomize order |
192
- | `clear()` | Clear all tracks |
193
- | `loop(mode)` | Set loop mode |
194
- | `autoPlay(enabled)` | Enable/disable |
195
- | `previous()` | Get previous track |
196
- | `jumpToHistory(steps)` | Jump back in history |
197
-
198
- #### Properties
199
-
200
- ```typescript
201
- queue.size; // number
202
- queue.isEmpty; // boolean
203
- queue.currentTrack; // Track | null
204
- queue.nextTrack; // Track | null
205
- queue.lastTrack; // Track | null
206
- queue.previousTracks; // Track[]
207
- ```
208
-
209
- ### FilterManager
210
-
211
- ```typescript
212
- // Apply filters
213
- await player.filter.applyFilter("bassboost");
214
- await player.filter.applyFilters(["bassboost", "nightcore"]);
215
-
216
- // Available filters
217
- // bassboost, trebleboost, nightcore, lofi, vaporwave,
218
- // echo, reverb, chorus, karaoke, normalize, compressor, limiter
219
-
220
- // Clear filters
221
- await player.filter.clearAll();
222
- await player.filter.clear("bassboost");
223
-
224
- // Get current filters
225
- const filterString = player.filter.getFilterString(); // "bassboost,nightcore"
226
- ```
227
-
228
- ### PersistenceManager
229
-
230
- #### Options
231
-
232
- ```typescript
233
- interface PersistenceOptions {
234
- enabled: boolean; // Enable persistence
235
- provider: "file" | "redis" | "database";
236
- filePath?: string; // For file provider
237
- saveInterval?: number; // Auto-save interval (ms)
238
- autoLoad?: boolean; // Auto-load on start
239
- compress?: boolean; // Compress data
240
- maxBackups?: number; // Max backup files
241
- }
242
- ```
243
-
244
- #### Methods
245
-
246
- ```typescript
247
- const persistence = manager.getPersistence();
248
-
249
- await persistence.savePlayer(player);
250
- await persistence.saveAll();
251
- await persistence.loadPlayer(guildId, restorePosition);
252
- await persistence.loadAll();
253
- await persistence.deletePlayer(guildId);
254
- await persistence.restoreBackup(guildId, timestamp);
255
- ```
256
-
257
- ---
258
-
259
- ## 🔧 Common Patterns
260
-
261
- ### 1. Basic Music Bot Setup
262
-
263
- ```typescript
264
- import { Client, GatewayIntentBits } from "discord.js";
265
- import { PlayerManager } from "ziplayer";
266
- import { YouTubePlugin, SpotifyPlugin } from "@ziplayer/plugin";
267
-
268
- const client = new Client({
269
- intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
270
- });
271
-
272
- const manager = new PlayerManager({
273
- plugins: [new YouTubePlugin(), new SpotifyPlugin()],
274
- autoCleanup: true,
275
- persistence: {
276
- enabled: true,
277
- filePath: "./data/players",
278
- },
279
- });
280
-
281
- client.on("ready", async () => {
282
- // Auto-load saved players
283
- await manager.loadAllPlayers();
284
- });
285
-
286
- client.on("messageCreate", async (msg) => {
287
- if (msg.content.startsWith("!play")) {
288
- const player = await manager.create(msg.guildId);
289
- const voiceChannel = msg.member?.voice.channel;
290
-
291
- if (!player.connection) {
292
- await player.connect(voiceChannel);
293
- }
294
-
295
- const query = msg.content.slice(6);
296
- await player.play(query, msg.author.id);
297
- }
298
- });
299
- ```
300
-
301
- ### 2. Progress Bar with Time Format
302
-
303
- ```typescript
304
- // Get formatted time
305
- const time = player.getTime();
306
- console.log(`Current: ${time.formatted.current}`); // "1:22:12"
307
- console.log(`Total: ${time.formatted.total}`); // "3:45:30"
308
-
309
- // Progress bar
310
- const progressBar = player.getProgressBar({
311
- size: 20,
312
- barChar: "▬",
313
- progressChar: "🔘",
314
- timeFormat: "compact",
315
- showPercentage: true,
316
- });
317
- // Output: "1:22:12 ▬▬▬▬▬▬▬▬▬🔘▬▬▬▬▬▬▬▬ 3:45:30 (36%)"
318
- ```
319
-
320
- ### 3. Queue Management Commands
321
-
322
- ```typescript
323
- // Skip to specific track
324
- await player.skip(3); // Skip to index 3
325
-
326
- // Move track to front
327
- player.queue.move(5, 0);
328
-
329
- // Remove all tracks from specific source
330
- player.queue.removeWhere((t) => t.source === "soundcloud");
331
-
332
- // Jump back 2 tracks
333
- await player.queue.jumpToHistory(2);
334
-
335
- // Insert as next track
336
- await player.insert("song name", 0, userId);
337
- ```
338
-
339
- ### 4. Custom Plugin Implementation
340
-
341
- ```typescript
342
- import { BasePlugin, Track, StreamInfo } from "ziplayer";
343
-
344
- class CustomPlugin extends BasePlugin {
345
- name = "CustomPlugin";
346
- priority = 5;
347
-
348
- canHandle(query: string): boolean {
349
- return query.startsWith("custom:");
350
- }
351
-
352
- async search(query: string, requestedBy: string): Promise<SearchResult> {
353
- // Implementation
354
- return { tracks: [] };
355
- }
356
-
357
- async getStream(track: Track, signal: AbortSignal): Promise<StreamInfo> {
358
- // Return audio stream
359
- return { stream: readableStream, type: "arbitrary" };
360
- }
361
-
362
- async getRelatedTracks(track: Track): Promise<Track[]> {
363
- // Return recommendations
364
- return [];
365
- }
366
- }
367
- ```
368
-
369
- ### 5. Custom Extension Implementation
370
-
371
- ```typescript
372
- import { BaseExtension, ExtensionContext } from "ziplayer";
373
-
374
- class LoggerExtension extends BaseExtension {
375
- name = "Logger";
376
- priority = 10;
377
-
378
- async beforePlay(context: ExtensionContext, request: any) {
379
- console.log(`Playing: ${request.query}`);
380
- return { handled: false };
381
- }
382
-
383
- async afterPlay(context: ExtensionContext, payload: any) {
384
- if (payload.success) {
385
- console.log(`Successfully played ${payload.tracks?.length} tracks`);
386
- }
387
- }
388
- }
389
- ```
390
-
391
- ### 6. Event Handling
392
-
393
- ```typescript
394
- manager.on("trackStart", (player, track) => {
395
- console.log(`Now playing: ${track.title}`);
396
- });
397
-
398
- manager.on("queueEnd", (player) => {
399
- console.log("Queue finished!");
400
- });
401
-
402
- manager.on("playerError", (player, error, track) => {
403
- console.error(`Error on ${track?.title}:`, error.message);
404
- });
405
-
406
- manager.on("playerSaved", (guildId) => {
407
- console.log(`Saved state for guild ${guildId}`);
408
- });
409
-
410
- manager.on("stats", (stats) => {
411
- console.log(`Active players: ${stats.activePlayers}`);
412
- });
413
- ```
414
-
415
- ---
416
-
417
- ## 🐛 Troubleshooting
418
-
419
- ### Common Issues
420
-
421
- | Issue | Solution |
422
- | -------------------------- | ----------------------------------------------------------- |
423
- | **No audio** | Check `player.connection` exists, voice channel permissions |
424
- | **Plugin not working** | Verify `canHandle()` returns true, check priority |
425
- | **Filters not applying** | Call `refreshPlayerResource(true)` after applying |
426
- | **Persistence not saving** | Check `enabled: true`, file path writable |
427
- | **Memory leak** | Enable `autoCleanup`, call `player.destroy()` when done |
428
- | **Rate limiting** | Use search cache, increase `extractorTimeout` |
429
-
430
- ### Debug Mode
431
-
432
- ```typescript
433
- // Enable debug logging
434
- manager.on("debug", (message) => {
435
- console.log("[DEBUG]", message);
436
- });
437
-
438
- // Or check debug flag
439
- if (manager.debugEnabled) {
440
- // Debug-specific logic
441
- }
442
- ```
443
-
444
- ### Performance Tips
445
-
446
- 1. **Enable caching** for search and stream results
447
- 2. **Use persistence** to avoid re-fetching on restart
448
- 3. **Set appropriate timeouts** based on network conditions
449
- 4. **Batch operations** when modifying queue
450
- 5. **Destroy players** when no longer needed
451
-
452
- ---
453
-
454
- ## 📝 Code Examples
455
-
456
- ### Full Bot Example
457
-
458
- ```typescript
459
- import { Client, GatewayIntentBits, EmbedBuilder } from "discord.js";
460
- import { PlayerManager } from "ziplayer";
461
- import { YouTubePlugin, SpotifyPlugin, TTSPlugin } from "@ziplayer/plugin";
462
-
463
- const client = new Client({
464
- intents: [
465
- GatewayIntentBits.Guilds,
466
- GatewayIntentBits.GuildVoiceStates,
467
- GatewayIntentBits.GuildMessages,
468
- GatewayIntentBits.MessageContent,
469
- ],
470
- });
471
-
472
- const manager = new PlayerManager({
473
- plugins: [new YouTubePlugin(), new SpotifyPlugin(), new TTSPlugin()],
474
- autoCleanup: true,
475
- extractorTimeout: 30000,
476
- persistence: {
477
- enabled: true,
478
- filePath: "./player_data",
479
- saveInterval: 60000,
480
- autoLoad: true,
481
- },
482
- });
483
-
484
- client.on("messageCreate", async (msg) => {
485
- if (!msg.guildId || msg.author.bot) return;
486
-
487
- const args = msg.content.slice(1).split(" ");
488
- const command = args[0].toLowerCase();
489
- const query = args.slice(1).join(" ");
490
-
491
- const player = await manager.create(msg.guildId);
492
- const voiceChannel = msg.member?.voice.channel;
493
-
494
- switch (command) {
495
- case "play":
496
- if (!voiceChannel) return msg.reply("Join a voice channel!");
497
- if (!player.connection) await player.connect(voiceChannel);
498
- await player.play(query, msg.author.id);
499
- break;
500
-
501
- case "pause":
502
- player.pause();
503
- break;
504
-
505
- case "resume":
506
- player.resume();
507
- break;
508
-
509
- case "skip":
510
- player.skip();
511
- break;
512
-
513
- case "stop":
514
- player.stop();
515
- break;
516
-
517
- case "volume":
518
- const vol = parseInt(query);
519
- if (isNaN(vol)) return msg.reply("Volume must be a number!");
520
- player.setVolume(vol);
521
- break;
522
-
523
- case "queue":
524
- const tracks = player.upcomingTracks.slice(0, 10);
525
- const embed = new EmbedBuilder()
526
- .setTitle("Queue")
527
- .setDescription(tracks.map((t, i) => `${i + 1}. ${t.title}`).join("\n") || "Empty");
528
- msg.reply({ embeds: [embed] });
529
- break;
530
-
531
- case "nowplaying":
532
- const track = player.currentTrack;
533
- if (!track) return msg.reply("Nothing playing!");
534
-
535
- const progress = player.getProgressBar({ size: 15 });
536
- const time = player.getTime();
537
-
538
- const embed = new EmbedBuilder()
539
- .setTitle(track.title)
540
- .setURL(track.url)
541
- .setThumbnail(track.thumbnail)
542
- .setDescription(`\`${progress}\`\n${time.formatted.current} / ${time.formatted.total}`);
543
- msg.reply({ embeds: [embed] });
544
- break;
545
- }
546
- });
547
-
548
- client.login(process.env.DISCORD_TOKEN);
549
- ```
550
-
551
- ---
552
-
553
- ## 🔗 Quick Reference
554
-
555
- ### Import Paths
556
-
557
- ```typescript
558
- // Core
559
- import { PlayerManager, Player, Queue } from "ziplayer";
560
-
561
- // Types
562
- import type { Track, SearchResult, LoopMode, StreamInfo } from "ziplayer";
563
-
564
- // Plugins (external package)
565
- import { YouTubePlugin, SpotifyPlugin, TTSPlugin } from "@ziplayer/plugin";
566
-
567
- // infinity plugin support stream audio from YouTube, TikTok, Instagram, Twitter/X, SoundCloud, Reddit, Twitch, Bilibili, and 1000+ other sites
568
-
569
- import { InfinityPlugin } from "@ziplayer/infinity";
570
-
571
- // Extensions (external package)
572
- import { voiceExt, lyricsExt, lavalinkExt } from "@ziplayer/extension";
573
- ```
574
-
575
- ### Type Definitions
576
-
577
- ```typescript
578
- interface Track {
579
- id: string;
580
- title: string;
581
- url: string;
582
- source: string;
583
- duration: number;
584
- thumbnail?: string;
585
- requestedBy?: string;
586
- isLive?: boolean;
587
- }
588
-
589
- type LoopMode = "off" | "track" | "queue";
590
-
591
- interface SearchResult {
592
- tracks: Track[];
593
- playlist?: { name: string; url?: string };
594
- }
595
- ```
596
-
597
- ---
598
-
599
- ## 📖 Additional Resources
600
-
601
- - [GitHub Repository](https://github.com/ZiProject/ZiPlayer)
602
- - [npm Package](https://www.npmjs.com/package/ziplayer)
603
- - [Examples Folder](https://github.com/ZiProject/ZiPlayer/tree/main/examples)
604
-
605
- ---
606
-
607
- _This guide is maintained for AI assistants and developers. For questions or contributions, please open an issue on GitHub._
1
+ # 🤖 AI Guide for ZiPlayer
2
+
3
+ A comprehensive guide for AI assistants and developers working with ZiPlayer - a powerful Discord music player library.
4
+
5
+ ## 📋 Table of Contents
6
+
7
+ 1. [Project Overview](#project-overview)
8
+ 2. [Architecture](#architecture)
9
+ 3. [Core Concepts](#core-concepts)
10
+ 4. [API Reference](#api-reference)
11
+ 5. [Common Patterns](#common-patterns)
12
+ 6. [Troubleshooting](#troubleshooting)
13
+ 7. [Code Examples](#code-examples)
14
+
15
+ ---
16
+
17
+ ## 🎯 Project Overview
18
+
19
+ **ZiPlayer** is an extensible Discord music engine built on `@discordjs/voice`.
20
+
21
+ ### Key Features
22
+
23
+ - Plugin-driven architecture (YouTube, SoundCloud, Spotify, TTS)
24
+ - Extension system (voice commands, lyrics, Lavalink)
25
+ - Audio filters (bassboost, nightcore, etc.)
26
+ - Smart caching and fallback system
27
+
28
+ ### Tech Stack
29
+
30
+ - TypeScript
31
+ - `@discordjs/voice` for audio
32
+ - FFmpeg for audio processing
33
+ - Node.js EventEmitter for events
34
+
35
+ ---
36
+
37
+ ### Component Responsibilities
38
+
39
+ | Component | Responsibility |
40
+ | ------------------ | -------------------------------------------------- |
41
+ | `PlayerManager` | Creates/manages players, global event bus |
42
+ | `Player` | Per-guild audio playback, controls, event emission |
43
+ | `Queue` | Track management, loop modes, history, auto-play |
44
+ | `PluginManager` | Audio source resolution, streaming, fallback logic |
45
+ | `ExtensionManager` | Custom hooks (search, stream, before/after play) |
46
+ | `FilterManager` | FFmpeg audio effects |
47
+ | `StreamManager` | Centralized stream management/Auto cleanup |
48
+
49
+ ---
50
+
51
+ ## 🧠 Core Concepts
52
+
53
+ ### 1. Player Lifecycle
54
+
55
+ ```typescript
56
+ // Create Connect Play (Auto-save) → Destroy
57
+ const player = await manager.create(guildId, options);
58
+ await player.connect(voiceChannel);
59
+ await player.play(query, userId);
60
+ // ... auto-saves periodically
61
+ player.destroy();
62
+ ```
63
+
64
+ ### 2. Queue Loop Modes
65
+
66
+ ```typescript
67
+ player.loop("off"); // No loop (default)
68
+ player.loop("track"); // Repeat current track
69
+ player.loop("queue"); // Repeat entire queue
70
+ ```
71
+
72
+ ### 3. Event Flow
73
+
74
+ ```
75
+ trackStart → playing → trackEnd → playNext → (loop/autoplay)
76
+
77
+ queueEnd → leave
78
+ ```
79
+
80
+ ### 4. Plugin Priority & Fallback
81
+
82
+ ```typescript
83
+ // Plugins are tried in priority order (higher = first)
84
+ // If primary fails, fallback plugins are attempted sequentially
85
+ // Failed plugins don't block the queue
86
+
87
+ plugin.priority = 10; // Higher priority
88
+ ```
89
+
90
+ ### 5. Caching Strategy
91
+
92
+ | Cache Type | TTL | Purpose |
93
+ | --------------- | ------- | --------------------------- |
94
+ | Search cache | 2 min | Avoid duplicate API calls |
95
+ | Stream cache | 5 min | Cache resolved streams |
96
+ | Extension cache | 1-5 min | Extension operation results |
97
+
98
+ ---
99
+
100
+ ## 📚 API Reference
101
+
102
+ ### PlayerManager
103
+
104
+ #### Constructor Options
105
+
106
+ ```typescript
107
+ interface PlayerManagerOptions {
108
+ plugins?: SourcePlugin[]; // Audio source plugins
109
+ extensions?: BaseExtension[]; // Custom extensions
110
+ extractorTimeout?: number; // Default: 10000ms
111
+ autoCleanup?: boolean; // Default: true
112
+ cleanupInterval?: number; // Default: 60000ms
113
+ enableSearchCache?: boolean; // Default: true
114
+ enableStatsCollection?: boolean; // Default: false
115
+ }
116
+ ```
117
+
118
+ #### Player Runtime Options (Performance Profile)
119
+
120
+ ```typescript
121
+ interface PlayerOptions {
122
+ lowPerformance?: boolean; // Default: false (or true when quality === "low")
123
+ preload?: {
124
+ enabled?: boolean; // Default: true
125
+ autoDisableInLowPerformance?: boolean; // Default: true
126
+ };
127
+ crossfade?: {
128
+ enabled?: boolean; // Explicit on/off
129
+ autoEnable?: boolean; // Default: true when enabled is undefined
130
+ autoDisableInLowPerformance?: boolean; // Default: true
131
+ durationMs?: number; // Default: 5000
132
+ };
133
+ smartTransition?: {
134
+ enabled?: boolean;
135
+ genreAware?: boolean;
136
+ beatAlign?: boolean;
137
+ baseDurationMs?: number;
138
+ minDurationMs?: number;
139
+ maxDurationMs?: number;
140
+ genreDurations?: Record<string, number>;
141
+ beatAlignMaxWaitMs?: number;
142
+ };
143
+ antiStuck?: {
144
+ enabled?: boolean;
145
+ maxRetries?: number;
146
+ retryDelayMs?: number;
147
+ reusePreloadFirst?: boolean;
148
+ reduceQualityOnRetry?: boolean;
149
+ controlledSkipThreshold?: number;
150
+ };
151
+ loudnessNormalization?: {
152
+ enabled?: boolean;
153
+ targetLUFS?: number;
154
+ maxBoostDb?: number;
155
+ maxCutDb?: number;
156
+ limiterCeiling?: number;
157
+ };
158
+ }
159
+ ```
160
+
161
+ - If `lowPerformance=true`, preload and crossfade are auto-disabled by default.
162
+ - `crossfade.autoEnable=true` allows crossfade to be enabled automatically when `crossfade.enabled` is not explicitly set.
163
+ - You can still force behavior by setting `enabled` flags directly.
164
+ - Runtime behavior: crossfade is used for next-track transitions and `skip()`.
165
+ - Smart transition can tune fade by `metadata.genre` and beat-align by `metadata.bpm`.
166
+ - Loudness normalization uses `metadata.lufs` with limiter ceiling protection.
167
+ - Anti-stuck retries in-place before controlled skip to avoid skip storms.
168
+
169
+ #### Key Methods
170
+
171
+ | Method | Description |
172
+ | ---------------------------- | -------------------------- |
173
+ | `create(guildId, options)` | Create new player |
174
+ | `get(guildId)` | Get existing player |
175
+ | `delete(guildId)` | Destroy and remove player |
176
+ | `getAll()` | Get all players |
177
+ | `broadcast(action, ...args)` | Send action to all players |
178
+
179
+ ### Player
180
+
181
+ #### Core Methods
182
+
183
+ | Method | Description | Returns |
184
+ | ------------------------------ | ----------------------- | ------------------- |
185
+ | `play(query, userId)` | Play track/search/queue | `Promise<boolean>` |
186
+ | `pause()` | Pause current | `boolean` |
187
+ | `resume()` | Resume playback | `boolean` |
188
+ | `skip(index?)` | Skip to next/index | `boolean` |
189
+ | `stop()` | Stop and clear queue | `boolean` |
190
+ | `seek(position)` | Seek to position (ms) | `Promise<boolean>` |
191
+ | `previous()` | Play previous track | `Promise<boolean>` |
192
+ | `setVolume(vol)` | Set volume (0-200) | `boolean` |
193
+ | `loop(mode)` | Set loop mode | `LoopMode` |
194
+ | `shuffle()` | Shuffle queue | `void` |
195
+ | `insert(query, index, userId)` | Insert at position | `Promise<boolean>` |
196
+ | `save(track, options)` | Save track to stream | `Promise<Readable>` |
197
+
198
+ #### Getters
199
+
200
+ ```typescript
201
+ player.currentTrack; // Track | null
202
+ player.queueSize; // number
203
+ player.isPlaying; // boolean
204
+ player.isPaused; // boolean
205
+ player.volume; // number
206
+ player.upcomingTracks; // Track[]
207
+ player.previousTracks; // Track[]
208
+ player.relatedTracks; // Track[] | null
209
+ ```
210
+
211
+ ### Queue
212
+
213
+ #### Methods
214
+
215
+ | Method | Description |
216
+ | ------------------------- | -------------------- |
217
+ | `add(track)` | Add single track |
218
+ | `addMultiple(tracks)` | Add multiple tracks |
219
+ | `insert(track, index)` | Insert at position |
220
+ | `remove(index)` | Remove at index |
221
+ | `removeMultiple(indices)` | Remove multiple |
222
+ | `removeWhere(predicate)` | Remove by condition |
223
+ | `move(from, to)` | Move track |
224
+ | `swap(a, b)` | Swap tracks |
225
+ | `shuffle()` | Randomize order |
226
+ | `clear()` | Clear all tracks |
227
+ | `loop(mode)` | Set loop mode |
228
+ | `autoPlay(enabled)` | Enable/disable |
229
+ | `previous()` | Get previous track |
230
+ | `jumpToHistory(steps)` | Jump back in history |
231
+
232
+ #### Properties
233
+
234
+ ```typescript
235
+ queue.size; // number
236
+ queue.isEmpty; // boolean
237
+ queue.currentTrack; // Track | null
238
+ queue.nextTrack; // Track | null
239
+ queue.lastTrack; // Track | null
240
+ queue.previousTracks; // Track[]
241
+ ```
242
+
243
+ ### FilterManager
244
+
245
+ ```typescript
246
+ // Apply filters
247
+ await player.filter.applyFilter("bassboost");
248
+ await player.filter.applyFilters(["bassboost", "nightcore"]);
249
+
250
+ // Available filters
251
+ // bassboost, trebleboost, nightcore, lofi, vaporwave,
252
+ // echo, reverb, chorus, karaoke, normalize, compressor, limiter
253
+
254
+ // Clear filters
255
+ await player.filter.clearAll();
256
+ await player.filter.clear("bassboost");
257
+
258
+ // Get current filters
259
+ const filterString = player.filter.getFilterString(); // "bassboost,nightcore"
260
+ ```
261
+
262
+ ## 🔧 Common Patterns
263
+
264
+ ### 1. Basic Music Bot Setup
265
+
266
+ ```typescript
267
+ import { Client, GatewayIntentBits } from "discord.js";
268
+ import { PlayerManager } from "ziplayer";
269
+ import { YouTubePlugin, SpotifyPlugin } from "@ziplayer/plugin";
270
+
271
+ const client = new Client({
272
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
273
+ });
274
+
275
+ const manager = new PlayerManager({
276
+ plugins: [new YouTubePlugin(), new SpotifyPlugin()],
277
+ autoCleanup: true,
278
+ });
279
+
280
+ client.on("ready", async () => {
281
+ // Auto-load saved players
282
+ await manager.loadAllPlayers();
283
+ });
284
+
285
+ client.on("messageCreate", async (msg) => {
286
+ if (msg.content.startsWith("!play")) {
287
+ const player = await manager.create(msg.guildId);
288
+ const voiceChannel = msg.member?.voice.channel;
289
+
290
+ if (!player.connection) {
291
+ await player.connect(voiceChannel);
292
+ }
293
+
294
+ const query = msg.content.slice(6);
295
+ await player.play(query, msg.author.id);
296
+ }
297
+ });
298
+
299
+ const player = await manager.create(guildId, {
300
+ lowPerformance: false,
301
+ preload: { enabled: true, autoDisableInLowPerformance: true },
302
+ crossfade: { autoEnable: true, autoDisableInLowPerformance: true, durationMs: 5000 },
303
+ smartTransition: {
304
+ enabled: true,
305
+ genreAware: true,
306
+ beatAlign: true,
307
+ baseDurationMs: 5000,
308
+ genreDurations: { chill: 7000, edm: 2200 },
309
+ },
310
+ antiStuck: {
311
+ enabled: true,
312
+ maxRetries: 2,
313
+ retryDelayMs: 900,
314
+ reusePreloadFirst: true,
315
+ reduceQualityOnRetry: true,
316
+ controlledSkipThreshold: 3,
317
+ },
318
+ loudnessNormalization: {
319
+ enabled: true,
320
+ targetLUFS: -14,
321
+ limiterCeiling: 0.95,
322
+ },
323
+ });
324
+ ```
325
+
326
+ ### 2. Progress Bar with Time Format
327
+
328
+ ```typescript
329
+ // Get formatted time
330
+ const time = player.getTime();
331
+ console.log(`Current: ${time.formatted.current}`); // "1:22:12"
332
+ console.log(`Total: ${time.formatted.total}`); // "3:45:30"
333
+
334
+ // Progress bar
335
+ const progressBar = player.getProgressBar({
336
+ size: 20,
337
+ barChar: "▬",
338
+ progressChar: "🔘",
339
+ timeFormat: "compact",
340
+ showPercentage: true,
341
+ });
342
+ // Output: "1:22:12 ▬▬▬▬▬▬▬▬▬🔘▬▬▬▬▬▬▬▬ 3:45:30 (36%)"
343
+ ```
344
+
345
+ ### 3. Queue Management Commands
346
+
347
+ ```typescript
348
+ // Skip to specific track
349
+ await player.skip(3); // Skip to index 3
350
+
351
+ // Move track to front
352
+ player.queue.move(5, 0);
353
+
354
+ // Remove all tracks from specific source
355
+ player.queue.removeWhere((t) => t.source === "soundcloud");
356
+
357
+ // Jump back 2 tracks
358
+ await player.queue.jumpToHistory(2);
359
+
360
+ // Insert as next track
361
+ await player.insert("song name", 0, userId);
362
+ ```
363
+
364
+ ### 4. Custom Plugin Implementation
365
+
366
+ ```typescript
367
+ import { BasePlugin, Track, StreamInfo } from "ziplayer";
368
+
369
+ class CustomPlugin extends BasePlugin {
370
+ name = "CustomPlugin";
371
+ priority = 5;
372
+
373
+ canHandle(query: string): boolean {
374
+ return query.startsWith("custom:");
375
+ }
376
+
377
+ async search(query: string, requestedBy: string): Promise<SearchResult> {
378
+ // Implementation
379
+ return { tracks: [] };
380
+ }
381
+
382
+ async getStream(track: Track, signal: AbortSignal): Promise<StreamInfo> {
383
+ // Return audio stream
384
+ return { stream: readableStream, type: "arbitrary" };
385
+ }
386
+
387
+ async getRelatedTracks(track: Track): Promise<Track[]> {
388
+ // Return recommendations
389
+ return [];
390
+ }
391
+ }
392
+ ```
393
+
394
+ ### 5. Custom Extension Implementation
395
+
396
+ ```typescript
397
+ import { BaseExtension, ExtensionContext } from "ziplayer";
398
+
399
+ class LoggerExtension extends BaseExtension {
400
+ name = "Logger";
401
+ priority = 10;
402
+
403
+ async beforePlay(context: ExtensionContext, request: any) {
404
+ console.log(`Playing: ${request.query}`);
405
+ return { handled: false };
406
+ }
407
+
408
+ async afterPlay(context: ExtensionContext, payload: any) {
409
+ if (payload.success) {
410
+ console.log(`Successfully played ${payload.tracks?.length} tracks`);
411
+ }
412
+ }
413
+ }
414
+ ```
415
+
416
+ ### 6. Event Handling
417
+
418
+ ```typescript
419
+ manager.on("trackStart", (player, track) => {
420
+ console.log(`Now playing: ${track.title}`);
421
+ });
422
+
423
+ manager.on("queueEnd", (player) => {
424
+ console.log("Queue finished!");
425
+ });
426
+
427
+ manager.on("playerError", (player, error, track) => {
428
+ console.error(`Error on ${track?.title}:`, error.message);
429
+ });
430
+
431
+ manager.on("playerSaved", (guildId) => {
432
+ console.log(`Saved state for guild ${guildId}`);
433
+ });
434
+
435
+ manager.on("stats", (stats) => {
436
+ console.log(`Active players: ${stats.activePlayers}`);
437
+ });
438
+ ```
439
+
440
+ ---
441
+
442
+ ## 🐛 Troubleshooting
443
+
444
+ ### Common Issues
445
+
446
+ | Issue | Solution |
447
+ | ------------------------ | ----------------------------------------------------------- |
448
+ | **No audio** | Check `player.connection` exists, voice channel permissions |
449
+ | **Plugin not working** | Verify `canHandle()` returns true, check priority |
450
+ | **Filters not applying** | Call `refreshPlayerResource(true)` after applying |
451
+ | **Memory leak** | Enable `autoCleanup`, call `player.destroy()` when done |
452
+ | **Rate limiting** | Use search cache, increase `extractorTimeout` |
453
+
454
+ ### Debug Mode
455
+
456
+ ```typescript
457
+ // Enable debug logging
458
+ manager.on("debug", (message) => {
459
+ console.log("[DEBUG]", message);
460
+ });
461
+
462
+ // Or check debug flag
463
+ if (manager.debugEnabled) {
464
+ // Debug-specific logic
465
+ }
466
+ ```
467
+
468
+ ### Performance Tips
469
+
470
+ 1. **Enable caching** for search and stream results
471
+ 2. **Set appropriate timeouts** based on network conditions
472
+ 3. **Batch operations** when modifying queue
473
+ 4. **Destroy players** when no longer needed
474
+
475
+ ---
476
+
477
+ ## 📝 Code Examples
478
+
479
+ ### Full Bot Example
480
+
481
+ ```typescript
482
+ import { Client, GatewayIntentBits, EmbedBuilder } from "discord.js";
483
+ import { PlayerManager } from "ziplayer";
484
+ import { YouTubePlugin, SpotifyPlugin, TTSPlugin } from "@ziplayer/plugin";
485
+
486
+ const client = new Client({
487
+ intents: [
488
+ GatewayIntentBits.Guilds,
489
+ GatewayIntentBits.GuildVoiceStates,
490
+ GatewayIntentBits.GuildMessages,
491
+ GatewayIntentBits.MessageContent,
492
+ ],
493
+ });
494
+
495
+ const manager = new PlayerManager({
496
+ plugins: [new YouTubePlugin(), new SpotifyPlugin(), new TTSPlugin()],
497
+ autoCleanup: true,
498
+ extractorTimeout: 30000,
499
+ });
500
+
501
+ client.on("messageCreate", async (msg) => {
502
+ if (!msg.guildId || msg.author.bot) return;
503
+
504
+ const args = msg.content.slice(1).split(" ");
505
+ const command = args[0].toLowerCase();
506
+ const query = args.slice(1).join(" ");
507
+
508
+ const player = await manager.create(msg.guildId);
509
+ const voiceChannel = msg.member?.voice.channel;
510
+
511
+ switch (command) {
512
+ case "play":
513
+ if (!voiceChannel) return msg.reply("Join a voice channel!");
514
+ if (!player.connection) await player.connect(voiceChannel);
515
+ await player.play(query, msg.author.id);
516
+ break;
517
+
518
+ case "pause":
519
+ player.pause();
520
+ break;
521
+
522
+ case "resume":
523
+ player.resume();
524
+ break;
525
+
526
+ case "skip":
527
+ player.skip();
528
+ break;
529
+
530
+ case "stop":
531
+ player.stop();
532
+ break;
533
+
534
+ case "volume":
535
+ const vol = parseInt(query);
536
+ if (isNaN(vol)) return msg.reply("Volume must be a number!");
537
+ player.setVolume(vol);
538
+ break;
539
+
540
+ case "queue":
541
+ const tracks = player.upcomingTracks.slice(0, 10);
542
+ const embed = new EmbedBuilder()
543
+ .setTitle("Queue")
544
+ .setDescription(tracks.map((t, i) => `${i + 1}. ${t.title}`).join("\n") || "Empty");
545
+ msg.reply({ embeds: [embed] });
546
+ break;
547
+
548
+ case "nowplaying":
549
+ const track = player.currentTrack;
550
+ if (!track) return msg.reply("Nothing playing!");
551
+
552
+ const progress = player.getProgressBar({ size: 15 });
553
+ const time = player.getTime();
554
+
555
+ const embed = new EmbedBuilder()
556
+ .setTitle(track.title)
557
+ .setURL(track.url)
558
+ .setThumbnail(track.thumbnail)
559
+ .setDescription(`\`${progress}\`\n${time.formatted.current} / ${time.formatted.total}`);
560
+ msg.reply({ embeds: [embed] });
561
+ break;
562
+ }
563
+ });
564
+
565
+ client.login(process.env.DISCORD_TOKEN);
566
+ ```
567
+
568
+ ---
569
+
570
+ ## 🔗 Quick Reference
571
+
572
+ ### Import Paths
573
+
574
+ ```typescript
575
+ // Core
576
+ import { PlayerManager, Player, Queue } from "ziplayer";
577
+
578
+ // Types
579
+ import type { Track, SearchResult, LoopMode, StreamInfo } from "ziplayer";
580
+
581
+ // Plugins (external package)
582
+ import { YouTubePlugin, SpotifyPlugin, TTSPlugin } from "@ziplayer/plugin";
583
+
584
+ // infinity plugin support stream audio from YouTube, TikTok, Instagram, Twitter/X, SoundCloud, Reddit, Twitch, Bilibili, and 1000+ other sites
585
+
586
+ import { InfinityPlugin } from "@ziplayer/infinity";
587
+
588
+ // Extensions (external package)
589
+ import { voiceExt, lyricsExt, lavalinkExt } from "@ziplayer/extension";
590
+ ```
591
+
592
+ ### Type Definitions
593
+
594
+ ```typescript
595
+ interface Track {
596
+ id: string;
597
+ title: string;
598
+ url: string;
599
+ source: string;
600
+ duration: number;
601
+ thumbnail?: string;
602
+ requestedBy?: string;
603
+ isLive?: boolean;
604
+ }
605
+
606
+ type LoopMode = "off" | "track" | "queue";
607
+
608
+ interface SearchResult {
609
+ tracks: Track[];
610
+ playlist?: { name: string; url?: string };
611
+ }
612
+ ```
613
+
614
+ ---
615
+
616
+ ## 📖 Additional Resources
617
+
618
+ - [GitHub Repository](https://github.com/ZiProject/ZiPlayer)
619
+ - [npm Package](https://www.npmjs.com/package/ziplayer)
620
+ - [Examples Folder](https://github.com/ZiProject/ZiPlayer/tree/main/examples)
621
+
622
+ ---
623
+
624
+ _This guide is maintained for AI assistants and developers. For questions or contributions, please open an issue on GitHub._