Events
DisTube tells your bot what happens through typed events. Subscribe with distube.on(name, listener). It returns a
function that removes the listener, so keep it if you ever need to unsubscribe.
import type { DisTube } from "distube";
declare const distube: DisTube;
const unsubscribe = distube.on("queue.item.started", event => { console.log(event.guildId, event.item.song.name, event.timestamp);});unsubscribe();The events
Section titled “The events”| Event | Fires when | Extra fields |
|---|---|---|
queue.created |
A guild gets a new, empty queue | none |
queue.item.added |
A Song was added | item |
queue.playlist.added |
A Playlist was added | playlist, items |
queue.item.started |
An item became audible | item, optional fallback |
queue.item.finished |
An item played to its end | item |
queue.item.failed |
An item could not be prepared or played | item, error |
queue.finished |
Playback ran out of items | none |
queue.autoplay.failed |
Autoplay could not add a related Song | song, error |
queue.updated |
Anything a status view shows changed | position, playbackRate |
queue.deleted |
The queue is gone | reason |
voice.disconnected |
The bot’s voice in a guild ended | voice, channelId, reason, optional error |
Every queue event also carries guildId, the queue, a snapshot state and the timestamp of the change. state
holds current, upcoming, history, status, repeat and autoplay as they were at that moment. state.status
is idle, preparing, playing or paused, as the queue reference explains. Voice events carry
guildId, voice and timestamp.
on throws INVALID_ARGUMENT for a name that is not in this table. A misspelled or v5 name fails when you register
it, instead of never firing.
Fallback
Section titled “Fallback”fallback is the Song that actually plays when the item’s own plugin cannot stream. For a Spotify track, it is the
YouTube video DisTube found for it, and item.song stays the Spotify Song.
If nothing can play the item, it fails with NO_RESULT. When the searches or streams failed instead, it fails with the
code they all share, or SOURCE_FAILED when they differ. See
when every plugin failed.
queue.updated
Section titled “queue.updated”queue.updated is the one event a live status view needs. DisTube publishes it once for each change to a live queue:
its state, its pause, its position after a seek, or the volume or filters of its voice. On top of the queue fields, it
carries position and playbackRate as they were at the change.
While state.status is playing, work out the position now from the event:
import type { DisTube } from "distube";
declare const distube: DisTube;
distube.on("queue.updated", ({ state, position, playbackRate, timestamp }) => { const now = state.status === "playing" ? position + ((Date.now() - timestamp) / 1000) * playbackRate : position; console.log(`At ${now.toFixed(1)} seconds`);});With any other status the position stands still. Announce what is playing has a full listener that edits a message.
When a change also has an event of its own, queue.updated comes right after it, with the same state and
timestamp:
| Change | Events, in order |
|---|---|
play, add |
queue.item.added or queue.playlist.added, queue.updated |
| Autoplay adds a Song | queue.item.added, queue.updated |
| The player takes an item | queue.updated |
| An item becomes audible | queue.item.started, queue.updated |
| An item finishes on its own, with repeat and history | queue.item.finished, queue.updated |
| An item fails to prepare, or the player rejects it | queue.item.failed, queue.updated |
| An autoplay lookup fails | queue.autoplay.failed, queue.updated |
skip, jump, previous |
queue.updated, once per command |
remove, move, shuffle, clear |
queue.updated |
setRepeat, setAutoplay |
queue.updated |
pause, resume, once the player confirms |
queue.updated |
seek, once the player confirms |
queue.updated |
voice.setVolume, voice.filters.*, once confirmed |
queue.updated |
- A command that changes several things publishes one update. A
jumpover three items puts all three in thestate.historyof one event. - A call that changes nothing publishes nothing. That covers
setRepeatto the current mode,clearon an empty list,moveto the place the item already has,pauseon a paused queue,setVolumeto the current volume, and a filter selection that stayed the same. - A rejected call publishes nothing, and neither does a pause, resume, seek, volume or filter change the player rejects.
- A deleted queue publishes nothing more. Deleting a queue publishes
queue.deletedand no update. - A voice with no queue publishes no
queue.updated. - Volume and filters are not in the payload, because they belong to the voice. Read
event.queue.voice?.volumein the listener, and you get the latest value. queue.item.finishedcarries the state after the finish, so itsstate.historyalready ends with the finished item.- Starting an item publishes two updates, one when the player takes it and one when it becomes audible. Moving from one item to the next publishes three: the finish, the hand-off and the audible start.
Reasons
Section titled “Reasons”queue.deleted carries one of these reasons:
| Reason | Cause |
|---|---|
stop |
queue.stop() |
finished |
The queue ran out of items. queue.finished came first. |
voice-loss |
The voice session was lost |
leave |
voice.leave(), voices.leave(guildId), or stop() during the first join |
shutdown |
distube.dispose() |
failed |
The first insertion failed and left the queue empty, with no other insertion pending. No queue.finished fires. |
voice.disconnected carries one of these:
| Reason | Cause |
|---|---|
left |
voice.leave() or voices.leave(guildId) |
removed |
Discord removed the bot from voice, for example a member disconnected it. No error. |
platform |
The connection dropped and the adapter could not recover it. error is VOICE_DISCONNECTED, with the adapter’s error as its cause. |
shutdown |
distube.dispose() |
voice.disconnected fires once per voice, after that guild’s queue.deleted.
getQueue(guildId)finds a new queue beforequeue.createdarrives. The event does not promise that the media resolves or that the insertion succeeds.- Adding a Song fires one
queue.item.added. Adding a Playlist fires onequeue.playlist.addedand no event per item.playresolves after that. The item can still fail to prepare or play later, which firesqueue.item.failedfor it. - Each item gets at most one of
queue.item.finishedandqueue.item.failed. An item that a command ends early gets neither, only the command’s update. The queue reference has the history rules. - When the queue runs out,
queue.finishedfires, thenqueue.deletedwith reasonfinished. - When the voice session ends during a track,
queue.deletedwith reasonvoice-lossfires first, thenvoice.disconnectedwith reasonremovedorplatform. The playing item gets noqueue.item.failed, because the voice failed, not the item.
Delivery
Section titled “Delivery”DisTube delivers each event in a later microtask, in the order the events happened. A listener added during delivery
does not receive that event. A listener that throws or rejects affects nothing else. DisTube reports it to
onDiagnostic with stage event.listener.
From v5
Section titled “From v5”| v5 event | v6 |
|---|---|
initQueue |
queue.created |
addSong |
queue.item.added |
addList |
queue.playlist.added |
playSong |
queue.item.started |
finishSong |
queue.item.finished. A skipped item publishes none. |
finish |
queue.finished |
deleteQueue |
queue.deleted |
disconnect |
voice.disconnected |
error |
queue.item.failed, and the rejection of play or add |
noRelated |
queue.autoplay.failed |
empty |
none. See leave when empty. |
ffmpegDebug |
none. The built-in engine runs no ffmpeg process. |
debug |
the onDiagnostic option |