PlayQueueManager.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using MediaBrowser.Model.SyncPlay;
  5. namespace MediaBrowser.Controller.SyncPlay
  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(IEnumerable<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(IEnumerable<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(IEnumerable<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. PlayingItemIndex = NoPlayingItemIndex;
  241. var playlist = GetPlaylistInternal();
  242. foreach (var item in playlist)
  243. {
  244. if (item.ItemId.Equals(itemId))
  245. {
  246. PlayingItemIndex = playlist.IndexOf(item);
  247. break;
  248. }
  249. }
  250. LastChange = DateTime.UtcNow;
  251. }
  252. /// <summary>
  253. /// Sets the playing item using its playlist identifier. If not in the playlist, the playing item is reset.
  254. /// </summary>
  255. /// <param name="playlistItemId">The new playing item identifier.</param>
  256. /// <returns><c>true</c> if playing item has been set; <c>false</c> if item is not in the playlist.</returns>
  257. public bool SetPlayingItemByPlaylistId(string playlistItemId)
  258. {
  259. PlayingItemIndex = NoPlayingItemIndex;
  260. var playlist = GetPlaylistInternal();
  261. foreach (var item in playlist)
  262. {
  263. if (item.PlaylistItemId.Equals(playlistItemId, StringComparison.OrdinalIgnoreCase))
  264. {
  265. PlayingItemIndex = playlist.IndexOf(item);
  266. break;
  267. }
  268. }
  269. LastChange = DateTime.UtcNow;
  270. return PlayingItemIndex != NoPlayingItemIndex;
  271. }
  272. /// <summary>
  273. /// Sets the playing item using its position. If not in range, the playing item is reset.
  274. /// </summary>
  275. /// <param name="playlistIndex">The new playing item index.</param>
  276. public void SetPlayingItemByIndex(int playlistIndex)
  277. {
  278. var playlist = GetPlaylistInternal();
  279. if (playlistIndex < 0 || playlistIndex > playlist.Count)
  280. {
  281. PlayingItemIndex = NoPlayingItemIndex;
  282. }
  283. else
  284. {
  285. PlayingItemIndex = playlistIndex;
  286. }
  287. LastChange = DateTime.UtcNow;
  288. }
  289. /// <summary>
  290. /// Removes items from the playlist. If not removed, the playing item is preserved.
  291. /// </summary>
  292. /// <param name="playlistItemIds">The items to remove.</param>
  293. /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns>
  294. public bool RemoveFromPlaylist(IEnumerable<string> playlistItemIds)
  295. {
  296. var playingItem = GetPlayingItem();
  297. var playlistItemIdsList = playlistItemIds.ToList();
  298. SortedPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId));
  299. ShuffledPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId));
  300. LastChange = DateTime.UtcNow;
  301. if (playingItem != null)
  302. {
  303. if (playlistItemIds.Contains(playingItem.PlaylistItemId))
  304. {
  305. // Playing item has been removed, picking previous item.
  306. PlayingItemIndex--;
  307. if (PlayingItemIndex < 0)
  308. {
  309. // Was first element, picking next if available.
  310. // Default to no playing item otherwise.
  311. PlayingItemIndex = SortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex;
  312. }
  313. return true;
  314. }
  315. else
  316. {
  317. // Restoring playing item.
  318. SetPlayingItemByPlaylistId(playingItem.PlaylistItemId);
  319. return false;
  320. }
  321. }
  322. else
  323. {
  324. return false;
  325. }
  326. }
  327. /// <summary>
  328. /// Moves an item in the playlist to another position.
  329. /// </summary>
  330. /// <param name="playlistItemId">The item to move.</param>
  331. /// <param name="newIndex">The new position.</param>
  332. /// <returns><c>true</c> if the item has been moved; <c>false</c> otherwise.</returns>
  333. public bool MovePlaylistItem(string playlistItemId, int newIndex)
  334. {
  335. var playlist = GetPlaylistInternal();
  336. var playingItem = GetPlayingItem();
  337. var oldIndex = -1;
  338. foreach (var item in playlist)
  339. {
  340. if (item.PlaylistItemId.Equals(playlistItemId, StringComparison.OrdinalIgnoreCase))
  341. {
  342. oldIndex = playlist.IndexOf(item);
  343. break;
  344. }
  345. }
  346. if (oldIndex < 0)
  347. {
  348. return false;
  349. }
  350. var queueItem = playlist[oldIndex];
  351. playlist.RemoveAt(oldIndex);
  352. newIndex = Math.Min(newIndex, playlist.Count);
  353. newIndex = Math.Max(newIndex, 0);
  354. playlist.Insert(newIndex, queueItem);
  355. LastChange = DateTime.UtcNow;
  356. PlayingItemIndex = playlist.IndexOf(playingItem);
  357. return true;
  358. }
  359. /// <summary>
  360. /// Resets the playlist to its initial state.
  361. /// </summary>
  362. public void Reset()
  363. {
  364. ProgressiveId = 0;
  365. SortedPlaylist.Clear();
  366. ShuffledPlaylist.Clear();
  367. PlayingItemIndex = NoPlayingItemIndex;
  368. ShuffleMode = GroupShuffleMode.Sorted;
  369. RepeatMode = GroupRepeatMode.RepeatNone;
  370. LastChange = DateTime.UtcNow;
  371. }
  372. /// <summary>
  373. /// Sets the repeat mode.
  374. /// </summary>
  375. /// <param name="mode">The new mode.</param>
  376. public void SetRepeatMode(GroupRepeatMode mode)
  377. {
  378. RepeatMode = mode;
  379. LastChange = DateTime.UtcNow;
  380. }
  381. /// <summary>
  382. /// Sets the shuffle mode.
  383. /// </summary>
  384. /// <param name="mode">The new mode.</param>
  385. public void SetShuffleMode(GroupShuffleMode mode)
  386. {
  387. if (mode.Equals(GroupShuffleMode.Shuffle))
  388. {
  389. ShufflePlaylist();
  390. }
  391. else
  392. {
  393. RestoreSortedPlaylist();
  394. }
  395. }
  396. /// <summary>
  397. /// Toggles the shuffle mode between sorted and shuffled.
  398. /// </summary>
  399. public void ToggleShuffleMode()
  400. {
  401. if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
  402. {
  403. ShufflePlaylist();
  404. }
  405. else
  406. {
  407. RestoreSortedPlaylist();
  408. }
  409. }
  410. /// <summary>
  411. /// Gets the next item in the playlist considering repeat mode and shuffle mode.
  412. /// </summary>
  413. /// <returns>The next item in the playlist.</returns>
  414. public QueueItem GetNextItemPlaylistId()
  415. {
  416. int newIndex;
  417. var playlist = GetPlaylistInternal();
  418. switch (RepeatMode)
  419. {
  420. case GroupRepeatMode.RepeatOne:
  421. newIndex = PlayingItemIndex;
  422. break;
  423. case GroupRepeatMode.RepeatAll:
  424. newIndex = PlayingItemIndex + 1;
  425. if (newIndex >= playlist.Count)
  426. {
  427. newIndex = 0;
  428. }
  429. break;
  430. default:
  431. newIndex = PlayingItemIndex + 1;
  432. break;
  433. }
  434. if (newIndex < 0 || newIndex >= playlist.Count)
  435. {
  436. return null;
  437. }
  438. return playlist[newIndex];
  439. }
  440. /// <summary>
  441. /// Sets the next item in the queue as playing item.
  442. /// </summary>
  443. /// <returns><c>true</c> if the playing item changed; <c>false</c> otherwise.</returns>
  444. public bool Next()
  445. {
  446. if (RepeatMode.Equals(GroupRepeatMode.RepeatOne))
  447. {
  448. LastChange = DateTime.UtcNow;
  449. return true;
  450. }
  451. PlayingItemIndex++;
  452. if (PlayingItemIndex >= SortedPlaylist.Count)
  453. {
  454. if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
  455. {
  456. PlayingItemIndex = 0;
  457. }
  458. else
  459. {
  460. PlayingItemIndex--;
  461. return false;
  462. }
  463. }
  464. LastChange = DateTime.UtcNow;
  465. return true;
  466. }
  467. /// <summary>
  468. /// Sets the previous item in the queue as playing item.
  469. /// </summary>
  470. /// <returns><c>true</c> if the playing item changed; <c>false</c> otherwise.</returns>
  471. public bool Previous()
  472. {
  473. if (RepeatMode.Equals(GroupRepeatMode.RepeatOne))
  474. {
  475. LastChange = DateTime.UtcNow;
  476. return true;
  477. }
  478. PlayingItemIndex--;
  479. if (PlayingItemIndex < 0)
  480. {
  481. if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
  482. {
  483. PlayingItemIndex = SortedPlaylist.Count - 1;
  484. }
  485. else
  486. {
  487. PlayingItemIndex++;
  488. return false;
  489. }
  490. }
  491. LastChange = DateTime.UtcNow;
  492. return true;
  493. }
  494. /// <summary>
  495. /// Shuffles a given list.
  496. /// </summary>
  497. /// <param name="list">The list to shuffle.</param>
  498. private void Shuffle<T>(IList<T> list)
  499. {
  500. int n = list.Count;
  501. while (n > 1)
  502. {
  503. n--;
  504. int k = randomNumberGenerator.Next(n + 1);
  505. T value = list[k];
  506. list[k] = list[n];
  507. list[n] = value;
  508. }
  509. }
  510. /// <summary>
  511. /// Gets the next available identifier.
  512. /// </summary>
  513. /// <returns>The next available identifier.</returns>
  514. private int GetNextProgressiveId()
  515. {
  516. return ProgressiveId++;
  517. }
  518. /// <summary>
  519. /// Creates a list from the array of items. Each item is given an unique playlist identifier.
  520. /// </summary>
  521. /// <returns>The list of queue items.</returns>
  522. private List<QueueItem> CreateQueueItemsFromArray(IEnumerable<Guid> items)
  523. {
  524. var list = new List<QueueItem>();
  525. foreach (var item in items)
  526. {
  527. list.Add(new QueueItem()
  528. {
  529. ItemId = item,
  530. PlaylistItemId = "syncPlayItem" + GetNextProgressiveId()
  531. });
  532. }
  533. return list;
  534. }
  535. /// <summary>
  536. /// Gets the current playlist considering the shuffle mode.
  537. /// </summary>
  538. /// <returns>The playlist.</returns>
  539. private List<QueueItem> GetPlaylistInternal()
  540. {
  541. if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
  542. {
  543. return ShuffledPlaylist;
  544. }
  545. else
  546. {
  547. return SortedPlaylist;
  548. }
  549. }
  550. /// <summary>
  551. /// Gets the current playing item, depending on the shuffle mode.
  552. /// </summary>
  553. /// <returns>The playing item.</returns>
  554. private QueueItem GetPlayingItem()
  555. {
  556. if (PlayingItemIndex == NoPlayingItemIndex)
  557. {
  558. return null;
  559. }
  560. else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
  561. {
  562. return ShuffledPlaylist[PlayingItemIndex];
  563. }
  564. else
  565. {
  566. return SortedPlaylist[PlayingItemIndex];
  567. }
  568. }
  569. }
  570. }