Resource hooks
Replace or inspect the audio source of a track before Remedia opens it.
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.
| Hook | Runs | Can return |
|---|---|---|
onBeforeCreateStream | Before normal source resolution | A replacement source, or null to continue |
onStreamExtracted | After the plugin resolved a source | The source to use, or a nullish value to keep it |
onAfterCreateStream | On 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:
- The queue:
queue.onBeforeCreateStream = ...or the queue option. - The Player constructor options.
- 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, orUint8Arrayof encoded audio.
| Source | Embedded node | Remote node |
|---|---|---|
| URL or file | Yes | Yes, if the server can open it |
Readable or buffer | Yes | No |
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