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
|
@@ -1,624 +1,717 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
autoDisableInLowPerformance?: boolean; // Default: true
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
player.
|
|
203
|
-
player.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
player.
|
|
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
|
-
await player.filter.
|
|
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
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
player.
|
|
532
|
-
break;
|
|
533
|
-
|
|
534
|
-
case "
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1
|
+
# AI / agent notes — ZiPlayer core
|
|
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
|
+
## 📦 Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install ziplayer @ziplayer/plugin @ziplayer/extension @ziplayer/infinity @discordjs/voice discord.js opusscript
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
### Component Responsibilities
|
|
44
|
+
|
|
45
|
+
| Component | Responsibility |
|
|
46
|
+
| ------------------ | -------------------------------------------------- |
|
|
47
|
+
| `PlayerManager` | Creates/manages players, global event bus |
|
|
48
|
+
| `Player` | Per-guild audio playback, controls, event emission |
|
|
49
|
+
| `Queue` | Track management, loop modes, history, auto-play |
|
|
50
|
+
| `PluginManager` | Audio source resolution, streaming, fallback logic |
|
|
51
|
+
| `ExtensionManager` | Custom hooks (search, stream, before/after play) |
|
|
52
|
+
| `FilterManager` | FFmpeg audio effects |
|
|
53
|
+
| `StreamManager` | Centralized stream management/Auto cleanup |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🧠 Core Concepts
|
|
58
|
+
|
|
59
|
+
### 1. Player Lifecycle
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// Create → Connect → Play → Destroy
|
|
63
|
+
const player = await manager.create(guildId, options);
|
|
64
|
+
await player.connect(voiceChannel);
|
|
65
|
+
await player.play(query, userId);
|
|
66
|
+
player.destroy();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. Queue Loop Modes
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
player.loop("off"); // No loop (default)
|
|
73
|
+
player.loop("track"); // Repeat current track
|
|
74
|
+
player.loop("queue"); // Repeat entire queue
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Event Flow
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
trackStart → playing → trackEnd → playNext → (loop/autoplay)
|
|
81
|
+
↓
|
|
82
|
+
queueEnd → leave
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. Plugin Priority & Fallback
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Plugins are tried in priority order (higher = first)
|
|
89
|
+
// If primary fails, fallback plugins are attempted sequentially
|
|
90
|
+
// Failed plugins don't block the queue
|
|
91
|
+
|
|
92
|
+
plugin.priority = 10; // Higher priority
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 5. Caching Strategy
|
|
96
|
+
|
|
97
|
+
| Cache Type | TTL | Purpose |
|
|
98
|
+
| --------------- | ------- | --------------------------- |
|
|
99
|
+
| Search cache | 2 min | Avoid duplicate API calls |
|
|
100
|
+
| Stream cache | 5 min | Cache resolved streams |
|
|
101
|
+
| Extension cache | 1-5 min | Extension operation results |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 📚 API Reference
|
|
106
|
+
|
|
107
|
+
### PlayerManager
|
|
108
|
+
|
|
109
|
+
#### Constructor Options
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
interface PlayerManagerOptions {
|
|
113
|
+
plugins?: SourcePlugin[]; // Audio source plugins
|
|
114
|
+
extensions?: BaseExtension[]; // Custom extensions
|
|
115
|
+
extractorTimeout?: number; // Default: 10000ms
|
|
116
|
+
autoCleanup?: boolean; // Default: true
|
|
117
|
+
cleanupInterval?: number; // Default: 60000ms
|
|
118
|
+
enableSearchCache?: boolean; // Default: true
|
|
119
|
+
enableStatsCollection?: boolean; // Default: false
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Player Runtime Options (Performance Profile)
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
interface PlayerOptions {
|
|
127
|
+
lowPerformance?: boolean; // Default: false (or true when quality === "low")
|
|
128
|
+
preload?: {
|
|
129
|
+
enabled?: boolean; // Default: true
|
|
130
|
+
autoDisableInLowPerformance?: boolean; // Default: true
|
|
131
|
+
};
|
|
132
|
+
crossfade?: {
|
|
133
|
+
enabled?: boolean; // Explicit on/off
|
|
134
|
+
autoEnable?: boolean; // Default: true when enabled is undefined
|
|
135
|
+
autoDisableInLowPerformance?: boolean; // Default: true
|
|
136
|
+
durationMs?: number; // Default: 5000
|
|
137
|
+
};
|
|
138
|
+
smartTransition?: {
|
|
139
|
+
enabled?: boolean;
|
|
140
|
+
genreAware?: boolean;
|
|
141
|
+
beatAlign?: boolean;
|
|
142
|
+
baseDurationMs?: number;
|
|
143
|
+
minDurationMs?: number;
|
|
144
|
+
maxDurationMs?: number;
|
|
145
|
+
genreDurations?: Record<string, number>;
|
|
146
|
+
beatAlignMaxWaitMs?: number;
|
|
147
|
+
};
|
|
148
|
+
antiStuck?: {
|
|
149
|
+
enabled?: boolean;
|
|
150
|
+
maxRetries?: number;
|
|
151
|
+
retryDelayMs?: number;
|
|
152
|
+
reusePreloadFirst?: boolean;
|
|
153
|
+
reduceQualityOnRetry?: boolean;
|
|
154
|
+
controlledSkipThreshold?: number;
|
|
155
|
+
};
|
|
156
|
+
loudnessNormalization?: {
|
|
157
|
+
enabled?: boolean;
|
|
158
|
+
targetLUFS?: number;
|
|
159
|
+
maxBoostDb?: number;
|
|
160
|
+
maxCutDb?: number;
|
|
161
|
+
limiterCeiling?: number;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- If `lowPerformance=true`, preload and crossfade are auto-disabled by default.
|
|
167
|
+
- `crossfade.autoEnable=true` allows crossfade to be enabled automatically when `crossfade.enabled` is not explicitly set.
|
|
168
|
+
- You can still force behavior by setting `enabled` flags directly.
|
|
169
|
+
- Runtime behavior: crossfade is used for next-track transitions and `skip()`.
|
|
170
|
+
- Smart transition can tune fade by `metadata.genre` and beat-align by `metadata.bpm`.
|
|
171
|
+
- Loudness normalization uses `metadata.lufs` with limiter ceiling protection.
|
|
172
|
+
- Anti-stuck retries in-place before controlled skip to avoid skip storms.
|
|
173
|
+
|
|
174
|
+
#### Key Methods
|
|
175
|
+
|
|
176
|
+
| Method | Description |
|
|
177
|
+
| ---------------------------- | -------------------------- |
|
|
178
|
+
| `create(guildId, options)` | Create new player |
|
|
179
|
+
| `get(guildId)` | Get existing player |
|
|
180
|
+
| `delete(guildId)` | Destroy and remove player |
|
|
181
|
+
| `getAll()` | Get all players |
|
|
182
|
+
| `broadcast(action, ...args)` | Send action to all players |
|
|
183
|
+
|
|
184
|
+
### Player
|
|
185
|
+
|
|
186
|
+
#### Core Methods
|
|
187
|
+
|
|
188
|
+
| Method | Description | Returns |
|
|
189
|
+
| ------------------------------ | ---------------------------------------------------------- | ------------------- |
|
|
190
|
+
| `play(query, userId)` | Play track/search/queue | `Promise<boolean>` |
|
|
191
|
+
| `pause()` | Pause current | `boolean` |
|
|
192
|
+
| `resume()` | Resume playback | `boolean` |
|
|
193
|
+
| `skip(index?)` | Skip to next/index | `boolean` |
|
|
194
|
+
| `stop()` | Stop and clear queue | `boolean` |
|
|
195
|
+
| `seek(position)` | Seek to position (ms) | `Promise<boolean>` |
|
|
196
|
+
| `previous()` | Play previous track | `Promise<boolean>` |
|
|
197
|
+
| `setVolume(vol)` | Set volume (0-200) | `boolean` |
|
|
198
|
+
| `loop(mode)` | Set loop mode | `LoopMode` |
|
|
199
|
+
| `shuffle()` | Shuffle queue | `void` |
|
|
200
|
+
| `insert(query, index, userId)` | Insert at position | `Promise<boolean>` |
|
|
201
|
+
| `save(track, options)` | Save track to stream | `Promise<Readable>` |
|
|
202
|
+
| `subscribeTo(leader, options)` | Subscribe this player to another player's playback stream. | `boolean` |
|
|
203
|
+
| `unsubscribeForward()` | Unsubscribe this player from its current playback leader. | `boolean` |
|
|
204
|
+
|
|
205
|
+
#### Getters
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
player.currentTrack; // Track | null
|
|
209
|
+
player.queueSize; // number
|
|
210
|
+
player.isPlaying; // boolean
|
|
211
|
+
player.isPaused; // boolean
|
|
212
|
+
player.volume; // number
|
|
213
|
+
player.upcomingTracks; // Track[]
|
|
214
|
+
player.previousTracks; // Track[]
|
|
215
|
+
player.relatedTracks; // Track[] | null
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Queue
|
|
219
|
+
|
|
220
|
+
#### Methods
|
|
221
|
+
|
|
222
|
+
| Method | Description |
|
|
223
|
+
| ------------------------- | -------------------- |
|
|
224
|
+
| `add(track)` | Add single track |
|
|
225
|
+
| `addMultiple(tracks)` | Add multiple tracks |
|
|
226
|
+
| `insert(track, index)` | Insert at position |
|
|
227
|
+
| `remove(index)` | Remove at index |
|
|
228
|
+
| `removeMultiple(indices)` | Remove multiple |
|
|
229
|
+
| `removeWhere(predicate)` | Remove by condition |
|
|
230
|
+
| `move(from, to)` | Move track |
|
|
231
|
+
| `swap(a, b)` | Swap tracks |
|
|
232
|
+
| `shuffle()` | Randomize order |
|
|
233
|
+
| `clear()` | Clear all tracks |
|
|
234
|
+
| `loop(mode)` | Set loop mode |
|
|
235
|
+
| `autoPlay(enabled)` | Enable/disable |
|
|
236
|
+
| `previous()` | Get previous track |
|
|
237
|
+
| `jumpToHistory(steps)` | Jump back in history |
|
|
238
|
+
|
|
239
|
+
#### Properties
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
queue.size; // number
|
|
243
|
+
queue.isEmpty; // boolean
|
|
244
|
+
queue.currentTrack; // Track | null
|
|
245
|
+
queue.nextTrack; // Track | null
|
|
246
|
+
queue.lastTrack; // Track | null
|
|
247
|
+
queue.previousTracks; // Track[]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### FilterManager
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// Apply filters
|
|
254
|
+
await player.filter.applyFilter("bassboost");
|
|
255
|
+
await player.filter.applyFilters(["bassboost", "nightcore"]);
|
|
256
|
+
|
|
257
|
+
// Available filters
|
|
258
|
+
// bassboost, trebleboost, nightcore, lofi, vaporwave,
|
|
259
|
+
// echo, reverb, chorus, karaoke, normalize, compressor, limiter
|
|
260
|
+
|
|
261
|
+
// Clear filters
|
|
262
|
+
await player.filter.clearAll();
|
|
263
|
+
await player.filter.clear("bassboost");
|
|
264
|
+
|
|
265
|
+
// Get current filters
|
|
266
|
+
const filterString = player.filter.getFilterString(); // "bassboost,nightcore"
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## 🔧 Common Patterns
|
|
270
|
+
|
|
271
|
+
### 1. Basic Music Bot Setup
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { Client, GatewayIntentBits } from "discord.js";
|
|
275
|
+
import { PlayerManager } from "ziplayer";
|
|
276
|
+
import { YouTubePlugin, SpotifyPlugin } from "@ziplayer/plugin";
|
|
277
|
+
|
|
278
|
+
const client = new Client({
|
|
279
|
+
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const manager = new PlayerManager({
|
|
283
|
+
plugins: [new YouTubePlugin({}), new SpotifyPlugin()],
|
|
284
|
+
autoCleanup: true,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
client.on("messageCreate", async (msg) => {
|
|
288
|
+
if (msg.content.startsWith("!play")) {
|
|
289
|
+
const player = await manager.create(msg.guildId);
|
|
290
|
+
const voiceChannel = msg.member?.voice.channel;
|
|
291
|
+
|
|
292
|
+
if (!player.connection) {
|
|
293
|
+
await player.connect(voiceChannel);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const query = msg.content.slice(6);
|
|
297
|
+
await player.play(query, msg.author.id);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const player = await manager.create(guildId, {
|
|
302
|
+
lowPerformance: false,
|
|
303
|
+
preload: { enabled: true, autoDisableInLowPerformance: true },
|
|
304
|
+
crossfade: { autoEnable: true, autoDisableInLowPerformance: true, durationMs: 5000 },
|
|
305
|
+
smartTransition: {
|
|
306
|
+
enabled: true,
|
|
307
|
+
genreAware: true,
|
|
308
|
+
beatAlign: true,
|
|
309
|
+
baseDurationMs: 5000,
|
|
310
|
+
genreDurations: { chill: 7000, edm: 2200 },
|
|
311
|
+
},
|
|
312
|
+
antiStuck: {
|
|
313
|
+
enabled: true,
|
|
314
|
+
maxRetries: 2,
|
|
315
|
+
retryDelayMs: 900,
|
|
316
|
+
reusePreloadFirst: true,
|
|
317
|
+
reduceQualityOnRetry: true,
|
|
318
|
+
controlledSkipThreshold: 3,
|
|
319
|
+
},
|
|
320
|
+
loudnessNormalization: {
|
|
321
|
+
enabled: true,
|
|
322
|
+
targetLUFS: -14,
|
|
323
|
+
limiterCeiling: 0.95,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 2. Progress Bar with Time Format
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// Get formatted time
|
|
332
|
+
const time = player.getTime();
|
|
333
|
+
console.log(`Current: ${time.formatted.current}`); // "1:22:12"
|
|
334
|
+
console.log(`Total: ${time.formatted.total}`); // "3:45:30"
|
|
335
|
+
|
|
336
|
+
// Progress bar
|
|
337
|
+
const progressBar = player.getProgressBar({
|
|
338
|
+
size: 20,
|
|
339
|
+
barChar: "▬",
|
|
340
|
+
progressChar: "🔘",
|
|
341
|
+
timeFormat: "compact",
|
|
342
|
+
showPercentage: true,
|
|
343
|
+
});
|
|
344
|
+
// Output: "1:22:12 ▬▬▬▬▬▬▬▬▬🔘▬▬▬▬▬▬▬▬ 3:45:30 (36%)"
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 3. Queue Management Commands
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// Skip to specific track
|
|
351
|
+
await player.skip(3); // Skip to index 3
|
|
352
|
+
|
|
353
|
+
// Move track to front
|
|
354
|
+
player.queue.move(5, 0);
|
|
355
|
+
|
|
356
|
+
// Remove all tracks from specific source
|
|
357
|
+
player.queue.removeWhere((t) => t.source === "soundcloud");
|
|
358
|
+
|
|
359
|
+
// Jump back 2 tracks
|
|
360
|
+
await player.queue.jumpToHistory(2);
|
|
361
|
+
|
|
362
|
+
// Insert as next track
|
|
363
|
+
await player.insert("song name", 0, userId);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### 4. Custom Plugin Implementation
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { BasePlugin, Track, StreamInfo } from "ziplayer";
|
|
370
|
+
|
|
371
|
+
class CustomPlugin extends BasePlugin {
|
|
372
|
+
name = "CustomPlugin";
|
|
373
|
+
priority = 5;
|
|
374
|
+
|
|
375
|
+
canHandle(query: string): boolean {
|
|
376
|
+
return query.startsWith("custom:");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async search(query: string, requestedBy: string): Promise<SearchResult> {
|
|
380
|
+
// Implementation
|
|
381
|
+
return { tracks: [] };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async getStream(track: Track, signal: AbortSignal): Promise<StreamInfo> {
|
|
385
|
+
// Return audio stream
|
|
386
|
+
return { stream: readableStream, type: "arbitrary" };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async getRelatedTracks(track: Track): Promise<Track[]> {
|
|
390
|
+
// Return recommendations
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### 5. Custom Extension Implementation
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
import { BaseExtension, ExtensionContext } from "ziplayer";
|
|
400
|
+
|
|
401
|
+
class LoggerExtension extends BaseExtension {
|
|
402
|
+
name = "Logger";
|
|
403
|
+
priority = 10;
|
|
404
|
+
|
|
405
|
+
async beforePlay(context: ExtensionContext, request: any) {
|
|
406
|
+
console.log(`Playing: ${request.query}`);
|
|
407
|
+
return { handled: false };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async afterPlay(context: ExtensionContext, payload: any) {
|
|
411
|
+
if (payload.success) {
|
|
412
|
+
console.log(`Successfully played ${payload.tracks?.length} tracks`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### 6. Event Handling
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
manager.on("trackStart", (player, track) => {
|
|
422
|
+
console.log(`Now playing: ${track.title}`);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
manager.on("queueEnd", (player) => {
|
|
426
|
+
console.log("Queue finished!");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
manager.on("playerError", (player, error, track) => {
|
|
430
|
+
console.error(`Error on ${track?.title}:`, error.message);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
manager.on("stats", (stats) => {
|
|
434
|
+
console.log(`Active players: ${stats.activePlayers}`);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Listen globally via manager:
|
|
438
|
+
manager.on("trackStart", (player, track) => {});
|
|
439
|
+
manager.on("trackEnd", (player, track) => {});
|
|
440
|
+
manager.on("queueEnd", (player) => {});
|
|
441
|
+
manager.on("playerError", (player, error, track) => {});
|
|
442
|
+
manager.on("playerPause", (player, track) => {});
|
|
443
|
+
manager.on("playerResume", (player, track) => {});
|
|
444
|
+
manager.on("volumeChange", (player, oldVolume, newVolume) => {});
|
|
445
|
+
manager.on("queueAdd", (player, track) => {});
|
|
446
|
+
manager.on("queueAddList", (player, tracks) => {});
|
|
447
|
+
manager.on("queueRemove", (player, track, index) => {});
|
|
448
|
+
manager.on("playerDestroy", (player) => {});
|
|
449
|
+
manager.on("ttsStart", (player, payload) => {});
|
|
450
|
+
manager.on("ttsEnd", (player) => {});
|
|
451
|
+
manager.on("stats", (PlayerStats) => {});
|
|
452
|
+
manager.on("forwardModeStart", (player, leader) => {});
|
|
453
|
+
manager.on("forwardModeEnd", (player, leader) => {});
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## 🐛 Troubleshooting
|
|
459
|
+
|
|
460
|
+
### Common Issues
|
|
461
|
+
|
|
462
|
+
| Issue | Solution |
|
|
463
|
+
| ------------------------ | ----------------------------------------------------------- |
|
|
464
|
+
| **No audio** | Check `player.connection` exists, voice channel permissions |
|
|
465
|
+
| **Plugin not working** | Verify `canHandle()` returns true, check priority |
|
|
466
|
+
| **Filters not applying** | Call `refreshPlayerResource(true)` after applying |
|
|
467
|
+
| **Memory leak** | Enable `autoCleanup`, call `player.destroy()` when done |
|
|
468
|
+
| **Rate limiting** | Use search cache, increase `extractorTimeout` |
|
|
469
|
+
|
|
470
|
+
### Debug Mode
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
// Enable debug logging
|
|
474
|
+
manager.on("debug", (message) => {
|
|
475
|
+
console.log("[DEBUG]", message);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Or check debug flag
|
|
479
|
+
if (manager.debugEnabled) {
|
|
480
|
+
// Debug-specific logic
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Performance Tips
|
|
485
|
+
|
|
486
|
+
1. **Enable caching** for search and stream results
|
|
487
|
+
2. **Set appropriate timeouts** based on network conditions
|
|
488
|
+
3. **Batch operations** when modifying queue
|
|
489
|
+
4. **Destroy players** when no longer needed
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## 📝 Code Examples
|
|
494
|
+
|
|
495
|
+
### Full Bot Example
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
import { Client, GatewayIntentBits, EmbedBuilder } from "discord.js";
|
|
499
|
+
import { PlayerManager } from "ziplayer";
|
|
500
|
+
import { YouTubePlugin, SpotifyPlugin, TTSPlugin } from "@ziplayer/plugin";
|
|
501
|
+
|
|
502
|
+
const client = new Client({
|
|
503
|
+
intents: [
|
|
504
|
+
GatewayIntentBits.Guilds,
|
|
505
|
+
GatewayIntentBits.GuildVoiceStates,
|
|
506
|
+
GatewayIntentBits.GuildMessages,
|
|
507
|
+
GatewayIntentBits.MessageContent,
|
|
508
|
+
],
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
const manager = new PlayerManager({
|
|
512
|
+
plugins: [new YouTubePlugin(), new SpotifyPlugin(), new TTSPlugin()],
|
|
513
|
+
autoCleanup: true,
|
|
514
|
+
extractorTimeout: 30000,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
client.on("messageCreate", async (msg) => {
|
|
518
|
+
if (!msg.guildId || msg.author.bot) return;
|
|
519
|
+
|
|
520
|
+
const args = msg.content.slice(1).split(" ");
|
|
521
|
+
const command = args[0].toLowerCase();
|
|
522
|
+
const query = args.slice(1).join(" ");
|
|
523
|
+
|
|
524
|
+
const player = await manager.create(msg.guildId);
|
|
525
|
+
const voiceChannel = msg.member?.voice.channel;
|
|
526
|
+
|
|
527
|
+
switch (command) {
|
|
528
|
+
case "play":
|
|
529
|
+
if (!voiceChannel) return msg.reply("Join a voice channel!");
|
|
530
|
+
if (!player.connection) await player.connect(voiceChannel);
|
|
531
|
+
await player.play(query, msg.author.id);
|
|
532
|
+
break;
|
|
533
|
+
|
|
534
|
+
case "pause":
|
|
535
|
+
player.pause();
|
|
536
|
+
break;
|
|
537
|
+
|
|
538
|
+
case "resume":
|
|
539
|
+
player.resume();
|
|
540
|
+
break;
|
|
541
|
+
|
|
542
|
+
case "skip":
|
|
543
|
+
player.skip();
|
|
544
|
+
break;
|
|
545
|
+
|
|
546
|
+
case "stop":
|
|
547
|
+
player.stop();
|
|
548
|
+
break;
|
|
549
|
+
|
|
550
|
+
case "volume":
|
|
551
|
+
const vol = parseInt(query);
|
|
552
|
+
if (isNaN(vol)) return msg.reply("Volume must be a number!");
|
|
553
|
+
player.setVolume(vol);
|
|
554
|
+
break;
|
|
555
|
+
|
|
556
|
+
case "queue":
|
|
557
|
+
const tracks = player.upcomingTracks.slice(0, 10);
|
|
558
|
+
const embed = new EmbedBuilder()
|
|
559
|
+
.setTitle("Queue")
|
|
560
|
+
.setDescription(tracks.map((t, i) => `${i + 1}. ${t.title}`).join("\n") || "Empty");
|
|
561
|
+
msg.reply({ embeds: [embed] });
|
|
562
|
+
break;
|
|
563
|
+
|
|
564
|
+
case "nowplaying":
|
|
565
|
+
const track = player.currentTrack;
|
|
566
|
+
if (!track) return msg.reply("Nothing playing!");
|
|
567
|
+
|
|
568
|
+
const progress = player.getProgressBar({ size: 15 });
|
|
569
|
+
const time = player.getTime();
|
|
570
|
+
|
|
571
|
+
const embed = new EmbedBuilder()
|
|
572
|
+
.setTitle(track.title)
|
|
573
|
+
.setURL(track.url)
|
|
574
|
+
.setThumbnail(track.thumbnail)
|
|
575
|
+
.setDescription(`\`${progress}\`\n${time.formatted.current} / ${time.formatted.total}`);
|
|
576
|
+
msg.reply({ embeds: [embed] });
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
client.login(process.env.DISCORD_TOKEN);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
586
|
+
## 🔗 Quick Reference
|
|
587
|
+
|
|
588
|
+
### Import Paths
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
// Core
|
|
592
|
+
import { PlayerManager, Player, Queue } from "ziplayer";
|
|
593
|
+
|
|
594
|
+
// Types
|
|
595
|
+
import type { Track, SearchResult, LoopMode, StreamInfo } from "ziplayer";
|
|
596
|
+
|
|
597
|
+
// Plugins (external package)
|
|
598
|
+
import { YouTubePlugin, SpotifyPlugin, TTSPlugin } from "@ziplayer/plugin";
|
|
599
|
+
|
|
600
|
+
// infinity plugin support stream audio from YouTube, TikTok, Instagram, Twitter/X, SoundCloud, Reddit, Twitch, Bilibili, and 1000+ other sites
|
|
601
|
+
|
|
602
|
+
import { InfinityPlugin } from "@ziplayer/infinity";
|
|
603
|
+
|
|
604
|
+
// Extensions (external package)
|
|
605
|
+
import { voiceExt, lyricsExt, lavalinkExt } from "@ziplayer/extension";
|
|
606
|
+
|
|
607
|
+
//fallback for youtube plugin if YouTubePlugin getStream error tunnel
|
|
608
|
+
import { YTexec } from "@ziplayer/ytexecplug"; //ytexecplug needs python, install python fist
|
|
609
|
+
|
|
610
|
+
const ytbplg = new YouTubePlugin({ fistStream: new YTexec().getStream });
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### Type Definitions
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
interface Track {
|
|
617
|
+
id: string;
|
|
618
|
+
title: string;
|
|
619
|
+
url: string;
|
|
620
|
+
source: string;
|
|
621
|
+
duration: number;
|
|
622
|
+
thumbnail?: string;
|
|
623
|
+
requestedBy?: string;
|
|
624
|
+
isLive?: boolean;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
type LoopMode = "off" | "track" | "queue";
|
|
628
|
+
|
|
629
|
+
interface SearchResult {
|
|
630
|
+
tracks: Track[];
|
|
631
|
+
playlist?: { name: string; url?: string };
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
## Terminology
|
|
636
|
+
|
|
637
|
+
- **Plugins** (`SourcePlugin`): search, `getStream`, playlists — audio **sources** (YouTube, SoundCloud, etc.).
|
|
638
|
+
- **Extensions** (`SourceExtension` / `BaseExtension`): cross-cutting behavior — **not** the same as **`trackMiddleware`**.
|
|
639
|
+
|
|
640
|
+
## Track transforms before stream
|
|
641
|
+
|
|
642
|
+
| Goal | Mechanism |
|
|
643
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
644
|
+
| Enrich every queued track right before stream extraction (preload, playback, `save`, TTS interrupt path uses middleware before raw plugin stream where applicable) | **`trackMiddleware`** on `PlayerManagerOptions` / `PlayerOptions` — ordered chain; merged order: manager chain first, then per-player chain. Implemented in `Player.applyTrackMiddleware` → called at start of `Player.getStream`, and before direct `pluginManager.getStream` in TTS interrupt / `save`. |
|
|
645
|
+
| Change query or inject tracks before search resolves | Extension **`beforePlay`** — mutate `payload.query` or return `{ tracks }`. |
|
|
646
|
+
| Custom stream backend | Extension **`provideStream`** — runs **after** track middleware, before plugins. |
|
|
647
|
+
|
|
648
|
+
Types: `TrackMiddleware`, `TrackMiddlewareContext`, `normalizeTrackMiddleware` in `src/types/index.ts`.
|
|
649
|
+
|
|
650
|
+
When returning a **new** `Track` from middleware, the core **merges** into the original reference (`mergeTrackPreserveRef`) so
|
|
651
|
+
queue pointers stay valid.
|
|
652
|
+
|
|
653
|
+
## Metadata: BPM, LUFS, genre
|
|
654
|
+
|
|
655
|
+
Advanced playback reads **`track.metadata`**:
|
|
656
|
+
|
|
657
|
+
- **`bpm`**, **`genre`**, **`lufs`** — smart transition + loudness (see `Player`).
|
|
658
|
+
|
|
659
|
+
## Multi-guild broadcast
|
|
660
|
+
|
|
661
|
+
| API | Behavior |
|
|
662
|
+
| -------------------------------------------- | ----------------------------------------------------------------- |
|
|
663
|
+
| `broadcast(action, ...args)` | Sync fan-out of `player[action]` to **all** players. |
|
|
664
|
+
| `broadcastAsync(action, ...args)` | Same, but `Promise.allSettled` on return values (use for `play`). |
|
|
665
|
+
| `broadcastGuilds(guildIds, action, ...args)` | Subset of guilds. |
|
|
666
|
+
|
|
667
|
+
Followers must already exist (`create`). One mirror subscription per leader id replaces the previous.
|
|
668
|
+
|
|
669
|
+
### Playback Mirror / Forward Mode
|
|
670
|
+
|
|
671
|
+
Ziplayer supports built-in multi-guild playback mirroring using shared audio forwarding. A leader player streams audio normally,
|
|
672
|
+
while followers directly subscribe to the leader's internal audioPlayer.
|
|
673
|
+
|
|
674
|
+
This allows multiple guilds to hear the exact same playback while using only:
|
|
675
|
+
|
|
676
|
+
- one stream
|
|
677
|
+
- one decoder
|
|
678
|
+
- one extractor pipeline
|
|
679
|
+
|
|
680
|
+
Resulting in extremely low CPU and bandwidth usage.
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
const stopMirror = manager.subscribeForwardMirror({
|
|
684
|
+
leaderGuildId: "123",
|
|
685
|
+
followerGuildIds: ["456", "789"],
|
|
686
|
+
syncVolume: true,
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// later
|
|
690
|
+
stopMirror();
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**Direct Player Subscription:**
|
|
694
|
+
|
|
695
|
+
Followers may also subscribe manually:
|
|
696
|
+
|
|
697
|
+
````ts
|
|
698
|
+
const leader = manager.get("123");
|
|
699
|
+
const follower = manager.get("456");
|
|
700
|
+
|
|
701
|
+
follower.subscribeTo(leader);
|
|
702
|
+
//Unsubscribe:
|
|
703
|
+
//follower.unsubscribeForward();
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## 📖 Additional Resources
|
|
709
|
+
|
|
710
|
+
- [GitHub Repository](https://github.com/ZiProject/ZiPlayer)
|
|
711
|
+
- [npm Package](https://www.npmjs.com/package/ziplayer)
|
|
712
|
+
- [Examples Folder](https://github.com/ZiProject/ZiPlayer/tree/main/examples)
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
_This guide is maintained for AI assistants and developers. For questions or contributions, please open an issue on GitHub._
|
|
717
|
+
````
|