From 8819a9d478e6fc11dbfdcff80d9a2dc175953373 Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca Date: Thu, 24 Sep 2020 23:04:21 +0200 Subject: Add playlist-sync and group-wait to SyncPlay --- .../SyncPlay/Queue/PlayQueueManager.cs | 596 +++++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100644 MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs (limited to 'MediaBrowser.Controller/SyncPlay/Queue') diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs new file mode 100644 index 0000000000..701982cc00 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs @@ -0,0 +1,596 @@ +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; + } + } +} -- cgit v1.2.3