ziplayer 0.3.11 → 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.
@@ -244,8 +244,7 @@ export class FilterManager {
244
244
  wasRecreated = true;
245
245
 
246
246
  position = -1;
247
- streamInfo.type = "arbitrary";
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`);
@@ -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
- stream.destroy().catch((e: any) => this.debug("Stream destroy error:", e));
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;
@@ -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 = this.streamManager.registerStream(streamInfo.stream, track, {
1137
- source: track.source || "stream",
1138
- isPreload: false,
1139
- isRemote: !!streamInfo?.remote,
1140
- priority: 10,
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 ((this.currentSlot as any).processedStreamId && (this.currentSlot as any).processedStreamId !== playStreamId) {
1163
- this.streamManager.unregisterStream((this.currentSlot as any).processedStreamId, true);
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
- (this.currentSlot as any).processedStreamId = processedStream ? playStreamId : null;
1177
+ this.currentSlot.processedStreamId = processedStream ? playStreamId : null;
1170
1178
  this.currentSlot.isValid = true;
1171
1179
  this.currentResource = resource;
1172
1180
  this.seekOffset = 0;
@@ -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 = (this.currentSlot as any).processedStreamId;
2519
+ const processedStreamId = this.currentSlot.processedStreamId;
2512
2520
  if (processedStreamId && processedStreamId !== currentStreamId) {
2513
2521
  this.streamManager.unregisterStream(processedStreamId, true);
2514
- (this.currentSlot as any).processedStreamId = null;
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 = this.streamManager.registerStream(streaminfo.stream, track, {
2558
- source: track.source || "stream",
2559
- isPreload: false,
2560
- priority: 10,
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
- (this.currentSlot as any).processedStreamId = newProcessedStreamId;
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
- private pluginManager: PluginManager;
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 using PluginManager without creating a Player.
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
- * - cache
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.pluginManager.search(query, requestedBy);
825
+ const result = await this.getSearchPlayer().search(query, requestedBy);
803
826
 
804
- if (!result || !Array.isArray(result.tracks) || result.tracks.length === 0) {
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
- this.pluginManager.unregister(name);
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 = this.streamManager.registerStream(streamInfo.stream, track, {
247
- source: track.source || "preload",
248
- isPreload: true,
249
- priority: 5,
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
  });
@@ -420,6 +420,7 @@ export interface SaveOptions {
420
420
  /** Seek position in milliseconds to start saving from */
421
421
  seek?: number;
422
422
  }
423
+
423
424
  export interface PlayerSession {
424
425
  guildId: string;
425
426
  queue: Track[];
@@ -471,6 +472,7 @@ export interface StreamSlot {
471
472
  resource: AudioResource | null;
472
473
  track: Track | null;
473
474
  streamId: string | null;
475
+ processedStreamId: string | null;
474
476
  abortController: AbortController | null;
475
477
  isValid: boolean;
476
478
  isLoading: boolean;