WaitingGroupState.cs 30 KB

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