Skip to main content

Lavalink

Transform your bot into a professional DJ with the power of the Lavalink ecosystem. This package uses lavalink-client behind the scenes, providing a high-performance and efficient solution for managing audio streams on Discord. By leveraging Lavalink, your bot gains the ability to manage audio playback, queues, and real-time controls with minimal latency, transforming it into a fully capable and professional music system.

Installation

npm i lavalink-client

Usage

Once the installation process is complete, we can import the NestCordLavalinkModule with your NestCordModule into the root AppModule:

app.module.ts
import { NestCordLavalinkModule } from '@globalart/nestcord';
import { Module } from '@nestjs/common';
import { Client } from 'discord.js';
import { AppService } from './app.service';

@Module({
imports: [
NestCordModule.forRoot({
token: process.env.DISCORD_TOKEN,
intents: [
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildVoiceStates
],
}),
NestCordLavalinkModule.forRoot({
// At least 1 node is required
nodes: [
{
authorization: 'youshallnotpass',
host: '127.0.0.1',
port: 2333,
}
]
})
],
providers: [AppService]
})
export class AppModule {}

Checkout more Module options in the official lavalink-client Documentation.

Listeners

app.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { Context, OnLavalinkManager, OnNodeManager, LavalinkManagerContextOf, NodeManagerContextOf } from '@globalart/nestcord';

@Injectable()
export class AppService {
private readonly logger = new Logger(AppService.name);

@OnNodeManager('connect')
public onConnect(@Context() [node]: NodeManagerContextOf<'connect'>) {
this.logger.log(`Node: ${node.options.id} Connected`);
}

@OnLavalinkManager('playerCreate')
public onPlayerCreate(@Context() [player]: LavalinkManagerContextOf<'playerCreate'>) {
this.logger.log(`Player created at ${player.guildId}`);
}
}

LavalinkManager Events

Event NameDescription
trackStartEmitted whenever a Track plays.
trackEndEmitted whenever a track finished playing.
trackStuckEmitted whenever a Track got stuck while playing.
trackErrorEmitted whenever a Track errored.
queueEndEmitted when the track Ended, but there are no more tracks in the queue. (trackEnd, does NOT get executed)
playerCreateEmitted whenever a Player gets created.
playerMoveEmitted whenever a Player gets moved between Voice Channels.
playerDisconnectEmitted whenever a player is disconnected from a channel.
playerSocketCloseEmitted whenever a Node-Socket got closed for a specific Player.
playerDestroyEmitted whenever a Player got destroyed.
playerUpdateEmitted whenever a Player gets an update from Lavalink's playerUpdate Event.
playerMuteChangeEmitted whenever Player's voice state related to muting is changed.
playerDeafChangeEmitted whenever Player's voice state related to deafing is changed.
playerSuppressChangeEmitted whenever Player's voice state related to supressing is changed.
playerQueueEmptyStartEmitted whenever the Queue empty handler started (timeout).
playerQueueEmptyEndEmitted whenever the Queue empty handler finished (successfully) and destroyed the player.
playerQueueEmptyCancelEmitted whenever the Queue empty handler cancelled (e.g. because a new track got added).
playerVoiceJoinEmitted whenever a user joins the player's voice channel.
playerVoiceLeaveEmitted whenever a user leaves the player's voice channel.
debugEmitted for several erros, and logs within lavalink-client, if managerOptions.advancedOptions.enableDebugEvents is true.

SponsorBlock Plugin Events

Event NameDescription
SegmentsLoadedEmitted whenever Segments are loaded.
SegmentSkippedEmitted whenever a specific Segment was skipped.
ChapterStartedEmitted whenever a specific Chapter starts playing.
ChaptersLoadedEmitted whenever Chapters are loaded.

LavaLyrics Plugin Events

Event NameDescription
LyricsLineEmitted whenever a Lyrics line is received.
LyricsFoundEmitted whenever a Lyrics is found.
LyricsNotFoundEmitted whenever a Lyrics is not found.

NodeManager Events

Event NameDescription
createEmitted whenever a Node is created.
destroyEmitted whenever a Node is destroyed.
connectEmitted whenever a Node is connected.
reconnectingEmitted whenever a Node is reconnecting.
reconnectinprogressEmitted whenever a node starts to reconnect. (if you have a reconnection delay, the reconnecting event will be emitted after the retryDelay.) Useful to check wether the internal node reconnect system works or not.
disconnectEmitted whenever a Node is disconnects.
errorEmitted whenever a Node is error.
rawEmits every single Node event.

Providers

app.service.ts
import { Injectable } from '@nestjs/common';
import { LavalinkManager, NodeManager } from 'lavalink-client';

@Injectable()
export class AppService {
public constructor(
private readonly lavalinkManager: LavalinkManager,
private readonly nodeManager: NodeManager,
) {}
}
Class (Type to be injected)Manager Property (Will access to)Description
LavalinkManagerlavalinkManagerLavalink Manager
NodeManagerlavalinkManager.nodeManagerNode Manager
PlayerManagerlavalinkManager (player functions)Player Manager

Play Tracks

app.commands.ts
import { Injectable, UseInterceptors } from '@nestjs/common';
import { NestCordLavalinkService, PlayerManager } from 'lavalink-client';
import { Context, Options, SlashCommand, SlashCommandContext } from '@globalart/nestcord';
import { QueryDto } from './query.dto';
import { SourceAutocompleteInterceptor } from 'source.autocomplete';

@Injectable()
export class AppCommands {
public constructor(
private readonly playerManager: PlayerManager,
private readonly lavalinkService: NestCordLavalinkService
) {}

@UseInterceptors(SourceAutocompleteInterceptor)
@SlashCommand({
name: 'play',
description: 'play a track',
})
public async onPlay(
@Context() [interaction]: SlashCommandContext,
@Options() { query, source }: QueryDto,
) {
const player =
this.playerManager.get(interaction.guild.id) ??
this.playerManager.create({
...this.lavalinkService.extractInfoForPlayer(interaction),
// optional configurations:
selfDeaf: true,
selfMute: false,
volume: 100,
});

await player.connect();

const res = await player.search(
{
query,
source: source ?? 'soundcloud'
},
interaction.user.id,
);

await player.queue.add(res.tracks[0]);
if (!player.playing) await player.play();

// It's extremely recommended to use `trackStart` event for this announcement
return interaction.reply({
content: `Now playing ${res.tracks[0].info.title}`,
});
}
}
query.dto.ts
import { SearchPlatform } from 'lavalink-client';
import { StringOption } from '@globalart/nestcord';

export class QueryDto {
@StringOption({
name: 'query',
description: '<name | url> of the requested track'
required: true
})
public readonly query!: string;

@StringOption({
name: 'source',
description: 'source of the track',
autocomplete: true,
required: false,
})
public readonly source?: SourcePlatform;
}
source.autocomplete.ts
import { Injectable } from '@nestjs/common';
import { AutocompleteInteraction } from 'discord.js';
import { DefaultSources } from 'lavalink-client';
import { AutocompleteInterceptor } from '@globalart/nestcord';

@Injectable()
export class SourceAutocompleteInterceptor extends AutocompleteInterceptor {
public transformOptions(interaction: AutocompleteInteraction) {
const focused = interaction.options.getFocused(true);
let choices: string[];

if (focused.name === 'source') {
choices = [DefaultSources.soundcloud] // Note that some Sources needs extra plugins/configuration to property work
}

return interaction.respond(
choices
.filter((choice) => choice.startsWith(focused.value.toString()))
.map((choice) => ({ name: choice, value: choice })),
);
}
}