2020-11-21 08:26:03 -05:00
using System ;
2020-06-27 06:26:43 -04:00
using System.Buffers ;
2020-08-06 10:17:45 -04:00
using System.ComponentModel.DataAnnotations ;
2020-06-27 06:26:43 -04:00
using System.Linq ;
using System.Net.Mime ;
using System.Threading.Tasks ;
2020-09-01 19:26:49 -04:00
using Jellyfin.Api.Attributes ;
2020-06-27 06:26:43 -04:00
using Jellyfin.Api.Constants ;
2020-08-09 19:20:14 -04:00
using Jellyfin.Api.Helpers ;
2020-08-03 15:33:43 -04:00
using Jellyfin.Api.Models.MediaInfoDtos ;
2020-09-10 08:16:41 -04:00
using MediaBrowser.Common.Extensions ;
2020-06-27 06:26:43 -04:00
using MediaBrowser.Controller.Devices ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Net ;
using MediaBrowser.Model.Dlna ;
using MediaBrowser.Model.MediaInfo ;
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Http ;
using Microsoft.AspNetCore.Mvc ;
2020-12-10 13:36:58 -05:00
using Microsoft.AspNetCore.Mvc.ModelBinding ;
2020-06-27 06:26:43 -04:00
using Microsoft.Extensions.Logging ;
namespace Jellyfin.Api.Controllers
{
/// <summary>
/// The media info controller.
/// </summary>
2020-08-04 14:48:53 -04:00
[Route("")]
2020-06-27 06:26:43 -04:00
[Authorize(Policy = Policies.DefaultAuthorization)]
public class MediaInfoController : BaseJellyfinApiController
{
private readonly IMediaSourceManager _mediaSourceManager ;
private readonly IDeviceManager _deviceManager ;
private readonly ILibraryManager _libraryManager ;
private readonly IAuthorizationContext _authContext ;
2020-08-03 15:16:29 -04:00
private readonly ILogger < MediaInfoController > _logger ;
2020-08-09 19:20:14 -04:00
private readonly MediaInfoHelper _mediaInfoHelper ;
2020-06-27 06:26:43 -04:00
/// <summary>
/// Initializes a new instance of the <see cref="MediaInfoController"/> class.
/// </summary>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
2020-08-09 19:20:14 -04:00
/// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param>
2020-06-27 06:26:43 -04:00
public MediaInfoController (
IMediaSourceManager mediaSourceManager ,
IDeviceManager deviceManager ,
ILibraryManager libraryManager ,
IAuthorizationContext authContext ,
ILogger < MediaInfoController > logger ,
2020-08-09 19:20:14 -04:00
MediaInfoHelper mediaInfoHelper )
2020-06-27 06:26:43 -04:00
{
_mediaSourceManager = mediaSourceManager ;
_deviceManager = deviceManager ;
_libraryManager = libraryManager ;
_authContext = authContext ;
_logger = logger ;
2020-08-09 19:20:14 -04:00
_mediaInfoHelper = mediaInfoHelper ;
2020-06-27 06:26:43 -04:00
}
/// <summary>
/// Gets live playback media info for an item.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="userId">The user id.</param>
/// <response code="200">Playback info returned.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
2020-08-04 14:48:53 -04:00
[HttpGet("Items/{itemId}/PlaybackInfo")]
2020-06-27 06:26:43 -04:00
[ProducesResponseType(StatusCodes.Status200OK)]
2020-09-09 16:28:30 -04:00
public async Task < ActionResult < PlaybackInfoResponse > > GetPlaybackInfo ( [ FromRoute , Required ] Guid itemId , [ FromQuery , Required ] Guid userId )
2020-06-27 06:26:43 -04:00
{
2020-08-09 19:20:14 -04:00
return await _mediaInfoHelper . GetPlaybackInfo (
itemId ,
userId )
. ConfigureAwait ( false ) ;
2020-06-27 06:26:43 -04:00
}
/// <summary>
/// Gets live playback media info for an item.
/// </summary>
2020-11-23 11:49:42 -05:00
/// <remarks>
/// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.
2021-01-20 18:24:15 -05:00
/// Query parameters are obsolete.
2020-11-23 11:49:42 -05:00
/// </remarks>
2020-06-27 06:26:43 -04:00
/// <param name="itemId">The item id.</param>
/// <param name="userId">The user id.</param>
/// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
/// <param name="startTimeTicks">The start time in ticks.</param>
/// <param name="audioStreamIndex">The audio stream index.</param>
/// <param name="subtitleStreamIndex">The subtitle stream index.</param>
/// <param name="maxAudioChannels">The maximum number of audio channels.</param>
/// <param name="mediaSourceId">The media source id.</param>
/// <param name="liveStreamId">The livestream id.</param>
/// <param name="autoOpenLiveStream">Whether to auto open the livestream.</param>
/// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
/// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
/// <param name="enableTranscoding">Whether to enable transcoding. Default: true.</param>
/// <param name="allowVideoStreamCopy">Whether to allow to copy the video stream. Default: true.</param>
/// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
2020-11-23 11:49:42 -05:00
/// <param name="playbackInfoDto">The playback info.</param>
2020-06-27 06:26:43 -04:00
/// <response code="200">Playback info returned.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
2020-08-04 14:48:53 -04:00
[HttpPost("Items/{itemId}/PlaybackInfo")]
2020-06-27 06:26:43 -04:00
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task < ActionResult < PlaybackInfoResponse > > GetPostedPlaybackInfo (
2020-09-06 11:07:27 -04:00
[FromRoute, Required] Guid itemId ,
2021-01-20 18:24:15 -05:00
[FromQuery, ParameterObsolete] Guid ? userId ,
[FromQuery, ParameterObsolete] int? maxStreamingBitrate ,
[FromQuery, ParameterObsolete] long? startTimeTicks ,
[FromQuery, ParameterObsolete] int? audioStreamIndex ,
[FromQuery, ParameterObsolete] int? subtitleStreamIndex ,
[FromQuery, ParameterObsolete] int? maxAudioChannels ,
[FromQuery, ParameterObsolete] string? mediaSourceId ,
[FromQuery, ParameterObsolete] string? liveStreamId ,
[FromQuery, ParameterObsolete] bool? autoOpenLiveStream ,
[FromQuery, ParameterObsolete] bool? enableDirectPlay ,
[FromQuery, ParameterObsolete] bool? enableDirectStream ,
[FromQuery, ParameterObsolete] bool? enableTranscoding ,
[FromQuery, ParameterObsolete] bool? allowVideoStreamCopy ,
[FromQuery, ParameterObsolete] bool? allowAudioStreamCopy ,
2020-12-10 13:36:58 -05:00
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto ? playbackInfoDto )
2020-06-27 06:26:43 -04:00
{
var authInfo = _authContext . GetAuthorizationInfo ( Request ) ;
2020-11-23 12:50:18 -05:00
var profile = playbackInfoDto ? . DeviceProfile ;
2020-06-27 06:26:43 -04:00
_logger . LogInformation ( "GetPostedPlaybackInfo profile: {@Profile}" , profile ) ;
if ( profile = = null )
{
var caps = _deviceManager . GetCapabilities ( authInfo . DeviceId ) ;
if ( caps ! = null )
{
profile = caps . DeviceProfile ;
}
}
2020-11-23 11:49:42 -05:00
// Copy params from posted body
2020-11-23 11:51:24 -05:00
// TODO clean up when breaking API compatibility.
2020-11-23 11:49:42 -05:00
userId ? ? = playbackInfoDto ? . UserId ;
maxStreamingBitrate ? ? = playbackInfoDto ? . MaxStreamingBitrate ;
startTimeTicks ? ? = playbackInfoDto ? . StartTimeTicks ;
audioStreamIndex ? ? = playbackInfoDto ? . AudioStreamIndex ;
subtitleStreamIndex ? ? = playbackInfoDto ? . SubtitleStreamIndex ;
maxAudioChannels ? ? = playbackInfoDto ? . MaxAudioChannels ;
mediaSourceId ? ? = playbackInfoDto ? . MediaSourceId ;
liveStreamId ? ? = playbackInfoDto ? . LiveStreamId ;
autoOpenLiveStream ? ? = playbackInfoDto ? . AutoOpenLiveStream ? ? false ;
enableDirectPlay ? ? = playbackInfoDto ? . EnableDirectPlay ? ? true ;
enableDirectStream ? ? = playbackInfoDto ? . EnableDirectStream ? ? true ;
enableTranscoding ? ? = playbackInfoDto ? . EnableTranscoding ? ? true ;
allowVideoStreamCopy ? ? = playbackInfoDto ? . AllowVideoStreamCopy ? ? true ;
allowAudioStreamCopy ? ? = playbackInfoDto ? . AllowAudioStreamCopy ? ? true ;
2020-08-09 19:20:14 -04:00
var info = await _mediaInfoHelper . GetPlaybackInfo (
itemId ,
userId ,
mediaSourceId ,
liveStreamId )
. ConfigureAwait ( false ) ;
2020-06-27 06:26:43 -04:00
2021-07-16 05:48:08 -04:00
if ( info . ErrorCode ! = null )
{
return info ;
}
2020-06-27 06:26:43 -04:00
if ( profile ! = null )
{
// set device specific data
var item = _libraryManager . GetItemById ( itemId ) ;
foreach ( var mediaSource in info . MediaSources )
{
2020-08-09 19:20:14 -04:00
_mediaInfoHelper . SetDeviceSpecificData (
2020-06-27 06:26:43 -04:00
item ,
mediaSource ,
profile ,
authInfo ,
maxStreamingBitrate ? ? profile . MaxStreamingBitrate ,
startTimeTicks ? ? 0 ,
2020-07-07 11:10:51 -04:00
mediaSourceId ? ? string . Empty ,
2020-06-27 06:26:43 -04:00
audioStreamIndex ,
subtitleStreamIndex ,
maxAudioChannels ,
info ! . PlaySessionId ! ,
2020-07-07 11:10:51 -04:00
userId ? ? Guid . Empty ,
2020-11-23 11:49:42 -05:00
enableDirectPlay . Value ,
enableDirectStream . Value ,
enableTranscoding . Value ,
allowVideoStreamCopy . Value ,
allowAudioStreamCopy . Value ,
2020-09-10 08:16:41 -04:00
Request . HttpContext . GetNormalizedRemoteIp ( ) ) ;
2020-06-27 06:26:43 -04:00
}
2020-08-09 19:20:14 -04:00
_mediaInfoHelper . SortMediaSources ( info , maxStreamingBitrate ) ;
2020-06-27 06:26:43 -04:00
}
2020-11-23 11:49:42 -05:00
if ( autoOpenLiveStream . Value )
2020-06-27 06:26:43 -04:00
{
var mediaSource = string . IsNullOrWhiteSpace ( mediaSourceId ) ? info . MediaSources [ 0 ] : info . MediaSources . FirstOrDefault ( i = > string . Equals ( i . Id , mediaSourceId , StringComparison . Ordinal ) ) ;
if ( mediaSource ! = null & & mediaSource . RequiresOpening & & string . IsNullOrWhiteSpace ( mediaSource . LiveStreamId ) )
{
2020-08-09 19:20:14 -04:00
var openStreamResult = await _mediaInfoHelper . OpenMediaSource (
Request ,
new LiveStreamRequest
{
AudioStreamIndex = audioStreamIndex ,
2020-11-23 12:50:18 -05:00
DeviceProfile = playbackInfoDto ? . DeviceProfile ,
2020-11-23 11:49:42 -05:00
EnableDirectPlay = enableDirectPlay . Value ,
EnableDirectStream = enableDirectStream . Value ,
2020-08-09 19:20:14 -04:00
ItemId = itemId ,
MaxAudioChannels = maxAudioChannels ,
MaxStreamingBitrate = maxStreamingBitrate ,
PlaySessionId = info . PlaySessionId ,
StartTimeTicks = startTimeTicks ,
SubtitleStreamIndex = subtitleStreamIndex ,
UserId = userId ? ? Guid . Empty ,
OpenToken = mediaSource . OpenToken
} ) . ConfigureAwait ( false ) ;
2020-06-27 06:26:43 -04:00
info . MediaSources = new [ ] { openStreamResult . MediaSource } ;
}
}
if ( info . MediaSources ! = null )
{
foreach ( var mediaSource in info . MediaSources )
{
2020-08-09 19:20:14 -04:00
_mediaInfoHelper . NormalizeMediaSourceContainer ( mediaSource , profile ! , DlnaProfileType . Video ) ;
2020-06-27 06:26:43 -04:00
}
}
return info ;
}
/// <summary>
/// Opens a media source.
/// </summary>
/// <param name="openToken">The open token.</param>
/// <param name="userId">The user id.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
/// <param name="startTimeTicks">The start time in ticks.</param>
/// <param name="audioStreamIndex">The audio stream index.</param>
/// <param name="subtitleStreamIndex">The subtitle stream index.</param>
/// <param name="maxAudioChannels">The maximum number of audio channels.</param>
/// <param name="itemId">The item id.</param>
2020-08-03 15:33:43 -04:00
/// <param name="openLiveStreamDto">The open live stream dto.</param>
2020-06-27 06:26:43 -04:00
/// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
/// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
/// <response code="200">Media source opened.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
2020-08-04 14:48:53 -04:00
[HttpPost("LiveStreams/Open")]
2020-06-27 06:26:43 -04:00
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task < ActionResult < LiveStreamResponse > > OpenLiveStream (
2020-07-07 11:10:51 -04:00
[FromQuery] string? openToken ,
[FromQuery] Guid ? userId ,
[FromQuery] string? playSessionId ,
2020-10-31 04:09:22 -04:00
[FromQuery] int? maxStreamingBitrate ,
2020-06-27 06:26:43 -04:00
[FromQuery] long? startTimeTicks ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? subtitleStreamIndex ,
[FromQuery] int? maxAudioChannels ,
2020-07-07 11:10:51 -04:00
[FromQuery] Guid ? itemId ,
2020-12-19 12:55:07 -05:00
[FromBody] OpenLiveStreamDto ? openLiveStreamDto ,
[FromQuery] bool? enableDirectPlay ,
[FromQuery] bool? enableDirectStream )
2020-06-27 06:26:43 -04:00
{
var request = new LiveStreamRequest
{
2020-12-19 12:55:07 -05:00
OpenToken = openToken ? ? openLiveStreamDto ? . OpenToken ,
UserId = userId ? ? openLiveStreamDto ? . UserId ? ? Guid . Empty ,
PlaySessionId = playSessionId ? ? openLiveStreamDto ? . PlaySessionId ,
MaxStreamingBitrate = maxStreamingBitrate ? ? openLiveStreamDto ? . MaxStreamingBitrate ,
StartTimeTicks = startTimeTicks ? ? openLiveStreamDto ? . StartTimeTicks ,
AudioStreamIndex = audioStreamIndex ? ? openLiveStreamDto ? . AudioStreamIndex ,
SubtitleStreamIndex = subtitleStreamIndex ? ? openLiveStreamDto ? . SubtitleStreamIndex ,
MaxAudioChannels = maxAudioChannels ? ? openLiveStreamDto ? . MaxAudioChannels ,
ItemId = itemId ? ? openLiveStreamDto ? . ItemId ? ? Guid . Empty ,
2020-08-03 15:33:43 -04:00
DeviceProfile = openLiveStreamDto ? . DeviceProfile ,
2020-12-19 12:55:07 -05:00
EnableDirectPlay = enableDirectPlay ? ? openLiveStreamDto ? . EnableDirectPlay ? ? true ,
EnableDirectStream = enableDirectStream ? ? openLiveStreamDto ? . EnableDirectStream ? ? true ,
2020-08-03 15:33:43 -04:00
DirectPlayProtocols = openLiveStreamDto ? . DirectPlayProtocols ? ? new [ ] { MediaProtocol . Http }
2020-06-27 06:26:43 -04:00
} ;
2020-08-09 19:20:14 -04:00
return await _mediaInfoHelper . OpenMediaSource ( Request , request ) . ConfigureAwait ( false ) ;
2020-06-27 06:26:43 -04:00
}
/// <summary>
/// Closes a media source.
/// </summary>
/// <param name="liveStreamId">The livestream id.</param>
/// <response code="204">Livestream closed.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
2020-08-04 14:48:53 -04:00
[HttpPost("LiveStreams/Close")]
2020-06-27 06:26:43 -04:00
[ProducesResponseType(StatusCodes.Status204NoContent)]
2020-09-09 16:28:30 -04:00
public async Task < ActionResult > CloseLiveStream ( [ FromQuery , Required ] string liveStreamId )
2020-06-27 06:26:43 -04:00
{
2020-08-21 16:06:06 -04:00
await _mediaSourceManager . CloseLiveStream ( liveStreamId ) . ConfigureAwait ( false ) ;
2020-06-27 06:26:43 -04:00
return NoContent ( ) ;
}
/// <summary>
/// Tests the network with a request with the size of the bitrate.
/// </summary>
/// <param name="size">The bitrate. Defaults to 102400.</param>
/// <response code="200">Test buffer returned.</response>
/// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
2020-08-04 14:48:53 -04:00
[HttpGet("Playback/BitrateTest")]
2020-06-27 06:26:43 -04:00
[ProducesResponseType(StatusCodes.Status200OK)]
2020-09-01 19:26:49 -04:00
[ProducesFile(MediaTypeNames.Application.Octet)]
2021-07-10 07:34:15 -04:00
public ActionResult GetBitrateTestBytes ( [ FromQuery ] [ Range ( 1 , 100_000_000 , ErrorMessage = "The requested size must be greater than or equal to {1} and less than or equal to {2}" ) ] int size = 102400 )
2020-06-27 06:26:43 -04:00
{
byte [ ] buffer = ArrayPool < byte > . Shared . Rent ( size ) ;
try
{
new Random ( ) . NextBytes ( buffer ) ;
return File ( buffer , MediaTypeNames . Application . Octet ) ;
}
finally
{
ArrayPool < byte > . Shared . Return ( buffer ) ;
}
}
}
}