< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.SessionController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/SessionController.cs
Line coverage
31%
Covered lines: 16
Uncovered lines: 35
Coverable lines: 51
Total lines: 518
Line coverage: 31.3%
Branch coverage
16%
Covered branches: 3
Total branches: 18
Branch coverage: 16.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetSessions(...)16.66%174.011821.62%
AddUserToSession(...)100%210%
RemoveUserFromSession(...)100%210%
GetAuthProviders()100%210%
GetPasswordResetProviders()100%210%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/SessionController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.ComponentModel.DataAnnotations;
 4using System.Linq;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Jellyfin.Api.Constants;
 8using Jellyfin.Api.Extensions;
 9using Jellyfin.Api.Helpers;
 10using Jellyfin.Api.ModelBinders;
 11using Jellyfin.Api.Models.SessionDtos;
 12using Jellyfin.Data.Enums;
 13using Jellyfin.Extensions;
 14using MediaBrowser.Common.Api;
 15using MediaBrowser.Controller.Devices;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Controller.Session;
 18using MediaBrowser.Model.Dto;
 19using MediaBrowser.Model.Entities;
 20using MediaBrowser.Model.Session;
 21using Microsoft.AspNetCore.Authorization;
 22using Microsoft.AspNetCore.Http;
 23using Microsoft.AspNetCore.Mvc;
 24
 25namespace Jellyfin.Api.Controllers;
 26
 27/// <summary>
 28/// The session controller.
 29/// </summary>
 30[Route("")]
 31public class SessionController : BaseJellyfinApiController
 32{
 33    private readonly ISessionManager _sessionManager;
 34    private readonly IUserManager _userManager;
 35    private readonly IDeviceManager _deviceManager;
 36
 37    /// <summary>
 38    /// Initializes a new instance of the <see cref="SessionController"/> class.
 39    /// </summary>
 40    /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param>
 41    /// <param name="userManager">Instance of <see cref="IUserManager"/> interface.</param>
 42    /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param>
 143    public SessionController(
 144        ISessionManager sessionManager,
 145        IUserManager userManager,
 146        IDeviceManager deviceManager)
 47    {
 148        _sessionManager = sessionManager;
 149        _userManager = userManager;
 150        _deviceManager = deviceManager;
 151    }
 52
 53    /// <summary>
 54    /// Gets a list of sessions.
 55    /// </summary>
 56    /// <param name="controllableByUserId">Filter by sessions that a given user is allowed to remote control.</param>
 57    /// <param name="deviceId">Filter by device Id.</param>
 58    /// <param name="activeWithinSeconds">Optional. Filter by sessions that were active in the last n seconds.</param>
 59    /// <response code="200">List of sessions returned.</response>
 60    /// <returns>An <see cref="IEnumerable{SessionInfo}"/> with the available sessions.</returns>
 61    [HttpGet("Sessions")]
 62    [Authorize]
 63    [ProducesResponseType(StatusCodes.Status200OK)]
 64    public ActionResult<IEnumerable<SessionInfo>> GetSessions(
 65        [FromQuery] Guid? controllableByUserId,
 66        [FromQuery] string? deviceId,
 67        [FromQuery] int? activeWithinSeconds)
 68    {
 169        var result = _sessionManager.Sessions;
 170        var isRequestingFromAdmin = User.IsInRole(UserRoles.Administrator);
 71
 172        if (!string.IsNullOrEmpty(deviceId))
 73        {
 074            result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
 75        }
 76
 177        if (!controllableByUserId.IsNullOrEmpty())
 78        {
 179            result = result.Where(i => i.SupportsRemoteControl);
 80
 181            var user = _userManager.GetUserById(controllableByUserId.Value);
 182            if (user is null)
 83            {
 184                return NotFound();
 85            }
 86
 087            if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
 88            {
 89                // User cannot control other user's sessions, validate user id.
 090                result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(RequestHelpers.GetUserId(User, controlla
 91            }
 92
 093            if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl))
 94            {
 095                result = result.Where(i => !i.UserId.IsEmpty());
 96            }
 97
 098            result = result.Where(i =>
 099            {
 0100                if (!string.IsNullOrWhiteSpace(i.DeviceId))
 0101                {
 0102                    if (!_deviceManager.CanAccessDevice(user, i.DeviceId))
 0103                    {
 0104                        return false;
 0105                    }
 0106                }
 0107
 0108                return true;
 0109            });
 110        }
 0111        else if (!isRequestingFromAdmin)
 112        {
 113            // Request isn't from administrator, limit to "own" sessions.
 0114            result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(User.GetUserId()));
 115        }
 116
 0117        if (activeWithinSeconds.HasValue && activeWithinSeconds.Value > 0)
 118        {
 0119            var minActiveDate = DateTime.UtcNow.AddSeconds(0 - activeWithinSeconds.Value);
 0120            result = result.Where(i => i.LastActivityDate >= minActiveDate);
 121        }
 122
 123        // Request isn't from administrator, don't report acceleration type.
 0124        if (!isRequestingFromAdmin)
 125        {
 0126            result = result.Select(r =>
 0127            {
 0128                r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none;
 0129                return r;
 0130            });
 131        }
 132
 0133        return Ok(result);
 134    }
 135
 136    /// <summary>
 137    /// Instructs a session to browse to an item or view.
 138    /// </summary>
 139    /// <param name="sessionId">The session Id.</param>
 140    /// <param name="itemType">The type of item to browse to.</param>
 141    /// <param name="itemId">The Id of the item.</param>
 142    /// <param name="itemName">The name of the item.</param>
 143    /// <response code="204">Instruction sent to session.</response>
 144    /// <returns>A <see cref="NoContentResult"/>.</returns>
 145    [HttpPost("Sessions/{sessionId}/Viewing")]
 146    [Authorize]
 147    [ProducesResponseType(StatusCodes.Status204NoContent)]
 148    public async Task<ActionResult> DisplayContent(
 149        [FromRoute, Required] string sessionId,
 150        [FromQuery, Required] BaseItemKind itemType,
 151        [FromQuery, Required] string itemId,
 152        [FromQuery, Required] string itemName)
 153    {
 154        var command = new BrowseRequest
 155        {
 156            ItemId = itemId,
 157            ItemName = itemName,
 158            ItemType = itemType
 159        };
 160
 161        await _sessionManager.SendBrowseCommand(
 162            await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false),
 163            sessionId,
 164            command,
 165            CancellationToken.None)
 166            .ConfigureAwait(false);
 167
 168        return NoContent();
 169    }
 170
 171    /// <summary>
 172    /// Instructs a session to play an item.
 173    /// </summary>
 174    /// <param name="sessionId">The session id.</param>
 175    /// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not 
 176    /// <param name="itemIds">The ids of the items to play, comma delimited.</param>
 177    /// <param name="startPositionTicks">The starting position of the first item.</param>
 178    /// <param name="mediaSourceId">Optional. The media source id.</param>
 179    /// <param name="audioStreamIndex">Optional. The index of the audio stream to play.</param>
 180    /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to play.</param>
 181    /// <param name="startIndex">Optional. The start index.</param>
 182    /// <response code="204">Instruction sent to session.</response>
 183    /// <returns>A <see cref="NoContentResult"/>.</returns>
 184    [HttpPost("Sessions/{sessionId}/Playing")]
 185    [Authorize]
 186    [ProducesResponseType(StatusCodes.Status204NoContent)]
 187    public async Task<ActionResult> Play(
 188        [FromRoute, Required] string sessionId,
 189        [FromQuery, Required] PlayCommand playCommand,
 190        [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
 191        [FromQuery] long? startPositionTicks,
 192        [FromQuery] string? mediaSourceId,
 193        [FromQuery] int? audioStreamIndex,
 194        [FromQuery] int? subtitleStreamIndex,
 195        [FromQuery] int? startIndex)
 196    {
 197        var playRequest = new PlayRequest
 198        {
 199            ItemIds = itemIds,
 200            StartPositionTicks = startPositionTicks,
 201            PlayCommand = playCommand,
 202            MediaSourceId = mediaSourceId,
 203            AudioStreamIndex = audioStreamIndex,
 204            SubtitleStreamIndex = subtitleStreamIndex,
 205            StartIndex = startIndex
 206        };
 207
 208        await _sessionManager.SendPlayCommand(
 209            await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false),
 210            sessionId,
 211            playRequest,
 212            CancellationToken.None)
 213            .ConfigureAwait(false);
 214
 215        return NoContent();
 216    }
 217
 218    /// <summary>
 219    /// Issues a playstate command to a client.
 220    /// </summary>
 221    /// <param name="sessionId">The session id.</param>
 222    /// <param name="command">The <see cref="PlaystateCommand"/>.</param>
 223    /// <param name="seekPositionTicks">The optional position ticks.</param>
 224    /// <param name="controllingUserId">The optional controlling user id.</param>
 225    /// <response code="204">Playstate command sent to session.</response>
 226    /// <returns>A <see cref="NoContentResult"/>.</returns>
 227    [HttpPost("Sessions/{sessionId}/Playing/{command}")]
 228    [Authorize]
 229    [ProducesResponseType(StatusCodes.Status204NoContent)]
 230    public async Task<ActionResult> SendPlaystateCommand(
 231        [FromRoute, Required] string sessionId,
 232        [FromRoute, Required] PlaystateCommand command,
 233        [FromQuery] long? seekPositionTicks,
 234        [FromQuery] string? controllingUserId)
 235    {
 236        await _sessionManager.SendPlaystateCommand(
 237            await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false),
 238            sessionId,
 239            new PlaystateRequest()
 240            {
 241                Command = command,
 242                ControllingUserId = controllingUserId,
 243                SeekPositionTicks = seekPositionTicks,
 244            },
 245            CancellationToken.None)
 246            .ConfigureAwait(false);
 247
 248        return NoContent();
 249    }
 250
 251    /// <summary>
 252    /// Issues a system command to a client.
 253    /// </summary>
 254    /// <param name="sessionId">The session id.</param>
 255    /// <param name="command">The command to send.</param>
 256    /// <response code="204">System command sent to session.</response>
 257    /// <returns>A <see cref="NoContentResult"/>.</returns>
 258    [HttpPost("Sessions/{sessionId}/System/{command}")]
 259    [Authorize]
 260    [ProducesResponseType(StatusCodes.Status204NoContent)]
 261    public async Task<ActionResult> SendSystemCommand(
 262        [FromRoute, Required] string sessionId,
 263        [FromRoute, Required] GeneralCommandType command)
 264    {
 265        var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(
 266        var generalCommand = new GeneralCommand
 267        {
 268            Name = command,
 269            ControllingUserId = currentSession.UserId
 270        };
 271
 272        await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None).C
 273
 274        return NoContent();
 275    }
 276
 277    /// <summary>
 278    /// Issues a general command to a client.
 279    /// </summary>
 280    /// <param name="sessionId">The session id.</param>
 281    /// <param name="command">The command to send.</param>
 282    /// <response code="204">General command sent to session.</response>
 283    /// <returns>A <see cref="NoContentResult"/>.</returns>
 284    [HttpPost("Sessions/{sessionId}/Command/{command}")]
 285    [Authorize]
 286    [ProducesResponseType(StatusCodes.Status204NoContent)]
 287    public async Task<ActionResult> SendGeneralCommand(
 288        [FromRoute, Required] string sessionId,
 289        [FromRoute, Required] GeneralCommandType command)
 290    {
 291        var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(
 292
 293        var generalCommand = new GeneralCommand
 294        {
 295            Name = command,
 296            ControllingUserId = currentSession.UserId
 297        };
 298
 299        await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None)
 300            .ConfigureAwait(false);
 301
 302        return NoContent();
 303    }
 304
 305    /// <summary>
 306    /// Issues a full general command to a client.
 307    /// </summary>
 308    /// <param name="sessionId">The session id.</param>
 309    /// <param name="command">The <see cref="GeneralCommand"/>.</param>
 310    /// <response code="204">Full general command sent to session.</response>
 311    /// <returns>A <see cref="NoContentResult"/>.</returns>
 312    [HttpPost("Sessions/{sessionId}/Command")]
 313    [Authorize]
 314    [ProducesResponseType(StatusCodes.Status204NoContent)]
 315    public async Task<ActionResult> SendFullGeneralCommand(
 316        [FromRoute, Required] string sessionId,
 317        [FromBody, Required] GeneralCommand command)
 318    {
 319        var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(
 320
 321        ArgumentNullException.ThrowIfNull(command);
 322
 323        command.ControllingUserId = currentSession.UserId;
 324
 325        await _sessionManager.SendGeneralCommand(
 326            currentSession.Id,
 327            sessionId,
 328            command,
 329            CancellationToken.None)
 330            .ConfigureAwait(false);
 331
 332        return NoContent();
 333    }
 334
 335    /// <summary>
 336    /// Issues a command to a client to display a message to the user.
 337    /// </summary>
 338    /// <param name="sessionId">The session id.</param>
 339    /// <param name="command">The <see cref="MessageCommand" /> object containing Header, Message Text, and TimeoutMs.</
 340    /// <response code="204">Message sent.</response>
 341    /// <returns>A <see cref="NoContentResult"/>.</returns>
 342    [HttpPost("Sessions/{sessionId}/Message")]
 343    [Authorize]
 344    [ProducesResponseType(StatusCodes.Status204NoContent)]
 345    public async Task<ActionResult> SendMessageCommand(
 346        [FromRoute, Required] string sessionId,
 347        [FromBody, Required] MessageCommand command)
 348    {
 349        if (string.IsNullOrWhiteSpace(command.Header))
 350        {
 351            command.Header = "Message from Server";
 352        }
 353
 354        await _sessionManager.SendMessageCommand(
 355            await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false),
 356            sessionId,
 357            command,
 358            CancellationToken.None)
 359            .ConfigureAwait(false);
 360
 361        return NoContent();
 362    }
 363
 364    /// <summary>
 365    /// Adds an additional user to a session.
 366    /// </summary>
 367    /// <param name="sessionId">The session id.</param>
 368    /// <param name="userId">The user id.</param>
 369    /// <response code="204">User added to session.</response>
 370    /// <returns>A <see cref="NoContentResult"/>.</returns>
 371    [HttpPost("Sessions/{sessionId}/User/{userId}")]
 372    [Authorize]
 373    [ProducesResponseType(StatusCodes.Status204NoContent)]
 374    public ActionResult AddUserToSession(
 375        [FromRoute, Required] string sessionId,
 376        [FromRoute, Required] Guid userId)
 377    {
 0378        _sessionManager.AddAdditionalUser(sessionId, userId);
 0379        return NoContent();
 380    }
 381
 382    /// <summary>
 383    /// Removes an additional user from a session.
 384    /// </summary>
 385    /// <param name="sessionId">The session id.</param>
 386    /// <param name="userId">The user id.</param>
 387    /// <response code="204">User removed from session.</response>
 388    /// <returns>A <see cref="NoContentResult"/>.</returns>
 389    [HttpDelete("Sessions/{sessionId}/User/{userId}")]
 390    [Authorize]
 391    [ProducesResponseType(StatusCodes.Status204NoContent)]
 392    public ActionResult RemoveUserFromSession(
 393        [FromRoute, Required] string sessionId,
 394        [FromRoute, Required] Guid userId)
 395    {
 0396        _sessionManager.RemoveAdditionalUser(sessionId, userId);
 0397        return NoContent();
 398    }
 399
 400    /// <summary>
 401    /// Updates capabilities for a device.
 402    /// </summary>
 403    /// <param name="id">The session id.</param>
 404    /// <param name="playableMediaTypes">A list of playable media types, comma delimited. Audio, Video, Book, Photo.</pa
 405    /// <param name="supportedCommands">A list of supported remote control commands, comma delimited.</param>
 406    /// <param name="supportsMediaControl">Determines whether media can be played remotely..</param>
 407    /// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param>
 408    /// <response code="204">Capabilities posted.</response>
 409    /// <returns>A <see cref="NoContentResult"/>.</returns>
 410    [HttpPost("Sessions/Capabilities")]
 411    [Authorize]
 412    [ProducesResponseType(StatusCodes.Status204NoContent)]
 413    public async Task<ActionResult> PostCapabilities(
 414        [FromQuery] string? id,
 415        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes,
 416        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands,
 417        [FromQuery] bool supportsMediaControl = false,
 418        [FromQuery] bool supportsPersistentIdentifier = true)
 419    {
 420        if (string.IsNullOrWhiteSpace(id))
 421        {
 422            id = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
 423        }
 424
 425        _sessionManager.ReportCapabilities(id, new ClientCapabilities
 426        {
 427            PlayableMediaTypes = playableMediaTypes,
 428            SupportedCommands = supportedCommands,
 429            SupportsMediaControl = supportsMediaControl,
 430            SupportsPersistentIdentifier = supportsPersistentIdentifier
 431        });
 432        return NoContent();
 433    }
 434
 435    /// <summary>
 436    /// Updates capabilities for a device.
 437    /// </summary>
 438    /// <param name="id">The session id.</param>
 439    /// <param name="capabilities">The <see cref="ClientCapabilities"/>.</param>
 440    /// <response code="204">Capabilities updated.</response>
 441    /// <returns>A <see cref="NoContentResult"/>.</returns>
 442    [HttpPost("Sessions/Capabilities/Full")]
 443    [Authorize]
 444    [ProducesResponseType(StatusCodes.Status204NoContent)]
 445    public async Task<ActionResult> PostFullCapabilities(
 446        [FromQuery] string? id,
 447        [FromBody, Required] ClientCapabilitiesDto capabilities)
 448    {
 449        if (string.IsNullOrWhiteSpace(id))
 450        {
 451            id = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
 452        }
 453
 454        _sessionManager.ReportCapabilities(id, capabilities.ToClientCapabilities());
 455
 456        return NoContent();
 457    }
 458
 459    /// <summary>
 460    /// Reports that a session is viewing an item.
 461    /// </summary>
 462    /// <param name="sessionId">The session id.</param>
 463    /// <param name="itemId">The item id.</param>
 464    /// <response code="204">Session reported to server.</response>
 465    /// <returns>A <see cref="NoContentResult"/>.</returns>
 466    [HttpPost("Sessions/Viewing")]
 467    [Authorize]
 468    [ProducesResponseType(StatusCodes.Status204NoContent)]
 469    public async Task<ActionResult> ReportViewing(
 470        [FromQuery] string? sessionId,
 471        [FromQuery, Required] string? itemId)
 472    {
 473        string session = sessionId ?? await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).Conf
 474
 475        _sessionManager.ReportNowViewingItem(session, itemId);
 476        return NoContent();
 477    }
 478
 479    /// <summary>
 480    /// Reports that a session has ended.
 481    /// </summary>
 482    /// <response code="204">Session end reported to server.</response>
 483    /// <returns>A <see cref="NoContentResult"/>.</returns>
 484    [HttpPost("Sessions/Logout")]
 485    [Authorize]
 486    [ProducesResponseType(StatusCodes.Status204NoContent)]
 487    public async Task<ActionResult> ReportSessionEnded()
 488    {
 489        await _sessionManager.Logout(User.GetToken()).ConfigureAwait(false);
 490        return NoContent();
 491    }
 492
 493    /// <summary>
 494    /// Get all auth providers.
 495    /// </summary>
 496    /// <response code="200">Auth providers retrieved.</response>
 497    /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the auth providers.</returns>
 498    [HttpGet("Auth/Providers")]
 499    [Authorize(Policy = Policies.RequiresElevation)]
 500    [ProducesResponseType(StatusCodes.Status200OK)]
 501    public ActionResult<IEnumerable<NameIdPair>> GetAuthProviders()
 502    {
 0503        return _userManager.GetAuthenticationProviders();
 504    }
 505
 506    /// <summary>
 507    /// Get all password reset providers.
 508    /// </summary>
 509    /// <response code="200">Password reset providers retrieved.</response>
 510    /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the password reset providers.</returns>
 511    [HttpGet("Auth/PasswordResetProviders")]
 512    [ProducesResponseType(StatusCodes.Status200OK)]
 513    [Authorize(Policy = Policies.RequiresElevation)]
 514    public ActionResult<IEnumerable<NameIdPair>> GetPasswordResetProviders()
 515    {
 0516        return _userManager.GetPasswordResetProviders();
 517    }
 518}