ziplayer 0.3.1 → 0.3.3
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} +36 -7
- package/README.md +113 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/Player.d.ts +48 -17
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +154 -377
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PlayerManager.d.ts +35 -6
- package/dist/structures/PlayerManager.d.ts.map +1 -1
- package/dist/structures/PlayerManager.js +215 -19
- package/dist/structures/PlayerManager.js.map +1 -1
- package/dist/structures/PreloadManager.d.ts +32 -0
- package/dist/structures/PreloadManager.d.ts.map +1 -0
- package/dist/structures/PreloadManager.js +230 -0
- package/dist/structures/PreloadManager.js.map +1 -0
- 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/structures/StreamManager.d.ts +1 -0
- package/dist/structures/StreamManager.d.ts.map +1 -1
- package/dist/structures/StreamManager.js +37 -3
- package/dist/structures/StreamManager.js.map +1 -1
- package/dist/types/index.d.ts +41 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/plugins/index.ts +1 -1
- package/src/structures/Player.ts +174 -440
- package/src/structures/PlayerManager.ts +253 -23
- package/src/structures/PreloadManager.ts +274 -0
- package/src/structures/Queue.ts +22 -0
- package/src/structures/StreamManager.ts +41 -4
- package/src/types/index.ts +47 -1
|
@@ -37,6 +37,7 @@ export interface StreamManagerOptions {
|
|
|
37
37
|
|
|
38
38
|
export class StreamManager extends EventEmitter {
|
|
39
39
|
private streams = new Map<string, ManagedStream>();
|
|
40
|
+
private suppressPrematureCloseErrors = new Set<string>();
|
|
40
41
|
private options: Required<StreamManagerOptions>;
|
|
41
42
|
private cleanupTimer: NodeJS.Timeout | null = null;
|
|
42
43
|
private metrics = {
|
|
@@ -72,6 +73,20 @@ export class StreamManager extends EventEmitter {
|
|
|
72
73
|
* Register a new stream
|
|
73
74
|
*/
|
|
74
75
|
registerStream(stream: Readable, track: Track, metadata: Partial<ManagedStream["metadata"]> = {}): string {
|
|
76
|
+
for (const existing of this.streams.values()) {
|
|
77
|
+
if (existing.stream === stream) {
|
|
78
|
+
existing.lastAccessed = Date.now();
|
|
79
|
+
existing.track = track;
|
|
80
|
+
existing.metadata = {
|
|
81
|
+
...existing.metadata,
|
|
82
|
+
source: track.source || existing.metadata.source || "unknown",
|
|
83
|
+
...metadata,
|
|
84
|
+
};
|
|
85
|
+
this.debug(`Stream already managed, reusing ID: ${existing.id}`);
|
|
86
|
+
return existing.id;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
const streamId = this.generateStreamId(track);
|
|
76
91
|
|
|
77
92
|
// Check if stream already exists
|
|
@@ -81,8 +96,9 @@ export class StreamManager extends EventEmitter {
|
|
|
81
96
|
}
|
|
82
97
|
|
|
83
98
|
// Check concurrent limit
|
|
84
|
-
|
|
85
|
-
this.evictOldestStream();
|
|
99
|
+
while (this.streams.size >= this.options.maxConcurrentStreams) {
|
|
100
|
+
const evicted = this.evictOldestStream();
|
|
101
|
+
if (!evicted) break;
|
|
86
102
|
}
|
|
87
103
|
|
|
88
104
|
// Configure stream
|
|
@@ -141,6 +157,14 @@ export class StreamManager extends EventEmitter {
|
|
|
141
157
|
private createStreamListeners(streamId: string): ManagedStream["listeners"] {
|
|
142
158
|
return {
|
|
143
159
|
error: (err: Error) => {
|
|
160
|
+
const isPrematureClose = err?.message?.toLowerCase().includes("premature close");
|
|
161
|
+
if (isPrematureClose && this.suppressPrematureCloseErrors.has(streamId)) {
|
|
162
|
+
this.debug(`Ignored expected premature close [${streamId}] during controlled destroy`);
|
|
163
|
+
this.suppressPrematureCloseErrors.delete(streamId);
|
|
164
|
+
this.unregisterStream(streamId, false);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
144
168
|
this.debug(`Stream error [${streamId}]:`, err);
|
|
145
169
|
if (this.options.enableMetrics) {
|
|
146
170
|
this.metrics.totalErrors++;
|
|
@@ -232,6 +256,7 @@ export class StreamManager extends EventEmitter {
|
|
|
232
256
|
unregisterStream(streamId: string, forceDestroy: boolean = true): boolean {
|
|
233
257
|
const managed = this.streams.get(streamId);
|
|
234
258
|
if (!managed) {
|
|
259
|
+
this.suppressPrematureCloseErrors.delete(streamId);
|
|
235
260
|
return false;
|
|
236
261
|
}
|
|
237
262
|
|
|
@@ -258,9 +283,11 @@ export class StreamManager extends EventEmitter {
|
|
|
258
283
|
// Force destroy if needed
|
|
259
284
|
if (forceDestroy && !stream.destroyed && typeof stream.destroy === "function") {
|
|
260
285
|
try {
|
|
286
|
+
this.suppressPrematureCloseErrors.add(streamId);
|
|
261
287
|
stream.destroy();
|
|
262
288
|
managed.status = "destroyed";
|
|
263
289
|
} catch (err) {
|
|
290
|
+
this.suppressPrematureCloseErrors.delete(streamId);
|
|
264
291
|
this.debug(`Error destroying stream:`, err);
|
|
265
292
|
}
|
|
266
293
|
}
|
|
@@ -274,6 +301,7 @@ export class StreamManager extends EventEmitter {
|
|
|
274
301
|
}
|
|
275
302
|
|
|
276
303
|
this.emit("streamUnregistered", { streamId, track: managed.track, reason: forceDestroy ? "destroyed" : "natural" });
|
|
304
|
+
this.suppressPrematureCloseErrors.delete(streamId);
|
|
277
305
|
|
|
278
306
|
return true;
|
|
279
307
|
}
|
|
@@ -336,7 +364,7 @@ export class StreamManager extends EventEmitter {
|
|
|
336
364
|
/**
|
|
337
365
|
* Evict oldest stream when limit reached
|
|
338
366
|
*/
|
|
339
|
-
private evictOldestStream():
|
|
367
|
+
private evictOldestStream(): boolean {
|
|
340
368
|
// Evict lowest priority streams first
|
|
341
369
|
const sorted = Array.from(this.streams.values()).sort((a, b) => a.metadata.priority - b.metadata.priority);
|
|
342
370
|
|
|
@@ -344,9 +372,18 @@ export class StreamManager extends EventEmitter {
|
|
|
344
372
|
if (managed.metadata.isPreload && managed.metadata.priority < 5) {
|
|
345
373
|
this.debug(`Evicting low priority preload stream: ${managed.track.title}`);
|
|
346
374
|
this.unregisterStream(managed.id, true);
|
|
347
|
-
|
|
375
|
+
return true;
|
|
348
376
|
}
|
|
349
377
|
}
|
|
378
|
+
|
|
379
|
+
if (sorted.length > 0) {
|
|
380
|
+
const fallback = sorted[0];
|
|
381
|
+
this.debug(`Evicting fallback stream to enforce limit: ${fallback.track.title}`);
|
|
382
|
+
this.unregisterStream(fallback.id, true);
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return false;
|
|
350
387
|
}
|
|
351
388
|
|
|
352
389
|
/**
|
package/src/types/index.ts
CHANGED
|
@@ -122,6 +122,43 @@ export interface StreamInfo {
|
|
|
122
122
|
metadata?: Record<string, any>;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/** Passed to each {@link TrackMiddleware} run (before stream resolution). */
|
|
126
|
+
export interface TrackMiddlewareContext {
|
|
127
|
+
player: Player;
|
|
128
|
+
manager: PlayerManager;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Runs immediately before stream extraction (`Player.getStream`): after enqueue, before extension `provideStream` and plugins.
|
|
133
|
+
* Prefer mutating `track` in place (especially `metadata`). If you return another object, its fields are merged into the original
|
|
134
|
+
* `track` reference so queue/current-track pointers stay valid.
|
|
135
|
+
*/
|
|
136
|
+
export type TrackMiddleware = (track: Track, context: TrackMiddlewareContext) => void | Track | Promise<void | Track>;
|
|
137
|
+
|
|
138
|
+
/** Options for {@link PlayerManager.subscribePlaybackMirror}. */
|
|
139
|
+
export interface PlaybackMirrorOptions {
|
|
140
|
+
leaderGuildId: string;
|
|
141
|
+
followerGuildIds: string[];
|
|
142
|
+
/** User id passed to follower `play()` (often the bot application id). */
|
|
143
|
+
mirrorUserId: string;
|
|
144
|
+
/** When true (default), follower `setVolume` tracks the leader. */
|
|
145
|
+
syncVolume?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* When enabled, follower connections subscribe directly
|
|
148
|
+
* to leader.audioPlayer instead of creating their own streams.
|
|
149
|
+
*
|
|
150
|
+
* Greatly reduces bandwidth/CPU usage.
|
|
151
|
+
*
|
|
152
|
+
* Default: true
|
|
153
|
+
*/
|
|
154
|
+
forwardMode?: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function normalizeTrackMiddleware(input?: TrackMiddleware | TrackMiddleware[]): TrackMiddleware[] {
|
|
158
|
+
if (!input) return [];
|
|
159
|
+
return Array.isArray(input) ? input : [input];
|
|
160
|
+
}
|
|
161
|
+
|
|
125
162
|
/**
|
|
126
163
|
* Configuration options for creating a new player instance.
|
|
127
164
|
*
|
|
@@ -228,7 +265,7 @@ export interface PlayerOptions {
|
|
|
228
265
|
autoDisableInLowPerformance?: boolean;
|
|
229
266
|
/**
|
|
230
267
|
* Target crossfade duration in milliseconds.
|
|
231
|
-
* Default:
|
|
268
|
+
* Default: 500
|
|
232
269
|
*/
|
|
233
270
|
durationMs?: number;
|
|
234
271
|
};
|
|
@@ -302,6 +339,11 @@ export interface PlayerOptions {
|
|
|
302
339
|
*/
|
|
303
340
|
limiterCeiling?: number;
|
|
304
341
|
};
|
|
342
|
+
/**
|
|
343
|
+
* Chain of middleware applied to every track immediately before stream extraction (after queueing).
|
|
344
|
+
* Merged after {@link PlayerManagerOptions.trackMiddleware} from the manager.
|
|
345
|
+
*/
|
|
346
|
+
trackMiddleware?: TrackMiddleware | TrackMiddleware[];
|
|
305
347
|
}
|
|
306
348
|
|
|
307
349
|
export interface PlayerManagerOptions {
|
|
@@ -312,6 +354,10 @@ export interface PlayerManagerOptions {
|
|
|
312
354
|
cleanupInterval?: number; // Cleanup interval in ms
|
|
313
355
|
enableSearchCache?: boolean; // Enable search result caching
|
|
314
356
|
enableStatsCollection?: boolean; // Enable stats collection events
|
|
357
|
+
/**
|
|
358
|
+
* Global track middleware for every {@link Player} created from this manager (before per-player middleware).
|
|
359
|
+
*/
|
|
360
|
+
trackMiddleware?: TrackMiddleware | TrackMiddleware[];
|
|
315
361
|
}
|
|
316
362
|
|
|
317
363
|
/**
|