< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.MediaInfoController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/MediaInfoController.cs
Line coverage
100%
Covered lines: 20
Uncovered lines: 0
Coverable lines: 20
Total lines: 344
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
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%
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    {
 77        userId = RequestHelpers.GetUserId(User, userId);
 78        var user = userId.IsNullOrEmpty()
 79            ? null
 80            : _userManager.GetUserById(userId.Value);
 81        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 82        if (item is null)
 83        {
 84            return NotFound();
 85        }
 86
 87        return await _mediaInfoHelper.GetPlaybackInfo(item, user).ConfigureAwait(false);
 88    }
 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    {
 137        var profile = playbackInfoDto?.DeviceProfile;
 138        _logger.LogDebug("GetPostedPlaybackInfo profile: {@Profile}", profile);
 139
 140        if (profile is null)
 141        {
 142            var caps = _deviceManager.GetCapabilities(User.GetDeviceId());
 143            if (caps is not null)
 144            {
 145                profile = caps.DeviceProfile;
 146            }
 147        }
 148
 149        // Copy params from posted body
 150        // TODO clean up when breaking API compatibility.
 151        userId ??= playbackInfoDto?.UserId;
 152        userId = RequestHelpers.GetUserId(User, userId);
 153        maxStreamingBitrate ??= playbackInfoDto?.MaxStreamingBitrate;
 154        startTimeTicks ??= playbackInfoDto?.StartTimeTicks;
 155        audioStreamIndex ??= playbackInfoDto?.AudioStreamIndex;
 156        subtitleStreamIndex ??= playbackInfoDto?.SubtitleStreamIndex;
 157        maxAudioChannels ??= playbackInfoDto?.MaxAudioChannels;
 158        mediaSourceId ??= playbackInfoDto?.MediaSourceId;
 159        liveStreamId ??= playbackInfoDto?.LiveStreamId;
 160        autoOpenLiveStream ??= playbackInfoDto?.AutoOpenLiveStream ?? false;
 161        enableDirectPlay ??= playbackInfoDto?.EnableDirectPlay ?? true;
 162        enableDirectStream ??= playbackInfoDto?.EnableDirectStream ?? true;
 163        enableTranscoding ??= playbackInfoDto?.EnableTranscoding ?? true;
 164        allowVideoStreamCopy ??= playbackInfoDto?.AllowVideoStreamCopy ?? true;
 165        allowAudioStreamCopy ??= playbackInfoDto?.AllowAudioStreamCopy ?? true;
 166
 167        userId = RequestHelpers.GetUserId(User, userId);
 168        var user = userId.IsNullOrEmpty()
 169            ? null
 170            : _userManager.GetUserById(userId.Value);
 171        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 172        if (item is null)
 173        {
 174            return NotFound();
 175        }
 176
 177        var info = await _mediaInfoHelper.GetPlaybackInfo(
 178                item,
 179                user,
 180                mediaSourceId,
 181                liveStreamId)
 182            .ConfigureAwait(false);
 183
 184        if (info.ErrorCode is not null)
 185        {
 186            return info;
 187        }
 188
 189        if (profile is not null)
 190        {
 191            // set device specific data
 192            foreach (var mediaSource in info.MediaSources)
 193            {
 194                _mediaInfoHelper.SetDeviceSpecificData(
 195                    item,
 196                    mediaSource,
 197                    profile,
 198                    User,
 199                    maxStreamingBitrate ?? profile.MaxStreamingBitrate,
 200                    startTimeTicks ?? 0,
 201                    mediaSourceId ?? string.Empty,
 202                    audioStreamIndex,
 203                    subtitleStreamIndex,
 204                    maxAudioChannels,
 205                    info.PlaySessionId!,
 206                    userId ?? Guid.Empty,
 207                    enableDirectPlay.Value,
 208                    enableDirectStream.Value,
 209                    enableTranscoding.Value,
 210                    allowVideoStreamCopy.Value,
 211                    allowAudioStreamCopy.Value,
 212                    playbackInfoDto?.AlwaysBurnInSubtitleWhenTranscoding ?? false,
 213                    Request.HttpContext.GetNormalizedRemoteIP());
 214            }
 215
 216            _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
 217        }
 218
 219        if (autoOpenLiveStream.Value)
 220        {
 221            var mediaSource = string.IsNullOrWhiteSpace(mediaSourceId) ? info.MediaSources[0] : info.MediaSources.FirstO
 222
 223            if (mediaSource is not null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStre
 224            {
 225                var openStreamResult = await _mediaInfoHelper.OpenMediaSource(
 226                    HttpContext,
 227                    new LiveStreamRequest
 228                    {
 229                        AudioStreamIndex = audioStreamIndex,
 230                        DeviceProfile = playbackInfoDto?.DeviceProfile,
 231                        EnableDirectPlay = enableDirectPlay.Value,
 232                        EnableDirectStream = enableDirectStream.Value,
 233                        ItemId = itemId,
 234                        MaxAudioChannels = maxAudioChannels,
 235                        MaxStreamingBitrate = maxStreamingBitrate,
 236                        PlaySessionId = info.PlaySessionId,
 237                        StartTimeTicks = startTimeTicks,
 238                        SubtitleStreamIndex = subtitleStreamIndex,
 239                        UserId = userId ?? Guid.Empty,
 240                        OpenToken = mediaSource.OpenToken,
 241                        AlwaysBurnInSubtitleWhenTranscoding = playbackInfoDto?.AlwaysBurnInSubtitleWhenTranscoding ?? fa
 242                    }).ConfigureAwait(false);
 243
 244                info.MediaSources = new[] { openStreamResult.MediaSource };
 245            }
 246        }
 247
 248        return info;
 249    }
 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    {
 286        userId ??= openLiveStreamDto?.UserId;
 287        userId = RequestHelpers.GetUserId(User, userId);
 288        var request = new LiveStreamRequest
 289        {
 290            OpenToken = openToken ?? openLiveStreamDto?.OpenToken,
 291            UserId = userId.Value,
 292            PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId,
 293            MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate,
 294            StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks,
 295            AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex,
 296            SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex,
 297            MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels,
 298            ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty,
 299            DeviceProfile = openLiveStreamDto?.DeviceProfile,
 300            EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true,
 301            EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true,
 302            DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http },
 303            AlwaysBurnInSubtitleWhenTranscoding = alwaysBurnInSubtitleWhenTranscoding ?? openLiveStreamDto?.AlwaysBurnIn
 304        };
 305        return await _mediaInfoHelper.OpenMediaSource(HttpContext, request).ConfigureAwait(false);
 306    }
 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    {
 318        await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
 319        return NoContent();
 320    }
 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}