< Summary - Jellyfin

Information
Class: MediaBrowser.Controller.Session.SessionInfo
Assembly: MediaBrowser.Controller
File(s): /srv/git/jellyfin/MediaBrowser.Controller/Session/SessionInfo.cs
Line coverage
28%
Covered lines: 30
Uncovered lines: 74
Coverable lines: 104
Total lines: 479
Line coverage: 28.8%
Branch coverage
18%
Covered branches: 12
Total branches: 64
Branch coverage: 18.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/5/2026 - 12:13:57 AM Line coverage: 35.2% (25/71) Branch coverage: 27.2% (12/44) Total lines: 4864/19/2026 - 12:14:27 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: 479 3/5/2026 - 12:13:57 AM Line coverage: 35.2% (25/71) Branch coverage: 27.2% (12/44) Total lines: 4864/19/2026 - 12:14:27 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: 479

Coverage delta

Coverage delta 6 -6

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_PlayableMediaTypes()50%2266.66%
get_IsActive()50%11650%
get_SupportsMediaControl()25%35825%
get_SupportsRemoteControl()25%35825%
get_SupportedCommands()50%22100%
EnsureController(...)0%2040%
AddController(...)0%620%
ContainsUser(...)0%4260%
StartAutomaticProgress(...)0%2040%
OnProgressTimerCallback()0%210140%
StopAutomaticProgress()50%2271.42%
DisposeAsync()33.33%11650%

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
 1528        private readonly Lock _progressLock = new();
 29        private Timer _progressTimer;
 30        private PlaybackProgressInfo _lastProgressInfo;
 31
 32        private bool _disposed;
 33
 34        /// <summary>
 35        /// Initializes a new instance of the <see cref="SessionInfo"/> class.
 36        /// </summary>
 37        /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
 38        /// <param name="logger">Instance of <see cref="ILogger"/> interface.</param>
 39        public SessionInfo(ISessionManager sessionManager, ILogger logger)
 40        {
 1541            _sessionManager = sessionManager;
 1542            _logger = logger;
 43
 1544            AdditionalUsers = [];
 1545            PlayState = new PlayerStateInfo();
 1546            SessionControllers = [];
 1547            NowPlayingQueue = [];
 1548        }
 49
 50        /// <summary>
 51        /// Gets or sets the play state.
 52        /// </summary>
 53        /// <value>The play state.</value>
 54        public PlayerStateInfo PlayState { get; set; }
 55
 56        /// <summary>
 57        /// Gets or sets the additional users.
 58        /// </summary>
 59        /// <value>The additional users.</value>
 60        public IReadOnlyList<SessionUserInfo> AdditionalUsers { get; set; }
 61
 62        /// <summary>
 63        /// Gets or sets the client capabilities.
 64        /// </summary>
 65        /// <value>The client capabilities.</value>
 66        public ClientCapabilities Capabilities { get; set; }
 67
 68        /// <summary>
 69        /// Gets or sets the remote end point.
 70        /// </summary>
 71        /// <value>The remote end point.</value>
 72        public string RemoteEndPoint { get; set; }
 73
 74        /// <summary>
 75        /// Gets the playable media types.
 76        /// </summary>
 77        /// <value>The playable media types.</value>
 78        public IReadOnlyList<MediaType> PlayableMediaTypes
 79        {
 80            get
 81            {
 1582                if (Capabilities is null)
 83                {
 084                    return [];
 85                }
 86
 1587                return Capabilities.PlayableMediaTypes;
 88            }
 89        }
 90
 91        /// <summary>
 92        /// Gets or sets the id.
 93        /// </summary>
 94        /// <value>The id.</value>
 95        public string Id { get; set; }
 96
 97        /// <summary>
 98        /// Gets or sets the user id.
 99        /// </summary>
 100        /// <value>The user id.</value>
 101        public Guid UserId { get; set; }
 102
 103        /// <summary>
 104        /// Gets or sets the username.
 105        /// </summary>
 106        /// <value>The username.</value>
 107        public string UserName { get; set; }
 108
 109        /// <summary>
 110        /// Gets or sets the type of the client.
 111        /// </summary>
 112        /// <value>The type of the client.</value>
 113        public string Client { get; set; }
 114
 115        /// <summary>
 116        /// Gets or sets the last activity date.
 117        /// </summary>
 118        /// <value>The last activity date.</value>
 119        public DateTime LastActivityDate { get; set; }
 120
 121        /// <summary>
 122        /// Gets or sets the last playback check in.
 123        /// </summary>
 124        /// <value>The last playback check in.</value>
 125        public DateTime LastPlaybackCheckIn { get; set; }
 126
 127        /// <summary>
 128        /// Gets or sets the last paused date.
 129        /// </summary>
 130        /// <value>The last paused date.</value>
 131        public DateTime? LastPausedDate { get; set; }
 132
 133        /// <summary>
 134        /// Gets or sets the name of the device.
 135        /// </summary>
 136        /// <value>The name of the device.</value>
 137        public string DeviceName { get; set; }
 138
 139        /// <summary>
 140        /// Gets or sets the type of the device.
 141        /// </summary>
 142        /// <value>The type of the device.</value>
 143        public string DeviceType { get; set; }
 144
 145        /// <summary>
 146        /// Gets or sets the now playing item.
 147        /// </summary>
 148        /// <value>The now playing item.</value>
 149        public BaseItemDto NowPlayingItem { get; set; }
 150
 151        /// <summary>
 152        /// Gets or sets the now playing queue full items.
 153        /// </summary>
 154        /// <value>The now playing queue full items.</value>
 155        [JsonIgnore]
 156        public BaseItem FullNowPlayingItem { get; set; }
 157
 158        /// <summary>
 159        /// Gets or sets the now viewing item.
 160        /// </summary>
 161        /// <value>The now viewing item.</value>
 162        public BaseItemDto NowViewingItem { get; set; }
 163
 164        /// <summary>
 165        /// Gets or sets the device id.
 166        /// </summary>
 167        /// <value>The device id.</value>
 168        public string DeviceId { get; set; }
 169
 170        /// <summary>
 171        /// Gets or sets the application version.
 172        /// </summary>
 173        /// <value>The application version.</value>
 174        public string ApplicationVersion { get; set; }
 175
 176        /// <summary>
 177        /// Gets or sets the session controller.
 178        /// </summary>
 179        /// <value>The session controller.</value>
 180        [JsonIgnore]
 181        public IReadOnlyList<ISessionController> SessionControllers { get; set; }
 182
 183        /// <summary>
 184        /// Gets or sets the transcoding info.
 185        /// </summary>
 186        /// <value>The transcoding info.</value>
 187        public TranscodingInfo TranscodingInfo { get; set; }
 188
 189        /// <summary>
 190        /// Gets a value indicating whether this instance is active.
 191        /// </summary>
 192        /// <value><c>true</c> if this instance is active; otherwise, <c>false</c>.</value>
 193        public bool IsActive
 194        {
 195            get
 196            {
 15197                var controllers = SessionControllers;
 30198                foreach (var controller in controllers)
 199                {
 0200                    if (controller.IsSessionActive)
 201                    {
 0202                        return true;
 203                    }
 204                }
 205
 15206                if (controllers.Count > 0)
 207                {
 0208                    return false;
 209                }
 210
 15211                return true;
 0212            }
 213        }
 214
 215        /// <summary>
 216        /// Gets a value indicating whether the session supports media control.
 217        /// </summary>
 218        /// <value><c>true</c> if this session supports media control; otherwise, <c>false</c>.</value>
 219        public bool SupportsMediaControl
 220        {
 221            get
 222            {
 15223                if (Capabilities is null || !Capabilities.SupportsMediaControl)
 224                {
 15225                    return false;
 226                }
 227
 0228                var controllers = SessionControllers;
 0229                foreach (var controller in controllers)
 230                {
 0231                    if (controller.SupportsMediaControl)
 232                    {
 0233                        return true;
 234                    }
 235                }
 236
 0237                return false;
 0238            }
 239        }
 240
 241        /// <summary>
 242        /// Gets a value indicating whether the session supports remote control.
 243        /// </summary>
 244        /// <value><c>true</c> if this session supports remote control; otherwise, <c>false</c>.</value>
 245        public bool SupportsRemoteControl
 246        {
 247            get
 248            {
 15249                if (Capabilities is null || !Capabilities.SupportsMediaControl)
 250                {
 15251                    return false;
 252                }
 253
 0254                var controllers = SessionControllers;
 0255                foreach (var controller in controllers)
 256                {
 0257                    if (controller.SupportsMediaControl)
 258                    {
 0259                        return true;
 260                    }
 261                }
 262
 0263                return false;
 0264            }
 265        }
 266
 267        /// <summary>
 268        /// Gets or sets the now playing queue.
 269        /// </summary>
 270        /// <value>The now playing queue.</value>
 271        public IReadOnlyList<QueueItem> NowPlayingQueue { get; set; }
 272
 273        /// <summary>
 274        /// Gets or sets a value indicating whether the session has a custom device name.
 275        /// </summary>
 276        /// <value><c>true</c> if the session has a custom device name; otherwise, <c>false</c>.</value>
 277        public bool HasCustomDeviceName { get; set; }
 278
 279        /// <summary>
 280        /// Gets or sets the playlist item id.
 281        /// </summary>
 282        /// <value>The playlist item id.</value>
 283        public string PlaylistItemId { get; set; }
 284
 285        /// <summary>
 286        /// Gets or sets the server id.
 287        /// </summary>
 288        /// <value>The server id.</value>
 289        public string ServerId { get; set; }
 290
 291        /// <summary>
 292        /// Gets or sets the user primary image tag.
 293        /// </summary>
 294        /// <value>The user primary image tag.</value>
 295        public string UserPrimaryImageTag { get; set; }
 296
 297        /// <summary>
 298        /// Gets the supported commands.
 299        /// </summary>
 300        /// <value>The supported commands.</value>
 301        public IReadOnlyList<GeneralCommandType> SupportedCommands
 15302            => Capabilities is null ? [] : Capabilities.SupportedCommands;
 303
 304        /// <summary>
 305        /// Ensures a controller of type exists.
 306        /// </summary>
 307        /// <typeparam name="T">Class to register.</typeparam>
 308        /// <param name="factory">The factory.</param>
 309        /// <returns>Tuple{ISessionController, bool}.</returns>
 310        public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
 311        {
 0312            var controllers = SessionControllers.ToList();
 0313            foreach (var controller in controllers)
 314            {
 0315                if (controller is T)
 316                {
 0317                    return new Tuple<ISessionController, bool>(controller, false);
 318                }
 319            }
 320
 0321            var newController = factory(this);
 0322            _logger.LogDebug("Creating new {Factory}", newController.GetType().Name);
 0323            controllers.Add(newController);
 324
 0325            SessionControllers = [.. controllers];
 0326            return new Tuple<ISessionController, bool>(newController, true);
 0327        }
 328
 329        /// <summary>
 330        /// Adds a controller to the session.
 331        /// </summary>
 332        /// <param name="controller">The controller.</param>
 333        public void AddController(ISessionController controller)
 334        {
 0335            SessionControllers = [.. SessionControllers, controller];
 0336        }
 337
 338        /// <summary>
 339        /// Gets a value indicating whether the session contains a user.
 340        /// </summary>
 341        /// <param name="userId">The user id to check.</param>
 342        /// <returns><c>true</c> if this session contains the user; otherwise, <c>false</c>.</returns>
 343        public bool ContainsUser(Guid userId)
 344        {
 0345            if (UserId.Equals(userId))
 346            {
 0347                return true;
 348            }
 349
 0350            foreach (var additionalUser in AdditionalUsers)
 351            {
 0352                if (additionalUser.UserId.Equals(userId))
 353                {
 0354                    return true;
 355                }
 356            }
 357
 0358            return false;
 0359        }
 360
 361        /// <summary>
 362        /// Starts automatic progressing.
 363        /// </summary>
 364        /// <param name="progressInfo">The playback progress info.</param>
 365        /// <value>The supported commands.</value>
 366        public void StartAutomaticProgress(PlaybackProgressInfo progressInfo)
 367        {
 0368            if (_disposed)
 369            {
 0370                return;
 371            }
 372
 373            lock (_progressLock)
 374            {
 0375                _lastProgressInfo = progressInfo;
 376
 0377                if (_progressTimer is null)
 378                {
 0379                    _progressTimer = new Timer(OnProgressTimerCallback, null, 1000, 1000);
 380                }
 381                else
 382                {
 0383                    _progressTimer.Change(1000, 1000);
 384                }
 0385            }
 0386        }
 387
 388        private async void OnProgressTimerCallback(object state)
 389        {
 0390            if (_disposed)
 391            {
 0392                return;
 393            }
 394
 0395            var progressInfo = _lastProgressInfo;
 0396            if (progressInfo is null)
 397            {
 0398                return;
 399            }
 400
 0401            if (progressInfo.IsPaused)
 402            {
 0403                return;
 404            }
 405
 0406            var positionTicks = progressInfo.PositionTicks ?? 0;
 0407            if (positionTicks < 0)
 408            {
 0409                positionTicks = 0;
 410            }
 411
 0412            var newPositionTicks = positionTicks + ProgressIncrement;
 0413            var item = progressInfo.Item;
 0414            long? runtimeTicks = item?.RunTimeTicks;
 415
 416            // Don't report beyond the runtime
 0417            if (runtimeTicks.HasValue && newPositionTicks >= runtimeTicks.Value)
 418            {
 0419                return;
 420            }
 421
 0422            progressInfo.PositionTicks = newPositionTicks;
 423
 424            try
 425            {
 0426                await _sessionManager.OnPlaybackProgress(progressInfo, true).ConfigureAwait(false);
 0427            }
 0428            catch (Exception ex)
 429            {
 0430                _logger.LogError(ex, "Error reporting playback progress");
 0431            }
 0432        }
 433
 434        /// <summary>
 435        /// Stops automatic progressing.
 436        /// </summary>
 437        public void StopAutomaticProgress()
 15438        {
 439            lock (_progressLock)
 440            {
 15441                if (_progressTimer is not null)
 442                {
 0443                    _progressTimer.Dispose();
 0444                    _progressTimer = null;
 445                }
 446
 15447                _lastProgressInfo = null;
 15448            }
 15449        }
 450
 451        /// <summary>
 452        /// Disposes the instance async.
 453        /// </summary>
 454        /// <returns>ValueTask.</returns>
 455        public async ValueTask DisposeAsync()
 456        {
 15457            _disposed = true;
 458
 15459            StopAutomaticProgress();
 460
 15461            var controllers = SessionControllers.ToList();
 15462            SessionControllers = [];
 463
 30464            foreach (var controller in controllers)
 465            {
 0466                if (controller is IAsyncDisposable disposableAsync)
 467                {
 0468                    _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType()
 0469                    await disposableAsync.DisposeAsync().ConfigureAwait(false);
 470                }
 0471                else if (controller is IDisposable disposable)
 472                {
 0473                    _logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name)
 0474                    disposable.Dispose();
 475                }
 476            }
 15477        }
 478    }
 479}