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
- Yarn
- pnpm
npm i lavalink-client
yarn add lavalink-client
pnpm add 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
- View in Official lavalink-client Documentation.
Event Name | Description |
---|---|
trackStart | Emitted whenever a Track plays. |
trackEnd | Emitted whenever a track finished playing. |
trackStuck | Emitted whenever a Track got stuck while playing. |
trackError | Emitted whenever a Track errored. |
queueEnd | Emitted when the track Ended, but there are no more tracks in the queue. (trackEnd , does NOT get executed) |
playerCreate | Emitted whenever a Player gets created. |
playerMove | Emitted whenever a Player gets moved between Voice Channels. |
playerDisconnect | Emitted whenever a player is disconnected from a channel. |
playerSocketClose | Emitted whenever a Node-Socket got closed for a specific Player. |
playerDestroy | Emitted whenever a Player got destroyed. |
playerUpdate | Emitted whenever a Player gets an update from Lavalink's playerUpdate Event. |
playerMuteChange | Emitted whenever Player's voice state related to muting is changed. |
playerDeafChange | Emitted whenever Player's voice state related to deafing is changed. |
playerSuppressChange | Emitted whenever Player's voice state related to supressing is changed. |
playerQueueEmptyStart | Emitted whenever the Queue empty handler started (timeout). |
playerQueueEmptyEnd | Emitted whenever the Queue empty handler finished (successfully) and destroyed the player. |
playerQueueEmptyCancel | Emitted whenever the Queue empty handler cancelled (e.g. because a new track got added). |
playerVoiceJoin | Emitted whenever a user joins the player's voice channel. |
playerVoiceLeave | Emitted whenever a user leaves the player's voice channel. |
debug | Emitted for several erros, and logs within lavalink-client, if managerOptions.advancedOptions.enableDebugEvents is true . |
SponsorBlock Plugin Events
- This Events is a part of LavalinkManager Events.
- This requires the SponsorBlock Plugin added in Lavalink Server.
Event Name | Description |
---|---|
SegmentsLoaded | Emitted whenever Segments are loaded. |
SegmentSkipped | Emitted whenever a specific Segment was skipped. |
ChapterStarted | Emitted whenever a specific Chapter starts playing. |
ChaptersLoaded | Emitted whenever Chapters are loaded. |
LavaLyrics Plugin Events
- This Events is a part of LavalinkManager Events.
- This requires the LavaLyrics Plugin added in Lavalink Server.
Event Name | Description |
---|---|
LyricsLine | Emitted whenever a Lyrics line is received. |
LyricsFound | Emitted whenever a Lyrics is found. |
LyricsNotFound | Emitted whenever a Lyrics is not found. |
NodeManager Events
- View in Official lavalink-client Documentation.
Event Name | Description |
---|---|
create | Emitted whenever a Node is created. |
destroy | Emitted whenever a Node is destroyed. |
connect | Emitted whenever a Node is connected. |
reconnecting | Emitted whenever a Node is reconnecting. |
reconnectinprogress | Emitted 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. |
disconnect | Emitted whenever a Node is disconnects. |
error | Emitted whenever a Node is error. |
raw | Emits 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 |
---|---|---|
LavalinkManager | lavalinkManager | Lavalink Manager |
NodeManager | lavalinkManager.nodeManager | Node Manager |
PlayerManager | lavalinkManager (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 })),
);
}
}