using System;
using System.Collections.Generic;
using System.Linq;
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;
///
/// 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(IReadOnlyList items)
{
SortedPlaylist.Clear();
ShuffledPlaylist.Clear();
SortedPlaylist = CreateQueueItemsFromArray(items);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
ShuffledPlaylist = new List(SortedPlaylist);
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(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);
Shuffle(ShuffledPlaylist);
}
else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
{
// First time shuffle.
var playingItem = SortedPlaylist[PlayingItemIndex];
ShuffledPlaylist = new List(SortedPlaylist);
ShuffledPlaylist.RemoveAt(PlayingItemIndex);
Shuffle(ShuffledPlaylist);
ShuffledPlaylist.Insert(0, playingItem);
PlayingItemIndex = 0;
}
else
{
// Re-shuffle playlist.
var playingItem = ShuffledPlaylist[PlayingItemIndex];
ShuffledPlaylist.RemoveAt(PlayingItemIndex);
Shuffle(ShuffledPlaylist);
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 != 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(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 string GetPlayingItemPlaylistId()
{
var playingItem = GetPlayingItem();
return playingItem?.PlaylistItemId;
}
///
/// 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(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(IReadOnlyList playlistItemIds)
{
var playingItem = GetPlayingItem();
SortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
ShuffledPlaylist.RemoveAll(item => playlistItemIds.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.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()
{
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 = 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;
}
///
/// 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(IReadOnlyList items)
{
var list = new List();
foreach (var item in items)
{
var queueItem = new QueueItem(item, "syncPlayItem" + GetNextProgressiveId());
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];
}
}
}
}