WaitingGroupState.cs 31 KB

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