MediaInfoController.cs 16 KB

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