PlayQueueManager.cs 20 KB

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