forked from EllieBotDevs/elliebot
342 lines
No EOL
8.5 KiB
C#
342 lines
No EOL
8.5 KiB
C#
namespace EllieBot.Modules.Music;
|
|
|
|
public sealed partial class MusicQueue
|
|
{
|
|
private sealed class QueuedTrackInfo : IQueuedTrackInfo
|
|
{
|
|
public ITrackInfo TrackInfo { get; }
|
|
public string Queuer { get; }
|
|
|
|
public string Title
|
|
=> TrackInfo.Title;
|
|
|
|
public string Url
|
|
=> TrackInfo.Url;
|
|
|
|
public string Thumbnail
|
|
=> TrackInfo.Thumbnail;
|
|
|
|
public TimeSpan Duration
|
|
=> TrackInfo.Duration;
|
|
|
|
public MusicPlatform Platform
|
|
=> TrackInfo.Platform;
|
|
|
|
|
|
public QueuedTrackInfo(ITrackInfo trackInfo, string queuer)
|
|
{
|
|
TrackInfo = trackInfo;
|
|
Queuer = queuer;
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed partial class MusicQueue : IMusicQueue
|
|
{
|
|
public int Index
|
|
{
|
|
get
|
|
{
|
|
// just make sure the internal logic runs first
|
|
// to make sure that some potential intermediate value is not returned
|
|
lock (_locker)
|
|
{
|
|
return index;
|
|
}
|
|
}
|
|
}
|
|
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
lock (_locker)
|
|
{
|
|
return tracks.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
private LinkedList<QueuedTrackInfo> tracks;
|
|
|
|
private int index;
|
|
|
|
private readonly object _locker = new();
|
|
|
|
public MusicQueue()
|
|
{
|
|
index = 0;
|
|
tracks = new();
|
|
}
|
|
|
|
public IQueuedTrackInfo Enqueue(ITrackInfo trackInfo, string queuer, out int enqueuedAt)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
var added = new QueuedTrackInfo(trackInfo, queuer);
|
|
enqueuedAt = tracks.Count;
|
|
tracks.AddLast(added);
|
|
|
|
return added;
|
|
}
|
|
}
|
|
|
|
|
|
public IQueuedTrackInfo EnqueueNext(ITrackInfo trackInfo, string queuer, out int trackIndex)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
if (tracks.Count == 0)
|
|
return Enqueue(trackInfo, queuer, out trackIndex);
|
|
|
|
var currentNode = tracks.First!;
|
|
int i;
|
|
for (i = 1; i <= index; i++)
|
|
currentNode = currentNode.Next!; // can't be null because index is always in range of the count
|
|
|
|
var added = new QueuedTrackInfo(trackInfo, queuer);
|
|
trackIndex = i;
|
|
|
|
tracks.AddAfter(currentNode, added);
|
|
|
|
return added;
|
|
}
|
|
}
|
|
|
|
public void EnqueueMany(IEnumerable<ITrackInfo> toEnqueue, string queuer)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
foreach (var track in toEnqueue)
|
|
{
|
|
var added = new QueuedTrackInfo(track, queuer);
|
|
tracks.AddLast(added);
|
|
}
|
|
}
|
|
}
|
|
|
|
public IReadOnlyCollection<IQueuedTrackInfo> List()
|
|
{
|
|
lock (_locker)
|
|
{
|
|
return tracks.ToList();
|
|
}
|
|
}
|
|
|
|
public IQueuedTrackInfo? GetCurrent(out int currentIndex)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
currentIndex = index;
|
|
return tracks.ElementAtOrDefault(index);
|
|
}
|
|
}
|
|
|
|
public void Advance()
|
|
{
|
|
lock (_locker)
|
|
{
|
|
if (++index >= tracks.Count)
|
|
index = 0;
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
lock (_locker)
|
|
{
|
|
tracks.Clear();
|
|
}
|
|
}
|
|
|
|
public bool SetIndex(int newIndex)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
if (newIndex < 0 || newIndex >= tracks.Count)
|
|
return false;
|
|
|
|
index = newIndex;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private void RemoveAtInternal(int remoteAtIndex, out IQueuedTrackInfo trackInfo)
|
|
{
|
|
var removedNode = tracks.First!;
|
|
int i;
|
|
for (i = 0; i < remoteAtIndex; i++)
|
|
removedNode = removedNode.Next!;
|
|
|
|
trackInfo = removedNode.Value;
|
|
tracks.Remove(removedNode);
|
|
|
|
if (i <= index)
|
|
--index;
|
|
|
|
if (index < 0)
|
|
index = Count;
|
|
|
|
// if it was the last song in the queue
|
|
// // wrap back to start
|
|
// if (_index == Count)
|
|
// _index = 0;
|
|
// else if (i <= _index)
|
|
// if (_index == 0)
|
|
// _index = Count;
|
|
// else --_index;
|
|
}
|
|
|
|
public void RemoveCurrent()
|
|
{
|
|
lock (_locker)
|
|
{
|
|
if (index < tracks.Count)
|
|
RemoveAtInternal(index, out _);
|
|
}
|
|
}
|
|
|
|
public IQueuedTrackInfo? MoveTrack(int from, int to)
|
|
{
|
|
ArgumentOutOfRangeException.ThrowIfNegative(from);
|
|
ArgumentOutOfRangeException.ThrowIfNegative(to);
|
|
ArgumentOutOfRangeException.ThrowIfEqual(to, from);
|
|
|
|
lock (_locker)
|
|
{
|
|
if (from >= Count || to >= Count)
|
|
return null;
|
|
|
|
// update current track index
|
|
if (from == index)
|
|
{
|
|
// if the song being moved is the current track
|
|
// it means that it will for sure end up on the destination
|
|
index = to;
|
|
}
|
|
else
|
|
{
|
|
// moving a track from below the current track means
|
|
// means it will drop down
|
|
if (from < index)
|
|
index--;
|
|
|
|
// moving a track to below the current track
|
|
// means it will rise up
|
|
if (to <= index)
|
|
index++;
|
|
|
|
|
|
// if both from and to are below _index - net change is + 1 - 1 = 0
|
|
// if from is below and to is above - net change is -1 (as the track is taken and put above)
|
|
// if from is above and to is below - net change is 1 (as the track is inserted under)
|
|
// if from is above and to is above - net change is 0
|
|
}
|
|
|
|
// get the node which needs to be moved
|
|
var fromNode = tracks.First!;
|
|
for (var i = 0; i < from; i++)
|
|
fromNode = fromNode.Next!;
|
|
|
|
// remove it from the queue
|
|
tracks.Remove(fromNode);
|
|
|
|
// if it needs to be added as a first node,
|
|
// add it directly and return
|
|
if (to == 0)
|
|
{
|
|
tracks.AddFirst(fromNode);
|
|
return fromNode.Value;
|
|
}
|
|
|
|
// else find the node at the index before the specified target
|
|
var addAfterNode = tracks.First!;
|
|
for (var i = 1; i < to; i++)
|
|
addAfterNode = addAfterNode.Next!;
|
|
|
|
// and add after it
|
|
tracks.AddAfter(addAfterNode, fromNode);
|
|
return fromNode.Value;
|
|
}
|
|
}
|
|
|
|
public void Shuffle(Random rng)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
var list = tracks.ToArray();
|
|
rng.Shuffle(list);
|
|
tracks = new(list);
|
|
}
|
|
}
|
|
|
|
public bool IsLast()
|
|
{
|
|
lock (_locker)
|
|
{
|
|
return index == tracks.Count // if there are no tracks
|
|
|| index == tracks.Count - 1;
|
|
}
|
|
}
|
|
|
|
public void ReorderFairly()
|
|
{
|
|
lock (_locker)
|
|
{
|
|
var groups = new Dictionary<string, int>();
|
|
var queuers = new List<Queue<QueuedTrackInfo>>();
|
|
|
|
foreach (var track in tracks.Skip(index).Concat(tracks.Take(index)))
|
|
{
|
|
if (!groups.TryGetValue(track.Queuer, out var qIndex))
|
|
{
|
|
queuers.Add(new Queue<QueuedTrackInfo>());
|
|
qIndex = queuers.Count - 1;
|
|
groups.Add(track.Queuer, qIndex);
|
|
}
|
|
|
|
queuers[qIndex].Enqueue(track);
|
|
}
|
|
|
|
tracks = new LinkedList<QueuedTrackInfo>();
|
|
index = 0;
|
|
|
|
while (true)
|
|
{
|
|
for (var i = 0; i < queuers.Count; i++)
|
|
{
|
|
var queue = queuers[i];
|
|
tracks.AddLast(queue.Dequeue());
|
|
|
|
if (queue.Count == 0)
|
|
{
|
|
queuers.RemoveAt(i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
if (queuers.Count == 0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool TryRemoveAt(int remoteAt, out IQueuedTrackInfo? trackInfo, out bool isCurrent)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
isCurrent = false;
|
|
trackInfo = null;
|
|
|
|
if (remoteAt < 0 || remoteAt >= tracks.Count)
|
|
return false;
|
|
|
|
if (remoteAt == index)
|
|
isCurrent = true;
|
|
|
|
RemoveAtInternal(remoteAt, out trackInfo);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
} |