< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.SyncPlay.Group
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/SyncPlay/Group.cs
Line coverage
34%
Covered lines: 73
Uncovered lines: 137
Coverable lines: 210
Total lines: 680
Line coverage: 34.7%
Branch coverage
16%
Covered branches: 11
Total branches: 65
Branch coverage: 16.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/26/2026 - 12:14:14 AM Line coverage: 0% (0/210) Branch coverage: 0% (0/63) Total lines: 6796/19/2026 - 12:16:12 AM Line coverage: 10% (21/210) Branch coverage: 12.3% (8/65) Total lines: 6807/3/2026 - 12:15:32 AM Line coverage: 34.7% (73/210) Branch coverage: 16.9% (11/65) Total lines: 680 3/26/2026 - 12:14:14 AM Line coverage: 0% (0/210) Branch coverage: 0% (0/63) Total lines: 6796/19/2026 - 12:16:12 AM Line coverage: 10% (21/210) Branch coverage: 12.3% (8/65) Total lines: 6807/3/2026 - 12:15:32 AM Line coverage: 34.7% (73/210) Branch coverage: 16.9% (11/65) Total lines: 680

Coverage delta

Coverage delta 25 -25

Metrics

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/SyncPlay/Group.cs

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Linq;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Jellyfin.Database.Implementations.Entities;
 9using Jellyfin.Extensions;
 10using MediaBrowser.Controller.Library;
 11using MediaBrowser.Controller.Session;
 12using MediaBrowser.Controller.SyncPlay;
 13using MediaBrowser.Controller.SyncPlay.GroupStates;
 14using MediaBrowser.Controller.SyncPlay.Queue;
 15using MediaBrowser.Controller.SyncPlay.Requests;
 16using MediaBrowser.Model.SyncPlay;
 17using Microsoft.Extensions.Logging;
 18
 19namespace Emby.Server.Implementations.SyncPlay
 20{
 21    /// <summary>
 22    /// Class Group.
 23    /// </summary>
 24    /// <remarks>
 25    /// Class is not thread-safe, external locking is required when accessing methods.
 26    /// </remarks>
 27    public class Group : IGroupStateContext
 28    {
 29        /// <summary>
 30        /// The logger.
 31        /// </summary>
 32        private readonly ILogger<Group> _logger;
 33
 34        /// <summary>
 35        /// The logger factory.
 36        /// </summary>
 37        private readonly ILoggerFactory _loggerFactory;
 38
 39        /// <summary>
 40        /// The user manager.
 41        /// </summary>
 42        private readonly IUserManager _userManager;
 43
 44        /// <summary>
 45        /// The session manager.
 46        /// </summary>
 47        private readonly ISessionManager _sessionManager;
 48
 49        /// <summary>
 50        /// The library manager.
 51        /// </summary>
 52        private readonly ILibraryManager _libraryManager;
 53
 54        /// <summary>
 55        /// The participants, or members of the group.
 56        /// </summary>
 357        private readonly Dictionary<string, GroupMember> _participants =
 358            new Dictionary<string, GroupMember>(StringComparer.OrdinalIgnoreCase);
 59
 60        /// <summary>
 61        /// The internal group state.
 62        /// </summary>
 63        private IGroupState _state;
 64
 65        /// <summary>
 66        /// Initializes a new instance of the <see cref="Group" /> class.
 67        /// </summary>
 68        /// <param name="loggerFactory">The logger factory.</param>
 69        /// <param name="userManager">The user manager.</param>
 70        /// <param name="sessionManager">The session manager.</param>
 71        /// <param name="libraryManager">The library manager.</param>
 72        public Group(
 73            ILoggerFactory loggerFactory,
 74            IUserManager userManager,
 75            ISessionManager sessionManager,
 76            ILibraryManager libraryManager)
 77        {
 378            _loggerFactory = loggerFactory;
 379            _userManager = userManager;
 380            _sessionManager = sessionManager;
 381            _libraryManager = libraryManager;
 382            _logger = loggerFactory.CreateLogger<Group>();
 83
 384            _state = new IdleGroupState(loggerFactory);
 385        }
 86
 87        /// <summary>
 88        /// Gets the default ping value used for sessions.
 89        /// </summary>
 90        /// <value>The default ping.</value>
 391        public long DefaultPing { get; } = 500;
 92
 93        /// <summary>
 94        /// Gets the maximum time offset error accepted for dates reported by clients, in milliseconds.
 95        /// </summary>
 96        /// <value>The maximum time offset error.</value>
 397        public long TimeSyncOffset { get; } = 2000;
 98
 99        /// <summary>
 100        /// Gets the maximum offset error accepted for position reported by clients, in milliseconds.
 101        /// </summary>
 102        /// <value>The maximum offset error.</value>
 3103        public long MaxPlaybackOffset { get; } = 500;
 104
 105        /// <summary>
 106        /// Gets the group identifier.
 107        /// </summary>
 108        /// <value>The group identifier.</value>
 109        public Guid GroupId { get; } = Guid.NewGuid();
 110
 111        /// <summary>
 112        /// Gets the group name.
 113        /// </summary>
 114        /// <value>The group name.</value>
 115        public string GroupName { get; private set; }
 116
 117        /// <summary>
 118        /// Gets the group identifier.
 119        /// </summary>
 120        /// <value>The group identifier.</value>
 121        public PlayQueueManager PlayQueue { get; } = new PlayQueueManager();
 122
 123        /// <summary>
 124        /// Gets the runtime ticks of current playing item.
 125        /// </summary>
 126        /// <value>The runtime ticks of current playing item.</value>
 127        public long RunTimeTicks { get; private set; }
 128
 129        /// <summary>
 130        /// Gets or sets the position ticks.
 131        /// </summary>
 132        /// <value>The position ticks.</value>
 133        public long PositionTicks { get; set; }
 134
 135        /// <summary>
 136        /// Gets or sets the last activity.
 137        /// </summary>
 138        /// <value>The last activity.</value>
 139        public DateTime LastActivity { get; set; }
 140
 141        /// <summary>
 142        /// Adds the session to the group.
 143        /// </summary>
 144        /// <param name="session">The session.</param>
 145        private void AddSession(SessionInfo session)
 146        {
 1147            _participants.TryAdd(
 1148                session.Id,
 1149                new GroupMember(session)
 1150                {
 1151                    Ping = DefaultPing,
 1152                    IsBuffering = false
 1153                });
 1154        }
 155
 156        /// <summary>
 157        /// Removes the session from the group.
 158        /// </summary>
 159        /// <param name="session">The session.</param>
 160        private void RemoveSession(SessionInfo session)
 161        {
 1162            _participants.Remove(session.Id);
 1163        }
 164
 165        /// <summary>
 166        /// Filters sessions of this group.
 167        /// </summary>
 168        /// <param name="fromId">The current session identifier.</param>
 169        /// <param name="type">The filtering type.</param>
 170        /// <returns>The list of sessions matching the filter.</returns>
 171        private IEnumerable<string> FilterSessions(string fromId, SyncPlayBroadcastType type)
 172        {
 4173            return type switch
 4174            {
 3175                SyncPlayBroadcastType.CurrentSession => new string[] { fromId },
 0176                SyncPlayBroadcastType.AllGroup => _participants
 0177                    .Values
 0178                    .Select(member => member.SessionId),
 1179                SyncPlayBroadcastType.AllExceptCurrentSession => _participants
 1180                    .Values
 1181                    .Select(member => member.SessionId)
 1182                    .Where(sessionId => !sessionId.Equals(fromId, StringComparison.OrdinalIgnoreCase)),
 0183                SyncPlayBroadcastType.AllReady => _participants
 0184                    .Values
 0185                    .Where(member => !member.IsBuffering)
 0186                    .Select(member => member.SessionId),
 0187                _ => Enumerable.Empty<string>()
 4188            };
 189        }
 190
 191        /// <summary>
 192        /// Checks if a given user can access all items of a given queue, that is,
 193        /// the user has the required minimum parental access and has access to all required folders.
 194        /// </summary>
 195        /// <param name="user">The user.</param>
 196        /// <param name="queue">The queue.</param>
 197        /// <returns><c>true</c> if the user can access all the items in the queue, <c>false</c> otherwise.</returns>
 198        private bool HasAccessToQueue(User user, IReadOnlyList<Guid> queue)
 199        {
 200            // Check if queue is empty.
 23201            if (queue is null || queue.Count == 0)
 202            {
 21203                return true;
 204            }
 205
 7206            foreach (var itemId in queue)
 207            {
 2208                var item = _libraryManager.GetItemById(itemId);
 209
 2210                if (item is null || !item.IsVisibleStandalone(user))
 211                {
 1212                    return false;
 213                }
 214            }
 215
 1216            return true;
 1217        }
 218
 219        private bool AllUsersHaveAccessToQueue(IReadOnlyList<Guid> queue)
 220        {
 221            // Check if queue is empty.
 0222            if (queue is null || queue.Count == 0)
 223            {
 0224                return true;
 225            }
 226
 227            // Get list of users.
 0228            var users = _participants
 0229                .Values
 0230                .Select(participant => _userManager.GetUserById(participant.UserId));
 231
 232            // Find problematic users.
 0233            var usersWithNoAccess = users.Where(user => !HasAccessToQueue(user, queue));
 234
 235            // All users must be able to access the queue.
 0236            return !usersWithNoAccess.Any();
 237        }
 238
 239        /// <summary>
 240        /// Checks if the group is empty.
 241        /// </summary>
 242        /// <returns><c>true</c> if the group is empty, <c>false</c> otherwise.</returns>
 1243        public bool IsGroupEmpty() => _participants.Count == 0;
 244
 245        /// <summary>
 246        /// Initializes the group with the session's info.
 247        /// </summary>
 248        /// <param name="session">The session.</param>
 249        /// <param name="request">The request.</param>
 250        /// <param name="cancellationToken">The cancellation token.</param>
 251        public void CreateGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken)
 252        {
 1253            GroupName = request.GroupName;
 1254            AddSession(session);
 255
 1256            var sessionIsPlayingAnItem = session.FullNowPlayingItem is not null;
 257
 1258            RestartCurrentItem();
 259
 1260            if (sessionIsPlayingAnItem)
 261            {
 0262                var playlist = session.NowPlayingQueue.Select(item => item.Id).ToList();
 0263                PlayQueue.Reset();
 0264                PlayQueue.SetPlaylist(playlist);
 0265                PlayQueue.SetPlayingItemById(session.FullNowPlayingItem.Id);
 0266                RunTimeTicks = session.FullNowPlayingItem.RunTimeTicks ?? 0;
 0267                PositionTicks = session.PlayState.PositionTicks ?? 0;
 268
 269                // Maintain playstate.
 0270                var waitingState = new WaitingGroupState(_loggerFactory)
 0271                {
 0272                    ResumePlaying = !session.PlayState.IsPaused
 0273                };
 0274                SetState(waitingState);
 275            }
 276
 1277            var updateSession = new SyncPlayGroupJoinedUpdate(GroupId, GetInfo());
 1278            SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 279
 1280            _state.SessionJoined(this, _state.Type, session, cancellationToken);
 281
 1282            _logger.LogInformation("Session {SessionId} created group {GroupId}.", session.Id, GroupId.ToString());
 1283        }
 284
 285        /// <summary>
 286        /// Adds the session to the group.
 287        /// </summary>
 288        /// <param name="session">The session.</param>
 289        /// <param name="request">The request.</param>
 290        /// <param name="cancellationToken">The cancellation token.</param>
 291        public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken)
 292        {
 0293            AddSession(session);
 294
 0295            var updateSession = new SyncPlayGroupJoinedUpdate(GroupId, GetInfo());
 0296            SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 297
 0298            var updateOthers = new SyncPlayUserJoinedUpdate(GroupId, session.UserName);
 0299            SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 300
 0301            _state.SessionJoined(this, _state.Type, session, cancellationToken);
 302
 0303            _logger.LogInformation("Session {SessionId} joined group {GroupId}.", session.Id, GroupId.ToString());
 0304        }
 305
 306        /// <summary>
 307        /// Removes the session from the group.
 308        /// </summary>
 309        /// <param name="session">The session.</param>
 310        /// <param name="request">The request.</param>
 311        /// <param name="cancellationToken">The cancellation token.</param>
 312        public void SessionLeave(SessionInfo session, LeaveGroupRequest request, CancellationToken cancellationToken)
 313        {
 1314            _state.SessionLeaving(this, _state.Type, session, cancellationToken);
 315
 1316            RemoveSession(session);
 317
 1318            var updateSession = new SyncPlayGroupLeftUpdate(GroupId, GroupId.ToString());
 1319            SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 320
 1321            var updateOthers = new SyncPlayUserLeftUpdate(GroupId, session.UserName);
 1322            SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 323
 1324            _logger.LogInformation("Session {SessionId} left group {GroupId}.", session.Id, GroupId.ToString());
 1325        }
 326
 327        /// <summary>
 328        /// Handles the requested action by the session.
 329        /// </summary>
 330        /// <param name="session">The session.</param>
 331        /// <param name="request">The requested action.</param>
 332        /// <param name="cancellationToken">The cancellation token.</param>
 333        public void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToke
 334        {
 335            // The server's job is to maintain a consistent state for clients to reference
 336            // and notify clients of state changes. The actual syncing of media playback
 337            // happens client side. Clients are aware of the server's time and use it to sync.
 0338            _logger.LogInformation("Session {SessionId} requested {RequestType} in group {GroupId} that is {StateType}."
 339
 340            // Apply requested changes to this group given its current state.
 341            // Every request has a slightly different outcome depending on the group's state.
 342            // There are currently four different group states that accomplish different goals:
 343            // - Idle: in this state no media is playing and clients should be idle (playback is stopped).
 344            // - Waiting: in this state the group is waiting for all the clients to be ready to start the playback,
 345            //      that is, they've either finished loading the media for the first time or they've finished buffering.
 346            //      Once all clients report to be ready the group's state can change to Playing or Paused.
 347            // - Playing: clients have some media loaded and playback is unpaused.
 348            // - Paused: clients have some media loaded but playback is currently paused.
 0349            request.Apply(this, _state, session, cancellationToken);
 0350        }
 351
 352        /// <summary>
 353        /// Gets the info about the group for the clients.
 354        /// </summary>
 355        /// <returns>The group info for the clients.</returns>
 356        public GroupInfoDto GetInfo()
 357        {
 23358            var participants = _participants.Values.Select(session => session.UserName).Distinct().ToList();
 23359            return new GroupInfoDto(GroupId, GroupName, _state.Type, participants, DateTime.UtcNow);
 360        }
 361
 362        /// <summary>
 363        /// Checks if a user has access to all content in the play queue.
 364        /// </summary>
 365        /// <param name="user">The user.</param>
 366        /// <returns><c>true</c> if the user can access the play queue; <c>false</c> otherwise.</returns>
 367        public bool HasAccessToPlayQueue(User user)
 368        {
 23369            var items = PlayQueue.GetPlaylist().Select(item => item.ItemId).ToList();
 23370            return HasAccessToQueue(user, items);
 371        }
 372
 373        /// <inheritdoc />
 374        public void SetIgnoreGroupWait(SessionInfo session, bool ignoreGroupWait)
 375        {
 0376            if (_participants.TryGetValue(session.Id, out GroupMember value))
 377            {
 0378                value.IgnoreGroupWait = ignoreGroupWait;
 379            }
 0380        }
 381
 382        /// <inheritdoc />
 383        public void SetState(IGroupState state)
 384        {
 0385            _logger.LogInformation("Group {GroupId} switching from {FromStateType} to {ToStateType}.", GroupId.ToString(
 0386            this._state = state;
 0387        }
 388
 389        /// <inheritdoc />
 390        public Task SendGroupUpdate<T>(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate<T> message, Cancellatio
 391        {
 392            IEnumerable<Task> GetTasks()
 393            {
 394                foreach (var sessionId in FilterSessions(from.Id, type))
 395                {
 396                    yield return _sessionManager.SendSyncPlayGroupUpdate(sessionId, message, cancellationToken);
 397                }
 398            }
 399
 3400            return Task.WhenAll(GetTasks());
 401        }
 402
 403        /// <inheritdoc />
 404        public Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken can
 405        {
 406            IEnumerable<Task> GetTasks()
 407            {
 408                foreach (var sessionId in FilterSessions(from.Id, type))
 409                {
 410                    yield return _sessionManager.SendSyncPlayCommand(sessionId, message, cancellationToken);
 411                }
 412            }
 413
 1414            return Task.WhenAll(GetTasks());
 415        }
 416
 417        /// <inheritdoc />
 418        public SendCommand NewSyncPlayCommand(SendCommandType type)
 419        {
 1420            return new SendCommand(
 1421                GroupId,
 1422                PlayQueue.GetPlayingItemPlaylistId(),
 1423                LastActivity,
 1424                type,
 1425                PositionTicks,
 1426                DateTime.UtcNow);
 427        }
 428
 429        /// <inheritdoc />
 430        public long SanitizePositionTicks(long? positionTicks)
 431        {
 0432            var ticks = positionTicks ?? 0;
 0433            return Math.Clamp(ticks, 0, RunTimeTicks);
 434        }
 435
 436        /// <inheritdoc />
 437        public void UpdatePing(SessionInfo session, long ping)
 438        {
 0439            if (_participants.TryGetValue(session.Id, out GroupMember value))
 440            {
 0441                value.Ping = ping;
 442            }
 0443        }
 444
 445        /// <inheritdoc />
 446        public long GetHighestPing()
 447        {
 0448            long max = long.MinValue;
 0449            foreach (var session in _participants.Values)
 450            {
 0451                max = Math.Max(max, session.Ping);
 452            }
 453
 0454            return max;
 455        }
 456
 457        /// <inheritdoc />
 458        public void SetBuffering(SessionInfo session, bool isBuffering)
 459        {
 0460            if (_participants.TryGetValue(session.Id, out GroupMember value))
 461            {
 0462                value.IsBuffering = isBuffering;
 463            }
 0464        }
 465
 466        /// <inheritdoc />
 467        public void SetAllBuffering(bool isBuffering)
 468        {
 0469            foreach (var session in _participants.Values)
 470            {
 0471                session.IsBuffering = isBuffering;
 472            }
 0473        }
 474
 475        /// <inheritdoc />
 476        public bool IsBuffering()
 477        {
 0478            foreach (var session in _participants.Values)
 479            {
 0480                if (session.IsBuffering && !session.IgnoreGroupWait)
 481                {
 0482                    return true;
 483                }
 484            }
 485
 0486            return false;
 0487        }
 488
 489        /// <inheritdoc />
 490        public bool SetPlayQueue(IReadOnlyList<Guid> playQueue, int playingItemPosition, long startPositionTicks)
 491        {
 492            // Ignore on empty queue or invalid item position.
 0493            if (playQueue.Count == 0 || playingItemPosition >= playQueue.Count || playingItemPosition < 0)
 494            {
 0495                return false;
 496            }
 497
 498            // Check if participants can access the new playing queue.
 0499            if (!AllUsersHaveAccessToQueue(playQueue))
 500            {
 0501                return false;
 502            }
 503
 0504            PlayQueue.Reset();
 0505            PlayQueue.SetPlaylist(playQueue);
 0506            PlayQueue.SetPlayingItemByIndex(playingItemPosition);
 0507            var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId());
 0508            RunTimeTicks = item.RunTimeTicks ?? 0;
 0509            PositionTicks = startPositionTicks;
 0510            LastActivity = DateTime.UtcNow;
 511
 0512            return true;
 513        }
 514
 515        /// <inheritdoc />
 516        public bool SetPlayingItem(Guid playlistItemId)
 517        {
 0518            var itemFound = PlayQueue.SetPlayingItemByPlaylistId(playlistItemId);
 519
 0520            if (itemFound)
 521            {
 0522                var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId());
 0523                RunTimeTicks = item.RunTimeTicks ?? 0;
 524            }
 525            else
 526            {
 0527                RunTimeTicks = 0;
 528            }
 529
 0530            RestartCurrentItem();
 531
 0532            return itemFound;
 533        }
 534
 535        /// <inheritdoc />
 536        public void ClearPlayQueue(bool clearPlayingItem)
 537        {
 0538            PlayQueue.ClearPlaylist(clearPlayingItem);
 0539            if (clearPlayingItem)
 540            {
 0541                RestartCurrentItem();
 542            }
 0543        }
 544
 545        /// <inheritdoc />
 546        public bool RemoveFromPlayQueue(IReadOnlyList<Guid> playlistItemIds)
 547        {
 0548            var playingItemRemoved = PlayQueue.RemoveFromPlaylist(playlistItemIds);
 0549            if (playingItemRemoved)
 550            {
 0551                var itemId = PlayQueue.GetPlayingItemId();
 0552                if (!itemId.IsEmpty())
 553                {
 0554                    var item = _libraryManager.GetItemById(itemId);
 0555                    RunTimeTicks = item.RunTimeTicks ?? 0;
 556                }
 557                else
 558                {
 0559                    RunTimeTicks = 0;
 560                }
 561
 0562                RestartCurrentItem();
 563            }
 564
 0565            return playingItemRemoved;
 566        }
 567
 568        /// <inheritdoc />
 569        public bool MoveItemInPlayQueue(Guid playlistItemId, int newIndex)
 570        {
 0571            return PlayQueue.MovePlaylistItem(playlistItemId, newIndex);
 572        }
 573
 574        /// <inheritdoc />
 575        public bool AddToPlayQueue(IReadOnlyList<Guid> newItems, GroupQueueMode mode)
 576        {
 577            // Ignore on empty list.
 0578            if (newItems.Count == 0)
 579            {
 0580                return false;
 581            }
 582
 583            // Check if participants can access the new playing queue.
 0584            if (!AllUsersHaveAccessToQueue(newItems))
 585            {
 0586                return false;
 587            }
 588
 0589            if (mode.Equals(GroupQueueMode.QueueNext))
 590            {
 0591                PlayQueue.QueueNext(newItems);
 592            }
 593            else
 594            {
 0595                PlayQueue.Queue(newItems);
 596            }
 597
 0598            return true;
 599        }
 600
 601        /// <inheritdoc />
 602        public void RestartCurrentItem()
 603        {
 1604            PositionTicks = 0;
 1605            LastActivity = DateTime.UtcNow;
 1606        }
 607
 608        /// <inheritdoc />
 609        public bool NextItemInQueue()
 610        {
 0611            var update = PlayQueue.Next();
 0612            if (update)
 613            {
 0614                var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId());
 0615                RunTimeTicks = item.RunTimeTicks ?? 0;
 0616                RestartCurrentItem();
 0617                return true;
 618            }
 619
 0620            return false;
 621        }
 622
 623        /// <inheritdoc />
 624        public bool PreviousItemInQueue()
 625        {
 0626            var update = PlayQueue.Previous();
 0627            if (update)
 628            {
 0629                var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId());
 0630                RunTimeTicks = item.RunTimeTicks ?? 0;
 0631                RestartCurrentItem();
 0632                return true;
 633            }
 634
 0635            return false;
 636        }
 637
 638        /// <inheritdoc />
 639        public void SetRepeatMode(GroupRepeatMode mode)
 640        {
 0641            PlayQueue.SetRepeatMode(mode);
 0642        }
 643
 644        /// <inheritdoc />
 645        public void SetShuffleMode(GroupShuffleMode mode)
 646        {
 0647            PlayQueue.SetShuffleMode(mode);
 0648        }
 649
 650        /// <inheritdoc />
 651        public PlayQueueUpdate GetPlayQueueUpdate(PlayQueueUpdateReason reason)
 652        {
 0653            var startPositionTicks = PositionTicks;
 0654            var isPlaying = _state.Type.Equals(GroupStateType.Playing);
 655
 0656            if (isPlaying)
 657            {
 0658                var currentTime = DateTime.UtcNow;
 0659                var elapsedTime = currentTime - LastActivity;
 660                // Elapsed time is negative if event happens
 661                // during the delay added to account for latency.
 662                // In this phase clients haven't started the playback yet.
 663                // In other words, LastActivity is in the future,
 664                // when playback unpause is supposed to happen.
 665                // Adjust ticks only if playback actually started.
 0666                startPositionTicks += Math.Max(elapsedTime.Ticks, 0);
 667            }
 668
 0669            return new PlayQueueUpdate(
 0670                reason,
 0671                PlayQueue.LastChange,
 0672                PlayQueue.GetPlaylist(),
 0673                PlayQueue.PlayingItemIndex,
 0674                startPositionTicks,
 0675                isPlaying,
 0676                PlayQueue.ShuffleMode,
 0677                PlayQueue.RepeatMode);
 678        }
 679    }
 680}

Methods/Properties

.ctor(Microsoft.Extensions.Logging.ILoggerFactory,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Session.ISessionManager,MediaBrowser.Controller.Library.ILibraryManager)
AddSession(MediaBrowser.Controller.Session.SessionInfo)
RemoveSession(MediaBrowser.Controller.Session.SessionInfo)
FilterSessions(System.String,MediaBrowser.Model.SyncPlay.SyncPlayBroadcastType)
HasAccessToQueue(Jellyfin.Database.Implementations.Entities.User,System.Collections.Generic.IReadOnlyList`1<System.Guid>)
AllUsersHaveAccessToQueue(System.Collections.Generic.IReadOnlyList`1<System.Guid>)
IsGroupEmpty()
CreateGroup(MediaBrowser.Controller.Session.SessionInfo,MediaBrowser.Controller.SyncPlay.Requests.NewGroupRequest,System.Threading.CancellationToken)
SessionJoin(MediaBrowser.Controller.Session.SessionInfo,MediaBrowser.Controller.SyncPlay.Requests.JoinGroupRequest,System.Threading.CancellationToken)
SessionLeave(MediaBrowser.Controller.Session.SessionInfo,MediaBrowser.Controller.SyncPlay.Requests.LeaveGroupRequest,System.Threading.CancellationToken)
HandleRequest(MediaBrowser.Controller.Session.SessionInfo,MediaBrowser.Controller.SyncPlay.IGroupPlaybackRequest,System.Threading.CancellationToken)
GetInfo()
HasAccessToPlayQueue(Jellyfin.Database.Implementations.Entities.User)
SetIgnoreGroupWait(MediaBrowser.Controller.Session.SessionInfo,System.Boolean)
SetState(MediaBrowser.Controller.SyncPlay.IGroupState)
SendGroupUpdate(MediaBrowser.Controller.Session.SessionInfo,MediaBrowser.Model.SyncPlay.SyncPlayBroadcastType,MediaBrowser.Model.SyncPlay.GroupUpdate`1<T>,System.Threading.CancellationToken)
SendCommand(MediaBrowser.Controller.Session.SessionInfo,MediaBrowser.Model.SyncPlay.SyncPlayBroadcastType,MediaBrowser.Model.SyncPlay.SendCommand,System.Threading.CancellationToken)
NewSyncPlayCommand(MediaBrowser.Model.SyncPlay.SendCommandType)
SanitizePositionTicks(System.Nullable`1<System.Int64>)
UpdatePing(MediaBrowser.Controller.Session.SessionInfo,System.Int64)
GetHighestPing()
SetBuffering(MediaBrowser.Controller.Session.SessionInfo,System.Boolean)
SetAllBuffering(System.Boolean)
IsBuffering()
SetPlayQueue(System.Collections.Generic.IReadOnlyList`1<System.Guid>,System.Int32,System.Int64)
SetPlayingItem(System.Guid)
ClearPlayQueue(System.Boolean)
RemoveFromPlayQueue(System.Collections.Generic.IReadOnlyList`1<System.Guid>)
MoveItemInPlayQueue(System.Guid,System.Int32)
AddToPlayQueue(System.Collections.Generic.IReadOnlyList`1<System.Guid>,MediaBrowser.Model.SyncPlay.GroupQueueMode)
RestartCurrentItem()
NextItemInQueue()
PreviousItemInQueue()
SetRepeatMode(MediaBrowser.Model.SyncPlay.GroupRepeatMode)
SetShuffleMode(MediaBrowser.Model.SyncPlay.GroupShuffleMode)
GetPlayQueueUpdate(MediaBrowser.Model.SyncPlay.PlayQueueUpdateReason)