< 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
44%
Covered lines: 131
Uncovered lines: 161
Coverable lines: 292
Total lines: 698
Line coverage: 44.8%
Branch coverage
30%
Covered branches: 33
Total branches: 108
Branch coverage: 30.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 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: 68110/29/2026 - 12:17:11 AM Line coverage: 44.8% (131/292) Branch coverage: 30.5% (33/108) Total lines: 698 5/16/2026 - 12:15:55 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: 68110/29/2026 - 12:17:11 AM Line coverage: 44.8% (131/292) Branch coverage: 30.5% (33/108) Total lines: 698

Coverage delta

Coverage delta 45 -45

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
SessionJoined(...)75%44100%
SessionLeaving(...)0%4260%
HandleRequest(...)25%12859.09%
HandleRequest(...)25%14855%
HandleRequest(...)50%12645.83%
HandleRequest(...)0%620%
HandleRequest(...)0%620%
HandleRequest(...)33.33%6687.5%
HandleRequest(...)25%321248.38%
HandleRequest(...)60%723064%
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)
 1130            : base(loggerFactory)
 31        {
 1132            _logger = LoggerFactory.CreateLogger<WaitingGroupState>();
 1133        }
 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        /// <summary>
 54        /// Gets or sets a value indicating whether the group position moved during this wait.
 55        /// </summary>
 56        private bool PositionJumped { get; set; }
 57
 58        /// <inheritdoc />
 59        public override void SessionJoined(IGroupStateContext context, GroupStateType prevState, SessionInfo session, Ca
 60        {
 61            // Save state if first event.
 462            if (!InitialStateSet)
 63            {
 464                InitialState = prevState;
 465                InitialStateSet = true;
 66            }
 67
 468            if (prevState.Equals(GroupStateType.Playing))
 69            {
 370                ResumePlaying = true;
 71                // Pause group and compute the media playback position.
 372                var currentTime = DateTime.UtcNow;
 373                var elapsedTime = currentTime - context.LastActivity;
 374                context.LastActivity = currentTime;
 75                // Elapsed time is negative if event happens
 76                // during the delay added to account for latency.
 77                // In this phase clients haven't started the playback yet.
 78                // In other words, LastActivity is in the future,
 79                // when playback unpause is supposed to happen.
 80                // Seek only if playback actually started.
 381                context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
 82            }
 83
 84            // Prepare new session.
 485            var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
 486            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 487            context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
 88
 489            context.SetBuffering(session, true);
 90
 91            // Send pause command to all non-buffering sessions.
 492            var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 493            context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
 494        }
 95
 96        /// <inheritdoc />
 97        public override void SessionLeaving(IGroupStateContext context, GroupStateType prevState, SessionInfo session, C
 98        {
 99            // Save state if first event.
 0100            if (!InitialStateSet)
 101            {
 0102                InitialState = prevState;
 0103                InitialStateSet = true;
 104            }
 105
 0106            context.SetBuffering(session, false);
 107
 0108            if (!context.IsBuffering())
 109            {
 0110                if (ResumePlaying)
 111                {
 0112                    _logger.LogDebug("Session {SessionId} left group {GroupId}, notifying others to resume.", session.Id
 113
 114                    // Client, that was buffering, left the group.
 0115                    var playingState = new PlayingGroupState(LoggerFactory);
 0116                    context.SetState(playingState);
 0117                    var unpauseRequest = new UnpauseGroupRequest();
 0118                    playingState.HandleRequest(unpauseRequest, context, Type, session, cancellationToken);
 119                }
 120                else
 121                {
 0122                    _logger.LogDebug("Session {SessionId} left group {GroupId}, returning to previous state.", session.I
 123
 124                    // Group is ready, returning to previous state.
 0125                    var pausedState = new PausedGroupState(LoggerFactory);
 0126                    context.SetState(pausedState);
 127                }
 128            }
 0129        }
 130
 131        /// <inheritdoc />
 132        public override void HandleRequest(PlayGroupRequest request, IGroupStateContext context, GroupStateType prevStat
 133        {
 134            // Save state if first event.
 1135            if (!InitialStateSet)
 136            {
 1137                InitialState = prevState;
 1138                InitialStateSet = true;
 139            }
 140
 1141            ResumePlaying = true;
 142
 1143            var setQueueStatus = context.SetPlayQueue(request.PlayingQueue, request.PlayingItemPosition, request.StartPo
 1144            PositionJumped = setQueueStatus;
 1145            if (!setQueueStatus)
 146            {
 0147                _logger.LogError("Unable to set playing queue in group {GroupId}.", context.GroupId.ToString());
 148
 149                // Ignore request and return to previous state.
 0150                IGroupState newState = prevState switch
 0151                {
 0152                    GroupStateType.Playing => new PlayingGroupState(LoggerFactory),
 0153                    GroupStateType.Paused => new PausedGroupState(LoggerFactory),
 0154                    _ => new IdleGroupState(LoggerFactory)
 0155                };
 156
 0157                context.SetState(newState);
 0158                return;
 159            }
 160
 1161            var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
 1162            var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 1163            context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 164
 165            // Reset status of sessions and await for all Ready events.
 1166            context.SetAllBuffering(true);
 167
 1168            _logger.LogDebug("Session {SessionId} set a new play queue in group {GroupId}.", session.Id, context.GroupId
 1169        }
 170
 171        /// <inheritdoc />
 172        public override void HandleRequest(SetPlaylistItemGroupRequest request, IGroupStateContext context, GroupStateTy
 173        {
 174            // Save state if first event.
 1175            if (!InitialStateSet)
 176            {
 1177                InitialState = prevState;
 1178                InitialStateSet = true;
 179            }
 180
 1181            ResumePlaying = true;
 182
 1183            var result = context.SetPlayingItem(request.PlaylistItemId);
 1184            PositionJumped = result;
 1185            if (result)
 186            {
 1187                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
 1188                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 1189                context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 190
 191                // Reset status of sessions and await for all Ready events.
 1192                context.SetAllBuffering(true);
 193            }
 194            else
 195            {
 196                // Return to old state.
 0197                IGroupState newState = prevState switch
 0198                {
 0199                    GroupStateType.Playing => new PlayingGroupState(LoggerFactory),
 0200                    GroupStateType.Paused => new PausedGroupState(LoggerFactory),
 0201                    _ => new IdleGroupState(LoggerFactory)
 0202                };
 203
 0204                context.SetState(newState);
 205
 0206                _logger.LogDebug("Unable to change current playing item in group {GroupId}.", context.GroupId.ToString()
 207            }
 0208        }
 209
 210        /// <inheritdoc />
 211        public override void HandleRequest(UnpauseGroupRequest request, IGroupStateContext context, GroupStateType prevS
 212        {
 213            // Save state if first event.
 3214            if (!InitialStateSet)
 215            {
 0216                InitialState = prevState;
 0217                InitialStateSet = true;
 218            }
 219
 3220            if (prevState.Equals(GroupStateType.Idle))
 221            {
 0222                ResumePlaying = true;
 0223                context.RestartCurrentItem();
 0224                PositionJumped = true;
 225
 0226                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist);
 0227                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0228                context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 229
 230                // Reset status of sessions and await for all Ready events.
 0231                context.SetAllBuffering(true);
 232
 0233                _logger.LogDebug("Group {GroupId} is waiting for all ready events.", context.GroupId.ToString());
 234            }
 235            else
 236            {
 3237                if (ResumePlaying)
 238                {
 3239                    _logger.LogDebug("Forcing the playback to start in group {GroupId}. Group-wait is disabled until nex
 240
 241                    // An Unpause request is forcing the playback to start, ignoring sessions that are not ready.
 3242                    context.SetAllBuffering(false);
 243
 244                    // Change state.
 3245                    var playingState = new PlayingGroupState(LoggerFactory)
 3246                    {
 3247                        IgnoreBuffering = true
 3248                    };
 3249                    context.SetState(playingState);
 3250                    playingState.HandleRequest(request, context, Type, session, cancellationToken);
 251                }
 252                else
 253                {
 254                    // Group would have gone to paused state, now will go to playing state when ready.
 0255                    ResumePlaying = true;
 256
 257                    // Notify relevant state change event.
 0258                    SendGroupStateUpdate(context, request, session, cancellationToken);
 259                }
 260            }
 0261        }
 262
 263        /// <inheritdoc />
 264        public override void HandleRequest(PauseGroupRequest request, IGroupStateContext context, GroupStateType prevSta
 265        {
 266            // Save state if first event.
 0267            if (!InitialStateSet)
 268            {
 0269                InitialState = prevState;
 0270                InitialStateSet = true;
 271            }
 272
 273            // Wait for sessions to be ready, then switch to paused state.
 0274            ResumePlaying = false;
 275
 276            // Notify relevant state change event.
 0277            SendGroupStateUpdate(context, request, session, cancellationToken);
 0278        }
 279
 280        /// <inheritdoc />
 281        public override void HandleRequest(StopGroupRequest request, IGroupStateContext context, GroupStateType prevStat
 282        {
 283            // Save state if first event.
 0284            if (!InitialStateSet)
 285            {
 0286                InitialState = prevState;
 0287                InitialStateSet = true;
 288            }
 289
 290            // Change state.
 0291            var idleState = new IdleGroupState(LoggerFactory);
 0292            context.SetState(idleState);
 0293            idleState.HandleRequest(request, context, Type, session, cancellationToken);
 0294        }
 295
 296        /// <inheritdoc />
 297        public override void HandleRequest(SeekGroupRequest request, IGroupStateContext context, GroupStateType prevStat
 298        {
 299            // Save state if first event.
 1300            if (!InitialStateSet)
 301            {
 1302                InitialState = prevState;
 1303                InitialStateSet = true;
 304            }
 305
 1306            if (prevState.Equals(GroupStateType.Playing))
 307            {
 1308                ResumePlaying = true;
 309            }
 0310            else if (prevState.Equals(GroupStateType.Paused))
 311            {
 0312                ResumePlaying = false;
 313            }
 314
 315            // Sanitize PositionTicks.
 1316            var ticks = context.SanitizePositionTicks(request.PositionTicks);
 317
 318            // Seek.
 1319            context.PositionTicks = ticks;
 1320            context.LastActivity = DateTime.UtcNow;
 1321            PositionJumped = true;
 322
 1323            var command = context.NewSyncPlayCommand(SendCommandType.Seek);
 1324            context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
 325
 326            // Reset status of sessions and await for all Ready events.
 1327            context.SetAllBuffering(true);
 328
 329            // Notify relevant state change event.
 1330            SendGroupStateUpdate(context, request, session, cancellationToken);
 1331        }
 332
 333        /// <inheritdoc />
 334        public override void HandleRequest(BufferGroupRequest request, IGroupStateContext context, GroupStateType prevSt
 335        {
 336            // Save state if first event.
 1337            if (!InitialStateSet)
 338            {
 1339                InitialState = prevState;
 1340                InitialStateSet = true;
 341            }
 342
 343            // Make sure the client is playing the correct item.
 1344            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
 345            {
 0346                _logger.LogDebug("Session {SessionId} reported wrong playlist item in group {GroupId}.", session.Id, con
 347
 0348                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
 0349                var updateSession = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0350                context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken)
 0351                context.SetBuffering(session, true);
 352
 0353                return;
 354            }
 355
 1356            if (prevState.Equals(GroupStateType.Playing))
 357            {
 358                // Resume playback when all ready.
 1359                ResumePlaying = true;
 360
 1361                context.SetBuffering(session, true);
 362
 363                // Pause group and compute the media playback position.
 1364                var currentTime = DateTime.UtcNow;
 1365                var elapsedTime = currentTime - context.LastActivity;
 1366                context.LastActivity = currentTime;
 367                // Elapsed time is negative if event happens
 368                // during the delay added to account for latency.
 369                // In this phase clients haven't started the playback yet.
 370                // In other words, LastActivity is in the future,
 371                // when playback unpause is supposed to happen.
 372                // Seek only if playback actually started.
 1373                context.PositionTicks += Math.Max(elapsedTime.Ticks, 0);
 374
 375                // Send pause command to all non-buffering sessions.
 1376                var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 1377                context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken);
 378            }
 0379            else if (prevState.Equals(GroupStateType.Paused))
 380            {
 381                // Don't resume playback when all ready.
 0382                ResumePlaying = false;
 383
 0384                context.SetBuffering(session, true);
 385
 386                // Send pause command to buffering session.
 0387                var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 0388                context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 389            }
 0390            else if (prevState.Equals(GroupStateType.Waiting))
 391            {
 392                // Another session is now buffering.
 0393                context.SetBuffering(session, true);
 394
 0395                if (!ResumePlaying)
 396                {
 397                    // Force update for this session that should be paused.
 0398                    var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 0399                    context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 400                }
 401            }
 402
 403            // Notify relevant state change event.
 1404            SendGroupStateUpdate(context, request, session, cancellationToken);
 1405        }
 406
 407        /// <inheritdoc />
 408        public override void HandleRequest(ReadyGroupRequest request, IGroupStateContext context, GroupStateType prevSta
 409        {
 410            // Save state if first event.
 6411            if (!InitialStateSet)
 412            {
 3413                InitialState = prevState;
 3414                InitialStateSet = true;
 415            }
 416
 417            // Make sure the client is playing the correct item.
 6418            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
 419            {
 0420                _logger.LogDebug("Session {SessionId} reported wrong playlist item in group {GroupId}.", session.Id, con
 421
 0422                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem);
 0423                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0424                context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken);
 0425                context.SetBuffering(session, true);
 426
 0427                return;
 428            }
 429
 430            // Compute elapsed time between the client reported time and now.
 431            // Elapsed time is used to estimate the client position when playback is unpaused.
 432            // Ideally, the request is received and handled without major delays.
 433            // However, to avoid waiting indefinitely when a client is not reporting a correct time,
 434            // the elapsed time is ignored after a certain threshold.
 6435            var currentTime = DateTime.UtcNow;
 6436            var elapsedTime = currentTime.Subtract(request.When);
 6437            var timeSyncThresholdTicks = TimeSpan.FromMilliseconds(context.TimeSyncOffset).Ticks;
 6438            if (Math.Abs(elapsedTime.Ticks) > timeSyncThresholdTicks)
 439            {
 0440                _logger.LogWarning("Session {SessionId} is not time syncing properly. Ignoring elapsed time.", session.I
 441
 0442                elapsedTime = TimeSpan.Zero;
 443            }
 444
 445            // Ignore elapsed time if client is paused.
 6446            if (!request.IsPlaying)
 447            {
 1448                elapsedTime = TimeSpan.Zero;
 449            }
 450
 6451            var requestTicks = context.SanitizePositionTicks(request.PositionTicks);
 6452            var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime;
 6453            var delayTicks = context.PositionTicks - clientPosition.Ticks;
 6454            var maxPlaybackOffsetTicks = TimeSpan.FromMilliseconds(context.MaxPlaybackOffset).Ticks;
 455
 6456            _logger.LogDebug("Session {SessionId} is at {PositionTicks} (delay of {Delay} seconds) in group {GroupId}.",
 457
 6458            if (ResumePlaying)
 459            {
 460                // Handle case where session reported as ready but in reality
 461                // it has no clue of the real position nor the playback state.
 462                // A jump means the session has not applied the new position; without one it is
 463                // catching up after buffering and is allowed to lag.
 6464                var maxOffsetTicks = request.IsPlaying && !PositionJumped
 6465                    ? TimeSpan.FromMilliseconds(context.MaxCatchUpOffset).Ticks
 6466                    : maxPlaybackOffsetTicks;
 467
 6468                if (Math.Abs(delayTicks) > maxOffsetTicks)
 469                {
 470                    // Session not ready at all.
 2471                    context.SetBuffering(session, true);
 472
 473                    // Correcting session's position.
 2474                    var command = context.NewSyncPlayCommand(SendCommandType.Seek);
 2475                    context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 476
 477                    // Notify relevant state change event.
 2478                    SendGroupStateUpdate(context, request, session, cancellationToken);
 479
 2480                    _logger.LogWarning("Session {SessionId} got lost in time, correcting.", session.Id);
 2481                    return;
 482                }
 483
 484                // Session is ready.
 4485                context.SetBuffering(session, false);
 486
 4487                if (context.IsBuffering())
 488                {
 489                    // Others are still buffering, tell this client to pause when ready.
 1490                    var command = context.NewSyncPlayCommand(SendCommandType.Pause);
 1491                    command.When = currentTime.AddTicks(delayTicks);
 1492                    context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 493
 1494                    _logger.LogInformation("Session {SessionId} will pause when ready in {Delay} seconds. Group {GroupId
 495                }
 496                else
 497                {
 498                    // If all ready, then start playback.
 499                    // Let other clients resume as soon as the buffering client catches up.
 3500                    if (delayTicks > context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond)
 501                    {
 502                        // Client that was buffering is recovering, notifying others to resume.
 1503                        context.LastActivity = currentTime.AddTicks(delayTicks);
 1504                        var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
 1505                        var filter = SyncPlayBroadcastType.AllExceptCurrentSession;
 1506                        if (!request.IsPlaying)
 507                        {
 0508                            filter = SyncPlayBroadcastType.AllGroup;
 509                        }
 510
 1511                        context.SendCommand(session, filter, command, cancellationToken);
 512
 1513                        _logger.LogInformation("Session {SessionId} is recovering, group {GroupId} will resume in {Delay
 514                    }
 515                    else
 516                    {
 517                        // Client, that was buffering, resumed playback but did not update others in time.
 2518                        delayTicks = context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond;
 2519                        delayTicks = Math.Max(delayTicks, TimeSpan.FromMilliseconds(context.DefaultPing).Ticks);
 520
 2521                        context.LastActivity = currentTime.AddTicks(delayTicks);
 522
 2523                        var command = context.NewSyncPlayCommand(SendCommandType.Unpause);
 2524                        context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken);
 525
 2526                        _logger.LogWarning("Session {SessionId} resumed playback, group {GroupId} has {Delay} seconds to
 527                    }
 528
 529                    // Change state.
 3530                    var playingState = new PlayingGroupState(LoggerFactory);
 3531                    context.SetState(playingState);
 3532                    playingState.HandleRequest(request, context, Type, session, cancellationToken);
 533                }
 534            }
 535            else
 536            {
 537                // Check that session is really ready, tolerate player imperfections under a certain threshold.
 0538                if (Math.Abs(context.PositionTicks - requestTicks) > maxPlaybackOffsetTicks)
 539                {
 540                    // Session still not ready.
 0541                    context.SetBuffering(session, true);
 542                    // Session is seeking to wrong position, correcting.
 0543                    var command = context.NewSyncPlayCommand(SendCommandType.Seek);
 0544                    context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken);
 545
 546                    // Notify relevant state change event.
 0547                    SendGroupStateUpdate(context, request, session, cancellationToken);
 548
 0549                    _logger.LogWarning("Session {SessionId} is seeking to wrong position, correcting.", session.Id);
 0550                    return;
 551                }
 552
 553                // Session is ready.
 0554                context.SetBuffering(session, false);
 555
 0556                if (!context.IsBuffering())
 557                {
 0558                    _logger.LogDebug("Session {SessionId} is ready, group {GroupId} is ready.", session.Id, context.Grou
 559
 560                    // Group is ready, returning to previous state.
 0561                    var pausedState = new PausedGroupState(LoggerFactory);
 0562                    context.SetState(pausedState);
 563
 0564                    if (InitialState.Equals(GroupStateType.Playing))
 565                    {
 566                        // Group went from playing to waiting state and a pause request occurred while waiting.
 0567                        var pauseRequest = new PauseGroupRequest();
 0568                        pausedState.HandleRequest(pauseRequest, context, Type, session, cancellationToken);
 569                    }
 0570                    else if (InitialState.Equals(GroupStateType.Paused))
 571                    {
 0572                        pausedState.HandleRequest(request, context, Type, session, cancellationToken);
 573                    }
 574                }
 575            }
 0576        }
 577
 578        /// <inheritdoc />
 579        public override void HandleRequest(NextItemGroupRequest request, IGroupStateContext context, GroupStateType prev
 580        {
 581            // Save state if first event.
 0582            if (!InitialStateSet)
 583            {
 0584                InitialState = prevState;
 0585                InitialStateSet = true;
 586            }
 587
 0588            ResumePlaying = true;
 589
 590            // Make sure the client knows the playing item, to avoid duplicate requests.
 0591            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
 592            {
 0593                _logger.LogDebug("Session {SessionId} provided the wrong playlist item for group {GroupId}.", session.Id
 0594                return;
 595            }
 596
 0597            var newItem = context.NextItemInQueue();
 0598            PositionJumped = newItem;
 0599            if (newItem)
 600            {
 601                // Send playing-queue update.
 0602                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NextItem);
 0603                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0604                context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 605
 606                // Reset status of sessions and await for all Ready events.
 0607                context.SetAllBuffering(true);
 608            }
 609            else
 610            {
 611                // Return to old state.
 0612                IGroupState newState = prevState switch
 0613                {
 0614                    GroupStateType.Playing => new PlayingGroupState(LoggerFactory),
 0615                    GroupStateType.Paused => new PausedGroupState(LoggerFactory),
 0616                    _ => new IdleGroupState(LoggerFactory)
 0617                };
 618
 0619                context.SetState(newState);
 620
 0621                _logger.LogDebug("No next item available in group {GroupId}.", context.GroupId.ToString());
 622            }
 0623        }
 624
 625        /// <inheritdoc />
 626        public override void HandleRequest(PreviousItemGroupRequest request, IGroupStateContext context, GroupStateType 
 627        {
 628            // Save state if first event.
 0629            if (!InitialStateSet)
 630            {
 0631                InitialState = prevState;
 0632                InitialStateSet = true;
 633            }
 634
 0635            ResumePlaying = true;
 636
 637            // Make sure the client knows the playing item, to avoid duplicate requests.
 0638            if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId()))
 639            {
 0640                _logger.LogDebug("Session {SessionId} provided the wrong playlist item for group {GroupId}.", session.Id
 0641                return;
 642            }
 643
 0644            var newItem = context.PreviousItemInQueue();
 0645            PositionJumped = newItem;
 0646            if (newItem)
 647            {
 648                // Send playing-queue update.
 0649                var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.PreviousItem);
 0650                var update = new SyncPlayPlayQueueUpdate(context.GroupId, playQueueUpdate);
 0651                context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken);
 652
 653                // Reset status of sessions and await for all Ready events.
 0654                context.SetAllBuffering(true);
 655            }
 656            else
 657            {
 658                // Return to old state.
 0659                IGroupState newState = prevState switch
 0660                {
 0661                    GroupStateType.Playing => new PlayingGroupState(LoggerFactory),
 0662                    GroupStateType.Paused => new PausedGroupState(LoggerFactory),
 0663                    _ => new IdleGroupState(LoggerFactory)
 0664                };
 665
 0666                context.SetState(newState);
 667
 0668                _logger.LogDebug("No previous item available in group {GroupId}.", context.GroupId.ToString());
 669            }
 0670        }
 671
 672        /// <inheritdoc />
 673        public override void HandleRequest(IgnoreWaitGroupRequest request, IGroupStateContext context, GroupStateType pr
 674        {
 0675            context.SetIgnoreGroupWait(session, request.IgnoreWait);
 676
 0677            if (!context.IsBuffering())
 678            {
 0679                _logger.LogDebug("Ignoring session {SessionId}, group {GroupId} is ready.", session.Id, context.GroupId.
 680
 0681                if (ResumePlaying)
 682                {
 683                    // Client, that was buffering, stopped following playback.
 0684                    var playingState = new PlayingGroupState(LoggerFactory);
 0685                    context.SetState(playingState);
 0686                    var unpauseRequest = new UnpauseGroupRequest();
 0687                    playingState.HandleRequest(unpauseRequest, context, Type, session, cancellationToken);
 688                }
 689                else
 690                {
 691                    // Group is ready, returning to previous state.
 0692                    var pausedState = new PausedGroupState(LoggerFactory);
 0693                    context.SetState(pausedState);
 694                }
 695            }
 0696        }
 697    }
 698}

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)