PlayQueueManager.cs 20 KB

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