Overview
Discord Player automatically manages voice state updates, handling scenarios like bot disconnections, empty channels, and server mute/unmute events. You can customize this behavior or implement your own voice state handler.
Default Voice State Handler
The default handler (defaultVoiceStateHandler) manages several key scenarios:
Bot Disconnection
When the bot is disconnected from a voice channel, the queue is automatically deleted:
// Detected when oldState.channelId exists but newState.channelId is null
if ( isBotState && oldState . channelId && ! newState . channelId ) {
queue . delete ();
player . events . emit ( GuildQueueEvent . Disconnect , queue );
}
Empty Channel Management
The handler monitors when users leave the voice channel:
const queue = player . nodes . create ( interaction . guild , {
leaveOnEmpty: true ,
leaveOnEmptyCooldown: 60_000 , // 60 seconds
});
Server Mute/Unmute
The bot automatically pauses when server muted:
DefaultVoiceStateHandler.ts
// Handles server mute state changes
if ( newState . serverMute != null && oldState . serverMute !== newState . serverMute ) {
queue . node . setPaused ( newState . serverMute );
}
Stage Channel Support
Automatic handling for stage channels:
DefaultVoiceStateHandler.ts
if ( newState . channel ?. type === ChannelType . GuildStageVoice ) {
if ( newState . suppress != null && oldState . suppress !== newState . suppress ) {
queue . node . setPaused ( newState . suppress );
if ( newState . suppress ) {
newState . guild . members . me ?. voice . setRequestToSpeak ( true );
}
}
}
Voice State Events
Listening to Voice State Updates
player . events . on ( GuildQueueEvent . VoiceStateUpdate , ( queue , oldState , newState ) => {
console . log ( `Voice state updated in ${ queue . guild . name } ` );
console . log ( `Old channel: ${ oldState . channelId } ` );
console . log ( `New channel: ${ newState . channelId } ` );
});
Empty Channel Events
// Emitted when all users leave the voice channel
player . events . on ( GuildQueueEvent . EmptyChannel , ( queue ) => {
console . log ( `Voice channel is now empty in ${ queue . guild . name } ` );
});
// Emitted when users rejoin the voice channel
player . events . on ( GuildQueueEvent . ChannelPopulate , ( queue ) => {
console . log ( `Voice channel was repopulated in ${ queue . guild . name } ` );
});
Disconnect Event
player . events . on ( GuildQueueEvent . Disconnect , ( queue ) => {
console . log ( `Bot was disconnected from ${ queue . guild . name } ` );
// Queue is automatically deleted
});
Custom Voice State Handler
You can override the default voice state handler:
import { VoiceState } from 'discord.js' ;
import { Player , GuildQueue } from 'discord-player' ;
player . onVoiceStateUpdate (( player , queue , oldState , newState ) => {
// Your custom logic here
const isBotState = newState . member ?. id === newState . guild . members . me ?. id ;
if ( isBotState && oldState . channelId && ! newState . channelId ) {
console . log ( 'Bot was disconnected!' );
queue . delete ();
}
// Handle other scenarios...
});
When implementing a custom handler, you’re responsible for all voice state logic including disconnections, empty channels, and server mute handling.
Voice State Handler Lock
Control whether custom handlers can override the default behavior:
// Lock the handler (always use default, ignore custom implementations)
player . lockVoiceStateHandler ();
// Unlock the handler (allow custom implementations)
player . unlockVoiceStateHandler ();
// Check if locked
const isLocked = player . isVoiceStateHandlerLocked ();
Or set it during initialization:
const player = new Player ( client , {
lockVoiceStateHandler: true ,
});
Event-Based Voice State Handling
Consume the VoiceStateUpdate event without disabling the default handler:
player . events . on ( GuildQueueEvent . VoiceStateUpdate , ( queue , oldState , newState ) => {
// Your custom logic runs alongside the default handler
if ( oldState . channelId !== newState . channelId ) {
console . log ( 'User moved channels' );
}
// Default handler still runs unless lockVoiceStateHandler is enabled
});
If you consume the VoiceStateUpdate event and lockVoiceStateHandler is false, the default handler will be disabled. Set lockVoiceStateHandler: true to run both.
Timeout Management
The voice state handler uses internal timeouts tracked in queue.timeouts:
// Example: Clear empty channel timeout
const emptyTimeout = queue . timeouts . get ( `empty_ ${ guildId } ` );
if ( emptyTimeout ) {
clearTimeout ( emptyTimeout );
queue . timeouts . delete ( `empty_ ${ guildId } ` );
}
Configuration Options
Queue-Level Options
const queue = player . nodes . create ( interaction . guild , {
// Leave when channel is empty
leaveOnEmpty: true ,
leaveOnEmptyCooldown: 60_000 , // 1 minute
// Leave when queue ends
leaveOnEnd: true ,
leaveOnEndCooldown: 30_000 , // 30 seconds
// Leave when stopped
leaveOnStop: true ,
leaveOnStopCooldown: 5_000 , // 5 seconds
// Pause when channel is empty
pauseOnEmpty: true ,
// Self deaf (recommended)
selfDeaf: true ,
});
Debugging Voice States
Enable debug mode to see voice state handling in action:
player . events . on ( GuildQueueEvent . Debug , ( queue , message ) => {
console . log ( `[ ${ queue . guild . name } ] ${ message } ` );
});
Debug output examples:
[Guild Name] Voice channel is empty and options#pauseOnEmpty is true, pausing...
[Guild Name] Voice channel is not empty and options#pauseOnEmpty is true, resuming...
[Guild Name] Connecting to voice channel Main Voice (ID: 123456789)
Best Practices
Use Default Handler The default handler covers most use cases. Only override if you have specific requirements.
Set Cooldowns Use cooldowns to prevent immediate disconnections when users briefly leave.
Enable Self Deaf Always set selfDeaf: true to reduce bandwidth usage.
Monitor Events Listen to EmptyChannel and ChannelPopulate events for custom notifications.
Common Patterns
Prevent Auto-Disconnect
const queue = player . nodes . create ( interaction . guild , {
leaveOnEmpty: false ,
leaveOnEnd: false ,
leaveOnStop: false ,
});
Smart Pause/Resume
const queue = player . nodes . create ( interaction . guild , {
pauseOnEmpty: true , // Auto-pause when alone
leaveOnEmpty: true ,
leaveOnEmptyCooldown: 300_000 , // Leave after 5 minutes
});
Notification on Empty Channel
player . events . on ( GuildQueueEvent . EmptyChannel , async ( queue ) => {
const channel = queue . metadata ?. channel ; // Store channel in metadata
if ( channel ?. isSendable ()) {
await channel . send ( 'Everyone left! Leaving in 60 seconds...' );
}
});