#nullable disable using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Extensions; using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.SyncPlay.Queue { /// /// Class PlayQueueManager. /// public class PlayQueueManager { /// /// Placeholder index for when no item is playing. /// /// The no-playing item index. private const int NoPlayingItemIndex = -1; /// /// The sorted playlist. /// /// The sorted playlist, or play queue of the group. private List _sortedPlaylist = new List(); /// /// The shuffled playlist. /// /// The shuffled playlist, or play queue of the group. private List _shuffledPlaylist = new List(); /// /// 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; /// /// 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(IReadOnlyList items) { _sortedPlaylist.Clear(); _shuffledPlaylist.Clear(); _sortedPlaylist = CreateQueueItemsFromArray(items); if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) { _shuffledPlaylist = new List(_sortedPlaylist); _shuffledPlaylist.Shuffle(); } PlayingItemIndex = NoPlayingItemIndex; LastChange = DateTime.UtcNow; } /// /// Appends new items to the playlist. The specified order is maintained. /// /// The items to add to the playlist. public void Queue(IReadOnlyList 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 = new List(_sortedPlaylist); _shuffledPlaylist.Shuffle(); } else if (ShuffleMode.Equals(GroupShuffleMode.Sorted)) { // First time shuffle. var playingItem = _sortedPlaylist[PlayingItemIndex]; _shuffledPlaylist = new List(_sortedPlaylist); _shuffledPlaylist.RemoveAt(PlayingItemIndex); _shuffledPlaylist.Shuffle(); _shuffledPlaylist.Insert(0, playingItem); PlayingItemIndex = 0; } else { // Re-shuffle playlist. var playingItem = _shuffledPlaylist[PlayingItemIndex]; _shuffledPlaylist.RemoveAt(PlayingItemIndex); _shuffledPlaylist.Shuffle(); _shuffledPlaylist.Insert(0, playingItem); 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 is not 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 maintained. /// /// The items to add to the playlist. public void QueueNext(IReadOnlyList 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 Guid GetPlayingItemPlaylistId() { var playingItem = GetPlayingItem(); return playingItem?.PlaylistItemId ?? Guid.Empty; } /// /// Gets the playing item identifier, if any. /// /// The playing item identifier. public Guid GetPlayingItemId() { var playingItem = GetPlayingItem(); return playingItem?.ItemId ?? 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(Guid playlistItemId) { var playlist = GetPlaylistInternal(); PlayingItemIndex = playlist.FindIndex(item => item.PlaylistItemId.Equals(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 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(IReadOnlyList playlistItemIds) { var playingItem = GetPlayingItem(); _sortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId)); _shuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId)); LastChange = DateTime.UtcNow; if (playingItem is not 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(Guid playlistItemId, int newIndex) { var playlist = GetPlaylistInternal(); var playingItem = GetPlayingItem(); var oldIndex = playlist.FindIndex(item => item.PlaylistItemId.Equals(playlistItemId)); if (oldIndex < 0) { return false; } var queueItem = playlist[oldIndex]; playlist.RemoveAt(oldIndex); newIndex = Math.Clamp(newIndex, 0, playlist.Count); playlist.Insert(newIndex, queueItem); LastChange = DateTime.UtcNow; PlayingItemIndex = playlist.IndexOf(playingItem); return true; } /// /// Resets the playlist to its initial state. /// public void Reset() { _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 = _sortedPlaylist.Count - 1; 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 = 0; return false; } } LastChange = DateTime.UtcNow; return true; } /// /// Creates a list from the array of items. Each item is given an unique playlist identifier. /// /// The list of queue items. private List CreateQueueItemsFromArray(IReadOnlyList items) { var list = new List(); foreach (var item in items) { var queueItem = new QueueItem(item); list.Add(queueItem); } 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]; } } } }