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 identifier counter. /// /// The progressive identifier. private int ProgressiveId { get; set; } = 0; /// /// Placeholder index for when no item is playing. /// /// The no-playing item index. private const int NoPlayingItemIndex = -1; /// /// 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 identifier. /// /// The next available identifier. private int GetNextProgressiveId() { return ProgressiveId++; } /// /// Creates a list from the array of items. Each item is given an unique playlist identifier. /// /// 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 playing item, depending on the shuffle mode. /// /// The playing item. private QueueItem GetPlayingItem() { if (PlayingItemIndex == NoPlayingItemIndex) { return null; } else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { return ShuffledPlaylist[PlayingItemIndex]; } else { return SortedPlaylist[PlayingItemIndex]; } } /// /// 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 reset. /// /// The new items of the playlist. public void SetPlaylist(Guid[] items) { SortedPlaylist.Clear(); ShuffledPlaylist.Clear(); SortedPlaylist = CreateQueueItemsFromArray(items); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { ShuffledPlaylist = SortedPlaylist.ToList(); ShuffledPlaylist.Shuffle(); } PlayingItemIndex = NoPlayingItemIndex; LastChange = DateTime.UtcNow; } /// /// Appends new items to the playlist. The specified order is mantained. /// /// The items to add to the playlist. public void Queue(Guid[] items) { var newItems = CreateQueueItemsFromArray(items); SortedPlaylist.AddRange(newItems); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { ShuffledPlaylist.AddRange(newItems); } LastChange = DateTime.UtcNow; } /// /// Shuffles the playlist. Shuffle mode is changed. /// public void ShufflePlaylist() { if (PlayingItemIndex == NoPlayingItemIndex) { ShuffledPlaylist = SortedPlaylist.ToList(); ShuffledPlaylist.Shuffle(); } else if (ShuffleMode.Equals(GroupShuffleMode.Sorted)) { // First time shuffle. var playingItem = SortedPlaylist[PlayingItemIndex]; ShuffledPlaylist = SortedPlaylist.ToList(); ShuffledPlaylist.RemoveAt(PlayingItemIndex); ShuffledPlaylist.Shuffle(); ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList(); PlayingItemIndex = 0; } else { // Re-shuffle playlist. var playingItem = ShuffledPlaylist[PlayingItemIndex]; 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 RestoreSortedPlaylist() { if (PlayingItemIndex != NoPlayingItemIndex) { var playingItem = ShuffledPlaylist[PlayingItemIndex]; PlayingItemIndex = SortedPlaylist.IndexOf(playingItem); } ShuffledPlaylist.Clear(); ShuffleMode = GroupShuffleMode.Sorted; LastChange = DateTime.UtcNow; } /// /// Clears the playlist. Shuffle mode is preserved. /// /// Whether to remove the playing item as well. public void ClearPlaylist(bool clearPlayingItem) { var playingItem = GetPlayingItem(); SortedPlaylist.Clear(); ShuffledPlaylist.Clear(); LastChange = DateTime.UtcNow; if (!clearPlayingItem && playingItem != null) { SortedPlaylist.Add(playingItem); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { ShuffledPlaylist.Add(playingItem); } PlayingItemIndex = 0; } else { PlayingItemIndex = NoPlayingItemIndex; } } /// /// Adds new items to the playlist right after the playing item. The specified order is mantained. /// /// The items to add to the playlist. public void QueueNext(Guid[] items) { var newItems = CreateQueueItemsFromArray(items); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { var playingItem = GetPlayingItem(); var sortedPlayingItemIndex = SortedPlaylist.IndexOf(playingItem); // Append items to sorted and shuffled playlist as they are. SortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems); ShuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems); } else { SortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems); } LastChange = DateTime.UtcNow; } /// /// Gets playlist identifier of the playing item, if any. /// /// The playlist identifier of the playing item. public string GetPlayingItemPlaylistId() { var playingItem = GetPlayingItem(); if (playingItem != null) { return playingItem.PlaylistItemId; } else { return null; } } /// /// Gets the playing item identifier, if any. /// /// The playing item identifier. public Guid GetPlayingItemId() { var playingItem = GetPlayingItem(); if (playingItem != null) { return playingItem.ItemId; } else { return Guid.Empty; } } /// /// Sets the playing item using its identifier. If not in the playlist, the playing item is reset. /// /// The new playing item identifier. 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 identifier. If not in the playlist, the playing item is reset. /// /// The new playing item identifier. /// 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 != NoPlayingItemIndex; } /// /// 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 = NoPlayingItemIndex; } 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 = GetPlayingItem(); 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. // Default to no playing item otherwise. PlayingItemIndex = SortedPlaylist.Count() > 0 ? 0 : NoPlayingItemIndex; } 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 = GetPlayingItem(); 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 = Math.Min(newIndex, list.Count()); newIndex = Math.Max(newIndex, 0); 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 = NoPlayingItemIndex; 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: RestoreSortedPlaylist(); 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; } } }