Discord Player
Guides

Player context

Read the current Player, queue, or history inside a command without passing them around.

You are reading the v8 docsDev

The context uses Node.js AsyncLocalStorage. It belongs to one Player, so several Players do not share an active queue.

import { useHistory, useMainPlayer, usePlayer, useQueue } from 'discord-player';

await player.context.provide({ guildId }, async () => {
  const mainPlayer = useMainPlayer();
  const node = usePlayer();
  const queue = useQueue();
  const history = useHistory();

  console.log(mainPlayer.options, node?.isPaused(), queue?.currentTrack?.title, history?.size);
});

The context stays available across await inside the callback.

Hooks

HookReturns
useMainPlayer()The root DiscordPlayer in the current context, otherwise the first active Player. Throws if no Player exists
usePlayer(node?)The queue's GuildQueuePlayerNode, or null
useQueue(node?)The guild queue, or null
useHistory(node?)The queue's GuildQueueHistory, or null
useMetadata(node?)A [get, set] tuple backed by the queue's real metadata. The setter accepts a value or a functional update
useVolume(node?)A [get, set] tuple using percentage volume. The setter is asynchronous: await set(50) or await set(v => v + 5)
useTimeline(opts?)A GuildQueueTimeline controller, or null

Every node argument accepts a guild ID, a guild-like object ({ id } or { guildId }), or a GuildQueue instance. Without it, the hook uses the guild or queue supplied to provide().

await player.context.provide({ guildId }, async () => {
  const [getMetadata, setMetadata] = useMetadata<{ locale: string }>();
  const [getVolume, setVolume] = useVolume();

  setMetadata((previous) => ({ ...previous, locale: 'en' }));
  await setVolume((previous) => previous + 10);
});

Timeline

useTimeline() returns a live view of the queue. Reading a property reflects current playback; controlling it awaits Remedia.

const timeline = useTimeline();

if (timeline) {
  console.log(timeline.track?.title, timeline.timestamp?.current.label, timeline.volume, timeline.paused);

  await timeline.pause();
  await timeline.resume();
  await timeline.setVolume(80);
  await timeline.setPosition(30_000);
}

Control methods return Promise<boolean>. pause(), resume(), and setPosition() return false when the queue has no active session or Track. setVolume() resolves after Remedia applies the percentage.

Pass { ignoreFilters: true } to read the source media position instead of Remedia's filtered output clock. Remedia does not expose the duration multiplier of tempo-changing filters, so timestamp.total always reflects the source duration. Pass { node } to target another queue.

const sourceTimeline = useTimeline({ node: guildId, ignoreFilters: true });

Outside a context

Background jobs have no async context. Pass the guild or queue explicitly:

const queue = useQueue(guildId);
const node = usePlayer({ guildId });
const history = useHistory(queueInstance);

Calling a guild-scoped hook without a context, guild, or node throws IllegalHookInvocationError with code ERR_ILLEGAL_HOOK_INVOCATION. It never guesses a guild.

When a Player is destroyed, the fallback moves to the next active Player.

Continue reading

Save tracks

On this page