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