WaitingGroupState.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. using System;
  2. using System.Threading;
  3. using MediaBrowser.Controller.Session;
  4. using MediaBrowser.Model.SyncPlay;
  5. using Microsoft.Extensions.Logging;
  6. namespace MediaBrowser.Controller.SyncPlay
  7. {
  8. /// <summary>
  9. /// Class WaitingGroupState.
  10. /// </summary>
  11. /// <remarks>
  12. /// Class is not thread-safe, external locking is required when accessing methods.
  13. /// </remarks>
  14. public class WaitingGroupState : AbstractGroupState
  15. {
  16. /// <summary>
  17. /// Tells the state to switch to after buffering is done.
  18. /// </summary>
  19. public bool ResumePlaying { get; set; } = false;
  20. /// <summary>
  21. /// Whether the initial state has been set.
  22. /// </summary>
  23. private bool InitialStateSet { get; set; } = false;
  24. /// <summary>
  25. /// The group state before the first ever event.
  26. /// </summary>
  27. private GroupState InitialState { get; set; }
  28. /// <summary>
  29. /// Default constructor.
  30. /// </summary>
  31. public WaitingGroupState(ILogger logger)
  32. : base(logger)
  33. {
  34. // Do nothing.
  35. }
  36. /// <inheritdoc />
  37. public override GroupState GetGroupState()
  38. {
  39. return GroupState.Waiting;
  40. }
  41. /// <inheritdoc />
  42. public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
  43. {
  44. // Save state if first event.
  45. if (!InitialStateSet)
  46. {
  47. InitialState = prevState;
  48. InitialStateSet = true;
  49. }
  50. if (prevState.Equals(GroupState.Playing)) {
  51. ResumePlaying = true;
  52. // Pause group and compute the media playback position.
  53. var currentTime = DateTime.UtcNow;
  54. var elapsedTime = currentTime - context.LastActivity;
  55. context.LastActivity = currentTime;
  56. // Elapsed time is negative if event happens
  57. // during the delay added to account for latency.
  58. // In this phase clients haven't started the playback yet.
  59. // In other words, LastActivity is in the future,
  60. // when playback unpause is supposed to happen.
  61. // Seek only if playback actually started.
  62. context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
  63. }
  64. // Prepare new session.
  65. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
  66. var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
  67. context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
  68. context.SetBuffering(session, true);
  69. // Send pause command to all non-buffering sessions.
  70. var command = context.NewSyncPlayCommand(SendCommandType.Pause);
  71. context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
  72. }
  73. /// <inheritdoc />
  74. public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken)
  75. {
  76. // Save state if first event.
  77. if (!InitialStateSet)
  78. {
  79. InitialState = prevState;
  80. InitialStateSet = true;
  81. }
  82. context.SetBuffering(session, false);
  83. if (!context.IsBuffering())
  84. {
  85. if (ResumePlaying)
  86. {
  87. // Client, that was buffering, left the group.
  88. var playingState = new PlayingGroupState(_logger);
  89. context.SetState(playingState);
  90. var unpauseRequest = new UnpauseGroupRequest();
  91. playingState.HandleRequest(context, GetGroupState(), unpauseRequest, session, cancellationToken);
  92. _logger.LogDebug("SessionLeaving: {0} left the group {1}, notifying others to resume.", session.Id.ToString(), context.GroupId.ToString());
  93. }
  94. else
  95. {
  96. // Group is ready, returning to previous state.
  97. var pausedState = new PausedGroupState(_logger);
  98. context.SetState(pausedState);
  99. _logger.LogDebug("SessionLeaving: {0} left the group {1}, returning to previous state.", session.Id.ToString(), context.GroupId.ToString());
  100. }
  101. }
  102. }
  103. /// <inheritdoc />
  104. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  105. {
  106. // Save state if first event.
  107. if (!InitialStateSet)
  108. {
  109. InitialState = prevState;
  110. InitialStateSet = true;
  111. }
  112. ResumePlaying = true;
  113. var setQueueStatus = context.SetPlayQueue(request.PlayingQueue, request.PlayingItemPosition, request.StartPositionTicks);
  114. if (!setQueueStatus)
  115. {
  116. _logger.LogError("HandleRequest: {0} in group {1}, unable to set playing queue.", request.GetRequestType(), context.GroupId.ToString());
  117. // Ignore request and return to previous state.
  118. ISyncPlayState newState;
  119. switch (prevState)
  120. {
  121. case GroupState.Playing:
  122. newState = new PlayingGroupState(_logger);
  123. break;
  124. case GroupState.Paused:
  125. newState = new PausedGroupState(_logger);
  126. break;
  127. default:
  128. newState = new IdleGroupState(_logger);
  129. break;
  130. }
  131. context.SetState(newState);
  132. return;
  133. }
  134. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
  135. var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
  136. context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
  137. // Reset status of sessions and await for all Ready events.
  138. context.SetAllBuffering(true);
  139. _logger.LogDebug("HandleRequest: {0} in group {1}, {2} set a new play queue.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  140. }
  141. /// <inheritdoc />
  142. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  143. {
  144. // Save state if first event.
  145. if (!InitialStateSet)
  146. {
  147. InitialState = prevState;
  148. InitialStateSet = true;
  149. }
  150. ResumePlaying = true;
  151. var result = context.SetPlayingItem(request.PlaylistItemId);
  152. if (result)
  153. {
  154. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
  155. var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
  156. context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
  157. // Reset status of sessions and await for all Ready events.
  158. context.SetAllBuffering(true);
  159. }
  160. else
  161. {
  162. // Return to old state.
  163. ISyncPlayState newState;
  164. switch (prevState)
  165. {
  166. case GroupState.Playing:
  167. newState = new PlayingGroupState(_logger);
  168. break;
  169. case GroupState.Paused:
  170. newState = new PausedGroupState(_logger);
  171. break;
  172. default:
  173. newState = new IdleGroupState(_logger);
  174. break;
  175. }
  176. context.SetState(newState);
  177. _logger.LogDebug("HandleRequest: {0} in group {1}, unable to change current playing item.", request.GetRequestType(), context.GroupId.ToString());
  178. }
  179. }
  180. /// <inheritdoc />
  181. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  182. {
  183. // Save state if first event.
  184. if (!InitialStateSet)
  185. {
  186. InitialState = prevState;
  187. InitialStateSet = true;
  188. }
  189. if (prevState.Equals(GroupState.Idle))
  190. {
  191. ResumePlaying = true;
  192. context.RestartCurrentItem();
  193. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
  194. var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
  195. context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
  196. // Reset status of sessions and await for all Ready events.
  197. context.SetAllBuffering(true);
  198. _logger.LogDebug("HandleRequest: {0} in group {1}, waiting for all ready events.", request.GetRequestType(), context.GroupId.ToString());
  199. }
  200. else
  201. {
  202. if (ResumePlaying)
  203. {
  204. _logger.LogDebug("HandleRequest: {0} in group {1}, ignoring sessions that are not ready and forcing the playback to start.", request.GetRequestType(), context.GroupId.ToString());
  205. // An Unpause request is forcing the playback to start, ignoring sessions that are not ready.
  206. context.SetAllBuffering(false);
  207. // Change state.
  208. var playingState = new PlayingGroupState(_logger);
  209. playingState.IgnoreBuffering = true;
  210. context.SetState(playingState);
  211. playingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
  212. }
  213. else
  214. {
  215. // Group would have gone to paused state, now will go to playing state when ready.
  216. ResumePlaying = true;
  217. // Notify relevant state change event.
  218. SendGroupStateUpdate(context, request, session, cancellationToken);
  219. }
  220. }
  221. }
  222. /// <inheritdoc />
  223. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  224. {
  225. // Save state if first event.
  226. if (!InitialStateSet)
  227. {
  228. InitialState = prevState;
  229. InitialStateSet = true;
  230. }
  231. // Wait for sessions to be ready, then switch to paused state.
  232. ResumePlaying = false;
  233. // Notify relevant state change event.
  234. SendGroupStateUpdate(context, request, session, cancellationToken);
  235. }
  236. /// <inheritdoc />
  237. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  238. {
  239. // Save state if first event.
  240. if (!InitialStateSet)
  241. {
  242. InitialState = prevState;
  243. InitialStateSet = true;
  244. }
  245. // Change state.
  246. var idleState = new IdleGroupState(_logger);
  247. context.SetState(idleState);
  248. idleState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
  249. }
  250. /// <inheritdoc />
  251. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  252. {
  253. // Save state if first event.
  254. if (!InitialStateSet)
  255. {
  256. InitialState = prevState;
  257. InitialStateSet = true;
  258. }
  259. if (prevState.Equals(GroupState.Playing))
  260. {
  261. ResumePlaying = true;
  262. }
  263. else if(prevState.Equals(GroupState.Paused))
  264. {
  265. ResumePlaying = false;
  266. }
  267. // Sanitize PositionTicks.
  268. var ticks = context.SanitizePositionTicks(request.PositionTicks);
  269. // Seek.
  270. context.PositionTicks = ticks;
  271. context.LastActivity = DateTime.UtcNow;
  272. var command = context.NewSyncPlayCommand(SendCommandType.Seek);
  273. context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
  274. // Reset status of sessions and await for all Ready events.
  275. context.SetAllBuffering(true);
  276. // Notify relevant state change event.
  277. SendGroupStateUpdate(context, request, session, cancellationToken);
  278. }
  279. /// <inheritdoc />
  280. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  281. {
  282. // Save state if first event.
  283. if (!InitialStateSet)
  284. {
  285. InitialState = prevState;
  286. InitialStateSet = true;
  287. }
  288. // Make sure the client is playing the correct item.
  289. if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
  290. {
  291. _logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  292. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
  293. var updateSession = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
  294. context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
  295. context.SetBuffering(session, true);
  296. return;
  297. }
  298. if (prevState.Equals(GroupState.Playing))
  299. {
  300. // Resume playback when all ready.
  301. ResumePlaying = true;
  302. context.SetBuffering(session, true);
  303. // Pause group and compute the media playback position.
  304. var currentTime = DateTime.UtcNow;
  305. var elapsedTime = currentTime - context.LastActivity;
  306. context.LastActivity = currentTime;
  307. // Elapsed time is negative if event happens
  308. // during the delay added to account for latency.
  309. // In this phase clients haven't started the playback yet.
  310. // In other words, LastActivity is in the future,
  311. // when playback unpause is supposed to happen.
  312. // Seek only if playback actually started.
  313. context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
  314. // Send pause command to all non-buffering sessions.
  315. var command = context.NewSyncPlayCommand(SendCommandType.Pause);
  316. context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
  317. }
  318. else if (prevState.Equals(GroupState.Paused))
  319. {
  320. // Don't resume playback when all ready.
  321. ResumePlaying = false;
  322. context.SetBuffering(session, true);
  323. // Send pause command to buffering session.
  324. var command = context.NewSyncPlayCommand(SendCommandType.Pause);
  325. context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
  326. }
  327. else if (prevState.Equals(GroupState.Waiting))
  328. {
  329. // Another session is now buffering.
  330. context.SetBuffering(session, true);
  331. if (!ResumePlaying)
  332. {
  333. // Force update for this session that should be paused.
  334. var command = context.NewSyncPlayCommand(SendCommandType.Pause);
  335. context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
  336. }
  337. }
  338. // Notify relevant state change event.
  339. SendGroupStateUpdate(context, request, session, cancellationToken);
  340. }
  341. /// <inheritdoc />
  342. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  343. {
  344. // Save state if first event.
  345. if (!InitialStateSet)
  346. {
  347. InitialState = prevState;
  348. InitialStateSet = true;
  349. }
  350. // Make sure the client is playing the correct item.
  351. if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
  352. {
  353. _logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  354. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
  355. var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
  356. context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
  357. context.SetBuffering(session, true);
  358. return;
  359. }
  360. var requestTicks = context.SanitizePositionTicks(request.PositionTicks);
  361. var currentTime = DateTime.UtcNow;
  362. var elapsedTime = currentTime - request.When;
  363. if (!request.IsPlaying)
  364. {
  365. elapsedTime = TimeSpan.Zero;
  366. }
  367. var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime;
  368. var delayTicks = context.PositionTicks - clientPosition.Ticks;
  369. if (delayTicks > TimeSpan.FromSeconds(5).Ticks)
  370. {
  371. // The client is really behind, other participants will have to wait a lot of time...
  372. _logger.LogWarning("HandleRequest: {0} in group {1}, {2} got lost in time.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  373. }
  374. if (ResumePlaying)
  375. {
  376. // Handle case where session reported as ready but in reality
  377. // it has no clue of the real position nor the playback state.
  378. if (!request.IsPlaying && Math.Abs(context.PositionTicks - requestTicks) > TimeSpan.FromSeconds(0.5).Ticks) {
  379. // Session not ready at all.
  380. context.SetBuffering(session, true);
  381. // Correcting session's position.
  382. var command = context.NewSyncPlayCommand(SendCommandType.Seek);
  383. context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
  384. // Notify relevant state change event.
  385. SendGroupStateUpdate(context, request, session, cancellationToken);
  386. _logger.LogDebug("HandleRequest: {0} in group {1}, {2} got lost in time, correcting.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  387. return;
  388. }
  389. // Session is ready.
  390. context.SetBuffering(session, false);
  391. if (context.IsBuffering())
  392. {
  393. // Others are still buffering, tell this client to pause when ready.
  394. var command = context.NewSyncPlayCommand(SendCommandType.Pause);
  395. var pauseAtTime = currentTime.AddTicks(delayTicks);
  396. command.When = context.DateToUTCString(pauseAtTime);
  397. context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
  398. _logger.LogDebug("HandleRequest: {0} in group {1}, others still buffering, {2} will pause when ready.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  399. }
  400. else
  401. {
  402. // If all ready, then start playback.
  403. // Let other clients resume as soon as the buffering client catches up.
  404. if (delayTicks > context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond)
  405. {
  406. // Client that was buffering is recovering, notifying others to resume.
  407. context.LastActivity = currentTime.AddTicks(delayTicks);
  408. var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
  409. var filter = SyncPlayBroadcastType.AllExceptCurrentSession;
  410. if (!request.IsPlaying)
  411. {
  412. filter = SyncPlayBroadcastType.AllGroup;
  413. }
  414. context.SendCommand(session, filter, command, cancellationToken);
  415. _logger.LogDebug("HandleRequest: {0} in group {1}, {2} is recovering, notifying others to resume.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  416. }
  417. else
  418. {
  419. // Client, that was buffering, resumed playback but did not update others in time.
  420. delayTicks = context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond;
  421. delayTicks = Math.Max(delayTicks, context.DefaultPing);
  422. context.LastActivity = currentTime.AddTicks(delayTicks);
  423. var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
  424. context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
  425. _logger.LogDebug("HandleRequest: {0} in group {1}, {2} resumed playback but did not update others in time.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  426. }
  427. // Change state.
  428. var playingState = new PlayingGroupState(_logger);
  429. context.SetState(playingState);
  430. playingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
  431. }
  432. }
  433. else
  434. {
  435. // Check that session is really ready, tollerate half second difference to account for player imperfections.
  436. if (Math.Abs(context.PositionTicks - requestTicks) > TimeSpan.FromSeconds(0.5).Ticks)
  437. {
  438. // Session still not ready.
  439. context.SetBuffering(session, true);
  440. // Session is seeking to wrong position, correcting.
  441. var command = context.NewSyncPlayCommand(SendCommandType.Seek);
  442. context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
  443. // Notify relevant state change event.
  444. SendGroupStateUpdate(context, request, session, cancellationToken);
  445. _logger.LogDebug("HandleRequest: {0} in group {1}, {2} was seeking to wrong position, correcting.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  446. return;
  447. } else {
  448. // Session is ready.
  449. context.SetBuffering(session, false);
  450. }
  451. if (!context.IsBuffering())
  452. {
  453. // Group is ready, returning to previous state.
  454. var pausedState = new PausedGroupState(_logger);
  455. context.SetState(pausedState);
  456. if (InitialState.Equals(GroupState.Playing))
  457. {
  458. // Group went from playing to waiting state and a pause request occured while waiting.
  459. var pauserequest = new PauseGroupRequest();
  460. pausedState.HandleRequest(context, GetGroupState(), pauserequest, session, cancellationToken);
  461. }
  462. else if (InitialState.Equals(GroupState.Paused))
  463. {
  464. pausedState.HandleRequest(context, GetGroupState(), request, session, cancellationToken);
  465. }
  466. _logger.LogDebug("HandleRequest: {0} in group {1}, {2} is ready, returning to previous state.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString());
  467. }
  468. }
  469. }
  470. /// <inheritdoc />
  471. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  472. {
  473. // Save state if first event.
  474. if (!InitialStateSet)
  475. {
  476. InitialState = prevState;
  477. InitialStateSet = true;
  478. }
  479. ResumePlaying = true;
  480. // Make sure the client knows the playing item, to avoid duplicate requests.
  481. if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
  482. {
  483. _logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.GetRequestType(), context.GroupId.ToString());
  484. return;
  485. }
  486. var newItem = context.NextItemInQueue();
  487. if (newItem)
  488. {
  489. // Send playing-queue update.
  490. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NextTrack);
  491. var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
  492. context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
  493. // Reset status of sessions and await for all Ready events.
  494. context.SetAllBuffering(true);
  495. }
  496. else
  497. {
  498. // Return to old state.
  499. ISyncPlayState newState;
  500. switch (prevState)
  501. {
  502. case GroupState.Playing:
  503. newState = new PlayingGroupState(_logger);
  504. break;
  505. case GroupState.Paused:
  506. newState = new PausedGroupState(_logger);
  507. break;
  508. default:
  509. newState = new IdleGroupState(_logger);
  510. break;
  511. }
  512. context.SetState(newState);
  513. _logger.LogDebug("HandleRequest: {0} in group {1}, no next track available.", request.GetRequestType(), context.GroupId.ToString());
  514. }
  515. }
  516. /// <inheritdoc />
  517. public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken)
  518. {
  519. // Save state if first event.
  520. if (!InitialStateSet)
  521. {
  522. InitialState = prevState;
  523. InitialStateSet = true;
  524. }
  525. ResumePlaying = true;
  526. // Make sure the client knows the playing item, to avoid duplicate requests.
  527. if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
  528. {
  529. _logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist identifier.", request.GetRequestType(), context.GroupId.ToString());
  530. return;
  531. }
  532. var newItem = context.PreviousItemInQueue();
  533. if (newItem)
  534. {
  535. // Send playing-queue update.
  536. var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.PreviousTrack);
  537. var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate);
  538. context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
  539. // Reset status of sessions and await for all Ready events.
  540. context.SetAllBuffering(true);
  541. }
  542. else
  543. {
  544. // Return to old state.
  545. ISyncPlayState newState;
  546. switch (prevState)
  547. {
  548. case GroupState.Playing:
  549. newState = new PlayingGroupState(_logger);
  550. break;
  551. case GroupState.Paused:
  552. newState = new PausedGroupState(_logger);
  553. break;
  554. default:
  555. newState = new IdleGroupState(_logger);
  556. break;
  557. }
  558. context.SetState(newState);
  559. _logger.LogDebug("HandleRequest: {0} in group {1}, no previous track available.", request.GetRequestType(), context.GroupId.ToString());
  560. }
  561. }
  562. }
  563. }