MediaInfoController.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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 Jellyfin.Api.Models.VideoDtos;
  12. using MediaBrowser.Common.Extensions;
  13. using MediaBrowser.Controller.Devices;
  14. using MediaBrowser.Controller.Library;
  15. using MediaBrowser.Controller.Net;
  16. using MediaBrowser.Model.Dlna;
  17. using MediaBrowser.Model.MediaInfo;
  18. using Microsoft.AspNetCore.Authorization;
  19. using Microsoft.AspNetCore.Http;
  20. using Microsoft.AspNetCore.Mvc;
  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. /// <param name="itemId">The item id.</param>
  81. /// <param name="userId">The user id.</param>
  82. /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
  83. /// <param name="startTimeTicks">The start time in ticks.</param>
  84. /// <param name="audioStreamIndex">The audio stream index.</param>
  85. /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
  86. /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
  87. /// <param name="mediaSourceId">The media source id.</param>
  88. /// <param name="liveStreamId">The livestream id.</param>
  89. /// <param name="deviceProfile">The device profile.</param>
  90. /// <param name="autoOpenLiveStream">Whether to auto open the livestream.</param>
  91. /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
  92. /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
  93. /// <param name="enableTranscoding">Whether to enable transcoding. Default: true.</param>
  94. /// <param name="allowVideoStreamCopy">Whether to allow to copy the video stream. Default: true.</param>
  95. /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
  96. /// <response code="200">Playback info returned.</response>
  97. /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
  98. [HttpPost("Items/{itemId}/PlaybackInfo")]
  99. [ProducesResponseType(StatusCodes.Status200OK)]
  100. public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
  101. [FromRoute, Required] Guid itemId,
  102. [FromQuery] Guid? userId,
  103. [FromQuery] int? maxStreamingBitrate,
  104. [FromQuery] long? startTimeTicks,
  105. [FromQuery] int? audioStreamIndex,
  106. [FromQuery] int? subtitleStreamIndex,
  107. [FromQuery] int? maxAudioChannels,
  108. [FromQuery] string? mediaSourceId,
  109. [FromQuery] string? liveStreamId,
  110. [FromBody] DeviceProfileDto? deviceProfile,
  111. [FromQuery] bool autoOpenLiveStream = false,
  112. [FromQuery] bool enableDirectPlay = true,
  113. [FromQuery] bool enableDirectStream = true,
  114. [FromQuery] bool enableTranscoding = true,
  115. [FromQuery] bool allowVideoStreamCopy = true,
  116. [FromQuery] bool allowAudioStreamCopy = true)
  117. {
  118. var authInfo = _authContext.GetAuthorizationInfo(Request);
  119. var profile = deviceProfile?.DeviceProfile;
  120. _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
  121. if (profile == null)
  122. {
  123. var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
  124. if (caps != null)
  125. {
  126. profile = caps.DeviceProfile;
  127. }
  128. }
  129. var info = await _mediaInfoHelper.GetPlaybackInfo(
  130. itemId,
  131. userId,
  132. mediaSourceId,
  133. liveStreamId)
  134. .ConfigureAwait(false);
  135. if (profile != null)
  136. {
  137. // set device specific data
  138. var item = _libraryManager.GetItemById(itemId);
  139. foreach (var mediaSource in info.MediaSources)
  140. {
  141. _mediaInfoHelper.SetDeviceSpecificData(
  142. item,
  143. mediaSource,
  144. profile,
  145. authInfo,
  146. maxStreamingBitrate ?? profile.MaxStreamingBitrate,
  147. startTimeTicks ?? 0,
  148. mediaSourceId ?? string.Empty,
  149. audioStreamIndex,
  150. subtitleStreamIndex,
  151. maxAudioChannels,
  152. info!.PlaySessionId!,
  153. userId ?? Guid.Empty,
  154. enableDirectPlay,
  155. enableDirectStream,
  156. enableTranscoding,
  157. allowVideoStreamCopy,
  158. allowAudioStreamCopy,
  159. Request.HttpContext.GetNormalizedRemoteIp());
  160. }
  161. _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
  162. }
  163. if (autoOpenLiveStream)
  164. {
  165. var mediaSource = string.IsNullOrWhiteSpace(mediaSourceId) ? info.MediaSources[0] : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.Ordinal));
  166. if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
  167. {
  168. var openStreamResult = await _mediaInfoHelper.OpenMediaSource(
  169. Request,
  170. new LiveStreamRequest
  171. {
  172. AudioStreamIndex = audioStreamIndex,
  173. DeviceProfile = deviceProfile?.DeviceProfile,
  174. EnableDirectPlay = enableDirectPlay,
  175. EnableDirectStream = enableDirectStream,
  176. ItemId = itemId,
  177. MaxAudioChannels = maxAudioChannels,
  178. MaxStreamingBitrate = maxStreamingBitrate,
  179. PlaySessionId = info.PlaySessionId,
  180. StartTimeTicks = startTimeTicks,
  181. SubtitleStreamIndex = subtitleStreamIndex,
  182. UserId = userId ?? Guid.Empty,
  183. OpenToken = mediaSource.OpenToken
  184. }).ConfigureAwait(false);
  185. info.MediaSources = new[] { openStreamResult.MediaSource };
  186. }
  187. }
  188. if (info.MediaSources != null)
  189. {
  190. foreach (var mediaSource in info.MediaSources)
  191. {
  192. _mediaInfoHelper.NormalizeMediaSourceContainer(mediaSource, profile!, DlnaProfileType.Video);
  193. }
  194. }
  195. return info;
  196. }
  197. /// <summary>
  198. /// Opens a media source.
  199. /// </summary>
  200. /// <param name="openToken">The open token.</param>
  201. /// <param name="userId">The user id.</param>
  202. /// <param name="playSessionId">The play session id.</param>
  203. /// <param name="maxStreamingBitrate">The maximum streaming bitrate.</param>
  204. /// <param name="startTimeTicks">The start time in ticks.</param>
  205. /// <param name="audioStreamIndex">The audio stream index.</param>
  206. /// <param name="subtitleStreamIndex">The subtitle stream index.</param>
  207. /// <param name="maxAudioChannels">The maximum number of audio channels.</param>
  208. /// <param name="itemId">The item id.</param>
  209. /// <param name="openLiveStreamDto">The open live stream dto.</param>
  210. /// <param name="enableDirectPlay">Whether to enable direct play. Default: true.</param>
  211. /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
  212. /// <response code="200">Media source opened.</response>
  213. /// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
  214. [HttpPost("LiveStreams/Open")]
  215. [ProducesResponseType(StatusCodes.Status200OK)]
  216. public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
  217. [FromQuery] string? openToken,
  218. [FromQuery] Guid? userId,
  219. [FromQuery] string? playSessionId,
  220. [FromQuery] int? maxStreamingBitrate,
  221. [FromQuery] long? startTimeTicks,
  222. [FromQuery] int? audioStreamIndex,
  223. [FromQuery] int? subtitleStreamIndex,
  224. [FromQuery] int? maxAudioChannels,
  225. [FromQuery] Guid? itemId,
  226. [FromBody] OpenLiveStreamDto openLiveStreamDto,
  227. [FromQuery] bool enableDirectPlay = true,
  228. [FromQuery] bool enableDirectStream = true)
  229. {
  230. var request = new LiveStreamRequest
  231. {
  232. OpenToken = openToken,
  233. UserId = userId ?? Guid.Empty,
  234. PlaySessionId = playSessionId,
  235. MaxStreamingBitrate = maxStreamingBitrate,
  236. StartTimeTicks = startTimeTicks,
  237. AudioStreamIndex = audioStreamIndex,
  238. SubtitleStreamIndex = subtitleStreamIndex,
  239. MaxAudioChannels = maxAudioChannels,
  240. ItemId = itemId ?? Guid.Empty,
  241. DeviceProfile = openLiveStreamDto?.DeviceProfile,
  242. EnableDirectPlay = enableDirectPlay,
  243. EnableDirectStream = enableDirectStream,
  244. DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http }
  245. };
  246. return await _mediaInfoHelper.OpenMediaSource(Request, request).ConfigureAwait(false);
  247. }
  248. /// <summary>
  249. /// Closes a media source.
  250. /// </summary>
  251. /// <param name="liveStreamId">The livestream id.</param>
  252. /// <response code="204">Livestream closed.</response>
  253. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  254. [HttpPost("LiveStreams/Close")]
  255. [ProducesResponseType(StatusCodes.Status204NoContent)]
  256. public async Task<ActionResult> CloseLiveStream([FromQuery, Required] string liveStreamId)
  257. {
  258. await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
  259. return NoContent();
  260. }
  261. /// <summary>
  262. /// Tests the network with a request with the size of the bitrate.
  263. /// </summary>
  264. /// <param name="size">The bitrate. Defaults to 102400.</param>
  265. /// <response code="200">Test buffer returned.</response>
  266. /// <response code="400">Size has to be a numer between 0 and 10,000,000.</response>
  267. /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
  268. [HttpGet("Playback/BitrateTest")]
  269. [ProducesResponseType(StatusCodes.Status200OK)]
  270. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  271. [Produces(MediaTypeNames.Application.Octet)]
  272. [ProducesFile(MediaTypeNames.Application.Octet)]
  273. public ActionResult GetBitrateTestBytes([FromQuery] int size = 102400)
  274. {
  275. const int MaxSize = 10_000_000;
  276. if (size <= 0)
  277. {
  278. return BadRequest($"The requested size ({size}) is equal to or smaller than 0.");
  279. }
  280. if (size > MaxSize)
  281. {
  282. return BadRequest($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).");
  283. }
  284. byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
  285. try
  286. {
  287. new Random().NextBytes(buffer);
  288. return File(buffer, MediaTypeNames.Application.Octet);
  289. }
  290. finally
  291. {
  292. ArrayPool<byte>.Shared.Return(buffer);
  293. }
  294. }
  295. }
  296. }