< 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
0%
Covered lines: 0
Uncovered lines: 211
Coverable lines: 211
Total lines: 685
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 63
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

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.Data.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>
 057        private readonly Dictionary<string, GroupMember> _participants =
 058            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        {
 078            _loggerFactory = loggerFactory;
 079            _userManager = userManager;
 080            _sessionManager = sessionManager;
 081            _libraryManager = libraryManager;
 082            _logger = loggerFactory.CreateLogger<Group>();
 83
 084            _state = new IdleGroupState(loggerFactory);
 085        }
 86
 87        /// <summary>
 88        /// Gets the default ping value used for sessions.
 89        /// </summary>
 90        /// <value>The default ping.</value>
 091        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>
 097        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>
 0103        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        {
 0147            _participants.TryAdd(
 0148                session.Id,
 0149                new GroupMember(session)
 0150                {
 0151                    Ping = DefaultPing,
 0152                    IsBuffering = false
 0153                });
 0154        }
 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        {
 0162            _participants.Remove(session.Id);
 0163        }
 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        {
 0173            return type switch
 0174            {
 0175                SyncPlayBroadcastType.CurrentSession => new string[] { fromId },
 0176                SyncPlayBroadcastType.AllGroup => _participants
 0177                    .Values
 0178                    .Select(member => member.SessionId),
 0179                SyncPlayBroadcastType.AllExceptCurrentSession => _participants
 0180                    .Values
 0181                    .Select(member => member.SessionId)
 0182                    .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>()
 0188            };
 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.
 0201            if (queue is null || queue.Count == 0)
 202            {
 0203                return true;
 204            }
 205
 0206            foreach (var itemId in queue)
 207            {
 0208                var item = _libraryManager.GetItemById(itemId);
 0209                if (!item.IsVisibleStandalone(user))
 210                {
 0211                    return false;
 212                }
 213            }
 214
 0215            return true;
 0216        }
 217
 218        private bool AllUsersHaveAccessToQueue(IReadOnlyList<Guid> queue)
 219        {
 220            // Check if queue is empty.
 0221            if (queue is null || queue.Count == 0)
 222            {
 0223                return true;
 224            }
 225
 226            // Get list of users.
 0227            var users = _participants
 0228                .Values
 0229                .Select(participant => _userManager.GetUserById(participant.UserId));
 230
 231            // Find problematic users.
 0232            var usersWithNoAccess = users.Where(user => !HasAccessToQueue(user, queue));
 233
 234            // All users must be able to access the queue.
 0235            return !usersWithNoAccess.Any();
 236        }
 237
 238        /// <summary>
 239        /// Checks if the group is empty.
 240        /// </summary>
 241        /// <returns><c>true</c> if the group is empty, <c>false</c> otherwise.</returns>
 0242        public bool IsGroupEmpty() => _participants.Count == 0;
 243
 244        /// <summary>
 245        /// Initializes the group with the session's info.
 246        /// </summary>
 247        /// <param name="session">The session.</param>
 248        /// <param name="request">The request.</param>
 249        /// <param name="cancellationToken">The cancellation token.</param>
 250        public void CreateGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken)
 251        {
 0252            GroupName = request.GroupName;
 0253            AddSession(session);
 254
 0255            var sessionIsPlayingAnItem = session.FullNowPlayingItem is not null;
 256
 0257            RestartCurrentItem();
 258
 0259            if (sessionIsPlayingAnItem)
 260            {
 0261                var playlist = session.NowPlayingQueue.Select(item => item.Id).ToList();
 0262                PlayQueue.Reset();
 0263                PlayQueue.SetPlaylist(playlist);
 0264                PlayQueue.SetPlayingItemById(session.FullNowPlayingItem.Id);
 0265                RunTimeTicks = session.FullNowPlayingItem.RunTimeTicks ?? 0;
 0266                PositionTicks = session.PlayState.PositionTicks ?? 0;
 267
 268                // Maintain playstate.
 0269                var waitingState = new WaitingGroupState(_loggerFactory)
 0270                {
 0271                    ResumePlaying = !session.PlayState.IsPaused
 0272                };
 0273                SetState(waitingState);
 274            }
 275
 0276            var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo());
 0277            SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 278
 0279            _state.SessionJoined(this, _state.Type, session, cancellationToken);
 280
 0281            _logger.LogInformation("Session {SessionId} created group {GroupId}.", session.Id, GroupId.ToString());
 0282        }
 283
 284        /// <summary>
 285        /// Adds the session to the group.
 286        /// </summary>
 287        /// <param name="session">The session.</param>
 288        /// <param name="request">The request.</param>
 289        /// <param name="cancellationToken">The cancellation token.</param>
 290        public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken)
 291        {
 0292            AddSession(session);
 293
 0294            var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo());
 0295            SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 296
 0297            var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
 0298            SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 299
 0300            _state.SessionJoined(this, _state.Type, session, cancellationToken);
 301
 0302            _logger.LogInformation("Session {SessionId} joined group {GroupId}.", session.Id, GroupId.ToString());
 0303        }
 304
 305        /// <summary>
 306        /// Removes the session from the group.
 307        /// </summary>
 308        /// <param name="session">The session.</param>
 309        /// <param name="request">The request.</param>
 310        /// <param name="cancellationToken">The cancellation token.</param>
 311        public void SessionLeave(SessionInfo session, LeaveGroupRequest request, CancellationToken cancellationToken)
 312        {
 0313            _state.SessionLeaving(this, _state.Type, session, cancellationToken);
 314
 0315            RemoveSession(session);
 316
 0317            var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, GroupId.ToString());
 0318            SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken);
 319
 0320            var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName);
 0321            SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
 322
 0323            _logger.LogInformation("Session {SessionId} left group {GroupId}.", session.Id, GroupId.ToString());
 0324        }
 325
 326        /// <summary>
 327        /// Handles the requested action by the session.
 328        /// </summary>
 329        /// <param name="session">The session.</param>
 330        /// <param name="request">The requested action.</param>
 331        /// <param name="cancellationToken">The cancellation token.</param>
 332        public void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToke
 333        {
 334            // The server's job is to maintain a consistent state for clients to reference
 335            // and notify clients of state changes. The actual syncing of media playback
 336            // happens client side. Clients are aware of the server's time and use it to sync.
 0337            _logger.LogInformation("Session {SessionId} requested {RequestType} in group {GroupId} that is {StateType}."
 338
 339            // Apply requested changes to this group given its current state.
 340            // Every request has a slightly different outcome depending on the group's state.
 341            // There are currently four different group states that accomplish different goals:
 342            // - Idle: in this state no media is playing and clients should be idle (playback is stopped).
 343            // - Waiting: in this state the group is waiting for all the clients to be ready to start the playback,
 344            //      that is, they've either finished loading the media for the first time or they've finished buffering.
 345            //      Once all clients report to be ready the group's state can change to Playing or Paused.
 346            // - Playing: clients have some media loaded and playback is unpaused.
 347            // - Paused: clients have some media loaded but playback is currently paused.
 0348            request.Apply(this, _state, session, cancellationToken);
 0349        }
 350
 351        /// <summary>
 352        /// Gets the info about the group for the clients.
 353        /// </summary>
 354        /// <returns>The group info for the clients.</returns>
 355        public GroupInfoDto GetInfo()
 356        {
 0357            var participants = _participants.Values.Select(session => session.UserName).Distinct().ToList();
 0358            return new GroupInfoDto(GroupId, GroupName, _state.Type, participants, DateTime.UtcNow);
 359        }
 360
 361        /// <summary>
 362        /// Checks if a user has access to all content in the play queue.
 363        /// </summary>
 364        /// <param name="user">The user.</param>
 365        /// <returns><c>true</c> if the user can access the play queue; <c>false</c> otherwise.</returns>
 366        public bool HasAccessToPlayQueue(User user)
 367        {
 0368            var items = PlayQueue.GetPlaylist().Select(item => item.ItemId).ToList();
 0369            return HasAccessToQueue(user, items);
 370        }
 371
 372        /// <inheritdoc />
 373        public void SetIgnoreGroupWait(SessionInfo session, bool ignoreGroupWait)
 374        {
 0375            if (_participants.TryGetValue(session.Id, out GroupMember value))
 376            {
 0377                value.IgnoreGroupWait = ignoreGroupWait;
 378            }
 0379        }
 380
 381        /// <inheritdoc />
 382        public void SetState(IGroupState state)
 383        {
 0384            _logger.LogInformation("Group {GroupId} switching from {FromStateType} to {ToStateType}.", GroupId.ToString(
 0385            this._state = state;
 0386        }
 387
 388        /// <inheritdoc />
 389        public Task SendGroupUpdate<T>(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate<T> message, Cancellatio
 390        {
 391            IEnumerable<Task> GetTasks()
 392            {
 393                foreach (var sessionId in FilterSessions(from.Id, type))
 394                {
 395                    yield return _sessionManager.SendSyncPlayGroupUpdate(sessionId, message, cancellationToken);
 396                }
 397            }
 398
 0399            return Task.WhenAll(GetTasks());
 400        }
 401
 402        /// <inheritdoc />
 403        public Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken can
 404        {
 405            IEnumerable<Task> GetTasks()
 406            {
 407                foreach (var sessionId in FilterSessions(from.Id, type))
 408                {
 409                    yield return _sessionManager.SendSyncPlayCommand(sessionId, message, cancellationToken);
 410                }
 411            }
 412
 0413            return Task.WhenAll(GetTasks());
 414        }
 415
 416        /// <inheritdoc />
 417        public SendCommand NewSyncPlayCommand(SendCommandType type)
 418        {
 0419            return new SendCommand(
 0420                GroupId,
 0421                PlayQueue.GetPlayingItemPlaylistId(),
 0422                LastActivity,
 0423                type,
 0424                PositionTicks,
 0425                DateTime.UtcNow);
 426        }
 427
 428        /// <inheritdoc />
 429        public GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data)
 430        {
 0431            return new GroupUpdate<T>(GroupId, type, data);
 432        }
 433
 434        /// <inheritdoc />
 435        public long SanitizePositionTicks(long? positionTicks)
 436        {
 0437            var ticks = positionTicks ?? 0;
 0438            return Math.Clamp(ticks, 0, RunTimeTicks);
 439        }
 440
 441        /// <inheritdoc />
 442        public void UpdatePing(SessionInfo session, long ping)
 443        {
 0444            if (_participants.TryGetValue(session.Id, out GroupMember value))
 445            {
 0446                value.Ping = ping;
 447            }
 0448        }
 449
 450        /// <inheritdoc />
 451        public long GetHighestPing()
 452        {
 0453            long max = long.MinValue;
 0454            foreach (var session in _participants.Values)
 455            {
 0456                max = Math.Max(max, session.Ping);
 457            }
 458
 0459            return max;
 460        }
 461
 462        /// <inheritdoc />
 463        public void SetBuffering(SessionInfo session, bool isBuffering)
 464        {
 0465            if (_participants.TryGetValue(session.Id, out GroupMember value))
 466            {
 0467                value.IsBuffering = isBuffering;
 468            }
 0469        }
 470
 471        /// <inheritdoc />
 472        public void SetAllBuffering(bool isBuffering)
 473        {
 0474            foreach (var session in _participants.Values)
 475            {
 0476                session.IsBuffering = isBuffering;
 477            }
 0478        }
 479
 480        /// <inheritdoc />
 481        public bool IsBuffering()
 482        {
 0483            foreach (var session in _participants.Values)
 484            {
 0485                if (session.IsBuffering && !session.IgnoreGroupWait)
 486                {
 0487                    return true;
 488                }
 489            }
 490
 0491            return false;
 0492        }
 493
 494        /// <inheritdoc />
 495        public bool SetPlayQueue(IReadOnlyList<Guid> playQueue, int playingItemPosition, long startPositionTicks)
 496        {
 497            // Ignore on empty queue or invalid item position.
 0498            if (playQueue.Count == 0 || playingItemPosition >= playQueue.Count || playingItemPosition < 0)
 499            {
 0500                return false;
 501            }
 502
 503            // Check if participants can access the new playing queue.
 0504            if (!AllUsersHaveAccessToQueue(playQueue))
 505            {
 0506                return false;
 507            }
 508
 0509            PlayQueue.Reset();
 0510            PlayQueue.SetPlaylist(playQueue);
 0511            PlayQueue.SetPlayingItemByIndex(playingItemPosition);
 0512            var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId());
 0513            RunTimeTicks = item.RunTimeTicks ?? 0;
 0514            PositionTicks = startPositionTicks;
 0515            LastActivity = DateTime.UtcNow;
 516
 0517            return true;
 518        }
 519
 520        /// <inheritdoc />
 521        public bool SetPlayingItem(Guid playlistItemId)
 522        {
 0523            var itemFound = PlayQueue.SetPlayingItemByPlaylistId(playlistItemId);
 524
 0525            if (itemFound)
 526            {
 0527                var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId());
 0528                RunTimeTicks = item.RunTimeTicks ?? 0;
 529            }
 530            else
 531            {
 0532                RunTimeTicks = 0;
 533            }
 534
 0535            RestartCurrentItem();
 536
 0537            return itemFound;
 538        }
 539
 540        /// <inheritdoc />
 541        public void ClearPlayQueue(bool clearPlayingItem)
 542        {
 0543            PlayQueue.ClearPlaylist(clearPlayingItem);
 0544            if (clearPlayingItem)
 545            {
 0546                RestartCurrentItem();
 547            }
 0548        }
 549
 550        /// <inheritdoc />
 551        public bool RemoveFromPlayQueue(IReadOnlyList<Guid> playlistItemIds)
 552        {
 0553            var playingItemRemoved = PlayQueue.RemoveFromPlaylist(playlistItemIds);
 0554            if (playingItemRemoved)
 555            {
 0556                var itemId = PlayQueue.GetPlayingItemId();
 0557                if (!itemId.IsEmpty())
 558                {
 0559                    var item = _libraryManager.GetItemById(itemId);
 0560                    RunTimeTicks = item.RunTimeTicks ?? 0;
 561                }
 562                else
 563                {
 0564                    RunTimeTicks = 0;
 565                }
 566
 0567                RestartCurrentItem();
 568            }
 569
 0570            return playingItemRemoved;
 571        }
 572
 573        /// <inheritdoc />
 574        public bool MoveItemInPlayQueue(Guid playlistItemId, int newIndex)
 575        {
 0576            return PlayQueue.MovePlaylistItem(playlistItemId, newIndex);
 577        }
 578
 579        /// <inheritdoc />
 580        public bool AddToPlayQueue(IReadOnlyList<Guid> newItems, GroupQueueMode mode)
 581        {
 582            // Ignore on empty list.
 0583            if (newItems.Count == 0)
 584            {
 0585                return false;
 586            }
 587
 588            // Check if participants can access the new playing queue.
 0589            if (!AllUsersHaveAccessToQueue(newItems))
 590            {
 0591                return false;
 592            }
 593
 0594            if (mode.Equals(GroupQueueMode.QueueNext))
 595            {
 0596                PlayQueue.QueueNext(newItems);
 597            }
 598            else
 599            {
 0600                PlayQueue.Queue(newItems);
 601            }
 602
 0603            return true;
 604        }
 605
 606        /// <inheritdoc />
 607        public void RestartCurrentItem()
 608        {
 0609            PositionTicks = 0;
 0610            LastActivity = DateTime.UtcNow;
 0611        }
 612
 613        /// <inheritdoc />
 614        public bool NextItemInQueue()
 615        {
 0616            var update = PlayQueue.Next();
 0617            if (update)
 618            {
 0619                var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId());
 0620                RunTimeTicks = item.RunTimeTicks ?? 0;
 0621                RestartCurrentItem();
 0622                return true;
 623            }
 624
 0625            return false;
 626        }
 627
 628        /// <inheritdoc />
 629        public bool PreviousItemInQueue()
 630        {
 0631            var update = PlayQueue.Previous();
 0632            if (update)
 633            {
 0634                var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId());
 0635                RunTimeTicks = item.RunTimeTicks ?? 0;
 0636                RestartCurrentItem();
 0637                return true;
 638            }
 639
 0640            return false;
 641        }
 642
 643        /// <inheritdoc />
 644        public void SetRepeatMode(GroupRepeatMode mode)
 645        {
 0646            PlayQueue.SetRepeatMode(mode);
 0647        }
 648
 649        /// <inheritdoc />
 650        public void SetShuffleMode(GroupShuffleMode mode)
 651        {
 0652            PlayQueue.SetShuffleMode(mode);
 0653        }
 654
 655        /// <inheritdoc />
 656        public PlayQueueUpdate GetPlayQueueUpdate(PlayQueueUpdateReason reason)
 657        {
 0658            var startPositionTicks = PositionTicks;
 0659            var isPlaying = _state.Type.Equals(GroupStateType.Playing);
 660
 0661            if (isPlaying)
 662            {
 0663                var currentTime = DateTime.UtcNow;
 0664                var elapsedTime = currentTime - LastActivity;
 665                // Elapsed time is negative if event happens
 666                // during the delay added to account for latency.
 667                // In this phase clients haven't started the playback yet.
 668                // In other words, LastActivity is in the future,
 669                // when playback unpause is supposed to happen.
 670                // Adjust ticks only if playback actually started.
 0671                startPositionTicks += Math.Max(elapsedTime.Ticks, 0);
 672            }
 673
 0674            return new PlayQueueUpdate(
 0675                reason,
 0676                PlayQueue.LastChange,
 0677                PlayQueue.GetPlaylist(),
 0678                PlayQueue.PlayingItemIndex,
 0679                startPositionTicks,
 0680                isPlaying,
 0681                PlayQueue.ShuffleMode,
 0682                PlayQueue.RepeatMode);
 683        }
 684    }
 685}

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.Data.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.Data.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)
NewSyncPlayGroupUpdate(MediaBrowser.Model.SyncPlay.GroupUpdateType,T)
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)