PlayQueueManager.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using MediaBrowser.Model.SyncPlay;
  5. namespace MediaBrowser.Controller.SyncPlay.Queue
  6. {
  7. /// <summary>
  8. /// Class PlayQueueManager.
  9. /// </summary>
  10. public class PlayQueueManager
  11. {
  12. /// <summary>
  13. /// Placeholder index for when no item is playing.
  14. /// </summary>
  15. /// <value>The no-playing item index.</value>
  16. private const int NoPlayingItemIndex = -1;
  17. /// <summary>
  18. /// Random number generator used to shuffle lists.
  19. /// </summary>
  20. /// <value>The random number generator.</value>
  21. private readonly Random _randomNumberGenerator = new Random();
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
  24. /// </summary>
  25. public PlayQueueManager()
  26. {
  27. Reset();
  28. }
  29. /// <summary>
  30. /// Gets the playing item index.
  31. /// </summary>
  32. /// <value>The playing item index.</value>
  33. public int PlayingItemIndex { get; private set; }
  34. /// <summary>
  35. /// Gets the last time the queue has been changed.
  36. /// </summary>
  37. /// <value>The last time the queue has been changed.</value>
  38. public DateTime LastChange { get; private set; }
  39. /// <summary>
  40. /// Gets the shuffle mode.
  41. /// </summary>
  42. /// <value>The shuffle mode.</value>
  43. public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted;
  44. /// <summary>
  45. /// Gets the repeat mode.
  46. /// </summary>
  47. /// <value>The repeat mode.</value>
  48. public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone;
  49. /// <summary>
  50. /// Gets or sets the sorted playlist.
  51. /// </summary>
  52. /// <value>The sorted playlist, or play queue of the group.</value>
  53. private List<QueueItem> SortedPlaylist { get; set; } = new List<QueueItem>();
  54. /// <summary>
  55. /// Gets or sets the shuffled playlist.
  56. /// </summary>
  57. /// <value>The shuffled playlist, or play queue of the group.</value>
  58. private List<QueueItem> ShuffledPlaylist { get; set; } = new List<QueueItem>();
  59. /// <summary>
  60. /// Checks if an item is playing.
  61. /// </summary>
  62. /// <returns><c>true</c> if an item is playing; <c>false</c> otherwise.</returns>
  63. public bool IsItemPlaying()
  64. {
  65. return PlayingItemIndex != NoPlayingItemIndex;
  66. }
  67. /// <summary>
  68. /// Gets the current playlist considering the shuffle mode.
  69. /// </summary>
  70. /// <returns>The playlist.</returns>
  71. public IReadOnlyList<QueueItem> GetPlaylist()
  72. {
  73. return GetPlaylistInternal();
  74. }
  75. /// <summary>
  76. /// Sets a new playlist. Playing item is reset.
  77. /// </summary>
  78. /// <param name="items">The new items of the playlist.</param>
  79. public void SetPlaylist(IReadOnlyList<Guid> items)
  80. {
  81. SortedPlaylist.Clear();
  82. ShuffledPlaylist.Clear();
  83. SortedPlaylist = CreateQueueItemsFromArray(items);
  84. if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
  85. {
  86. ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
  87. Shuffle(ShuffledPlaylist);
  88. }
  89. PlayingItemIndex = NoPlayingItemIndex;
  90. LastChange = DateTime.UtcNow;
  91. }
  92. /// <summary>
  93. /// Appends new items to the playlist. The specified order is mantained.
  94. /// </summary>
  95. /// <param name="items">The items to add to the playlist.</param>
  96. public void Queue(IReadOnlyList<Guid> items)
  97. {
  98. var newItems = CreateQueueItemsFromArray(items);
  99. SortedPlaylist.AddRange(newItems);
  100. if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
  101. {
  102. ShuffledPlaylist.AddRange(newItems);
  103. }
  104. LastChange = DateTime.UtcNow;
  105. }
  106. /// <summary>
  107. /// Shuffles the playlist. Shuffle mode is changed. The playlist gets re-shuffled if already shuffled.
  108. /// </summary>
  109. public void ShufflePlaylist()
  110. {
  111. if (PlayingItemIndex == NoPlayingItemIndex)
  112. {
  113. ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
  114. Shuffle(ShuffledPlaylist);
  115. }
  116. else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
  117. {
  118. // First time shuffle.
  119. var playingItem = SortedPlaylist[PlayingItemIndex];
  120. ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
  121. ShuffledPlaylist.RemoveAt(PlayingItemIndex);
  122. Shuffle(ShuffledPlaylist);
  123. ShuffledPlaylist.Insert(0, playingItem);
  124. PlayingItemIndex = 0;
  125. }
  126. else
  127. {
  128. // Re-shuffle playlist.
  129. var playingItem = ShuffledPlaylist[PlayingItemIndex];
  130. ShuffledPlaylist.RemoveAt(PlayingItemIndex);
  131. Shuffle(ShuffledPlaylist);
  132. ShuffledPlaylist.Insert(0, playingItem);
  133. PlayingItemIndex = 0;
  134. }
  135. ShuffleMode = GroupShuffleMode.Shuffle;
  136. LastChange = DateTime.UtcNow;
  137. }
  138. /// <summary>
  139. /// Resets the playlist to sorted mode. Shuffle mode is changed.
  140. /// </summary>
  141. public void RestoreSortedPlaylist()
  142. {
  143. if (PlayingItemIndex != NoPlayingItemIndex)
  144. {
  145. var playingItem = ShuffledPlaylist[PlayingItemIndex];
  146. PlayingItemIndex = SortedPlaylist.IndexOf(playingItem);
  147. }
  148. ShuffledPlaylist.Clear();
  149. ShuffleMode = GroupShuffleMode.Sorted;
  150. LastChange = DateTime.UtcNow;
  151. }
  152. /// <summary>
  153. /// Clears the playlist. Shuffle mode is preserved.
  154. /// </summary>
  155. /// <param name="clearPlayingItem">Whether to remove the playing item as well.</param>
  156. public void ClearPlaylist(bool clearPlayingItem)
  157. {
  158. var playingItem = GetPlayingItem();
  159. SortedPlaylist.Clear();
  160. ShuffledPlaylist.Clear();
  161. LastChange = DateTime.UtcNow;
  162. if (!clearPlayingItem && playingItem != null)
  163. {
  164. SortedPlaylist.Add(playingItem);
  165. if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
  166. {
  167. ShuffledPlaylist.Add(playingItem);
  168. }
  169. PlayingItemIndex = 0;
  170. }
  171. else
  172. {
  173. PlayingItemIndex = NoPlayingItemIndex;
  174. }
  175. }
  176. /// <summary>
  177. /// Adds new items to the playlist right after the playing item. The specified order is mantained.
  178. /// </summary>
  179. /// <param name="items">The items to add to the playlist.</param>
  180. public void QueueNext(IReadOnlyList<Guid> items)
  181. {
  182. var newItems = CreateQueueItemsFromArray(items);
  183. if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
  184. {
  185. var playingItem = GetPlayingItem();
  186. var sortedPlayingItemIndex = SortedPlaylist.IndexOf(playingItem);
  187. // Append items to sorted and shuffled playlist as they are.
  188. SortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems);
  189. ShuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
  190. }
  191. else
  192. {
  193. SortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
  194. }
  195. LastChange = DateTime.UtcNow;
  196. }
  197. /// <summary>
  198. /// Gets playlist identifier of the playing item, if any.
  199. /// </summary>
  200. /// <returns>The playlist identifier of the playing item.</returns>
  201. public Guid GetPlayingItemPlaylistId()
  202. {
  203. var playingItem = GetPlayingItem();
  204. return playingItem?.PlaylistItemId ?? Guid.Empty;
  205. }
  206. /// <summary>
  207. /// Gets the playing item identifier, if any.
  208. /// </summary>
  209. /// <returns>The playing item identifier.</returns>
  210. public Guid GetPlayingItemId()
  211. {
  212. var playingItem = GetPlayingItem();
  213. return playingItem?.ItemId ?? Guid.Empty;
  214. }
  215. /// <summary>
  216. /// Sets the playing item using its identifier. If not in the playlist, the playing item is reset.
  217. /// </summary>
  218. /// <param name="itemId">The new playing item identifier.</param>
  219. public void SetPlayingItemById(Guid itemId)
  220. {
  221. var playlist = GetPlaylistInternal();
  222. PlayingItemIndex = playlist.FindIndex(item => item.ItemId.Equals(itemId));
  223. LastChange = DateTime.UtcNow;
  224. }
  225. /// <summary>
  226. /// Sets the playing item using its playlist identifier. If not in the playlist, the playing item is reset.
  227. /// </summary>
  228. /// <param name="playlistItemId">The new playing item identifier.</param>
  229. /// <returns><c>true</c> if playing item has been set; <c>false</c> if item is not in the playlist.</returns>
  230. public bool SetPlayingItemByPlaylistId(Guid playlistItemId)
  231. {
  232. var playlist = GetPlaylistInternal();
  233. PlayingItemIndex = playlist.FindIndex(item => item.PlaylistItemId.Equals(playlistItemId));
  234. LastChange = DateTime.UtcNow;
  235. return PlayingItemIndex != NoPlayingItemIndex;
  236. }
  237. /// <summary>
  238. /// Sets the playing item using its position. If not in range, the playing item is reset.
  239. /// </summary>
  240. /// <param name="playlistIndex">The new playing item index.</param>
  241. public void SetPlayingItemByIndex(int playlistIndex)
  242. {
  243. var playlist = GetPlaylistInternal();
  244. if (playlistIndex < 0 || playlistIndex > playlist.Count)
  245. {
  246. PlayingItemIndex = NoPlayingItemIndex;
  247. }
  248. else
  249. {
  250. PlayingItemIndex = playlistIndex;
  251. }
  252. LastChange = DateTime.UtcNow;
  253. }
  254. /// <summary>
  255. /// Removes items from the playlist. If not removed, the playing item is preserved.
  256. /// </summary>
  257. /// <param name="playlistItemIds">The items to remove.</param>
  258. /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns>
  259. public bool RemoveFromPlaylist(IReadOnlyList<Guid> playlistItemIds)
  260. {
  261. var playingItem = GetPlayingItem();
  262. SortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
  263. ShuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
  264. LastChange = DateTime.UtcNow;
  265. if (playingItem != null)
  266. {
  267. if (playlistItemIds.Contains(playingItem.PlaylistItemId))
  268. {
  269. // Playing item has been removed, picking previous item.
  270. PlayingItemIndex--;
  271. if (PlayingItemIndex < 0)
  272. {
  273. // Was first element, picking next if available.
  274. // Default to no playing item otherwise.
  275. PlayingItemIndex = SortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex;
  276. }
  277. return true;
  278. }
  279. else
  280. {
  281. // Restoring playing item.
  282. SetPlayingItemByPlaylistId(playingItem.PlaylistItemId);
  283. return false;
  284. }
  285. }
  286. else
  287. {
  288. return false;
  289. }
  290. }
  291. /// <summary>
  292. /// Moves an item in the playlist to another position.
  293. /// </summary>
  294. /// <param name="playlistItemId">The item to move.</param>
  295. /// <param name="newIndex">The new position.</param>
  296. /// <returns><c>true</c> if the item has been moved; <c>false</c> otherwise.</returns>
  297. public bool MovePlaylistItem(Guid playlistItemId, int newIndex)
  298. {
  299. var playlist = GetPlaylistInternal();
  300. var playingItem = GetPlayingItem();
  301. var oldIndex = playlist.FindIndex(item => item.PlaylistItemId.Equals(playlistItemId));
  302. if (oldIndex < 0)
  303. {
  304. return false;
  305. }
  306. var queueItem = playlist[oldIndex];
  307. playlist.RemoveAt(oldIndex);
  308. newIndex = Math.Clamp(newIndex, 0, playlist.Count);
  309. playlist.Insert(newIndex, queueItem);
  310. LastChange = DateTime.UtcNow;
  311. PlayingItemIndex = playlist.IndexOf(playingItem);
  312. return true;
  313. }
  314. /// <summary>
  315. /// Resets the playlist to its initial state.
  316. /// </summary>
  317. public void Reset()
  318. {
  319. SortedPlaylist.Clear();
  320. ShuffledPlaylist.Clear();
  321. PlayingItemIndex = NoPlayingItemIndex;
  322. ShuffleMode = GroupShuffleMode.Sorted;
  323. RepeatMode = GroupRepeatMode.RepeatNone;
  324. LastChange = DateTime.UtcNow;
  325. }
  326. /// <summary>
  327. /// Sets the repeat mode.
  328. /// </summary>
  329. /// <param name="mode">The new mode.</param>
  330. public void SetRepeatMode(GroupRepeatMode mode)
  331. {
  332. RepeatMode = mode;
  333. LastChange = DateTime.UtcNow;
  334. }
  335. /// <summary>
  336. /// Sets the shuffle mode.
  337. /// </summary>
  338. /// <param name="mode">The new mode.</param>
  339. public void SetShuffleMode(GroupShuffleMode mode)
  340. {
  341. if (mode.Equals(GroupShuffleMode.Shuffle))
  342. {
  343. ShufflePlaylist();
  344. }
  345. else
  346. {
  347. RestoreSortedPlaylist();
  348. }
  349. }
  350. /// <summary>
  351. /// Toggles the shuffle mode between sorted and shuffled.
  352. /// </summary>
  353. public void ToggleShuffleMode()
  354. {
  355. if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
  356. {
  357. ShufflePlaylist();
  358. }
  359. else
  360. {
  361. RestoreSortedPlaylist();
  362. }
  363. }
  364. /// <summary>
  365. /// Gets the next item in the playlist considering repeat mode and shuffle mode.
  366. /// </summary>
  367. /// <returns>The next item in the playlist.</returns>
  368. public QueueItem GetNextItemPlaylistId()
  369. {
  370. int newIndex;
  371. var playlist = GetPlaylistInternal();
  372. switch (RepeatMode)
  373. {
  374. case GroupRepeatMode.RepeatOne:
  375. newIndex = PlayingItemIndex;
  376. break;
  377. case GroupRepeatMode.RepeatAll:
  378. newIndex = PlayingItemIndex + 1;
  379. if (newIndex >= playlist.Count)
  380. {
  381. newIndex = 0;
  382. }
  383. break;
  384. default:
  385. newIndex = PlayingItemIndex + 1;
  386. break;
  387. }
  388. if (newIndex < 0 || newIndex >= playlist.Count)
  389. {
  390. return null;
  391. }
  392. return playlist[newIndex];
  393. }
  394. /// <summary>
  395. /// Sets the next item in the queue as playing item.
  396. /// </summary>
  397. /// <returns><c>true</c> if the playing item changed; <c>false</c> otherwise.</returns>
  398. public bool Next()
  399. {
  400. if (RepeatMode.Equals(GroupRepeatMode.RepeatOne))
  401. {
  402. LastChange = DateTime.UtcNow;
  403. return true;
  404. }
  405. PlayingItemIndex++;
  406. if (PlayingItemIndex >= SortedPlaylist.Count)
  407. {
  408. if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
  409. {
  410. PlayingItemIndex = 0;
  411. }
  412. else
  413. {
  414. PlayingItemIndex = SortedPlaylist.Count - 1;
  415. return false;
  416. }
  417. }
  418. LastChange = DateTime.UtcNow;
  419. return true;
  420. }
  421. /// <summary>
  422. /// Sets the previous item in the queue as playing item.
  423. /// </summary>
  424. /// <returns><c>true</c> if the playing item changed; <c>false</c> otherwise.</returns>
  425. public bool Previous()
  426. {
  427. if (RepeatMode.Equals(GroupRepeatMode.RepeatOne))
  428. {
  429. LastChange = DateTime.UtcNow;
  430. return true;
  431. }
  432. PlayingItemIndex--;
  433. if (PlayingItemIndex < 0)
  434. {
  435. if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
  436. {
  437. PlayingItemIndex = SortedPlaylist.Count - 1;
  438. }
  439. else
  440. {
  441. PlayingItemIndex = 0;
  442. return false;
  443. }
  444. }
  445. LastChange = DateTime.UtcNow;
  446. return true;
  447. }
  448. /// <summary>
  449. /// Shuffles a given list.
  450. /// </summary>
  451. /// <param name="list">The list to shuffle.</param>
  452. private void Shuffle<T>(IList<T> list)
  453. {
  454. int n = list.Count;
  455. while (n > 1)
  456. {
  457. n--;
  458. int k = _randomNumberGenerator.Next(n + 1);
  459. T value = list[k];
  460. list[k] = list[n];
  461. list[n] = value;
  462. }
  463. }
  464. /// <summary>
  465. /// Creates a list from the array of items. Each item is given an unique playlist identifier.
  466. /// </summary>
  467. /// <returns>The list of queue items.</returns>
  468. private List<QueueItem> CreateQueueItemsFromArray(IReadOnlyList<Guid> items)
  469. {
  470. var list = new List<QueueItem>();
  471. foreach (var item in items)
  472. {
  473. var queueItem = new QueueItem(item);
  474. list.Add(queueItem);
  475. }
  476. return list;
  477. }
  478. /// <summary>
  479. /// Gets the current playlist considering the shuffle mode.
  480. /// </summary>
  481. /// <returns>The playlist.</returns>
  482. private List<QueueItem> GetPlaylistInternal()
  483. {
  484. if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
  485. {
  486. return ShuffledPlaylist;
  487. }
  488. else
  489. {
  490. return SortedPlaylist;
  491. }
  492. }
  493. /// <summary>
  494. /// Gets the current playing item, depending on the shuffle mode.
  495. /// </summary>
  496. /// <returns>The playing item.</returns>
  497. private QueueItem GetPlayingItem()
  498. {
  499. if (PlayingItemIndex == NoPlayingItemIndex)
  500. {
  501. return null;
  502. }
  503. else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
  504. {
  505. return ShuffledPlaylist[PlayingItemIndex];
  506. }
  507. else
  508. {
  509. return SortedPlaylist[PlayingItemIndex];
  510. }
  511. }
  512. }
  513. }