MediaInfoController.cs 16 KB

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