PlayQueueManager.cs 19 KB

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