< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.MediaInfoController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/MediaInfoController.cs
Line coverage
14%
Covered lines: 20
Uncovered lines: 117
Coverable lines: 137
Total lines: 345
Line coverage: 14.5%
Branch coverage
0%
Covered branches: 0
Total branches: 158
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/6/2026 - 12:15:23 AM Line coverage: 14.7% (20/136) Branch coverage: 0% (0/158) Total lines: 3448/3/2026 - 12:16:46 AM Line coverage: 14.5% (20/137) Branch coverage: 0% (0/158) Total lines: 345 5/6/2026 - 12:15:23 AM Line coverage: 14.7% (20/136) Branch coverage: 0% (0/158) Total lines: 3448/3/2026 - 12:16:46 AM Line coverage: 14.5% (20/137) Branch coverage: 0% (0/158) Total lines: 345

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetPlaybackInfo()0%2040%
GetPostedPlaybackInfo()0%9702980%
OpenLiveStream()0%3192560%
CloseLiveStream()100%210%
GetBitrateTestBytes(...)100%11100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Buffers;
 3using System.ComponentModel.DataAnnotations;
 4using System.Linq;
 5using System.Net.Mime;
 6using System.Threading.Tasks;
 7using Jellyfin.Api.Attributes;
 8using Jellyfin.Api.Extensions;
 9using Jellyfin.Api.Helpers;
 10using Jellyfin.Api.Models.MediaInfoDtos;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Common.Extensions;
 13using MediaBrowser.Controller.Devices;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Model.MediaInfo;
 17using Microsoft.AspNetCore.Authorization;
 18using Microsoft.AspNetCore.Http;
 19using Microsoft.AspNetCore.Mvc;
 20using Microsoft.AspNetCore.Mvc.ModelBinding;
 21using Microsoft.Extensions.Logging;
 22
 23namespace Jellyfin.Api.Controllers;
 24
 25/// <summary>
 26/// The media info controller.
 27/// </summary>
 28[Route("")]
 29[Authorize]
 30public class MediaInfoController : BaseJellyfinApiController
 31{
 32    private readonly IMediaSourceManager _mediaSourceManager;
 33    private readonly IDeviceManager _deviceManager;
 34    private readonly ILibraryManager _libraryManager;
 35    private readonly ILogger<MediaInfoController> _logger;
 36    private readonly MediaInfoHelper _mediaInfoHelper;
 37    private readonly IUserManager _userManager;
 38
 39    /// <summary>
 40    /// Initializes a new instance of the <see cref="MediaInfoController"/> class.
 41    /// </summary>
 42    /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 43    /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
 44    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 45    /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
 46    /// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param>
 47    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface..</param>
 548    public MediaInfoController(
 549        IMediaSourceManager mediaSourceManager,
 550        IDeviceManager deviceManager,
 551        ILibraryManager libraryManager,
 552        ILogger<MediaInfoController> logger,
 553        MediaInfoHelper mediaInfoHelper,
 554        IUserManager userManager)
 55    {
 556        _mediaSourceManager = mediaSourceManager;
 557        _deviceManager = deviceManager;
 558        _libraryManager = libraryManager;
 559        _logger = logger;
 560        _mediaInfoHelper = mediaInfoHelper;
 561        _userManager = userManager;
 562    }
 63
 64    /// <summary>
 65    /// Gets live playback media info for an item.
 66    /// </summary>
 67    /// <param name="itemId">The item id.</param>
 68    /// <param name="userId">The user id.</param>
 69    /// <response code="200">Playback info returned.</response>
 70    /// <response code="404">Item not found.</response>
 71    /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</re
 72    [HttpGet("Items/{itemId}/PlaybackInfo")]
 73    [ProducesResponseType(StatusCodes.Status200OK)]
 74    [ProducesResponseType(StatusCodes.Status404NotFound)]
 75    public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery]
 76    {
 077        userId = RequestHelpers.GetUserId(User, userId);
 078        var user = userId.IsNullOrEmpty()
 079            ? null
 080            : _userManager.GetUserById(userId.Value);
 081        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 082        if (item is null)
 83        {
 084            return NotFound();
 85        }
 86
 087        return await _mediaInfoHelper.GetPlaybackInfo(item, user, Request).ConfigureAwait(false);
 088    }
 89
 90    /// <summary>
 91    /// Gets live playback media info for an item.
 92    /// </summary>
 93    /// <remarks>
 94    /// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.
 95    /// Query parameters are obsolete.
 96    /// </remarks>
 97    /// <param name="itemId">The item id.</param>
 98    /// <param name="userId">The user id.</param>
 99    /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
 100    /// <param name="startTimeTicks">The start time in ticks.</param>
 101    /// <param name="audioStreamIndex">The audio stream index.</param>
 102    /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
 103    /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
 104    /// <param name="mediaSourceId">The media source id.</param>
 105    /// <param name="liveStreamId">The livestream id.</param>
 106    /// <param name="autoOpenLiveStream">Whether to auto open the livestream.</param>
 107    /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
 108    /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
 109    /// <param name="enableTranscoding">Whether to enable transcoding. Default: true.</param>
 110    /// <param name="allowVideoStreamCopy">Whether to allow to copy the video stream. Default: true.</param>
 111    /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
 112    /// <param name="playbackInfoDto">The playback info.</param>
 113    /// <response code="200">Playback info returned.</response>
 114    /// <response code="404">Item not found.</response>
 115    /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
 116    [HttpPost("Items/{itemId}/PlaybackInfo")]
 117    [ProducesResponseType(StatusCodes.Status200OK)]
 118    [ProducesResponseType(StatusCodes.Status404NotFound)]
 119    public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
 120        [FromRoute, Required] Guid itemId,
 121        [FromQuery, ParameterObsolete] Guid? userId,
 122        [FromQuery, ParameterObsolete] int? maxStreamingBitrate,
 123        [FromQuery, ParameterObsolete] long? startTimeTicks,
 124        [FromQuery, ParameterObsolete] int? audioStreamIndex,
 125        [FromQuery, ParameterObsolete] int? subtitleStreamIndex,
 126        [FromQuery, ParameterObsolete] int? maxAudioChannels,
 127        [FromQuery, ParameterObsolete] string? mediaSourceId,
 128        [FromQuery, ParameterObsolete] string? liveStreamId,
 129        [FromQuery, ParameterObsolete] bool? autoOpenLiveStream,
 130        [FromQuery, ParameterObsolete] bool? enableDirectPlay,
 131        [FromQuery, ParameterObsolete] bool? enableDirectStream,
 132        [FromQuery, ParameterObsolete] bool? enableTranscoding,
 133        [FromQuery, ParameterObsolete] bool? allowVideoStreamCopy,
 134        [FromQuery, ParameterObsolete] bool? allowAudioStreamCopy,
 135        [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto)
 136    {
 0137        var profile = playbackInfoDto?.DeviceProfile;
 0138        _logger.LogDebug("GetPostedPlaybackInfo profile: {@Profile}", profile);
 139
 0140        if (profile is null)
 141        {
 0142            var caps = _deviceManager.GetCapabilities(User.GetDeviceId());
 0143            if (caps is not null)
 144            {
 0145                profile = caps.DeviceProfile;
 146            }
 147        }
 148
 149        // Copy params from posted body
 150        // TODO clean up when breaking API compatibility.
 0151        userId ??= playbackInfoDto?.UserId;
 0152        userId = RequestHelpers.GetUserId(User, userId);
 0153        maxStreamingBitrate ??= playbackInfoDto?.MaxStreamingBitrate;
 0154        startTimeTicks ??= playbackInfoDto?.StartTimeTicks;
 0155        audioStreamIndex ??= playbackInfoDto?.AudioStreamIndex;
 0156        subtitleStreamIndex ??= playbackInfoDto?.SubtitleStreamIndex;
 0157        maxAudioChannels ??= playbackInfoDto?.MaxAudioChannels;
 0158        mediaSourceId ??= playbackInfoDto?.MediaSourceId;
 0159        liveStreamId ??= playbackInfoDto?.LiveStreamId;
 0160        autoOpenLiveStream ??= playbackInfoDto?.AutoOpenLiveStream ?? false;
 0161        enableDirectPlay ??= playbackInfoDto?.EnableDirectPlay ?? true;
 0162        enableDirectStream ??= playbackInfoDto?.EnableDirectStream ?? true;
 0163        enableTranscoding ??= playbackInfoDto?.EnableTranscoding ?? true;
 0164        allowVideoStreamCopy ??= playbackInfoDto?.AllowVideoStreamCopy ?? true;
 0165        allowAudioStreamCopy ??= playbackInfoDto?.AllowAudioStreamCopy ?? true;
 166
 0167        userId = RequestHelpers.GetUserId(User, userId);
 0168        var user = userId.IsNullOrEmpty()
 0169            ? null
 0170            : _userManager.GetUserById(userId.Value);
 0171        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 0172        if (item is null)
 173        {
 0174            return NotFound();
 175        }
 176
 0177        var info = await _mediaInfoHelper.GetPlaybackInfo(
 0178                item,
 0179                user,
 0180                Request,
 0181                mediaSourceId,
 0182                liveStreamId)
 0183            .ConfigureAwait(false);
 184
 0185        if (info.ErrorCode is not null)
 186        {
 0187            return info;
 188        }
 189
 0190        if (profile is not null)
 191        {
 192            // set device specific data
 0193            foreach (var mediaSource in info.MediaSources)
 194            {
 0195                _mediaInfoHelper.SetDeviceSpecificData(
 0196                    item,
 0197                    mediaSource,
 0198                    profile,
 0199                    User,
 0200                    maxStreamingBitrate ?? profile.MaxStreamingBitrate,
 0201                    startTimeTicks ?? 0,
 0202                    mediaSourceId ?? string.Empty,
 0203                    audioStreamIndex,
 0204                    subtitleStreamIndex,
 0205                    maxAudioChannels,
 0206                    info.PlaySessionId!,
 0207                    userId ?? Guid.Empty,
 0208                    enableDirectPlay.Value,
 0209                    enableDirectStream.Value,
 0210                    enableTranscoding.Value,
 0211                    allowVideoStreamCopy.Value,
 0212                    allowAudioStreamCopy.Value,
 0213                    playbackInfoDto?.AlwaysBurnInSubtitleWhenTranscoding ?? false,
 0214                    Request.HttpContext.GetNormalizedRemoteIP());
 215            }
 216
 0217            _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate, item.Id);
 218        }
 219
 0220        if (autoOpenLiveStream.Value)
 221        {
 0222            var mediaSource = string.IsNullOrWhiteSpace(mediaSourceId) ? info.MediaSources[0] : info.MediaSources.FirstO
 223
 0224            if (mediaSource is not null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStre
 225            {
 0226                var openStreamResult = await _mediaInfoHelper.OpenMediaSource(
 0227                    HttpContext,
 0228                    new LiveStreamRequest
 0229                    {
 0230                        AudioStreamIndex = audioStreamIndex,
 0231                        DeviceProfile = playbackInfoDto?.DeviceProfile,
 0232                        EnableDirectPlay = enableDirectPlay.Value,
 0233                        EnableDirectStream = enableDirectStream.Value,
 0234                        ItemId = itemId,
 0235                        MaxAudioChannels = maxAudioChannels,
 0236                        MaxStreamingBitrate = maxStreamingBitrate,
 0237                        PlaySessionId = info.PlaySessionId,
 0238                        StartTimeTicks = startTimeTicks,
 0239                        SubtitleStreamIndex = subtitleStreamIndex,
 0240                        UserId = userId ?? Guid.Empty,
 0241                        OpenToken = mediaSource.OpenToken,
 0242                        AlwaysBurnInSubtitleWhenTranscoding = playbackInfoDto?.AlwaysBurnInSubtitleWhenTranscoding ?? fa
 0243                    }).ConfigureAwait(false);
 244
 0245                info.MediaSources = new[] { openStreamResult.MediaSource };
 246            }
 247        }
 248
 0249        return info;
 0250    }
 251
 252    /// <summary>
 253    /// Opens a media source.
 254    /// </summary>
 255    /// <param name="openToken">The open token.</param>
 256    /// <param name="userId">The user id.</param>
 257    /// <param name="playSessionId">The play session id.</param>
 258    /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
 259    /// <param name="startTimeTicks">The start time in ticks.</param>
 260    /// <param name="audioStreamIndex">The audio stream index.</param>
 261    /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
 262    /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
 263    /// <param name="itemId">The item id.</param>
 264    /// <param name="openLiveStreamDto">The open live stream dto.</param>
 265    /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
 266    /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
 267    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Always burn-in subtitle when transcoding.</param>
 268    /// <response code="200">Media source opened.</response>
 269    /// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
 270    [HttpPost("LiveStreams/Open")]
 271    [ProducesResponseType(StatusCodes.Status200OK)]
 272    public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
 273        [FromQuery] string? openToken,
 274        [FromQuery] Guid? userId,
 275        [FromQuery] string? playSessionId,
 276        [FromQuery] int? maxStreamingBitrate,
 277        [FromQuery] long? startTimeTicks,
 278        [FromQuery] int? audioStreamIndex,
 279        [FromQuery] int? subtitleStreamIndex,
 280        [FromQuery] int? maxAudioChannels,
 281        [FromQuery] Guid? itemId,
 282        [FromBody] OpenLiveStreamDto? openLiveStreamDto,
 283        [FromQuery] bool? enableDirectPlay,
 284        [FromQuery] bool? enableDirectStream,
 285        [FromQuery] bool? alwaysBurnInSubtitleWhenTranscoding)
 286    {
 0287        userId ??= openLiveStreamDto?.UserId;
 0288        userId = RequestHelpers.GetUserId(User, userId);
 0289        var request = new LiveStreamRequest
 0290        {
 0291            OpenToken = openToken ?? openLiveStreamDto?.OpenToken,
 0292            UserId = userId.Value,
 0293            PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId,
 0294            MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate,
 0295            StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks,
 0296            AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex,
 0297            SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex,
 0298            MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels,
 0299            ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty,
 0300            DeviceProfile = openLiveStreamDto?.DeviceProfile,
 0301            EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true,
 0302            EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true,
 0303            DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http },
 0304            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding ?? openLiveStreamDto?.AlwaysBurnIn
 0305        };
 0306        return await _mediaInfoHelper.OpenMediaSource(HttpContext, request).ConfigureAwait(false);
 0307    }
 308
 309    /// <summary>
 310    /// Closes a media source.
 311    /// </summary>
 312    /// <param name="liveStreamId">The livestream id.</param>
 313    /// <response code="204">Livestream closed.</response>
 314    /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 315    [HttpPost("LiveStreams/Close")]
 316    [ProducesResponseType(StatusCodes.Status204NoContent)]
 317    public async Task<ActionResult> CloseLiveStream([FromQuery, Required] string liveStreamId)
 318    {
 0319        await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
 0320        return NoContent();
 0321    }
 322
 323    /// <summary>
 324    /// Tests the network with a request with the size of the bitrate.
 325    /// </summary>
 326    /// <param name="size">The bitrate. Defaults to 102400.</param>
 327    /// <response code="200">Test buffer returned.</response>
 328    /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
 329    [HttpGet("Playback/BitrateTest")]
 330    [ProducesResponseType(StatusCodes.Status200OK)]
 331    [ProducesFile(MediaTypeNames.Application.Octet)]
 332    public ActionResult GetBitrateTestBytes([FromQuery][Range(1, 100_000_000, ErrorMessage = "The requested size must be
 333    {
 2334        byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
 335        try
 336        {
 2337            Random.Shared.NextBytes(buffer);
 2338            return File(buffer, MediaTypeNames.Application.Octet);
 339        }
 340        finally
 341        {
 2342            ArrayPool<byte>.Shared.Return(buffer);
 2343        }
 2344    }
 345}