< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.SyncPlay.GroupStates.WaitingGroupState
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 283
Coverable lines: 283
Total lines: 681
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 106
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 AM Line coverage: 0% (0/282) Branch coverage: 0% (0/106) Total lines: 6805/22/2026 - 12:15:17 AM Line coverage: 0% (0/283) Branch coverage: 0% (0/106) Total lines: 681 2/13/2026 - 12:11:21 AM Line coverage: 0% (0/282) Branch coverage: 0% (0/106) Total lines: 6805/22/2026 - 12:15:17 AM Line coverage: 0% (0/283) Branch coverage: 0% (0/106) Total lines: 681

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
SessionJoined(...)0%2040%
SessionLeaving(...)0%4260%
HandleRequest(...)0%7280%
HandleRequest(...)0%7280%
HandleRequest(...)0%4260%
HandleRequest(...)0%620%
HandleRequest(...)0%620%
HandleRequest(...)0%4260%
HandleRequest(...)0%156120%
HandleRequest(...)0%812280%
HandleRequest(...)0%110100%
HandleRequest(...)0%110100%
HandleRequest(...)0%2040%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Threading;
 5using MediaBrowser.Controller.Session;
 6using MediaBrowser.Controller.SyncPlay.PlaybackRequests;
 7using MediaBrowser.Model.SyncPlay;
 8using Microsoft.Extensions.Logging;
 9
 10namespace MediaBrowser.Controller.SyncPlay.GroupStates
 11{
 12    /// <summary>
 13    /// Class WaitingGroupState.
 14    /// </summary>
 15    /// <remarks>
 16    /// Class is not thread-safe, external locking is required when accessing methods.
 17    /// </remarks>
 18    public class WaitingGroupState : AbstractGroupState
 19    {
 20        /// <summary>
 21        /// The logger.
 22        /// </summary>
 23        private readonly ILogger<WaitingGroupState> _logger;
 24
 25        /// <summary>
 26        /// Initializes a new instance of the <see cref="WaitingGroupState"/> class.
 27        /// </summary>
 28        /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
 29        public WaitingGroupState(ILoggerFactory loggerFactory)
 030            : base(loggerFactory)
 31        {
 032            _logger = LoggerFactory.CreateLogger<WaitingGroupState>();
 033        }
 34
 35        /// <inheritdoc />
 36        public override GroupStateType Type { get; } = GroupStateType.Waiting;
 37
 38        /// <summary>
 39        /// Gets or sets a value indicating whether playback should resume when group is ready.
 40        /// </summary>
 41        public bool ResumePlaying { get; set; } = false;
 42
 43        /// <summary>
 44        /// Gets or sets a value indicating whether the initial state has been set.
 45        /// </summary>
 46        private bool InitialStateSet { get; set; } = false;
 47
 48        /// <summary>
 49        /// Gets or sets the group state before the first ever event.
 50        /// </summary>
 51        private GroupStateType InitialState { get; set; }
 52
 53        /// <inheritdoc />
 54        public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, Ca
 55        {
 56            // Save state if first event.
 057            if (!InitialStateSet)
 58            {
 059                InitialState = prevState;
 060                InitialStateSet = true;
 61            }
 62
 063            if (prevState.Equals(GroupStateType.Playing))
 64            {
 065                ResumePlaying = true;
 66                // Pause group and compute the media playback position.
 067                var currentTime = DateTime.UtcNow;
 068                var elapsedTime = currentTime - context.LastActivity;
 069                context.LastActivity = currentTime;
 70                // Elapsed time is negative if event happens
 71                // during the delay added to account for latency.
 72                // In this phase clients haven't started the playback yet.
 73                // In other words, LastActivity is in the future,
 74                // when playback unpause is supposed to happen.
 75                // Seek only if playback actually started.
 076                context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
 77            }
 78
 79            // Prepare new session.
 080            var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
 081            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 082            context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
 83
 084            context.SetBuffering(session, true);
 85
 86            // Send pause command to all non-buffering sessions.
 087            var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 088            context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
 089        }
 90
 91        /// <inheritdoc />
 92        public override void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, C
 93        {
 94            // Save state if first event.
 095            if (!InitialStateSet)
 96            {
 097                InitialState = prevState;
 098                InitialStateSet = true;
 99            }
 100
 0101            context.SetBuffering(session, false);
 102
 0103            if (!context.IsBuffering())
 104            {
 0105                if (ResumePlaying)
 106                {
 0107                    _logger.LogDebug("Session {SessionId} left group {GroupId}, notifying others to resume.", session.Id
 108
 109                    // Client, that was buffering, left the group.
 0110                    var playingState = new PlayingGroupState(LoggerFactory);
 0111                    context.SetState(playingState);
 0112                    var unpauseRequest = new UnpauseGroupRequest();
 0113                    playingState.HandleRequest(unpauseRequest, context, Type, session, cancellationToken);
 114                }
 115                else
 116                {
 0117                    _logger.LogDebug("Session {SessionId} left group {GroupId}, returning to previous state.", session.I
 118
 119                    // Group is ready, returning to previous state.
 0120                    var pausedState = new PausedGroupState(LoggerFactory);
 0121                    context.SetState(pausedState);
 122                }
 123            }
 0124        }
 125
 126        /// <inheritdoc />
 127        public override void HandleRequest(PlayGroupRequest request, IGroupStateContext context, GroupStateType prevStat
 128        {
 129            // Save state if first event.
 0130            if (!InitialStateSet)
 131            {
 0132                InitialState = prevState;
 0133                InitialStateSet = true;
 134            }
 135
 0136            ResumePlaying = true;
 137
 0138            var setQueueStatus = context.SetPlayQueue(request.PlayingQueue, request.PlayingItemPosition, request.StartPo
 0139            if (!setQueueStatus)
 140            {
 0141                _logger.LogError("Unable to set playing queue in group {GroupId}.", context.GroupId.ToString());
 142
 143                // Ignore request and return to previous state.
 0144                IGroupState newState = prevState switch
 0145                {
 0146                    GroupStateType.Playing => new PlayingGroupState(LoggerFactory),
 0147                    GroupStateType.Paused => new PausedGroupState(LoggerFactory),
 0148                    _ => new IdleGroupState(LoggerFactory)
 0149                };
 150
 0151                context.SetState(newState);
 0152                return;
 153            }
 154
 0155            var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
 0156            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0157            context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 158
 159            // Reset status of sessions and await for all Ready events.
 0160            context.SetAllBuffering(true);
 161
 0162            _logger.LogDebug("Session {SessionId} set a new play queue in group {GroupId}.", session.Id, context.GroupId
 0163        }
 164
 165        /// <inheritdoc />
 166        public override void HandleRequest(SetPlaylistItemGroupRequest request, IGroupStateContext context, GroupStateTy
 167        {
 168            // Save state if first event.
 0169            if (!InitialStateSet)
 170            {
 0171                InitialState = prevState;
 0172                InitialStateSet = true;
 173            }
 174
 0175            ResumePlaying = true;
 176
 0177            var result = context.SetPlayingItem(request.PlaylistItemId);
 0178            if (result)
 179            {
 0180                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
 0181                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0182                context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 183
 184                // Reset status of sessions and await for all Ready events.
 0185                context.SetAllBuffering(true);
 186            }
 187            else
 188            {
 189                // Return to old state.
 0190                IGroupState newState = prevState switch
 0191                {
 0192                    GroupStateType.Playing => new PlayingGroupState(LoggerFactory),
 0193                    GroupStateType.Paused => new PausedGroupState(LoggerFactory),
 0194                    _ => new IdleGroupState(LoggerFactory)
 0195                };
 196
 0197                context.SetState(newState);
 198
 0199                _logger.LogDebug("Unable to change current playing item in group {GroupId}.", context.GroupId.ToString()
 200            }
 0201        }
 202
 203        /// <inheritdoc />
 204        public override void HandleRequest(UnpauseGroupRequest request, IGroupStateContext context, GroupStateType prevS
 205        {
 206            // Save state if first event.
 0207            if (!InitialStateSet)
 208            {
 0209                InitialState = prevState;
 0210                InitialStateSet = true;
 211            }
 212
 0213            if (prevState.Equals(GroupStateType.Idle))
 214            {
 0215                ResumePlaying = true;
 0216                context.RestartCurrentItem();
 217
 0218                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
 0219                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0220                context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 221
 222                // Reset status of sessions and await for all Ready events.
 0223                context.SetAllBuffering(true);
 224
 0225                _logger.LogDebug("Group {GroupId} is waiting for all ready events.", context.GroupId.ToString());
 226            }
 227            else
 228            {
 0229                if (ResumePlaying)
 230                {
 0231                    _logger.LogDebug("Forcing the playback to start in group {GroupId}. Group-wait is disabled until nex
 232
 233                    // An Unpause request is forcing the playback to start, ignoring sessions that are not ready.
 0234                    context.SetAllBuffering(false);
 235
 236                    // Change state.
 0237                    var playingState = new PlayingGroupState(LoggerFactory)
 0238                    {
 0239                        IgnoreBuffering = true
 0240                    };
 0241                    context.SetState(playingState);
 0242                    playingState.HandleRequest(request, context, Type, session, cancellationToken);
 243                }
 244                else
 245                {
 246                    // Group would have gone to paused state, now will go to playing state when ready.
 0247                    ResumePlaying = true;
 248
 249                    // Notify relevant state change event.
 0250                    SendGroupStateUpdate(context, request, session, cancellationToken);
 251                }
 252            }
 0253        }
 254
 255        /// <inheritdoc />
 256        public override void HandleRequest(PauseGroupRequest request, IGroupStateContext context, GroupStateType prevSta
 257        {
 258            // Save state if first event.
 0259            if (!InitialStateSet)
 260            {
 0261                InitialState = prevState;
 0262                InitialStateSet = true;
 263            }
 264
 265            // Wait for sessions to be ready, then switch to paused state.
 0266            ResumePlaying = false;
 267
 268            // Notify relevant state change event.
 0269            SendGroupStateUpdate(context, request, session, cancellationToken);
 0270        }
 271
 272        /// <inheritdoc />
 273        public override void HandleRequest(StopGroupRequest request, IGroupStateContext context, GroupStateType prevStat
 274        {
 275            // Save state if first event.
 0276            if (!InitialStateSet)
 277            {
 0278                InitialState = prevState;
 0279                InitialStateSet = true;
 280            }
 281
 282            // Change state.
 0283            var idleState = new IdleGroupState(LoggerFactory);
 0284            context.SetState(idleState);
 0285            idleState.HandleRequest(request, context, Type, session, cancellationToken);
 0286        }
 287
 288        /// <inheritdoc />
 289        public override void HandleRequest(SeekGroupRequest request, IGroupStateContext context, GroupStateType prevStat
 290        {
 291            // Save state if first event.
 0292            if (!InitialStateSet)
 293            {
 0294                InitialState = prevState;
 0295                InitialStateSet = true;
 296            }
 297
 0298            if (prevState.Equals(GroupStateType.Playing))
 299            {
 0300                ResumePlaying = true;
 301            }
 0302            else if (prevState.Equals(GroupStateType.Paused))
 303            {
 0304                ResumePlaying = false;
 305            }
 306
 307            // Sanitize PositionTicks.
 0308            var ticks = context.SanitizePositionTicks(request.PositionTicks);
 309
 310            // Seek.
 0311            context.PositionTicks = ticks;
 0312            context.LastActivity = DateTime.UtcNow;
 313
 0314            var command = context.NewSyncPlayCommand(SendCommandType.Seek);
 0315            context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
 316
 317            // Reset status of sessions and await for all Ready events.
 0318            context.SetAllBuffering(true);
 319
 320            // Notify relevant state change event.
 0321            SendGroupStateUpdate(context, request, session, cancellationToken);
 0322        }
 323
 324        /// <inheritdoc />
 325        public override void HandleRequest(BufferGroupRequest request, IGroupStateContext context, GroupStateType prevSt
 326        {
 327            // Save state if first event.
 0328            if (!InitialStateSet)
 329            {
 0330                InitialState = prevState;
 0331                InitialStateSet = true;
 332            }
 333
 334            // Make sure the client is playing the correct item.
 0335            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
 336            {
 0337                _logger.LogDebug("Session {SessionId} reported wrong playlist item in group {GroupId}.", session.Id, con
 338
 0339                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
 0340                var updateSession = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0341                context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken)
 0342                context.SetBuffering(session, true);
 343
 0344                return;
 345            }
 346
 0347            if (prevState.Equals(GroupStateType.Playing))
 348            {
 349                // Resume playback when all ready.
 0350                ResumePlaying = true;
 351
 0352                context.SetBuffering(session, true);
 353
 354                // Pause group and compute the media playback position.
 0355                var currentTime = DateTime.UtcNow;
 0356                var elapsedTime = currentTime - context.LastActivity;
 0357                context.LastActivity = currentTime;
 358                // Elapsed time is negative if event happens
 359                // during the delay added to account for latency.
 360                // In this phase clients haven't started the playback yet.
 361                // In other words, LastActivity is in the future,
 362                // when playback unpause is supposed to happen.
 363                // Seek only if playback actually started.
 0364                context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
 365
 366                // Send pause command to all non-buffering sessions.
 0367                var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 0368                context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
 369            }
 0370            else if (prevState.Equals(GroupStateType.Paused))
 371            {
 372                // Don't resume playback when all ready.
 0373                ResumePlaying = false;
 374
 0375                context.SetBuffering(session, true);
 376
 377                // Send pause command to buffering session.
 0378                var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 0379                context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 380            }
 0381            else if (prevState.Equals(GroupStateType.Waiting))
 382            {
 383                // Another session is now buffering.
 0384                context.SetBuffering(session, true);
 385
 0386                if (!ResumePlaying)
 387                {
 388                    // Force update for this session that should be paused.
 0389                    var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 0390                    context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 391                }
 392            }
 393
 394            // Notify relevant state change event.
 0395            SendGroupStateUpdate(context, request, session, cancellationToken);
 0396        }
 397
 398        /// <inheritdoc />
 399        public override void HandleRequest(ReadyGroupRequest request, IGroupStateContext context, GroupStateType prevSta
 400        {
 401            // Save state if first event.
 0402            if (!InitialStateSet)
 403            {
 0404                InitialState = prevState;
 0405                InitialStateSet = true;
 406            }
 407
 408            // Make sure the client is playing the correct item.
 0409            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
 410            {
 0411                _logger.LogDebug("Session {SessionId} reported wrong playlist item in group {GroupId}.", session.Id, con
 412
 0413                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
 0414                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0415                context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
 0416                context.SetBuffering(session, true);
 417
 0418                return;
 419            }
 420
 421            // Compute elapsed time between the client reported time and now.
 422            // Elapsed time is used to estimate the client position when playback is unpaused.
 423            // Ideally, the request is received and handled without major delays.
 424            // However, to avoid waiting indefinitely when a client is not reporting a correct time,
 425            // the elapsed time is ignored after a certain threshold.
 0426            var currentTime = DateTime.UtcNow;
 0427            var elapsedTime = currentTime.Subtract(request.When);
 0428            var timeSyncThresholdTicks = TimeSpan.FromMilliseconds(context.TimeSyncOffset).Ticks;
 0429            if (Math.Abs(elapsedTime.Ticks) > timeSyncThresholdTicks)
 430            {
 0431                _logger.LogWarning("Session {SessionId} is not time syncing properly. Ignoring elapsed time.", session.I
 432
 0433                elapsedTime = TimeSpan.Zero;
 434            }
 435
 436            // Ignore elapsed time if client is paused.
 0437            if (!request.IsPlaying)
 438            {
 0439                elapsedTime = TimeSpan.Zero;
 440            }
 441
 0442            var requestTicks = context.SanitizePositionTicks(request.PositionTicks);
 0443            var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime;
 0444            var delayTicks = context.PositionTicks - clientPosition.Ticks;
 0445            var maxPlaybackOffsetTicks = TimeSpan.FromMilliseconds(context.MaxPlaybackOffset).Ticks;
 446
 0447            _logger.LogDebug("Session {SessionId} is at {PositionTicks} (delay of {Delay} seconds) in group {GroupId}.",
 448
 0449            if (ResumePlaying)
 450            {
 451                // Handle case where session reported as ready but in reality
 452                // it has no clue of the real position nor the playback state.
 0453                if (!request.IsPlaying && Math.Abs(delayTicks) > maxPlaybackOffsetTicks)
 454                {
 455                    // Session not ready at all.
 0456                    context.SetBuffering(session, true);
 457
 458                    // Correcting session's position.
 0459                    var command = context.NewSyncPlayCommand(SendCommandType.Seek);
 0460                    context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 461
 462                    // Notify relevant state change event.
 0463                    SendGroupStateUpdate(context, request, session, cancellationToken);
 464
 0465                    _logger.LogWarning("Session {SessionId} got lost in time, correcting.", session.Id);
 0466                    return;
 467                }
 468
 469                // Session is ready.
 0470                context.SetBuffering(session, false);
 471
 0472                if (context.IsBuffering())
 473                {
 474                    // Others are still buffering, tell this client to pause when ready.
 0475                    var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 0476                    command.When = currentTime.AddTicks(delayTicks);
 0477                    context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 478
 0479                    _logger.LogInformation("Session {SessionId} will pause when ready in {Delay} seconds. Group {GroupId
 480                }
 481                else
 482                {
 483                    // If all ready, then start playback.
 484                    // Let other clients resume as soon as the buffering client catches up.
 0485                    if (delayTicks > context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond)
 486                    {
 487                        // Client that was buffering is recovering, notifying others to resume.
 0488                        context.LastActivity = currentTime.AddTicks(delayTicks);
 0489                        var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
 0490                        var filter = SyncPlayBroadcastType.AllExceptCurrentSession;
 0491                        if (!request.IsPlaying)
 492                        {
 0493                            filter = SyncPlayBroadcastType.AllGroup;
 494                        }
 495
 0496                        context.SendCommand(session, filter, command, cancellationToken);
 497
 0498                        _logger.LogInformation("Session {SessionId} is recovering, group {GroupId} will resume in {Delay
 499                    }
 500                    else
 501                    {
 502                        // Client, that was buffering, resumed playback but did not update others in time.
 0503                        delayTicks = context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond;
 0504                        delayTicks = Math.Max(delayTicks, context.DefaultPing);
 505
 0506                        context.LastActivity = currentTime.AddTicks(delayTicks);
 507
 0508                        var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
 0509                        context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
 510
 0511                        _logger.LogWarning("Session {SessionId} resumed playback, group {GroupId} has {Delay} seconds to
 512                    }
 513
 514                    // Change state.
 0515                    var playingState = new PlayingGroupState(LoggerFactory);
 0516                    context.SetState(playingState);
 0517                    playingState.HandleRequest(request, context, Type, session, cancellationToken);
 518                }
 519            }
 520            else
 521            {
 522                // Check that session is really ready, tolerate player imperfections under a certain threshold.
 0523                if (Math.Abs(context.PositionTicks - requestTicks) > maxPlaybackOffsetTicks)
 524                {
 525                    // Session still not ready.
 0526                    context.SetBuffering(session, true);
 527                    // Session is seeking to wrong position, correcting.
 0528                    var command = context.NewSyncPlayCommand(SendCommandType.Seek);
 0529                    context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 530
 531                    // Notify relevant state change event.
 0532                    SendGroupStateUpdate(context, request, session, cancellationToken);
 533
 0534                    _logger.LogWarning("Session {SessionId} is seeking to wrong position, correcting.", session.Id);
 0535                    return;
 536                }
 537
 538                // Session is ready.
 0539                context.SetBuffering(session, false);
 540
 0541                if (!context.IsBuffering())
 542                {
 0543                    _logger.LogDebug("Session {SessionId} is ready, group {GroupId} is ready.", session.Id, context.Grou
 544
 545                    // Group is ready, returning to previous state.
 0546                    var pausedState = new PausedGroupState(LoggerFactory);
 0547                    context.SetState(pausedState);
 548
 0549                    if (InitialState.Equals(GroupStateType.Playing))
 550                    {
 551                        // Group went from playing to waiting state and a pause request occurred while waiting.
 0552                        var pauseRequest = new PauseGroupRequest();
 0553                        pausedState.HandleRequest(pauseRequest, context, Type, session, cancellationToken);
 554                    }
 0555                    else if (InitialState.Equals(GroupStateType.Paused))
 556                    {
 0557                        pausedState.HandleRequest(request, context, Type, session, cancellationToken);
 558                    }
 559                }
 560            }
 0561        }
 562
 563        /// <inheritdoc />
 564        public override void HandleRequest(NextItemGroupRequest request, IGroupStateContext context, GroupStateType prev
 565        {
 566            // Save state if first event.
 0567            if (!InitialStateSet)
 568            {
 0569                InitialState = prevState;
 0570                InitialStateSet = true;
 571            }
 572
 0573            ResumePlaying = true;
 574
 575            // Make sure the client knows the playing item, to avoid duplicate requests.
 0576            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
 577            {
 0578                _logger.LogDebug("Session {SessionId} provided the wrong playlist item for group {GroupId}.", session.Id
 0579                return;
 580            }
 581
 0582            var newItem = context.NextItemInQueue();
 0583            if (newItem)
 584            {
 585                // Send playing-queue update.
 0586                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NextItem);
 0587                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0588                context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 589
 590                // Reset status of sessions and await for all Ready events.
 0591                context.SetAllBuffering(true);
 592            }
 593            else
 594            {
 595                // Return to old state.
 0596                IGroupState newState = prevState switch
 0597                {
 0598                    GroupStateType.Playing => new PlayingGroupState(LoggerFactory),
 0599                    GroupStateType.Paused => new PausedGroupState(LoggerFactory),
 0600                    _ => new IdleGroupState(LoggerFactory)
 0601                };
 602
 0603                context.SetState(newState);
 604
 0605                _logger.LogDebug("No next item available in group {GroupId}.", context.GroupId.ToString());
 606            }
 0607        }
 608
 609        /// <inheritdoc />
 610        public override void HandleRequest(PreviousItemGroupRequest request, IGroupStateContext context, GroupStateType 
 611        {
 612            // Save state if first event.
 0613            if (!InitialStateSet)
 614            {
 0615                InitialState = prevState;
 0616                InitialStateSet = true;
 617            }
 618
 0619            ResumePlaying = true;
 620
 621            // Make sure the client knows the playing item, to avoid duplicate requests.
 0622            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
 623            {
 0624                _logger.LogDebug("Session {SessionId} provided the wrong playlist item for group {GroupId}.", session.Id
 0625                return;
 626            }
 627
 0628            var newItem = context.PreviousItemInQueue();
 0629            if (newItem)
 630            {
 631                // Send playing-queue update.
 0632                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.PreviousItem);
 0633                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0634                context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 635
 636                // Reset status of sessions and await for all Ready events.
 0637                context.SetAllBuffering(true);
 638            }
 639            else
 640            {
 641                // Return to old state.
 0642                IGroupState newState = prevState switch
 0643                {
 0644                    GroupStateType.Playing => new PlayingGroupState(LoggerFactory),
 0645                    GroupStateType.Paused => new PausedGroupState(LoggerFactory),
 0646                    _ => new IdleGroupState(LoggerFactory)
 0647                };
 648
 0649                context.SetState(newState);
 650
 0651                _logger.LogDebug("No previous item available in group {GroupId}.", context.GroupId.ToString());
 652            }
 0653        }
 654
 655        /// <inheritdoc />
 656        public override void HandleRequest(IgnoreWaitGroupRequest request, IGroupStateContext context, GroupStateType pr
 657        {
 0658            context.SetIgnoreGroupWait(session, request.IgnoreWait);
 659
 0660            if (!context.IsBuffering())
 661            {
 0662                _logger.LogDebug("Ignoring session {SessionId}, group {GroupId} is ready.", session.Id, context.GroupId.
 663
 0664                if (ResumePlaying)
 665                {
 666                    // Client, that was buffering, stopped following playback.
 0667                    var playingState = new PlayingGroupState(LoggerFactory);
 0668                    context.SetState(playingState);
 0669                    var unpauseRequest = new UnpauseGroupRequest();
 0670                    playingState.HandleRequest(unpauseRequest, context, Type, session, cancellationToken);
 671                }
 672                else
 673                {
 674                    // Group is ready, returning to previous state.
 0675                    var pausedState = new PausedGroupState(LoggerFactory);
 0676                    context.SetState(pausedState);
 677                }
 678            }
 0679        }
 680    }
 681}

Methods/Properties

.ctor(Microsoft.Extensions.Logging.ILoggerFactory)
SessionJoined(MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
SessionLeaving(MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.PlayGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.SetPlaylistItemGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.UnpauseGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.PauseGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.StopGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.SeekGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.BufferGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.ReadyGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.NextItemGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.PreviousItemGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.SyncPlay.PlaybackRequests.IgnoreWaitGroupRequest,MediaBrowser.Controller.SyncPlay.IGroupStateContext,MediaBrowser.Model.SyncPlay.GroupStateType,MediaBrowser.Controller.Session.SessionInfo,System.Threading.CancellationToken)