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/README.md CHANGED
@@ -1,524 +1,526 @@
1
- <img width="1175" height="305" alt="logo" src="https://raw.githubusercontent.com/ZiProject/ZiPlayer/refs/heads/main/publish/logo.png" />
2
-
3
- # ZiPlayer
4
-
5
- A powerful, extensible Discord music engine built on top of `@discordjs/voice`, designed for scalability, flexibility, and
6
- developer experience.
7
-
8
- ZiPlayer is not just a player — it's a **full ecosystem** with plugins, extensions, and a modular architecture that lets you build
9
- advanced music bots quickly.
10
-
11
- ---
12
-
13
- ## ✨ Highlights
14
-
15
- - 🔌 **Plugin-driven architecture** — Easily support new audio sources
16
- - 🌐 **Multi-source playback** — YouTube, SoundCloud, Spotify (with fallback), TTS, and more
17
- - 🧠 **Smart fallback system** — Automatically resolves streams across plugins
18
- - 🎛️ **Advanced audio filters** — Real-time FFmpeg effects (bassboost, nightcore, etc.)
19
- - 🔁 **Autoplay & looping** — Seamless listening experience
20
- - 🧩 **Extension system** — Add STT, lyrics, Lavalink, and custom logic
21
- - 🗂️ **Per-guild player system** — Scales across multiple Discord servers
22
- - 📡 **Event-driven core** — Full lifecycle hooks for customization
23
- - 💾 **Custom userdata** — Attach context to each player
24
- - 💿 **Persistence** — Auto-save and restore player state across restarts
25
- - **Smart caching** — Search and stream caching for better performance
26
- - 🎯 **Queue management** Advanced queue operations (move, swap, batch remove)
27
-
28
- ---
29
-
30
- ## 📦 Installation
31
-
32
- ```bash
33
- npm install ziplayer @ziplayer/plugin @ziplayer/extension @ziplayer/infinity @discordjs/voice discord.js opusscript
34
- ```
35
-
36
- ---
37
-
38
- ## 🚀 Quick Start
39
-
40
- ```ts
41
- import { Client, GatewayIntentBits } from "discord.js";
42
- import { PlayerManager } from "ziplayer";
43
- import { YouTubePlugin, SoundCloudPlugin, SpotifyPlugin } from "@ziplayer/plugin";
44
- import { InfinityPlugin } from "@ziplayer/infinity";
45
-
46
- const client = new Client({
47
- intents: [
48
- GatewayIntentBits.Guilds,
49
- GatewayIntentBits.GuildVoiceStates,
50
- GatewayIntentBits.GuildMessages,
51
- GatewayIntentBits.MessageContent,
52
- ],
53
- });
54
-
55
- const manager = new PlayerManager({
56
- plugins: [new YouTubePlugin(), new SoundCloudPlugin(), new SpotifyPlugin(), new InfinityPlugin()],
57
- });
58
-
59
- client.on("messageCreate", async (msg) => {
60
- if (!msg.content.startsWith("!play ") || !msg.guildId) return;
61
-
62
- const voiceChannel = msg.member?.voice?.channel;
63
- if (!voiceChannel) return msg.reply("Join a voice channel first!");
64
-
65
- const player = await manager.create(msg.guildId, {
66
- leaveOnEnd: true,
67
- userdata: { channel: msg.channel },
68
- });
69
-
70
- if (!player.connection) await player.connect(voiceChannel);
71
- await player.play(msg.content.slice(6), msg.author.id);
72
- });
73
-
74
- client.login(process.env.DISCORD_TOKEN);
75
- ```
76
-
77
- ---
78
-
79
- ## 🧱 Architecture Overview
80
-
81
- ```
82
- PlayerManager (global)
83
- ├── PersistenceManager (auto-save/load)
84
- └── Player (per guild)
85
- ├── Queue (advanced operations)
86
- ├── PluginManager (with caching & fallback)
87
- ├── ExtensionManager (with priority & caching)
88
- └── FilterManager (FFmpeg filters)
89
- ```
90
-
91
- ### Flow
92
-
93
- ```
94
- create connect → play → stream → events → destroy
95
-
96
- auto-save (periodic)
97
-
98
- restore on restart
99
- ```
100
-
101
- ---
102
-
103
- ## 🎵 Core Usage
104
-
105
- ### Play music
106
-
107
- ```ts
108
- await player.play("Never Gonna Give You Up", userId);
109
- await player.play("https://youtube.com/watch?v=...", userId);
110
- await player.play("tts: Hello world", userId);
111
- await player.play(searchResult, userId); // Play from SearchResult
112
- await player.play(null); // Resume from queue
113
- ```
114
-
115
- ### Controls
116
-
117
- ```ts
118
- player.pause();
119
- player.resume();
120
- player.skip();
121
- player.skip(2); // Skip to track at index 2
122
- player.stop();
123
- player.setVolume(100);
124
- player.loop("track"); // Loop current track
125
- player.loop("queue"); // Loop entire queue
126
- player.loop(1); // Number mode: 0=off, 1=track, 2=queue
127
- player.shuffle();
128
- player.seek(30000); // Seek to 30 seconds
129
- player.previous(); // Go back to previous track
130
- ```
131
-
132
- ### Queue Management
133
-
134
- ```ts
135
- // Basic operations
136
- player.queue.add(track);
137
- player.queue.addMultiple([track1, track2]);
138
- player.queue.remove(0);
139
- player.queue.removeMultiple([0, 2, 5]); // Remove multiple indices
140
- player.queue.removeWhere((t) => t.source === "youtube"); // Remove by condition
141
- player.queue.clear();
142
-
143
- // Queue manipulation
144
- player.queue.move(3, 0); // Move track at index 3 to front
145
- player.queue.swap(1, 3); // Swap positions 1 and 3
146
- player.queue.shuffle();
147
-
148
- // Queue inspection
149
- player.queue.size;
150
- player.queue.isEmpty;
151
- player.queue.currentTrack;
152
- player.queue.nextTrack;
153
- player.queue.lastTrack;
154
- player.queue.previousTracks;
155
- player.queue.getTrack(5);
156
- player.queue.findTracks((t) => t.duration > 300000);
157
- player.queue.indexOf(track);
158
- player.queue.has(track);
159
-
160
- // History navigation
161
- player.queue.jumpToHistory(2); // Go back 2 tracks
162
- ```
163
-
164
- ---
165
-
166
- ## 💾 Persistence (Auto-save & Restore)
167
-
168
- Automatically save and restore player state across bot restarts.
169
-
170
- ### Setup
171
-
172
- ```ts
173
- const manager = new PlayerManager({
174
- plugins: [new YouTubePlugin()],
175
- persistence: {
176
- enabled: true,
177
- filePath: "./player_data",
178
-
179
- // Backup management
180
- maxBackups: 3, // Keep only 3 backups per player
181
- maxTotalBackups: 20, // Keep max 20 total backup files
182
- autoCleanupBackupsOnStart: true, // Clean old backups on startup
183
- backupRetentionDays: 3, // Delete backups older than 3 days
184
-
185
- // Auto restore
186
- autoRestoreOnRestart: true,
187
- restoreDelay: 3000,
188
-
189
- compress: true,
190
- },
191
- });
192
-
193
- // Listen to persistence events
194
- manager.on("playerSaved", (guildId) => console.log(`Saved ${guildId}`));
195
- manager.on("playerLoaded", (guildId, data) => console.log(`Loaded ${guildId} from ${new Date(data.lastUpdate)}`));
196
- ```
197
-
198
- ### Manual Persistence
199
-
200
- ```ts
201
- // Save specific player
202
- await manager.savePlayer(guildId);
203
- await player.save(); // From player instance
204
-
205
- // Save all players
206
- await manager.saveAllPlayers();
207
-
208
- // Load players
209
- await manager.loadPlayer(guildId, true); // Restore playback position
210
- await manager.loadAllPlayers();
211
-
212
- // Delete saved data
213
- const persistence = manager.getPersistence();
214
- await persistence?.deletePlayer(guildId);
215
- await persistence?.restoreBackup(guildId); // Restore from backup
216
- ```
217
-
218
- ---
219
-
220
- ## 🔌 Plugins
221
-
222
- Install via `@ziplayer/plugin`:
223
-
224
- - **YouTubePlugin** YouTube + search
225
- - **SoundCloudPlugin** — SoundCloud streaming
226
- - **SpotifyPlugin** — Metadata (uses fallback)
227
- - **TTSPlugin** — Text-to-speech
228
- - **AttachmentsPlugin** — Local/URL audio files
229
-
230
- ### Example
231
-
232
- ```ts
233
- import { TTSPlugin } from "@ziplayer/plugin";
234
-
235
- new PlayerManager({
236
- plugins: [new TTSPlugin({ defaultLang: "en" })],
237
- });
238
- ```
239
-
240
- ### Dynamic Plugin Registration
241
-
242
- ```ts
243
- // Register plugin after initialization
244
- manager.registerPlugin(new YouTubePlugin());
245
-
246
- // Get all registered plugins
247
- const plugins = manager.getPlugins();
248
- ```
249
-
250
- ---
251
-
252
- ## 🧩 Extensions
253
-
254
- Enhance player behavior:
255
-
256
- - 🎤 `voiceExt` — Speech-to-text commands
257
- - 🎤 `lyricsExt` Auto lyrics (synced support)
258
- - ⚡ `lavalinkExt` — External Lavalink node
259
-
260
- ### Example
261
-
262
- ```ts
263
- import { voiceExt, lyricsExt } from "@ziplayer/extension";
264
-
265
- const manager = new PlayerManager({
266
- extensions: [new voiceExt(null, { lang: "en-US" }), new lyricsExt(null, { provider: "lrclib" })],
267
- });
268
- ```
269
-
270
- ### Extension Capabilities
271
-
272
- Extensions can now provide:
273
-
274
- - **Search** — Custom search handling
275
- - **Stream** — Custom stream sources (Lavalink, etc.)
276
- - **Before/After play hooks** — Modify playback behavior
277
-
278
- ---
279
-
280
- ## 🎛️ Audio Filters
281
-
282
- Apply FFmpeg filters in real-time:
283
-
284
- ```ts
285
- await player.filter.applyFilter("bassboost");
286
- await player.filter.applyFilter("nightcore");
287
- await player.filter.applyFilters(["bassboost", "trebleboost"]); // Multiple filters
288
- await player.filter.getFilterString(); // "bassboost,trebleboost"
289
- await player.filter.clearAll();
290
- ```
291
-
292
- ### Available filters
293
-
294
- - bassboost, trebleboost
295
- - nightcore, lofi, vaporwave
296
- - echo, reverb, chorus
297
- - karaoke
298
- - normalize, compressor, limiter
299
-
300
- ---
301
-
302
- ## 🔊 TTS (Interrupt Mode)
303
-
304
- ```ts
305
- const player = await manager.create(guildId, {
306
- tts: {
307
- createPlayer: true,
308
- interrupt: true,
309
- volume: 100,
310
- maxTimeTts: 60000,
311
- },
312
- });
313
-
314
- await player.play("tts: Hello everyone", userId);
315
- ```
316
-
317
- ---
318
-
319
- ## 📡 Events
320
-
321
- Listen globally via manager:
322
-
323
- ```ts
324
- manager.on("trackStart", (player, track) => {});
325
- manager.on("trackEnd", (player, track) => {});
326
- manager.on("queueEnd", (player) => {});
327
- manager.on("playerError", (player, error, track) => {});
328
- manager.on("playerPause", (player, track) => {});
329
- manager.on("playerResume", (player, track) => {});
330
- manager.on("volumeChange", (player, oldVolume, newVolume) => {});
331
- manager.on("queueAdd", (player, track) => {});
332
- manager.on("queueAddList", (player, tracks) => {});
333
- manager.on("queueRemove", (player, track, index) => {});
334
- manager.on("playerDestroy", (player) => {});
335
- manager.on("ttsStart", (player, payload) => {});
336
- manager.on("ttsEnd", (player) => {});
337
-
338
- // Persistence events
339
- manager.on("playerSaved", (guildId) => {});
340
- manager.on("playerLoaded", (guildId, data) => {});
341
- manager.on("savedAll", (results) => {});
342
- manager.on("loadedAll", (results) => {});
343
- ```
344
-
345
- ---
346
-
347
- ## 🧠 Advanced Features
348
-
349
- ### Autoplay
350
-
351
- ```ts
352
- player.queue.autoPlay(true);
353
- ```
354
-
355
- ### Insert next track
356
-
357
- ```ts
358
- await player.insert("song", 0); // Insert at position 0 (play next)
359
- await player.insert([track1, track2], 2); // Insert multiple at index 2
360
- ```
361
-
362
- ### Save stream to file
363
-
364
- ```ts
365
- const stream = await player.save(track);
366
- stream.pipe(fs.createWriteStream("song.mp3"));
367
-
368
- // Save with filters
369
- const filteredStream = await player.save(track, {
370
- filter: ["bassboost"],
371
- seek: 30000, // Start from 30 seconds
372
- });
373
- ```
374
-
375
- ### Progress Bar
376
-
377
- ```ts
378
- // Default (compact time format)
379
- console.log(player.getProgressBar());
380
- // Output: "1:22:12 ▬▬▬▬▬▬▬▬▬▬🔘▬▬▬▬▬▬▬▬ 1:45:30"
381
-
382
- // Custom options
383
- console.log(
384
- player.getProgressBar({
385
- size: 30,
386
- barChar: "─",
387
- progressChar: "●",
388
- timeFormat: "full", // "full" or "compact"
389
- showPercentage: true,
390
- }),
391
- );
392
- // Output: "01:22:12 ───────●───────────────────── 01:45:30 (47%)"
393
- ```
394
-
395
- ### Time Formatting
396
-
397
- ```ts
398
- const time = player.getTime();
399
- console.log(time.formatted.current); // "1:22:12" (compact)
400
- console.log(time.format); // "01:22:12" (full with leading zeros)
401
- ```
402
-
403
- ### Batch Operations
404
-
405
- ```ts
406
- // Broadcast action to all players
407
- manager.broadcast("setVolume", 50);
408
- manager.broadcast("pause");
409
-
410
- // Get players by filter
411
- const activePlayers = manager.getPlayersByFilter((p) => p.isPlaying);
412
-
413
- // Delete multiple players
414
- manager.deleteWhere((p) => p.queue.isEmpty && !p.isPlaying);
415
- ```
416
-
417
- ---
418
-
419
- ## ⚙️ Advanced Configuration
420
-
421
- ### PlayerManager Options
422
-
423
- ```ts
424
- const manager = new PlayerManager({
425
- plugins: [...],
426
- extensions: [...],
427
- extractorTimeout: 30000, // Timeout for stream extraction
428
- autoCleanup: true, // Auto cleanup inactive players
429
- cleanupInterval: 120000, // Cleanup interval (ms)
430
- enableSearchCache: true, // Cache search results
431
- enableStatsCollection: true, // Enable stats events
432
- persistence: {...} // Persistence configuration
433
- });
434
- ```
435
-
436
- ### Player Options
437
-
438
- ```ts
439
- const player = await manager.create(guildId, {
440
- volume: 100,
441
- quality: "high",
442
- leaveOnEnd: true,
443
- leaveOnEmpty: true,
444
- leaveTimeout: 100000,
445
- selfDeaf: true,
446
- selfMute: false,
447
- extractorTimeout: 50000,
448
- filters: ["bassboost", "nightcore"],
449
- tts: {
450
- createPlayer: false,
451
- interrupt: true,
452
- volume: 100,
453
- maxTimeTts: 60000,
454
- },
455
- userdata: { customField: "value" },
456
- });
457
- ```
458
-
459
- ---
460
-
461
- ## 📊 Monitoring & Stats
462
-
463
- ```ts
464
- // Get manager statistics
465
- const stats = manager.getStats();
466
- console.log({
467
- totalPlayers: stats.totalPlayers,
468
- activePlayers: stats.activePlayers,
469
- pausedPlayers: stats.pausedPlayers,
470
- connectedPlayers: stats.connectedPlayers,
471
- totalTracksInQueue: stats.totalTracksInQueue,
472
- });
473
-
474
- // Get plugin/extension stats
475
- console.log(manager.getConfig());
476
- console.log(player.pluginManager.getStats());
477
- console.log(player.extensionManager.getStats());
478
-
479
- // Clear caches
480
- player.clearSearchCache();
481
- player.extensionManager.clearCache("search");
482
- ```
483
-
484
- ---
485
-
486
- ## ⚠️ Best Practices
487
-
488
- - Use **one PlayerManager** per bot
489
- - Always `await player.connect()` before playing
490
- - Handle `playerError` events
491
- - Do not reuse a destroyed player
492
- - Enable **persistence** for production bots to survive restarts
493
- - Use **autoCleanup** to prevent memory leaks
494
- - Set appropriate **extractorTimeout** based on your network (default: 10-50 seconds)
495
-
496
- ---
497
-
498
- ## 🌟 Migration Guide
499
-
500
- ### From v1.x to v2.x
501
-
502
- - `player.getTime()` now returns `{ current, total, format, formatted }`
503
- - `player.getProgressBar()` supports new options
504
- - `player.queue.remove(index)` removed track is now returned
505
- - New `queue.removeMultiple()`, `queue.move()`, `queue.swap()` methods
506
- - Extension hooks now support async properly
507
-
508
- ---
509
-
510
- ## 📚 Resources
511
-
512
- - Examples: [https://github.com/ZiProject/ZiPlayer/tree/main/examples](https://github.com/ZiProject/ZiPlayer/tree/main/examples)
513
- - GitHub: [https://github.com/ZiProject/ZiPlayer](https://github.com/ZiProject/ZiPlayer)
514
- - npm: [https://www.npmjs.com/package/ziplayer](https://www.npmjs.com/package/ziplayer)
515
-
516
- ---
517
-
518
- ## 📄 License
519
-
520
- MIT License
521
-
522
- ```
523
-
524
- ```
1
+ <img width="1175" height="305" alt="logo" src="https://raw.githubusercontent.com/ZiProject/ZiPlayer/refs/heads/main/publish/logo.png" />
2
+
3
+ # ZiPlayer
4
+
5
+ A powerful, extensible Discord music engine built on top of `@discordjs/voice`, designed for scalability, flexibility, and
6
+ developer experience.
7
+
8
+ ZiPlayer is not just a player — it's a **full ecosystem** with plugins, extensions, and a modular architecture that lets you build
9
+ advanced music bots quickly.
10
+
11
+ ---
12
+
13
+ ## ✨ Highlights
14
+
15
+ - 🔌 **Plugin-driven architecture** — Easily support new audio sources
16
+ - 🌐 **Multi-source playback** — YouTube, SoundCloud, Spotify (with fallback), TTS, and more
17
+ - 🧠 **Smart fallback system** — Automatically resolves streams across plugins
18
+ - 🎛️ **Advanced audio filters** — Real-time FFmpeg effects (bassboost, nightcore, etc.)
19
+ - 🔁 **Autoplay & looping** — Seamless listening experience
20
+ - 🧩 **Extension system** — Add STT, lyrics, Lavalink, and custom logic
21
+ - 🗂️ **Per-guild player system** — Scales across multiple Discord servers
22
+ - 📡 **Event-driven core** — Full lifecycle hooks for customization
23
+ - 💾 **Custom userdata** — Attach context to each player
24
+ - **Smart caching** — Search and stream caching for better performance
25
+ - 🎯 **Queue management** — Advanced queue operations (move, swap, batch remove)
26
+ - 💹 **Preload** - Auto Preload next Track
27
+ - 🔃 **Crossfade** - Suport crossfade for new/slip Track
28
+ - 🧠 **Transition Engine** - BPM/genre-aware crossfade (chill → long fade, EDM → short fade) with beat-aligned entry instead of
29
+ blind time-based fading
30
+ - 🔄 **Anti-Stuck Recovery 2.0** - Automatic stream failure recovery: reuse preload → fallback plugin → reduce quality →
31
+ controlled skip (no chaotic skipping)
32
+ - 🔊 **Loudness Normalization** - LUFS-based normalization prevents sudden volume jumps between tracks, with gentle limiter to
33
+ avoid distortion
34
+
35
+ ---
36
+
37
+ ## 📦 Installation
38
+
39
+ ```bash
40
+ npm install ziplayer @ziplayer/plugin @ziplayer/extension @ziplayer/infinity @discordjs/voice discord.js opusscript
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 🚀 Quick Start
46
+
47
+ ```ts
48
+ import { Client, GatewayIntentBits } from "discord.js";
49
+ import { PlayerManager } from "ziplayer";
50
+ import { YouTubePlugin, SoundCloudPlugin, SpotifyPlugin } from "@ziplayer/plugin";
51
+ import { InfinityPlugin } from "@ziplayer/infinity";
52
+
53
+ const client = new Client({
54
+ intents: [
55
+ GatewayIntentBits.Guilds,
56
+ GatewayIntentBits.GuildVoiceStates,
57
+ GatewayIntentBits.GuildMessages,
58
+ GatewayIntentBits.MessageContent,
59
+ ],
60
+ });
61
+
62
+ const manager = new PlayerManager({
63
+ plugins: [new YouTubePlugin(), new SoundCloudPlugin(), new SpotifyPlugin(), new InfinityPlugin()],
64
+ });
65
+
66
+ client.on("messageCreate", async (msg) => {
67
+ if (!msg.content.startsWith("!play ") || !msg.guildId) return;
68
+
69
+ const voiceChannel = msg.member?.voice?.channel;
70
+ if (!voiceChannel) return msg.reply("Join a voice channel first!");
71
+
72
+ const player = await manager.create(msg.guildId, {
73
+ leaveOnEnd: true,
74
+ userdata: { channel: msg.channel },
75
+ });
76
+
77
+ if (!player.connection) await player.connect(voiceChannel);
78
+ await player.play(msg.content.slice(6), msg.author.id);
79
+ });
80
+
81
+ client.login(process.env.DISCORD_TOKEN);
82
+ ```
83
+
84
+ ---
85
+
86
+ ## 🧱 Architecture Overview
87
+
88
+ ```
89
+ PlayerManager (global)
90
+ └── Player (per guild)
91
+ ├── Queue (advanced operations)
92
+ ├── PluginManager (with caching & fallback)
93
+ ├── ExtensionManager (with priority & caching)
94
+ └── FilterManager (FFmpeg filters)
95
+ ```
96
+
97
+ ### Flow
98
+
99
+ ```
100
+ create → connect → play → stream → events → destroy
101
+
102
+ auto-save (periodic)
103
+
104
+ restore on restart
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 🎵 Core Usage
110
+
111
+ ### Play music
112
+
113
+ ```ts
114
+ await player.play("Never Gonna Give You Up", userId);
115
+ await player.play("https://youtube.com/watch?v=...", userId);
116
+ await player.play("tts: Hello world", userId);
117
+ await player.play(searchResult, userId); // Play from SearchResult
118
+ await player.play(null); // Resume from queue
119
+ ```
120
+
121
+ ### Controls
122
+
123
+ ```ts
124
+ player.pause();
125
+ player.resume();
126
+ player.skip();
127
+ player.skip(2); // Skip to track at index 2
128
+ player.stop();
129
+ player.setVolume(100);
130
+ player.loop("track"); // Loop current track
131
+ player.loop("queue"); // Loop entire queue
132
+ player.loop(1); // Number mode: 0=off, 1=track, 2=queue
133
+ player.shuffle();
134
+ player.seek(30000); // Seek to 30 seconds
135
+ player.previous(); // Go back to previous track
136
+ ```
137
+
138
+ ### Queue Management
139
+
140
+ ```ts
141
+ // Basic operations
142
+ player.queue.add(track);
143
+ player.queue.addMultiple([track1, track2]);
144
+ player.queue.remove(0);
145
+ player.queue.removeMultiple([0, 2, 5]); // Remove multiple indices
146
+ player.queue.removeWhere((t) => t.source === "youtube"); // Remove by condition
147
+ player.queue.clear();
148
+
149
+ // Queue manipulation
150
+ player.queue.move(3, 0); // Move track at index 3 to front
151
+ player.queue.swap(1, 3); // Swap positions 1 and 3
152
+ player.queue.shuffle();
153
+
154
+ // Queue inspection
155
+ player.queue.size;
156
+ player.queue.isEmpty;
157
+ player.queue.currentTrack;
158
+ player.queue.nextTrack;
159
+ player.queue.lastTrack;
160
+ player.queue.previousTracks;
161
+ player.queue.getTrack(5);
162
+ player.queue.findTracks((t) => t.duration > 300000);
163
+ player.queue.indexOf(track);
164
+ player.queue.has(track);
165
+
166
+ // History navigation
167
+ player.queue.jumpToHistory(2); // Go back 2 tracks
168
+ ```
169
+
170
+ ---
171
+
172
+ ## 🔌 Plugins
173
+
174
+ Install via `@ziplayer/plugin`:
175
+
176
+ - **YouTubePlugin** — YouTube + search
177
+ - **SoundCloudPlugin** — SoundCloud streaming
178
+ - **SpotifyPlugin** — Metadata (uses fallback)
179
+ - **TTSPlugin** — Text-to-speech
180
+ - **AttachmentsPlugin** Local/URL audio files
181
+
182
+ ### Example
183
+
184
+ ```ts
185
+ import { TTSPlugin } from "@ziplayer/plugin";
186
+
187
+ new PlayerManager({
188
+ plugins: [new TTSPlugin({ defaultLang: "en" })],
189
+ });
190
+ ```
191
+
192
+ ### Dynamic Plugin Registration
193
+
194
+ ```ts
195
+ // Register plugin after initialization
196
+ manager.registerPlugin(new YouTubePlugin());
197
+
198
+ // Get all registered plugins
199
+ const plugins = manager.getPlugins();
200
+ ```
201
+
202
+ ---
203
+
204
+ ## 🧩 Extensions
205
+
206
+ Enhance player behavior:
207
+
208
+ - 🎤 `voiceExt` — Speech-to-text commands
209
+ - 🎤 `lyricsExt` Auto lyrics (synced support)
210
+ - ⚡ `lavalinkExt` — External Lavalink node
211
+
212
+ ### Example
213
+
214
+ ```ts
215
+ import { voiceExt, lyricsExt } from "@ziplayer/extension";
216
+
217
+ const manager = new PlayerManager({
218
+ extensions: [new voiceExt(null, { lang: "en-US" }), new lyricsExt(null, { provider: "lrclib" })],
219
+ });
220
+ ```
221
+
222
+ ### Extension Capabilities
223
+
224
+ Extensions can now provide:
225
+
226
+ - **Search** — Custom search handling
227
+ - **Stream** — Custom stream sources (Lavalink, etc.)
228
+ - **Before/After play hooks** — Modify playback behavior
229
+
230
+ ---
231
+
232
+ ## 🎛️ Audio Filters
233
+
234
+ Apply FFmpeg filters in real-time:
235
+
236
+ ```ts
237
+ await player.filter.applyFilter("bassboost");
238
+ await player.filter.applyFilter("nightcore");
239
+ await player.filter.applyFilters(["bassboost", "trebleboost"]); // Multiple filters
240
+ await player.filter.getFilterString(); // "bassboost,trebleboost"
241
+ await player.filter.clearAll();
242
+ ```
243
+
244
+ ### Available filters
245
+
246
+ - bassboost, trebleboost
247
+ - nightcore, lofi, vaporwave
248
+ - echo, reverb, chorus
249
+ - karaoke
250
+ - normalize, compressor, limiter
251
+
252
+ ---
253
+
254
+ ## 🔊 TTS (Interrupt Mode)
255
+
256
+ ```ts
257
+ const player = await manager.create(guildId, {
258
+ tts: {
259
+ createPlayer: true,
260
+ interrupt: true,
261
+ volume: 100,
262
+ maxTimeTts: 60000,
263
+ },
264
+ });
265
+
266
+ await player.play("tts: Hello everyone", userId);
267
+ ```
268
+
269
+ ---
270
+
271
+ ## 📡 Events
272
+
273
+ Listen globally via manager:
274
+
275
+ ```ts
276
+ manager.on("trackStart", (player, track) => {});
277
+ manager.on("trackEnd", (player, track) => {});
278
+ manager.on("queueEnd", (player) => {});
279
+ manager.on("playerError", (player, error, track) => {});
280
+ manager.on("playerPause", (player, track) => {});
281
+ manager.on("playerResume", (player, track) => {});
282
+ manager.on("volumeChange", (player, oldVolume, newVolume) => {});
283
+ manager.on("queueAdd", (player, track) => {});
284
+ manager.on("queueAddList", (player, tracks) => {});
285
+ manager.on("queueRemove", (player, track, index) => {});
286
+ manager.on("playerDestroy", (player) => {});
287
+ manager.on("ttsStart", (player, payload) => {});
288
+ manager.on("ttsEnd", (player) => {});
289
+ manager.on("stats", (PlayerStats) => {});
290
+ ```
291
+
292
+ ---
293
+
294
+ ## 🧠 Advanced Features
295
+
296
+ ### Autoplay
297
+
298
+ ```ts
299
+ player.queue.autoPlay(true);
300
+ ```
301
+
302
+ ### Insert next track
303
+
304
+ ```ts
305
+ await player.insert("song", 0); // Insert at position 0 (play next)
306
+ await player.insert([track1, track2], 2); // Insert multiple at index 2
307
+ ```
308
+
309
+ ### Save stream to file
310
+
311
+ ```ts
312
+ const stream = await player.save(track);
313
+ stream.pipe(fs.createWriteStream("song.mp3"));
314
+
315
+ // Save with filters
316
+ const filteredStream = await player.save(track, {
317
+ filter: ["bassboost"],
318
+ seek: 30000, // Start from 30 seconds
319
+ });
320
+ ```
321
+
322
+ ### Progress Bar
323
+
324
+ ```ts
325
+ // Default (compact time format)
326
+ console.log(player.getProgressBar());
327
+ // Output: "1:22:12 ▬▬▬▬▬▬▬▬▬▬🔘▬▬▬▬▬▬▬▬ 1:45:30"
328
+
329
+ // Custom options
330
+ console.log(
331
+ player.getProgressBar({
332
+ size: 30,
333
+ barChar: "",
334
+ progressChar: "",
335
+ timeFormat: "full", // "full" or "compact"
336
+ showPercentage: true,
337
+ }),
338
+ );
339
+ // Output: "01:22:12 ───────●───────────────────── 01:45:30 (47%)"
340
+ ```
341
+
342
+ ### Time Formatting
343
+
344
+ ```ts
345
+ const time = player.getTime();
346
+ console.log(time.formatted.current); // "1:22:12" (compact)
347
+ console.log(time.format); // "01:22:12" (full with leading zeros)
348
+ ```
349
+
350
+ ### Batch Operations
351
+
352
+ ```ts
353
+ // Broadcast action to all players
354
+ manager.broadcast("setVolume", 50);
355
+ manager.broadcast("pause");
356
+
357
+ // Get players by filter
358
+ const activePlayers = manager.getPlayersByFilter((p) => p.isPlaying);
359
+
360
+ // Delete multiple players
361
+ manager.deleteWhere((p) => p.queue.isEmpty && !p.isPlaying);
362
+ ```
363
+
364
+ ---
365
+
366
+ ## ⚙️ Advanced Configuration
367
+
368
+ ### PlayerManager Options
369
+
370
+ ```ts
371
+ const manager = new PlayerManager({
372
+ plugins: [...],
373
+ extensions: [...],
374
+ extractorTimeout: 30000, // Timeout for stream extraction
375
+ autoCleanup: true, // Auto cleanup inactive players
376
+ cleanupInterval: 120000, // Cleanup interval (ms)
377
+ enableSearchCache: true, // Cache search results
378
+ enableStatsCollection: true, // Enable stats events
379
+ persistence: {...} // Persistence configuration
380
+ });
381
+ ```
382
+
383
+ ### Player Options
384
+
385
+ ```ts
386
+ const player = await manager.create(guildId, {
387
+ volume: 100,
388
+ quality: "high",
389
+ leaveOnEnd: true,
390
+ leaveOnEmpty: true,
391
+ leaveTimeout: 100000,
392
+ selfDeaf: true,
393
+ selfMute: false,
394
+ extractorTimeout: 50000,
395
+ filters: ["bassboost", "nightcore"],
396
+ tts: {
397
+ createPlayer: false,
398
+ interrupt: true,
399
+ volume: 100,
400
+ maxTimeTts: 60000,
401
+ },
402
+ // Runtime profile
403
+ lowPerformance: false,
404
+ preload: {
405
+ enabled: true,
406
+ autoDisableInLowPerformance: true,
407
+ },
408
+ crossfade: {
409
+ enabled: undefined, // omit to let autoEnable decide
410
+ autoEnable: true,
411
+ autoDisableInLowPerformance: true,
412
+ durationMs: 5000,
413
+ },
414
+ smartTransition: {
415
+ enabled: true,
416
+ genreAware: true,
417
+ beatAlign: true,
418
+ baseDurationMs: 5000,
419
+ minDurationMs: 1200,
420
+ maxDurationMs: 8000,
421
+ genreDurations: { chill: 7000, edm: 2200 },
422
+ beatAlignMaxWaitMs: 1200,
423
+ },
424
+ antiStuck: {
425
+ enabled: true,
426
+ maxRetries: 2,
427
+ retryDelayMs: 900,
428
+ reusePreloadFirst: true,
429
+ reduceQualityOnRetry: true,
430
+ controlledSkipThreshold: 3,
431
+ },
432
+ loudnessNormalization: {
433
+ enabled: true,
434
+ targetLUFS: -14,
435
+ maxBoostDb: 8,
436
+ maxCutDb: 10,
437
+ limiterCeiling: 0.95,
438
+ },
439
+ userdata: { customField: "value" },
440
+ });
441
+ ```
442
+
443
+ ### Crossfade + Low Performance
444
+
445
+ ```ts
446
+ // Auto mode: crossfade/preload enabled unless lowPerformance is on
447
+ const player = await manager.create(guildId, {
448
+ lowPerformance: false,
449
+ preload: { enabled: true, autoDisableInLowPerformance: true },
450
+ crossfade: { autoEnable: true, autoDisableInLowPerformance: true, durationMs: 4000 },
451
+ });
452
+
453
+ // Low performance mode: auto disable preload and crossfade
454
+ const litePlayer = await manager.create(guildId, {
455
+ lowPerformance: true,
456
+ preload: { enabled: true, autoDisableInLowPerformance: true }, // resolved: disabled
457
+ crossfade: { autoEnable: true, autoDisableInLowPerformance: true }, // resolved: disabled
458
+ });
459
+ ```
460
+
461
+ > Crossfade is applied when switching to the next track and when calling `player.skip()`. Smart transition adapts fade by
462
+ > `metadata.genre` and can align to beat using `metadata.bpm`. Loudness normalization uses `metadata.lufs` when available and
463
+ > applies a limiter ceiling.
464
+
465
+ ---
466
+
467
+ ## 📊 Monitoring & Stats
468
+
469
+ ```ts
470
+ // Get manager statistics
471
+ const stats = manager.getStats();
472
+ console.log({
473
+ totalPlayers: stats.totalPlayers,
474
+ activePlayers: stats.activePlayers,
475
+ pausedPlayers: stats.pausedPlayers,
476
+ connectedPlayers: stats.connectedPlayers,
477
+ totalTracksInQueue: stats.totalTracksInQueue,
478
+ });
479
+
480
+ // Get plugin/extension stats
481
+ console.log(manager.getConfig());
482
+ console.log(player.pluginManager.getStats());
483
+ console.log(player.extensionManager.getStats());
484
+
485
+ // Clear caches
486
+ player.clearSearchCache();
487
+ player.extensionManager.clearCache("search");
488
+ ```
489
+
490
+ ---
491
+
492
+ ## ⚠️ Best Practices
493
+
494
+ - Use **one PlayerManager** per bot
495
+ - Always `await player.connect()` before playing
496
+ - Handle `playerError` events
497
+ - Do not reuse a destroyed player
498
+ - Enable **persistence** for production bots to survive restarts
499
+ - Use **autoCleanup** to prevent memory leaks
500
+ - Set appropriate **extractorTimeout** based on your network (default: 10-50 seconds)
501
+
502
+ ---
503
+
504
+ ## 🌟 Migration Guide
505
+
506
+ ### From v1.x to v2.x
507
+
508
+ - `player.getTime()` now returns `{ current, total, format, formatted }`
509
+ - `player.getProgressBar()` supports new options
510
+ - `player.queue.remove(index)` removed track is now returned
511
+ - New `queue.removeMultiple()`, `queue.move()`, `queue.swap()` methods
512
+ - Extension hooks now support async properly
513
+
514
+ ---
515
+
516
+ ## 📚 Resources
517
+
518
+ - Examples: [https://github.com/ZiProject/ZiPlayer/tree/main/examples](https://github.com/ZiProject/ZiPlayer/tree/main/examples)
519
+ - GitHub: [https://github.com/ZiProject/ZiPlayer](https://github.com/ZiProject/ZiPlayer)
520
+ - npm: [https://www.npmjs.com/package/ziplayer](https://www.npmjs.com/package/ziplayer)
521
+
522
+ ---
523
+
524
+ ## 📄 License
525
+
526
+ MIT License