Leave voice
DisTube never leaves a voice channel on its own. The bot stays connected until you call voices.leave or dispose,
or until Discord ends the connection. When to leave is your bot’s decision.
In this guide you add two listeners to index.ts. The first leaves a while after the queue ends. The second leaves
when nobody but bots is left in the channel. Use either one or both.
Leave after the queue ends
Section titled “Leave after the queue ends”When a queue runs out of tracks, a member runs /stop, or every track a /play asked for fails to load, DisTube
deletes the queue. The voice stays connected and keeps its volume and filters, and the next /play reuses the
connection. To leave if no new queue arrives within five minutes, add this to index.ts, after you create distube:
const leaveDelayMs = 300_000;const leaveTimers = new Map<string, NodeJS.Timeout>();
distube.on("queue.deleted", ({ guildId, reason }) => { if (reason !== "finished" && reason !== "stop" && reason !== "failed") return; clearTimeout(leaveTimers.get(guildId)); leaveTimers.set( guildId, setTimeout(() => { leaveTimers.delete(guildId); if (!distube.getQueue(guildId)) { distube.voices.leave(guildId).catch(error => console.error("Leave failed", error)); } }, leaveDelayMs).unref(), );});
distube.on("queue.created", ({ guildId }) => { clearTimeout(leaveTimers.get(guildId)); leaveTimers.delete(guildId);});Each guild gets its own timer. A deleted queue starts it, and a new queue cancels it.
The reason check skips the other deletions, leave, voice-loss and shutdown. In those the voice is gone or about
to go, so there is nothing to leave. unref() lets Node exit while a timer is still waiting. Without it, a timer
started shortly before you stop the bot would keep the process alive for up to five minutes after
shutdown.
The timer checks getQueue once more before it leaves. DisTube delivers events a moment after they happen, but
getQueue sees a new queue as soon as play creates it. So a /play that lands just before the timer fires keeps
the bot in the channel.
If the voice already ended, for example because someone disconnected the bot by hand, voices.leave does nothing.
Leave when the channel is empty
Section titled “Leave when the channel is empty”A bot playing to an empty channel keeps playing. To stop that, watch Discord’s voice state updates and leave once
only bots remain. Add this to index.ts below the other listeners. It uses the Events import that file already has.
const emptyDelayMs = 30_000;const emptyTimers = new Map<string, NodeJS.Timeout>();
function botIsAlone(guildId: string): boolean { const channelId = distube.voices.get(guildId)?.channelId; if (!channelId) return false; const channel = client.channels.cache.get(channelId); return channel?.isVoiceBased() === true && channel.members.every(member => member.user.bot);}
client.on(Events.VoiceStateUpdate, (_oldState, newState) => { const guildId = newState.guild.id; const pending = emptyTimers.get(guildId); if (!botIsAlone(guildId)) { clearTimeout(pending); emptyTimers.delete(guildId); return; } if (pending) return; emptyTimers.set( guildId, setTimeout(() => { emptyTimers.delete(guildId); if (botIsAlone(guildId)) { distube.voices.leave(guildId).catch(error => console.error("Leave failed", error)); } }, emptyDelayMs).unref(), );});Every voice state update in the server runs the check:
- When only bots remain in the bot’s channel, the listener starts one 30 second timer.
- When a person joins before it fires, the listener cancels it.
- When it fires, the listener checks again before it leaves, so a member who rejoined at the last moment keeps the bot.
This timer calls unref() for the same reason as the first one.
The listener needs the GuildVoiceStates intent. Your client already has it, because the bot cannot join voice
without it.
When the voice ends for any reason, DisTube emits voice.disconnected with a reason. The
events reference lists those reasons, and the reasons on queue.deleted.
Shut down cleanly releases the voice connections when you stop the bot.