ziplayer 0.3.10 → 0.3.12
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/extensions/index.d.ts +1 -0
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +9 -1
- package/dist/extensions/index.js.map +1 -1
- package/dist/structures/FilterManager.d.ts +1 -0
- package/dist/structures/FilterManager.d.ts.map +1 -1
- package/dist/structures/FilterManager.js +69 -8
- package/dist/structures/FilterManager.js.map +1 -1
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +26 -16
- package/dist/structures/Player.js.map +1 -1
- package/dist/structures/PlayerManager.d.ts +11 -7
- package/dist/structures/PlayerManager.d.ts.map +1 -1
- package/dist/structures/PlayerManager.js +39 -20
- package/dist/structures/PlayerManager.js.map +1 -1
- package/dist/structures/PreloadManager.d.ts.map +1 -1
- package/dist/structures/PreloadManager.js +10 -7
- package/dist/structures/PreloadManager.js.map +1 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/src/extensions/index.ts +9 -1
- package/src/plugins/index.ts +975 -975
- package/src/structures/FilterManager.ts +81 -10
- package/src/structures/Player.ts +35 -24
- package/src/structures/PlayerManager.ts +46 -21
- package/src/structures/PreloadManager.ts +11 -7
- package/src/types/index.ts +5 -2
|
@@ -16,7 +16,7 @@ export class FilterManager {
|
|
|
16
16
|
private debug: DebugFn;
|
|
17
17
|
private player: Player;
|
|
18
18
|
private ffmpeg: FFmpeg | null = null;
|
|
19
|
-
private currentInputStream: Readable | null = null;
|
|
19
|
+
private currentInputStream: Readable | null | undefined | string = null;
|
|
20
20
|
public StreamType: FilterManagerStreamType = "arbitrary";
|
|
21
21
|
private ffmpegProcess: ChildProcess | null = null;
|
|
22
22
|
private ffmpegAbortController: AbortController | null = null;
|
|
@@ -236,7 +236,7 @@ export class FilterManager {
|
|
|
236
236
|
const generation = ++this.ffmpegGeneration;
|
|
237
237
|
const filterString = this.getFilterString();
|
|
238
238
|
|
|
239
|
-
let sourceStream = streamInfo.stream
|
|
239
|
+
let sourceStream: Readable | string = streamInfo.stream || streamInfo.url!;
|
|
240
240
|
let wasRecreated = false;
|
|
241
241
|
|
|
242
242
|
if (position >= 0 && streamInfo.recreate) {
|
|
@@ -244,8 +244,7 @@ export class FilterManager {
|
|
|
244
244
|
wasRecreated = true;
|
|
245
245
|
|
|
246
246
|
position = -1;
|
|
247
|
-
streamInfo
|
|
248
|
-
if (!filterString) return { ...streamInfo, stream: sourceStream, wasRecreated };
|
|
247
|
+
if (!filterString) return { ...streamInfo, type: "arbitrary", stream: sourceStream, wasRecreated };
|
|
249
248
|
}
|
|
250
249
|
|
|
251
250
|
this.debug(`Applying filters and seek — filters: ${filterString || "none"}, seek: ${position}ms`);
|
|
@@ -264,7 +263,13 @@ export class FilterManager {
|
|
|
264
263
|
return { ...streamInfo, stream };
|
|
265
264
|
}
|
|
266
265
|
|
|
267
|
-
|
|
266
|
+
if (typeof sourceStream === "string") {
|
|
267
|
+
return {
|
|
268
|
+
...streamInfo,
|
|
269
|
+
stream: await this.createFFmpegFromUrl(sourceStream, filterString),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
268
273
|
const args = [
|
|
269
274
|
"-analyzeduration",
|
|
270
275
|
"0",
|
|
@@ -287,7 +292,6 @@ export class FilterManager {
|
|
|
287
292
|
}
|
|
288
293
|
|
|
289
294
|
try {
|
|
290
|
-
// Sử dụng prism.FFmpeg cho trường hợp không seek
|
|
291
295
|
this.ffmpeg = sourceStream.pipe(new prism.FFmpeg({ args }));
|
|
292
296
|
return { ...streamInfo, stream: this.ffmpeg };
|
|
293
297
|
} catch (spawnError) {
|
|
@@ -297,7 +301,7 @@ export class FilterManager {
|
|
|
297
301
|
}
|
|
298
302
|
|
|
299
303
|
private spawnFFmpegInputSeek(
|
|
300
|
-
stream: Readable,
|
|
304
|
+
stream: Readable | string,
|
|
301
305
|
position: number,
|
|
302
306
|
filterString: string,
|
|
303
307
|
signal: AbortSignal,
|
|
@@ -308,7 +312,11 @@ export class FilterManager {
|
|
|
308
312
|
|
|
309
313
|
// Chuyển sang dùng s16le (Raw PCM) để Discord.js dễ xử lý nhất khi có filter
|
|
310
314
|
// NOTE: -ss MUST come BEFORE -i for proper seeking and timing
|
|
311
|
-
|
|
315
|
+
//if url is provided, we can let ffmpeg handle it directly without piping, which is more efficient and less error-prone
|
|
316
|
+
const args: string[] =
|
|
317
|
+
typeof stream === "string" ?
|
|
318
|
+
["-ss", seekSeconds, "-i", stream, "-analyzeduration", "0", "-loglevel", "0"]
|
|
319
|
+
: ["-ss", seekSeconds, "-i", "pipe:0", "-analyzeduration", "0", "-loglevel", "0"];
|
|
312
320
|
|
|
313
321
|
if (filterString) {
|
|
314
322
|
args.push("-af", filterString);
|
|
@@ -337,7 +345,11 @@ export class FilterManager {
|
|
|
337
345
|
signal.removeEventListener("abort", onAbort);
|
|
338
346
|
|
|
339
347
|
try {
|
|
340
|
-
stream
|
|
348
|
+
if (typeof stream === "string") {
|
|
349
|
+
// If stream is a URL, no need to unpipe
|
|
350
|
+
} else {
|
|
351
|
+
stream.unpipe(proc.stdin!);
|
|
352
|
+
}
|
|
341
353
|
} catch {}
|
|
342
354
|
|
|
343
355
|
try {
|
|
@@ -361,7 +373,11 @@ export class FilterManager {
|
|
|
361
373
|
}
|
|
362
374
|
|
|
363
375
|
// Pipe source → ffmpeg stdin
|
|
364
|
-
stream
|
|
376
|
+
if (typeof stream === "string") {
|
|
377
|
+
// If stream is a URL, no need to pipe
|
|
378
|
+
} else {
|
|
379
|
+
stream.pipe(proc.stdin!);
|
|
380
|
+
}
|
|
365
381
|
|
|
366
382
|
// Suppress EPIPE on stdin when the process exits early
|
|
367
383
|
proc.stdin!.on("error", (err: Error) => {
|
|
@@ -409,4 +425,59 @@ export class FilterManager {
|
|
|
409
425
|
|
|
410
426
|
return proc.stdout as Readable;
|
|
411
427
|
}
|
|
428
|
+
private createFFmpegFromUrl(url: string, filterString: string): Readable {
|
|
429
|
+
const args = ["-analyzeduration", "0", "-loglevel", "0", "-i", url];
|
|
430
|
+
|
|
431
|
+
if (filterString) {
|
|
432
|
+
args.push("-af", filterString);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
args.push("-acodec", "libopus", "-f", "opus", "-ar", "48000", "-ac", "2", "pipe:1");
|
|
436
|
+
|
|
437
|
+
const proc = spawn(ffmpegPath!, args, {
|
|
438
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const oldProcess = this.ffmpegProcess;
|
|
442
|
+
this.ffmpegProcess = proc;
|
|
443
|
+
|
|
444
|
+
proc.on("close", (code) => {
|
|
445
|
+
this.debug(`FFmpeg URL process exited (code: ${code})`);
|
|
446
|
+
|
|
447
|
+
if (this.ffmpegProcess === proc) {
|
|
448
|
+
this.ffmpegProcess = null;
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
proc.on("error", (err) => {
|
|
453
|
+
this.debug(`FFmpeg URL process error: ${err.message}`);
|
|
454
|
+
|
|
455
|
+
if (this.ffmpegProcess === proc) {
|
|
456
|
+
this.ffmpegProcess = null;
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// kill process cũ sau khi process mới đã sẵn sàng
|
|
461
|
+
if (oldProcess && oldProcess !== proc) {
|
|
462
|
+
try {
|
|
463
|
+
oldProcess.kill("SIGKILL");
|
|
464
|
+
} catch {}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const output = proc.stdout as Readable;
|
|
468
|
+
|
|
469
|
+
output.once("close", () => {
|
|
470
|
+
try {
|
|
471
|
+
proc.kill("SIGKILL");
|
|
472
|
+
} catch {}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
output.once("end", () => {
|
|
476
|
+
try {
|
|
477
|
+
proc.kill("SIGKILL");
|
|
478
|
+
} catch {}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
return output;
|
|
482
|
+
}
|
|
412
483
|
}
|
package/src/structures/Player.ts
CHANGED
|
@@ -125,6 +125,7 @@ export class Player extends EventEmitter {
|
|
|
125
125
|
resource: null,
|
|
126
126
|
track: null,
|
|
127
127
|
streamId: null,
|
|
128
|
+
processedStreamId: null,
|
|
128
129
|
abortController: null,
|
|
129
130
|
isValid: false,
|
|
130
131
|
isLoading: false,
|
|
@@ -331,7 +332,11 @@ export class Player extends EventEmitter {
|
|
|
331
332
|
const stream = (this.currentResource as any)?.metadata?.stream ?? (this.currentResource as any)?.stream;
|
|
332
333
|
|
|
333
334
|
if (stream && typeof stream.destroy === "function") {
|
|
334
|
-
|
|
335
|
+
try {
|
|
336
|
+
stream.destroy();
|
|
337
|
+
} catch (e: any) {
|
|
338
|
+
this.debug("Stream destroy error:", e);
|
|
339
|
+
}
|
|
335
340
|
}
|
|
336
341
|
|
|
337
342
|
this.currentResource = null;
|
|
@@ -907,7 +912,7 @@ export class Player extends EventEmitter {
|
|
|
907
912
|
if (filterString || position > 0) {
|
|
908
913
|
const processedStream = await this.filter.applyFiltersAndSeek(streamInfo, seekArg);
|
|
909
914
|
|
|
910
|
-
const resource = createAudioResource(processedStream.stream
|
|
915
|
+
const resource = createAudioResource(processedStream.stream || processedStream.url!, {
|
|
911
916
|
metadata: track,
|
|
912
917
|
inputType:
|
|
913
918
|
processedStream.wasRecreated && !filterString ? StreamType.Arbitrary
|
|
@@ -916,10 +921,10 @@ export class Player extends EventEmitter {
|
|
|
916
921
|
inlineVolume: true,
|
|
917
922
|
});
|
|
918
923
|
|
|
919
|
-
return { resource, processedStream: processedStream.stream };
|
|
924
|
+
return { resource, processedStream: processedStream.stream! };
|
|
920
925
|
}
|
|
921
926
|
|
|
922
|
-
const resource = createAudioResource(streamInfo.stream
|
|
927
|
+
const resource = createAudioResource(streamInfo.stream || streamInfo.url!, {
|
|
923
928
|
metadata: track,
|
|
924
929
|
inputType:
|
|
925
930
|
streamInfo.type === "webm/opus" ? StreamType.WebmOpus
|
|
@@ -1128,17 +1133,20 @@ export class Player extends EventEmitter {
|
|
|
1128
1133
|
return await this.playRemote(track, streamInfo);
|
|
1129
1134
|
}
|
|
1130
1135
|
|
|
1131
|
-
if (!streamInfo?.stream) {
|
|
1136
|
+
if (!streamInfo?.stream && !streamInfo?.url) {
|
|
1132
1137
|
throw new Error(`No stream available`);
|
|
1133
1138
|
}
|
|
1134
1139
|
|
|
1135
1140
|
// Register the RAW source stream — this is what we can reuse on seek
|
|
1136
|
-
const rawStreamId =
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1141
|
+
const rawStreamId =
|
|
1142
|
+
streamInfo.stream ?
|
|
1143
|
+
this.streamManager.registerStream(streamInfo.stream, track, {
|
|
1144
|
+
source: track.source || "stream",
|
|
1145
|
+
isPreload: false,
|
|
1146
|
+
isRemote: !!streamInfo?.remote,
|
|
1147
|
+
priority: 10,
|
|
1148
|
+
})
|
|
1149
|
+
: null;
|
|
1142
1150
|
|
|
1143
1151
|
// createResource now returns both the AudioResource
|
|
1144
1152
|
// AND the processedStream (ffmpeg stdout) when filters/seek are involved.
|
|
@@ -1159,14 +1167,14 @@ export class Player extends EventEmitter {
|
|
|
1159
1167
|
if (this.currentSlot.streamId && this.currentSlot.streamId !== rawStreamId) {
|
|
1160
1168
|
this.streamManager.unregisterStream(this.currentSlot.streamId, true);
|
|
1161
1169
|
}
|
|
1162
|
-
if (
|
|
1163
|
-
this.streamManager.unregisterStream(
|
|
1170
|
+
if (this.currentSlot.processedStreamId && this.currentSlot.processedStreamId !== playStreamId) {
|
|
1171
|
+
this.streamManager.unregisterStream(this.currentSlot.processedStreamId, true);
|
|
1164
1172
|
}
|
|
1165
1173
|
|
|
1166
1174
|
this.currentSlot.resource = resource;
|
|
1167
1175
|
this.currentSlot.track = track;
|
|
1168
1176
|
this.currentSlot.streamId = rawStreamId;
|
|
1169
|
-
|
|
1177
|
+
this.currentSlot.processedStreamId = processedStream ? playStreamId : null;
|
|
1170
1178
|
this.currentSlot.isValid = true;
|
|
1171
1179
|
this.currentResource = resource;
|
|
1172
1180
|
this.seekOffset = 0;
|
|
@@ -1980,7 +1988,7 @@ export class Player extends EventEmitter {
|
|
|
1980
1988
|
}
|
|
1981
1989
|
|
|
1982
1990
|
// Return the stream directly - caller can pipe it to fs.createWriteStream()
|
|
1983
|
-
return finalStream.stream
|
|
1991
|
+
return finalStream.stream!;
|
|
1984
1992
|
} catch (error) {
|
|
1985
1993
|
this.debug(`[Player] save error:`, error);
|
|
1986
1994
|
this.emit("playerError", error as Error, track);
|
|
@@ -2508,10 +2516,10 @@ export class Player extends EventEmitter {
|
|
|
2508
2516
|
}
|
|
2509
2517
|
|
|
2510
2518
|
// Clean up processedStream first (it's what AudioResource reads)
|
|
2511
|
-
const processedStreamId =
|
|
2519
|
+
const processedStreamId = this.currentSlot.processedStreamId;
|
|
2512
2520
|
if (processedStreamId && processedStreamId !== currentStreamId) {
|
|
2513
2521
|
this.streamManager.unregisterStream(processedStreamId, true);
|
|
2514
|
-
|
|
2522
|
+
this.currentSlot.processedStreamId = null;
|
|
2515
2523
|
}
|
|
2516
2524
|
|
|
2517
2525
|
if (currentStreamId) {
|
|
@@ -2544,7 +2552,7 @@ export class Player extends EventEmitter {
|
|
|
2544
2552
|
streaminfo = await this.getStream(track);
|
|
2545
2553
|
}
|
|
2546
2554
|
|
|
2547
|
-
if (!streaminfo?.stream) {
|
|
2555
|
+
if (!streaminfo?.stream && !streaminfo?.url) {
|
|
2548
2556
|
this.debug(`[Player] No stream available for refresh`);
|
|
2549
2557
|
return false;
|
|
2550
2558
|
}
|
|
@@ -2554,11 +2562,14 @@ export class Player extends EventEmitter {
|
|
|
2554
2562
|
const { resource, processedStream } = await this.createResource(streaminfo, track, createPosition);
|
|
2555
2563
|
|
|
2556
2564
|
// Register raw source stream
|
|
2557
|
-
const newStreamId =
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2565
|
+
const newStreamId =
|
|
2566
|
+
streaminfo.stream ?
|
|
2567
|
+
this.streamManager.registerStream(streaminfo.stream, track, {
|
|
2568
|
+
source: track.source || "stream",
|
|
2569
|
+
isPreload: false,
|
|
2570
|
+
priority: 10,
|
|
2571
|
+
})
|
|
2572
|
+
: null;
|
|
2562
2573
|
|
|
2563
2574
|
let newProcessedStreamId: string | null = null;
|
|
2564
2575
|
if (processedStream && processedStream !== streaminfo.stream) {
|
|
@@ -2572,7 +2583,7 @@ export class Player extends EventEmitter {
|
|
|
2572
2583
|
this.currentSlot.resource = resource;
|
|
2573
2584
|
this.currentSlot.track = track;
|
|
2574
2585
|
this.currentSlot.streamId = newStreamId;
|
|
2575
|
-
|
|
2586
|
+
this.currentSlot.processedStreamId = newProcessedStreamId;
|
|
2576
2587
|
this.currentSlot.isValid = true;
|
|
2577
2588
|
this.currentResource = resource;
|
|
2578
2589
|
|
|
@@ -15,9 +15,10 @@ import {
|
|
|
15
15
|
} from "../types";
|
|
16
16
|
import type { BaseExtension } from "../extensions";
|
|
17
17
|
import { withTimeout } from "../utils/timeout";
|
|
18
|
-
import { PluginManager } from "../plugins";
|
|
19
18
|
|
|
20
19
|
const GLOBAL_MANAGER_KEY: symbol = Symbol.for("ziplayer.PlayerManager.instance");
|
|
20
|
+
/** Guild id for the internal search-only player (never stored in {@link PlayerManager.players}). */
|
|
21
|
+
const SEARCH_PLAYER_GUILD_ID = "__ziplayer_search__";
|
|
21
22
|
|
|
22
23
|
export const getGlobalManager = (): PlayerManager | null => {
|
|
23
24
|
try {
|
|
@@ -105,7 +106,8 @@ export class PlayerManager extends EventEmitter {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
private plugins: SourcePlugin[];
|
|
108
|
-
|
|
109
|
+
/** Reused player for {@link search}; not registered in {@link players}. */
|
|
110
|
+
private searchPlayer: Player | null = null;
|
|
109
111
|
private extensions: any[];
|
|
110
112
|
private B_debug: boolean = false;
|
|
111
113
|
private extractorTimeout: number = 10000;
|
|
@@ -126,9 +128,6 @@ export class PlayerManager extends EventEmitter {
|
|
|
126
128
|
constructor(options: PlayerManagerOptions = {}) {
|
|
127
129
|
super();
|
|
128
130
|
this.plugins = [];
|
|
129
|
-
this.pluginManager = new PluginManager(null as any, this, {
|
|
130
|
-
extractorTimeout: this.extractorTimeout,
|
|
131
|
-
});
|
|
132
131
|
this.searchCache = new Map();
|
|
133
132
|
|
|
134
133
|
// Initialize plugins
|
|
@@ -145,7 +144,6 @@ export class PlayerManager extends EventEmitter {
|
|
|
145
144
|
|
|
146
145
|
if (instance) {
|
|
147
146
|
this.plugins.push(instance);
|
|
148
|
-
this.pluginManager.register(instance);
|
|
149
147
|
}
|
|
150
148
|
this.debug(`Registered plugin: ${p.name || "unnamed"}`);
|
|
151
149
|
} catch (e) {
|
|
@@ -249,6 +247,25 @@ export class PlayerManager extends EventEmitter {
|
|
|
249
247
|
this.debug(`Auto-cleanup started with interval: ${this.cleanupTimeout}ms`);
|
|
250
248
|
}
|
|
251
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Lazy internal player used only for {@link search}.
|
|
252
|
+
* Not added to {@link players} and does not forward manager events.
|
|
253
|
+
*/
|
|
254
|
+
private getSearchPlayer(): Player {
|
|
255
|
+
if (this.searchPlayer && !this.searchPlayer.destroyed) {
|
|
256
|
+
return this.searchPlayer;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const player = new Player(SEARCH_PLAYER_GUILD_ID, { extractorTimeout: this.extractorTimeout }, this);
|
|
260
|
+
for (const plugin of this.plugins) {
|
|
261
|
+
player.addPlugin(plugin);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.searchPlayer = player;
|
|
265
|
+
this.debug(`Created internal search player (not stored in players map)`);
|
|
266
|
+
return player;
|
|
267
|
+
}
|
|
268
|
+
|
|
252
269
|
private startStatsCollection(): void {
|
|
253
270
|
if (this.statsInterval) {
|
|
254
271
|
clearInterval(this.statsInterval);
|
|
@@ -291,6 +308,10 @@ export class PlayerManager extends EventEmitter {
|
|
|
291
308
|
async create(guildOrId: string | { id: string }, options?: PlayerOptions): Promise<Player> {
|
|
292
309
|
const guildId = this.resolveGuildId(guildOrId);
|
|
293
310
|
|
|
311
|
+
if (guildId === SEARCH_PLAYER_GUILD_ID) {
|
|
312
|
+
throw new Error(`Guild id "${SEARCH_PLAYER_GUILD_ID}" is reserved for internal search.`);
|
|
313
|
+
}
|
|
314
|
+
|
|
294
315
|
if (this.players.has(guildId)) {
|
|
295
316
|
this.debug(`Player already exists for guildId: ${guildId}, returning existing`);
|
|
296
317
|
return this.players.get(guildId)!;
|
|
@@ -770,6 +791,11 @@ export class PlayerManager extends EventEmitter {
|
|
|
770
791
|
player.destroy();
|
|
771
792
|
}
|
|
772
793
|
|
|
794
|
+
if (this.searchPlayer && !this.searchPlayer.destroyed) {
|
|
795
|
+
this.searchPlayer.destroy();
|
|
796
|
+
}
|
|
797
|
+
this.searchPlayer = null;
|
|
798
|
+
|
|
773
799
|
this.players.clear();
|
|
774
800
|
this.searchCache.clear();
|
|
775
801
|
this.removeAllListeners();
|
|
@@ -777,13 +803,11 @@ export class PlayerManager extends EventEmitter {
|
|
|
777
803
|
}
|
|
778
804
|
|
|
779
805
|
/**
|
|
780
|
-
* Search
|
|
806
|
+
* Search via an internal Player instance (all registered plugins) without
|
|
807
|
+
* storing it in {@link players}.
|
|
781
808
|
*
|
|
782
|
-
* Uses the same search pipeline as Player.search
|
|
783
|
-
*
|
|
784
|
-
* - plugin deduplication
|
|
785
|
-
* - plugin scoring/evaluation
|
|
786
|
-
* - fallback handling
|
|
809
|
+
* Uses the same search pipeline as {@link Player.search}:
|
|
810
|
+
* extension hooks, plugin deduplication, scoring, and fallback handling.
|
|
787
811
|
*
|
|
788
812
|
* @param {string} query
|
|
789
813
|
* @param {string} requestedBy
|
|
@@ -792,20 +816,15 @@ export class PlayerManager extends EventEmitter {
|
|
|
792
816
|
async search(query: string, requestedBy: string): Promise<SearchResult> {
|
|
793
817
|
this.debug(`Search called with query: ${query}, requestedBy: ${requestedBy}`);
|
|
794
818
|
|
|
795
|
-
// Cache
|
|
796
819
|
const cached = this.getCachedSearch(query);
|
|
797
820
|
if (cached) {
|
|
798
821
|
return cached;
|
|
799
822
|
}
|
|
800
823
|
|
|
801
824
|
try {
|
|
802
|
-
const result = await this.
|
|
825
|
+
const result = await this.getSearchPlayer().search(query, requestedBy);
|
|
803
826
|
|
|
804
|
-
|
|
805
|
-
throw new Error(`No results found for: ${query}`);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
this.debug(`Plugin search returned ${result.tracks.length} tracks (score: ${result.score?.score ?? "unknown"}%)`);
|
|
827
|
+
this.debug(`Search returned ${result.tracks.length} tracks (score: ${result.score?.score ?? "unknown"}%)`);
|
|
809
828
|
|
|
810
829
|
if (result.score) {
|
|
811
830
|
this.debug(`Search evaluation - ${result.score.reason}`);
|
|
@@ -836,10 +855,13 @@ export class PlayerManager extends EventEmitter {
|
|
|
836
855
|
*/
|
|
837
856
|
registerPlugin(plugin: SourcePlugin): void {
|
|
838
857
|
this.plugins.push(plugin);
|
|
839
|
-
this.pluginManager.register(plugin);
|
|
840
858
|
|
|
841
859
|
this.debug(`Registered plugin: ${plugin.name}`);
|
|
842
860
|
|
|
861
|
+
if (this.searchPlayer && !this.searchPlayer.destroyed) {
|
|
862
|
+
this.searchPlayer.addPlugin(plugin);
|
|
863
|
+
}
|
|
864
|
+
|
|
843
865
|
for (const player of this.players.values()) {
|
|
844
866
|
player.addPlugin(plugin);
|
|
845
867
|
}
|
|
@@ -856,7 +878,10 @@ export class PlayerManager extends EventEmitter {
|
|
|
856
878
|
if (index === -1) return false;
|
|
857
879
|
|
|
858
880
|
this.plugins.splice(index, 1);
|
|
859
|
-
|
|
881
|
+
|
|
882
|
+
if (this.searchPlayer && !this.searchPlayer.destroyed) {
|
|
883
|
+
this.searchPlayer.removePlugin(name);
|
|
884
|
+
}
|
|
860
885
|
|
|
861
886
|
this.debug(`Unregistered plugin: ${name}`);
|
|
862
887
|
|
|
@@ -24,6 +24,7 @@ export class PreloadManager {
|
|
|
24
24
|
resource: null,
|
|
25
25
|
track: null,
|
|
26
26
|
streamId: null,
|
|
27
|
+
processedStreamId: null,
|
|
27
28
|
abortController: null,
|
|
28
29
|
isValid: false,
|
|
29
30
|
isLoading: false,
|
|
@@ -239,17 +240,20 @@ export class PreloadManager {
|
|
|
239
240
|
this.debugLog(`[Preload] Track changed after stream fetch`);
|
|
240
241
|
throw new Error("PRELOAD_CANCELLED");
|
|
241
242
|
}
|
|
242
|
-
if (!streamInfo?.stream) {
|
|
243
|
+
if (!streamInfo?.stream && !streamInfo?.url) {
|
|
243
244
|
throw new Error(`No stream available`);
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
const streamId =
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
const streamId =
|
|
248
|
+
streamInfo.stream ?
|
|
249
|
+
this.streamManager.registerStream(streamInfo.stream, track, {
|
|
250
|
+
source: track.source || "preload",
|
|
251
|
+
isPreload: true,
|
|
252
|
+
priority: 5,
|
|
253
|
+
})
|
|
254
|
+
: null;
|
|
251
255
|
|
|
252
|
-
const resource = createAudioResource(streamInfo.stream
|
|
256
|
+
const resource = createAudioResource(streamInfo.stream || streamInfo.url!, {
|
|
253
257
|
inlineVolume: true,
|
|
254
258
|
metadata: { ...track, preloaded: true },
|
|
255
259
|
});
|
package/src/types/index.ts
CHANGED
|
@@ -124,8 +124,9 @@ export interface SearchScore {
|
|
|
124
124
|
* };
|
|
125
125
|
*/
|
|
126
126
|
export interface StreamInfo {
|
|
127
|
-
stream
|
|
128
|
-
|
|
127
|
+
stream?: Readable;
|
|
128
|
+
url?: string;
|
|
129
|
+
type: "webm/opus" | "ogg/opus" | "arbitrary" | "url" | string;
|
|
129
130
|
metadata?: Record<string, any>;
|
|
130
131
|
position?: number;
|
|
131
132
|
recreate?: (position: number) => Promise<Readable>;
|
|
@@ -419,6 +420,7 @@ export interface SaveOptions {
|
|
|
419
420
|
/** Seek position in milliseconds to start saving from */
|
|
420
421
|
seek?: number;
|
|
421
422
|
}
|
|
423
|
+
|
|
422
424
|
export interface PlayerSession {
|
|
423
425
|
guildId: string;
|
|
424
426
|
queue: Track[];
|
|
@@ -470,6 +472,7 @@ export interface StreamSlot {
|
|
|
470
472
|
resource: AudioResource | null;
|
|
471
473
|
track: Track | null;
|
|
472
474
|
streamId: string | null;
|
|
475
|
+
processedStreamId: string | null;
|
|
473
476
|
abortController: AbortController | null;
|
|
474
477
|
isValid: boolean;
|
|
475
478
|
isLoading: boolean;
|