using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.SyncPlay { static class ListShuffleExtension { private static Random rng = new Random(); public static void Shuffle(this IList list) { int n = list.Count; while (n > 1) { n--; int k = rng.Next(n + 1); T value = list[k]; list[k] = list[n]; list[n] = value; } } } /// /// Class PlayQueueManager. /// public class PlayQueueManager : IDisposable { /// /// Gets or sets the playing item index. /// /// The playing item index. public int PlayingItemIndex { get; private set; } /// /// Gets or sets the last time the queue has been changed. /// /// The last time the queue has been changed. public DateTime LastChange { get; private set; } /// /// Gets the sorted playlist. /// /// The sorted playlist, or play queue of the group. private List SortedPlaylist { get; set; } = new List(); /// /// Gets the shuffled playlist. /// /// The shuffled playlist, or play queue of the group. private List ShuffledPlaylist { get; set; } = new List(); /// /// Gets or sets the shuffle mode. /// /// The shuffle mode. public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted; /// /// Gets or sets the repeat mode. /// /// The repeat mode. public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone; /// /// Gets or sets the progressive id counter. /// /// The progressive id. private int ProgressiveId { get; set; } = 0; private bool _disposed = false; /// /// Initializes a new instance of the class. /// public PlayQueueManager() { Reset(); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and optionally managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (_disposed) { return; } _disposed = true; } /// /// Gets the next available id. /// /// The next available id. private int GetNextProgressiveId() { return ProgressiveId++; } /// /// Creates a list from the array of items. Each item is given an unique playlist id. /// /// The list of queue items. private List CreateQueueItemsFromArray(Guid[] items) { return items.ToList() .Select(item => new QueueItem() { ItemId = item, PlaylistItemId = "syncPlayItem" + GetNextProgressiveId() }) .ToList(); } /// /// Gets the current playlist, depending on the shuffle mode. /// /// The playlist. private List GetPlaylistAsList() { if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { return ShuffledPlaylist; } else { return SortedPlaylist; } } /// /// Gets the current playlist as an array, depending on the shuffle mode. /// /// The array of items in the playlist. public QueueItem[] GetPlaylist() { if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { return ShuffledPlaylist.ToArray(); } else { return SortedPlaylist.ToArray(); } } /// /// Sets a new playlist. Playing item is set to none. Resets shuffle mode and repeat mode as well. /// /// The new items of the playlist. public void SetPlaylist(Guid[] items) { SortedPlaylist = CreateQueueItemsFromArray(items); PlayingItemIndex = -1; ShuffleMode = GroupShuffleMode.Sorted; RepeatMode = GroupRepeatMode.RepeatNone; LastChange = DateTime.UtcNow; } /// /// Appends new items to the playlist. The specified order is mantained for the sorted playlist, whereas items get shuffled for the shuffled playlist. /// /// The items to add to the playlist. public void Queue(Guid[] items) { var newItems = CreateQueueItemsFromArray(items); SortedPlaylist.AddRange(newItems); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { newItems.Shuffle(); ShuffledPlaylist.AddRange(newItems); } LastChange = DateTime.UtcNow; } /// /// Shuffles the playlist. Shuffle mode is changed. /// public void ShufflePlaylist() { if (SortedPlaylist.Count() == 0) { return; } if (PlayingItemIndex < 0) { ShuffledPlaylist = SortedPlaylist.ToList(); ShuffledPlaylist.Shuffle(); } else { var playingItem = SortedPlaylist[PlayingItemIndex]; ShuffledPlaylist = SortedPlaylist.ToList(); ShuffledPlaylist.RemoveAt(PlayingItemIndex); ShuffledPlaylist.Shuffle(); ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList(); PlayingItemIndex = 0; } ShuffleMode = GroupShuffleMode.Shuffle; LastChange = DateTime.UtcNow; } /// /// Resets the playlist to sorted mode. Shuffle mode is changed. /// public void SortShuffledPlaylist() { if (PlayingItemIndex >= 0) { var playingItem = ShuffledPlaylist[PlayingItemIndex]; PlayingItemIndex = SortedPlaylist.IndexOf(playingItem); } ShuffledPlaylist.Clear(); ShuffleMode = GroupShuffleMode.Sorted; LastChange = DateTime.UtcNow; } /// /// Clears the playlist. /// /// Whether to remove the playing item as well. public void ClearPlaylist(bool clearPlayingItem) { var playingItem = SortedPlaylist[PlayingItemIndex]; SortedPlaylist.Clear(); ShuffledPlaylist.Clear(); LastChange = DateTime.UtcNow; if (!clearPlayingItem && playingItem != null) { SortedPlaylist.Add(playingItem); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { SortedPlaylist.Add(playingItem); } } } /// /// Adds new items to the playlist right after the playing item. The specified order is mantained for the sorted playlist, whereas items get shuffled for the shuffled playlist. /// /// The items to add to the playlist. public void QueueNext(Guid[] items) { var newItems = CreateQueueItemsFromArray(items); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { // Append items to sorted playlist as they are. SortedPlaylist.AddRange(newItems); // Shuffle items before adding to shuffled playlist. newItems.Shuffle(); ShuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems); } else { SortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems); } LastChange = DateTime.UtcNow; } /// /// Gets playlist id of the playing item, if any. /// /// The playlist id of the playing item. public string GetPlayingItemPlaylistId() { if (PlayingItemIndex < 0) { return null; } var list = GetPlaylistAsList(); if (list.Count() > 0) { return list[PlayingItemIndex].PlaylistItemId; } else { return null; } } /// /// Gets the playing item id, if any. /// /// The playing item id. public Guid GetPlayingItemId() { if (PlayingItemIndex < 0) { return Guid.Empty; } var list = GetPlaylistAsList(); if (list.Count() > 0) { return list[PlayingItemIndex].ItemId; } else { return Guid.Empty; } } /// /// Sets the playing item using its id. If not in the playlist, the playing item is reset. /// /// The new playing item id. public void SetPlayingItemById(Guid itemId) { var itemIds = GetPlaylistAsList().Select(queueItem => queueItem.ItemId).ToList(); PlayingItemIndex = itemIds.IndexOf(itemId); LastChange = DateTime.UtcNow; } /// /// Sets the playing item using its playlist id. If not in the playlist, the playing item is reset. /// /// The new playing item id. /// true if playing item has been set; false if item is not in the playlist. public bool SetPlayingItemByPlaylistId(string playlistItemId) { var playlistIds = GetPlaylistAsList().Select(queueItem => queueItem.PlaylistItemId).ToList(); PlayingItemIndex = playlistIds.IndexOf(playlistItemId); LastChange = DateTime.UtcNow; return PlayingItemIndex != -1; } /// /// Sets the playing item using its position. If not in range, the playing item is reset. /// /// The new playing item index. public void SetPlayingItemByIndex(int playlistIndex) { var list = GetPlaylistAsList(); if (playlistIndex < 0 || playlistIndex > list.Count()) { PlayingItemIndex = -1; } else { PlayingItemIndex = playlistIndex; } LastChange = DateTime.UtcNow; } /// /// Removes items from the playlist. If not removed, the playing item is preserved. /// /// The items to remove. /// true if playing item got removed; false otherwise. public bool RemoveFromPlaylist(string[] playlistItemIds) { var playingItem = SortedPlaylist[PlayingItemIndex]; if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { playingItem = ShuffledPlaylist[PlayingItemIndex]; } var playlistItemIdsList = playlistItemIds.ToList(); SortedPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId)); ShuffledPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId)); LastChange = DateTime.UtcNow; if (playingItem != null) { if (playlistItemIds.Contains(playingItem.PlaylistItemId)) { // Playing item has been removed, picking previous item. PlayingItemIndex--; if (PlayingItemIndex < 0) { // Was first element, picking next if available. PlayingItemIndex = SortedPlaylist.Count() > 0 ? 0 : -1; } return true; } else { // Restoring playing item. SetPlayingItemByPlaylistId(playingItem.PlaylistItemId); return false; } } else { return false; } } /// /// Moves an item in the playlist to another position. /// /// The item to move. /// The new position. /// true if the item has been moved; false otherwise. public bool MovePlaylistItem(string playlistItemId, int newIndex) { var list = GetPlaylistAsList(); var playingItem = list[PlayingItemIndex]; var playlistIds = list.Select(queueItem => queueItem.PlaylistItemId).ToList(); var oldIndex = playlistIds.IndexOf(playlistItemId); if (oldIndex < 0) { return false; } var queueItem = list[oldIndex]; list.RemoveAt(oldIndex); newIndex = newIndex > list.Count() ? list.Count() : newIndex; newIndex = newIndex < 0 ? 0 : newIndex; list.Insert(newIndex, queueItem); LastChange = DateTime.UtcNow; PlayingItemIndex = list.IndexOf(playingItem); return true; } /// /// Resets the playlist to its initial state. /// public void Reset() { ProgressiveId = 0; SortedPlaylist.Clear(); ShuffledPlaylist.Clear(); PlayingItemIndex = -1; ShuffleMode = GroupShuffleMode.Sorted; RepeatMode = GroupRepeatMode.RepeatNone; LastChange = DateTime.UtcNow; } /// /// Sets the repeat mode. /// /// The new mode. public void SetRepeatMode(string mode) { switch (mode) { case "RepeatOne": RepeatMode = GroupRepeatMode.RepeatOne; break; case "RepeatAll": RepeatMode = GroupRepeatMode.RepeatAll; break; default: RepeatMode = GroupRepeatMode.RepeatNone; break; } LastChange = DateTime.UtcNow; } /// /// Sets the shuffle mode. /// /// The new mode. public void SetShuffleMode(string mode) { switch (mode) { case "Shuffle": ShufflePlaylist(); break; default: SortShuffledPlaylist(); break; } } /// /// Toggles the shuffle mode between sorted and shuffled. /// public void ToggleShuffleMode() { SetShuffleMode(ShuffleMode.Equals(GroupShuffleMode.Shuffle) ? "Shuffle" : ""); } /// /// Gets the next item in the playlist considering repeat mode and shuffle mode. /// /// The next item in the playlist. public QueueItem GetNextItemPlaylistId() { int newIndex; var playlist = GetPlaylistAsList(); switch (RepeatMode) { case GroupRepeatMode.RepeatOne: newIndex = PlayingItemIndex; break; case GroupRepeatMode.RepeatAll: newIndex = PlayingItemIndex + 1; if (newIndex >= playlist.Count()) { newIndex = 0; } break; default: newIndex = PlayingItemIndex + 1; break; } if (newIndex < 0 || newIndex >= playlist.Count()) { return null; } return playlist[newIndex]; } /// /// Sets the next item in the queue as playing item. /// /// true if the playing item changed; false otherwise. public bool Next() { if (RepeatMode.Equals(GroupRepeatMode.RepeatOne)) { LastChange = DateTime.UtcNow; return true; } PlayingItemIndex++; if (PlayingItemIndex >= SortedPlaylist.Count()) { if (RepeatMode.Equals(GroupRepeatMode.RepeatAll)) { PlayingItemIndex = 0; } else { PlayingItemIndex--; return false; } } LastChange = DateTime.UtcNow; return true; } /// /// Sets the previous item in the queue as playing item. /// /// true if the playing item changed; false otherwise. public bool Previous() { if (RepeatMode.Equals(GroupRepeatMode.RepeatOne)) { LastChange = DateTime.UtcNow; return true; } PlayingItemIndex--; if (PlayingItemIndex < 0) { if (RepeatMode.Equals(GroupRepeatMode.RepeatAll)) { PlayingItemIndex = SortedPlaylist.Count() - 1; } else { PlayingItemIndex++; return false; } } LastChange = DateTime.UtcNow; return true; } } }