ziplayer 0.3.8 → 0.3.10

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.
@@ -1,628 +1,630 @@
1
- import { Readable } from "stream";
2
- import { Player } from "../structures/Player";
3
- import type { PlayerManager } from "../structures/PlayerManager";
4
- import type { AudioFilter } from "./fillter";
5
- import type { SourcePluginLike } from "./plugin";
6
- import type { AudioResource } from "@discordjs/voice";
7
-
8
- export enum PlaybackMode {
9
- NATIVE = "native",
10
- REMOTE = "remote",
11
- FORWARD = "forward",
12
- }
13
- /**
14
- * Represents a music track with metadata and streaming information.
15
- *
16
- * @example
17
- * // Basic track from YouTube
18
- * const track: Track = {
19
- * id: "dQw4w9WgXcQ",
20
- * title: "Never Gonna Give You Up",
21
- * url: "https://youtube.com/watch?v=dQw4w9WgXcQ",
22
- * duration: 212000,
23
- * thumbnail: "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
24
- * requestedBy: "123456789",
25
- * source: "youtube",
26
- * metadata: {
27
- * artist: "Rick Astley",
28
- * album: "Whenever You Need Somebody"
29
- * }
30
- * };
31
- *
32
- * // Track from SoundCloud
33
- * const soundcloudTrack: Track = {
34
- * id: "soundcloud-track-123",
35
- * title: "Electronic Song",
36
- * url: "https://soundcloud.com/artist/electronic-song",
37
- * duration: 180000,
38
- * requestedBy: "user456",
39
- * source: "soundcloud",
40
- * metadata: {
41
- * artist: "Electronic Artist",
42
- * genre: "Electronic"
43
- * }
44
- * };
45
- *
46
- * // TTS track
47
- * const ttsTrack: Track = {
48
- * id: "tts-" + Date.now(),
49
- * title: "TTS: Hello everyone!",
50
- * url: "tts: Hello everyone!",
51
- * duration: 5000,
52
- * requestedBy: "user789",
53
- * source: "tts",
54
- * metadata: {
55
- * text: "Hello everyone!",
56
- * language: "en"
57
- * }
58
- * };
59
- */
60
- export interface Track {
61
- id: string;
62
- title: string;
63
- url: string;
64
- duration: number;
65
- thumbnail?: string;
66
- requestedBy: string;
67
- source: string;
68
- metadata?: Record<string, any>;
69
- isLive?: boolean;
70
- author?: string;
71
- }
72
-
73
- /**
74
- * Contains search results from plugins, including tracks and optional playlist information.
75
- *
76
- * @example
77
- * const result: SearchResult = {
78
- * tracks: [
79
- * {
80
- * id: "track1",
81
- * title: "Song 1",
82
- * url: "https://example.com/track1",
83
- * duration: 180000,
84
- * requestedBy: "user123",
85
- * source: "youtube"
86
- * }
87
- * ],
88
- * playlist: {
89
- * name: "My Playlist",
90
- * url: "https://example.com/playlist",
91
- * thumbnail: "https://example.com/thumb.jpg"
92
- * }
93
- * };
94
- */
95
- export interface SearchResult {
96
- tracks: Track[];
97
- playlist?: {
98
- name: string;
99
- url?: string;
100
- thumbnail?: string;
101
- } | null;
102
- query?: string;
103
- score?: SearchScore;
104
- source?: string;
105
- }
106
-
107
- export interface SearchScore {
108
- score: number; // 0-100
109
- reason: string; // Lý do đạt điểm
110
- matchedBy: "url" | "title" | "partial" | "none" | "playlist";
111
- exactMatch: boolean;
112
- }
113
- /**
114
- * Contains streaming information for audio playback.
115
- *
116
- * @example
117
- * const streamInfo: StreamInfo = {
118
- * stream: audioStream,
119
- * type: "webm/opus",
120
- * metadata: {
121
- * bitrate: 128000,
122
- * sampleRate: 48000
123
- * }
124
- * };
125
- */
126
- export interface StreamInfo {
127
- stream: Readable;
128
- type: "webm/opus" | "ogg/opus" | "arbitrary" | string;
129
- metadata?: Record<string, any>;
130
-
131
- remote?: boolean;
132
-
133
- handle?: {
134
- play(): Promise<void>;
135
- stop(): Promise<void>;
136
- pause(): Promise<void>;
137
- resume(): Promise<void>;
138
- seek(position: number): Promise<void>;
139
- setVolume(volume: number): Promise<void>;
140
- destroy(): Promise<void>;
141
- };
142
- }
143
-
144
- /** Passed to each {@link TrackMiddleware} run (before stream resolution). */
145
- export interface TrackMiddlewareContext {
146
- player: Player;
147
- manager: PlayerManager;
148
- }
149
-
150
- /**
151
- * Runs immediately before stream extraction (`Player.getStream`): after enqueue, before extension `provideStream` and plugins.
152
- * Prefer mutating `track` in place (especially `metadata`). If you return another object, its fields are merged into the original
153
- * `track` reference so queue/current-track pointers stay valid.
154
- */
155
- export type TrackMiddleware = (track: Track, context: TrackMiddlewareContext) => void | Track | Promise<void | Track>;
156
-
157
- /** Options for {@link PlayerManager.subscribeForwardMirror}. */
158
- export interface PlaybackMirrorOptions {
159
- leaderGuildId: string;
160
- followerGuildIds: string[];
161
- /**
162
- * When enabled, follower connections subscribe directly
163
- * to leader.audioPlayer
164
- * Default: true
165
- */
166
- forwardMode?: boolean;
167
- }
168
-
169
- export function normalizeTrackMiddleware(input?: TrackMiddleware | TrackMiddleware[]): TrackMiddleware[] {
170
- if (!input) return [];
171
- return Array.isArray(input) ? input : [input];
172
- }
173
-
174
- /**
175
- * Configuration options for creating a new player instance.
176
- *
177
- * @example
178
- * const options: PlayerOptions = {
179
- * leaveOnEnd: true,
180
- * leaveOnEmpty: true,
181
- * leaveTimeout: 30000,
182
- * volume: 0.5,
183
- * quality: "high",
184
- * selfDeaf: false,
185
- * selfMute: false,
186
- * extractorTimeout: 10000,
187
- * tts: {
188
- * createPlayer: true,
189
- * interrupt: true,
190
- * volume: 1.0,
191
- * maxTimeTts: 30000
192
- * }
193
- * };
194
- */
195
- export interface PlayerOptions {
196
- leaveOnEnd?: boolean;
197
- leaveOnEmpty?: boolean;
198
- leaveTimeout?: number;
199
- volume?: number;
200
- quality?: "high" | "low";
201
- selfDeaf?: boolean;
202
- selfMute?: boolean;
203
- group?: string;
204
- /**
205
- * Timeout in milliseconds for plugin operations (search, streaming, etc.)
206
- * to prevent long-running tasks from blocking the player.
207
- */
208
- extractorTimeout?: number;
209
- userdata?: Record<string, any>;
210
- /**
211
- * Text-to-Speech settings. When enabled, the player can create a
212
- * dedicated AudioPlayer to play TTS while pausing the music player
213
- * then resume the music after TTS finishes.
214
- */
215
- tts?: {
216
- /** Create a dedicated tts AudioPlayer at construction time */
217
- createPlayer?: boolean;
218
- /** Pause music and swap subscription to play TTS */
219
- interrupt?: boolean;
220
- /** Default TTS volume multiplier 1 => 100% */
221
- volume?: number;
222
- /** Max time tts playback Duration */
223
- maxTimeTts?: number;
224
- };
225
- /**
226
- * Optional per-player extension selection. When provided, only these
227
- * extensions will be activated for the created player.
228
- * - Provide instances or constructors to use them explicitly
229
- * - Or provide names (string) to select from manager-registered extensions
230
- */
231
- extensions?: any[] | string[];
232
- /**
233
- * Audio filters configuration. When provided, these filters will be
234
- * applied to all audio streams played by this player.
235
- * - Provide filter names (string) to use predefined filters
236
- * - Or provide AudioFilter objects for custom filters
237
- * - Multiple filters can be combined
238
- */
239
- filters?: (string | AudioFilter)[];
240
- /**
241
- * Enable low performance mode.
242
- * When enabled, heavy features such as preload and crossfade can be auto-disabled.
243
- */
244
- lowPerformance?: boolean;
245
- /**
246
- * Preload behavior configuration.
247
- */
248
- preload?: {
249
- /**
250
- * Enable/disable preload explicitly.
251
- * Default: true
252
- */
253
- enabled?: boolean;
254
- /**
255
- * Auto disable preload when lowPerformance is enabled.
256
- * Default: true
257
- */
258
- autoDisableInLowPerformance?: boolean;
259
- };
260
- /**
261
- * Crossfade behavior configuration.
262
- */
263
- crossfade?: {
264
- /**
265
- * Enable/disable crossfade explicitly.
266
- * If omitted and autoEnable=true, ZiPlayer may enable it automatically.
267
- */
268
- enabled?: boolean;
269
- /**
270
- * Auto enable crossfade if runtime profile allows.
271
- * Default: true
272
- */
273
- autoEnable?: boolean;
274
- /**
275
- * Auto disable crossfade when lowPerformance is enabled.
276
- * Default: true
277
- */
278
- autoDisableInLowPerformance?: boolean;
279
- /**
280
- * Target crossfade duration in milliseconds.
281
- * Default: 500
282
- */
283
- durationMs?: number;
284
- };
285
- /**
286
- * Smart transition engine settings.
287
- */
288
- smartTransition?: {
289
- enabled?: boolean;
290
- /**
291
- * Prefer genre-aware fade duration when metadata.genre is available.
292
- */
293
- genreAware?: boolean;
294
- /**
295
- * Try to align transition with beat boundary using metadata.bpm.
296
- */
297
- beatAlign?: boolean;
298
- /**
299
- * Base duration in milliseconds when no specific profile matched.
300
- */
301
- baseDurationMs?: number;
302
- minDurationMs?: number;
303
- maxDurationMs?: number;
304
- /**
305
- * Genre profiles in milliseconds (e.g. chill, edm, pop, rock).
306
- */
307
- genreDurations?: Record<string, number>;
308
- /**
309
- * Max wait time while trying to align to beat boundary.
310
- */
311
- beatAlignMaxWaitMs?: number;
312
- };
313
- /**
314
- * Recovery strategy for stream/player failures.
315
- */
316
- antiStuck?: {
317
- enabled?: boolean;
318
- maxRetries?: number;
319
- retryDelayMs?: number;
320
- /**
321
- * Reuse preload/current cached stream before hard retries.
322
- */
323
- reusePreloadFirst?: boolean;
324
- /**
325
- * Temporarily force low quality during retry attempts.
326
- */
327
- reduceQualityOnRetry?: boolean;
328
- /**
329
- * If consecutive failures exceed this threshold, allow skipping track.
330
- */
331
- controlledSkipThreshold?: number;
332
- };
333
- /**
334
- * Loudness normalization and limiter.
335
- */
336
- loudnessNormalization?: {
337
- enabled?: boolean;
338
- /**
339
- * Target LUFS (integrated). Track metadata.lufs is used if available.
340
- */
341
- targetLUFS?: number;
342
- /**
343
- * Cap gain boost to avoid over-amplification.
344
- */
345
- maxBoostDb?: number;
346
- /**
347
- * Cap attenuation.
348
- */
349
- maxCutDb?: number;
350
- /**
351
- * Output ceiling multiplier (<= 1.0), acts as soft limiter ceiling.
352
- */
353
- limiterCeiling?: number;
354
- };
355
- /**
356
- * Chain of middleware applied to every track immediately before stream extraction (after queueing).
357
- * Merged after {@link PlayerManagerOptions.trackMiddleware} from the manager.
358
- */
359
- trackMiddleware?: TrackMiddleware | TrackMiddleware[];
360
- }
361
-
362
- export interface PlayerManagerOptions {
363
- plugins?: SourcePluginLike[];
364
- extensions?: any[];
365
- extractorTimeout?: number;
366
- autoCleanup?: boolean; // Auto cleanup inactive players
367
- cleanupInterval?: number; // Cleanup interval in ms
368
- enableSearchCache?: boolean; // Enable search result caching
369
- enableStatsCollection?: boolean; // Enable stats collection events
370
- /**
371
- * Global track middleware for every {@link Player} created from this manager (before per-player middleware).
372
- */
373
- trackMiddleware?: TrackMiddleware | TrackMiddleware[];
374
- }
375
-
376
- /**
377
- * Options for the progress bar
378
- *
379
- * @example
380
- * const options: ProgressBarOptions = {
381
- * size: 10,
382
- * barChar: "=",
383
- * progressChar: ">"
384
- * };
385
- */
386
- export interface ProgressBarOptions {
387
- size?: number; // Bar length (default: 20)
388
- barChar?: string; // Character for empty bar (default: "▬")
389
- progressChar?: string; // Character for progress pointer (default: "🔘")
390
- timeFormat?: "compact" | "full"; // Time format style (default: "compact")
391
- showPercentage?: boolean; // Show percentage at end (default: false)
392
- showTime?: boolean; // Show time labels (default: true)
393
- hideProgressChar?: boolean; // Hide progress character (default: false)
394
- }
395
-
396
- /**
397
- * Options for saving tracks
398
- *
399
- * @example
400
- * const options: SaveOptions = {
401
- * filename: "my-song.mp3",
402
- * quality: "high",
403
- * timeout: 30000
404
- * };
405
- */
406
- export interface SaveOptions {
407
- /** Optional filename for the saved file */
408
- filename?: string;
409
- /** Quality of the saved audio ("high" | "low") */
410
- quality?: "high" | "low";
411
- /** Timeout in milliseconds for the save operation */
412
- timeout?: number;
413
- /** Additional metadata to include */
414
- metadata?: Record<string, any>;
415
- /** Filter */
416
- filter?: AudioFilter[];
417
- /** Seek position in milliseconds to start saving from */
418
- seek?: number;
419
- }
420
- export interface PlayerSession {
421
- guildId: string;
422
- queue: Track[];
423
- currentTrack: Track | null;
424
- volume: number;
425
- loopMode: LoopMode;
426
- autoPlay: boolean;
427
- position: number | null;
428
- extensions: string[];
429
- plugins: string[];
430
- userdata?: Record<string, any>;
431
- }
432
-
433
- export interface PreloadState {
434
- resource: AudioResource | null;
435
- track: Track | null;
436
- abortController: AbortController | null;
437
- timeoutId: NodeJS.Timeout | null;
438
- isValid: boolean;
439
- streamId?: string;
440
- isBeingUsed: boolean;
441
- }
442
-
443
- export interface ForwardHealthStatus {
444
- guildId: string;
445
- healthy: boolean;
446
- role: "leader" | "follower" | "none";
447
- issues: string[];
448
- details: {
449
- leaderId?: string;
450
- followerCount?: number;
451
- connectionState?: string;
452
- audioPlayerState?: string;
453
- };
454
- }
455
-
456
- export interface PlayerStats {
457
- totalPlayers: number;
458
- leader: number;
459
- follower: number;
460
- activePlayers: number;
461
- pausedPlayers: number;
462
- connectedPlayers: number;
463
- totalTracksInQueue: number;
464
- forwardHealthStatus: ForwardHealthStatus[];
465
- }
466
-
467
- export interface StreamSlot {
468
- resource: AudioResource | null;
469
- track: Track | null;
470
- streamId: string | null;
471
- abortController: AbortController | null;
472
- isValid: boolean;
473
- isLoading: boolean;
474
- loadPromise: Promise<void> | null;
475
- }
476
-
477
- export type LoopMode = "off" | "track" | "queue";
478
-
479
- export interface VoiceChannel {
480
- id: string;
481
- guildId: string;
482
- type: any;
483
- guild: any;
484
- // Placeholder for VoiceConnection properties and methods
485
- }
486
-
487
- /**
488
- * Event types emitted by Player instances.
489
- *
490
- * @example
491
- *
492
- * manager.on("willPlay", (player, track) => {
493
- * console.log(`Up next: ${track.title}`);
494
- * });
495
- *
496
- * manager.on("trackEnd", (player, track) => {
497
- * console.log(`Now playing: ${track.title}`);
498
- * });
499
- *
500
- * manager.on("queueAdd", (player, track) => {
501
- * console.log(`Queue added: ${track.title}`);
502
- * });
503
- *
504
- * manager.on("queueAddList", (player, tracks) => {
505
- * console.log(`Queue added: ${tracks.length} tracks`);
506
- * });
507
- *
508
- * manager.on("queueRemove", (player, track, index) => {
509
- * console.log(`Queue removed: ${track.title} at index ${index}`);
510
- * });
511
- *
512
- * manager.on("playerPause", (player, track) => {
513
- * console.log(`Player paused: ${track.title}`);
514
- * });
515
- *
516
- * manager.on("playerResume", (player, track) => {
517
- * console.log(`Player resumed: ${track.title}`);
518
- * });
519
- *
520
- * manager.on("playerStop", (player) => {
521
- * console.log("Player stopped");
522
- * });
523
- *
524
- * manager.on("playerDestroy", (player) => {
525
- * console.log("Player destroyed");
526
- * });
527
- *
528
- * manager.on("ttsStart", (player, payload) => {
529
- * console.log(`TTS started: ${payload.text}`);
530
- * });
531
- *
532
- * manager.on("ttsEnd", (player) => {
533
- * console.log("TTS ended");
534
- * });
535
- *
536
- * manager.on("playerError", (player, error, track) => {
537
- * console.log(`Player error: ${error.message}`);
538
- * });
539
- *
540
- * manager.on("connectionError", (player, error) => {
541
- * console.log(`Connection error: ${error.message}`);
542
- * });
543
- * manager.on("trackStart", (player, track) => {
544
- * console.log(`Track started: ${track.title}`);
545
- * });
546
- *
547
- * manager.on("volumeChange", (player, oldVolume, newVolume) => {
548
- * console.log(`Volume changed: ${oldVolume} -> ${newVolume}`);
549
- * });
550
- *
551
- * manager.on("queueEnd", (player) => {
552
- * console.log("Queue finished");
553
- * });
554
- *
555
- */
556
- export interface ManagerEvents {
557
- debug: [message: string, ...args: any[]];
558
- willPlay: [player: Player, track: Track, upcomingTracks: Track[]];
559
- trackStart: [player: Player, track: Track];
560
- trackEnd: [player: Player, track: Track];
561
- queueEnd: [player: Player];
562
- playerError: [player: Player, error: Error, track?: Track];
563
- connectionError: [player: Player, error: Error];
564
- volumeChange: [player: Player, oldVolume: number, newVolume: number];
565
- queueAdd: [player: Player, track: Track];
566
- queueAddList: [player: Player, tracks: Track[]];
567
- queueRemove: [player: Player, track: Track, index: number];
568
- playerPause: [player: Player, track: Track];
569
- playerResume: [player: Player, track: Track];
570
- playerStop: [player: Player];
571
- playerDestroy: [player: Player];
572
- ttsStart: [player: Player, payload: { text?: string; track?: Track }];
573
- ttsEnd: [player: Player];
574
- /** Emitted when audio filter is applied */
575
- filterApplied: [player: Player, filter: AudioFilter];
576
- /** Emitted when audio filter is removed */
577
- filterRemoved: [player: Player, filter: AudioFilter];
578
- /** Emitted when all filters are cleared */
579
- filtersCleared: [player: Player];
580
- //extension events
581
- lyricsCreate: [player: Player, track: Track, lyrics: any];
582
- lyricsChange: [player: Player, track: Track, lyrics: any];
583
- voiceCreate: [player: Player, evt: any];
584
- stats: [stats: PlayerStats];
585
- streamError: [player: Player, error: Error, track: Track | null];
586
-
587
- forwardModeStart: [player: Player, leader: Player];
588
- forwardModeEnd: [player: Player, leader: Player, reason: string | undefined];
589
- }
590
- export interface PlayerEvents {
591
- debug: [message: string, ...args: any[]];
592
- willPlay: [track: Track, upcomingTracks: Track[]];
593
- trackStart: [track: Track];
594
- trackEnd: [track: Track];
595
- queueEnd: [];
596
- playerError: [error: Error, track?: Track];
597
- connectionError: [error: Error];
598
- volumeChange: [oldVolume: number, newVolume: number];
599
- queueAdd: [track: Track];
600
- queueAddList: [tracks: Track[]];
601
- queueRemove: [track: Track, index: number];
602
- playerPause: [track: Track];
603
- playerResume: [track: Track];
604
- playerStop: [];
605
- playerDestroy: [];
606
- /** Emitted when seeking to a position in current track */
607
- seek: [payload: { track: Track; position: number }];
608
- /** Emitted when TTS starts playing (interruption mode) */
609
- ttsStart: [payload: { text?: string; track?: Track }];
610
- /** Emitted when TTS finished (interruption mode) */
611
- ttsEnd: [];
612
- /** Emitted when audio filter is applied */
613
- filterApplied: [filter: AudioFilter];
614
- /** Emitted when audio filter is removed */
615
- filterRemoved: [filter: AudioFilter];
616
- /** Emitted when all filters are cleared */
617
- filtersCleared: [];
618
- trackStuck: [track: Track | null];
619
- streamError: [error: Error, track: Track | null];
620
- /** Emitted when player stats are updated (if enabled) */
621
- stats: [stats: PlayerStats];
622
- forwardModeStart: [leader: Player];
623
- forwardModeEnd: [leader: Player, reason: string | undefined];
624
- }
625
-
626
- export * from "./fillter";
627
- export * from "./plugin";
628
- export * from "./extension";
1
+ import { Readable } from "stream";
2
+ import { Player } from "../structures/Player";
3
+ import type { PlayerManager } from "../structures/PlayerManager";
4
+ import type { AudioFilter } from "./fillter";
5
+ import type { SourcePluginLike } from "./plugin";
6
+ import type { AudioResource } from "@discordjs/voice";
7
+
8
+ export enum PlaybackMode {
9
+ NATIVE = "native",
10
+ REMOTE = "remote",
11
+ FORWARD = "forward",
12
+ }
13
+ /**
14
+ * Represents a music track with metadata and streaming information.
15
+ *
16
+ * @example
17
+ * // Basic track from YouTube
18
+ * const track: Track = {
19
+ * id: "dQw4w9WgXcQ",
20
+ * title: "Never Gonna Give You Up",
21
+ * url: "https://youtube.com/watch?v=dQw4w9WgXcQ",
22
+ * duration: 212000,
23
+ * thumbnail: "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
24
+ * requestedBy: "123456789",
25
+ * source: "youtube",
26
+ * metadata: {
27
+ * artist: "Rick Astley",
28
+ * album: "Whenever You Need Somebody"
29
+ * }
30
+ * };
31
+ *
32
+ * // Track from SoundCloud
33
+ * const soundcloudTrack: Track = {
34
+ * id: "soundcloud-track-123",
35
+ * title: "Electronic Song",
36
+ * url: "https://soundcloud.com/artist/electronic-song",
37
+ * duration: 180000,
38
+ * requestedBy: "user456",
39
+ * source: "soundcloud",
40
+ * metadata: {
41
+ * artist: "Electronic Artist",
42
+ * genre: "Electronic"
43
+ * }
44
+ * };
45
+ *
46
+ * // TTS track
47
+ * const ttsTrack: Track = {
48
+ * id: "tts-" + Date.now(),
49
+ * title: "TTS: Hello everyone!",
50
+ * url: "tts: Hello everyone!",
51
+ * duration: 5000,
52
+ * requestedBy: "user789",
53
+ * source: "tts",
54
+ * metadata: {
55
+ * text: "Hello everyone!",
56
+ * language: "en"
57
+ * }
58
+ * };
59
+ */
60
+ export interface Track {
61
+ id: string;
62
+ title: string;
63
+ url: string;
64
+ duration: number;
65
+ thumbnail?: string;
66
+ requestedBy: string;
67
+ source: string;
68
+ metadata?: Record<string, any>;
69
+ isLive?: boolean;
70
+ author?: string;
71
+ }
72
+
73
+ /**
74
+ * Contains search results from plugins, including tracks and optional playlist information.
75
+ *
76
+ * @example
77
+ * const result: SearchResult = {
78
+ * tracks: [
79
+ * {
80
+ * id: "track1",
81
+ * title: "Song 1",
82
+ * url: "https://example.com/track1",
83
+ * duration: 180000,
84
+ * requestedBy: "user123",
85
+ * source: "youtube"
86
+ * }
87
+ * ],
88
+ * playlist: {
89
+ * name: "My Playlist",
90
+ * url: "https://example.com/playlist",
91
+ * thumbnail: "https://example.com/thumb.jpg"
92
+ * }
93
+ * };
94
+ */
95
+ export interface SearchResult {
96
+ tracks: Track[];
97
+ playlist?: {
98
+ name: string;
99
+ url?: string;
100
+ thumbnail?: string;
101
+ } | null;
102
+ query?: string;
103
+ score?: SearchScore;
104
+ source?: string;
105
+ }
106
+
107
+ export interface SearchScore {
108
+ score: number; // 0-100
109
+ reason: string; // Lý do đạt điểm
110
+ matchedBy: "url" | "title" | "partial" | "none" | "playlist";
111
+ exactMatch: boolean;
112
+ }
113
+ /**
114
+ * Contains streaming information for audio playback.
115
+ *
116
+ * @example
117
+ * const streamInfo: StreamInfo = {
118
+ * stream: audioStream,
119
+ * type: "webm/opus",
120
+ * metadata: {
121
+ * bitrate: 128000,
122
+ * sampleRate: 48000
123
+ * }
124
+ * };
125
+ */
126
+ export interface StreamInfo {
127
+ stream: Readable;
128
+ type: "webm/opus" | "ogg/opus" | "arbitrary" | string;
129
+ metadata?: Record<string, any>;
130
+ position?: number;
131
+ recreate?: (position: number) => Promise<Readable>;
132
+ remote?: boolean;
133
+ handle?: {
134
+ play(): Promise<void>;
135
+ stop(): Promise<void>;
136
+ pause(): Promise<void>;
137
+ resume(): Promise<void>;
138
+ seek(position: number): Promise<void>;
139
+ setVolume(volume: number): Promise<void>;
140
+ destroy(): Promise<void>;
141
+ };
142
+ }
143
+
144
+ /** Passed to each {@link TrackMiddleware} run (before stream resolution). */
145
+ export interface TrackMiddlewareContext {
146
+ player: Player;
147
+ manager: PlayerManager;
148
+ }
149
+
150
+ /**
151
+ * Runs immediately before stream extraction (`Player.getStream`): after enqueue, before extension `provideStream` and plugins.
152
+ * Prefer mutating `track` in place (especially `metadata`). If you return another object, its fields are merged into the original
153
+ * `track` reference so queue/current-track pointers stay valid.
154
+ */
155
+ export type TrackMiddleware = (track: Track, context: TrackMiddlewareContext) => void | Track | Promise<void | Track>;
156
+
157
+ /** Options for {@link PlayerManager.subscribeForwardMirror}. */
158
+ export interface PlaybackMirrorOptions {
159
+ leaderGuildId: string;
160
+ followerGuildIds: string[];
161
+ /**
162
+ * When enabled, follower connections subscribe directly
163
+ * to leader.audioPlayer
164
+ * Default: true
165
+ */
166
+ forwardMode?: boolean;
167
+ }
168
+
169
+ export function normalizeTrackMiddleware(input?: TrackMiddleware | TrackMiddleware[]): TrackMiddleware[] {
170
+ if (!input) return [];
171
+ return Array.isArray(input) ? input : [input];
172
+ }
173
+
174
+ /**
175
+ * Configuration options for creating a new player instance.
176
+ *
177
+ * @example
178
+ * const options: PlayerOptions = {
179
+ * leaveOnEnd: true,
180
+ * leaveOnEmpty: true,
181
+ * leaveTimeout: 30000,
182
+ * volume: 0.5,
183
+ * quality: "high",
184
+ * selfDeaf: false,
185
+ * selfMute: false,
186
+ * extractorTimeout: 10000,
187
+ * tts: {
188
+ * createPlayer: true,
189
+ * interrupt: true,
190
+ * volume: 1.0,
191
+ * maxTimeTts: 30000
192
+ * }
193
+ * };
194
+ */
195
+ export interface PlayerOptions {
196
+ leaveOnEnd?: boolean;
197
+ leaveOnEmpty?: boolean;
198
+ leaveTimeout?: number;
199
+ volume?: number;
200
+ quality?: "high" | "low";
201
+ selfDeaf?: boolean;
202
+ selfMute?: boolean;
203
+ group?: string;
204
+ /**
205
+ * Timeout in milliseconds for plugin operations (search, streaming, etc.)
206
+ * to prevent long-running tasks from blocking the player.
207
+ */
208
+ extractorTimeout?: number;
209
+ userdata?: Record<string, any>;
210
+ /**
211
+ * Text-to-Speech settings. When enabled, the player can create a
212
+ * dedicated AudioPlayer to play TTS while pausing the music player
213
+ * then resume the music after TTS finishes.
214
+ */
215
+ tts?: {
216
+ /** Create a dedicated tts AudioPlayer at construction time */
217
+ createPlayer?: boolean;
218
+ /** Pause music and swap subscription to play TTS */
219
+ interrupt?: boolean;
220
+ /** Default TTS volume multiplier 1 => 100% */
221
+ volume?: number;
222
+ /** Max time tts playback Duration */
223
+ maxTimeTts?: number;
224
+ };
225
+ /**
226
+ * Optional per-player extension selection. When provided, only these
227
+ * extensions will be activated for the created player.
228
+ * - Provide instances or constructors to use them explicitly
229
+ * - Or provide names (string) to select from manager-registered extensions
230
+ */
231
+ extensions?: any[] | string[];
232
+ /**
233
+ * Audio filters configuration. When provided, these filters will be
234
+ * applied to all audio streams played by this player.
235
+ * - Provide filter names (string) to use predefined filters
236
+ * - Or provide AudioFilter objects for custom filters
237
+ * - Multiple filters can be combined
238
+ */
239
+ filters?: (string | AudioFilter)[];
240
+ /**
241
+ * Enable low performance mode.
242
+ * When enabled, heavy features such as preload and crossfade can be auto-disabled.
243
+ */
244
+ lowPerformance?: boolean;
245
+ /**
246
+ * Preload behavior configuration.
247
+ */
248
+ preload?: {
249
+ /**
250
+ * Enable/disable preload explicitly.
251
+ * Default: true
252
+ */
253
+ enabled?: boolean;
254
+ /**
255
+ * Auto disable preload when lowPerformance is enabled.
256
+ * Default: true
257
+ */
258
+ autoDisableInLowPerformance?: boolean;
259
+ };
260
+ /**
261
+ * Crossfade behavior configuration.
262
+ */
263
+ crossfade?: {
264
+ /**
265
+ * Enable/disable crossfade explicitly.
266
+ * If omitted and autoEnable=true, ZiPlayer may enable it automatically.
267
+ */
268
+ enabled?: boolean;
269
+ /**
270
+ * Auto enable crossfade if runtime profile allows.
271
+ * Default: true
272
+ */
273
+ autoEnable?: boolean;
274
+ /**
275
+ * Auto disable crossfade when lowPerformance is enabled.
276
+ * Default: true
277
+ */
278
+ autoDisableInLowPerformance?: boolean;
279
+ /**
280
+ * Target crossfade duration in milliseconds.
281
+ * Default: 500
282
+ */
283
+ durationMs?: number;
284
+ };
285
+ /**
286
+ * Smart transition engine settings.
287
+ */
288
+ smartTransition?: {
289
+ enabled?: boolean;
290
+ /**
291
+ * Prefer genre-aware fade duration when metadata.genre is available.
292
+ */
293
+ genreAware?: boolean;
294
+ /**
295
+ * Try to align transition with beat boundary using metadata.bpm.
296
+ */
297
+ beatAlign?: boolean;
298
+ /**
299
+ * Base duration in milliseconds when no specific profile matched.
300
+ */
301
+ baseDurationMs?: number;
302
+ minDurationMs?: number;
303
+ maxDurationMs?: number;
304
+ /**
305
+ * Genre profiles in milliseconds (e.g. chill, edm, pop, rock).
306
+ */
307
+ genreDurations?: Record<string, number>;
308
+ /**
309
+ * Max wait time while trying to align to beat boundary.
310
+ */
311
+ beatAlignMaxWaitMs?: number;
312
+ };
313
+ /**
314
+ * Recovery strategy for stream/player failures.
315
+ */
316
+ antiStuck?: {
317
+ enabled?: boolean;
318
+ maxRetries?: number;
319
+ retryDelayMs?: number;
320
+ /**
321
+ * Reuse preload/current cached stream before hard retries.
322
+ */
323
+ reusePreloadFirst?: boolean;
324
+ /**
325
+ * Temporarily force low quality during retry attempts.
326
+ */
327
+ reduceQualityOnRetry?: boolean;
328
+ /**
329
+ * If consecutive failures exceed this threshold, allow skipping track.
330
+ */
331
+ controlledSkipThreshold?: number;
332
+ };
333
+ /**
334
+ * Loudness normalization and limiter.
335
+ */
336
+ loudnessNormalization?: {
337
+ enabled?: boolean;
338
+ /**
339
+ * Target LUFS (integrated). Track metadata.lufs is used if available.
340
+ */
341
+ targetLUFS?: number;
342
+ /**
343
+ * Cap gain boost to avoid over-amplification.
344
+ */
345
+ maxBoostDb?: number;
346
+ /**
347
+ * Cap attenuation.
348
+ */
349
+ maxCutDb?: number;
350
+ /**
351
+ * Output ceiling multiplier (<= 1.0), acts as soft limiter ceiling.
352
+ */
353
+ limiterCeiling?: number;
354
+ };
355
+ /**
356
+ * Chain of middleware applied to every track immediately before stream extraction (after queueing).
357
+ * Merged after {@link PlayerManagerOptions.trackMiddleware} from the manager.
358
+ */
359
+ trackMiddleware?: TrackMiddleware | TrackMiddleware[];
360
+
361
+ maxStreamStore?: number;
362
+ }
363
+
364
+ export interface PlayerManagerOptions {
365
+ plugins?: SourcePluginLike[];
366
+ extensions?: any[];
367
+ extractorTimeout?: number;
368
+ autoCleanup?: boolean; // Auto cleanup inactive players
369
+ cleanupInterval?: number; // Cleanup interval in ms
370
+ enableSearchCache?: boolean; // Enable search result caching
371
+ enableStatsCollection?: boolean; // Enable stats collection events
372
+ /**
373
+ * Global track middleware for every {@link Player} created from this manager (before per-player middleware).
374
+ */
375
+ trackMiddleware?: TrackMiddleware | TrackMiddleware[];
376
+ }
377
+
378
+ /**
379
+ * Options for the progress bar
380
+ *
381
+ * @example
382
+ * const options: ProgressBarOptions = {
383
+ * size: 10,
384
+ * barChar: "=",
385
+ * progressChar: ">"
386
+ * };
387
+ */
388
+ export interface ProgressBarOptions {
389
+ size?: number; // Bar length (default: 20)
390
+ barChar?: string; // Character for empty bar (default: "")
391
+ progressChar?: string; // Character for progress pointer (default: "🔘")
392
+ timeFormat?: "compact" | "full"; // Time format style (default: "compact")
393
+ showPercentage?: boolean; // Show percentage at end (default: false)
394
+ showTime?: boolean; // Show time labels (default: true)
395
+ hideProgressChar?: boolean; // Hide progress character (default: false)
396
+ }
397
+
398
+ /**
399
+ * Options for saving tracks
400
+ *
401
+ * @example
402
+ * const options: SaveOptions = {
403
+ * filename: "my-song.mp3",
404
+ * quality: "high",
405
+ * timeout: 30000
406
+ * };
407
+ */
408
+ export interface SaveOptions {
409
+ /** Optional filename for the saved file */
410
+ filename?: string;
411
+ /** Quality of the saved audio ("high" | "low") */
412
+ quality?: "high" | "low";
413
+ /** Timeout in milliseconds for the save operation */
414
+ timeout?: number;
415
+ /** Additional metadata to include */
416
+ metadata?: Record<string, any>;
417
+ /** Filter */
418
+ filter?: AudioFilter[];
419
+ /** Seek position in milliseconds to start saving from */
420
+ seek?: number;
421
+ }
422
+ export interface PlayerSession {
423
+ guildId: string;
424
+ queue: Track[];
425
+ currentTrack: Track | null;
426
+ volume: number;
427
+ loopMode: LoopMode;
428
+ autoPlay: boolean;
429
+ position: number | null;
430
+ extensions: string[];
431
+ plugins: string[];
432
+ userdata?: Record<string, any>;
433
+ }
434
+
435
+ export interface PreloadState {
436
+ resource: AudioResource | null;
437
+ track: Track | null;
438
+ abortController: AbortController | null;
439
+ timeoutId: NodeJS.Timeout | null;
440
+ isValid: boolean;
441
+ streamId?: string;
442
+ isBeingUsed: boolean;
443
+ }
444
+
445
+ export interface ForwardHealthStatus {
446
+ guildId: string;
447
+ healthy: boolean;
448
+ role: "leader" | "follower" | "none";
449
+ issues: string[];
450
+ details: {
451
+ leaderId?: string;
452
+ followerCount?: number;
453
+ connectionState?: string;
454
+ audioPlayerState?: string;
455
+ };
456
+ }
457
+
458
+ export interface PlayerStats {
459
+ totalPlayers: number;
460
+ leader: number;
461
+ follower: number;
462
+ activePlayers: number;
463
+ pausedPlayers: number;
464
+ connectedPlayers: number;
465
+ totalTracksInQueue: number;
466
+ forwardHealthStatus: ForwardHealthStatus[];
467
+ }
468
+
469
+ export interface StreamSlot {
470
+ resource: AudioResource | null;
471
+ track: Track | null;
472
+ streamId: string | null;
473
+ abortController: AbortController | null;
474
+ isValid: boolean;
475
+ isLoading: boolean;
476
+ loadPromise: Promise<void> | null;
477
+ }
478
+
479
+ export type LoopMode = "off" | "track" | "queue";
480
+
481
+ export interface VoiceChannel {
482
+ id: string;
483
+ guildId: string;
484
+ type: any;
485
+ guild: any;
486
+ // Placeholder for VoiceConnection properties and methods
487
+ }
488
+
489
+ /**
490
+ * Event types emitted by Player instances.
491
+ *
492
+ * @example
493
+ *
494
+ * manager.on("willPlay", (player, track) => {
495
+ * console.log(`Up next: ${track.title}`);
496
+ * });
497
+ *
498
+ * manager.on("trackEnd", (player, track) => {
499
+ * console.log(`Now playing: ${track.title}`);
500
+ * });
501
+ *
502
+ * manager.on("queueAdd", (player, track) => {
503
+ * console.log(`Queue added: ${track.title}`);
504
+ * });
505
+ *
506
+ * manager.on("queueAddList", (player, tracks) => {
507
+ * console.log(`Queue added: ${tracks.length} tracks`);
508
+ * });
509
+ *
510
+ * manager.on("queueRemove", (player, track, index) => {
511
+ * console.log(`Queue removed: ${track.title} at index ${index}`);
512
+ * });
513
+ *
514
+ * manager.on("playerPause", (player, track) => {
515
+ * console.log(`Player paused: ${track.title}`);
516
+ * });
517
+ *
518
+ * manager.on("playerResume", (player, track) => {
519
+ * console.log(`Player resumed: ${track.title}`);
520
+ * });
521
+ *
522
+ * manager.on("playerStop", (player) => {
523
+ * console.log("Player stopped");
524
+ * });
525
+ *
526
+ * manager.on("playerDestroy", (player) => {
527
+ * console.log("Player destroyed");
528
+ * });
529
+ *
530
+ * manager.on("ttsStart", (player, payload) => {
531
+ * console.log(`TTS started: ${payload.text}`);
532
+ * });
533
+ *
534
+ * manager.on("ttsEnd", (player) => {
535
+ * console.log("TTS ended");
536
+ * });
537
+ *
538
+ * manager.on("playerError", (player, error, track) => {
539
+ * console.log(`Player error: ${error.message}`);
540
+ * });
541
+ *
542
+ * manager.on("connectionError", (player, error) => {
543
+ * console.log(`Connection error: ${error.message}`);
544
+ * });
545
+ * manager.on("trackStart", (player, track) => {
546
+ * console.log(`Track started: ${track.title}`);
547
+ * });
548
+ *
549
+ * manager.on("volumeChange", (player, oldVolume, newVolume) => {
550
+ * console.log(`Volume changed: ${oldVolume} -> ${newVolume}`);
551
+ * });
552
+ *
553
+ * manager.on("queueEnd", (player) => {
554
+ * console.log("Queue finished");
555
+ * });
556
+ *
557
+ */
558
+ export interface ManagerEvents {
559
+ debug: [message: string, ...args: any[]];
560
+ willPlay: [player: Player, track: Track, upcomingTracks: Track[]];
561
+ trackStart: [player: Player, track: Track];
562
+ trackEnd: [player: Player, track: Track];
563
+ queueEnd: [player: Player];
564
+ playerError: [player: Player, error: Error, track?: Track];
565
+ connectionError: [player: Player, error: Error];
566
+ volumeChange: [player: Player, oldVolume: number, newVolume: number];
567
+ queueAdd: [player: Player, track: Track];
568
+ queueAddList: [player: Player, tracks: Track[]];
569
+ queueRemove: [player: Player, track: Track, index: number];
570
+ playerPause: [player: Player, track: Track];
571
+ playerResume: [player: Player, track: Track];
572
+ playerStop: [player: Player];
573
+ playerDestroy: [player: Player];
574
+ ttsStart: [player: Player, payload: { text?: string; track?: Track }];
575
+ ttsEnd: [player: Player];
576
+ /** Emitted when audio filter is applied */
577
+ filterApplied: [player: Player, filter: AudioFilter];
578
+ /** Emitted when audio filter is removed */
579
+ filterRemoved: [player: Player, filter: AudioFilter];
580
+ /** Emitted when all filters are cleared */
581
+ filtersCleared: [player: Player];
582
+ //extension events
583
+ lyricsCreate: [player: Player, track: Track, lyrics: any];
584
+ lyricsChange: [player: Player, track: Track, lyrics: any];
585
+ voiceCreate: [player: Player, evt: any];
586
+ stats: [stats: PlayerStats];
587
+ streamError: [player: Player, error: Error, track: Track | null];
588
+
589
+ forwardModeStart: [player: Player, leader: Player];
590
+ forwardModeEnd: [player: Player, leader: Player, reason: string | undefined];
591
+ }
592
+ export interface PlayerEvents {
593
+ debug: [message: string, ...args: any[]];
594
+ willPlay: [track: Track, upcomingTracks: Track[]];
595
+ trackStart: [track: Track];
596
+ trackEnd: [track: Track];
597
+ queueEnd: [];
598
+ playerError: [error: Error, track?: Track];
599
+ connectionError: [error: Error];
600
+ volumeChange: [oldVolume: number, newVolume: number];
601
+ queueAdd: [track: Track];
602
+ queueAddList: [tracks: Track[]];
603
+ queueRemove: [track: Track, index: number];
604
+ playerPause: [track: Track];
605
+ playerResume: [track: Track];
606
+ playerStop: [];
607
+ playerDestroy: [];
608
+ /** Emitted when seeking to a position in current track */
609
+ seek: [payload: { track: Track; position: number }];
610
+ /** Emitted when TTS starts playing (interruption mode) */
611
+ ttsStart: [payload: { text?: string; track?: Track }];
612
+ /** Emitted when TTS finished (interruption mode) */
613
+ ttsEnd: [];
614
+ /** Emitted when audio filter is applied */
615
+ filterApplied: [filter: AudioFilter];
616
+ /** Emitted when audio filter is removed */
617
+ filterRemoved: [filter: AudioFilter];
618
+ /** Emitted when all filters are cleared */
619
+ filtersCleared: [];
620
+ trackStuck: [track: Track | null];
621
+ streamError: [error: Error, track: Track | null];
622
+ /** Emitted when player stats are updated (if enabled) */
623
+ stats: [stats: PlayerStats];
624
+ forwardModeStart: [leader: Player];
625
+ forwardModeEnd: [leader: Player, reason: string | undefined];
626
+ }
627
+
628
+ export * from "./fillter";
629
+ export * from "./plugin";
630
+ export * from "./extension";