DlnaServerController.cs 15 KB

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