< 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: 116
Coverable lines: 136
Total lines: 344
Line coverage: 14.7%
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 1/23/2026 - 12:11:06 AM Line coverage: 100% (20/20) Total lines: 3444/19/2026 - 12:14:27 AM Line coverage: 14.7% (20/136) Branch coverage: 0% (0/158) Total lines: 344 4/19/2026 - 12:14:27 AM Line coverage: 14.7% (20/136) Branch coverage: 0% (0/158) Total lines: 344

Coverage delta

Coverage delta 86 -86

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).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                mediaSourceId,
 0181                liveStreamId)
 0182            .ConfigureAwait(false);
 183
 0184        if (info.ErrorCode is not null)
 185        {
 0186            return info;
 187        }
 188
 0189        if (profile is not null)
 190        {
 191            // set device specific data
 0192            foreach (var mediaSource in info.MediaSources)
 193            {
 0194                _mediaInfoHelper.SetDeviceSpecificData(
 0195                    item,
 0196                    mediaSource,
 0197                    profile,
 0198                    User,
 0199                    maxStreamingBitrate ?? profile.MaxStreamingBitrate,
 0200                    startTimeTicks ?? 0,
 0201                    mediaSourceId ?? string.Empty,
 0202                    audioStreamIndex,
 0203                    subtitleStreamIndex,
 0204                    maxAudioChannels,
 0205                    info.PlaySessionId!,
 0206                    userId ?? Guid.Empty,
 0207                    enableDirectPlay.Value,
 0208                    enableDirectStream.Value,
 0209                    enableTranscoding.Value,
 0210                    allowVideoStreamCopy.Value,
 0211                    allowAudioStreamCopy.Value,
 0212                    playbackInfoDto?.AlwaysBurnInSubtitleWhenTranscoding ?? false,
 0213                    Request.HttpContext.GetNormalizedRemoteIP());
 214            }
 215
 0216            _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
 217        }
 218
 0219        if (autoOpenLiveStream.Value)
 220        {
 0221            var mediaSource = string.IsNullOrWhiteSpace(mediaSourceId) ? info.MediaSources[0] : info.MediaSources.FirstO
 222
 0223            if (mediaSource is not null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStre
 224            {
 0225                var openStreamResult = await _mediaInfoHelper.OpenMediaSource(
 0226                    HttpContext,
 0227                    new LiveStreamRequest
 0228                    {
 0229                        AudioStreamIndex = audioStreamIndex,
 0230                        DeviceProfile = playbackInfoDto?.DeviceProfile,
 0231                        EnableDirectPlay = enableDirectPlay.Value,
 0232                        EnableDirectStream = enableDirectStream.Value,
 0233                        ItemId = itemId,
 0234                        MaxAudioChannels = maxAudioChannels,
 0235                        MaxStreamingBitrate = maxStreamingBitrate,
 0236                        PlaySessionId = info.PlaySessionId,
 0237                        StartTimeTicks = startTimeTicks,
 0238                        SubtitleStreamIndex = subtitleStreamIndex,
 0239                        UserId = userId ?? Guid.Empty,
 0240                        OpenToken = mediaSource.OpenToken,
 0241                        AlwaysBurnInSubtitleWhenTranscoding = playbackInfoDto?.AlwaysBurnInSubtitleWhenTranscoding ?? fa
 0242                    }).ConfigureAwait(false);
 243
 0244                info.MediaSources = new[] { openStreamResult.MediaSource };
 245            }
 246        }
 247
 0248        return info;
 0249    }
 250
 251    /// <summary>
 252    /// Opens a media source.
 253    /// </summary>
 254    /// <param name="openToken">The open token.</param>
 255    /// <param name="userId">The user id.</param>
 256    /// <param name="playSessionId">The play session id.</param>
 257    /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
 258    /// <param name="startTimeTicks">The start time in ticks.</param>
 259    /// <param name="audioStreamIndex">The audio stream index.</param>
 260    /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
 261    /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
 262    /// <param name="itemId">The item id.</param>
 263    /// <param name="openLiveStreamDto">The open live stream dto.</param>
 264    /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
 265    /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
 266    /// <param name="alwaysBurnInSubtitleWhenTranscoding">Always burn-in subtitle when transcoding.</param>
 267    /// <response code="200">Media source opened.</response>
 268    /// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
 269    [HttpPost("LiveStreams/Open")]
 270    [ProducesResponseType(StatusCodes.Status200OK)]
 271    public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
 272        [FromQuery] string? openToken,
 273        [FromQuery] Guid? userId,
 274        [FromQuery] string? playSessionId,
 275        [FromQuery] int? maxStreamingBitrate,
 276        [FromQuery] long? startTimeTicks,
 277        [FromQuery] int? audioStreamIndex,
 278        [FromQuery] int? subtitleStreamIndex,
 279        [FromQuery] int? maxAudioChannels,
 280        [FromQuery] Guid? itemId,
 281        [FromBody] OpenLiveStreamDto? openLiveStreamDto,
 282        [FromQuery] bool? enableDirectPlay,
 283        [FromQuery] bool? enableDirectStream,
 284        [FromQuery] bool? alwaysBurnInSubtitleWhenTranscoding)
 285    {
 0286        userId ??= openLiveStreamDto?.UserId;
 0287        userId = RequestHelpers.GetUserId(User, userId);
 0288        var request = new LiveStreamRequest
 0289        {
 0290            OpenToken = openToken ?? openLiveStreamDto?.OpenToken,
 0291            UserId = userId.Value,
 0292            PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId,
 0293            MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate,
 0294            StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks,
 0295            AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex,
 0296            SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex,
 0297            MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels,
 0298            ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty,
 0299            DeviceProfile = openLiveStreamDto?.DeviceProfile,
 0300            EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true,
 0301            EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true,
 0302            DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http },
 0303            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding ?? openLiveStreamDto?.AlwaysBurnIn
 0304        };
 0305        return await _mediaInfoHelper.OpenMediaSource(HttpContext, request).ConfigureAwait(false);
 0306    }
 307
 308    /// <summary>
 309    /// Closes a media source.
 310    /// </summary>
 311    /// <param name="liveStreamId">The livestream id.</param>
 312    /// <response code="204">Livestream closed.</response>
 313    /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 314    [HttpPost("LiveStreams/Close")]
 315    [ProducesResponseType(StatusCodes.Status204NoContent)]
 316    public async Task<ActionResult> CloseLiveStream([FromQuery, Required] string liveStreamId)
 317    {
 0318        await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
 0319        return NoContent();
 0320    }
 321
 322    /// <summary>
 323    /// Tests the network with a request with the size of the bitrate.
 324    /// </summary>
 325    /// <param name="size">The bitrate. Defaults to 102400.</param>
 326    /// <response code="200">Test buffer returned.</response>
 327    /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
 328    [HttpGet("Playback/BitrateTest")]
 329    [ProducesResponseType(StatusCodes.Status200OK)]
 330    [ProducesFile(MediaTypeNames.Application.Octet)]
 331    public ActionResult GetBitrateTestBytes([FromQuery][Range(1, 100_000_000, ErrorMessage = "The requested size must be
 332    {
 2333        byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
 334        try
 335        {
 2336            Random.Shared.NextBytes(buffer);
 2337            return File(buffer, MediaTypeNames.Application.Octet);
 338        }
 339        finally
 340        {
 2341            ArrayPool<byte>.Shared.Return(buffer);
 2342        }
 2343    }
 344}