Discord Player provides a hooks system inspired by React, allowing you to access player resources within an execution context. This is particularly useful for organizing code and accessing queues without passing objects around.
Overview
Hooks provide a clean way to access player resources within a specific context:
import { useQueue , useMainPlayer } from 'discord-player' ;
const player = useMainPlayer ();
const queue = useQueue ();
Hooks must be called within a context provided by the player. This typically happens automatically when using extractors or event handlers.
Context System
The hooks system uses Node.js AsyncLocalStorage to maintain context:
import { createContext , useContext } from 'discord-player' ;
// Create a custom context
const myContext = createContext <{ userId : string }>();
// Provide context value
myContext . provide ({ userId: '123' }, () => {
// Use context
const value = useContext ( myContext );
console . log ( value ?. userId ); // '123'
});
Available Hooks
useMainPlayer()
Get the main Player instance:
import { useMainPlayer } from 'discord-player' ;
function myFunction () {
const player = useMainPlayer ();
// Access player methods
const stats = player . generateStatistics ();
console . log ( `Active queues: ${ stats . queuesCount } ` );
}
Returns: Player
useQueue()
Get the guild queue for the current context:
import { useQueue } from 'discord-player' ;
function myFunction () {
const queue = useQueue < MyMetadata >();
if ( ! queue ) {
console . log ( 'No active queue' );
return ;
}
console . log ( `Playing: ${ queue . currentTrack ?. title } ` );
}
Signature:
useQueue < Meta = unknown > ( node ?: NodeResolvable ): GuildQueue < Meta > | null
Parameters:
node (optional): Guild ID, Guild object, or queue to resolve
Returns: GuildQueue<Meta> | null
usePlayer()
Get the player node (playback controller) for a queue:
import { usePlayer } from 'discord-player' ;
function myFunction () {
const player = usePlayer < MyMetadata >();
if ( ! player ) return ;
// Control playback
player . pause ();
player . setVolume ( 50 );
await player . play ();
}
Signature:
usePlayer < Meta = unknown > ( node ?: NodeResolvable ): GuildQueuePlayerNode < Meta > | null
Returns: GuildQueuePlayerNode<Meta> | null
useHistory()
Get the queue history:
import { useHistory } from 'discord-player' ;
function myFunction () {
const history = useHistory < MyMetadata >();
if ( ! history ) return ;
// Access previous tracks
const previous = history . previousTrack ;
console . log ( `Previously played: ${ previous ?. title } ` );
// Navigate history
await history . back ();
}
Signature:
useHistory < Meta = unknown > ( node ?: NodeResolvable ): GuildQueueHistory < Meta > | null
Returns: GuildQueueHistory<Meta> | null
Get and set queue metadata:
import { useMetadata } from 'discord-player' ;
interface MyMetadata {
channel : TextChannel ;
volume : number ;
}
function myFunction () {
const [ getMetadata , setMetadata ] = useMetadata < MyMetadata >();
// Get metadata
const metadata = getMetadata ();
console . log ( `Channel: ${ metadata . channel . name } ` );
// Set metadata
setMetadata ({
channel: metadata . channel ,
volume: 80
});
// Update with function
setMetadata ( prev => ({
... prev ,
volume: prev . volume + 10
}));
}
Signature:
useMetadata < T = unknown > ( node ?: NodeResolvable ): readonly [
() => T ,
( metadata : T | (( prev : T ) => T )) => void
]
Returns: Tuple of [getter, setter]
useTimeline()
Get current playback timeline information:
import { useTimeline } from 'discord-player' ;
function myFunction () {
const timeline = useTimeline ();
if ( ! timeline ) return ;
// Get playback info
console . log ( `Current: ${ timeline . timestamp . current . label } ` );
console . log ( `Duration: ${ timeline . timestamp . total . label } ` );
console . log ( `Volume: ${ timeline . volume } %` );
console . log ( `Paused: ${ timeline . paused } ` );
// Control playback
timeline . pause ();
timeline . resume ();
timeline . setVolume ( 75 );
await timeline . setPosition ( 60000 ); // Seek to 1 minute
}
Signature:
useTimeline ( options ?: {
ignoreFilters? : boolean ;
node ?: NodeResolvable ;
}): GuildQueueTimeline | null
Timeline Object:
interface GuildQueueTimeline {
readonly timestamp : PlayerTimestamp ;
readonly volume : number ;
readonly paused : boolean ;
readonly track : Track | null ;
pause () : boolean ;
resume () : boolean ;
setVolume ( vol : number ) : boolean ;
setPosition ( time : number ) : Promise < boolean >;
}
Hooks are automatically available within extractor methods:
import { BaseExtractor , useMainPlayer } from 'discord-player' ;
class MyExtractor extends BaseExtractor {
async handle ( query : string ) {
const player = useMainPlayer ();
// Access player within extractor context
if ( player . hasDebugger ) {
player . debug ( 'Handling query in MyExtractor' );
}
return {
playlist: null ,
tracks: []
};
}
}
Creating Custom Contexts
Create your own contexts for custom functionality:
import { createContext , useContext } from 'discord-player' ;
interface UserContext {
id : string ;
preferences : {
volume : number ;
autoplay : boolean ;
};
}
// Create context
const userContext = createContext < UserContext >();
// Provide context
function withUserContext ( userId : string , callback : () => void ) {
const preferences = loadUserPreferences ( userId );
userContext . provide (
{ id: userId , preferences },
callback
);
}
// Use context
function handleCommand () {
const user = useContext ( userContext );
if ( ! user ) {
console . log ( 'No user context available' );
return ;
}
console . log ( `User ${ user . id } prefers volume ${ user . preferences . volume } ` );
}
// Usage
withUserContext ( '123456' , () => {
handleCommand ();
});
Context API
createContext()
Create a new context:
const context = createContext < MyType >( defaultValue ? );
useContext()
Consume a context value:
const value = useContext ( context );
Context Methods
// Provide context value
context . provide ( value , () => {
// Code with context
});
// Consume context
const value = context . consume ();
// Check if context is lost
if ( context . isLost ) {
console . log ( 'No context available' );
}
// Exit context
context . exit (() => {
// Code without context
});
Example: Command Handler with Hooks
import {
useQueue ,
usePlayer ,
useMetadata ,
useTimeline
} from 'discord-player' ;
import { ChatInputCommandInteraction } from 'discord.js' ;
interface QueueMetadata {
channel : TextChannel ;
requestedBy : User ;
}
async function handlePauseCommand ( interaction : ChatInputCommandInteraction ) {
const queue = useQueue < QueueMetadata >();
const player = usePlayer < QueueMetadata >();
if ( ! queue || ! player ) {
return interaction . reply ( 'No music is playing!' );
}
player . pause ();
await interaction . reply ( 'Paused playback' );
}
async function handleVolumeCommand (
interaction : ChatInputCommandInteraction ,
volume : number
) {
const timeline = useTimeline ();
if ( ! timeline ) {
return interaction . reply ( 'No music is playing!' );
}
timeline . setVolume ( volume );
await interaction . reply ( `Volume set to ${ volume } %` );
}
async function handleNowPlayingCommand ( interaction : ChatInputCommandInteraction ) {
const timeline = useTimeline ();
if ( ! timeline || ! timeline . track ) {
return interaction . reply ( 'No music is playing!' );
}
const track = timeline . track ;
const { current , total } = timeline . timestamp ;
await interaction . reply (
`Now playing: ** ${ track . title } ** \n ` +
`Progress: ${ current . label } / ${ total . label } \n ` +
`Volume: ${ timeline . volume } %`
);
}
async function handleQueueCommand ( interaction : ChatInputCommandInteraction ) {
const queue = useQueue < QueueMetadata >();
const [ getMetadata ] = useMetadata < QueueMetadata >();
if ( ! queue ) {
return interaction . reply ( 'No active queue!' );
}
const metadata = getMetadata ();
const tracks = queue . tracks . store . slice ( 0 , 10 );
const list = tracks
. map (( track , i ) => ` ${ i + 1 } . ${ track . title } [ ${ track . duration } ]` )
. join ( ' \n ' );
await interaction . reply (
`**Queue for ${ queue . guild . name } ** \n ` +
`Now playing: ${ queue . currentTrack ?. title } \n\n ` +
`**Up next:** \n ${ list || 'Nothing' } \n\n ` +
`Total: ${ queue . size } tracks`
);
}
Example: Using Hooks in Event Handlers
import { useQueue , useMetadata } from 'discord-player' ;
interface QueueMetadata {
channel : TextChannel ;
}
// Hooks work automatically in player.events context
player . events . on ( 'playerStart' , ( queue , track ) => {
// Context is automatically provided
const [ getMetadata ] = useMetadata < QueueMetadata >();
const metadata = getMetadata ();
metadata . channel . send ( `Now playing: ** ${ track . title } **` );
});
player . events . on ( 'audioTrackAdd' , ( queue , track ) => {
const queue = useQueue < QueueMetadata >();
if ( ! queue ) return ;
const position = queue . size ;
queue . metadata . channel . send (
`Added ** ${ track . title } ** to position ${ position } `
);
});
Best Practices
Type Safety with Metadata
Always check for null returns from hooks: const queue = useQueue ();
if ( ! queue ) {
// Handle no active queue
return ;
}
// Safe to use queue
Hooks only work within proper context. If you get context errors, ensure you’re calling hooks:
Inside event handlers
Inside extractor methods
Within a custom context provider
Limitations
Hooks rely on AsyncLocalStorage and must be called within an active context. They will not work:
Outside of event handlers
In regular command handlers (unless wrapped with context)
After async operations that lose context
See Also