Discord Player
Guides

Resource hooks

Replace or inspect the audio source of a track before Remedia opens it.

You are reading the v8 docsDev

Hooks run while a track is prepared for playback. Use them to swap the audio source, rewrite a URL, or process encoded audio in your own process.

HookRunsCan return
onBeforeCreateStreamBefore normal source resolutionA replacement source, or null to continue
onStreamExtractedAfter the plugin resolved a sourceThe source to use, or a nullish value to keep it
onAfterCreateStreamOn encoded bytes, embedded nodes only{ stream }, or null to keep the input

The v7 argument order is preserved. v8 appends an AbortSignal as the last argument:

onBeforeCreateStream((track, queryType, queue, signal) => ...);
onStreamExtracted((source, track, queue, signal) => ...);
onAfterCreateStream((stream, queue, track, signal) => ...);

Register a hook

Set a hook in one of three places. The most specific wins:

  1. The queue: queue.onBeforeCreateStream = ... or the queue option.
  2. The Player constructor options.
  3. Global registration with the exported function.
import { onBeforeCreateStream } from 'discord-player';

const remove = onBeforeCreateStream(async (track, queryType, queue, signal) => {
  signal.throwIfAborted();

  return null; // Continue with normal resolution
});

queue.onBeforeCreateStream = async (track, queryType, queue, signal) => {
  signal.throwIfAborted();

  return { url: track.url };
};

A global registration replaces the previous one. Call the returned function to remove it when the component that registered it shuts down.

queryType uses the Track's stored query type, or 'arbitrary' when the Track has none, matching v7.

Hooks run inside the owning Player and queue context.

Accepted sources

A replacement can be:

  • A URL string or { url }.
  • { file }.
  • A native media descriptor.
  • A Node.js Readable, Buffer, or Uint8Array of encoded audio.
SourceEmbedded nodeRemote node
URL or fileYesYes, if the server can open it
Readable or bufferYesNo

An unsupported source fails with an error. The Player does not move the guild to another node.

Cancellation

The last argument is an AbortSignal for the preparation. Pass it to your own I/O. The queue also checks it after your hook returns and closes streams that arrive late.

With crossfade, hooks run when the next track is staged. The prepared source is reused when it becomes audible.

onStreamExtracted

import { onStreamExtracted } from 'discord-player';

onStreamExtracted(async (source, track, queue, signal) => {
  return typeof source === 'string' ? `${source}?token=${await getToken(signal)}` : source;
});

It receives the resolved source, which can be a URL or an operation descriptor. It does not see the audio bytes.

As in v7, it only runs after normal provider resolution. If onBeforeCreateStream returned a replacement source, provider resolution and onStreamExtracted are skipped. Returning null or undefined keeps the resolved source.

onAfterCreateStream

queue.onAfterCreateStream = async (stream, queue, track, signal) => {
  return { stream: myTransform(stream, signal) };
};

The result may include a legacy type field, which is accepted for v7 source compatibility but ignored because Remedia owns decoding. Return { stream } or null.

This hook needs the encoded bytes in your process, so it only works with an embedded node and a Readable source. When Remedia opens a URL or file itself, the bytes never reach your code and registering this hook for that source fails with an error. Return a Readable from onBeforeCreateStream if you need to process audio locally.

v8 has no hook for decoded PCM audio.

Continue reading

Use Player context

On this page