ziplayer 0.2.7 → 0.3.1

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.
Files changed (63) hide show
  1. package/AI-Guide.md +624 -956
  2. package/README.md +277 -10
  3. package/dist/extensions/BaseExtension.d.ts +1 -0
  4. package/dist/extensions/BaseExtension.d.ts.map +1 -1
  5. package/dist/extensions/BaseExtension.js.map +1 -1
  6. package/dist/extensions/index.d.ts +38 -3
  7. package/dist/extensions/index.d.ts.map +1 -1
  8. package/dist/extensions/index.js +259 -41
  9. package/dist/extensions/index.js.map +1 -1
  10. package/dist/persistence/PersistenceManager.d.ts +95 -0
  11. package/dist/persistence/PersistenceManager.d.ts.map +1 -0
  12. package/dist/persistence/PersistenceManager.js +975 -0
  13. package/dist/persistence/PersistenceManager.js.map +1 -0
  14. package/dist/plugins/BasePlugin.js +1 -1
  15. package/dist/plugins/BasePlugin.js.map +1 -1
  16. package/dist/plugins/index.d.ts +74 -8
  17. package/dist/plugins/index.d.ts.map +1 -1
  18. package/dist/plugins/index.js +657 -116
  19. package/dist/plugins/index.js.map +1 -1
  20. package/dist/structures/FilterManager.js +3 -3
  21. package/dist/structures/FilterManager.js.map +1 -1
  22. package/dist/structures/PersistenceManager.d.ts +96 -0
  23. package/dist/structures/PersistenceManager.d.ts.map +1 -0
  24. package/dist/structures/PersistenceManager.js +1008 -0
  25. package/dist/structures/PersistenceManager.js.map +1 -0
  26. package/dist/structures/Player.d.ts +158 -14
  27. package/dist/structures/Player.d.ts.map +1 -1
  28. package/dist/structures/Player.js +1175 -188
  29. package/dist/structures/Player.js.map +1 -1
  30. package/dist/structures/PlayerManager.d.ts +106 -91
  31. package/dist/structures/PlayerManager.d.ts.map +1 -1
  32. package/dist/structures/PlayerManager.js +365 -124
  33. package/dist/structures/PlayerManager.js.map +1 -1
  34. package/dist/structures/Queue.d.ts +136 -31
  35. package/dist/structures/Queue.d.ts.map +1 -1
  36. package/dist/structures/Queue.js +265 -46
  37. package/dist/structures/Queue.js.map +1 -1
  38. package/dist/structures/StreamManager.d.ts +137 -0
  39. package/dist/structures/StreamManager.d.ts.map +1 -0
  40. package/dist/structures/StreamManager.js +420 -0
  41. package/dist/structures/StreamManager.js.map +1 -0
  42. package/dist/types/index.d.ts +181 -8
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/dist/types/index.js.map +1 -1
  45. package/dist/types/persistence.d.ts +77 -0
  46. package/dist/types/persistence.d.ts.map +1 -0
  47. package/dist/types/persistence.js +3 -0
  48. package/dist/types/persistence.js.map +1 -0
  49. package/package.json +3 -2
  50. package/src/extensions/BaseExtension.ts +1 -0
  51. package/src/extensions/index.ts +320 -37
  52. package/src/plugins/BasePlugin.ts +1 -1
  53. package/src/plugins/index.ts +809 -139
  54. package/src/structures/FilterManager.ts +3 -3
  55. package/src/structures/Player.ts +2810 -1693
  56. package/src/structures/PlayerManager.ts +438 -129
  57. package/src/structures/Queue.ts +300 -55
  58. package/src/structures/StreamManager.ts +524 -0
  59. package/src/types/extension.ts +129 -129
  60. package/src/types/fillter.ts +264 -264
  61. package/src/types/index.ts +187 -12
  62. package/src/types/plugin.ts +59 -59
  63. package/tsconfig.json +0 -1
@@ -0,0 +1,524 @@
1
+ // src/managers/StreamManager.ts
2
+ import { Readable } from "stream";
3
+ import { EventEmitter } from "events";
4
+ import type { Track } from "../types";
5
+
6
+ export interface ManagedStream {
7
+ id: string;
8
+ stream: Readable;
9
+ track: Track;
10
+ createdAt: number;
11
+ lastAccessed: number;
12
+ metadata: {
13
+ source: string;
14
+ isPreload: boolean;
15
+ priority: number;
16
+ };
17
+ listeners: {
18
+ error: (err: Error) => void;
19
+ close: () => void;
20
+ end: () => void;
21
+ drain?: () => void;
22
+ pause?: () => void;
23
+ resume?: () => void;
24
+ };
25
+ status: "active" | "paused" | "ended" | "error" | "destroyed";
26
+ byteCount: number;
27
+ }
28
+
29
+ export interface StreamManagerOptions {
30
+ maxConcurrentStreams?: number;
31
+ streamTimeout?: number;
32
+ maxListenersPerStream?: number;
33
+ cleanupInterval?: number;
34
+ enableMetrics?: boolean;
35
+ autoDestroy?: boolean;
36
+ }
37
+
38
+ export class StreamManager extends EventEmitter {
39
+ private streams = new Map<string, ManagedStream>();
40
+ private options: Required<StreamManagerOptions>;
41
+ private cleanupTimer: NodeJS.Timeout | null = null;
42
+ private metrics = {
43
+ totalStreamsCreated: 0,
44
+ totalStreamsDestroyed: 0,
45
+ activeStreams: 0,
46
+ totalErrors: 0,
47
+ totalBytesProcessed: 0,
48
+ };
49
+
50
+ constructor(options: StreamManagerOptions = {}) {
51
+ super();
52
+ this.setMaxListeners(50);
53
+
54
+ this.options = {
55
+ maxConcurrentStreams: 20,
56
+ streamTimeout: 5 * 60 * 1000, // 5 minutes
57
+ maxListenersPerStream: 15,
58
+ cleanupInterval: 60000, // 1 minute
59
+ enableMetrics: true,
60
+ autoDestroy: true,
61
+ ...options,
62
+ };
63
+
64
+ if (this.options.cleanupInterval > 0) {
65
+ this.startCleanupInterval();
66
+ }
67
+
68
+ this.debug("StreamManager initialized");
69
+ }
70
+
71
+ /**
72
+ * Register a new stream
73
+ */
74
+ registerStream(stream: Readable, track: Track, metadata: Partial<ManagedStream["metadata"]> = {}): string {
75
+ const streamId = this.generateStreamId(track);
76
+
77
+ // Check if stream already exists
78
+ if (this.streams.has(streamId)) {
79
+ this.debug(`Stream already exists for track: ${track.title}, destroying old one`);
80
+ this.unregisterStream(streamId, true);
81
+ }
82
+
83
+ // Check concurrent limit
84
+ if (this.streams.size >= this.options.maxConcurrentStreams) {
85
+ this.evictOldestStream();
86
+ }
87
+
88
+ // Configure stream
89
+ if (stream.setMaxListeners) {
90
+ stream.setMaxListeners(this.options.maxListenersPerStream);
91
+ }
92
+
93
+ // Create listeners
94
+ const listeners = this.createStreamListeners(streamId);
95
+
96
+ // Apply listeners
97
+ stream.on("error", listeners.error);
98
+ stream.on("close", listeners.close);
99
+ stream.on("end", listeners.end);
100
+ stream.on("pause", listeners.pause!);
101
+ stream.on("resume", listeners.resume!);
102
+ stream.on("drain", listeners.drain!);
103
+
104
+ // Create managed stream
105
+ const managedStream: ManagedStream = {
106
+ id: streamId,
107
+ stream,
108
+ track,
109
+ createdAt: Date.now(),
110
+ lastAccessed: Date.now(),
111
+ metadata: {
112
+ source: track.source || "unknown",
113
+ isPreload: metadata.isPreload || false,
114
+ priority: metadata.priority || 0,
115
+ ...metadata,
116
+ },
117
+ listeners,
118
+ status: "active",
119
+ byteCount: 0,
120
+ };
121
+
122
+ this.streams.set(streamId, managedStream);
123
+
124
+ if (this.options.enableMetrics) {
125
+ this.metrics.totalStreamsCreated++;
126
+ this.metrics.activeStreams = this.streams.size;
127
+ }
128
+
129
+ // Setup data counter
130
+ this.setupDataCounter(managedStream);
131
+
132
+ this.debug(`Stream registered: ${track.title} (ID: ${streamId}), Total: ${this.streams.size}`);
133
+ this.emit("streamRegistered", { streamId, track, metadata: managedStream.metadata });
134
+
135
+ return streamId;
136
+ }
137
+
138
+ /**
139
+ * Create stream listeners
140
+ */
141
+ private createStreamListeners(streamId: string): ManagedStream["listeners"] {
142
+ return {
143
+ error: (err: Error) => {
144
+ this.debug(`Stream error [${streamId}]:`, err);
145
+ if (this.options.enableMetrics) {
146
+ this.metrics.totalErrors++;
147
+ }
148
+ this.emit("streamError", { streamId, error: err });
149
+ this.unregisterStream(streamId, true);
150
+ },
151
+
152
+ close: () => {
153
+ this.debug(`Stream closed [${streamId}]`);
154
+ const managed = this.streams.get(streamId);
155
+ if (managed) {
156
+ managed.status = "ended";
157
+ }
158
+ this.emit("streamClose", { streamId });
159
+ this.unregisterStream(streamId, false);
160
+ },
161
+
162
+ end: () => {
163
+ this.debug(`Stream ended [${streamId}]`);
164
+ const managed = this.streams.get(streamId);
165
+ if (managed) {
166
+ managed.status = "ended";
167
+ }
168
+ this.emit("streamEnd", { streamId });
169
+ this.unregisterStream(streamId, false);
170
+ },
171
+
172
+ pause: () => {
173
+ const managed = this.streams.get(streamId);
174
+ if (managed) {
175
+ managed.status = "paused";
176
+ this.emit("streamPaused", { streamId, track: managed.track });
177
+ }
178
+ },
179
+
180
+ resume: () => {
181
+ const managed = this.streams.get(streamId);
182
+ if (managed) {
183
+ managed.status = "active";
184
+ managed.lastAccessed = Date.now();
185
+ this.emit("streamResumed", { streamId, track: managed.track });
186
+ }
187
+ },
188
+
189
+ drain: () => {
190
+ const managed = this.streams.get(streamId);
191
+ if (managed) {
192
+ this.emit("streamDrained", { streamId, track: managed.track });
193
+ }
194
+ },
195
+ };
196
+ }
197
+
198
+ /**
199
+ * Setup data counter for stream
200
+ */
201
+ private setupDataCounter(managed: ManagedStream): void {
202
+ let dataListener: (chunk: Buffer) => void;
203
+
204
+ if (managed.stream.readable) {
205
+ dataListener = (chunk: Buffer) => {
206
+ managed.byteCount += chunk.length;
207
+ if (this.options.enableMetrics) {
208
+ this.metrics.totalBytesProcessed += chunk.length;
209
+ }
210
+
211
+ // Emit progress every ~1MB
212
+ if (managed.byteCount % (1024 * 1024) < chunk.length) {
213
+ this.emit("streamProgress", {
214
+ streamId: managed.id,
215
+ track: managed.track,
216
+ bytes: managed.byteCount,
217
+ megabytes: Math.floor(managed.byteCount / (1024 * 1024)),
218
+ });
219
+ }
220
+ };
221
+
222
+ managed.stream.on("data", dataListener);
223
+
224
+ // Store data listener for cleanup
225
+ (managed as any).dataListener = dataListener;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Unregister a stream
231
+ */
232
+ unregisterStream(streamId: string, forceDestroy: boolean = true): boolean {
233
+ const managed = this.streams.get(streamId);
234
+ if (!managed) {
235
+ return false;
236
+ }
237
+
238
+ this.debug(`Unregistering stream: ${managed.track.title} (${streamId})`);
239
+
240
+ // Remove data listener
241
+ const dataListener = (managed as any).dataListener;
242
+ if (dataListener && managed.stream) {
243
+ managed.stream.removeListener("data", dataListener);
244
+ }
245
+
246
+ // Remove all listeners
247
+ const { listeners } = managed;
248
+ const stream = managed.stream;
249
+
250
+ if (stream) {
251
+ stream.removeListener("error", listeners.error);
252
+ stream.removeListener("close", listeners.close);
253
+ stream.removeListener("end", listeners.end);
254
+ stream.removeListener("pause", listeners.pause!);
255
+ stream.removeListener("resume", listeners.resume!);
256
+ stream.removeListener("drain", listeners.drain!);
257
+
258
+ // Force destroy if needed
259
+ if (forceDestroy && !stream.destroyed && typeof stream.destroy === "function") {
260
+ try {
261
+ stream.destroy();
262
+ managed.status = "destroyed";
263
+ } catch (err) {
264
+ this.debug(`Error destroying stream:`, err);
265
+ }
266
+ }
267
+ }
268
+
269
+ this.streams.delete(streamId);
270
+
271
+ if (this.options.enableMetrics) {
272
+ this.metrics.totalStreamsDestroyed++;
273
+ this.metrics.activeStreams = this.streams.size;
274
+ }
275
+
276
+ this.emit("streamUnregistered", { streamId, track: managed.track, reason: forceDestroy ? "destroyed" : "natural" });
277
+
278
+ return true;
279
+ }
280
+
281
+ /**
282
+ * Get a stream by ID
283
+ */
284
+ getStream(streamId: string): Readable | null {
285
+ const managed = this.streams.get(streamId);
286
+ if (managed && managed.status === "active") {
287
+ managed.lastAccessed = Date.now();
288
+ return managed.stream;
289
+ }
290
+ return null;
291
+ }
292
+
293
+ /**
294
+ * Update stream metadata
295
+ */
296
+ updateMetadata(streamId: string, metadata: Partial<ManagedStream["metadata"]>): boolean {
297
+ const managed = this.streams.get(streamId);
298
+ if (managed) {
299
+ managed.metadata = { ...managed.metadata, ...metadata };
300
+ managed.lastAccessed = Date.now();
301
+ this.emit("streamMetadataUpdated", { streamId, metadata });
302
+ return true;
303
+ }
304
+ return false;
305
+ }
306
+
307
+ /**
308
+ * Pause a stream
309
+ */
310
+ pauseStream(streamId: string): boolean {
311
+ const managed = this.streams.get(streamId);
312
+ if (managed && managed.status === "active" && !managed.stream.isPaused()) {
313
+ managed.stream.pause();
314
+ managed.status = "paused";
315
+ this.emit("streamPaused", { streamId, track: managed.track });
316
+ return true;
317
+ }
318
+ return false;
319
+ }
320
+
321
+ /**
322
+ * Resume a stream
323
+ */
324
+ resumeStream(streamId: string): boolean {
325
+ const managed = this.streams.get(streamId);
326
+ if (managed && managed.status === "paused") {
327
+ managed.stream.resume();
328
+ managed.status = "active";
329
+ managed.lastAccessed = Date.now();
330
+ this.emit("streamResumed", { streamId, track: managed.track });
331
+ return true;
332
+ }
333
+ return false;
334
+ }
335
+
336
+ /**
337
+ * Evict oldest stream when limit reached
338
+ */
339
+ private evictOldestStream(): void {
340
+ // Evict lowest priority streams first
341
+ const sorted = Array.from(this.streams.values()).sort((a, b) => a.metadata.priority - b.metadata.priority);
342
+
343
+ for (const managed of sorted) {
344
+ if (managed.metadata.isPreload && managed.metadata.priority < 5) {
345
+ this.debug(`Evicting low priority preload stream: ${managed.track.title}`);
346
+ this.unregisterStream(managed.id, true);
347
+ break;
348
+ }
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Cleanup expired streams
354
+ */
355
+ private cleanupExpiredStreams(): void {
356
+ const now = Date.now();
357
+ let cleaned = 0;
358
+
359
+ for (const [streamId, managed] of this.streams) {
360
+ const age = now - managed.lastAccessed;
361
+
362
+ if (age > this.options.streamTimeout) {
363
+ this.debug(`Cleaning up expired stream: ${managed.track.title} (age: ${age}ms)`);
364
+ this.unregisterStream(streamId, this.options.autoDestroy);
365
+ cleaned++;
366
+ }
367
+ }
368
+
369
+ if (cleaned > 0) {
370
+ this.emit("cleanupCompleted", { cleaned, remaining: this.streams.size });
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Start automatic cleanup interval
376
+ */
377
+ private startCleanupInterval(): void {
378
+ if (this.cleanupTimer) {
379
+ clearInterval(this.cleanupTimer);
380
+ }
381
+
382
+ this.cleanupTimer = setInterval(() => {
383
+ this.cleanupExpiredStreams();
384
+ }, this.options.cleanupInterval);
385
+
386
+ this.cleanupTimer.unref(); // Don't keep process alive
387
+ }
388
+
389
+ /**
390
+ * Stop cleanup interval
391
+ */
392
+ stopCleanupInterval(): void {
393
+ if (this.cleanupTimer) {
394
+ clearInterval(this.cleanupTimer);
395
+ this.cleanupTimer = null;
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Get all active streams
401
+ */
402
+ getAllStreams(): ManagedStream[] {
403
+ return Array.from(this.streams.values());
404
+ }
405
+
406
+ /**
407
+ * Get streams by status
408
+ */
409
+ getStreamsByStatus(status: ManagedStream["status"]): ManagedStream[] {
410
+ return Array.from(this.streams.values()).filter((s) => s.status === status);
411
+ }
412
+
413
+ /**
414
+ * Get stream by track ID (using track.id, track.url, or track.title as identifier)
415
+ */
416
+ getStreamByTrack(trackId: string): Readable | null {
417
+ for (const managed of this.streams.values()) {
418
+ const managedTrackId = managed.track.id || managed.track.url || managed.track.title;
419
+ if (managedTrackId === trackId && managed.status === "active") {
420
+ managed.lastAccessed = Date.now();
421
+ return managed.stream;
422
+ }
423
+ }
424
+ return null;
425
+ }
426
+
427
+ /**
428
+ * Check if a stream exists for a given track ID
429
+ */
430
+ hasStream(trackId: string): boolean {
431
+ for (const managed of this.streams.values()) {
432
+ const managedTrackId = managed.track.id || managed.track.url || managed.track.title;
433
+ if (managedTrackId === trackId && managed.status === "active") {
434
+ return true;
435
+ }
436
+ }
437
+ return false;
438
+ }
439
+ /**
440
+ * Get stream count
441
+ */
442
+ getStreamCount(): number {
443
+ return this.streams.size;
444
+ }
445
+
446
+ /**
447
+ * Get metrics
448
+ */
449
+ getMetrics(): typeof this.metrics {
450
+ if (!this.options.enableMetrics) {
451
+ return {
452
+ totalStreamsCreated: 0,
453
+ totalStreamsDestroyed: 0,
454
+ activeStreams: 0,
455
+ totalErrors: 0,
456
+ totalBytesProcessed: 0,
457
+ };
458
+ }
459
+ return { ...this.metrics };
460
+ }
461
+
462
+ /**
463
+ * Get statistics
464
+ */
465
+ getStats(): {
466
+ active: number;
467
+ paused: number;
468
+ ended: number;
469
+ error: number;
470
+ destroyed: number;
471
+ total: number;
472
+ bySource: Record<string, number>;
473
+ } {
474
+ const stats = {
475
+ active: 0,
476
+ paused: 0,
477
+ ended: 0,
478
+ error: 0,
479
+ destroyed: 0,
480
+ total: 0,
481
+ bySource: {} as Record<string, number>,
482
+ };
483
+
484
+ for (const managed of this.streams.values()) {
485
+ stats[managed.status]++;
486
+ stats.total++;
487
+
488
+ const source = managed.metadata.source;
489
+ stats.bySource[source] = (stats.bySource[source] || 0) + 1;
490
+ }
491
+
492
+ return stats;
493
+ }
494
+
495
+ /**
496
+ * Destroy all streams
497
+ */
498
+ destroyAll(force: boolean = true): void {
499
+ this.debug(`Destroying all streams (${this.streams.size})`);
500
+
501
+ for (const streamId of Array.from(this.streams.keys())) {
502
+ this.unregisterStream(streamId, force);
503
+ }
504
+
505
+ this.stopCleanupInterval();
506
+ this.emit("destroyed", { totalDestroyed: this.metrics.totalStreamsDestroyed });
507
+ }
508
+
509
+ /**
510
+ * Generate unique stream ID
511
+ */
512
+ private generateStreamId(track: Track): string {
513
+ return `${track.source || "unknown"}:${track.id || track.url || track.title}:${Date.now()}:${Math.random().toString(36).substr(2, 9)}`;
514
+ }
515
+
516
+ /**
517
+ * Debug logging
518
+ */
519
+ private debug(message: string, ...args: any[]): void {
520
+ if (this.listenerCount("debug") > 0) {
521
+ this.emit("debug", `[StreamManager] ${message}`, ...args);
522
+ }
523
+ }
524
+ }