MediaInfoController.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. using System;
  2. using System.Buffers;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Linq;
  5. using System.Net.Mime;
  6. using System.Threading.Tasks;
  7. using Jellyfin.Api.Attributes;
  8. using Jellyfin.Api.Constants;
  9. using Jellyfin.Api.Helpers;
  10. using Jellyfin.Api.Models.MediaInfoDtos;
  11. using MediaBrowser.Common.Extensions;
  12. using MediaBrowser.Controller.Devices;
  13. using MediaBrowser.Controller.Library;
  14. using MediaBrowser.Controller.Net;
  15. using MediaBrowser.Model.Dlna;
  16. using MediaBrowser.Model.MediaInfo;
  17. using Microsoft.AspNetCore.Authorization;
  18. using Microsoft.AspNetCore.Http;
  19. using Microsoft.AspNetCore.Mvc;
  20. using Microsoft.AspNetCore.Mvc.ModelBinding;
  21. using Microsoft.Extensions.Logging;
  22. namespace Jellyfin.Api.Controllers
  23. {
  24. /// <summary>
  25. /// The media info controller.
  26. /// </summary>
  27. [Route("")]
  28. [Authorize(Policy = Policies.DefaultAuthorization)]
  29. public class MediaInfoController : BaseJellyfinApiController
  30. {
  31. private readonly IMediaSourceManager _mediaSourceManager;
  32. private readonly IDeviceManager _deviceManager;
  33. private readonly ILibraryManager _libraryManager;
  34. private readonly IAuthorizationContext _authContext;
  35. private readonly ILogger<MediaInfoController> _logger;
  36. private readonly MediaInfoHelper _mediaInfoHelper;
  37. /// <summary>
  38. /// Initializes a new instance of the <see cref="MediaInfoController"/> class.
  39. /// </summary>
  40. /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
  41. /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
  42. /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
  43. /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
  44. /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
  45. /// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param>
  46. public MediaInfoController(
  47. IMediaSourceManager mediaSourceManager,
  48. IDeviceManager deviceManager,
  49. ILibraryManager libraryManager,
  50. IAuthorizationContext authContext,
  51. ILogger<MediaInfoController> logger,
  52. MediaInfoHelper mediaInfoHelper)
  53. {
  54. _mediaSourceManager = mediaSourceManager;
  55. _deviceManager = deviceManager;
  56. _libraryManager = libraryManager;
  57. _authContext = authContext;
  58. _logger = logger;
  59. _mediaInfoHelper = mediaInfoHelper;
  60. }
  61. /// <summary>
  62. /// Gets live playback media info for an item.
  63. /// </summary>
  64. /// <param name="itemId">The item id.</param>
  65. /// <param name="userId">The user id.</param>
  66. /// <response code="200">Playback info returned.</response>
  67. /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
  68. [HttpGet("Items/{itemId}/PlaybackInfo")]
  69. [ProducesResponseType(StatusCodes.Status200OK)]
  70. public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery, Required] Guid userId)
  71. {
  72. return await _mediaInfoHelper.GetPlaybackInfo(
  73. itemId,
  74. userId)
  75. .ConfigureAwait(false);
  76. }
  77. /// <summary>
  78. /// Gets live playback media info for an item.
  79. /// </summary>
  80. /// <remarks>
  81. /// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.
  82. /// Query parameters are obsolete.
  83. /// </remarks>
  84. /// <param name="itemId">The item id.</param>
  85. /// <param name="userId">The user id.</param>
  86. /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
  87. /// <param name="startTimeTicks">The start time in ticks.</param>
  88. /// <param name="audioStreamIndex">The audio stream index.</param>
  89. /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
  90. /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
  91. /// <param name="mediaSourceId">The media source id.</param>
  92. /// <param name="liveStreamId">The livestream id.</param>
  93. /// <param name="autoOpenLiveStream">Whether to auto open the livestream.</param>
  94. /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
  95. /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
  96. /// <param name="enableTranscoding">Whether to enable transcoding. Default: true.</param>
  97. /// <param name="allowVideoStreamCopy">Whether to allow to copy the video stream. Default: true.</param>
  98. /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
  99. /// <param name="playbackInfoDto">The playback info.</param>
  100. /// <response code="200">Playback info returned.</response>
  101. /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
  102. [HttpPost("Items/{itemId}/PlaybackInfo")]
  103. [ProducesResponseType(StatusCodes.Status200OK)]
  104. public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
  105. [FromRoute, Required] Guid itemId,
  106. [FromQuery, ParameterObsolete] Guid? userId,
  107. [FromQuery, ParameterObsolete] int? maxStreamingBitrate,
  108. [FromQuery, ParameterObsolete] long? startTimeTicks,
  109. [FromQuery, ParameterObsolete] int? audioStreamIndex,
  110. [FromQuery, ParameterObsolete] int? subtitleStreamIndex,
  111. [FromQuery, ParameterObsolete] int? maxAudioChannels,
  112. [FromQuery, ParameterObsolete] string? mediaSourceId,
  113. [FromQuery, ParameterObsolete] string? liveStreamId,
  114. [FromQuery, ParameterObsolete] bool? autoOpenLiveStream,
  115. [FromQuery, ParameterObsolete] bool? enableDirectPlay,
  116. [FromQuery, ParameterObsolete] bool? enableDirectStream,
  117. [FromQuery, ParameterObsolete] bool? enableTranscoding,
  118. [FromQuery, ParameterObsolete] bool? allowVideoStreamCopy,
  119. [FromQuery, ParameterObsolete] bool? allowAudioStreamCopy,
  120. [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto)
  121. {
  122. var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
  123. var profile = playbackInfoDto?.DeviceProfile;
  124. _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
  125. if (profile == null)
  126. {
  127. var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
  128. if (caps != null)
  129. {
  130. profile = caps.DeviceProfile;
  131. }
  132. }
  133. // Copy params from posted body
  134. // TODO clean up when breaking API compatibility.
  135. userId ??= playbackInfoDto?.UserId;
  136. maxStreamingBitrate ??= playbackInfoDto?.MaxStreamingBitrate;
  137. startTimeTicks ??= playbackInfoDto?.StartTimeTicks;
  138. audioStreamIndex ??= playbackInfoDto?.AudioStreamIndex;
  139. subtitleStreamIndex ??= playbackInfoDto?.SubtitleStreamIndex;
  140. maxAudioChannels ??= playbackInfoDto?.MaxAudioChannels;
  141. mediaSourceId ??= playbackInfoDto?.MediaSourceId;
  142. liveStreamId ??= playbackInfoDto?.LiveStreamId;
  143. autoOpenLiveStream ??= playbackInfoDto?.AutoOpenLiveStream ?? false;
  144. enableDirectPlay ??= playbackInfoDto?.EnableDirectPlay ?? true;
  145. enableDirectStream ??= playbackInfoDto?.EnableDirectStream ?? true;
  146. enableTranscoding ??= playbackInfoDto?.EnableTranscoding ?? true;
  147. allowVideoStreamCopy ??= playbackInfoDto?.AllowVideoStreamCopy ?? true;
  148. allowAudioStreamCopy ??= playbackInfoDto?.AllowAudioStreamCopy ?? true;
  149. var info = await _mediaInfoHelper.GetPlaybackInfo(
  150. itemId,
  151. userId,
  152. mediaSourceId,
  153. liveStreamId)
  154. .ConfigureAwait(false);
  155. if (info.ErrorCode != null)
  156. {
  157. return info;
  158. }
  159. if (profile != null)
  160. {
  161. // set device specific data
  162. var item = _libraryManager.GetItemById(itemId);
  163. foreach (var mediaSource in info.MediaSources)
  164. {
  165. _mediaInfoHelper.SetDeviceSpecificData(
  166. item,
  167. mediaSource,
  168. profile,
  169. authInfo,
  170. maxStreamingBitrate ?? profile.MaxStreamingBitrate,
  171. startTimeTicks ?? 0,
  172. mediaSourceId ?? string.Empty,
  173. audioStreamIndex,
  174. subtitleStreamIndex,
  175. maxAudioChannels,
  176. info.PlaySessionId!,
  177. userId ?? Guid.Empty,
  178. enableDirectPlay.Value,
  179. enableDirectStream.Value,
  180. enableTranscoding.Value,
  181. allowVideoStreamCopy.Value,
  182. allowAudioStreamCopy.Value,
  183. Request.HttpContext.GetNormalizedRemoteIp());
  184. }
  185. _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
  186. }
  187. if (autoOpenLiveStream.Value)
  188. {
  189. var mediaSource = string.IsNullOrWhiteSpace(mediaSourceId) ? info.MediaSources[0] : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.Ordinal));
  190. if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
  191. {
  192. var openStreamResult = await _mediaInfoHelper.OpenMediaSource(
  193. Request,
  194. new LiveStreamRequest
  195. {
  196. AudioStreamIndex = audioStreamIndex,
  197. DeviceProfile = playbackInfoDto?.DeviceProfile,
  198. EnableDirectPlay = enableDirectPlay.Value,
  199. EnableDirectStream = enableDirectStream.Value,
  200. ItemId = itemId,
  201. MaxAudioChannels = maxAudioChannels,
  202. MaxStreamingBitrate = maxStreamingBitrate,
  203. PlaySessionId = info.PlaySessionId,
  204. StartTimeTicks = startTimeTicks,
  205. SubtitleStreamIndex = subtitleStreamIndex,
  206. UserId = userId ?? Guid.Empty,
  207. OpenToken = mediaSource.OpenToken
  208. }).ConfigureAwait(false);
  209. info.MediaSources = new[] { openStreamResult.MediaSource };
  210. }
  211. }
  212. if (info.MediaSources != null)
  213. {
  214. foreach (var mediaSource in info.MediaSources)
  215. {
  216. _mediaInfoHelper.NormalizeMediaSourceContainer(mediaSource, profile!, DlnaProfileType.Video);
  217. }
  218. }
  219. return info;
  220. }
  221. /// <summary>
  222. /// Opens a media source.
  223. /// </summary>
  224. /// <param name="openToken">The open token.</param>
  225. /// <param name="userId">The user id.</param>
  226. /// <param name="playSessionId">The play session id.</param>
  227. /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
  228. /// <param name="startTimeTicks">The start time in ticks.</param>
  229. /// <param name="audioStreamIndex">The audio stream index.</param>
  230. /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
  231. /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
  232. /// <param name="itemId">The item id.</param>
  233. /// <param name="openLiveStreamDto">The open live stream dto.</param>
  234. /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
  235. /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
  236. /// <response code="200">Media source opened.</response>
  237. /// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
  238. [HttpPost("LiveStreams/Open")]
  239. [ProducesResponseType(StatusCodes.Status200OK)]
  240. public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
  241. [FromQuery] string? openToken,
  242. [FromQuery] Guid? userId,
  243. [FromQuery] string? playSessionId,
  244. [FromQuery] int? maxStreamingBitrate,
  245. [FromQuery] long? startTimeTicks,
  246. [FromQuery] int? audioStreamIndex,
  247. [FromQuery] int? subtitleStreamIndex,
  248. [FromQuery] int? maxAudioChannels,
  249. [FromQuery] Guid? itemId,
  250. [FromBody] OpenLiveStreamDto? openLiveStreamDto,
  251. [FromQuery] bool? enableDirectPlay,
  252. [FromQuery] bool? enableDirectStream)
  253. {
  254. var request = new LiveStreamRequest
  255. {
  256. OpenToken = openToken ?? openLiveStreamDto?.OpenToken,
  257. UserId = userId ?? openLiveStreamDto?.UserId ?? Guid.Empty,
  258. PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId,
  259. MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate,
  260. StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks,
  261. AudioStreamIndex = audioStreamIndex ?? openLiveStreamDto?.AudioStreamIndex,
  262. SubtitleStreamIndex = subtitleStreamIndex ?? openLiveStreamDto?.SubtitleStreamIndex,
  263. MaxAudioChannels = maxAudioChannels ?? openLiveStreamDto?.MaxAudioChannels,
  264. ItemId = itemId ?? openLiveStreamDto?.ItemId ?? Guid.Empty,
  265. DeviceProfile = openLiveStreamDto?.DeviceProfile,
  266. EnableDirectPlay = enableDirectPlay ?? openLiveStreamDto?.EnableDirectPlay ?? true,
  267. EnableDirectStream = enableDirectStream ?? openLiveStreamDto?.EnableDirectStream ?? true,
  268. DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http }
  269. };
  270. return await _mediaInfoHelper.OpenMediaSource(Request, request).ConfigureAwait(false);
  271. }
  272. /// <summary>
  273. /// Closes a media source.
  274. /// </summary>
  275. /// <param name="liveStreamId">The livestream id.</param>
  276. /// <response code="204">Livestream closed.</response>
  277. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  278. [HttpPost("LiveStreams/Close")]
  279. [ProducesResponseType(StatusCodes.Status204NoContent)]
  280. public async Task<ActionResult> CloseLiveStream([FromQuery, Required] string liveStreamId)
  281. {
  282. await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
  283. return NoContent();
  284. }
  285. /// <summary>
  286. /// Tests the network with a request with the size of the bitrate.
  287. /// </summary>
  288. /// <param name="size">The bitrate. Defaults to 102400.</param>
  289. /// <response code="200">Test buffer returned.</response>
  290. /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
  291. [HttpGet("Playback/BitrateTest")]
  292. [ProducesResponseType(StatusCodes.Status200OK)]
  293. [ProducesFile(MediaTypeNames.Application.Octet)]
  294. 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)
  295. {
  296. byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
  297. try
  298. {
  299. Random.Shared.NextBytes(buffer);
  300. return File(buffer, MediaTypeNames.Application.Octet);
  301. }
  302. finally
  303. {
  304. ArrayPool<byte>.Shared.Return(buffer);
  305. }
  306. }
  307. }
  308. }