Skip to content

Quick start

Player is the main entry point. Give it a node and the forwardVoiceUpdate callback — It’s the bridge between Discolink and your Discord gateway client.

import { Client, GatewayIntentBits, SlashCommandBuilder } from "discord.js";
import { Player } from "discolink";
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
});
const node = {
name: "main",
origin: "http://localhost:2333",
password: "youshallnotpass",
};
const player = new Player({
nodes: [node],
async forwardVoiceUpdate(guildId, payload) {
// Send the raw gateway payload (op 4) to Discord
client.guilds.cache.get(guildId)?.shard.send(payload);
},
});
client.login("YOUR_BOT_TOKEN");

The exact send method depends on your library — shard.send() for discord.js, client.ws.send() for eris, and so on.

Discord sends two events when a bot joins a voice channel: VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE. Discolink needs to forward them to your node to establish a voice connection:

discord.js
// 'rawWS' for eris
client.on("raw", (payload) => player.voices.handleDispatch(payload));

handleDispatch also processes the Discord READY event. With autoInit: true (the default), this calls player.init() automatically. If you prefer explicit control, turn it off and initialize manually:

client.once("ready", () => player.init(client.user!.id));
const command = new SlashCommandBuilder()
.setName("play")
.setDescription("Play a song")
.addStringOption((option) => option.setName("query").setDescription("The song to play").setRequired(true));
// register the command globally
client.on("ready", async () => {
await player.init(client.user!.id);
await client.application!.commands.set([command]);
});
client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName !== "play") return;
const query = interaction.options.getString("query");
const guildId = interaction.guildId;
const voiceId = interaction.member.voice.channelId;
await player.play(query, { guildId, voiceId });
});

Player extends EventEmitter. At minimum, handle trackStart and queueFinish:

player.on("trackStart", (queue, track) => {
console.log(`${track.title} [${track.formattedDuration}]`);
});
player.on("queueFinish", (queue) => {
player.voices.destroy(queue.guildId, "Queue finished");
});
import { Client, GatewayIntentBits, SlashCommandBuilder } from "discord.js";
import { Player } from "discolink";
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
});
const node = {
name: "main",
origin: "http://localhost:2333",
password: "youshallnotpass",
};
const player = new Player({
nodes: [node],
async forwardVoiceUpdate(guildId, payload) {
client.guilds.cache.get(guildId)?.shard.send(payload);
},
});
const command = new SlashCommandBuilder()
.setName("play")
.setDescription("Play a song")
.addStringOption((option) => option.setName("query").setDescription("The song to play").setRequired(true));
player.on("trackStart", (queue, track) => {
console.log(`${track.title} [${track.formattedDuration}]`);
});
player.on("queueFinish", (queue) => {
player.voices.destroy(queue.guildId, "Queue finished");
});
client.on("raw", (payload) => player.voices.handleDispatch(payload));
client.on("ready", async () => {
await player.init(client.user!.id);
await client.application!.commands.set([command]);
});
client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName !== "play") return;
const query = interaction.options.getString("query");
const guildId = interaction.guildId;
const voiceId = interaction.member.voice.channelId;
await player.play(query, { guildId, voiceId });
});
client.login("YOUR_BOT_TOKEN");