DlnaServerController.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.IO;
  5. using System.Net.Mime;
  6. using System.Threading.Tasks;
  7. using Emby.Dlna;
  8. using Jellyfin.Api.Attributes;
  9. using Jellyfin.Api.Constants;
  10. using MediaBrowser.Common.Api;
  11. using MediaBrowser.Controller.Dlna;
  12. using MediaBrowser.Model.Net;
  13. using Microsoft.AspNetCore.Authorization;
  14. using Microsoft.AspNetCore.Http;
  15. using Microsoft.AspNetCore.Mvc;
  16. namespace Jellyfin.Api.Controllers;
  17. /// <summary>
  18. /// Dlna Server Controller.
  19. /// </summary>
  20. [Route("Dlna")]
  21. [DlnaEnabled]
  22. [Authorize(Policy = Policies.AnonymousLanAccessPolicy)]
  23. public class DlnaServerController : BaseJellyfinApiController
  24. {
  25. private readonly IDlnaManager _dlnaManager;
  26. private readonly IContentDirectory _contentDirectory;
  27. private readonly IConnectionManager _connectionManager;
  28. private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar;
  29. /// <summary>
  30. /// Initializes a new instance of the <see cref="DlnaServerController"/> class.
  31. /// </summary>
  32. /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
  33. /// <param name="contentDirectory">Instance of the <see cref="IContentDirectory"/> interface.</param>
  34. /// <param name="connectionManager">Instance of the <see cref="IConnectionManager"/> interface.</param>
  35. /// <param name="mediaReceiverRegistrar">Instance of the <see cref="IMediaReceiverRegistrar"/> interface.</param>
  36. public DlnaServerController(
  37. IDlnaManager dlnaManager,
  38. IContentDirectory contentDirectory,
  39. IConnectionManager connectionManager,
  40. IMediaReceiverRegistrar mediaReceiverRegistrar)
  41. {
  42. _dlnaManager = dlnaManager;
  43. _contentDirectory = contentDirectory;
  44. _connectionManager = connectionManager;
  45. _mediaReceiverRegistrar = mediaReceiverRegistrar;
  46. }
  47. /// <summary>
  48. /// Get Description Xml.
  49. /// </summary>
  50. /// <param name="serverId">Server UUID.</param>
  51. /// <response code="200">Description xml returned.</response>
  52. /// <response code="503">DLNA is disabled.</response>
  53. /// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
  54. [HttpGet("{serverId}/description")]
  55. [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
  56. [ProducesResponseType(StatusCodes.Status200OK)]
  57. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  58. [Produces(MediaTypeNames.Text.Xml)]
  59. [ProducesFile(MediaTypeNames.Text.Xml)]
  60. public ActionResult<string> GetDescriptionXml([FromRoute, Required] string serverId)
  61. {
  62. var url = GetAbsoluteUri();
  63. var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
  64. var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
  65. return Ok(xml);
  66. }
  67. /// <summary>
  68. /// Gets Dlna content directory xml.
  69. /// </summary>
  70. /// <param name="serverId">Server UUID.</param>
  71. /// <response code="200">Dlna content directory returned.</response>
  72. /// <response code="503">DLNA is disabled.</response>
  73. /// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
  74. [HttpGet("{serverId}/ContentDirectory")]
  75. [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
  76. [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
  77. [ProducesResponseType(StatusCodes.Status200OK)]
  78. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  79. [Produces(MediaTypeNames.Text.Xml)]
  80. [ProducesFile(MediaTypeNames.Text.Xml)]
  81. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  82. public ActionResult<string> GetContentDirectory([FromRoute, Required] string serverId)
  83. {
  84. return Ok(_contentDirectory.GetServiceXml());
  85. }
  86. /// <summary>
  87. /// Gets Dlna media receiver registrar xml.
  88. /// </summary>
  89. /// <param name="serverId">Server UUID.</param>
  90. /// <response code="200">Dlna media receiver registrar xml returned.</response>
  91. /// <response code="503">DLNA is disabled.</response>
  92. /// <returns>Dlna media receiver registrar xml.</returns>
  93. [HttpGet("{serverId}/MediaReceiverRegistrar")]
  94. [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
  95. [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
  96. [ProducesResponseType(StatusCodes.Status200OK)]
  97. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  98. [Produces(MediaTypeNames.Text.Xml)]
  99. [ProducesFile(MediaTypeNames.Text.Xml)]
  100. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  101. public ActionResult<string> GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
  102. {
  103. return Ok(_mediaReceiverRegistrar.GetServiceXml());
  104. }
  105. /// <summary>
  106. /// Gets Dlna media receiver registrar xml.
  107. /// </summary>
  108. /// <param name="serverId">Server UUID.</param>
  109. /// <response code="200">Dlna media receiver registrar xml returned.</response>
  110. /// <response code="503">DLNA is disabled.</response>
  111. /// <returns>Dlna media receiver registrar xml.</returns>
  112. [HttpGet("{serverId}/ConnectionManager")]
  113. [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
  114. [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
  115. [ProducesResponseType(StatusCodes.Status200OK)]
  116. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  117. [Produces(MediaTypeNames.Text.Xml)]
  118. [ProducesFile(MediaTypeNames.Text.Xml)]
  119. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  120. public ActionResult<string> GetConnectionManager([FromRoute, Required] string serverId)
  121. {
  122. return Ok(_connectionManager.GetServiceXml());
  123. }
  124. /// <summary>
  125. /// Process a content directory control request.
  126. /// </summary>
  127. /// <param name="serverId">Server UUID.</param>
  128. /// <response code="200">Request processed.</response>
  129. /// <response code="503">DLNA is disabled.</response>
  130. /// <returns>Control response.</returns>
  131. [HttpPost("{serverId}/ContentDirectory/Control")]
  132. [ProducesResponseType(StatusCodes.Status200OK)]
  133. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  134. [Produces(MediaTypeNames.Text.Xml)]
  135. [ProducesFile(MediaTypeNames.Text.Xml)]
  136. public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
  137. {
  138. return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
  139. }
  140. /// <summary>
  141. /// Process a connection manager control request.
  142. /// </summary>
  143. /// <param name="serverId">Server UUID.</param>
  144. /// <response code="200">Request processed.</response>
  145. /// <response code="503">DLNA is disabled.</response>
  146. /// <returns>Control response.</returns>
  147. [HttpPost("{serverId}/ConnectionManager/Control")]
  148. [ProducesResponseType(StatusCodes.Status200OK)]
  149. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  150. [Produces(MediaTypeNames.Text.Xml)]
  151. [ProducesFile(MediaTypeNames.Text.Xml)]
  152. public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
  153. {
  154. return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
  155. }
  156. /// <summary>
  157. /// Process a media receiver registrar control request.
  158. /// </summary>
  159. /// <param name="serverId">Server UUID.</param>
  160. /// <response code="200">Request processed.</response>
  161. /// <response code="503">DLNA is disabled.</response>
  162. /// <returns>Control response.</returns>
  163. [HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
  164. [ProducesResponseType(StatusCodes.Status200OK)]
  165. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  166. [Produces(MediaTypeNames.Text.Xml)]
  167. [ProducesFile(MediaTypeNames.Text.Xml)]
  168. public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
  169. {
  170. return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
  171. }
  172. /// <summary>
  173. /// Processes an event subscription request.
  174. /// </summary>
  175. /// <param name="serverId">Server UUID.</param>
  176. /// <response code="200">Request processed.</response>
  177. /// <response code="503">DLNA is disabled.</response>
  178. /// <returns>Event subscription response.</returns>
  179. [HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")]
  180. [HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")]
  181. [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
  182. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  183. [ProducesResponseType(StatusCodes.Status200OK)]
  184. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  185. [Produces(MediaTypeNames.Text.Xml)]
  186. [ProducesFile(MediaTypeNames.Text.Xml)]
  187. public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId)
  188. {
  189. return ProcessEventRequest(_mediaReceiverRegistrar);
  190. }
  191. /// <summary>
  192. /// Processes an event subscription request.
  193. /// </summary>
  194. /// <param name="serverId">Server UUID.</param>
  195. /// <response code="200">Request processed.</response>
  196. /// <response code="503">DLNA is disabled.</response>
  197. /// <returns>Event subscription response.</returns>
  198. [HttpSubscribe("{serverId}/ContentDirectory/Events")]
  199. [HttpUnsubscribe("{serverId}/ContentDirectory/Events")]
  200. [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
  201. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  202. [ProducesResponseType(StatusCodes.Status200OK)]
  203. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  204. [Produces(MediaTypeNames.Text.Xml)]
  205. [ProducesFile(MediaTypeNames.Text.Xml)]
  206. public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId)
  207. {
  208. return ProcessEventRequest(_contentDirectory);
  209. }
  210. /// <summary>
  211. /// Processes an event subscription request.
  212. /// </summary>
  213. /// <param name="serverId">Server UUID.</param>
  214. /// <response code="200">Request processed.</response>
  215. /// <response code="503">DLNA is disabled.</response>
  216. /// <returns>Event subscription response.</returns>
  217. [HttpSubscribe("{serverId}/ConnectionManager/Events")]
  218. [HttpUnsubscribe("{serverId}/ConnectionManager/Events")]
  219. [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
  220. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  221. [ProducesResponseType(StatusCodes.Status200OK)]
  222. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  223. [Produces(MediaTypeNames.Text.Xml)]
  224. [ProducesFile(MediaTypeNames.Text.Xml)]
  225. public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId)
  226. {
  227. return ProcessEventRequest(_connectionManager);
  228. }
  229. /// <summary>
  230. /// Gets a server icon.
  231. /// </summary>
  232. /// <param name="serverId">Server UUID.</param>
  233. /// <param name="fileName">The icon filename.</param>
  234. /// <response code="200">Request processed.</response>
  235. /// <response code="404">Not Found.</response>
  236. /// <response code="503">DLNA is disabled.</response>
  237. /// <returns>Icon stream.</returns>
  238. [HttpGet("{serverId}/icons/{fileName}")]
  239. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  240. [ProducesResponseType(StatusCodes.Status200OK)]
  241. [ProducesResponseType(StatusCodes.Status404NotFound)]
  242. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  243. [ProducesImageFile]
  244. public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
  245. {
  246. return GetIconInternal(fileName);
  247. }
  248. /// <summary>
  249. /// Gets a server icon.
  250. /// </summary>
  251. /// <param name="fileName">The icon filename.</param>
  252. /// <returns>Icon stream.</returns>
  253. /// <response code="200">Request processed.</response>
  254. /// <response code="404">Not Found.</response>
  255. /// <response code="503">DLNA is disabled.</response>
  256. [HttpGet("icons/{fileName}")]
  257. [ProducesResponseType(StatusCodes.Status200OK)]
  258. [ProducesResponseType(StatusCodes.Status404NotFound)]
  259. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  260. [ProducesImageFile]
  261. public ActionResult GetIcon([FromRoute, Required] string fileName)
  262. {
  263. return GetIconInternal(fileName);
  264. }
  265. private ActionResult GetIconInternal(string fileName)
  266. {
  267. var icon = _dlnaManager.GetIcon(fileName);
  268. if (icon is null)
  269. {
  270. return NotFound();
  271. }
  272. return File(icon.Stream, MimeTypes.GetMimeType(fileName));
  273. }
  274. private string GetAbsoluteUri()
  275. {
  276. return $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Request.Path}";
  277. }
  278. private Task<ControlResponse> ProcessControlRequestInternalAsync(string id, Stream requestStream, IUpnpService service)
  279. {
  280. return service.ProcessControlRequestAsync(new ControlRequest(Request.Headers)
  281. {
  282. InputXml = requestStream,
  283. TargetServerUuId = id,
  284. RequestedUrl = GetAbsoluteUri()
  285. });
  286. }
  287. private EventSubscriptionResponse ProcessEventRequest(IDlnaEventManager dlnaEventManager)
  288. {
  289. var subscriptionId = Request.Headers["SID"];
  290. if (string.Equals(Request.Method, "subscribe", StringComparison.OrdinalIgnoreCase))
  291. {
  292. var notificationType = Request.Headers["NT"];
  293. var callback = Request.Headers["CALLBACK"];
  294. var timeoutString = Request.Headers["TIMEOUT"];
  295. if (string.IsNullOrEmpty(notificationType))
  296. {
  297. return dlnaEventManager.RenewEventSubscription(
  298. subscriptionId,
  299. notificationType,
  300. timeoutString,
  301. callback);
  302. }
  303. return dlnaEventManager.CreateEventSubscription(notificationType, timeoutString, callback);
  304. }
  305. return dlnaEventManager.CancelEventSubscription(subscriptionId);
  306. }
  307. }