@kattowo/distube 6.2.1 → 6.2.2

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/index.d.mts CHANGED
@@ -343,7 +343,49 @@ type CrossfadeOptions = {
343
343
  * @default 5
344
344
  */
345
345
  warmup: number;
346
+ /**
347
+ * Prepare the fading-out song's playback input for the crossfade FFmpeg
348
+ * process. It is invoked right before the transition stream is spawned,
349
+ * with the song's stream url and the position (in seconds, counted from the
350
+ * beginning of the song) where the fade window begins.
351
+ *
352
+ * Streaming inputs that cannot fast-seek (for example HLS playlists) force
353
+ * FFmpeg to re-download the whole stream and discard everything before the
354
+ * seek position, which can miss the swap window on slow sources. The
355
+ * adapter can return a replacement input that starts near the boundary —
356
+ * a local playlist trimmed to the fade position, for example — along with
357
+ * the seek offset inside it and its own FFmpeg input options. Return `null`
358
+ * or `undefined` to use the url as-is.
359
+ */
360
+ preparePrimaryInput?: PrepareCrossfadeInput;
346
361
  };
362
+ /**
363
+ * The replacement input returned by {@link CrossfadeOptions.preparePrimaryInput}.
364
+ */
365
+ type PreparedCrossfadeInput = {
366
+ /**
367
+ * The replacement input url or path, for example a local playlist file
368
+ * trimmed to start at the fade boundary.
369
+ */
370
+ url: string;
371
+ /**
372
+ * The initial seek of the replacement input, in seconds relative to it.
373
+ * When omitted, the input is assumed to start exactly at the fade boundary.
374
+ */
375
+ seek?: number;
376
+ /**
377
+ * FFmpeg input options of the replacement input. They replace the default
378
+ * input options, which are tuned for http(s) streaming urls.
379
+ */
380
+ inputArgs?: FFmpegArg;
381
+ };
382
+ /**
383
+ * The {@link CrossfadeOptions.preparePrimaryInput} adapter type.
384
+ */
385
+ type PrepareCrossfadeInput = (url: string, boundary: {
386
+ seek: number;
387
+ song: Song;
388
+ }) => Awaitable<PreparedCrossfadeInput | null | undefined>;
347
389
  /**
348
390
  * DisTube options
349
391
  */
@@ -447,6 +489,9 @@ type DisTubeOptions = {
447
489
  * - `curve`: the FFmpeg `acrossfade` fade curve (@default "qsin")
448
490
  * - `warmup`: seconds before the fade window when the transition stream is
449
491
  * spawned to warm up its buffers (@default 5)
492
+ * - `preparePrimaryInput`: an optional adapter preparing the fading-out
493
+ * song's FFmpeg input before the transition stream is spawned. Useful for
494
+ * streaming inputs that cannot fast-seek ({@link CrossfadeOptions.preparePrimaryInput}).
450
495
  * @default { duration: 0, curve: "qsin", warmup: 5 }
451
496
  */
452
497
  crossfade?: {
@@ -462,6 +507,11 @@ type DisTubeOptions = {
462
507
  * Seconds before the fade window when the transition stream is spawned.
463
508
  */
464
509
  warmup?: number;
510
+ /**
511
+ * Optional adapter preparing the fading-out song's FFmpeg input
512
+ * ({@link CrossfadeOptions.preparePrimaryInput}).
513
+ */
514
+ preparePrimaryInput?: PrepareCrossfadeInput;
465
515
  };
466
516
  /**
467
517
  * FFmpeg options
@@ -1408,6 +1458,14 @@ interface CrossfadeStreamOptions {
1408
1458
  * @default "qsin"
1409
1459
  */
1410
1460
  inCurve?: string;
1461
+ /**
1462
+ * FFmpeg input options of the first (fading out) input. When set, they
1463
+ * replace the default input options, which are tuned for http(s) streaming
1464
+ * urls. Use it for inputs that need different flags — a local playlist
1465
+ * file, for example, requires the `protocol_whitelist` option and no
1466
+ * reconnect flags.
1467
+ */
1468
+ primaryInputArgs?: FFmpegArg;
1411
1469
  }
1412
1470
  /**
1413
1471
  * Check that FFmpeg is installed and can be spawned.
@@ -3623,4 +3681,4 @@ declare function handleUnhandledError(error: Error, behavior?: UnhandledErrorBeh
3623
3681
  declare const checkEncryptionLibraries: () => Promise<boolean>;
3624
3682
 
3625
3683
  export { AUDIO_CHANNELS, AUDIO_SAMPLE_RATE, BaseManager, CROSSFADE_DRIFT_TOLERANCE_SECS, DEFAULT_CROSSFADE_CURVE, DEFAULT_CROSSFADE_DURATION_SECS, DEFAULT_CROSSFADE_WARMUP_SECS, DEFAULT_PRELOAD_THRESHOLD_SECS, DEFAULT_VOLUME, DisTube, DisTubeBase, DisTubeError, DisTubeHandler, DisTubeStream, DisTubeVoice, DisTubeVoiceManager, Events, ExtractorPlugin, FilterManager, GuildIdManager, HTTP_REDIRECT_CODES, InfoExtractorPlugin, JOIN_TIMEOUT_MS, MAX_CROSSFADE_DURATION_SECS, MAX_REDIRECT_DEPTH, Options, PlayableExtractorPlugin, Playlist, Plugin, PluginError, PluginErrorCode, PluginType, Queue, QueueManager, RECONNECT_MAX_ATTEMPTS, RECONNECT_TIMEOUT_MS, RepeatMode, STUCK_THRESHOLD_MS, SearchResult, SearchResultType, Song, StreamServer, TaskQueue, checkEncryptionLibraries, checkFFmpeg, checkIntents, checkInvalidKey, DisTube as default, defaultFilters, defaultOptions, formatDuration, handleUnhandledError, isClientInstance, isGuildInstance, isMemberInstance, isMessageInstance, isNsfwChannel, isObject, isSnowflake, isSupportedVoiceChannel, isTextChannelInstance, isTruthy, isURL, isVoiceChannelEmpty, objectKeys, resolveGuildId, version };
3626
- export type { Awaitable, CrossfadeOptions, CrossfadeStreamOptions, CustomPlaylistOptions, DisTubeEvents, DisTubeOptions, DisTubePlugin, DisTubeVoiceEvents, Disposable, FFmpegArg, FFmpegArgs, FFmpegOptions, Falsy, Filter, FilterResolvable, Filters, GuildIdResolvable, JumpOptions, KeyOf, PlayHandlerOptions, PlayOptions, PlaylistInfo, PreloadOptions, RelatedSong, ResolveOptions, ResolvePlaylistOptions, SearchOptions, SongInfo, StreamHandler, StreamOptions, StreamRequest, StreamResponse, StreamServerOptions, TypedDisTubeEvents, UnhandledErrorBehavior };
3684
+ export type { Awaitable, CrossfadeOptions, CrossfadeStreamOptions, CustomPlaylistOptions, DisTubeEvents, DisTubeOptions, DisTubePlugin, DisTubeVoiceEvents, Disposable, FFmpegArg, FFmpegArgs, FFmpegOptions, Falsy, Filter, FilterResolvable, Filters, GuildIdResolvable, JumpOptions, KeyOf, PlayHandlerOptions, PlayOptions, PlaylistInfo, PreloadOptions, PrepareCrossfadeInput, PreparedCrossfadeInput, RelatedSong, ResolveOptions, ResolvePlaylistOptions, SearchOptions, SongInfo, StreamHandler, StreamOptions, StreamRequest, StreamResponse, StreamServerOptions, TypedDisTubeEvents, UnhandledErrorBehavior };
package/dist/index.d.ts CHANGED
@@ -343,7 +343,49 @@ type CrossfadeOptions = {
343
343
  * @default 5
344
344
  */
345
345
  warmup: number;
346
+ /**
347
+ * Prepare the fading-out song's playback input for the crossfade FFmpeg
348
+ * process. It is invoked right before the transition stream is spawned,
349
+ * with the song's stream url and the position (in seconds, counted from the
350
+ * beginning of the song) where the fade window begins.
351
+ *
352
+ * Streaming inputs that cannot fast-seek (for example HLS playlists) force
353
+ * FFmpeg to re-download the whole stream and discard everything before the
354
+ * seek position, which can miss the swap window on slow sources. The
355
+ * adapter can return a replacement input that starts near the boundary —
356
+ * a local playlist trimmed to the fade position, for example — along with
357
+ * the seek offset inside it and its own FFmpeg input options. Return `null`
358
+ * or `undefined` to use the url as-is.
359
+ */
360
+ preparePrimaryInput?: PrepareCrossfadeInput;
346
361
  };
362
+ /**
363
+ * The replacement input returned by {@link CrossfadeOptions.preparePrimaryInput}.
364
+ */
365
+ type PreparedCrossfadeInput = {
366
+ /**
367
+ * The replacement input url or path, for example a local playlist file
368
+ * trimmed to start at the fade boundary.
369
+ */
370
+ url: string;
371
+ /**
372
+ * The initial seek of the replacement input, in seconds relative to it.
373
+ * When omitted, the input is assumed to start exactly at the fade boundary.
374
+ */
375
+ seek?: number;
376
+ /**
377
+ * FFmpeg input options of the replacement input. They replace the default
378
+ * input options, which are tuned for http(s) streaming urls.
379
+ */
380
+ inputArgs?: FFmpegArg;
381
+ };
382
+ /**
383
+ * The {@link CrossfadeOptions.preparePrimaryInput} adapter type.
384
+ */
385
+ type PrepareCrossfadeInput = (url: string, boundary: {
386
+ seek: number;
387
+ song: Song;
388
+ }) => Awaitable<PreparedCrossfadeInput | null | undefined>;
347
389
  /**
348
390
  * DisTube options
349
391
  */
@@ -447,6 +489,9 @@ type DisTubeOptions = {
447
489
  * - `curve`: the FFmpeg `acrossfade` fade curve (@default "qsin")
448
490
  * - `warmup`: seconds before the fade window when the transition stream is
449
491
  * spawned to warm up its buffers (@default 5)
492
+ * - `preparePrimaryInput`: an optional adapter preparing the fading-out
493
+ * song's FFmpeg input before the transition stream is spawned. Useful for
494
+ * streaming inputs that cannot fast-seek ({@link CrossfadeOptions.preparePrimaryInput}).
450
495
  * @default { duration: 0, curve: "qsin", warmup: 5 }
451
496
  */
452
497
  crossfade?: {
@@ -462,6 +507,11 @@ type DisTubeOptions = {
462
507
  * Seconds before the fade window when the transition stream is spawned.
463
508
  */
464
509
  warmup?: number;
510
+ /**
511
+ * Optional adapter preparing the fading-out song's FFmpeg input
512
+ * ({@link CrossfadeOptions.preparePrimaryInput}).
513
+ */
514
+ preparePrimaryInput?: PrepareCrossfadeInput;
465
515
  };
466
516
  /**
467
517
  * FFmpeg options
@@ -1408,6 +1458,14 @@ interface CrossfadeStreamOptions {
1408
1458
  * @default "qsin"
1409
1459
  */
1410
1460
  inCurve?: string;
1461
+ /**
1462
+ * FFmpeg input options of the first (fading out) input. When set, they
1463
+ * replace the default input options, which are tuned for http(s) streaming
1464
+ * urls. Use it for inputs that need different flags — a local playlist
1465
+ * file, for example, requires the `protocol_whitelist` option and no
1466
+ * reconnect flags.
1467
+ */
1468
+ primaryInputArgs?: FFmpegArg;
1411
1469
  }
1412
1470
  /**
1413
1471
  * Check that FFmpeg is installed and can be spawned.
@@ -3625,4 +3683,4 @@ declare const checkEncryptionLibraries: () => Promise<boolean>;
3625
3683
  // @ts-ignore
3626
3684
  export = DisTube;
3627
3685
  export { AUDIO_CHANNELS, AUDIO_SAMPLE_RATE, BaseManager, CROSSFADE_DRIFT_TOLERANCE_SECS, DEFAULT_CROSSFADE_CURVE, DEFAULT_CROSSFADE_DURATION_SECS, DEFAULT_CROSSFADE_WARMUP_SECS, DEFAULT_PRELOAD_THRESHOLD_SECS, DEFAULT_VOLUME, DisTube, DisTubeBase, DisTubeError, DisTubeHandler, DisTubeStream, DisTubeVoice, DisTubeVoiceManager, Events, ExtractorPlugin, FilterManager, GuildIdManager, HTTP_REDIRECT_CODES, InfoExtractorPlugin, JOIN_TIMEOUT_MS, MAX_CROSSFADE_DURATION_SECS, MAX_REDIRECT_DEPTH, Options, PlayableExtractorPlugin, Playlist, Plugin, PluginError, PluginErrorCode, PluginType, Queue, QueueManager, RECONNECT_MAX_ATTEMPTS, RECONNECT_TIMEOUT_MS, RepeatMode, STUCK_THRESHOLD_MS, SearchResult, SearchResultType, Song, StreamServer, TaskQueue, checkEncryptionLibraries, checkFFmpeg, checkIntents, checkInvalidKey, defaultFilters, defaultOptions, formatDuration, handleUnhandledError, isClientInstance, isGuildInstance, isMemberInstance, isMessageInstance, isNsfwChannel, isObject, isSnowflake, isSupportedVoiceChannel, isTextChannelInstance, isTruthy, isURL, isVoiceChannelEmpty, objectKeys, resolveGuildId, version };
3628
- export type { Awaitable, CrossfadeOptions, CrossfadeStreamOptions, CustomPlaylistOptions, DisTubeEvents, DisTubeOptions, DisTubePlugin, DisTubeVoiceEvents, Disposable, FFmpegArg, FFmpegArgs, FFmpegOptions, Falsy, Filter, FilterResolvable, Filters, GuildIdResolvable, JumpOptions, KeyOf, PlayHandlerOptions, PlayOptions, PlaylistInfo, PreloadOptions, RelatedSong, ResolveOptions, ResolvePlaylistOptions, SearchOptions, SongInfo, StreamHandler, StreamOptions, StreamRequest, StreamResponse, StreamServerOptions, TypedDisTubeEvents, UnhandledErrorBehavior };
3686
+ export type { Awaitable, CrossfadeOptions, CrossfadeStreamOptions, CustomPlaylistOptions, DisTubeEvents, DisTubeOptions, DisTubePlugin, DisTubeVoiceEvents, Disposable, FFmpegArg, FFmpegArgs, FFmpegOptions, Falsy, Filter, FilterResolvable, Filters, GuildIdResolvable, JumpOptions, KeyOf, PlayHandlerOptions, PlayOptions, PlaylistInfo, PreloadOptions, PrepareCrossfadeInput, PreparedCrossfadeInput, RelatedSong, ResolveOptions, ResolvePlaylistOptions, SearchOptions, SongInfo, StreamHandler, StreamOptions, StreamRequest, StreamResponse, StreamServerOptions, TypedDisTubeEvents, UnhandledErrorBehavior };
package/dist/index.js CHANGED
@@ -102,7 +102,7 @@ __export(index_exports, {
102
102
  module.exports = __toCommonJS(index_exports);
103
103
 
104
104
  // src/constant.ts
105
- var version = "6.2.1";
105
+ var version = "6.2.2";
106
106
  var AUDIO_SAMPLE_RATE = 48e3;
107
107
  var AUDIO_CHANNELS = 2;
108
108
  var DEFAULT_VOLUME = 50;
@@ -2641,8 +2641,8 @@ var Options = class {
2641
2641
  if (typeof value !== "object" || Array.isArray(value)) {
2642
2642
  throw new DisTubeError("INVALID_TYPE", "object", value, "DisTubeOptions.crossfade");
2643
2643
  }
2644
- checkInvalidKey(value, ["duration", "curve", "warmup"], "DisTubeOptions.crossfade");
2645
- const { duration, curve, warmup } = value;
2644
+ checkInvalidKey(value, ["duration", "curve", "warmup", "preparePrimaryInput"], "DisTubeOptions.crossfade");
2645
+ const { duration, curve, warmup, preparePrimaryInput } = value;
2646
2646
  if (duration !== void 0 && (typeof duration !== "number" || Number.isNaN(duration) || duration < 0)) {
2647
2647
  throw new DisTubeError(
2648
2648
  "INVALID_TYPE",
@@ -2665,10 +2665,19 @@ var Options = class {
2665
2665
  if (warmup !== void 0 && (typeof warmup !== "number" || Number.isNaN(warmup) || warmup < 0)) {
2666
2666
  throw new DisTubeError("INVALID_TYPE", "number >= 0", warmup, "DisTubeOptions.crossfade.warmup");
2667
2667
  }
2668
+ if (preparePrimaryInput !== void 0 && typeof preparePrimaryInput !== "function") {
2669
+ throw new DisTubeError(
2670
+ "INVALID_TYPE",
2671
+ "function",
2672
+ preparePrimaryInput,
2673
+ "DisTubeOptions.crossfade.preparePrimaryInput"
2674
+ );
2675
+ }
2668
2676
  return {
2669
2677
  duration: duration ?? DEFAULT_CROSSFADE_DURATION_SECS,
2670
2678
  curve: curve ?? DEFAULT_CROSSFADE_CURVE,
2671
- warmup: warmup ?? DEFAULT_CROSSFADE_WARMUP_SECS
2679
+ warmup: warmup ?? DEFAULT_CROSSFADE_WARMUP_SECS,
2680
+ preparePrimaryInput
2672
2681
  };
2673
2682
  }
2674
2683
  #validateOptions(options = this) {
@@ -2891,13 +2900,15 @@ var DisTubeStream = class extends import_tiny_typed_emitter2.TypedEmitter {
2891
2900
  const extraFilters = typeof outputArgs.af === "string" ? outputArgs.af : Array.isArray(outputArgs.af) ? outputArgs.af.filter(Boolean).join(",") : void 0;
2892
2901
  delete outputArgs.af;
2893
2902
  const primaryOpts = {
2894
- reconnect: 1,
2895
- reconnect_streamed: 1,
2896
- reconnect_delay_max: 5,
2897
- analyzeduration: 0,
2898
2903
  hide_banner: true,
2899
2904
  ...ffmpeg.args.global,
2900
- ...ffmpeg.args.input,
2905
+ ...crossfade.primaryInputArgs ?? {
2906
+ reconnect: 1,
2907
+ reconnect_streamed: 1,
2908
+ reconnect_delay_max: 5,
2909
+ analyzeduration: 0,
2910
+ ...ffmpeg.args.input
2911
+ },
2901
2912
  ss: typeof primarySeek === "number" && primarySeek > 0 ? primarySeek.toString() : null,
2902
2913
  i: url
2903
2914
  };
@@ -3476,7 +3487,28 @@ var QueueManager = class extends GuildIdManager {
3476
3487
  if (!primary?.stream.playFromSource || !primary.stream.url) return;
3477
3488
  if (!secondary?.stream.playFromSource || !secondary.stream.url) return;
3478
3489
  this.debug(`[${queue.id}] Warming up crossfade stream: ${song} -> ${nextSong}`);
3479
- transitionStream = new DisTubeStream(primary.stream.url, {
3490
+ const boundarySeek = Math.max(0, songDuration - fade);
3491
+ let primaryUrl = primary.stream.url;
3492
+ let primarySeek = boundarySeek;
3493
+ let primaryInputArgs;
3494
+ const prepare = this.options.crossfade.preparePrimaryInput;
3495
+ if (prepare) {
3496
+ try {
3497
+ const prepared = await prepare(primary.stream.url, { seek: boundarySeek, song });
3498
+ if (prepared?.url) {
3499
+ primaryUrl = prepared.url;
3500
+ if (typeof prepared.seek === "number" && Number.isFinite(prepared.seek) && prepared.seek >= 0) {
3501
+ primarySeek = prepared.seek;
3502
+ }
3503
+ primaryInputArgs = prepared.inputArgs;
3504
+ this.debug(`[${queue.id}] Crossfade primary input prepared by the adapter`);
3505
+ }
3506
+ } catch (e) {
3507
+ const errorMessage = e instanceof Error ? e.message : String(e);
3508
+ this.debug(`[${queue.id}] Crossfade primary input preparation failed: ${errorMessage}`);
3509
+ }
3510
+ }
3511
+ transitionStream = new DisTubeStream(primaryUrl, {
3480
3512
  ffmpeg: {
3481
3513
  path: this.options.ffmpeg.path,
3482
3514
  args: {
@@ -3487,10 +3519,11 @@ var QueueManager = class extends GuildIdManager {
3487
3519
  },
3488
3520
  crossfade: {
3489
3521
  duration: fade,
3490
- primarySeek: Math.max(0, songDuration - fade),
3522
+ primarySeek,
3491
3523
  secondaryUrl: secondary.stream.url,
3492
3524
  inCurve: this.options.crossfade.curve,
3493
- outCurve: this.options.crossfade.curve
3525
+ outCurve: this.options.crossfade.curve,
3526
+ primaryInputArgs
3494
3527
  }
3495
3528
  });
3496
3529
  transitionStream.on("debug", (data) => this.emit("ffmpegDebug" /* FFMPEG_DEBUG */, `[${queue.id}] [crossfade] ${data}`));