< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Session.SessionInfo
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Session/SessionInfo.cs
Line coverage
65%
Covered lines: 71
Uncovered lines: 37
Coverable lines: 108
Total lines: 497
Line coverage: 65.7%
Branch coverage
48%
Covered branches: 31
Total branches: 64
Branch coverage: 48.4%
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: 29.5% (31/105) Branch coverage: 21.8% (14/64) Total lines: 4865/20/2026 - 12:15:44 AM Line coverage: 29.5% (31/105) Branch coverage: 18.7% (12/64) Total lines: 4866/1/2026 - 12:16:05 AM Line coverage: 28.8% (30/104) Branch coverage: 18.7% (12/64) Total lines: 4797/3/2026 - 12:15:32 AM Line coverage: 38.4% (40/104) Branch coverage: 25% (16/64) Total lines: 4797/6/2026 - 12:16:28 AM Line coverage: 41.3% (43/104) Branch coverage: 29.6% (19/64) Total lines: 47910/29/2026 - 12:17:11 AM Line coverage: 65.7% (71/108) Branch coverage: 48.4% (31/64) Total lines: 497 5/16/2026 - 12:15:55 AM Line coverage: 29.5% (31/105) Branch coverage: 21.8% (14/64) Total lines: 4865/20/2026 - 12:15:44 AM Line coverage: 29.5% (31/105) Branch coverage: 18.7% (12/64) Total lines: 4866/1/2026 - 12:16:05 AM Line coverage: 28.8% (30/104) Branch coverage: 18.7% (12/64) Total lines: 4797/3/2026 - 12:15:32 AM Line coverage: 38.4% (40/104) Branch coverage: 25% (16/64) Total lines: 4797/6/2026 - 12:16:28 AM Line coverage: 41.3% (43/104) Branch coverage: 29.6% (19/64) Total lines: 47910/29/2026 - 12:17:11 AM Line coverage: 65.7% (71/108) Branch coverage: 48.4% (31/64) Total lines: 497

Coverage delta

Coverage delta 25 -25

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_PlayableMediaTypes()50%2266.66%
get_LastPlaybackCheckInPositionTicks()100%11100%
get_IsActive()50%11650%
get_SupportsMediaControl()25%35825%
get_SupportsRemoteControl()25%35825%
get_SupportedCommands()50%22100%
EnsureController(...)50%4470%
AddController(...)0%620%
ContainsUser(...)66.66%9657.14%
StartAutomaticProgress(...)75%4488.88%
OnProgressTimerCallback()50%231463.63%
StopAutomaticProgress()100%22100%
DisposeAsync()66.66%7675%

File(s)

/srv/git/jellyfin/MediaBrowser.Controller/Session/SessionInfo.cs

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Linq;
 6using System.Text.Json.Serialization;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Jellyfin.Data.Enums;
 10using MediaBrowser.Controller.Entities;
 11using MediaBrowser.Model.Dto;
 12using MediaBrowser.Model.Session;
 13using Microsoft.Extensions.Logging;
 14
 15namespace MediaBrowser.Controller.Session
 16{
 17    /// <summary>
 18    /// Class SessionInfo.
 19    /// </summary>
 20    public sealed class SessionInfo : IAsyncDisposable
 21    {
 22        // 1 second
 23        private const long ProgressIncrement = 10000000;
 24
 25        private readonly ISessionManager _sessionManager;
 26        private readonly ILogger _logger;
 27
 9428        private readonly Lock _progressLock = new();
 29        private Timer _progressTimer;
 30        private PlaybackProgressInfo _lastProgressInfo;
 31        private long? _lastPlaybackCheckInPositionTicks;
 32
 33        private bool _disposed;
 34
 35        /// <summary>
 36        /// Initializes a new instance of the <see cref="SessionInfo"/> class.
 37        /// </summary>
 38        /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
 39        /// <param name="logger">Instance of <see cref="ILogger"/> interface.</param>
 40        public SessionInfo(ISessionManager sessionManager, ILogger logger)
 41        {
 9442            _sessionManager = sessionManager;
 9443            _logger = logger;
 44
 9445            AdditionalUsers = [];
 9446            PlayState = new PlayerStateInfo();
 9447            SessionControllers = [];
 9448            NowPlayingQueue = [];
 9449        }
 50
 51        /// <summary>
 52        /// Gets or sets the play state.
 53        /// </summary>
 54        /// <value>The play state.</value>
 55        public PlayerStateInfo PlayState { get; set; }
 56
 57        /// <summary>
 58        /// Gets or sets the additional users.
 59        /// </summary>
 60        /// <value>The additional users.</value>
 61        public IReadOnlyList<SessionUserInfo> AdditionalUsers { get; set; }
 62
 63        /// <summary>
 64        /// Gets or sets the client capabilities.
 65        /// </summary>
 66        /// <value>The client capabilities.</value>
 67        public ClientCapabilities Capabilities { get; set; }
 68
 69        /// <summary>
 70        /// Gets or sets the remote end point.
 71        /// </summary>
 72        /// <value>The remote end point.</value>
 73        public string RemoteEndPoint { get; set; }
 74
 75        /// <summary>
 76        /// Gets the playable media types.
 77        /// </summary>
 78        /// <value>The playable media types.</value>
 79        public IReadOnlyList<MediaType> PlayableMediaTypes
 80        {
 81            get
 82            {
 1683                if (Capabilities is null)
 84                {
 085                    return [];
 86                }
 87
 1688                return Capabilities.PlayableMediaTypes;
 89            }
 90        }
 91
 92        /// <summary>
 93        /// Gets or sets the id.
 94        /// </summary>
 95        /// <value>The id.</value>
 96        public string Id { get; set; }
 97
 98        /// <summary>
 99        /// Gets or sets the user id.
 100        /// </summary>
 101        /// <value>The user id.</value>
 102        public Guid UserId { get; set; }
 103
 104        /// <summary>
 105        /// Gets or sets the username.
 106        /// </summary>
 107        /// <value>The username.</value>
 108        public string UserName { get; set; }
 109
 110        /// <summary>
 111        /// Gets or sets the type of the client.
 112        /// </summary>
 113        /// <value>The type of the client.</value>
 114        public string Client { get; set; }
 115
 116        /// <summary>
 117        /// Gets or sets the last activity date.
 118        /// </summary>
 119        /// <value>The last activity date.</value>
 120        public DateTime LastActivityDate { get; set; }
 121
 122        /// <summary>
 123        /// Gets or sets the last playback check in.
 124        /// </summary>
 125        /// <value>The last playback check in.</value>
 126        public DateTime LastPlaybackCheckIn { get; set; }
 127
 128        /// <summary>
 129        /// Gets the position reported by the client at the last playback check-in.
 130        /// </summary>
 131        /// <value>The position ticks, or <see langword="null"/> if the client did not report a position.</value>
 132        [JsonIgnore]
 133        public long? LastPlaybackCheckInPositionTicks
 134        {
 135            get
 6136            {
 137                lock (_progressLock)
 138                {
 6139                    return _lastPlaybackCheckInPositionTicks;
 140                }
 6141            }
 142        }
 143
 144        /// <summary>
 145        /// Gets or sets the last paused date.
 146        /// </summary>
 147        /// <value>The last paused date.</value>
 148        public DateTime? LastPausedDate { get; set; }
 149
 150        /// <summary>
 151        /// Gets or sets the name of the device.
 152        /// </summary>
 153        /// <value>The name of the device.</value>
 154        public string DeviceName { get; set; }
 155
 156        /// <summary>
 157        /// Gets or sets the type of the device.
 158        /// </summary>
 159        /// <value>The type of the device.</value>
 160        public string DeviceType { get; set; }
 161
 162        /// <summary>
 163        /// Gets or sets the now playing item.
 164        /// </summary>
 165        /// <value>The now playing item.</value>
 166        public BaseItemDto NowPlayingItem { get; set; }
 167
 168        /// <summary>
 169        /// Gets or sets the now playing queue full items.
 170        /// </summary>
 171        /// <value>The now playing queue full items.</value>
 172        [JsonIgnore]
 173        public BaseItem FullNowPlayingItem { get; set; }
 174
 175        /// <summary>
 176        /// Gets or sets the now viewing item.
 177        /// </summary>
 178        /// <value>The now viewing item.</value>
 179        public BaseItemDto NowViewingItem { get; set; }
 180
 181        /// <summary>
 182        /// Gets or sets the device id.
 183        /// </summary>
 184        /// <value>The device id.</value>
 185        public string DeviceId { get; set; }
 186
 187        /// <summary>
 188        /// Gets or sets the application version.
 189        /// </summary>
 190        /// <value>The application version.</value>
 191        public string ApplicationVersion { get; set; }
 192
 193        /// <summary>
 194        /// Gets or sets the session controller.
 195        /// </summary>
 196        /// <value>The session controller.</value>
 197        [JsonIgnore]
 198        public IReadOnlyList<ISessionController> SessionControllers { get; set; }
 199
 200        /// <summary>
 201        /// Gets or sets the transcoding info.
 202        /// </summary>
 203        /// <value>The transcoding info.</value>
 204        public TranscodingInfo TranscodingInfo { get; set; }
 205
 206        /// <summary>
 207        /// Gets a value indicating whether this instance is active.
 208        /// </summary>
 209        /// <value><c>true</c> if this instance is active; otherwise, <c>false</c>.</value>
 210        public bool IsActive
 211        {
 212            get
 213            {
 16214                var controllers = SessionControllers;
 32215                foreach (var controller in controllers)
 216                {
 0217                    if (controller.IsSessionActive)
 218                    {
 0219                        return true;
 220                    }
 221                }
 222
 16223                if (controllers.Count > 0)
 224                {
 0225                    return false;
 226                }
 227
 16228                return true;
 0229            }
 230        }
 231
 232        /// <summary>
 233        /// Gets a value indicating whether the session supports media control.
 234        /// </summary>
 235        /// <value><c>true</c> if this session supports media control; otherwise, <c>false</c>.</value>
 236        public bool SupportsMediaControl
 237        {
 238            get
 239            {
 16240                if (Capabilities is null || !Capabilities.SupportsMediaControl)
 241                {
 16242                    return false;
 243                }
 244
 0245                var controllers = SessionControllers;
 0246                foreach (var controller in controllers)
 247                {
 0248                    if (controller.SupportsMediaControl)
 249                    {
 0250                        return true;
 251                    }
 252                }
 253
 0254                return false;
 0255            }
 256        }
 257
 258        /// <summary>
 259        /// Gets a value indicating whether the session supports remote control.
 260        /// </summary>
 261        /// <value><c>true</c> if this session supports remote control; otherwise, <c>false</c>.</value>
 262        public bool SupportsRemoteControl
 263        {
 264            get
 265            {
 16266                if (Capabilities is null || !Capabilities.SupportsMediaControl)
 267                {
 16268                    return false;
 269                }
 270
 0271                var controllers = SessionControllers;
 0272                foreach (var controller in controllers)
 273                {
 0274                    if (controller.SupportsMediaControl)
 275                    {
 0276                        return true;
 277                    }
 278                }
 279
 0280                return false;
 0281            }
 282        }
 283
 284        /// <summary>
 285        /// Gets or sets the now playing queue.
 286        /// </summary>
 287        /// <value>The now playing queue.</value>
 288        public IReadOnlyList<QueueItem> NowPlayingQueue { get; set; }
 289
 290        /// <summary>
 291        /// Gets or sets a value indicating whether the session has a custom device name.
 292        /// </summary>
 293        /// <value><c>true</c> if the session has a custom device name; otherwise, <c>false</c>.</value>
 294        public bool HasCustomDeviceName { get; set; }
 295
 296        /// <summary>
 297        /// Gets or sets the playlist item id.
 298        /// </summary>
 299        /// <value>The playlist item id.</value>
 300        public string PlaylistItemId { get; set; }
 301
 302        /// <summary>
 303        /// Gets or sets the server id.
 304        /// </summary>
 305        /// <value>The server id.</value>
 306        public string ServerId { get; set; }
 307
 308        /// <summary>
 309        /// Gets or sets the user primary image tag.
 310        /// </summary>
 311        /// <value>The user primary image tag.</value>
 312        public string UserPrimaryImageTag { get; set; }
 313
 314        /// <summary>
 315        /// Gets the supported commands.
 316        /// </summary>
 317        /// <value>The supported commands.</value>
 318        public IReadOnlyList<GeneralCommandType> SupportedCommands
 16319            => Capabilities is null ? [] : Capabilities.SupportedCommands;
 320
 321        /// <summary>
 322        /// Ensures a controller of type exists.
 323        /// </summary>
 324        /// <typeparam name="T">Class to register.</typeparam>
 325        /// <param name="factory">The factory.</param>
 326        /// <returns>Tuple{ISessionController, bool}.</returns>
 327        public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
 328        {
 1329            var controllers = SessionControllers.ToList();
 2330            foreach (var controller in controllers)
 331            {
 0332                if (controller is T)
 333                {
 0334                    return new Tuple<ISessionController, bool>(controller, false);
 335                }
 336            }
 337
 1338            var newController = factory(this);
 1339            _logger.LogDebug("Creating new {Factory}", newController.GetType().Name);
 1340            controllers.Add(newController);
 341
 1342            SessionControllers = [.. controllers];
 1343            return new Tuple<ISessionController, bool>(newController, true);
 0344        }
 345
 346        /// <summary>
 347        /// Adds a controller to the session.
 348        /// </summary>
 349        /// <param name="controller">The controller.</param>
 350        public void AddController(ISessionController controller)
 351        {
 0352            SessionControllers = [.. SessionControllers, controller];
 0353        }
 354
 355        /// <summary>
 356        /// Gets a value indicating whether the session contains a user.
 357        /// </summary>
 358        /// <param name="userId">The user id to check.</param>
 359        /// <returns><c>true</c> if this session contains the user; otherwise, <c>false</c>.</returns>
 360        public bool ContainsUser(Guid userId)
 361        {
 7362            if (UserId.Equals(userId))
 363            {
 2364                return true;
 365            }
 366
 10367            foreach (var additionalUser in AdditionalUsers)
 368            {
 0369                if (additionalUser.UserId.Equals(userId))
 370                {
 0371                    return true;
 372                }
 373            }
 374
 5375            return false;
 0376        }
 377
 378        /// <summary>
 379        /// Starts automatic progressing.
 380        /// </summary>
 381        /// <param name="progressInfo">The playback progress info.</param>
 382        /// <value>The supported commands.</value>
 383        public void StartAutomaticProgress(PlaybackProgressInfo progressInfo)
 384        {
 6385            if (_disposed)
 386            {
 0387                return;
 388            }
 389
 390            lock (_progressLock)
 391            {
 6392                _lastPlaybackCheckInPositionTicks = progressInfo.PositionTicks;
 6393                _lastProgressInfo = progressInfo;
 394
 6395                if (_progressTimer is null)
 396                {
 5397                    _progressTimer = new Timer(OnProgressTimerCallback, null, 1000, 1000);
 398                }
 399                else
 400                {
 1401                    _progressTimer.Change(1000, 1000);
 402                }
 1403            }
 6404        }
 405
 406        private async void OnProgressTimerCallback(object state)
 407        {
 1408            if (_disposed)
 409            {
 0410                return;
 411            }
 412
 1413            var progressInfo = _lastProgressInfo;
 1414            if (progressInfo is null)
 415            {
 0416                return;
 417            }
 418
 1419            if (progressInfo.IsPaused)
 420            {
 0421                return;
 422            }
 423
 1424            var positionTicks = progressInfo.PositionTicks ?? 0;
 1425            if (positionTicks < 0)
 426            {
 0427                positionTicks = 0;
 428            }
 429
 1430            var newPositionTicks = positionTicks + ProgressIncrement;
 1431            var item = progressInfo.Item;
 1432            long? runtimeTicks = item?.RunTimeTicks;
 433
 434            // Don't report beyond the runtime
 1435            if (runtimeTicks.HasValue && newPositionTicks >= runtimeTicks.Value)
 436            {
 0437                return;
 438            }
 439
 1440            progressInfo.PositionTicks = newPositionTicks;
 441
 442            try
 443            {
 1444                await _sessionManager.OnPlaybackProgress(progressInfo, true).ConfigureAwait(false);
 1445            }
 0446            catch (Exception ex)
 447            {
 0448                _logger.LogError(ex, "Error reporting playback progress");
 0449            }
 1450        }
 451
 452        /// <summary>
 453        /// Stops automatic progressing.
 454        /// </summary>
 455        public void StopAutomaticProgress()
 40456        {
 457            lock (_progressLock)
 458            {
 40459                if (_progressTimer is not null)
 460                {
 5461                    _progressTimer.Dispose();
 5462                    _progressTimer = null;
 463                }
 464
 40465                _lastProgressInfo = null;
 40466            }
 40467        }
 468
 469        /// <summary>
 470        /// Disposes the instance async.
 471        /// </summary>
 472        /// <returns>ValueTask.</returns>
 473        public async ValueTask DisposeAsync()
 474        {
 36475            _disposed = true;
 476
 36477            StopAutomaticProgress();
 478
 36479            var controllers = SessionControllers.ToList();
 36480            SessionControllers = [];
 481
 74482            foreach (var controller in controllers)
 483            {
 1484                if (controller is IAsyncDisposable disposableAsync)
 485                {
 1486                    _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType()
 1487                    await disposableAsync.DisposeAsync().ConfigureAwait(false);
 488                }
 0489                else if (controller is IDisposable disposable)
 490                {
 0491                    _logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name)
 0492                    disposable.Dispose();
 493                }
 494            }
 36495        }
 496    }
 497}