mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
323 lines
9.1 KiB
C#
323 lines
9.1 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace NadekoBot.Core.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 ValueTask<string?> GetStreamUrl() => TrackInfo.GetStreamUrl();
|
|
}
|
|
}
|
|
|
|
public sealed partial class MusicQueue : IMusicQueue
|
|
{
|
|
private LinkedList<QueuedTrackInfo> _tracks;
|
|
|
|
public int Index
|
|
{
|
|
get
|
|
{
|
|
// just make sure the internal logic runs first
|
|
// to make sure that some potential indermediate value is not returned
|
|
lock (locker)
|
|
{
|
|
return _index;
|
|
}
|
|
}
|
|
}
|
|
|
|
private int _index;
|
|
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
lock (locker)
|
|
{
|
|
return _tracks.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly object locker = new object();
|
|
|
|
public MusicQueue()
|
|
{
|
|
_index = 0;
|
|
_tracks = new LinkedList<QueuedTrackInfo>();
|
|
}
|
|
|
|
public IQueuedTrackInfo Enqueue(ITrackInfo trackInfo, string queuer, out int index)
|
|
{
|
|
lock (locker)
|
|
{
|
|
var added = new QueuedTrackInfo(trackInfo, queuer);
|
|
index = _tracks.Count;
|
|
_tracks.AddLast(added);
|
|
return added;
|
|
}
|
|
}
|
|
|
|
public IQueuedTrackInfo EnqueueNext(ITrackInfo trackInfo, string queuer, out int index)
|
|
{
|
|
lock (locker)
|
|
{
|
|
if (_tracks.Count == 0)
|
|
{
|
|
return Enqueue(trackInfo, queuer, out index);
|
|
}
|
|
|
|
LinkedListNode<QueuedTrackInfo> 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);
|
|
index = i;
|
|
|
|
_tracks.AddAfter(currentNode, added);
|
|
|
|
return added;
|
|
}
|
|
}
|
|
|
|
public void EnqueueMany(IEnumerable<ITrackInfo> tracks, string queuer)
|
|
{
|
|
lock (locker)
|
|
{
|
|
foreach (var track in tracks)
|
|
{
|
|
var added = new QueuedTrackInfo(track, queuer);
|
|
_tracks.AddLast(added);
|
|
}
|
|
}
|
|
}
|
|
|
|
public IReadOnlyCollection<IQueuedTrackInfo> List()
|
|
{
|
|
lock (locker)
|
|
{
|
|
return _tracks.ToList();
|
|
}
|
|
}
|
|
|
|
public IQueuedTrackInfo? GetCurrent(out int index)
|
|
{
|
|
lock (locker)
|
|
{
|
|
index = _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 index)
|
|
{
|
|
lock (locker)
|
|
{
|
|
if (index < 0 || index >= _tracks.Count)
|
|
return false;
|
|
|
|
_index = index;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private void RemoveAtInternal(int index, out IQueuedTrackInfo trackInfo)
|
|
{
|
|
var removedNode = _tracks.First!;
|
|
int i;
|
|
for (i = 0; i < index; 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)
|
|
{
|
|
if (from < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(from));
|
|
if (to < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(to));
|
|
if (to == from)
|
|
throw new ArgumentException($"{nameof(from)} and {nameof(to)} must be different");
|
|
|
|
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.ToList();
|
|
|
|
for (var i = 0; i < list.Count; i++)
|
|
{
|
|
var struck = rng.Next(i, list.Count);
|
|
var temp = list[struck];
|
|
list[struck] = list[i];
|
|
list[i] = temp;
|
|
|
|
// could preserving the index during shuffling be done better?
|
|
if (i == _index)
|
|
_index = struck;
|
|
else if (struck == _index)
|
|
_index = i;
|
|
}
|
|
|
|
_tracks = new LinkedList<QueuedTrackInfo>(list);
|
|
}
|
|
}
|
|
|
|
public bool IsLast()
|
|
{
|
|
lock (locker)
|
|
{
|
|
return _index == _tracks.Count // if there are no tracks
|
|
|| _index == _tracks.Count - 1;
|
|
}
|
|
}
|
|
|
|
public bool TryRemoveAt(int index, out IQueuedTrackInfo? trackInfo, out bool isCurrent)
|
|
{
|
|
lock (locker)
|
|
{
|
|
isCurrent = false;
|
|
trackInfo = null;
|
|
|
|
if (index < 0 || index >= _tracks.Count)
|
|
return false;
|
|
|
|
if (index == _index)
|
|
{
|
|
isCurrent = true;
|
|
}
|
|
|
|
RemoveAtInternal(index, out trackInfo);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} |