WaitingGroupState.cs 30 KB

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