Skip to content

Pause, resume and seek

On this page you add /pause, /resume and /seek. All three act on the track that is playing, and all three wait for the voice player to confirm the change.

commands/pause.ts
import { type ChatInputCommandInteraction, InteractionContextType, SlashCommandBuilder } from "discord.js";
import type { DisTube } from "distube";
import type { CommandMetadata } from "./index.ts";
export const data = new SlashCommandBuilder()
.setName("pause")
.setDescription("Pause playback")
.setContexts(InteractionContextType.Guild)
.toJSON();
export async function execute(
interaction: ChatInputCommandInteraction<"cached">,
distube: DisTube<CommandMetadata>,
): Promise<void> {
const queue = distube.getQueue(interaction.guildId);
if (!queue) {
await interaction.reply("There is no active queue.");
return;
}
await interaction.deferReply();
await queue.pause();
await interaction.editReply("Paused playback.");
}
commands/resume.ts
import { type ChatInputCommandInteraction, InteractionContextType, SlashCommandBuilder } from "discord.js";
import type { DisTube } from "distube";
import type { CommandMetadata } from "./index.ts";
export const data = new SlashCommandBuilder()
.setName("resume")
.setDescription("Resume playback")
.setContexts(InteractionContextType.Guild)
.toJSON();
export async function execute(
interaction: ChatInputCommandInteraction<"cached">,
distube: DisTube<CommandMetadata>,
): Promise<void> {
const queue = distube.getQueue(interaction.guildId);
if (!queue) {
await interaction.reply("There is no active queue.");
return;
}
await interaction.deferReply();
await queue.resume();
await interaction.editReply("Resumed playback.");
}
commands/seek.ts
import { type ChatInputCommandInteraction, InteractionContextType, SlashCommandBuilder } from "discord.js";
import type { DisTube } from "distube";
import type { CommandMetadata } from "./index.ts";
export const data = new SlashCommandBuilder()
.setName("seek")
.setDescription("Seek within the current track")
.setContexts(InteractionContextType.Guild)
.addNumberOption(option =>
option.setName("seconds").setDescription("Position in seconds").setRequired(true).setMinValue(0),
)
.toJSON();
export async function execute(
interaction: ChatInputCommandInteraction<"cached">,
distube: DisTube<CommandMetadata>,
): Promise<void> {
const queue = distube.getQueue(interaction.guildId);
if (!queue) {
await interaction.reply("There is no active queue.");
return;
}
const seconds = interaction.options.getNumber("seconds", true);
await interaction.deferReply();
await queue.seek(seconds);
await interaction.editReply(`Seeked to ${seconds} seconds.`);
}

Add the three to commands/index.ts: import each one, for example import * as pause from "./pause.ts";, and put pause, resume, seek in the commands array.

pause, resume and seek settle only once the voice player confirms the change. Discord gives you three seconds for the first reply, so each command defers before it waits on the player.

All three need a track that has started. While none has, for example before the first one or while the next one loads, they reject with INVALID_STATE. seek can also reject in these cases:

  • INVALID_ARGUMENT for a negative position, or one past the track’s known duration.
  • UNSUPPORTED when the track’s playback resource cannot seek, such as a plain byte stream.
  • INVALID_STATE when the track ends before the seek lands.
  • VOICE_FAILED when the player fails to seek.

You do not need to catch any of these. The router from Command handling replies with errorMessage. If the player rejects a change, the call rejects and DisTube publishes nothing.

queue.position gives the seconds played of the current track. A progress bar or a /nowplaying reply can read it at any time.

While the status is playing, queue.position advances queue.playbackRate seconds per second. Nightcore at 1.25 moves 1.25 seconds each second. The live status section on the now-playing page shows how to keep a clock from that.

Each pause, resume or seek the player confirms publishes one queue.updated with the new state.status and position. A progress message stays current without polling. Pausing a paused queue changes nothing and publishes nothing, and so does resuming a playing one.

See also: queue reference, errors reference and the example’s pause.ts, resume.ts and seek.ts.

Repeat and autoplay decides what plays when the waiting list runs out.