Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance queue management and interaction response #617

Merged
merged 19 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions DisCatSharp.ApplicationCommands/Context/BaseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ public Task CreateInteractionIframeResponseAsync(string customId, string title,
public Task<DiscordMessage> EditResponseAsync(DiscordWebhookBuilder builder)
=> this.Interaction.EditOriginalResponseAsync(builder);

/// <summary>
/// Edits the interaction response.
/// </summary>
/// <param name="content">The content to edit the response with.</param>
/// <returns></returns>
public Task<DiscordMessage> EditResponseAsync(string content)
=> this.Interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent(content));

/// <summary>
/// Deletes the interaction response.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
<PackageReference Include="Microsoft.DependencyValidation.Analyzers" Version="0.11.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions DisCatSharp.CommandsNext/DisCatSharp.CommandsNext.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@
</PackageReference>
<PackageReference Include="DisCatSharp.Attributes" Version="10.6.6" />
<PackageReference Include="Microsoft.DependencyValidation.Analyzers" Version="0.11.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.1" />
</ItemGroup>

</Project>
5 changes: 4 additions & 1 deletion DisCatSharp.Common/DisCatSharp.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</PropertyGroup>

<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<PackageReference Include="Backport.System.Threading.Lock" Version="3.1.1" />
<PackageReference Include="Backport.System.Threading.Lock" Version="3.1.4" />
</ItemGroup>

<ItemGroup>
Expand All @@ -36,11 +36,14 @@
</PackageReference>
<PackageReference Include="DisCatSharp.Attributes" Version="10.6.6" />
<PackageReference Include="Microsoft.DependencyValidation.Analyzers" Version="0.11.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" />
<PackageReference Include="Microsoft.Maui.Graphics" Version="9.0.30" />
<PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="9.0.30" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Collections.Immutable" Version="9.0.1" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.1" />
<PackageReference Include="System.Memory" Version="4.6.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
Expand Down
4 changes: 4 additions & 0 deletions DisCatSharp.Configuration/DisCatSharp.Configuration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@
<PackageReference Include="Microsoft.DependencyValidation.Analyzers" Version="0.11.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ General config:
```yml
lavalink:
plugins:
- dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.0.1"
repository: "https://maven.topi.wtf/releases"
- dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.3.0"
repository: "https://maven.lavalink.dev/releases"
[..]
plugins:
lavasrc:
Expand Down Expand Up @@ -136,4 +136,4 @@ LavalinkLoadResult result = await guildPlayer.LoadTracksAsync(type, query);

## Conclusion

This is the most basic example of how to use the new Lavalink features. You can find more information in the Lavalink plugin docs: https://github.com/topi314/LavaSrc#usage
This is the most basic example of how to use the new Lavalink features. You can find more information in the Lavalink plugin docs: https://github.com/topi314/LavaSrc?tab=readme-ov-file#lavalink-usage
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ We are now ready to start the bot. If everything is configured properly, you sho
And a client connection appear in your Lavalink console:

```yml
2023-06-25 09:05:28.757 INFO 4436 --- [ XNIO-1 task-1] io.undertow.servlet : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-06-25 09:05:28.758 INFO 4436 --- [ XNIO-1 task-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2023-06-25 09:05:28.760 INFO 4436 --- [ XNIO-1 task-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
2023-06-25 09:05:28.861 INFO 4436 --- [ XNIO-1 task-1] lavalink.server.io.RequestLoggingFilter : GET /version?trace=false, client=127.0.0.1
2023-06-25 09:05:28.876 INFO 4436 --- [ XNIO-1 task-1] l.server.io.HandshakeInterceptorImpl : Incoming connection from /127.0.0.1:54649
2023-06-25 09:05:28.918 INFO 4436 --- [ XNIO-1 task-1] lavalink.server.io.RequestLoggingFilter : GET /v4/websocket, client=127.0.0.1
2023-06-25 09:05:29.048 INFO 4436 --- [ XNIO-1 task-1] lavalink.server.io.SocketServer : Connection successfully established from DisCatSharp.Lavalink/10.4.1
INFO 4436 --- [ XNIO-1 task-1] io.undertow.servlet : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO 4436 --- [ XNIO-1 task-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
INFO 4436 --- [ XNIO-1 task-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
INFO 4436 --- [ XNIO-1 task-1] lavalink.server.io.RequestLoggingFilter : GET /version?trace=false, client=127.0.0.1
INFO 4436 --- [ XNIO-1 task-1] l.server.io.HandshakeInterceptorImpl : Incoming connection from /127.0.0.1:54649
INFO 4436 --- [ XNIO-1 task-1] lavalink.server.io.RequestLoggingFilter : GET /v4/websocket, client=127.0.0.1
INFO 4436 --- [ XNIO-1 task-1] lavalink.server.io.SocketServer : Connection successfully established from DisCatSharp.Lavalink/10.6.6+38c8062e7f88b2e9920a535637d4f3afccbbf205
```

We are now ready to set up some music commands!
38 changes: 38 additions & 0 deletions DisCatSharp.Docs/articles/modules/audio/lavalink_v4/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
uid: modules_audio_lavalink_v4_docker
title: Lavalink V4 Docker
author: DisCatSharp Team
---

# Template for running Lavalink in Docker

- Create a folder where you'd save the lavalink config and plugins. I.e. `mkdir /opt/lavalink`.
- Follow the [Setup](xref:modules_audio_lavalink_v4_setup) instructions to create your `application.yml`. Make sure to set `0.0.0.0` as `address`.
- Save the `application.yml` in the created folder.
- Create a `docker-compose.yml` file like this and save it in the created folder:
```yml
services:
lavalink:
image: ghcr.io/lavalink-devs/lavalink:latest
container_name: lavalink
restart: unless-stopped
environment:
- _JAVA_OPTIONS=-Xmx6G
- SERVER_PORT=2333
volumes:
- ./application.yml:/opt/Lavalink/application.yml
- ./plugins/:/opt/Lavalink/plugins/
networks:
- lavalink
expose:
- 2333
ports:
- "2333:2333"
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
lavalink:
name: lavalink
```
- Create a folder called `plugins` in the created folder.
- Run `docker compose up -d` to start lavalink
190 changes: 190 additions & 0 deletions DisCatSharp.Docs/articles/modules/audio/lavalink_v4/queue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
---
uid: modules_audio_lavalink_v4_queue_system
title: Lavalink V4 Queue System
author: DisCatSharp Team
---

# Lavalink Guild Player Queue System

The Lavalink module provides a built-in queue system for managing audio tracks. The queue system allows you to add, remove, and manipulate tracks in a guild-specific queue.

## Enabling the Queue System
The queue system is disabled by default, To use the queue system, you must enable it in your [`LavalinkConfiguration`](xref:DisCatSharp.Lavalink.LavalinkConfiguration):

```cs
var config = new LavalinkConfiguration
{
EnableBuiltInQueueSystem = true
};
```
## Basic Queue Operations
### Adding Tracks
You can add individual tracks or entire playlists to the queue:

```cs
// Add a single track
guildPlayer.AddToQueue(track);

// Add all tracks from a playlist
var loadResult = await guildPlayer.LoadTracksAsync(LavalinkSearchType.Youtube, query);
var playlist = loadResult.GetResultAs<LavalinkPlaylist>()
guildPlayer.AddToQueue(playlist);
```

### Playing the Queue
To start playing tracks from the queue:
```cs
guildPlayer.PlayQueue();
```

The finished commands should look like so:
```cs
[SlashCommand("play", "Play a track")]
public async Task PlayAsync(InteractionContext ctx, [Option("query", "The query to search for")] string query)
{
await ctx.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource);
if (ctx.Member.VoiceState == null || ctx.Member.VoiceState.Channel == null)
{
await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("You are not in a voice channel."));
return;
}

var lavalink = ctx.Client.GetLavalink();
var guildPlayer = lavalink.GetGuildPlayer(ctx.Guild);

if (guildPlayer == null)
{
await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent("Lavalink is not connected."));
return;
}

var loadResult = await guildPlayer.LoadTracksAsync(LavalinkSearchType.Youtube, query);

if (loadResult.LoadType == LavalinkLoadResultType.Empty || loadResult.LoadType == LavalinkLoadResultType.Error)
{
await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"Track search failed for {query}."));
return;
}

LavalinkTrack track = loadResult.LoadType switch
{
LavalinkLoadResultType.Track => loadResult.GetResultAs<LavalinkTrack>(),
LavalinkLoadResultType.Playlist => loadResult.GetResultAs<LavalinkPlaylist>().Tracks.First(),
LavalinkLoadResultType.Search => loadResult.GetResultAs<List<LavalinkTrack>>().First(),
_ => throw new InvalidOperationException("Unexpected load result type.")
};

if (guildPlayer.CurrentTrack == null)
{
guildPlayer.AddToQueue(track);
guildPlayer.PlayQueue();
await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"Now playing {query}!"));
}
else
{
guildPlayer.AddToQueue(track);
await ctx.EditResponseAsync(new DiscordWebhookBuilder().WithContent($"Track added to queue {track.Info.Title}!"));
}
}
```
### Skipping the Current Track
To skip the currently playing track, you can use the `SkipAsync` method of the `LavalinkGuildPlayer` class. This method will skip the current track. If the queue system is enabled and there are more tracks in the queue, the next track will automatically start playing.
```cs
await guildPlayer.SkipAsync();
```

### Managing the Queue

The queue system provides several other methods for managing tracks:
```cs
// See list queue
guildPlayer.Queue;

// Remove a specific track
guildPlayer.RemoveQueue(track);

// Remove a track by its title/identifier
guildPlayer.RemoveQueue("song title");

// Clear all tracks from the queue
guildPlayer.ClearQueue();

// Shuffle the queue
guildPlayer.ShuffleQueue();

// Reverse the queue order
guildPlayer.ReverseQueue();

...
```
## Queue Entry Pipeline
The Lavalink queue system includes a powerful pipeline system that allows you to execute custom actions before and after track playback. This is implemented through the `IQueueEntry` interface.
### Queue Entry Lifecycle
Each track in the queue goes through the following pipeline stages:
1. **Before Playing**: Called right before a track starts playing
2. **Track Playback**: The actual track playback
3. **After Playing**: Called after track playback completes

### Creating Custom Queue Entries
To create a custom queue entry, implement the `IQueueEntry` interface:

```cs
public class CustomQueueEntry : IQueueEntry
{
public LavalinkTrack Track { get; set; }

public async Task<bool> BeforePlayingAsync(LavalinkGuildPlayer player)
{
// Execute code before the track plays
// Return false to skip this track
return true;
}

public async Task AfterPlayingAsync(LavalinkGuildPlayer player)
{
// Execute code after the track finishes playing
}
}
```

### Configuring Custom Queue Entries
To use custom queue entries, configure them in your `LavalinkConfiguration`:
```cs
var config = new LavalinkConfiguration
{
EnableBuiltInQueueSystem = true,
QueueEntryFactory = () => new CustomQueueEntry()
};
```
or
```cs
services.AddTransient<IQueueEntry, CustomQueueEntry>();

var config = new LavalinkConfiguration
{
EnableBuiltInQueueSystem = true,
QueueEntryFactory = () => scope.ServiceProvider.GetRequiredService<IQueueEntry>
};
```
In case you use dependency injection in `CustomQueueEntry`

### Pipeline Flow Control
- The `BeforePlayingAsync` method can control whether a track should be played by returning `true` or `false`
- The `AfterPlayingAsync` method is called after the track finishes, allowing for cleanup or next-track preparation
- Both methods have access to the `LavalinkGuildPlayer` instance for advanced control

Example usage of flow control:
```cs
public async Task<bool> BeforePlayingAsync(LavalinkGuildPlayer player)
{
if (/* some condition */)
{
// Skip this track
return false;
}

// Play this track
await player.Channel.SendMessageAsync($"Track Started: {Track.Info.Title}");
return true;
}
```
Loading
Loading