using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.SyncPlay { /// /// Class PlayQueueManager. /// public class PlayQueueManager { /// /// Placeholder index for when no item is playing. /// /// The no-playing item index. private const int NoPlayingItemIndex = -1; /// /// Random number generator used to shuffle lists. /// /// The random number generator. private readonly Random randomNumberGenerator = new Random(); /// /// Initializes a new instance of the class. /// public PlayQueueManager() { Reset(); } /// /// Gets the playing item index. /// /// The playing item index. public int PlayingItemIndex { get; private set; } /// /// Gets the last time the queue has been changed. /// /// The last time the queue has been changed. public DateTime LastChange { get; private set; } /// /// Gets the shuffle mode. /// /// The shuffle mode. public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted; /// /// Gets the repeat mode. /// /// The repeat mode. public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone; /// /// Gets or sets the sorted playlist. /// /// The sorted playlist, or play queue of the group. private List SortedPlaylist { get; set; } = new List(); /// /// Gets or sets the shuffled playlist. /// /// The shuffled playlist, or play queue of the group. private List ShuffledPlaylist { get; set; } = new List(); /// /// Gets or sets the progressive identifier counter. /// /// The progressive identifier. private int ProgressiveId { get; set; } /// /// Checks if an item is playing. /// /// true if an item is playing; false otherwise. public bool IsItemPlaying() { return PlayingItemIndex != NoPlayingItemIndex; } /// /// Gets the current playlist considering the shuffle mode. /// /// The playlist. public IReadOnlyList GetPlaylist() { return GetPlaylistInternal(); } /// /// Sets a new playlist. Playing item is reset. /// /// The new items of the playlist. public void SetPlaylist(IEnumerable items) { SortedPlaylist.Clear(); ShuffledPlaylist.Clear(); SortedPlaylist = CreateQueueItemsFromArray(items); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { ShuffledPlaylist = SortedPlaylist.ToList(); Shuffle(ShuffledPlaylist); } 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(IEnumerable 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. The playlist gets re-shuffled if already shuffled. /// public void ShufflePlaylist() { if (PlayingItemIndex == NoPlayingItemIndex) { ShuffledPlaylist = SortedPlaylist.ToList(); Shuffle(ShuffledPlaylist); } else if (ShuffleMode.Equals(GroupShuffleMode.Sorted)) { // First time shuffle. var playingItem = SortedPlaylist[PlayingItemIndex]; ShuffledPlaylist = SortedPlaylist.ToList(); ShuffledPlaylist.RemoveAt(PlayingItemIndex); Shuffle(ShuffledPlaylist); ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList(); PlayingItemIndex = 0; } else { // Re-shuffle playlist. var playingItem = ShuffledPlaylist[PlayingItemIndex]; ShuffledPlaylist.RemoveAt(PlayingItemIndex); Shuffle(ShuffledPlaylist); 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(IEnumerable 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 playlist = GetPlaylistInternal(); PlayingItemIndex = playlist.FindIndex(item => item.ItemId.Equals(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 playlist = GetPlaylistInternal(); PlayingItemIndex = playlist.FindIndex(item => item.PlaylistItemId.Equals(playlistItemId, StringComparison.OrdinalIgnoreCase)); 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 playlist = GetPlaylistInternal(); if (playlistIndex < 0 || playlistIndex > playlist.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(IEnumerable 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 playlist = GetPlaylistInternal(); var playingItem = GetPlayingItem(); var oldIndex = playlist.FindIndex(item => item.PlaylistItemId.Equals(playlistItemId, StringComparison.OrdinalIgnoreCase)); if (oldIndex < 0) { return false; } var queueItem = playlist[oldIndex]; playlist.RemoveAt(oldIndex); newIndex = Math.Min(newIndex, playlist.Count); newIndex = Math.Max(newIndex, 0); playlist.Insert(newIndex, queueItem); LastChange = DateTime.UtcNow; PlayingItemIndex = playlist.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(GroupRepeatMode mode) { RepeatMode = mode; LastChange = DateTime.UtcNow; } /// /// Sets the shuffle mode. /// /// The new mode. public void SetShuffleMode(GroupShuffleMode mode) { if (mode.Equals(GroupShuffleMode.Shuffle)) { ShufflePlaylist(); } else { RestoreSortedPlaylist(); } } /// /// Toggles the shuffle mode between sorted and shuffled. /// public void ToggleShuffleMode() { if (ShuffleMode.Equals(GroupShuffleMode.Sorted)) { ShufflePlaylist(); } else { RestoreSortedPlaylist(); } } /// /// 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 = GetPlaylistInternal(); 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; } /// /// Shuffles a given list. /// /// The list to shuffle. private void Shuffle(IList list) { int n = list.Count; while (n > 1) { n--; int k = randomNumberGenerator.Next(n + 1); T value = list[k]; list[k] = list[n]; list[n] = value; } } /// /// 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(IEnumerable items) { var list = new List(); foreach (var item in items) { list.Add(new QueueItem() { ItemId = item, PlaylistItemId = "syncPlayItem" + GetNextProgressiveId() }); } return list; } /// /// Gets the current playlist considering the shuffle mode. /// /// The playlist. private List GetPlaylistInternal() { 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]; } } } }