ziplayer 0.3.7 → 0.3.9
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/dist/plugins/index.d.ts +1 -8
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +52 -106
- package/dist/plugins/index.js.map +1 -1
- package/dist/structures/FilterManager.d.ts +7 -24
- package/dist/structures/FilterManager.d.ts.map +1 -1
- package/dist/structures/FilterManager.js +123 -99
- package/dist/structures/FilterManager.js.map +1 -1
- package/dist/structures/Player.d.ts +2 -0
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +113 -91
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PreloadManager.d.ts +1 -0
- package/dist/structures/PreloadManager.d.ts.map +1 -1
- package/dist/structures/PreloadManager.js +26 -6
- package/dist/structures/PreloadManager.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.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/plugins/index.ts +63 -119
- package/src/structures/FilterManager.ts +439 -380
- package/src/structures/Player.ts +133 -99
- package/src/structures/PreloadManager.ts +293 -274
- package/src/structures/StreamManager.ts +2 -0
- package/src/types/index.ts +2 -0
|
@@ -1,274 +1,293 @@
|
|
|
1
|
-
import { createAudioResource, AudioResource } from "@discordjs/voice";
|
|
2
|
-
import type { Track, StreamInfo, StreamSlot } from "../types";
|
|
3
|
-
import type { StreamManager } from "./StreamManager";
|
|
4
|
-
|
|
5
|
-
interface PreloadManagerDeps {
|
|
6
|
-
streamManager: StreamManager;
|
|
7
|
-
debug: (message?: any, ...optionalParams: any[]) => void;
|
|
8
|
-
getNextTrack: () => Track | null;
|
|
9
|
-
getStream: (track: Track) => Promise<StreamInfo | null>;
|
|
10
|
-
isDestroyed: () => boolean;
|
|
11
|
-
isEnabled: () => boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class PreloadManager {
|
|
15
|
-
private readonly streamManager: StreamManager;
|
|
16
|
-
private readonly debugLog: (message?: any, ...optionalParams: any[]) => void;
|
|
17
|
-
private readonly getNextTrack: () => Track | null;
|
|
18
|
-
private readonly getStream: (track: Track) => Promise<StreamInfo | null>;
|
|
19
|
-
private readonly isDestroyed: () => boolean;
|
|
20
|
-
private readonly isEnabled: () => boolean;
|
|
21
|
-
|
|
22
|
-
private preloadLock = false;
|
|
23
|
-
private readonly preloadSlot: StreamSlot = {
|
|
24
|
-
resource: null,
|
|
25
|
-
track: null,
|
|
26
|
-
streamId: null,
|
|
27
|
-
abortController: null,
|
|
28
|
-
isValid: false,
|
|
29
|
-
isLoading: false,
|
|
30
|
-
loadPromise: null,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
constructor(deps: PreloadManagerDeps) {
|
|
34
|
-
this.streamManager = deps.streamManager;
|
|
35
|
-
this.debugLog = deps.debug;
|
|
36
|
-
this.getNextTrack = deps.getNextTrack;
|
|
37
|
-
this.getStream = deps.getStream;
|
|
38
|
-
this.isDestroyed = deps.isDestroyed;
|
|
39
|
-
this.isEnabled = deps.isEnabled;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return
|
|
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
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
this.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (this.preloadSlot.
|
|
168
|
-
this.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (this.preloadSlot.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.preloadSlot.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
if (!
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
1
|
+
import { createAudioResource, AudioResource } from "@discordjs/voice";
|
|
2
|
+
import type { Track, StreamInfo, StreamSlot } from "../types";
|
|
3
|
+
import type { StreamManager } from "./StreamManager";
|
|
4
|
+
|
|
5
|
+
interface PreloadManagerDeps {
|
|
6
|
+
streamManager: StreamManager;
|
|
7
|
+
debug: (message?: any, ...optionalParams: any[]) => void;
|
|
8
|
+
getNextTrack: () => Track | null;
|
|
9
|
+
getStream: (track: Track) => Promise<StreamInfo | null>;
|
|
10
|
+
isDestroyed: () => boolean;
|
|
11
|
+
isEnabled: () => boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class PreloadManager {
|
|
15
|
+
private readonly streamManager: StreamManager;
|
|
16
|
+
private readonly debugLog: (message?: any, ...optionalParams: any[]) => void;
|
|
17
|
+
private readonly getNextTrack: () => Track | null;
|
|
18
|
+
private readonly getStream: (track: Track) => Promise<StreamInfo | null>;
|
|
19
|
+
private readonly isDestroyed: () => boolean;
|
|
20
|
+
private readonly isEnabled: () => boolean;
|
|
21
|
+
|
|
22
|
+
private preloadLock = false;
|
|
23
|
+
private readonly preloadSlot: StreamSlot = {
|
|
24
|
+
resource: null,
|
|
25
|
+
track: null,
|
|
26
|
+
streamId: null,
|
|
27
|
+
abortController: null,
|
|
28
|
+
isValid: false,
|
|
29
|
+
isLoading: false,
|
|
30
|
+
loadPromise: null,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
constructor(deps: PreloadManagerDeps) {
|
|
34
|
+
this.streamManager = deps.streamManager;
|
|
35
|
+
this.debugLog = deps.debug;
|
|
36
|
+
this.getNextTrack = deps.getNextTrack;
|
|
37
|
+
this.getStream = deps.getStream;
|
|
38
|
+
this.isDestroyed = deps.isDestroyed;
|
|
39
|
+
this.isEnabled = deps.isEnabled;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private trackMatches(a: Track | null, b: Track | null): boolean {
|
|
43
|
+
if (!a || !b) return false;
|
|
44
|
+
if (a === b) return true;
|
|
45
|
+
if (a.id !== undefined && b.id !== undefined) return a.id === b.id;
|
|
46
|
+
// At least one id is missing — require url to match too
|
|
47
|
+
return a.url === b.url && a.url !== undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public hasValidPreload(track: Track): boolean {
|
|
51
|
+
return !!(
|
|
52
|
+
this.preloadSlot.isValid &&
|
|
53
|
+
this.trackMatches(this.preloadSlot.track, track) &&
|
|
54
|
+
this.preloadSlot.resource &&
|
|
55
|
+
this.preloadSlot.resource.playStream?.readable !== false
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public promoteToCurrent(track: Track, currentSlot: StreamSlot): AudioResource | null {
|
|
60
|
+
const promotedResource = this.preloadSlot.resource;
|
|
61
|
+
const promotedStreamId = this.preloadSlot.streamId;
|
|
62
|
+
if (!promotedResource) return null;
|
|
63
|
+
|
|
64
|
+
// upgrade stream priority BEFORE clearing the preload slot so
|
|
65
|
+
// that if registerStream for the next preload triggers eviction in the same
|
|
66
|
+
// tick, the promoted stream is already marked high-priority.
|
|
67
|
+
if (promotedStreamId) {
|
|
68
|
+
this.streamManager.updateMetadata(promotedStreamId, {
|
|
69
|
+
isPreload: false,
|
|
70
|
+
priority: 10,
|
|
71
|
+
});
|
|
72
|
+
this.debugLog(`[Preload] Promoted stream ${promotedStreamId} metadata updated to current (priority:10, isPreload:false)`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
currentSlot.resource = promotedResource;
|
|
76
|
+
currentSlot.track = track;
|
|
77
|
+
currentSlot.streamId = promotedStreamId;
|
|
78
|
+
currentSlot.abortController = null;
|
|
79
|
+
currentSlot.isValid = true;
|
|
80
|
+
currentSlot.isLoading = false;
|
|
81
|
+
currentSlot.loadPromise = null;
|
|
82
|
+
|
|
83
|
+
this.preloadSlot.resource = null;
|
|
84
|
+
this.preloadSlot.track = null;
|
|
85
|
+
this.preloadSlot.streamId = null;
|
|
86
|
+
this.preloadSlot.abortController = null;
|
|
87
|
+
this.preloadSlot.isValid = false;
|
|
88
|
+
this.preloadSlot.isLoading = false;
|
|
89
|
+
this.preloadSlot.loadPromise = null;
|
|
90
|
+
|
|
91
|
+
return promotedResource;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public async preloadNextTrack(): Promise<void> {
|
|
95
|
+
if (this.isDestroyed()) return;
|
|
96
|
+
if (!this.isEnabled()) {
|
|
97
|
+
this.debugLog(`[Preload] Disabled by options/runtime profile`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (this.preloadLock) {
|
|
102
|
+
this.debugLog(`[Preload] Already preloading, skipping`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const nextTrack = this.getNextTrack();
|
|
107
|
+
if (!nextTrack) {
|
|
108
|
+
this.debugLog(`[Preload] No next track to preload`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (this.preloadSlot.isValid && this.trackMatches(this.preloadSlot.track, nextTrack) && this.preloadSlot.resource) {
|
|
113
|
+
this.debugLog(`[Preload] Already have valid preload for: ${nextTrack.title}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (this.preloadSlot.isLoading && this.trackMatches(this.preloadSlot.track, nextTrack)) {
|
|
118
|
+
this.debugLog(`[Preload] Currently loading same track, waiting...`);
|
|
119
|
+
if (this.preloadSlot.loadPromise) {
|
|
120
|
+
await this.preloadSlot.loadPromise;
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.preloadSlot.isValid && !this.trackMatches(this.preloadSlot.track, nextTrack)) {
|
|
126
|
+
this.debugLog(`[Preload] Cancelling old preload for different track: ${this.preloadSlot.track?.title}`);
|
|
127
|
+
await this.safeCancelPreload();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.preloadLock = true;
|
|
131
|
+
const abortController = new AbortController();
|
|
132
|
+
this.preloadSlot.track = nextTrack;
|
|
133
|
+
this.preloadSlot.abortController = abortController;
|
|
134
|
+
this.preloadSlot.isLoading = true;
|
|
135
|
+
|
|
136
|
+
const loadPromise = this.executePreload(nextTrack, abortController);
|
|
137
|
+
this.preloadSlot.loadPromise = loadPromise;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await loadPromise;
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (err instanceof Error && err.message === "PRELOAD_CANCELLED") {
|
|
143
|
+
this.debugLog(`[Preload] Cancelled for ${nextTrack.title}`);
|
|
144
|
+
} else {
|
|
145
|
+
this.debugLog(`[Preload] Failed for ${nextTrack.title}:`, err);
|
|
146
|
+
}
|
|
147
|
+
this.clearPreloadSlot();
|
|
148
|
+
} finally {
|
|
149
|
+
this.preloadLock = false;
|
|
150
|
+
this.preloadSlot.isLoading = false;
|
|
151
|
+
this.preloadSlot.loadPromise = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public async safeCancelPreload(): Promise<void> {
|
|
156
|
+
if (!this.preloadSlot.abortController && !this.preloadSlot.resource) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.debugLog(`[Preload] Safely cancelling preload for: ${this.preloadSlot.track?.title || "unknown"}`);
|
|
161
|
+
|
|
162
|
+
if (this.preloadSlot.abortController) {
|
|
163
|
+
this.preloadSlot.abortController.abort();
|
|
164
|
+
this.preloadSlot.abortController = null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (this.preloadSlot.streamId) {
|
|
168
|
+
this.streamManager.unregisterStream(this.preloadSlot.streamId, true);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this.preloadSlot.resource) {
|
|
172
|
+
try {
|
|
173
|
+
const stream = this.preloadSlot.resource.playStream;
|
|
174
|
+
if (stream && typeof stream.destroy === "function" && !stream.destroyed) {
|
|
175
|
+
stream.destroy();
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
// ignore
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.clearPreloadSlot();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public cancelPreload(): void {
|
|
186
|
+
if (this.preloadSlot.abortController) {
|
|
187
|
+
this.debugLog(`[Preload] Cancelling preload for: ${this.preloadSlot.track?.title}`);
|
|
188
|
+
this.preloadSlot.abortController.abort();
|
|
189
|
+
}
|
|
190
|
+
if (this.preloadSlot.streamId) {
|
|
191
|
+
this.streamManager.unregisterStream(this.preloadSlot.streamId, true);
|
|
192
|
+
}
|
|
193
|
+
this.clearPreloadSlot();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public clearPreloadSlot(): void {
|
|
197
|
+
if (this.preloadSlot.resource) {
|
|
198
|
+
try {
|
|
199
|
+
const stream = this.preloadSlot.resource.playStream;
|
|
200
|
+
if (stream && typeof stream.destroy === "function" && !stream.destroyed) {
|
|
201
|
+
stream.destroy();
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
// ignore
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (this.preloadSlot.streamId) {
|
|
209
|
+
this.streamManager.unregisterStream(this.preloadSlot.streamId, true);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.preloadSlot.resource = null;
|
|
213
|
+
this.preloadSlot.track = null;
|
|
214
|
+
this.preloadSlot.streamId = null;
|
|
215
|
+
this.preloadSlot.abortController = null;
|
|
216
|
+
this.preloadSlot.isValid = false;
|
|
217
|
+
this.preloadSlot.isLoading = false;
|
|
218
|
+
this.preloadSlot.loadPromise = null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async executePreload(track: Track, abortController: AbortController): Promise<void> {
|
|
222
|
+
if (this.isDestroyed()) throw new Error("PLAYER_DESTROYED");
|
|
223
|
+
this.debugLog(`[Preload] Starting preload for: ${track.title}`);
|
|
224
|
+
|
|
225
|
+
if (abortController.signal.aborted) {
|
|
226
|
+
throw new Error("PRELOAD_CANCELLED");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!this.trackMatches(this.getNextTrack(), track)) {
|
|
230
|
+
this.debugLog(`[Preload] Track changed, cancelling`);
|
|
231
|
+
throw new Error("PRELOAD_CANCELLED");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const streamInfo = await this.getStreamWithCancel(track, abortController.signal);
|
|
235
|
+
if (abortController.signal.aborted) {
|
|
236
|
+
throw new Error("PRELOAD_CANCELLED");
|
|
237
|
+
}
|
|
238
|
+
if (!this.trackMatches(this.getNextTrack(), track)) {
|
|
239
|
+
this.debugLog(`[Preload] Track changed after stream fetch`);
|
|
240
|
+
throw new Error("PRELOAD_CANCELLED");
|
|
241
|
+
}
|
|
242
|
+
if (!streamInfo?.stream) {
|
|
243
|
+
throw new Error(`No stream available`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const streamId = this.streamManager.registerStream(streamInfo.stream, track, {
|
|
247
|
+
source: track.source || "preload",
|
|
248
|
+
isPreload: true,
|
|
249
|
+
priority: 5,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const resource = createAudioResource(streamInfo.stream, {
|
|
253
|
+
inlineVolume: true,
|
|
254
|
+
metadata: { ...track, preloaded: true },
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (!resource.playStream || resource.playStream.readable === false) {
|
|
258
|
+
throw new Error("Resource not readable");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.preloadSlot.resource = resource;
|
|
262
|
+
this.preloadSlot.streamId = streamId;
|
|
263
|
+
this.preloadSlot.isValid = true;
|
|
264
|
+
this.preloadSlot.track = track;
|
|
265
|
+
|
|
266
|
+
this.debugLog(`[Preload] Successfully preloaded: ${track.title} (Stream ID: ${streamId})`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private async getStreamWithCancel(track: Track, signal: AbortSignal): Promise<StreamInfo | null> {
|
|
270
|
+
if (this.isDestroyed()) throw new Error("PLAYER_DESTROYED");
|
|
271
|
+
const abortPromise = new Promise<never>((_, reject) => {
|
|
272
|
+
if (signal.aborted) {
|
|
273
|
+
reject(new Error("PRELOAD_CANCELLED"));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const handler = () => {
|
|
277
|
+
signal.removeEventListener("abort", handler);
|
|
278
|
+
reject(new Error("PRELOAD_CANCELLED"));
|
|
279
|
+
};
|
|
280
|
+
signal.addEventListener("abort", handler);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const existingStream = this.streamManager.getStreamByTrack(track.id || track.title);
|
|
284
|
+
if (existingStream && !existingStream.destroyed && existingStream.readable !== false) {
|
|
285
|
+
this.debugLog(`[Stream] Using existing stream for preload: ${track.title}`);
|
|
286
|
+
return { stream: existingStream, type: "arbitrary" };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const streamPromise = this.getStream(track);
|
|
290
|
+
const result = await Promise.race([streamPromise, abortPromise]);
|
|
291
|
+
return result as StreamInfo | null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -8,6 +8,7 @@ export interface ManagedStream {
|
|
|
8
8
|
track: Track;
|
|
9
9
|
createdAt: number;
|
|
10
10
|
lastAccessed: number;
|
|
11
|
+
playStream?: Readable;
|
|
11
12
|
metadata: {
|
|
12
13
|
source: string;
|
|
13
14
|
isPreload: boolean;
|
|
@@ -40,6 +41,7 @@ export class StreamManager extends EventEmitter {
|
|
|
40
41
|
private suppressPrematureCloseErrors = new Set<string>();
|
|
41
42
|
private options: Required<StreamManagerOptions>;
|
|
42
43
|
private cleanupTimer: NodeJS.Timeout | null = null;
|
|
44
|
+
|
|
43
45
|
private metrics = {
|
|
44
46
|
totalStreamsCreated: 0,
|
|
45
47
|
totalStreamsDestroyed: 0,
|
package/src/types/index.ts
CHANGED
|
@@ -357,6 +357,8 @@ export interface PlayerOptions {
|
|
|
357
357
|
* Merged after {@link PlayerManagerOptions.trackMiddleware} from the manager.
|
|
358
358
|
*/
|
|
359
359
|
trackMiddleware?: TrackMiddleware | TrackMiddleware[];
|
|
360
|
+
|
|
361
|
+
maxStreamStore?: number;
|
|
360
362
|
}
|
|
361
363
|
|
|
362
364
|
export interface PlayerManagerOptions {
|