-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Persistent queue & player resuming on restart
- Loading branch information
Showing
17 changed files
with
456 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Rhea.Models; | ||
|
||
public class PlayingTrack(EnrichedTrack track, ulong channelID, TimeSpan position = default) | ||
{ | ||
public EnrichedTrack track { get; set; } = track; | ||
public TimeSpan position { get; set; } = position; | ||
public ulong channelID { get; set; } = channelID; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,4 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
|
||
namespace Rhea.Models; | ||
namespace Rhea.Models; | ||
|
||
public class SMPacket | ||
{ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
using System.Collections; | ||
using Lavalink4NET.Players.Queued; | ||
using Rhea.Services; | ||
|
||
namespace Rhea.Models; | ||
|
||
public class TrackQueue(RedisService redis, ulong ID) : ITrackQueue | ||
{ | ||
public IEnumerator<ITrackQueueItem> GetEnumerator() => redis.GetQueue(ID).GetEnumerator(); | ||
|
||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | ||
|
||
public int Count => redis.GetQueue(ID).Count; | ||
|
||
public ITrackQueueItem this[int index] => redis.GetQueue(ID)[index]; | ||
|
||
public bool Contains(ITrackQueueItem item) => redis.GetQueue(ID).Contains(item); | ||
|
||
public int IndexOf(ITrackQueueItem item) => redis.GetQueue(ID).IndexOf((EnrichedTrack)item); | ||
|
||
public int IndexOf(Func<ITrackQueueItem, bool> predicate) | ||
{ | ||
var result = redis.GetQueue(ID); | ||
|
||
for (var index = 0; index < result.Count; index++) | ||
{ | ||
if (predicate(result[index])) | ||
{ | ||
return index; | ||
} | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
public async ValueTask<bool> RemoveAtAsync(int index, CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
if (index < 0 || index >= result.Count) return false; | ||
|
||
result.RemoveAt(index); | ||
await redis.SetQueueAsync(ID, result); | ||
return true; | ||
} | ||
|
||
public async ValueTask<bool> RemoveAsync(ITrackQueueItem item, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
var removed = result.Remove((EnrichedTrack)item); | ||
await redis.SetQueueAsync(ID, result); | ||
return removed; | ||
} | ||
|
||
public async ValueTask<int> RemoveAllAsync(Predicate<ITrackQueueItem> predicate, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
var previousCount = result.Count; | ||
result.RemoveAll(predicate); | ||
await redis.SetQueueAsync(ID, result); | ||
return previousCount - result.Count; | ||
} | ||
|
||
public async ValueTask RemoveRangeAsync(int index, int count, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
result.RemoveRange(index, count); | ||
await redis.SetQueueAsync(ID, result); | ||
} | ||
|
||
public async ValueTask<int> DistinctAsync(IEqualityComparer<ITrackQueueItem>? equalityComparer = null, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
=> throw new NotImplementedException(); | ||
|
||
public async ValueTask<int> AddAsync(ITrackQueueItem item, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
result.Add((EnrichedTrack)item); | ||
await redis.SetQueueAsync(ID, result); | ||
return result.Count; | ||
} | ||
|
||
public async ValueTask<int> AddRangeAsync(IReadOnlyList<ITrackQueueItem> items, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
result.AddRange((List<EnrichedTrack>)items); | ||
await redis.SetQueueAsync(ID, result); | ||
return result.Count; | ||
} | ||
|
||
public async ValueTask<int> ClearAsync(CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
await redis.ClearQueueAsync(ID); | ||
return 0; | ||
} | ||
|
||
public bool IsEmpty => Count is 0; | ||
|
||
public async ValueTask InsertAsync(int index, ITrackQueueItem item, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
result.Insert(index, (EnrichedTrack)item); | ||
await redis.SetQueueAsync(ID, result); | ||
} | ||
|
||
public async ValueTask InsertRangeAsync(int index, IEnumerable<ITrackQueueItem> items, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
result.InsertRange(index, (List<EnrichedTrack>)items); | ||
await redis.SetQueueAsync(ID, result); | ||
} | ||
|
||
public async ValueTask ShuffleAsync(CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
for (var index = 0; index < result.Count; index++) | ||
{ | ||
var targetIndex = index + Random.Shared.Next(result.Count - index); | ||
(result[index], result[targetIndex]) = (result[targetIndex], result[index]); | ||
} | ||
|
||
await redis.SetQueueAsync(ID, result); | ||
} | ||
|
||
public ITrackQueueItem? Peek() | ||
{ | ||
var result = redis.GetQueue(ID); | ||
return result.FirstOrDefault(); | ||
} | ||
|
||
public bool TryPeek(out ITrackQueueItem? queueItem) | ||
{ | ||
queueItem = Peek(); | ||
return queueItem is not null; | ||
} | ||
|
||
public async ValueTask<ITrackQueueItem?> TryDequeueAsync(TrackDequeueMode dequeueMode = TrackDequeueMode.Normal, | ||
CancellationToken cancellationToken = new CancellationToken()) | ||
{ | ||
var result = await redis.GetQueueAsync(ID); | ||
if (!result.Any()) return null; | ||
|
||
var index = dequeueMode is TrackDequeueMode.Shuffle | ||
? Random.Shared.Next(0, result.Count) | ||
: 0; | ||
|
||
var track = result[index]; | ||
result.RemoveAt(index); | ||
await redis.SetQueueAsync(ID, result); | ||
|
||
return track; | ||
} | ||
|
||
public ITrackHistory? History => null; | ||
public bool HasHistory => false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,41 @@ | ||
using Discord; | ||
using Discord.Interactions; | ||
using Discord.Interactions; | ||
using Discord.WebSocket; | ||
using Lavalink4NET; | ||
using Lavalink4NET.DiscordNet; | ||
using Lavalink4NET.Players; | ||
using Lavalink4NET.Players.Vote; | ||
using Rhea.Models; | ||
using Rhea.Services; | ||
|
||
namespace Rhea.Modules; | ||
|
||
public class BaseModule : InteractionModuleBase<SocketInteractionContext> | ||
public class BaseModule(IAudioService lavalink, RedisService redis) : InteractionModuleBase<SocketInteractionContext> | ||
{ | ||
private readonly IAudioService lavalink; | ||
|
||
protected BaseModule(IAudioService lavalink) | ||
{ | ||
this.lavalink = lavalink; | ||
} | ||
|
||
protected async ValueTask<VoteLavalinkPlayer?> GetPlayer(PlayerChannelBehavior joinBehavior = PlayerChannelBehavior.Join) | ||
protected async ValueTask<VoteLavalinkPlayer?> GetPlayer( | ||
PlayerChannelBehavior joinBehavior = PlayerChannelBehavior.Join) | ||
{ | ||
var member = Context.Guild.GetUser(Context.User.Id); | ||
var permissions = Context.Guild.CurrentUser.GetPermissions(member.VoiceChannel); | ||
if (!permissions.Connect) | ||
{ | ||
throw new Exception($"Unable to connect to {member.VoiceChannel.Mention}"); | ||
} | ||
|
||
var result = await lavalink.Players.RetrieveAsync(Context, playerFactory: PlayerFactory.Vote, new PlayerRetrieveOptions(joinBehavior)); | ||
if (!result.IsSuccess && result.Status != PlayerRetrieveStatus.BotNotConnected) throw new Exception($"Unable to retrieve player: {result.Status}"); | ||
|
||
var result = await lavalink.Players.RetrieveAsync(Context, PlayerFactory.Vote, new VoteLavalinkPlayerOptions | ||
{ | ||
TrackQueue = new TrackQueue(redis, Context.Guild.Id) | ||
}, | ||
new PlayerRetrieveOptions(joinBehavior)); | ||
if (!result.IsSuccess && result.Status != PlayerRetrieveStatus.BotNotConnected) | ||
throw new Exception($"Unable to retrieve player: {result.Status}"); | ||
return result.Player; | ||
} | ||
|
||
protected string FormatTime(TimeSpan time) | ||
=> time.ToString(@"hh\:mm\:ss").TrimStart('0', ':'); | ||
|
||
protected bool IsPrivileged(SocketGuildUser Member) | ||
=> Member.GetPermissions(Member.VoiceChannel).MoveMembers || Member.Roles.FirstOrDefault(role => role.Name.ToLower() == "dj") != null || | ||
=> Member.GetPermissions(Member.VoiceChannel).MoveMembers || | ||
Member.Roles.FirstOrDefault(role => role.Name.ToLower() == "dj") != null || | ||
!Member.VoiceChannel.ConnectedUsers.Any(user => !user.IsBot && user.Id != Member.Id); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.