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)); } } } }