b23d1aa71c
fixed Rewind not restarting song https://cloud.tomatentum.net/apps/deck/#/board/19/card/241 added song queue prepend position https://cloud.tomatentum.net/apps/deck/#/board/19/card/224
568 lines
16 KiB
C#
568 lines
16 KiB
C#
|
|
|
|
using Lavalink4NET.Player;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using TomatenMusic.Music;
|
|
|
|
namespace TomatenMusicCore.Music.Entities
|
|
{
|
|
//
|
|
// Summary:
|
|
// A thread-safe queue for Lavalink4NET.Player.LavalinkTrack.
|
|
public sealed class TrackList : IList<TomatenMusicTrack>, ICollection<TomatenMusicTrack>, IEnumerable<TomatenMusicTrack>, IEnumerable, IPlayableItem
|
|
{
|
|
private readonly List<TomatenMusicTrack> _list;
|
|
|
|
private readonly object _syncRoot;
|
|
|
|
//
|
|
// Summary:
|
|
// Gets the number of queued tracks.
|
|
//
|
|
// Remarks:
|
|
// This property is thread-safe, so it can be used from multiple threads at once
|
|
// safely.
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
return _list.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Gets a value indicating whether the queue is empty.
|
|
//
|
|
// Remarks:
|
|
// This property is thread-safe, so it can be used from multiple threads at once
|
|
// safely.
|
|
public bool IsEmpty => Count == 0;
|
|
|
|
//
|
|
// Summary:
|
|
// Gets a value indicating whether the queue is read-only.
|
|
//
|
|
// Remarks:
|
|
// This property is thread-safe, so it can be used from multiple threads at once
|
|
// safely.
|
|
public bool IsReadOnly => false;
|
|
|
|
//
|
|
// Summary:
|
|
// Gets or sets the enqueued tracks.
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public IReadOnlyList<TomatenMusicTrack> Tracks
|
|
{
|
|
get
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
return _list.ToArray();
|
|
}
|
|
}
|
|
set
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
_list.Clear();
|
|
_list.AddRange(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public string Title => $"Track List with {Count} Tracks";
|
|
|
|
//
|
|
// Summary:
|
|
// Gets or sets the track at the specified index.
|
|
//
|
|
// Parameters:
|
|
// index:
|
|
// the zero-based position
|
|
//
|
|
// Returns:
|
|
// the track at the specified index
|
|
//
|
|
// Remarks:
|
|
// This indexer property is thread-safe, so it can be used from multiple threads
|
|
// at once safely.
|
|
public TomatenMusicTrack this[int index]
|
|
{
|
|
get
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
return _list[index];
|
|
}
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw new ArgumentNullException("value");
|
|
}
|
|
|
|
lock (_syncRoot)
|
|
{
|
|
_list[index] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public TrackList()
|
|
{
|
|
_list = new List<TomatenMusicTrack>();
|
|
_syncRoot = new object();
|
|
}
|
|
|
|
public TrackList(IEnumerable<LavalinkTrack> tracks)
|
|
{
|
|
_list = new List<TomatenMusicTrack>();
|
|
_syncRoot = new object();
|
|
|
|
foreach (var track in tracks)
|
|
Add(new TomatenMusicTrack(track));
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Adds a track at the end of the queue.
|
|
//
|
|
// Parameters:
|
|
// track:
|
|
// the track to add
|
|
//
|
|
// Exceptions:
|
|
// T:System.ArgumentNullException:
|
|
// thrown if the specified track is null.
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public void Add(TomatenMusicTrack track)
|
|
{
|
|
if (track == null)
|
|
{
|
|
throw new ArgumentNullException("track");
|
|
}
|
|
|
|
lock (_syncRoot)
|
|
{
|
|
_list.Add(track);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Adds all specified tracks to the queue.
|
|
//
|
|
// Parameters:
|
|
// tracks:
|
|
// the tracks to enqueue
|
|
//
|
|
// Exceptions:
|
|
// T:System.ArgumentNullException:
|
|
// thrown if the specified tracks enumerable is null.
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public void AddRange(IEnumerable<TomatenMusicTrack> tracks)
|
|
{
|
|
if (tracks == null)
|
|
{
|
|
throw new ArgumentNullException("tracks");
|
|
}
|
|
|
|
lock (_syncRoot)
|
|
{
|
|
_list.AddRange(tracks);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Clears all tracks from the queue.
|
|
//
|
|
// Returns:
|
|
// the number of tracks removed
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public int Clear()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
int count = _list.Count;
|
|
_list.Clear();
|
|
return count;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Gets a value indicating whether the specified track is in the queue.
|
|
//
|
|
// Parameters:
|
|
// track:
|
|
// the track to find
|
|
//
|
|
// Returns:
|
|
// a value indicating whether the specified track is in the queue
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public bool Contains(TomatenMusicTrack track)
|
|
{
|
|
if (track == null)
|
|
{
|
|
throw new ArgumentNullException("track");
|
|
}
|
|
|
|
lock (_syncRoot)
|
|
{
|
|
return _list.Contains(track);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Copies all tracks to the specified array at the specified index.
|
|
//
|
|
// Parameters:
|
|
// array:
|
|
// the array to the tracks to
|
|
//
|
|
// index:
|
|
// the zero-based writing start index
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public void CopyTo(TomatenMusicTrack[] array, int index)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
_list.CopyTo(array, index);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Dequeues a track using the FIFO method.
|
|
//
|
|
// Returns:
|
|
// the dequeued track
|
|
//
|
|
// Exceptions:
|
|
// T:System.InvalidOperationException:
|
|
// thrown if no tracks were in the queue
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public TomatenMusicTrack Dequeue()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
if (_list.Count <= 0)
|
|
{
|
|
throw new InvalidOperationException("No tracks in to dequeue.");
|
|
}
|
|
|
|
TomatenMusicTrack result = _list[0];
|
|
_list.RemoveAt(0);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Deletes all duplicate tracks from the queue.
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public void Distinct()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
if (_list.Count > 1)
|
|
{
|
|
TomatenMusicTrack[] collection = (from track in _list
|
|
group track by track.Identifier into s
|
|
select s.First()).ToArray();
|
|
_list.Clear();
|
|
_list.AddRange(collection);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Gets the track enumerator.
|
|
//
|
|
// Returns:
|
|
// the track enumerator
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public IEnumerator<TomatenMusicTrack> GetEnumerator()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
return _list.ToList().GetEnumerator();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Gets the zero-based index of the specified track.
|
|
//
|
|
// Parameters:
|
|
// track:
|
|
// the track to locate
|
|
//
|
|
// Returns:
|
|
// the zero-based index of the specified track
|
|
//
|
|
// Exceptions:
|
|
// T:System.ArgumentNullException:
|
|
// thrown if the specified track is null.
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public int IndexOf(TomatenMusicTrack track)
|
|
{
|
|
if (track == null)
|
|
{
|
|
throw new ArgumentNullException("track");
|
|
}
|
|
|
|
lock (_syncRoot)
|
|
{
|
|
return _list.IndexOf(track);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Inserts the specified track at the specified index.
|
|
//
|
|
// Parameters:
|
|
// index:
|
|
// the zero-based index to insert (e.g. 0 = top)
|
|
//
|
|
// track:
|
|
// the track to insert
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public void Insert(int index, TomatenMusicTrack track)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
_list.Insert(index, track);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Tries to remove the specified track from the queue.
|
|
//
|
|
// Parameters:
|
|
// track:
|
|
// the track to remove
|
|
//
|
|
// Returns:
|
|
// a value indicating whether the track was found and removed from the queue
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public bool Remove(TomatenMusicTrack track)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
return _list.Remove(track);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Removes all tracks that matches the specified predicate.
|
|
//
|
|
// Parameters:
|
|
// predicate:
|
|
// the track predicate
|
|
//
|
|
// Returns:
|
|
// the number of tracks removed
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public int RemoveAll(Predicate<TomatenMusicTrack> predicate)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
return _list.RemoveAll(predicate);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Removes a track at the specified index.
|
|
//
|
|
// Parameters:
|
|
// index:
|
|
// the index to remove the track
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public void RemoveAt(int index)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
_list.RemoveAt(index);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Removes all count tracks from the specified index.
|
|
//
|
|
// Parameters:
|
|
// index:
|
|
// the start index (zero-based)
|
|
//
|
|
// count:
|
|
// the number of tracks to remove
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public void RemoveRange(int index, int count)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
_list.RemoveRange(index, count);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Shuffles / mixes all tracks in the queue.
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public void Shuffle()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
if (_list.Count > 2)
|
|
{
|
|
TomatenMusicTrack[] collection = _list.OrderBy((TomatenMusicTrack s) => Guid.NewGuid()).ToArray();
|
|
_list.Clear();
|
|
_list.AddRange(collection);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Tries to dequeue a track using the FIFO method.
|
|
//
|
|
// Parameters:
|
|
// track:
|
|
// the dequeued track; or default is the result is false.
|
|
//
|
|
// Returns:
|
|
// a value indicating whether a track was dequeued.
|
|
//
|
|
// Exceptions:
|
|
// T:System.InvalidOperationException:
|
|
// thrown if no tracks were in the queue
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
public bool TryDequeue(out TomatenMusicTrack? track)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
if (_list.Count <= 0)
|
|
{
|
|
track = null;
|
|
return false;
|
|
}
|
|
|
|
track = _list[0];
|
|
_list.RemoveAt(0);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Clears the queue.
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
void ICollection<TomatenMusicTrack>.Clear()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
_list.Clear();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Summary:
|
|
// Gets the track enumerator.
|
|
//
|
|
// Returns:
|
|
// the track enumerator
|
|
//
|
|
// Remarks:
|
|
// This method is thread-safe, so it can be used from multiple threads at once safely.
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
return _list.ToArray().GetEnumerator();
|
|
}
|
|
}
|
|
|
|
public async Task Play(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
|
|
{
|
|
await player.PlayerQueue.QueueTracksAsync(this);
|
|
|
|
if (player.State == PlayerState.NotPlaying)
|
|
{
|
|
LavalinkTrack nextTrack = player.PlayerQueue.NextTrack().Track;
|
|
await player.PlayAsync(nextTrack, startTime, endTime, noReplace);
|
|
}
|
|
}
|
|
|
|
public async Task PlayNow(GuildPlayer player, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
|
|
{
|
|
Queue<TomatenMusicTrack> reversedTracks = new Queue<TomatenMusicTrack>(this);
|
|
|
|
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(new TomatenMusicTrack(player.PlayerQueue.LastTrack.WithPosition(player.TrackPosition))));
|
|
|
|
TomatenMusicTrack track = reversedTracks.Dequeue();
|
|
player.PlayerQueue.LastTrack = track;
|
|
await player.PlayAsync(track, startTime, endTime);
|
|
|
|
reversedTracks.Reverse();
|
|
|
|
foreach (var item in reversedTracks)
|
|
{
|
|
player.PlayerQueue.Queue = new Queue<TomatenMusicTrack>(player.PlayerQueue.Queue.Prepend(item));
|
|
}
|
|
}
|
|
}
|
|
}
|