PlayQueueManager.cs 20 KB

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