ziplayer 0.3.2 → 0.3.4
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.
- package/{AI-Guide.md → AGENTS.md} +717 -624
- package/README.md +658 -526
- package/dist/extensions/BaseExtension.d.ts +10 -1
- package/dist/extensions/BaseExtension.d.ts.map +1 -1
- package/dist/extensions/BaseExtension.js +27 -1
- package/dist/extensions/BaseExtension.js.map +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +24 -6
- package/dist/extensions/index.js.map +1 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +105 -51
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/Player.d.ts +90 -15
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +487 -81
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PlayerManager.d.ts +70 -6
- package/dist/structures/PlayerManager.d.ts.map +1 -1
- package/dist/structures/PlayerManager.js +184 -19
- package/dist/structures/PlayerManager.js.map +1 -1
- package/dist/structures/Queue.d.ts +19 -0
- package/dist/structures/Queue.d.ts.map +1 -1
- package/dist/structures/Queue.js +21 -0
- package/dist/structures/Queue.js.map +1 -1
- package/dist/types/extension.d.ts +3 -0
- package/dist/types/extension.d.ts.map +1 -1
- package/dist/types/index.d.ts +69 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/extensions/BaseExtension.ts +31 -1
- package/src/extensions/index.ts +30 -7
- package/src/plugins/index.ts +137 -54
- package/src/structures/Player.ts +2937 -2457
- package/src/structures/PlayerManager.ts +916 -725
- package/src/structures/Queue.ts +621 -599
- package/src/types/extension.ts +3 -0
- package/src/types/index.ts +80 -2
package/README.md
CHANGED
|
@@ -1,526 +1,658 @@
|
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
await player.play("
|
|
115
|
-
await player.play("
|
|
116
|
-
await player.play(
|
|
117
|
-
await player.play(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
player.
|
|
125
|
-
player.
|
|
126
|
-
player.skip();
|
|
127
|
-
player.
|
|
128
|
-
player.
|
|
129
|
-
player.
|
|
130
|
-
player.loop("
|
|
131
|
-
player.loop(
|
|
132
|
-
player.
|
|
133
|
-
player.
|
|
134
|
-
player.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
player.queue.
|
|
143
|
-
player.queue.
|
|
144
|
-
player.queue.
|
|
145
|
-
player.queue.
|
|
146
|
-
player.queue.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
//
|
|
150
|
-
player.queue.
|
|
151
|
-
player.queue.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
player.queue.
|
|
156
|
-
player.queue.
|
|
157
|
-
player.queue.
|
|
158
|
-
player.queue.
|
|
159
|
-
player.queue.
|
|
160
|
-
player.queue.
|
|
161
|
-
player.queue.
|
|
162
|
-
player.queue.
|
|
163
|
-
player.queue.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
- **
|
|
177
|
-
- **
|
|
178
|
-
- **
|
|
179
|
-
- **
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
new
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
- 🎤 `
|
|
209
|
-
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
- **
|
|
227
|
-
- **
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
###
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
###
|
|
351
|
-
|
|
352
|
-
```ts
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
```ts
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
```ts
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
+
- ⚡ **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
|
+
- 🧪 **Track middleware (extensions)** — Transform or enrich tracks before streaming (for example fill `metadata.bpm`,
|
|
35
|
+
`metadata.lufs`, `metadata.genre` from an audio-analysis HTTP API instead of manual entry)
|
|
36
|
+
- 📻 **Multi-guild broadcast** — Fan out the same Player API calls to every active guild with `manager.broadcast()` (shared
|
|
37
|
+
controls / mirrored sessions across servers)
|
|
38
|
+
- 🎛️ **Playback Mirror / Forward Mode** - "forward mode", where the follower player directly subscribes to the leader player's
|
|
39
|
+
instead of creating its own stream.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 📦 Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install ziplayer @ziplayer/plugin @ziplayer/extension @ziplayer/infinity @discordjs/voice discord.js opusscript
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🚀 Quick Start
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { Client, GatewayIntentBits } from "discord.js";
|
|
55
|
+
import { PlayerManager } from "ziplayer";
|
|
56
|
+
import { YouTubePlugin, SoundCloudPlugin, SpotifyPlugin } from "@ziplayer/plugin";
|
|
57
|
+
import { InfinityPlugin } from "@ziplayer/infinity";
|
|
58
|
+
|
|
59
|
+
const client = new Client({
|
|
60
|
+
intents: [
|
|
61
|
+
GatewayIntentBits.Guilds,
|
|
62
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
63
|
+
GatewayIntentBits.GuildMessages,
|
|
64
|
+
GatewayIntentBits.MessageContent,
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const manager = new PlayerManager({
|
|
69
|
+
plugins: [new YouTubePlugin(), new SoundCloudPlugin(), new SpotifyPlugin(), new InfinityPlugin()],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
client.on("messageCreate", async (msg) => {
|
|
73
|
+
if (!msg.content.startsWith("!play ") || !msg.guildId) return;
|
|
74
|
+
|
|
75
|
+
const voiceChannel = msg.member?.voice?.channel;
|
|
76
|
+
if (!voiceChannel) return msg.reply("Join a voice channel first!");
|
|
77
|
+
|
|
78
|
+
const player = await manager.create(msg.guildId, {
|
|
79
|
+
leaveOnEnd: true,
|
|
80
|
+
userdata: { channel: msg.channel },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!player.connection) await player.connect(voiceChannel);
|
|
84
|
+
await player.play(msg.content.slice(6), msg.author.id);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
client.login(process.env.DISCORD_TOKEN);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🧱 Architecture Overview
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
PlayerManager (global)
|
|
96
|
+
└── Player (per guild)
|
|
97
|
+
├── Queue (advanced operations)
|
|
98
|
+
├── PluginManager (with caching & fallback)
|
|
99
|
+
├── ExtensionManager (with priority & caching)
|
|
100
|
+
├── StreamManager (Store & Manage streams)
|
|
101
|
+
├── PreloadManager (Preload next tracks)
|
|
102
|
+
└── FilterManager (FFmpeg filters)
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 🎵 Core Usage
|
|
109
|
+
|
|
110
|
+
### Play music
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
await player.play("Never Gonna Give You Up", userId);
|
|
114
|
+
await player.play("https://youtube.com/watch?v=...", userId);
|
|
115
|
+
await player.play("tts: Hello world", userId);
|
|
116
|
+
await player.play(searchResult, userId); // Play from SearchResult
|
|
117
|
+
await player.play(null); // Resume from queue
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Controls
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
player.pause();
|
|
124
|
+
player.resume();
|
|
125
|
+
player.skip();
|
|
126
|
+
player.skip(2); // Skip to track at index 2
|
|
127
|
+
player.stop();
|
|
128
|
+
player.setVolume(100);
|
|
129
|
+
player.loop("track"); // Loop current track
|
|
130
|
+
player.loop("queue"); // Loop entire queue
|
|
131
|
+
player.loop(1); // Number mode: 0=off, 1=track, 2=queue
|
|
132
|
+
player.shuffle();
|
|
133
|
+
player.seek(30000); // Seek to 30 seconds
|
|
134
|
+
player.previous(); // Go back to previous track
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Queue Management
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// Basic operations
|
|
141
|
+
player.queue.add(track);
|
|
142
|
+
player.queue.addMultiple([track1, track2]);
|
|
143
|
+
player.queue.remove(0);
|
|
144
|
+
player.queue.removeMultiple([0, 2, 5]); // Remove multiple indices
|
|
145
|
+
player.queue.removeWhere((t) => t.source === "youtube"); // Remove by condition
|
|
146
|
+
player.queue.clear();
|
|
147
|
+
|
|
148
|
+
// Queue manipulation
|
|
149
|
+
player.queue.move(3, 0); // Move track at index 3 to front
|
|
150
|
+
player.queue.swap(1, 3); // Swap positions 1 and 3
|
|
151
|
+
player.queue.shuffle();
|
|
152
|
+
|
|
153
|
+
// Queue inspection
|
|
154
|
+
player.queue.size;
|
|
155
|
+
player.queue.isEmpty;
|
|
156
|
+
player.queue.currentTrack;
|
|
157
|
+
player.queue.nextTrack;
|
|
158
|
+
player.queue.lastTrack;
|
|
159
|
+
player.queue.previousTracks;
|
|
160
|
+
player.queue.getTrack(5);
|
|
161
|
+
player.queue.findTracks((t) => t.duration > 300000);
|
|
162
|
+
player.queue.indexOf(track);
|
|
163
|
+
player.queue.has(track);
|
|
164
|
+
|
|
165
|
+
// History navigation
|
|
166
|
+
player.queue.jumpToHistory(2); // Go back 2 tracks
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 🔌 Plugins
|
|
172
|
+
|
|
173
|
+
Install via `@ziplayer/plugin`:
|
|
174
|
+
|
|
175
|
+
- **YouTubePlugin** — YouTube + search
|
|
176
|
+
- **SoundCloudPlugin** — SoundCloud streaming
|
|
177
|
+
- **SpotifyPlugin** — Metadata (uses fallback)
|
|
178
|
+
- **TTSPlugin** — Text-to-speech
|
|
179
|
+
- **AttachmentsPlugin** — Local/URL audio files
|
|
180
|
+
|
|
181
|
+
### Example
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import { TTSPlugin } from "@ziplayer/plugin";
|
|
185
|
+
|
|
186
|
+
new PlayerManager({
|
|
187
|
+
plugins: [new TTSPlugin({ defaultLang: "en" })],
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Dynamic Plugin Registration
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// Register plugin after initialization
|
|
195
|
+
manager.registerPlugin(new YouTubePlugin());
|
|
196
|
+
|
|
197
|
+
// Get all registered plugins
|
|
198
|
+
const plugins = manager.getPlugins();
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 🧩 Extensions
|
|
204
|
+
|
|
205
|
+
Enhance player behavior:
|
|
206
|
+
|
|
207
|
+
- 🎤 `voiceExt` — Speech-to-text commands
|
|
208
|
+
- 🎤 `lyricsExt` — Auto lyrics (synced support)
|
|
209
|
+
- ⚡ `lavalinkExt` — External Lavalink node
|
|
210
|
+
|
|
211
|
+
### Example
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { voiceExt, lyricsExt } from "@ziplayer/extension";
|
|
215
|
+
|
|
216
|
+
const manager = new PlayerManager({
|
|
217
|
+
extensions: [new voiceExt(null, { lang: "en-US" }), new lyricsExt(null, { provider: "lrclib" })],
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Extension Capabilities
|
|
222
|
+
|
|
223
|
+
Extensions can now provide:
|
|
224
|
+
|
|
225
|
+
- **Search** — Custom search handling
|
|
226
|
+
- **Stream** — Custom stream sources (Lavalink, etc.)
|
|
227
|
+
- **Before/After play hooks** — Modify playback behavior
|
|
228
|
+
|
|
229
|
+
### Track middleware (metadata before stream)
|
|
230
|
+
|
|
231
|
+
Core exposes **`trackMiddleware`** on **`PlayerManager`** options and **`Player`** options: an ordered chain of async/sync
|
|
232
|
+
functions `(track, { player, manager }) => void | Track`. They run **once per stream resolution**, immediately before extension
|
|
233
|
+
`provideStream` and plugins — including preload and `player.save()`.
|
|
234
|
+
|
|
235
|
+
Prefer mutating **`track.metadata`** in place. If you return a **new** object, its enumerable fields (and merged `metadata`) are
|
|
236
|
+
copied onto the original track reference so queue/current-track pointers stay stable.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
const manager = new PlayerManager({
|
|
240
|
+
plugins: [...],
|
|
241
|
+
trackMiddleware: async (track, { player }) => {
|
|
242
|
+
const analysis = await fetchAnalysis(track.url); // your HTTP API
|
|
243
|
+
track.metadata = {
|
|
244
|
+
...track.metadata,
|
|
245
|
+
bpm: analysis.bpm,
|
|
246
|
+
lufs: analysis.lufs,
|
|
247
|
+
genre: analysis.genre,
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Per-player middleware runs after manager-level middleware
|
|
253
|
+
await manager.create(guildId, {
|
|
254
|
+
trackMiddleware: [(track) => {
|
|
255
|
+
track.metadata = { ...track.metadata, sourcePreset: "guild-radio" };
|
|
256
|
+
}],
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Extensions remain useful for **`beforePlay`** (rewrite query / inject tracks before search) and **`provideStream`** (custom
|
|
261
|
+
backends):
|
|
262
|
+
|
|
263
|
+
1. **`beforePlay`** (capability `beforePlay`) runs inside `player.play()` before search resolution. You can:
|
|
264
|
+
- Adjust `payload.query` when it is a string (rewrite query) or a **`Track`** (mutate the object, including `track.metadata`).
|
|
265
|
+
- Return **`tracks`** to inject or replace the list of tracks (with enriched metadata).
|
|
266
|
+
- Set **`handled: true`** to short-circuit normal handling when you fully control the outcome.
|
|
267
|
+
|
|
268
|
+
2. **`provideStream`** (capability `stream`) runs **after** track middleware and **before** plugin extraction in
|
|
269
|
+
`Player.getStream()`. Use it to supply a stream from Lavalink or another backend while still using plugins for search.
|
|
270
|
+
|
|
271
|
+
Core features read optional **`Track.metadata`** fields:
|
|
272
|
+
|
|
273
|
+
| Key (in `track.metadata`) | Used by |
|
|
274
|
+
| ------------------------- | -------------------------------------------------------------------------- |
|
|
275
|
+
| `bpm` | Smart transition beat alignment (`smartTransition.beatAlign`) |
|
|
276
|
+
| `genre` | Genre-aware fade duration (`smartTransition.genreAware`, `genreDurations`) |
|
|
277
|
+
| `lufs` | Loudness normalization (`loudnessNormalization`) |
|
|
278
|
+
|
|
279
|
+
Example sketch (extension path): in `beforePlay`, if `payload.query` is a `Track`, call your analysis service (or cache), then
|
|
280
|
+
assign `track.metadata = { ...track.metadata, bpm, lufs, genre }` before returning.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 🎛️ Audio Filters
|
|
285
|
+
|
|
286
|
+
Apply FFmpeg filters in real-time:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
await player.filter.applyFilter("bassboost");
|
|
290
|
+
await player.filter.applyFilter("nightcore");
|
|
291
|
+
await player.filter.applyFilters(["bassboost", "trebleboost"]); // Multiple filters
|
|
292
|
+
await player.filter.getFilterString(); // "bassboost,trebleboost"
|
|
293
|
+
await player.filter.clearAll();
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Available filters
|
|
297
|
+
|
|
298
|
+
- bassboost, trebleboost
|
|
299
|
+
- nightcore, lofi, vaporwave
|
|
300
|
+
- echo, reverb, chorus
|
|
301
|
+
- karaoke
|
|
302
|
+
- normalize, compressor, limiter
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 🔊 TTS (Interrupt Mode)
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const player = await manager.create(guildId, {
|
|
310
|
+
tts: {
|
|
311
|
+
createPlayer: true,
|
|
312
|
+
interrupt: true,
|
|
313
|
+
volume: 100,
|
|
314
|
+
maxTimeTts: 60000,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
await player.play("tts: Hello everyone", userId);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 📡 Events
|
|
324
|
+
|
|
325
|
+
Listen globally via manager:
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
manager.on("trackStart", (player, track) => {});
|
|
329
|
+
manager.on("trackEnd", (player, track) => {});
|
|
330
|
+
manager.on("queueEnd", (player) => {});
|
|
331
|
+
manager.on("playerError", (player, error, track) => {});
|
|
332
|
+
manager.on("playerPause", (player, track) => {});
|
|
333
|
+
manager.on("playerResume", (player, track) => {});
|
|
334
|
+
manager.on("volumeChange", (player, oldVolume, newVolume) => {});
|
|
335
|
+
manager.on("queueAdd", (player, track) => {});
|
|
336
|
+
manager.on("queueAddList", (player, tracks) => {});
|
|
337
|
+
manager.on("queueRemove", (player, track, index) => {});
|
|
338
|
+
manager.on("playerDestroy", (player) => {});
|
|
339
|
+
manager.on("ttsStart", (player, payload) => {});
|
|
340
|
+
manager.on("ttsEnd", (player) => {});
|
|
341
|
+
manager.on("stats", (PlayerStats) => {});
|
|
342
|
+
manager.on("forwardModeStart", (player, leader) => {});
|
|
343
|
+
manager.on("forwardModeEnd", (player, leader) => {});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## 🧠 Advanced Features
|
|
349
|
+
|
|
350
|
+
### Autoplay
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
player.queue.autoPlay(true);
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Insert next track
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
await player.insert("song", 0); // Insert at position 0 (play next)
|
|
360
|
+
await player.insert([track1, track2], 2); // Insert multiple at index 2
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Save stream to file
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const stream = await player.save(track);
|
|
367
|
+
stream.pipe(fs.createWriteStream("song.mp3"));
|
|
368
|
+
|
|
369
|
+
// Save with filters
|
|
370
|
+
const filteredStream = await player.save(track, {
|
|
371
|
+
filter: ["bassboost"],
|
|
372
|
+
seek: 30000, // Start from 30 seconds
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Progress Bar
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
// Default (compact time format)
|
|
380
|
+
console.log(player.getProgressBar());
|
|
381
|
+
// Output: "1:22:12 ▬▬▬▬▬▬▬▬▬▬🔘▬▬▬▬▬▬▬▬ 1:45:30"
|
|
382
|
+
|
|
383
|
+
// Custom options
|
|
384
|
+
console.log(
|
|
385
|
+
player.getProgressBar({
|
|
386
|
+
size: 30,
|
|
387
|
+
barChar: "─",
|
|
388
|
+
progressChar: "●",
|
|
389
|
+
timeFormat: "full", // "full" or "compact"
|
|
390
|
+
showPercentage: true,
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
// Output: "01:22:12 ───────●───────────────────── 01:45:30 (47%)"
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Time Formatting
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
const time = player.getTime();
|
|
400
|
+
console.log(time.formatted.current); // "1:22:12" (compact)
|
|
401
|
+
console.log(time.format); // "01:22:12" (full with leading zeros)
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Batch Operations
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
// Broadcast action to all players
|
|
408
|
+
manager.broadcast("setVolume", 50);
|
|
409
|
+
manager.broadcast("pause");
|
|
410
|
+
|
|
411
|
+
// Get players by filter
|
|
412
|
+
const activePlayers = manager.getPlayersByFilter((p) => p.isPlaying);
|
|
413
|
+
|
|
414
|
+
// Delete multiple players
|
|
415
|
+
manager.deleteWhere((p) => p.queue.isEmpty && !p.isPlaying);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Multi-room / multi-guild broadcast
|
|
419
|
+
|
|
420
|
+
`PlayerManager.broadcast(action, ...args)` loops every registered **`Player`** and, if `player[action]` is a function, calls
|
|
421
|
+
`player[action](...args)`. It is a **control fan-out**: the same method name runs on all guild players (pause, volume, skip,etc.).
|
|
422
|
+
It does **not** multiplex one Discord voice stream to many guilds—each guild still has its own voice connection and decoder.
|
|
423
|
+
|
|
424
|
+
Use **`broadcastAsync`** when you need to await async methods (for example `play`):
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
const results = await manager.broadcastAsync("play", "https://youtu.be/...", botUserId);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Use **`broadcastGuilds`** to target a subset of guild ids:
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
manager.broadcastGuilds(["guildA", "guildB"], "pause");
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**“Subscribe” pattern (manual):**
|
|
437
|
+
|
|
438
|
+
1. Call `await manager.create(guildId, options)` (and `player.connect(voiceChannel)`) for **each** guild that should participate
|
|
439
|
+
so each server has a player instance.
|
|
440
|
+
2. Drive playback from your bot logic: mirror API above, or issue the same `play` / queue commands per guild, or use `broadcast`
|
|
441
|
+
for **synchronized controls** only.
|
|
442
|
+
3. Plain `broadcast` is **synchronous** and does not `await` async methods. Prefer `broadcastAsync` or a `for` loop with `await`
|
|
443
|
+
when order/errors matter.
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
// Same control on every guild that already has a player
|
|
447
|
+
manager.broadcast("pause");
|
|
448
|
+
manager.broadcast("setVolume", 75);
|
|
449
|
+
|
|
450
|
+
// Prefer explicit awaits if you need ordered or error-handled play on many guilds
|
|
451
|
+
for (const player of manager.getAll()) {
|
|
452
|
+
await player.play(sharedQueueUrl, botUserId).catch(console.error);
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Playback Mirror / Forward Mode
|
|
457
|
+
|
|
458
|
+
Ziplayer supports built-in multi-guild playback mirroring using shared audio forwarding. A leader player streams audio normally,
|
|
459
|
+
while followers directly subscribe to the leader's internal audioPlayer.
|
|
460
|
+
|
|
461
|
+
This allows multiple guilds to hear the exact same playback while using only:
|
|
462
|
+
|
|
463
|
+
- one stream
|
|
464
|
+
- one decoder
|
|
465
|
+
- one extractor pipeline
|
|
466
|
+
|
|
467
|
+
Resulting in extremely low CPU and bandwidth usage.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
const stopMirror = manager.subscribeForwardMirror({
|
|
471
|
+
leaderGuildId: "123",
|
|
472
|
+
followerGuildIds: ["456", "789"],
|
|
473
|
+
syncVolume: true,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// later
|
|
477
|
+
stopMirror();
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Direct Player Subscription:**
|
|
481
|
+
|
|
482
|
+
Followers may also subscribe manually:
|
|
483
|
+
|
|
484
|
+
````ts
|
|
485
|
+
const leader = manager.get("123");
|
|
486
|
+
const follower = manager.get("456");
|
|
487
|
+
|
|
488
|
+
follower.subscribeTo(leader);
|
|
489
|
+
//Unsubscribe:
|
|
490
|
+
//follower.unsubscribeForward();
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## ⚙️ Advanced Configuration
|
|
496
|
+
|
|
497
|
+
### PlayerManager Options
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
const manager = new PlayerManager({
|
|
501
|
+
plugins: [...],
|
|
502
|
+
extensions: [...],
|
|
503
|
+
extractorTimeout: 30000, // Timeout for stream extraction
|
|
504
|
+
autoCleanup: true, // Auto cleanup inactive players
|
|
505
|
+
cleanupInterval: 120000, // Cleanup interval (ms)
|
|
506
|
+
enableSearchCache: true, // Cache search results
|
|
507
|
+
enableStatsCollection: true, // Enable stats events
|
|
508
|
+
trackMiddleware: [...], // Global pre-stream track transforms (before per-player middleware)
|
|
509
|
+
persistence: {...} // Persistence configuration
|
|
510
|
+
});
|
|
511
|
+
````
|
|
512
|
+
|
|
513
|
+
### Player Options
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
const player = await manager.create(guildId, {
|
|
517
|
+
volume: 100,
|
|
518
|
+
quality: "high",
|
|
519
|
+
leaveOnEnd: true,
|
|
520
|
+
leaveOnEmpty: true,
|
|
521
|
+
leaveTimeout: 100000,
|
|
522
|
+
selfDeaf: true,
|
|
523
|
+
selfMute: false,
|
|
524
|
+
extractorTimeout: 50000,
|
|
525
|
+
filters: ["bassboost", "nightcore"],
|
|
526
|
+
tts: {
|
|
527
|
+
createPlayer: false,
|
|
528
|
+
interrupt: true,
|
|
529
|
+
volume: 100,
|
|
530
|
+
maxTimeTts: 60000,
|
|
531
|
+
},
|
|
532
|
+
// Runtime profile
|
|
533
|
+
lowPerformance: false,
|
|
534
|
+
preload: {
|
|
535
|
+
enabled: true,
|
|
536
|
+
autoDisableInLowPerformance: true,
|
|
537
|
+
},
|
|
538
|
+
crossfade: {
|
|
539
|
+
enabled: undefined, // omit to let autoEnable decide
|
|
540
|
+
autoEnable: true,
|
|
541
|
+
autoDisableInLowPerformance: true,
|
|
542
|
+
durationMs: 5000,
|
|
543
|
+
},
|
|
544
|
+
smartTransition: {
|
|
545
|
+
enabled: true,
|
|
546
|
+
genreAware: true,
|
|
547
|
+
beatAlign: true,
|
|
548
|
+
baseDurationMs: 5000,
|
|
549
|
+
minDurationMs: 1200,
|
|
550
|
+
maxDurationMs: 8000,
|
|
551
|
+
genreDurations: { chill: 7000, edm: 2200 },
|
|
552
|
+
beatAlignMaxWaitMs: 1200,
|
|
553
|
+
},
|
|
554
|
+
antiStuck: {
|
|
555
|
+
enabled: true,
|
|
556
|
+
maxRetries: 2,
|
|
557
|
+
retryDelayMs: 900,
|
|
558
|
+
reusePreloadFirst: true,
|
|
559
|
+
reduceQualityOnRetry: true,
|
|
560
|
+
controlledSkipThreshold: 3,
|
|
561
|
+
},
|
|
562
|
+
loudnessNormalization: {
|
|
563
|
+
enabled: true,
|
|
564
|
+
targetLUFS: -14,
|
|
565
|
+
maxBoostDb: 8,
|
|
566
|
+
maxCutDb: 10,
|
|
567
|
+
limiterCeiling: 0.95,
|
|
568
|
+
},
|
|
569
|
+
trackMiddleware: [], // Optional per-player chain (after manager trackMiddleware)
|
|
570
|
+
userdata: { customField: "value" },
|
|
571
|
+
});
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Crossfade + Low Performance
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
// Auto mode: crossfade/preload enabled unless lowPerformance is on
|
|
578
|
+
const player = await manager.create(guildId, {
|
|
579
|
+
lowPerformance: false,
|
|
580
|
+
preload: { enabled: true, autoDisableInLowPerformance: true },
|
|
581
|
+
crossfade: { autoEnable: true, autoDisableInLowPerformance: true, durationMs: 4000 },
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Low performance mode: auto disable preload and crossfade
|
|
585
|
+
const litePlayer = await manager.create(guildId, {
|
|
586
|
+
lowPerformance: true,
|
|
587
|
+
preload: { enabled: true, autoDisableInLowPerformance: true }, // resolved: disabled
|
|
588
|
+
crossfade: { autoEnable: true, autoDisableInLowPerformance: true }, // resolved: disabled
|
|
589
|
+
});
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
> Crossfade is applied when switching to the next track and when calling `player.skip()`. Smart transition adapts fade by
|
|
593
|
+
> `metadata.genre` and can align to beat using `metadata.bpm`. Loudness normalization uses `metadata.lufs` when available and
|
|
594
|
+
> applies a limiter ceiling.
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
## 📊 Monitoring & Stats
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
// Get manager statistics
|
|
602
|
+
const stats = manager.getStats();
|
|
603
|
+
console.log({
|
|
604
|
+
totalPlayers: stats.totalPlayers,
|
|
605
|
+
activePlayers: stats.activePlayers,
|
|
606
|
+
pausedPlayers: stats.pausedPlayers,
|
|
607
|
+
connectedPlayers: stats.connectedPlayers,
|
|
608
|
+
totalTracksInQueue: stats.totalTracksInQueue,
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// Get plugin/extension stats
|
|
612
|
+
console.log(manager.getConfig());
|
|
613
|
+
console.log(player.pluginManager.getStats());
|
|
614
|
+
console.log(player.extensionManager.getStats());
|
|
615
|
+
|
|
616
|
+
// Clear caches
|
|
617
|
+
player.clearSearchCache();
|
|
618
|
+
player.extensionManager.clearCache("search");
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## ⚠️ Best Practices
|
|
624
|
+
|
|
625
|
+
- Use **one PlayerManager** per bot
|
|
626
|
+
- Always `await player.connect()` before playing
|
|
627
|
+
- Handle `playerError` events
|
|
628
|
+
- Do not reuse a destroyed player
|
|
629
|
+
- Enable **persistence** for production bots to survive restarts
|
|
630
|
+
- Use **autoCleanup** to prevent memory leaks
|
|
631
|
+
- Set appropriate **extractorTimeout** based on your network (default: 10-50 seconds)
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## 🌟 Migration Guide
|
|
636
|
+
|
|
637
|
+
### From v1.x to v2.x
|
|
638
|
+
|
|
639
|
+
- `player.getTime()` now returns `{ current, total, format, formatted }`
|
|
640
|
+
- `player.getProgressBar()` supports new options
|
|
641
|
+
- `player.queue.remove(index)` removed track is now returned
|
|
642
|
+
- New `queue.removeMultiple()`, `queue.move()`, `queue.swap()` methods
|
|
643
|
+
- Extension hooks now support async properly
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
## 📚 Resources
|
|
648
|
+
|
|
649
|
+
- Examples: [https://github.com/ZiProject/ZiPlayer/tree/main/examples](https://github.com/ZiProject/ZiPlayer/tree/main/examples)
|
|
650
|
+
- GitHub: [https://github.com/ZiProject/ZiPlayer](https://github.com/ZiProject/ZiPlayer)
|
|
651
|
+
- npm: [https://www.npmjs.com/package/ziplayer](https://www.npmjs.com/package/ziplayer)
|
|
652
|
+
- AI/agent-oriented notes (middleware metadata, broadcast semantics): see `AGENTS.md` in this repo
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## 📄 License
|
|
657
|
+
|
|
658
|
+
MIT License
|