From f1aba6b95230474d47c580071370c7dbd00eba13 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 9 Nov 2023 14:45:16 -0500 Subject: [PATCH] Remove Emby.Dlna --- Emby.Dlna/Common/Argument.cs | 23 - Emby.Dlna/Common/DeviceIcon.cs | 41 - Emby.Dlna/Common/DeviceService.cs | 36 - Emby.Dlna/Common/ServiceAction.cs | 31 - Emby.Dlna/Common/StateVariable.cs | 34 - Emby.Dlna/Configuration/DlnaOptions.cs | 92 -- Emby.Dlna/ConfigurationExtension.cs | 15 - .../ConnectionManagerService.cs | 53 - .../ConnectionManagerXmlBuilder.cs | 119 -- Emby.Dlna/ConnectionManager/ControlHandler.cs | 55 - .../ServiceActionListBuilder.cs | 234 --- .../ContentDirectoryService.cs | 173 --- .../ContentDirectoryXmlBuilder.cs | 159 --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 1250 ---------------- Emby.Dlna/ContentDirectory/ServerItem.cs | 39 - .../ServiceActionListBuilder.cs | 415 ------ Emby.Dlna/ContentDirectory/StubType.cs | 30 - Emby.Dlna/ControlRequest.cs | 25 - Emby.Dlna/ControlResponse.cs | 28 - Emby.Dlna/Didl/DidlBuilder.cs | 1266 ----------------- Emby.Dlna/Didl/Filter.cs | 28 - Emby.Dlna/Didl/StringWriterWithEncoding.cs | 58 - Emby.Dlna/DlnaConfigurationFactory.cs | 23 - Emby.Dlna/DlnaManager.cs | 491 ------- Emby.Dlna/Emby.Dlna.csproj | 90 -- Emby.Dlna/EventSubscriptionResponse.cs | 22 - Emby.Dlna/Eventing/DlnaEventManager.cs | 183 --- Emby.Dlna/Eventing/EventSubscription.cs | 35 - .../DlnaServiceCollectionExtensions.cs | 72 - Emby.Dlna/IConnectionManager.cs | 8 - Emby.Dlna/IContentDirectory.cs | 8 - Emby.Dlna/IDlnaEventManager.cs | 34 - Emby.Dlna/IMediaReceiverRegistrar.cs | 8 - Emby.Dlna/IUpnpService.cs | 22 - Emby.Dlna/Images/logo120.jpg | Bin 5337 -> 0 bytes Emby.Dlna/Images/logo120.png | Bin 6201 -> 0 bytes Emby.Dlna/Images/logo240.jpg | Bin 11483 -> 0 bytes Emby.Dlna/Images/logo240.png | Bin 13339 -> 0 bytes Emby.Dlna/Images/logo48.jpg | Bin 1839 -> 0 bytes Emby.Dlna/Images/logo48.png | Bin 2263 -> 0 bytes Emby.Dlna/Images/people48.jpg | Bin 740 -> 0 bytes Emby.Dlna/Images/people48.png | Bin 278 -> 0 bytes Emby.Dlna/Images/people480.jpg | Bin 5181 -> 0 bytes Emby.Dlna/Images/people480.png | Bin 2105 -> 0 bytes Emby.Dlna/Main/DlnaHost.cs | 387 ----- .../MediaReceiverRegistrar/ControlHandler.cs | 58 - .../MediaReceiverRegistrarService.cs | 46 - .../MediaReceiverRegistrarXmlBuilder.cs | 90 -- .../ServiceActionListBuilder.cs | 187 --- Emby.Dlna/PlayTo/Device.cs | 1264 ---------------- Emby.Dlna/PlayTo/DeviceInfo.cs | 66 - Emby.Dlna/PlayTo/DlnaHttpClient.cs | 137 -- Emby.Dlna/PlayTo/MediaChangedEventArgs.cs | 19 - Emby.Dlna/PlayTo/PlayToController.cs | 980 ------------- Emby.Dlna/PlayTo/PlayToManager.cs | 258 ---- Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs | 16 - Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs | 16 - Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs | 16 - Emby.Dlna/PlayTo/PlaylistItem.cs | 19 - Emby.Dlna/PlayTo/PlaylistItemFactory.cs | 70 - Emby.Dlna/PlayTo/TransportCommands.cs | 181 --- Emby.Dlna/PlayTo/TransportState.cs | 16 - Emby.Dlna/PlayTo/UpnpContainer.cs | 25 - Emby.Dlna/PlayTo/uBaseObject.cs | 63 - Emby.Dlna/PlayTo/uPnpNamespaces.cs | 67 - Emby.Dlna/Profiles/DefaultProfile.cs | 179 --- Emby.Dlna/Profiles/Xml/Default.xml | 61 - Emby.Dlna/Profiles/Xml/Denon AVR.xml | 68 - Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml | 67 - Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml | 96 -- Emby.Dlna/Profiles/Xml/LG Smart TV.xml | 92 -- Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml | 54 - Emby.Dlna/Profiles/Xml/Marantz.xml | 62 - Emby.Dlna/Profiles/Xml/MediaMonkey.xml | 62 - Emby.Dlna/Profiles/Xml/Panasonic Viera.xml | 87 -- Emby.Dlna/Profiles/Xml/Popcorn Hour.xml | 92 -- Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml | 128 -- Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml | 60 - .../Profiles/Xml/Sony Blu-ray Player 2013.xml | 87 -- .../Profiles/Xml/Sony Blu-ray Player 2014.xml | 87 -- .../Profiles/Xml/Sony Blu-ray Player 2015.xml | 85 -- .../Profiles/Xml/Sony Blu-ray Player 2016.xml | 85 -- .../Profiles/Xml/Sony Blu-ray Player.xml | 115 -- Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml | 133 -- Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml | 139 -- Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml | 115 -- Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml | 114 -- Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml | 114 -- Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml | 105 -- Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml | 108 -- Emby.Dlna/Profiles/Xml/WDTV Live.xml | 94 -- Emby.Dlna/Profiles/Xml/Xbox One.xml | 126 -- Emby.Dlna/Profiles/Xml/foobar2000.xml | 67 - Emby.Dlna/Properties/AssemblyInfo.cs | 28 - Emby.Dlna/Server/DescriptionXmlBuilder.cs | 358 ----- Emby.Dlna/Service/BaseControlHandler.cs | 242 ---- Emby.Dlna/Service/BaseService.cs | 37 - Emby.Dlna/Service/ControlErrorHandler.cs | 52 - Emby.Dlna/Service/ServiceXmlBuilder.cs | 109 -- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 151 -- Emby.Dlna/Ssdp/SsdpExtensions.cs | 27 - .../ApplicationHost.cs | 4 - .../Emby.Server.Implementations.csproj | 1 - Emby.Server.Implementations/SystemManager.cs | 1 - Jellyfin.Server/Startup.cs | 2 - Jellyfin.sln | 6 - 106 files changed, 12881 deletions(-) delete mode 100644 Emby.Dlna/Common/Argument.cs delete mode 100644 Emby.Dlna/Common/DeviceIcon.cs delete mode 100644 Emby.Dlna/Common/DeviceService.cs delete mode 100644 Emby.Dlna/Common/ServiceAction.cs delete mode 100644 Emby.Dlna/Common/StateVariable.cs delete mode 100644 Emby.Dlna/Configuration/DlnaOptions.cs delete mode 100644 Emby.Dlna/ConfigurationExtension.cs delete mode 100644 Emby.Dlna/ConnectionManager/ConnectionManagerService.cs delete mode 100644 Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs delete mode 100644 Emby.Dlna/ConnectionManager/ControlHandler.cs delete mode 100644 Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs delete mode 100644 Emby.Dlna/ContentDirectory/ContentDirectoryService.cs delete mode 100644 Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs delete mode 100644 Emby.Dlna/ContentDirectory/ControlHandler.cs delete mode 100644 Emby.Dlna/ContentDirectory/ServerItem.cs delete mode 100644 Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs delete mode 100644 Emby.Dlna/ContentDirectory/StubType.cs delete mode 100644 Emby.Dlna/ControlRequest.cs delete mode 100644 Emby.Dlna/ControlResponse.cs delete mode 100644 Emby.Dlna/Didl/DidlBuilder.cs delete mode 100644 Emby.Dlna/Didl/Filter.cs delete mode 100644 Emby.Dlna/Didl/StringWriterWithEncoding.cs delete mode 100644 Emby.Dlna/DlnaConfigurationFactory.cs delete mode 100644 Emby.Dlna/DlnaManager.cs delete mode 100644 Emby.Dlna/Emby.Dlna.csproj delete mode 100644 Emby.Dlna/EventSubscriptionResponse.cs delete mode 100644 Emby.Dlna/Eventing/DlnaEventManager.cs delete mode 100644 Emby.Dlna/Eventing/EventSubscription.cs delete mode 100644 Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs delete mode 100644 Emby.Dlna/IConnectionManager.cs delete mode 100644 Emby.Dlna/IContentDirectory.cs delete mode 100644 Emby.Dlna/IDlnaEventManager.cs delete mode 100644 Emby.Dlna/IMediaReceiverRegistrar.cs delete mode 100644 Emby.Dlna/IUpnpService.cs delete mode 100644 Emby.Dlna/Images/logo120.jpg delete mode 100644 Emby.Dlna/Images/logo120.png delete mode 100644 Emby.Dlna/Images/logo240.jpg delete mode 100644 Emby.Dlna/Images/logo240.png delete mode 100644 Emby.Dlna/Images/logo48.jpg delete mode 100644 Emby.Dlna/Images/logo48.png delete mode 100644 Emby.Dlna/Images/people48.jpg delete mode 100644 Emby.Dlna/Images/people48.png delete mode 100644 Emby.Dlna/Images/people480.jpg delete mode 100644 Emby.Dlna/Images/people480.png delete mode 100644 Emby.Dlna/Main/DlnaHost.cs delete mode 100644 Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs delete mode 100644 Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs delete mode 100644 Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs delete mode 100644 Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs delete mode 100644 Emby.Dlna/PlayTo/Device.cs delete mode 100644 Emby.Dlna/PlayTo/DeviceInfo.cs delete mode 100644 Emby.Dlna/PlayTo/DlnaHttpClient.cs delete mode 100644 Emby.Dlna/PlayTo/MediaChangedEventArgs.cs delete mode 100644 Emby.Dlna/PlayTo/PlayToController.cs delete mode 100644 Emby.Dlna/PlayTo/PlayToManager.cs delete mode 100644 Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs delete mode 100644 Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs delete mode 100644 Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs delete mode 100644 Emby.Dlna/PlayTo/PlaylistItem.cs delete mode 100644 Emby.Dlna/PlayTo/PlaylistItemFactory.cs delete mode 100644 Emby.Dlna/PlayTo/TransportCommands.cs delete mode 100644 Emby.Dlna/PlayTo/TransportState.cs delete mode 100644 Emby.Dlna/PlayTo/UpnpContainer.cs delete mode 100644 Emby.Dlna/PlayTo/uBaseObject.cs delete mode 100644 Emby.Dlna/PlayTo/uPnpNamespaces.cs delete mode 100644 Emby.Dlna/Profiles/DefaultProfile.cs delete mode 100644 Emby.Dlna/Profiles/Xml/Default.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Denon AVR.xml delete mode 100644 Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml delete mode 100644 Emby.Dlna/Profiles/Xml/LG Smart TV.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Marantz.xml delete mode 100644 Emby.Dlna/Profiles/Xml/MediaMonkey.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Panasonic Viera.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Popcorn Hour.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml delete mode 100644 Emby.Dlna/Profiles/Xml/WDTV Live.xml delete mode 100644 Emby.Dlna/Profiles/Xml/Xbox One.xml delete mode 100644 Emby.Dlna/Profiles/Xml/foobar2000.xml delete mode 100644 Emby.Dlna/Properties/AssemblyInfo.cs delete mode 100644 Emby.Dlna/Server/DescriptionXmlBuilder.cs delete mode 100644 Emby.Dlna/Service/BaseControlHandler.cs delete mode 100644 Emby.Dlna/Service/BaseService.cs delete mode 100644 Emby.Dlna/Service/ControlErrorHandler.cs delete mode 100644 Emby.Dlna/Service/ServiceXmlBuilder.cs delete mode 100644 Emby.Dlna/Ssdp/DeviceDiscovery.cs delete mode 100644 Emby.Dlna/Ssdp/SsdpExtensions.cs diff --git a/Emby.Dlna/Common/Argument.cs b/Emby.Dlna/Common/Argument.cs deleted file mode 100644 index e4e9c55e0d..0000000000 --- a/Emby.Dlna/Common/Argument.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Emby.Dlna.Common -{ - /// - /// DLNA Query parameter type, used when querying DLNA devices via SOAP. - /// - public class Argument - { - /// - /// Gets or sets name of the DLNA argument. - /// - public string Name { get; set; } = string.Empty; - - /// - /// Gets or sets the direction of the parameter. - /// - public string Direction { get; set; } = string.Empty; - - /// - /// Gets or sets the related DLNA state variable for this argument. - /// - public string RelatedStateVariable { get; set; } = string.Empty; - } -} diff --git a/Emby.Dlna/Common/DeviceIcon.cs b/Emby.Dlna/Common/DeviceIcon.cs deleted file mode 100644 index f9fd1dcec6..0000000000 --- a/Emby.Dlna/Common/DeviceIcon.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Globalization; - -namespace Emby.Dlna.Common -{ - /// - /// Defines the . - /// - public class DeviceIcon - { - /// - /// Gets or sets the Url. - /// - public string Url { get; set; } = string.Empty; - - /// - /// Gets or sets the MimeType. - /// - public string MimeType { get; set; } = string.Empty; - - /// - /// Gets or sets the Width. - /// - public int Width { get; set; } - - /// - /// Gets or sets the Height. - /// - public int Height { get; set; } - - /// - /// Gets or sets the Depth. - /// - public string Depth { get; set; } = string.Empty; - - /// - public override string ToString() - { - return string.Format(CultureInfo.InvariantCulture, "{0}x{1}", Height, Width); - } - } -} diff --git a/Emby.Dlna/Common/DeviceService.cs b/Emby.Dlna/Common/DeviceService.cs deleted file mode 100644 index c1369558ec..0000000000 --- a/Emby.Dlna/Common/DeviceService.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace Emby.Dlna.Common -{ - /// - /// Defines the . - /// - public class DeviceService - { - /// - /// Gets or sets the Service Type. - /// - public string ServiceType { get; set; } = string.Empty; - - /// - /// Gets or sets the Service Id. - /// - public string ServiceId { get; set; } = string.Empty; - - /// - /// Gets or sets the Scpd Url. - /// - public string ScpdUrl { get; set; } = string.Empty; - - /// - /// Gets or sets the Control Url. - /// - public string ControlUrl { get; set; } = string.Empty; - - /// - /// Gets or sets the EventSubUrl. - /// - public string EventSubUrl { get; set; } = string.Empty; - - /// - public override string ToString() => ServiceId; - } -} diff --git a/Emby.Dlna/Common/ServiceAction.cs b/Emby.Dlna/Common/ServiceAction.cs deleted file mode 100644 index 02b81a0aa7..0000000000 --- a/Emby.Dlna/Common/ServiceAction.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; - -namespace Emby.Dlna.Common -{ - /// - /// Defines the . - /// - public class ServiceAction - { - /// - /// Initializes a new instance of the class. - /// - public ServiceAction() - { - ArgumentList = new List(); - } - - /// - /// Gets or sets the name of the action. - /// - public string Name { get; set; } = string.Empty; - - /// - /// Gets the ArgumentList. - /// - public List ArgumentList { get; } - - /// - public override string ToString() => Name; - } -} diff --git a/Emby.Dlna/Common/StateVariable.cs b/Emby.Dlna/Common/StateVariable.cs deleted file mode 100644 index fd733e0853..0000000000 --- a/Emby.Dlna/Common/StateVariable.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Emby.Dlna.Common -{ - /// - /// Defines the . - /// - public class StateVariable - { - /// - /// Gets or sets the name of the state variable. - /// - public string Name { get; set; } = string.Empty; - - /// - /// Gets or sets the data type of the state variable. - /// - public string DataType { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether it sends events. - /// - public bool SendsEvents { get; set; } - - /// - /// Gets or sets the allowed values range. - /// - public IReadOnlyList AllowedValues { get; set; } = Array.Empty(); - - /// - public override string ToString() => Name; - } -} diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs deleted file mode 100644 index f233468de3..0000000000 --- a/Emby.Dlna/Configuration/DlnaOptions.cs +++ /dev/null @@ -1,92 +0,0 @@ -#pragma warning disable CS1591 - -namespace Emby.Dlna.Configuration -{ - /// - /// The DlnaOptions class contains the user definable parameters for the dlna subsystems. - /// - public class DlnaOptions - { - /// - /// Initializes a new instance of the class. - /// - public DlnaOptions() - { - EnablePlayTo = true; - EnableServer = false; - BlastAliveMessages = true; - SendOnlyMatchedHost = true; - ClientDiscoveryIntervalSeconds = 60; - AliveMessageIntervalSeconds = 180; - } - - /// - /// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna playTo subsystem. - /// - public bool EnablePlayTo { get; set; } - - /// - /// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna server subsystem. - /// - public bool EnableServer { get; set; } - - /// - /// Gets or sets a value indicating whether detailed dlna server logs are sent to the console/log. - /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work. - /// - public bool EnableDebugLog { get; set; } - - /// - /// Gets or sets a value indicating whether whether detailed playTo debug logs are sent to the console/log. - /// If the setting "Emby.Dlna.PlayTo": "Debug" msut be set in logging.default.json for this property to work. - /// - public bool EnablePlayToTracing { get; set; } - - /// - /// Gets or sets the ssdp client discovery interval time (in seconds). - /// This is the time after which the server will send a ssdp search request. - /// - public int ClientDiscoveryIntervalSeconds { get; set; } - - /// - /// Gets or sets the frequency at which ssdp alive notifications are transmitted. - /// - public int AliveMessageIntervalSeconds { get; set; } - - /// - /// Gets or sets the frequency at which ssdp alive notifications are transmitted. MIGRATING - TO BE REMOVED ONCE WEB HAS BEEN ALTERED. - /// - public int BlastAliveMessageIntervalSeconds - { - get - { - return AliveMessageIntervalSeconds; - } - - set - { - AliveMessageIntervalSeconds = value; - } - } - - /// - /// Gets or sets the default user account that the dlna server uses. - /// - public string? DefaultUserId { get; set; } - - /// - /// Gets or sets a value indicating whether playTo device profiles should be created. - /// - public bool AutoCreatePlayToProfiles { get; set; } - - /// - /// Gets or sets a value indicating whether to blast alive messages. - /// - public bool BlastAliveMessages { get; set; } = true; - - /// - /// gets or sets a value indicating whether to send only matched host. - /// - public bool SendOnlyMatchedHost { get; set; } = true; - } -} diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs deleted file mode 100644 index 3ca43052a4..0000000000 --- a/Emby.Dlna/ConfigurationExtension.cs +++ /dev/null @@ -1,15 +0,0 @@ -#pragma warning disable CS1591 - -using Emby.Dlna.Configuration; -using MediaBrowser.Common.Configuration; - -namespace Emby.Dlna -{ - public static class ConfigurationExtension - { - public static DlnaOptions GetDlnaConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration("dlna"); - } - } -} diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs deleted file mode 100644 index 916044a0cc..0000000000 --- a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs +++ /dev/null @@ -1,53 +0,0 @@ -#pragma warning disable CS1591 - -using System.Net.Http; -using System.Threading.Tasks; -using Emby.Dlna.Service; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dlna; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.ConnectionManager -{ - /// - /// Defines the . - /// - public class ConnectionManagerService : BaseService, IConnectionManager - { - private readonly IDlnaManager _dlna; - private readonly IServerConfigurationManager _config; - - /// - /// Initializes a new instance of the class. - /// - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance.. - /// The for use with the instance.. - public ConnectionManagerService( - IDlnaManager dlna, - IServerConfigurationManager config, - ILogger logger, - IHttpClientFactory httpClientFactory) - : base(logger, httpClientFactory) - { - _dlna = dlna; - _config = config; - } - - /// - public string GetServiceXml() - { - return ConnectionManagerXmlBuilder.GetXml(); - } - - /// - public Task ProcessControlRequestAsync(ControlRequest request) - { - var profile = _dlna.GetProfile(request.Headers) ?? - _dlna.GetDefaultProfile(); - - return new ControlHandler(_config, Logger, profile).ProcessControlRequestAsync(request); - } - } -} diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs deleted file mode 100644 index db1190ae7c..0000000000 --- a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs +++ /dev/null @@ -1,119 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using Emby.Dlna.Common; -using Emby.Dlna.Service; - -namespace Emby.Dlna.ConnectionManager -{ - /// - /// Defines the . - /// - public static class ConnectionManagerXmlBuilder - { - /// - /// Gets the ConnectionManager:1 service template. - /// See http://upnp.org/specs/av/UPnP-av-ConnectionManager-v1-Service.pdf. - /// - /// An XML description of this service. - public static string GetXml() - { - return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); - } - - /// - /// Get the list of state variables for this invocation. - /// - /// The . - private static IEnumerable GetStateVariables() - { - return new StateVariable[] - { - new StateVariable - { - Name = "SourceProtocolInfo", - DataType = "string", - SendsEvents = true - }, - - new StateVariable - { - Name = "SinkProtocolInfo", - DataType = "string", - SendsEvents = true - }, - - new StateVariable - { - Name = "CurrentConnectionIDs", - DataType = "string", - SendsEvents = true - }, - - new StateVariable - { - Name = "A_ARG_TYPE_ConnectionStatus", - DataType = "string", - SendsEvents = false, - - AllowedValues = new[] - { - "OK", - "ContentFormatMismatch", - "InsufficientBandwidth", - "UnreliableChannel", - "Unknown" - } - }, - - new StateVariable - { - Name = "A_ARG_TYPE_ConnectionManager", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_Direction", - DataType = "string", - SendsEvents = false, - - AllowedValues = new[] - { - "Output", - "Input" - } - }, - - new StateVariable - { - Name = "A_ARG_TYPE_ProtocolInfo", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_ConnectionID", - DataType = "ui4", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_AVTransportID", - DataType = "ui4", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_RcsID", - DataType = "ui4", - SendsEvents = false - } - }; - } - } -} diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs deleted file mode 100644 index 1a1790ee6a..0000000000 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Xml; -using Emby.Dlna.Service; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Dlna; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.ConnectionManager -{ - /// - /// Defines the . - /// - public class ControlHandler : BaseControlHandler - { - private readonly DeviceProfile _profile; - - /// - /// Initializes a new instance of the class. - /// - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile) - : base(config, logger) - { - _profile = profile; - } - - /// - protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter) - { - if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase)) - { - HandleGetProtocolInfo(xmlWriter); - return; - } - - throw new ResourceNotFoundException("Unexpected control request name: " + methodName); - } - - /// - /// Builds the response to the GetProtocolInfo request. - /// - /// The . - private void HandleGetProtocolInfo(XmlWriter xmlWriter) - { - xmlWriter.WriteElementString("Source", _profile.ProtocolInfo); - xmlWriter.WriteElementString("Sink", string.Empty); - } - } -} diff --git a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs deleted file mode 100644 index 542c7bfb4b..0000000000 --- a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs +++ /dev/null @@ -1,234 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using Emby.Dlna.Common; - -namespace Emby.Dlna.ConnectionManager -{ - /// - /// Defines the . - /// - public static class ServiceActionListBuilder - { - /// - /// Returns an enumerable of the ConnectionManagar:1 DLNA actions. - /// - /// An . - public static IEnumerable GetActions() - { - var list = new List - { - GetCurrentConnectionInfo(), - GetProtocolInfo(), - GetCurrentConnectionIDs(), - ConnectionComplete(), - PrepareForConnection() - }; - - return list; - } - - /// - /// Returns the action details for "PrepareForConnection". - /// - /// The . - private static ServiceAction PrepareForConnection() - { - var action = new ServiceAction - { - Name = "PrepareForConnection" - }; - - action.ArgumentList.Add(new Argument - { - Name = "RemoteProtocolInfo", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PeerConnectionManager", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ConnectionManager" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PeerConnectionID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Direction", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Direction" - }); - - action.ArgumentList.Add(new Argument - { - Name = "ConnectionID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "AVTransportID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_AVTransportID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RcsID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_RcsID" - }); - - return action; - } - - /// - /// Returns the action details for "GetCurrentConnectionInfo". - /// - /// The . - private static ServiceAction GetCurrentConnectionInfo() - { - var action = new ServiceAction - { - Name = "GetCurrentConnectionInfo" - }; - - action.ArgumentList.Add(new Argument - { - Name = "ConnectionID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RcsID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_RcsID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "AVTransportID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_AVTransportID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "ProtocolInfo", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PeerConnectionManager", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ConnectionManager" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PeerConnectionID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Direction", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Direction" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Status", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_ConnectionStatus" - }); - - return action; - } - - /// - /// Returns the action details for "GetProtocolInfo". - /// - /// The . - private static ServiceAction GetProtocolInfo() - { - var action = new ServiceAction - { - Name = "GetProtocolInfo" - }; - - action.ArgumentList.Add(new Argument - { - Name = "Source", - Direction = "out", - RelatedStateVariable = "SourceProtocolInfo" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Sink", - Direction = "out", - RelatedStateVariable = "SinkProtocolInfo" - }); - - return action; - } - - /// - /// Returns the action details for "GetCurrentConnectionIDs". - /// - /// The . - private static ServiceAction GetCurrentConnectionIDs() - { - var action = new ServiceAction - { - Name = "GetCurrentConnectionIDs" - }; - - action.ArgumentList.Add(new Argument - { - Name = "ConnectionIDs", - Direction = "out", - RelatedStateVariable = "CurrentConnectionIDs" - }); - - return action; - } - - /// - /// Returns the action details for "ConnectionComplete". - /// - /// The . - private static ServiceAction ConnectionComplete() - { - var action = new ServiceAction - { - Name = "ConnectionComplete" - }; - - action.ArgumentList.Add(new Argument - { - Name = "ConnectionID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ConnectionID" - }); - - return action; - } - } -} diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs deleted file mode 100644 index 389e971a66..0000000000 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs +++ /dev/null @@ -1,173 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Emby.Dlna.Service; -using Jellyfin.Data.Entities; -using Jellyfin.Data.Enums; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Globalization; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.ContentDirectory -{ - /// - /// Defines the . - /// - public class ContentDirectoryService : BaseService, IContentDirectory - { - private readonly ILibraryManager _libraryManager; - private readonly IImageProcessor _imageProcessor; - private readonly IUserDataManager _userDataManager; - private readonly IDlnaManager _dlna; - private readonly IServerConfigurationManager _config; - private readonly IUserManager _userManager; - private readonly ILocalizationManager _localization; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IUserViewManager _userViewManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly ITVSeriesManager _tvSeriesManager; - - /// - /// Initializes a new instance of the class. - /// - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - /// The to use in the instance. - public ContentDirectoryService( - IDlnaManager dlna, - IUserDataManager userDataManager, - IImageProcessor imageProcessor, - ILibraryManager libraryManager, - IServerConfigurationManager config, - IUserManager userManager, - ILogger logger, - IHttpClientFactory httpClient, - ILocalizationManager localization, - IMediaSourceManager mediaSourceManager, - IUserViewManager userViewManager, - IMediaEncoder mediaEncoder, - ITVSeriesManager tvSeriesManager) - : base(logger, httpClient) - { - _dlna = dlna; - _userDataManager = userDataManager; - _imageProcessor = imageProcessor; - _libraryManager = libraryManager; - _config = config; - _userManager = userManager; - _localization = localization; - _mediaSourceManager = mediaSourceManager; - _userViewManager = userViewManager; - _mediaEncoder = mediaEncoder; - _tvSeriesManager = tvSeriesManager; - } - - /// - /// Gets the system id. (A unique id which changes on when our definition changes.) - /// - private static int SystemUpdateId - { - get - { - var now = DateTime.UtcNow; - - return now.Year + now.DayOfYear + now.Hour; - } - } - - /// - public string GetServiceXml() - { - return ContentDirectoryXmlBuilder.GetXml(); - } - - /// - public Task ProcessControlRequestAsync(ControlRequest request) - { - ArgumentNullException.ThrowIfNull(request); - - var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile(); - - var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase)); - - var user = GetUser(profile); - - return new ControlHandler( - Logger, - _libraryManager, - profile, - serverAddress, - null, - _imageProcessor, - _userDataManager, - user, - SystemUpdateId, - _config, - _localization, - _mediaSourceManager, - _userViewManager, - _mediaEncoder, - _tvSeriesManager) - .ProcessControlRequestAsync(request); - } - - /// - /// Get the user stored in the device profile. - /// - /// The . - /// The . - private User? GetUser(DeviceProfile profile) - { - if (!string.IsNullOrEmpty(profile.UserId)) - { - var user = _userManager.GetUserById(Guid.Parse(profile.UserId)); - - if (user is not null) - { - return user; - } - } - - var userId = _config.GetDlnaConfiguration().DefaultUserId; - - if (!string.IsNullOrEmpty(userId)) - { - var user = _userManager.GetUserById(Guid.Parse(userId)); - - if (user is not null) - { - return user; - } - } - - foreach (var user in _userManager.Users) - { - if (user.HasPermission(PermissionKind.IsAdministrator)) - { - return user; - } - } - - return _userManager.Users.FirstOrDefault(); - } - } -} diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs deleted file mode 100644 index 9af28aa7cb..0000000000 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs +++ /dev/null @@ -1,159 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using Emby.Dlna.Common; -using Emby.Dlna.Service; - -namespace Emby.Dlna.ContentDirectory -{ - /// - /// Defines the . - /// - public static class ContentDirectoryXmlBuilder - { - /// - /// Gets the ContentDirectory:1 service template. - /// See http://upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf. - /// - /// An XML description of this service. - public static string GetXml() - { - return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); - } - - /// - /// Get the list of state variables for this invocation. - /// - /// The . - private static IEnumerable GetStateVariables() - { - return new StateVariable[] - { - new StateVariable - { - Name = "A_ARG_TYPE_Filter", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_SortCriteria", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_Index", - DataType = "ui4", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_Count", - DataType = "ui4", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_UpdateID", - DataType = "ui4", - SendsEvents = false - }, - - new StateVariable - { - Name = "SearchCapabilities", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "SortCapabilities", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "SystemUpdateID", - DataType = "ui4", - SendsEvents = true - }, - - new StateVariable - { - Name = "A_ARG_TYPE_SearchCriteria", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_Result", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_ObjectID", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_BrowseFlag", - DataType = "string", - SendsEvents = false, - - AllowedValues = new[] - { - "BrowseMetadata", - "BrowseDirectChildren" - } - }, - - new StateVariable - { - Name = "A_ARG_TYPE_BrowseLetter", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_CategoryType", - DataType = "ui4", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_RID", - DataType = "ui4", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_PosSec", - DataType = "ui4", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_Featurelist", - DataType = "string", - SendsEvents = false - } - }; - } - } -} diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs deleted file mode 100644 index 99068826d9..0000000000 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ /dev/null @@ -1,1250 +0,0 @@ -#nullable disable - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Xml; -using Emby.Dlna.Didl; -using Emby.Dlna.Service; -using Jellyfin.Data.Entities; -using Jellyfin.Data.Enums; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Querying; -using Microsoft.Extensions.Logging; -using Genre = MediaBrowser.Controller.Entities.Genre; - -namespace Emby.Dlna.ContentDirectory -{ - /// - /// Defines the . - /// - public class ControlHandler : BaseControlHandler - { - private const string NsDc = "http://purl.org/dc/elements/1.1/"; - private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; - private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/"; - private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/"; - - private readonly ILibraryManager _libraryManager; - private readonly IUserDataManager _userDataManager; - private readonly User _user; - private readonly IUserViewManager _userViewManager; - private readonly ITVSeriesManager _tvSeriesManager; - - private readonly int _systemUpdateId; - - private readonly DidlBuilder _didlBuilder; - - private readonly DeviceProfile _profile; - - /// - /// Initializes a new instance of the class. - /// - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The server address to use in this instance> for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The system id for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - public ControlHandler( - ILogger logger, - ILibraryManager libraryManager, - DeviceProfile profile, - string serverAddress, - string accessToken, - IImageProcessor imageProcessor, - IUserDataManager userDataManager, - User user, - int systemUpdateId, - IServerConfigurationManager config, - ILocalizationManager localization, - IMediaSourceManager mediaSourceManager, - IUserViewManager userViewManager, - IMediaEncoder mediaEncoder, - ITVSeriesManager tvSeriesManager) - : base(config, logger) - { - _libraryManager = libraryManager; - _userDataManager = userDataManager; - _user = user; - _systemUpdateId = systemUpdateId; - _userViewManager = userViewManager; - _tvSeriesManager = tvSeriesManager; - _profile = profile; - - _didlBuilder = new DidlBuilder( - profile, - user, - imageProcessor, - serverAddress, - accessToken, - userDataManager, - localization, - mediaSourceManager, - Logger, - mediaEncoder, - libraryManager); - } - - /// - protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter) - { - ArgumentNullException.ThrowIfNull(xmlWriter); - - ArgumentNullException.ThrowIfNull(methodParams); - - const string DeviceId = "test"; - - if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase)) - { - HandleGetSearchCapabilities(xmlWriter); - return; - } - - if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase)) - { - HandleGetSortCapabilities(xmlWriter); - return; - } - - if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase)) - { - HandleGetSortExtensionCapabilities(xmlWriter); - return; - } - - if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase)) - { - HandleGetSystemUpdateID(xmlWriter); - return; - } - - if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase)) - { - HandleBrowse(xmlWriter, methodParams, DeviceId); - return; - } - - if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase)) - { - HandleXGetFeatureList(xmlWriter); - return; - } - - if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase)) - { - HandleGetFeatureList(xmlWriter); - return; - } - - if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase)) - { - HandleXSetBookmark(methodParams); - return; - } - - if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase)) - { - HandleSearch(xmlWriter, methodParams, DeviceId); - return; - } - - if (string.Equals(methodName, "X_BrowseByLetter", StringComparison.OrdinalIgnoreCase)) - { - HandleXBrowseByLetter(xmlWriter, methodParams, DeviceId); - return; - } - - throw new ResourceNotFoundException("Unexpected control request name: " + methodName); - } - - /// - /// Adds a "XSetBookmark" element to the xml document. - /// - /// The method parameters. - private void HandleXSetBookmark(IReadOnlyDictionary sparams) - { - var id = sparams["ObjectID"]; - - var serverItem = GetItemFromObjectId(id); - - var item = serverItem.Item; - - var newbookmark = int.Parse(sparams["PosSecond"], CultureInfo.InvariantCulture); - - var userdata = _userDataManager.GetUserData(_user, item); - - userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks; - - _userDataManager.SaveUserData( - _user, - item, - userdata, - UserDataSaveReason.TogglePlayed, - CancellationToken.None); - } - - /// - /// Adds the "SearchCaps" element to the xml document. - /// - /// The . - private static void HandleGetSearchCapabilities(XmlWriter xmlWriter) - { - xmlWriter.WriteElementString( - "SearchCaps", - "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords"); - } - - /// - /// Adds the "SortCaps" element to the xml document. - /// - /// The . - private static void HandleGetSortCapabilities(XmlWriter xmlWriter) - { - xmlWriter.WriteElementString( - "SortCaps", - "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating"); - } - - /// - /// Adds the "SortExtensionCaps" element to the xml document. - /// - /// The . - private static void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter) - { - xmlWriter.WriteElementString( - "SortExtensionCaps", - "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating"); - } - - /// - /// Adds the "Id" element to the xml document. - /// - /// The . - private void HandleGetSystemUpdateID(XmlWriter xmlWriter) - { - xmlWriter.WriteElementString("Id", _systemUpdateId.ToString(CultureInfo.InvariantCulture)); - } - - /// - /// Adds the "FeatureList" element to the xml document. - /// - /// The . - private static void HandleGetFeatureList(XmlWriter xmlWriter) - { - xmlWriter.WriteElementString("FeatureList", WriteFeatureListXml()); - } - - /// - /// Adds the "FeatureList" element to the xml document. - /// - /// The . - private static void HandleXGetFeatureList(XmlWriter xmlWriter) - => HandleGetFeatureList(xmlWriter); - - /// - /// Builds a static feature list. - /// - /// The xml feature list. - private static string WriteFeatureListXml() - { - return "" - + "" - + "" - + "" - + "" - + "" - + "" - + ""; - } - - /// - /// Builds the "Browse" xml response. - /// - /// The . - /// The method parameters. - /// The device Id to use. - private void HandleBrowse(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId) - { - var id = sparams["ObjectID"]; - var flag = sparams["BrowseFlag"]; - var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); - var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty)); - - var provided = 0; - - // Default to null instead of 0 - // Upnp inspector sends 0 as requestedCount when it wants everything - int? requestedCount = null; - int? start = 0; - - if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out var requestedVal) && requestedVal > 0) - { - requestedCount = requestedVal; - } - - if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out var startVal) && startVal > 0) - { - start = startVal; - } - - int totalCount; - - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CloseOutput = false, - OmitXmlDeclaration = true, - ConformanceLevel = ConformanceLevel.Fragment - }; - - using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8)) - using (var writer = XmlWriter.Create(builder, settings)) - { - writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl); - - writer.WriteAttributeString("xmlns", "dc", null, NsDc); - writer.WriteAttributeString("xmlns", "dlna", null, NsDlna); - writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp); - - DidlBuilder.WriteXmlRootAttributes(_profile, writer); - - var serverItem = GetItemFromObjectId(id); - var item = serverItem.Item; - - if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal)) - { - totalCount = 1; - - if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue) - { - var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount); - - _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id); - } - else - { - _didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter); - } - - provided++; - } - else - { - var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount); - totalCount = childrenResult.TotalRecordCount; - - provided = childrenResult.Items.Count; - - foreach (var i in childrenResult.Items) - { - var childItem = i.Item; - var displayStubType = i.StubType; - - if (childItem.IsDisplayedAsFolder || displayStubType.HasValue) - { - var childCount = GetUserItems(childItem, displayStubType, _user, sortCriteria, null, 0) - .TotalRecordCount; - - _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter); - } - else - { - _didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter); - } - } - } - - writer.WriteFullEndElement(); - writer.Flush(); - xmlWriter.WriteElementString("Result", builder.ToString()); - } - - xmlWriter.WriteElementString("NumberReturned", provided.ToString(CultureInfo.InvariantCulture)); - xmlWriter.WriteElementString("TotalMatches", totalCount.ToString(CultureInfo.InvariantCulture)); - xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture)); - } - - /// - /// Builds the response to the "X_BrowseByLetter request. - /// - /// The . - /// The method parameters. - /// The device id. - private void HandleXBrowseByLetter(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId) - { - // TODO: Implement this method - HandleSearch(xmlWriter, sparams, deviceId); - } - - /// - /// Builds a response to the "Search" request. - /// - /// The xmlWriter. - /// The method parameters. - /// The deviceId. - private void HandleSearch(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId) - { - var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", string.Empty)); - var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty)); - var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); - - // sort example: dc:title, dc:date - - // Default to null instead of 0 - // Upnp inspector sends 0 as requestedCount when it wants everything - int? requestedCount = null; - int? start = 0; - - if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out var requestedVal) && requestedVal > 0) - { - requestedCount = requestedVal; - } - - if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out var startVal) && startVal > 0) - { - start = startVal; - } - - QueryResult childrenResult; - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CloseOutput = false, - OmitXmlDeclaration = true, - ConformanceLevel = ConformanceLevel.Fragment - }; - - using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8)) - using (var writer = XmlWriter.Create(builder, settings)) - { - writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl); - writer.WriteAttributeString("xmlns", "dc", null, NsDc); - writer.WriteAttributeString("xmlns", "dlna", null, NsDlna); - writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp); - - DidlBuilder.WriteXmlRootAttributes(_profile, writer); - - var serverItem = GetItemFromObjectId(sparams["ContainerID"]); - - var item = serverItem.Item; - - childrenResult = GetChildrenSorted(item, _user, searchCriteria, sortCriteria, start, requestedCount); - foreach (var i in childrenResult.Items) - { - if (i.IsDisplayedAsFolder) - { - var childCount = GetChildrenSorted(i, _user, searchCriteria, sortCriteria, null, 0) - .TotalRecordCount; - - _didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter); - } - else - { - _didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter); - } - } - - writer.WriteFullEndElement(); - writer.Flush(); - xmlWriter.WriteElementString("Result", builder.ToString()); - } - - xmlWriter.WriteElementString("NumberReturned", childrenResult.Items.Count.ToString(CultureInfo.InvariantCulture)); - xmlWriter.WriteElementString("TotalMatches", childrenResult.TotalRecordCount.ToString(CultureInfo.InvariantCulture)); - xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture)); - } - - /// - /// Returns the child items meeting the criteria. - /// - /// The . - /// The . - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private static QueryResult GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) - { - var folder = (Folder)item; - - MediaType[] mediaTypes = Array.Empty(); - bool? isFolder = null; - - switch (search.SearchType) - { - case SearchType.Audio: - mediaTypes = new[] { MediaType.Audio }; - isFolder = false; - break; - case SearchType.Video: - mediaTypes = new[] { MediaType.Video }; - isFolder = false; - break; - case SearchType.Image: - mediaTypes = new[] { MediaType.Photo }; - isFolder = false; - break; - case SearchType.Playlist: - case SearchType.MusicAlbum: - isFolder = true; - break; - } - - return folder.GetItems(new InternalItemsQuery - { - Limit = limit, - StartIndex = startIndex, - OrderBy = GetOrderBy(sort, folder.IsPreSorted), - User = user, - Recursive = true, - IsMissing = false, - ExcludeItemTypes = new[] { BaseItemKind.Book }, - IsFolder = isFolder, - MediaTypes = mediaTypes, - DtoOptions = GetDtoOptions() - }); - } - - /// - /// Returns a new DtoOptions object. - /// - /// The . - private static DtoOptions GetDtoOptions() - { - return new DtoOptions(true); - } - - /// - /// Returns the User items meeting the criteria. - /// - /// The . - /// The . - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit) - { - switch (item) - { - case MusicGenre: - return GetMusicGenreItems(item, user, sort, startIndex, limit); - case MusicArtist: - return GetMusicArtistItems(item, user, sort, startIndex, limit); - case Genre: - return GetGenreItems(item, user, sort, startIndex, limit); - } - - if (stubType != StubType.Folder && item is IHasCollectionType collectionFolder) - { - switch (collectionFolder.CollectionType) - { - case CollectionType.Music: - return GetMusicFolders(item, user, stubType, sort, startIndex, limit); - case CollectionType.Movies: - return GetMovieFolders(item, user, stubType, sort, startIndex, limit); - case CollectionType.TvShows: - return GetTvFolders(item, user, stubType, sort, startIndex, limit); - case CollectionType.Folders: - return GetFolders(user, startIndex, limit); - case CollectionType.LiveTv: - return GetLiveTvChannels(user, sort, startIndex, limit); - } - } - - if (stubType.HasValue && stubType.Value != StubType.Folder) - { - // TODO should this be doing something? - return new QueryResult(); - } - - var folder = (Folder)item; - - var query = new InternalItemsQuery(user) - { - Limit = limit, - StartIndex = startIndex, - IsVirtualItem = false, - ExcludeItemTypes = new[] { BaseItemKind.Book }, - IsPlaceHolder = false, - DtoOptions = GetDtoOptions(), - OrderBy = GetOrderBy(sort, folder.IsPreSorted) - }; - - var queryResult = folder.GetItems(query); - - return ToResult(startIndex, queryResult); - } - - /// - /// Returns the Live Tv Channels meeting the criteria. - /// - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit) - { - var query = new InternalItemsQuery(user) - { - StartIndex = startIndex, - Limit = limit, - IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel }, - OrderBy = GetOrderBy(sort, false) - }; - - var result = _libraryManager.GetItemsResult(query); - - return ToResult(startIndex, result); - } - - /// - /// Returns the music folders meeting the criteria. - /// - /// The . - /// The . - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetMusicFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) - { - var query = new InternalItemsQuery(user) - { - StartIndex = startIndex, - Limit = limit, - OrderBy = GetOrderBy(sort, false) - }; - - switch (stubType) - { - case StubType.Latest: - return GetLatest(item, query, BaseItemKind.Audio); - case StubType.Playlists: - return GetMusicPlaylists(query); - case StubType.Albums: - return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum); - case StubType.Artists: - return GetMusicArtists(item, query); - case StubType.AlbumArtists: - return GetMusicAlbumArtists(item, query); - case StubType.FavoriteAlbums: - return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true); - case StubType.FavoriteArtists: - return GetFavoriteArtists(item, query); - case StubType.FavoriteSongs: - return GetChildrenOfItem(item, query, BaseItemKind.Audio, true); - case StubType.Songs: - return GetChildrenOfItem(item, query, BaseItemKind.Audio); - case StubType.Genres: - return GetMusicGenres(item, query); - } - - var serverItems = new ServerItem[] - { - new(item, StubType.Latest), - new(item, StubType.Playlists), - new(item, StubType.Albums), - new(item, StubType.AlbumArtists), - new(item, StubType.Artists), - new(item, StubType.Songs), - new(item, StubType.Genres), - new(item, StubType.FavoriteArtists), - new(item, StubType.FavoriteAlbums), - new(item, StubType.FavoriteSongs) - }; - - if (limit < serverItems.Length) - { - serverItems = serverItems[..limit.Value]; - } - - return new QueryResult( - startIndex, - serverItems.Length, - serverItems); - } - - /// - /// Returns the movie folders meeting the criteria. - /// - /// The . - /// The . - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetMovieFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) - { - var query = new InternalItemsQuery(user) - { - StartIndex = startIndex, - Limit = limit, - OrderBy = GetOrderBy(sort, false) - }; - - switch (stubType) - { - case StubType.ContinueWatching: - return GetMovieContinueWatching(item, query); - case StubType.Latest: - return GetLatest(item, query, BaseItemKind.Movie); - case StubType.Movies: - return GetChildrenOfItem(item, query, BaseItemKind.Movie); - case StubType.Collections: - return GetMovieCollections(query); - case StubType.Favorites: - return GetChildrenOfItem(item, query, BaseItemKind.Movie, true); - case StubType.Genres: - return GetGenres(item, query); - } - - var array = new ServerItem[] - { - new(item, StubType.ContinueWatching), - new(item, StubType.Latest), - new(item, StubType.Movies), - new(item, StubType.Collections), - new(item, StubType.Favorites), - new(item, StubType.Genres) - }; - - if (limit < array.Length) - { - array = array[..limit.Value]; - } - - return new QueryResult( - startIndex, - array.Length, - array); - } - - /// - /// Returns the folders meeting the criteria. - /// - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetFolders(User user, int? startIndex, int? limit) - { - var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true); - var totalRecordCount = folders.Count; - // Handle paging - var items = folders - .OrderBy(i => i.SortName) - .Skip(startIndex ?? 0) - .Take(limit ?? int.MaxValue) - .Select(i => new ServerItem(i, StubType.Folder)) - .ToArray(); - - return new QueryResult( - startIndex, - totalRecordCount, - items); - } - - /// - /// Returns the TV folders meeting the criteria. - /// - /// The . - /// The . - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) - { - var query = new InternalItemsQuery(user) - { - StartIndex = startIndex, - Limit = limit, - OrderBy = GetOrderBy(sort, false) - }; - - switch (stubType) - { - case StubType.ContinueWatching: - return GetMovieContinueWatching(item, query); - case StubType.NextUp: - return GetNextUp(item, query); - case StubType.Latest: - return GetLatest(item, query, BaseItemKind.Episode); - case StubType.Series: - return GetChildrenOfItem(item, query, BaseItemKind.Series); - case StubType.FavoriteSeries: - return GetChildrenOfItem(item, query, BaseItemKind.Series, true); - case StubType.FavoriteEpisodes: - return GetChildrenOfItem(item, query, BaseItemKind.Episode, true); - case StubType.Genres: - return GetGenres(item, query); - } - - var serverItems = new ServerItem[] - { - new(item, StubType.ContinueWatching), - new(item, StubType.NextUp), - new(item, StubType.Latest), - new(item, StubType.Series), - new(item, StubType.FavoriteSeries), - new(item, StubType.FavoriteEpisodes), - new(item, StubType.Genres) - }; - - if (limit < serverItems.Length) - { - serverItems = serverItems[..limit.Value]; - } - - return new QueryResult( - startIndex, - serverItems.Length, - serverItems); - } - - /// - /// Returns the Movies that are part watched that meet the criteria. - /// - /// The . - /// The . - /// The . - private QueryResult GetMovieContinueWatching(BaseItem parent, InternalItemsQuery query) - { - query.Recursive = true; - query.Parent = parent; - - query.OrderBy = new[] - { - (ItemSortBy.DatePlayed, SortOrder.Descending), - (ItemSortBy.SortName, SortOrder.Ascending) - }; - - query.IsResumable = true; - query.Limit ??= 10; - - var result = _libraryManager.GetItemsResult(query); - - return ToResult(query.StartIndex, result); - } - - /// - /// Returns the Movie collections meeting the criteria. - /// - /// The see cref="InternalItemsQuery"/>. - /// The . - private QueryResult GetMovieCollections(InternalItemsQuery query) - { - query.Recursive = true; - query.IncludeItemTypes = new[] { BaseItemKind.BoxSet }; - - var result = _libraryManager.GetItemsResult(query); - - return ToResult(query.StartIndex, result); - } - - /// - /// Returns the children that meet the criteria. - /// - /// The . - /// The . - /// The item type. - /// A value indicating whether to only fetch favorite items. - /// The . - private QueryResult GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType, bool isFavorite = false) - { - query.Recursive = true; - query.Parent = parent; - query.IsFavorite = isFavorite; - query.IncludeItemTypes = new[] { itemType }; - - var result = _libraryManager.GetItemsResult(query); - - return ToResult(query.StartIndex, result); - } - - /// - /// Returns the genres meeting the criteria. - /// The GetGenres. - /// - /// The . - /// The . - /// The . - private QueryResult GetGenres(BaseItem parent, InternalItemsQuery query) - { - // Don't sort - query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; - var genresResult = _libraryManager.GetGenres(query); - - return ToResult(query.StartIndex, genresResult); - } - - /// - /// Returns the music genres meeting the criteria. - /// - /// The . - /// The . - /// The . - private QueryResult GetMusicGenres(BaseItem parent, InternalItemsQuery query) - { - // Don't sort - query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; - var genresResult = _libraryManager.GetMusicGenres(query); - - return ToResult(query.StartIndex, genresResult); - } - - /// - /// Returns the music albums by artist that meet the criteria. - /// - /// The . - /// The . - /// The . - private QueryResult GetMusicAlbumArtists(BaseItem parent, InternalItemsQuery query) - { - // Don't sort - query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; - var artists = _libraryManager.GetAlbumArtists(query); - - return ToResult(query.StartIndex, artists); - } - - /// - /// Returns the music artists meeting the criteria. - /// - /// The . - /// The . - /// The . - private QueryResult GetMusicArtists(BaseItem parent, InternalItemsQuery query) - { - // Don't sort - query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; - var artists = _libraryManager.GetArtists(query); - return ToResult(query.StartIndex, artists); - } - - /// - /// Returns the artists tagged as favourite that meet the criteria. - /// - /// The . - /// The . - /// The . - private QueryResult GetFavoriteArtists(BaseItem parent, InternalItemsQuery query) - { - // Don't sort - query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - query.AncestorIds = new[] { parent.Id }; - query.IsFavorite = true; - var artists = _libraryManager.GetArtists(query); - return ToResult(query.StartIndex, artists); - } - - /// - /// Returns the music playlists meeting the criteria. - /// - /// The query. - /// The . - private QueryResult GetMusicPlaylists(InternalItemsQuery query) - { - query.Parent = null; - query.IncludeItemTypes = new[] { BaseItemKind.Playlist }; - query.Recursive = true; - - var result = _libraryManager.GetItemsResult(query); - - return ToResult(query.StartIndex, result); - } - - /// - /// Returns the next up item meeting the criteria. - /// - /// The . - /// The . - /// The . - private QueryResult GetNextUp(BaseItem parent, InternalItemsQuery query) - { - query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - - var result = _tvSeriesManager.GetNextUp( - new NextUpQuery - { - Limit = query.Limit, - StartIndex = query.StartIndex, - // User cannot be null here as the caller has set it - UserId = query.User!.Id - }, - new[] { parent }, - query.DtoOptions); - - return ToResult(query.StartIndex, result); - } - - /// - /// Returns the latest items of [itemType] meeting the criteria. - /// - /// The . - /// The . - /// The item type. - /// The . - private QueryResult GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType) - { - query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); - - var items = _userViewManager.GetLatestItems( - new LatestItemsQuery - { - // User cannot be null here as the caller has set it - UserId = query.User!.Id, - Limit = query.Limit ?? 50, - IncludeItemTypes = new[] { itemType }, - ParentId = parent?.Id ?? Guid.Empty, - GroupItems = true - }, - query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i is not null).ToArray(); - - return ToResult(query.StartIndex, items); - } - - /// - /// Returns music artist items that meet the criteria. - /// - /// The . - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetMusicArtistItems(BaseItem item, User user, SortCriteria sort, int? startIndex, int? limit) - { - var query = new InternalItemsQuery(user) - { - Recursive = true, - ArtistIds = new[] { item.Id }, - IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, - Limit = limit, - StartIndex = startIndex, - DtoOptions = GetDtoOptions(), - OrderBy = GetOrderBy(sort, false) - }; - - var result = _libraryManager.GetItemsResult(query); - - return ToResult(startIndex, result); - } - - /// - /// Returns the genre items meeting the criteria. - /// - /// The . - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetGenreItems(BaseItem item, User user, SortCriteria sort, int? startIndex, int? limit) - { - var query = new InternalItemsQuery(user) - { - Recursive = true, - GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] - { - BaseItemKind.Movie, - BaseItemKind.Series - }, - Limit = limit, - StartIndex = startIndex, - DtoOptions = GetDtoOptions(), - OrderBy = GetOrderBy(sort, false) - }; - - var result = _libraryManager.GetItemsResult(query); - - return ToResult(startIndex, result); - } - - /// - /// Returns the music genre items meeting the criteria. - /// - /// The . - /// The . - /// The . - /// The start index. - /// The maximum number to return. - /// The . - private QueryResult GetMusicGenreItems(BaseItem item, User user, SortCriteria sort, int? startIndex, int? limit) - { - var query = new InternalItemsQuery(user) - { - Recursive = true, - GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }, - Limit = limit, - StartIndex = startIndex, - DtoOptions = GetDtoOptions(), - OrderBy = GetOrderBy(sort, false) - }; - - var result = _libraryManager.GetItemsResult(query); - - return ToResult(startIndex, result); - } - - /// - /// Converts into a . - /// - /// The start index. - /// An array of . - /// A . - private static QueryResult ToResult(int? startIndex, IReadOnlyCollection result) - { - var serverItems = result - .Select(i => new ServerItem(i, null)) - .ToArray(); - - return new QueryResult( - startIndex, - result.Count, - serverItems); - } - - /// - /// Converts a to a . - /// - /// The index the result started at. - /// A . - /// The . - private static QueryResult ToResult(int? startIndex, QueryResult result) - { - var length = result.Items.Count; - var serverItems = new ServerItem[length]; - for (var i = 0; i < length; i++) - { - serverItems[i] = new ServerItem(result.Items[i], null); - } - - return new QueryResult( - startIndex, - result.TotalRecordCount, - serverItems); - } - - /// - /// Converts a query result to a . - /// - /// The start index. - /// A . - /// The . - private static QueryResult ToResult(int? startIndex, QueryResult<(BaseItem Item, ItemCounts ItemCounts)> result) - { - var length = result.Items.Count; - var serverItems = new ServerItem[length]; - for (var i = 0; i < length; i++) - { - serverItems[i] = new ServerItem(result.Items[i].Item, null); - } - - return new QueryResult( - startIndex, - result.TotalRecordCount, - serverItems); - } - - /// - /// Gets the sorting method on a query. - /// - /// The . - /// True if pre-sorted. - private static (ItemSortBy SortName, SortOrder SortOrder)[] GetOrderBy(SortCriteria sort, bool isPreSorted) - { - return isPreSorted ? Array.Empty<(ItemSortBy, SortOrder)>() : new[] { (ItemSortBy.SortName, sort.SortOrder) }; - } - - /// - /// Retrieves the ServerItem id. - /// - /// The id. - /// The . - private ServerItem GetItemFromObjectId(string id) - { - return DidlBuilder.IsIdRoot(id) - ? new ServerItem(_libraryManager.GetUserRootFolder(), null) - : ParseItemId(id); - } - - /// - /// Parses the item id into a . - /// - /// The . - /// The corresponding . - private ServerItem ParseItemId(string id) - { - StubType? stubType = null; - - // After using PlayTo, MediaMonkey sends a request to the server trying to get item info - const string ParamsSrch = "Params="; - var paramsIndex = id.IndexOf(ParamsSrch, StringComparison.OrdinalIgnoreCase); - if (paramsIndex != -1) - { - id = id[(paramsIndex + ParamsSrch.Length)..]; - - var parts = id.Split(';'); - id = parts[23]; - } - - var dividerIndex = id.IndexOf('_', StringComparison.Ordinal); - if (dividerIndex != -1 && Enum.TryParse(id.AsSpan(0, dividerIndex), true, out var parsedStubType)) - { - id = id[(dividerIndex + 1)..]; - stubType = parsedStubType; - } - - if (Guid.TryParse(id, out var itemId)) - { - var item = _libraryManager.GetItemById(itemId); - - return new ServerItem(item, stubType); - } - - Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id); - - return new ServerItem(_libraryManager.GetUserRootFolder(), null); - } - } -} diff --git a/Emby.Dlna/ContentDirectory/ServerItem.cs b/Emby.Dlna/ContentDirectory/ServerItem.cs deleted file mode 100644 index df05fa9666..0000000000 --- a/Emby.Dlna/ContentDirectory/ServerItem.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Controller.Entities; - -namespace Emby.Dlna.ContentDirectory -{ - /// - /// Defines the . - /// - internal class ServerItem - { - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The stub type. - public ServerItem(BaseItem item, StubType? stubType) - { - Item = item; - - if (stubType.HasValue) - { - StubType = stubType; - } - else if (item is IItemByName and not Folder) - { - StubType = Dlna.ContentDirectory.StubType.Folder; - } - } - - /// - /// Gets the underlying base item. - /// - public BaseItem Item { get; } - - /// - /// Gets the DLNA item type. - /// - public StubType? StubType { get; } - } -} diff --git a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs deleted file mode 100644 index 7e3db46519..0000000000 --- a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs +++ /dev/null @@ -1,415 +0,0 @@ -using System.Collections.Generic; -using Emby.Dlna.Common; - -namespace Emby.Dlna.ContentDirectory -{ - /// - /// Defines the . - /// - public static class ServiceActionListBuilder - { - /// - /// Returns a list of services that this instance provides. - /// - /// An . - public static IEnumerable GetActions() - { - return new[] - { - GetSearchCapabilitiesAction(), - GetSortCapabilitiesAction(), - GetGetSystemUpdateIDAction(), - GetBrowseAction(), - GetSearchAction(), - GetX_GetFeatureListAction(), - GetXSetBookmarkAction(), - GetBrowseByLetterAction() - }; - } - - /// - /// Returns the action details for "GetSystemUpdateID". - /// - /// The . - private static ServiceAction GetGetSystemUpdateIDAction() - { - var action = new ServiceAction - { - Name = "GetSystemUpdateID" - }; - - action.ArgumentList.Add(new Argument - { - Name = "Id", - Direction = "out", - RelatedStateVariable = "SystemUpdateID" - }); - - return action; - } - - /// - /// Returns the action details for "GetSearchCapabilities". - /// - /// The . - private static ServiceAction GetSearchCapabilitiesAction() - { - var action = new ServiceAction - { - Name = "GetSearchCapabilities" - }; - - action.ArgumentList.Add(new Argument - { - Name = "SearchCaps", - Direction = "out", - RelatedStateVariable = "SearchCapabilities" - }); - - return action; - } - - /// - /// Returns the action details for "GetSortCapabilities". - /// - /// The . - private static ServiceAction GetSortCapabilitiesAction() - { - var action = new ServiceAction - { - Name = "GetSortCapabilities" - }; - - action.ArgumentList.Add(new Argument - { - Name = "SortCaps", - Direction = "out", - RelatedStateVariable = "SortCapabilities" - }); - - return action; - } - - /// - /// Returns the action details for "X_GetFeatureList". - /// - /// The . - private static ServiceAction GetX_GetFeatureListAction() - { - var action = new ServiceAction - { - Name = "X_GetFeatureList" - }; - - action.ArgumentList.Add(new Argument - { - Name = "FeatureList", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Featurelist" - }); - - return action; - } - - /// - /// Returns the action details for "Search". - /// - /// The . - private static ServiceAction GetSearchAction() - { - var action = new ServiceAction - { - Name = "Search" - }; - - action.ArgumentList.Add(new Argument - { - Name = "ContainerID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ObjectID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "SearchCriteria", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_SearchCriteria" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Filter", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Filter" - }); - - action.ArgumentList.Add(new Argument - { - Name = "StartingIndex", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Index" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RequestedCount", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "SortCriteria", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_SortCriteria" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Result" - }); - - action.ArgumentList.Add(new Argument - { - Name = "NumberReturned", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "TotalMatches", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "UpdateID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_UpdateID" - }); - - return action; - } - - /// - /// Returns the action details for "Browse". - /// - /// The . - private static ServiceAction GetBrowseAction() - { - var action = new ServiceAction - { - Name = "Browse" - }; - - action.ArgumentList.Add(new Argument - { - Name = "ObjectID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ObjectID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "BrowseFlag", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_BrowseFlag" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Filter", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Filter" - }); - - action.ArgumentList.Add(new Argument - { - Name = "StartingIndex", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Index" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RequestedCount", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "SortCriteria", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_SortCriteria" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Result" - }); - - action.ArgumentList.Add(new Argument - { - Name = "NumberReturned", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "TotalMatches", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "UpdateID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_UpdateID" - }); - - return action; - } - - /// - /// Returns the action details for "X_BrowseByLetter". - /// - /// The . - private static ServiceAction GetBrowseByLetterAction() - { - var action = new ServiceAction - { - Name = "X_BrowseByLetter" - }; - - action.ArgumentList.Add(new Argument - { - Name = "ObjectID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ObjectID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "BrowseFlag", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_BrowseFlag" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Filter", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Filter" - }); - - action.ArgumentList.Add(new Argument - { - Name = "StartingLetter", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_BrowseLetter" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RequestedCount", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "SortCriteria", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_SortCriteria" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Result" - }); - - action.ArgumentList.Add(new Argument - { - Name = "NumberReturned", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "TotalMatches", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Count" - }); - - action.ArgumentList.Add(new Argument - { - Name = "UpdateID", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_UpdateID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "StartingIndex", - Direction = "out", - RelatedStateVariable = "A_ARG_TYPE_Index" - }); - - return action; - } - - /// - /// Returns the action details for "X_SetBookmark". - /// - /// The . - private static ServiceAction GetXSetBookmarkAction() - { - var action = new ServiceAction - { - Name = "X_SetBookmark" - }; - - action.ArgumentList.Add(new Argument - { - Name = "CategoryType", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_CategoryType" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_RID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "ObjectID", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_ObjectID" - }); - - action.ArgumentList.Add(new Argument - { - Name = "PosSecond", - Direction = "in", - RelatedStateVariable = "A_ARG_TYPE_PosSec" - }); - - return action; - } - } -} diff --git a/Emby.Dlna/ContentDirectory/StubType.cs b/Emby.Dlna/ContentDirectory/StubType.cs deleted file mode 100644 index 187dc1d75a..0000000000 --- a/Emby.Dlna/ContentDirectory/StubType.cs +++ /dev/null @@ -1,30 +0,0 @@ -#pragma warning disable CS1591 - -namespace Emby.Dlna.ContentDirectory -{ - /// - /// Defines the DLNA item types. - /// - public enum StubType - { - Folder = 0, - Latest = 2, - Playlists = 3, - Albums = 4, - AlbumArtists = 5, - Artists = 6, - Songs = 7, - Genres = 8, - FavoriteSongs = 9, - FavoriteArtists = 10, - FavoriteAlbums = 11, - ContinueWatching = 12, - Movies = 13, - Collections = 14, - Favorites = 15, - NextUp = 16, - Series = 17, - FavoriteSeries = 18, - FavoriteEpisodes = 19 - } -} diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs deleted file mode 100644 index 8ee6325e9e..0000000000 --- a/Emby.Dlna/ControlRequest.cs +++ /dev/null @@ -1,25 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.IO; -using Microsoft.AspNetCore.Http; - -namespace Emby.Dlna -{ - public class ControlRequest - { - public ControlRequest(IHeaderDictionary headers) - { - Headers = headers; - } - - public IHeaderDictionary Headers { get; } - - public Stream InputXml { get; set; } - - public string TargetServerUuId { get; set; } - - public string RequestedUrl { get; set; } - } -} diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs deleted file mode 100644 index 8b09588424..0000000000 --- a/Emby.Dlna/ControlResponse.cs +++ /dev/null @@ -1,28 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace Emby.Dlna -{ - public class ControlResponse - { - public ControlResponse(string xml, bool isSuccessful) - { - Headers = new Dictionary(); - Xml = xml; - IsSuccessful = isSuccessful; - } - - public IDictionary Headers { get; } - - public string Xml { get; set; } - - public bool IsSuccessful { get; set; } - - /// - public override string ToString() - { - return Xml; - } - } -} diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs deleted file mode 100644 index 9f152df132..0000000000 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ /dev/null @@ -1,1266 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using Emby.Dlna.ContentDirectory; -using Jellyfin.Data.Entities; -using Jellyfin.Data.Enums; -using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Playlists; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; -using Episode = MediaBrowser.Controller.Entities.TV.Episode; -using Genre = MediaBrowser.Controller.Entities.Genre; -using Movie = MediaBrowser.Controller.Entities.Movies.Movie; -using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; -using Season = MediaBrowser.Controller.Entities.TV.Season; -using Series = MediaBrowser.Controller.Entities.TV.Series; -using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute; - -namespace Emby.Dlna.Didl -{ - public class DidlBuilder - { - private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; - private const string NsDc = "http://purl.org/dc/elements/1.1/"; - private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/"; - private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/"; - - private readonly DeviceProfile _profile; - private readonly IImageProcessor _imageProcessor; - private readonly string _serverAddress; - private readonly string? _accessToken; - private readonly User? _user; - private readonly IUserDataManager _userDataManager; - private readonly ILocalizationManager _localization; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly ILogger _logger; - private readonly IMediaEncoder _mediaEncoder; - private readonly ILibraryManager _libraryManager; - - public DidlBuilder( - DeviceProfile profile, - User? user, - IImageProcessor imageProcessor, - string serverAddress, - string? accessToken, - IUserDataManager userDataManager, - ILocalizationManager localization, - IMediaSourceManager mediaSourceManager, - ILogger logger, - IMediaEncoder mediaEncoder, - ILibraryManager libraryManager) - { - _profile = profile; - _user = user; - _imageProcessor = imageProcessor; - _serverAddress = serverAddress; - _accessToken = accessToken; - _userDataManager = userDataManager; - _localization = localization; - _mediaSourceManager = mediaSourceManager; - _logger = logger; - _mediaEncoder = mediaEncoder; - _libraryManager = libraryManager; - } - - public static string NormalizeDlnaMediaUrl(string url) - { - return url + "&dlnaheaders=true"; - } - - public string GetItemDidl(BaseItem item, User? user, BaseItem? context, string deviceId, Filter filter, StreamInfo streamInfo) - { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CloseOutput = false, - OmitXmlDeclaration = true, - ConformanceLevel = ConformanceLevel.Fragment - }; - - using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8)) - { - // If this using are changed to single lines, then write.Flush needs to be appended before the return. - using (var writer = XmlWriter.Create(builder, settings)) - { - // writer.WriteStartDocument(); - - writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl); - - writer.WriteAttributeString("xmlns", "dc", null, NsDc); - writer.WriteAttributeString("xmlns", "dlna", null, NsDlna); - writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp); - // didl.SetAttribute("xmlns:sec", NS_SEC); - - WriteXmlRootAttributes(_profile, writer); - - WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo); - - writer.WriteFullEndElement(); - // writer.WriteEndDocument(); - } - - return builder.ToString(); - } - } - - public static void WriteXmlRootAttributes(DeviceProfile profile, XmlWriter writer) - { - foreach (var att in profile.XmlRootAttributes) - { - var parts = att.Name.Split(':', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length == 2) - { - writer.WriteAttributeString(parts[0], parts[1], null, att.Value); - } - else - { - writer.WriteAttributeString(att.Name, att.Value); - } - } - } - - public void WriteItemElement( - XmlWriter writer, - BaseItem item, - User? user, - BaseItem? context, - StubType? contextStubType, - string deviceId, - Filter filter, - StreamInfo? streamInfo = null) - { - var clientId = GetClientId(item, null); - - writer.WriteStartElement(string.Empty, "item", NsDidl); - - writer.WriteAttributeString("restricted", "1"); - writer.WriteAttributeString("id", clientId); - - if (context is not null) - { - writer.WriteAttributeString("parentID", GetClientId(context, contextStubType)); - } - else - { - var parent = item.DisplayParentId; - if (!parent.Equals(default)) - { - writer.WriteAttributeString("parentID", GetClientId(parent, null)); - } - } - - AddGeneralProperties(item, null, context, writer, filter); - - AddSamsungBookmarkInfo(item, user, writer, streamInfo); - - // refID? - // storeAttribute(itemNode, object, ClassProperties.REF_ID, false); - - if (item is IHasMediaSources) - { - switch (item.MediaType) - { - case MediaType.Audio: - AddAudioResource(writer, item, deviceId, filter, streamInfo); - break; - case MediaType.Video: - AddVideoResource(writer, item, deviceId, filter, streamInfo); - break; - } - } - - AddCover(item, null, writer); - writer.WriteFullEndElement(); - } - - private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo? streamInfo = null) - { - if (streamInfo is null) - { - var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user); - - streamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalVideoStream(new MediaOptions - { - ItemId = video.Id, - MediaSources = sources.ToArray(), - Profile = _profile, - DeviceId = deviceId, - MaxBitrate = _profile.MaxStreamingBitrate - }) ?? throw new InvalidOperationException("No optimal video stream found"); - } - - var targetWidth = streamInfo.TargetWidth; - var targetHeight = streamInfo.TargetHeight; - - var contentFeatureList = ContentFeatureBuilder.BuildVideoHeader( - _profile, - streamInfo.Container, - streamInfo.TargetVideoCodec.FirstOrDefault(), - streamInfo.TargetAudioCodec.FirstOrDefault(), - targetWidth, - targetHeight, - streamInfo.TargetVideoBitDepth, - streamInfo.TargetVideoBitrate, - streamInfo.TargetTimestamp, - streamInfo.IsDirectStream, - streamInfo.RunTimeTicks ?? 0, - streamInfo.TargetVideoProfile, - streamInfo.TargetVideoRangeType, - streamInfo.TargetVideoLevel, - streamInfo.TargetFramerate ?? 0, - streamInfo.TargetPacketLength, - streamInfo.TranscodeSeekInfo, - streamInfo.IsTargetAnamorphic, - streamInfo.IsTargetInterlaced, - streamInfo.TargetRefFrames, - streamInfo.TargetVideoStreamCount, - streamInfo.TargetAudioStreamCount, - streamInfo.TargetVideoCodecTag, - streamInfo.IsTargetAVC); - - foreach (var contentFeature in contentFeatureList) - { - AddVideoResource(writer, filter, contentFeature, streamInfo); - } - - var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken); - - foreach (var subtitle in subtitleProfiles) - { - if (subtitle.DeliveryMethod != SubtitleDeliveryMethod.External) - { - continue; - } - - var subtitleAdded = AddSubtitleElement(writer, subtitle); - - if (subtitleAdded && _profile.EnableSingleSubtitleLimit) - { - break; - } - } - } - - private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info) - { - var subtitleProfile = _profile.SubtitleProfiles - .FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) - && i.Method == SubtitleDeliveryMethod.External); - - if (subtitleProfile is null) - { - return false; - } - - var subtitleMode = subtitleProfile.DidlMode; - - if (string.Equals(subtitleMode, "CaptionInfoEx", StringComparison.OrdinalIgnoreCase)) - { - // http://192.168.1.3:9999/video.srt - // http://192.168.1.3:9999/video.srt - - writer.WriteStartElement("sec", "CaptionInfoEx", null); - writer.WriteAttributeString("sec", "type", null, info.Format.ToLowerInvariant()); - - writer.WriteString(info.Url); - writer.WriteFullEndElement(); - } - else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase)) - { - writer.WriteStartElement(string.Empty, "res", NsDidl); - - writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*"); - - writer.WriteString(info.Url); - writer.WriteFullEndElement(); - } - else - { - writer.WriteStartElement(string.Empty, "res", NsDidl); - var protocolInfo = string.Format( - CultureInfo.InvariantCulture, - "http-get:*:text/{0}:*", - info.Format.ToLowerInvariant()); - writer.WriteAttributeString("protocolInfo", protocolInfo); - - writer.WriteString(info.Url); - writer.WriteFullEndElement(); - } - - return true; - } - - private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo) - { - writer.WriteStartElement(string.Empty, "res", NsDidl); - - var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken)); - - var mediaSource = streamInfo.MediaSource; - - if (mediaSource?.RunTimeTicks.HasValue == true) - { - writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture)); - } - - if (filter.Contains("res@size")) - { - if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength) - { - var size = streamInfo.TargetSize; - - if (size.HasValue) - { - writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture)); - } - } - } - - var totalBitrate = streamInfo.TargetTotalBitrate; - var targetSampleRate = streamInfo.TargetAudioSampleRate; - var targetChannels = streamInfo.TargetAudioChannels; - - var targetWidth = streamInfo.TargetWidth; - var targetHeight = streamInfo.TargetHeight; - - if (targetChannels.HasValue) - { - writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (filter.Contains("res@resolution")) - { - if (targetWidth.HasValue && targetHeight.HasValue) - { - writer.WriteAttributeString( - "resolution", - string.Format( - CultureInfo.InvariantCulture, - "{0}x{1}", - targetWidth.Value, - targetHeight.Value)); - } - } - - if (targetSampleRate.HasValue) - { - writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (totalBitrate.HasValue) - { - writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(CultureInfo.InvariantCulture)); - } - - var mediaProfile = _profile.GetVideoMediaProfile( - streamInfo.Container, - streamInfo.TargetAudioCodec.FirstOrDefault(), - streamInfo.TargetVideoCodec.FirstOrDefault(), - streamInfo.TargetAudioBitrate, - targetWidth, - targetHeight, - streamInfo.TargetVideoBitDepth, - streamInfo.TargetVideoProfile, - streamInfo.TargetVideoRangeType, - streamInfo.TargetVideoLevel, - streamInfo.TargetFramerate ?? 0, - streamInfo.TargetPacketLength, - streamInfo.TargetTimestamp, - streamInfo.IsTargetAnamorphic, - streamInfo.IsTargetInterlaced, - streamInfo.TargetRefFrames, - streamInfo.TargetVideoStreamCount, - streamInfo.TargetAudioStreamCount, - streamInfo.TargetVideoCodecTag, - streamInfo.IsTargetAVC); - - var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal)); - - var mimeType = mediaProfile is null || string.IsNullOrEmpty(mediaProfile.MimeType) - ? MimeTypes.GetMimeType(filename) - : mediaProfile.MimeType; - - writer.WriteAttributeString( - "protocolInfo", - string.Format( - CultureInfo.InvariantCulture, - "http-get:*:{0}:{1}", - mimeType, - contentFeatures)); - - writer.WriteString(url); - - writer.WriteFullEndElement(); - } - - private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem? context) - { - if (itemStubType.HasValue) - { - switch (itemStubType.Value) - { - case StubType.Latest: return _localization.GetLocalizedString("Latest"); - case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); - case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); - case StubType.Albums: return _localization.GetLocalizedString("Albums"); - case StubType.Artists: return _localization.GetLocalizedString("Artists"); - case StubType.Songs: return _localization.GetLocalizedString("Songs"); - case StubType.Genres: return _localization.GetLocalizedString("Genres"); - case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); - case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); - case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); - case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching"); - case StubType.Movies: return _localization.GetLocalizedString("Movies"); - case StubType.Collections: return _localization.GetLocalizedString("Collections"); - case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); - case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); - case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); - case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); - case StubType.Series: return _localization.GetLocalizedString("Shows"); - } - } - - return item is Episode episode - ? GetEpisodeDisplayName(episode, context) - : item.Name; - } - - /// - /// Gets episode display name appropriate for the given context. - /// - /// - /// If context is a season, this will return a string containing just episode number and name. - /// Otherwise the result will include series names and season number. - /// - /// The episode. - /// Current context. - /// Formatted name of the episode. - private string GetEpisodeDisplayName(Episode episode, BaseItem? context) - { - string[] components; - - if (context is Season season) - { - // This is a special embedded within a season - if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0 - && season.IndexNumber.HasValue && season.IndexNumber.Value != 0) - { - return string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("ValueSpecialEpisodeName"), - episode.Name); - } - - // inside a season use simple format (ex. '12 - Episode Name') - var epNumberName = GetEpisodeIndexFullName(episode); - components = new[] { epNumberName, episode.Name }; - } - else - { - // outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name') - var epNumberName = GetEpisodeNumberDisplayName(episode); - components = new[] { episode.SeriesName, epNumberName, episode.Name }; - } - - return string.Join(" - ", components.Where(NotNullOrWhiteSpace)); - } - - /// - /// Gets complete episode number. - /// - /// The episode. - /// For single episodes returns just the number. For double episodes - current and ending numbers. - private string GetEpisodeIndexFullName(Episode episode) - { - var name = string.Empty; - if (episode.IndexNumber.HasValue) - { - name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); - - if (episode.IndexNumberEnd.HasValue) - { - name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture); - } - } - - return name; - } - - /// - /// Gets episode number formatted as 'S##E##'. - /// - /// The episode. - /// Formatted episode number. - private string GetEpisodeNumberDisplayName(Episode episode) - { - var name = string.Empty; - var seasonNumber = episode.Season?.IndexNumber; - - if (seasonNumber.HasValue) - { - name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture); - } - - var indexName = GetEpisodeIndexFullName(episode); - - if (!string.IsNullOrWhiteSpace(indexName)) - { - name += "E" + indexName; - } - - return name; - } - - private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s); - - private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo? streamInfo = null) - { - writer.WriteStartElement(string.Empty, "res", NsDidl); - - if (streamInfo is null) - { - var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user); - - streamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalAudioStream(new MediaOptions - { - ItemId = audio.Id, - MediaSources = sources.ToArray(), - Profile = _profile, - DeviceId = deviceId - }) ?? throw new InvalidOperationException("No optimal audio stream found"); - } - - var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken)); - - var mediaSource = streamInfo.MediaSource; - - if (mediaSource?.RunTimeTicks is not null) - { - writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture)); - } - - if (filter.Contains("res@size")) - { - if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength) - { - var size = streamInfo.TargetSize; - - if (size.HasValue) - { - writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture)); - } - } - } - - var targetAudioBitrate = streamInfo.TargetAudioBitrate; - var targetSampleRate = streamInfo.TargetAudioSampleRate; - var targetChannels = streamInfo.TargetAudioChannels; - var targetAudioBitDepth = streamInfo.TargetAudioBitDepth; - - if (targetChannels.HasValue) - { - writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (targetSampleRate.HasValue) - { - writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (targetAudioBitrate.HasValue) - { - writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); - } - - var mediaProfile = _profile.GetAudioMediaProfile( - streamInfo.Container, - streamInfo.TargetAudioCodec.FirstOrDefault(), - targetChannels, - targetAudioBitrate, - targetSampleRate, - targetAudioBitDepth); - - var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal)); - - var mimeType = mediaProfile is null || string.IsNullOrEmpty(mediaProfile.MimeType) - ? MimeTypes.GetMimeType(filename) - : mediaProfile.MimeType; - - var contentFeatures = ContentFeatureBuilder.BuildAudioHeader( - _profile, - streamInfo.Container, - streamInfo.TargetAudioCodec.FirstOrDefault(), - targetAudioBitrate, - targetSampleRate, - targetChannels, - targetAudioBitDepth, - streamInfo.IsDirectStream, - streamInfo.RunTimeTicks ?? 0, - streamInfo.TranscodeSeekInfo); - - writer.WriteAttributeString( - "protocolInfo", - string.Format( - CultureInfo.InvariantCulture, - "http-get:*:{0}:{1}", - mimeType, - contentFeatures)); - - writer.WriteString(url); - - writer.WriteFullEndElement(); - } - - public static bool IsIdRoot(string id) - => string.IsNullOrWhiteSpace(id) - || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase) - // Samsung sometimes uses 1 as root - || string.Equals(id, "1", StringComparison.OrdinalIgnoreCase); - - public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string? requestedId = null) - { - writer.WriteStartElement(string.Empty, "container", NsDidl); - - writer.WriteAttributeString("restricted", "1"); - writer.WriteAttributeString("searchable", "1"); - writer.WriteAttributeString("childCount", childCount.ToString(CultureInfo.InvariantCulture)); - - var clientId = GetClientId(folder, stubType); - - if (string.Equals(requestedId, "0", StringComparison.Ordinal)) - { - writer.WriteAttributeString("id", "0"); - writer.WriteAttributeString("parentID", "-1"); - } - else - { - writer.WriteAttributeString("id", clientId); - - if (context is not null) - { - writer.WriteAttributeString("parentID", GetClientId(context, null)); - } - else - { - var parent = folder.DisplayParentId; - if (parent.Equals(default)) - { - writer.WriteAttributeString("parentID", "0"); - } - else - { - writer.WriteAttributeString("parentID", GetClientId(parent, null)); - } - } - } - - AddGeneralProperties(folder, stubType, context, writer, filter); - - AddCover(folder, stubType, writer); - - writer.WriteFullEndElement(); - } - - private void AddSamsungBookmarkInfo(BaseItem item, User? user, XmlWriter writer, StreamInfo? streamInfo) - { - if (!item.SupportsPositionTicksResume || item is Folder) - { - return; - } - - XmlAttribute? secAttribute = null; - foreach (var attribute in _profile.XmlRootAttributes) - { - if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase)) - { - secAttribute = attribute; - break; - } - } - - // Not a samsung device or no user data - if (secAttribute is null || user is null) - { - return; - } - - var userdata = _userDataManager.GetUserData(user, item); - var playbackPositionTicks = (streamInfo is not null && streamInfo.StartPositionTicks > 0) ? streamInfo.StartPositionTicks : userdata.PlaybackPositionTicks; - - if (playbackPositionTicks > 0) - { - var elementValue = string.Format( - CultureInfo.InvariantCulture, - "BM={0}", - Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds)); - AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value); - } - } - - /// - /// Adds fields used by both items and folders. - /// - private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem? context, XmlWriter writer, Filter filter) - { - // Don't filter on dc:title because not all devices will include it in the filter - // MediaMonkey for example won't display content without a title - // if (filter.Contains("dc:title")) - { - AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NsDc); - } - - WriteObjectClass(writer, item, itemStubType); - - if (filter.Contains("dc:date")) - { - if (item.PremiereDate.HasValue) - { - AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), NsDc); - } - } - - if (filter.Contains("upnp:genre")) - { - foreach (var genre in item.Genres) - { - AddValue(writer, "upnp", "genre", genre, NsUpnp); - } - } - - foreach (var studio in item.Studios) - { - AddValue(writer, "upnp", "publisher", studio, NsUpnp); - } - - if (item is not Folder) - { - if (filter.Contains("dc:description")) - { - var desc = item.Overview; - - if (!string.IsNullOrWhiteSpace(desc)) - { - AddValue(writer, "dc", "description", desc, NsDc); - } - } - - // if (filter.Contains("upnp:longDescription")) - // { - // if (!string.IsNullOrWhiteSpace(item.Overview)) - // { - // AddValue(writer, "upnp", "longDescription", item.Overview, NsUpnp); - // } - // } - } - - if (!string.IsNullOrEmpty(item.OfficialRating)) - { - if (filter.Contains("dc:rating")) - { - AddValue(writer, "dc", "rating", item.OfficialRating, NsDc); - } - - if (filter.Contains("upnp:rating")) - { - AddValue(writer, "upnp", "rating", item.OfficialRating, NsUpnp); - } - } - - AddPeople(item, writer); - } - - private void WriteObjectClass(XmlWriter writer, BaseItem item, StubType? stubType) - { - // More types here - // http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs - - writer.WriteStartElement("upnp", "class", NsUpnp); - - if (item.IsDisplayedAsFolder || stubType.HasValue) - { - string? classType = null; - - if (!_profile.RequiresPlainFolders) - { - if (item is MusicAlbum) - { - classType = "object.container.album.musicAlbum"; - } - else if (item is MusicArtist) - { - classType = "object.container.person.musicArtist"; - } - else if (item is Series || item is Season || item is BoxSet || item is Video) - { - classType = "object.container.album.videoAlbum"; - } - else if (item is Playlist) - { - classType = "object.container.playlistContainer"; - } - else if (item is PhotoAlbum) - { - classType = "object.container.album.photoAlbum"; - } - } - - writer.WriteString(classType ?? "object.container.storageFolder"); - } - else if (item.MediaType == MediaType.Audio) - { - writer.WriteString("object.item.audioItem.musicTrack"); - } - else if (item.MediaType == MediaType.Photo) - { - writer.WriteString("object.item.imageItem.photo"); - } - else if (item.MediaType == MediaType.Video) - { - if (!_profile.RequiresPlainVideoItems && item is Movie) - { - writer.WriteString("object.item.videoItem.movie"); - } - else if (!_profile.RequiresPlainVideoItems && item is MusicVideo) - { - writer.WriteString("object.item.videoItem.musicVideoClip"); - } - else - { - writer.WriteString("object.item.videoItem"); - } - } - else if (item is MusicGenre) - { - writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre"); - } - else if (item is Genre) - { - writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre"); - } - else - { - writer.WriteString("object.item"); - } - - writer.WriteFullEndElement(); - } - - private void AddPeople(BaseItem item, XmlWriter writer) - { - if (!item.SupportsPeople) - { - return; - } - - var types = new[] - { - PersonKind.Director, - PersonKind.Writer, - PersonKind.Producer, - PersonKind.Composer, - PersonKind.Creator - }; - - // Seeing some LG models locking up due content with large lists of people - // The actual issue might just be due to processing a more metadata than it can handle - var people = _libraryManager.GetPeople( - new InternalPeopleQuery - { - ItemId = item.Id, - Limit = 6 - }); - - foreach (var actor in people) - { - var type = types.FirstOrDefault(i => i == actor.Type || string.Equals(actor.Role, i.ToString(), StringComparison.OrdinalIgnoreCase)); - if (type == PersonKind.Unknown) - { - type = PersonKind.Actor; - } - - AddValue(writer, "upnp", type.ToString().ToLowerInvariant(), actor.Name, NsUpnp); - } - } - - private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem? context, XmlWriter writer, Filter filter) - { - AddCommonFields(item, itemStubType, context, writer, filter); - - var hasAlbumArtists = item as IHasAlbumArtist; - - if (item is IHasArtist hasArtists) - { - foreach (var artist in hasArtists.Artists) - { - AddValue(writer, "upnp", "artist", artist, NsUpnp); - AddValue(writer, "dc", "creator", artist, NsDc); - - // If it doesn't support album artists (musicvideo), then tag as both - if (hasAlbumArtists is null) - { - AddAlbumArtist(writer, artist); - } - } - } - - if (hasAlbumArtists is not null) - { - foreach (var albumArtist in hasAlbumArtists.AlbumArtists) - { - AddAlbumArtist(writer, albumArtist); - } - } - - if (!string.IsNullOrWhiteSpace(item.Album)) - { - AddValue(writer, "upnp", "album", item.Album, NsUpnp); - } - - if (item.IndexNumber.HasValue) - { - AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp); - - if (item is Episode) - { - AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp); - } - } - } - - private void AddAlbumArtist(XmlWriter writer, string name) - { - try - { - writer.WriteStartElement("upnp", "artist", NsUpnp); - writer.WriteAttributeString("role", "AlbumArtist"); - - writer.WriteString(name); - - writer.WriteFullEndElement(); - } - catch (XmlException ex) - { - _logger.LogError(ex, "Error adding xml value: {Value}", name); - } - } - - private void AddValue(XmlWriter writer, string prefix, string name, string value, string namespaceUri) - { - try - { - writer.WriteElementString(prefix, name, namespaceUri, value); - } - catch (XmlException ex) - { - _logger.LogError(ex, "Error adding xml value: {Value}", value); - } - } - - private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer) - { - ImageDownloadInfo? imageInfo = GetImageInfo(item); - - if (imageInfo is null) - { - return; - } - - // TODO: Remove these default values - var albumArtUrlInfo = GetImageUrl( - imageInfo, - _profile.MaxAlbumArtWidth ?? 10000, - _profile.MaxAlbumArtHeight ?? 10000, - "jpg"); - - writer.WriteStartElement("upnp", "albumArtURI", NsUpnp); - if (!string.IsNullOrEmpty(_profile.AlbumArtPn)) - { - writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn); - } - - writer.WriteString(albumArtUrlInfo.Url); - writer.WriteFullEndElement(); - - // TODO: Remove these default values - var iconUrlInfo = GetImageUrl( - imageInfo, - _profile.MaxIconWidth ?? 48, - _profile.MaxIconHeight ?? 48, - "jpg"); - writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.Url); - - if (!_profile.EnableAlbumArtInDidl) - { - if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video) - { - if (!stubType.HasValue) - { - return; - } - } - } - - if (!_profile.EnableSingleAlbumArtLimit || item.MediaType == MediaType.Photo) - { - AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG"); - AddImageResElement(item, writer, 1024, 768, "jpg", "JPEG_MED"); - AddImageResElement(item, writer, 640, 480, "jpg", "JPEG_SM"); - AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG"); - AddImageResElement(item, writer, 160, 160, "png", "PNG_TN"); - } - - AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN"); - } - - private void AddImageResElement( - BaseItem item, - XmlWriter writer, - int maxWidth, - int maxHeight, - string format, - string org_Pn) - { - var imageInfo = GetImageInfo(item); - - if (imageInfo is null) - { - return; - } - - var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format); - - writer.WriteStartElement(string.Empty, "res", NsDidl); - - // Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail - // rather than using a larger one when available - var width = albumartUrlInfo.Width ?? maxWidth; - var height = albumartUrlInfo.Height ?? maxHeight; - - var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn); - - writer.WriteAttributeString( - "protocolInfo", - string.Format( - CultureInfo.InvariantCulture, - "http-get:*:{0}:{1}", - MimeTypes.GetMimeType("file." + format), - contentFeatures)); - - writer.WriteAttributeString( - "resolution", - string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height)); - - writer.WriteString(albumartUrlInfo.Url); - - writer.WriteFullEndElement(); - } - - private ImageDownloadInfo? GetImageInfo(BaseItem item) - { - if (item.HasImage(ImageType.Primary)) - { - return GetImageInfo(item, ImageType.Primary); - } - - if (item.HasImage(ImageType.Thumb)) - { - return GetImageInfo(item, ImageType.Thumb); - } - - if (item.HasImage(ImageType.Backdrop)) - { - if (item is Channel) - { - return GetImageInfo(item, ImageType.Backdrop); - } - } - - // For audio tracks without art use album art if available. - if (item is Audio audioItem) - { - var album = audioItem.AlbumEntity; - return album is not null && album.HasImage(ImageType.Primary) - ? GetImageInfo(album, ImageType.Primary) - : null; - } - - // Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder. - if (item is MusicAlbum || item is Playlist) - { - return null; - } - - // For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item. - var parentWithImage = GetFirstParentWithImageBelowUserRoot(item); - if (parentWithImage is not null) - { - return GetImageInfo(parentWithImage, ImageType.Primary); - } - - return null; - } - - private BaseItem? GetFirstParentWithImageBelowUserRoot(BaseItem item) - { - if (item is null) - { - return null; - } - - if (item.HasImage(ImageType.Primary)) - { - return item; - } - - var parent = item.GetParent(); - if (parent is UserRootFolder) - { - return null; - } - - // terminate in case we went past user root folder (unlikely?) - if (parent is Folder folder && folder.IsRoot) - { - return null; - } - - return GetFirstParentWithImageBelowUserRoot(parent); - } - - private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type) - { - var imageInfo = item.GetImageInfo(type, 0); - string? tag = null; - - try - { - tag = _imageProcessor.GetImageCacheTag(item, type); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting image cache tag"); - } - - int? width = imageInfo.Width; - int? height = imageInfo.Height; - - if (width == 0 || height == 0) - { - width = null; - height = null; - } - else if (width == -1 || height == -1) - { - width = null; - height = null; - } - - var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty) - .TrimStart('.') - .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase); - - return new ImageDownloadInfo - { - ItemId = item.Id, - Type = type, - ImageTag = tag, - Width = width, - Height = height, - Format = inputFormat, - ItemImageInfo = imageInfo - }; - } - - public static string GetClientId(BaseItem item, StubType? stubType) - { - return GetClientId(item.Id, stubType); - } - - public static string GetClientId(Guid idValue, StubType? stubType) - { - var id = idValue.ToString("N", CultureInfo.InvariantCulture); - - if (stubType.HasValue) - { - id = stubType.Value.ToString().ToLowerInvariant() + "_" + id; - } - - return id; - } - - private (string Url, int? Width, int? Height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format) - { - var url = string.Format( - CultureInfo.InvariantCulture, - "{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0", - _serverAddress, - info.ItemId.ToString("N", CultureInfo.InvariantCulture), - info.Type, - info.ImageTag, - format, - maxWidth.ToString(CultureInfo.InvariantCulture), - maxHeight.ToString(CultureInfo.InvariantCulture)); - - var width = info.Width; - var height = info.Height; - - info.IsDirectStream = false; - - if (width.HasValue && height.HasValue) - { - var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight); - - width = newSize.Width; - height = newSize.Height; - - var normalizedFormat = format - .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase); - - if (string.Equals(info.Format, normalizedFormat, StringComparison.OrdinalIgnoreCase)) - { - info.IsDirectStream = maxWidth >= width.Value && maxHeight >= height.Value; - } - } - - // just lie - info.IsDirectStream = true; - - return (url, width, height); - } - - private class ImageDownloadInfo - { - internal Guid ItemId { get; set; } - - internal string? ImageTag { get; set; } - - internal ImageType Type { get; set; } - - internal int? Width { get; set; } - - internal int? Height { get; set; } - - internal bool IsDirectStream { get; set; } - - internal required string Format { get; set; } - - internal required ItemImageInfo ItemImageInfo { get; set; } - } - } -} diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs deleted file mode 100644 index 6db6f3ae30..0000000000 --- a/Emby.Dlna/Didl/Filter.cs +++ /dev/null @@ -1,28 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace Emby.Dlna.Didl -{ - public class Filter - { - private readonly string[] _fields; - private readonly bool _all; - - public Filter() - : this("*") - { - } - - public Filter(string filter) - { - _all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase); - _fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries); - } - - public bool Contains(string field) - { - return _all || Array.Exists(_fields, x => x.Equals(field, StringComparison.OrdinalIgnoreCase)); - } - } -} diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs deleted file mode 100644 index b66f53ece2..0000000000 --- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs +++ /dev/null @@ -1,58 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable CA1305 - -using System; -using System.IO; -using System.Text; - -namespace Emby.Dlna.Didl -{ - public class StringWriterWithEncoding : StringWriter - { - private readonly Encoding? _encoding; - - public StringWriterWithEncoding() - { - } - - public StringWriterWithEncoding(IFormatProvider formatProvider) - : base(formatProvider) - { - } - - public StringWriterWithEncoding(StringBuilder sb) - : base(sb) - { - } - - public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider) - : base(sb, formatProvider) - { - } - - public StringWriterWithEncoding(Encoding encoding) - { - _encoding = encoding; - } - - public StringWriterWithEncoding(IFormatProvider formatProvider, Encoding encoding) - : base(formatProvider) - { - _encoding = encoding; - } - - public StringWriterWithEncoding(StringBuilder sb, Encoding encoding) - : base(sb) - { - _encoding = encoding; - } - - public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider, Encoding encoding) - : base(sb, formatProvider) - { - _encoding = encoding; - } - - public override Encoding Encoding => _encoding ?? base.Encoding; - } -} diff --git a/Emby.Dlna/DlnaConfigurationFactory.cs b/Emby.Dlna/DlnaConfigurationFactory.cs deleted file mode 100644 index 6cc6b73a0c..0000000000 --- a/Emby.Dlna/DlnaConfigurationFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using Emby.Dlna.Configuration; -using MediaBrowser.Common.Configuration; - -namespace Emby.Dlna -{ - public class DlnaConfigurationFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new[] - { - new ConfigurationStore - { - Key = "dlna", - ConfigurationType = typeof(DlnaOptions) - } - }; - } - } -} diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs deleted file mode 100644 index d67cb67b54..0000000000 --- a/Emby.Dlna/DlnaManager.cs +++ /dev/null @@ -1,491 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.Json; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Emby.Dlna.Profiles; -using Emby.Dlna.Server; -using Jellyfin.Extensions.Json; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; - -namespace Emby.Dlna -{ - public class DlnaManager : IDlnaManager - { - private readonly IApplicationPaths _appPaths; - private readonly IXmlSerializer _xmlSerializer; - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - private readonly IServerApplicationHost _appHost; - private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - - private readonly Dictionary> _profiles = new Dictionary>(StringComparer.Ordinal); - - public DlnaManager( - IXmlSerializer xmlSerializer, - IFileSystem fileSystem, - IApplicationPaths appPaths, - ILoggerFactory loggerFactory, - IServerApplicationHost appHost) - { - _xmlSerializer = xmlSerializer; - _fileSystem = fileSystem; - _appPaths = appPaths; - _logger = loggerFactory.CreateLogger(); - _appHost = appHost; - } - - private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user"); - - private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system"); - - public async Task InitProfilesAsync() - { - try - { - await ExtractSystemProfilesAsync().ConfigureAwait(false); - Directory.CreateDirectory(UserProfilesPath); - LoadProfiles(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error extracting DLNA profiles."); - } - } - - private void LoadProfiles() - { - var list = GetProfiles(UserProfilesPath, DeviceProfileType.User) - .OrderBy(i => i.Name) - .ToList(); - - list.AddRange(GetProfiles(SystemProfilesPath, DeviceProfileType.System) - .OrderBy(i => i.Name)); - } - - public IEnumerable GetProfiles() - { - lock (_profiles) - { - return _profiles.Values - .OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1) - .ThenBy(i => i.Item1.Info.Name) - .Select(i => i.Item2) - .ToList(); - } - } - - /// - public DeviceProfile GetDefaultProfile() - { - return new DefaultProfile(); - } - - /// - public DeviceProfile? GetProfile(DeviceIdentification deviceInfo) - { - ArgumentNullException.ThrowIfNull(deviceInfo); - - var profile = GetProfiles() - .FirstOrDefault(i => i.Identification is not null && IsMatch(deviceInfo, i.Identification)); - - if (profile is null) - { - _logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo); - } - else - { - _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name); - } - - return profile; - } - - /// - /// Attempts to match a device with a profile. - /// Rules: - /// - If the profile field has no value, the field matches regardless of its contents. - /// - the profile field can be an exact match, or a reg exp. - /// - /// The of the device. - /// The of the profile. - /// True if they match. - public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) - { - return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName) - && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer) - && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl) - && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription) - && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName) - && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber) - && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl) - && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber); - } - - private bool IsRegexOrSubstringMatch(string input, string pattern) - { - if (string.IsNullOrEmpty(pattern)) - { - // In profile identification: An empty pattern matches anything. - return true; - } - - if (string.IsNullOrEmpty(input)) - { - // The profile contains a value, and the device doesn't. - return false; - } - - try - { - return input.Equals(pattern, StringComparison.OrdinalIgnoreCase) - || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - catch (ArgumentException ex) - { - _logger.LogError(ex, "Error evaluating regex pattern {Pattern}", pattern); - return false; - } - } - - /// - public DeviceProfile? GetProfile(IHeaderDictionary headers) - { - ArgumentNullException.ThrowIfNull(headers); - - var profile = GetProfiles().FirstOrDefault(i => i.Identification is not null && IsMatch(headers, i.Identification)); - if (profile is null) - { - _logger.LogDebug("No matching device profile found. {@Headers}", headers); - } - else - { - _logger.LogDebug("Found matching device profile: {0}", profile.Name); - } - - return profile; - } - - private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo) - { - return profileInfo.Headers.Any(i => IsMatch(headers, i)); - } - - private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header) - { - // Handle invalid user setup - if (string.IsNullOrEmpty(header.Name)) - { - return false; - } - - if (headers.TryGetValue(header.Name, out StringValues value)) - { - if (StringValues.IsNullOrEmpty(value)) - { - return false; - } - - switch (header.Match) - { - case HeaderMatchType.Equals: - return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); - case HeaderMatchType.Substring: - var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; - // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); - return isMatch; - case HeaderMatchType.Regex: - // Can't be null, we checked above the switch statement - return Regex.IsMatch(value!, header.Value, RegexOptions.IgnoreCase); - default: - throw new ArgumentException("Unrecognized HeaderMatchType"); - } - } - - return false; - } - - private IEnumerable GetProfiles(string path, DeviceProfileType type) - { - try - { - return _fileSystem.GetFilePaths(path) - .Where(i => Path.GetExtension(i.AsSpan()).Equals(".xml", StringComparison.OrdinalIgnoreCase)) - .Select(i => ParseProfileFile(i, type)) - .Where(i => i is not null) - .ToList()!; // We just filtered out all the nulls - } - catch (IOException) - { - return Array.Empty(); - } - } - - private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type) - { - lock (_profiles) - { - if (_profiles.TryGetValue(path, out Tuple? profileTuple)) - { - return profileTuple.Item2; - } - - try - { - var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path); - var profile = ReserializeProfile(tempProfile); - - profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); - - _profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); - - return profile; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error parsing profile file: {Path}", path); - - return null; - } - } - } - - /// - public DeviceProfile? GetProfile(string id) - { - ArgumentException.ThrowIfNullOrEmpty(id); - - var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase)); - - if (info is null) - { - return null; - } - - return ParseProfileFile(info.Path, info.Info.Type); - } - - private IEnumerable GetProfileInfosInternal() - { - lock (_profiles) - { - return _profiles.Values - .Select(i => i.Item1) - .OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1) - .ThenBy(i => i.Info.Name); - } - } - - /// - public IEnumerable GetProfileInfos() - { - return GetProfileInfosInternal().Select(i => i.Info); - } - - private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type) - { - return new InternalProfileInfo( - new DeviceProfileInfo - { - Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture), - Name = _fileSystem.GetFileNameWithoutExtension(file), - Type = type - }, - file.FullName); - } - - private async Task ExtractSystemProfilesAsync() - { - var namespaceName = GetType().Namespace + ".Profiles.Xml."; - - var systemProfilesPath = SystemProfilesPath; - - foreach (var name in _assembly.GetManifestResourceNames()) - { - if (!name.StartsWith(namespaceName, StringComparison.Ordinal)) - { - continue; - } - - var path = Path.Join( - systemProfilesPath, - Path.GetFileName(name.AsSpan())[namespaceName.Length..]); - - if (File.Exists(path)) - { - continue; - } - - // The stream should exist as we just got its name from GetManifestResourceNames - using (var stream = _assembly.GetManifestResourceStream(name)!) - { - Directory.CreateDirectory(systemProfilesPath); - - var fileOptions = AsyncFile.WriteOptions; - fileOptions.Mode = FileMode.CreateNew; - fileOptions.PreallocationSize = stream.Length; - var fileStream = new FileStream(path, fileOptions); - await using (fileStream.ConfigureAwait(false)) - { - await stream.CopyToAsync(fileStream).ConfigureAwait(false); - } - } - } - } - - /// - public void DeleteProfile(string id) - { - var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase)); - - if (info.Info.Type == DeviceProfileType.System) - { - throw new ArgumentException("System profiles cannot be deleted."); - } - - _fileSystem.DeleteFile(info.Path); - - lock (_profiles) - { - _profiles.Remove(info.Path); - } - } - - /// - public void CreateProfile(DeviceProfile profile) - { - profile = ReserializeProfile(profile); - - ArgumentException.ThrowIfNullOrEmpty(profile.Name); - - var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml"; - var path = Path.Combine(UserProfilesPath, newFilename); - - SaveProfile(profile, path, DeviceProfileType.User); - } - - /// - public void UpdateProfile(string profileId, DeviceProfile profile) - { - profile = ReserializeProfile(profile); - - ArgumentException.ThrowIfNullOrEmpty(profile.Id); - - ArgumentException.ThrowIfNullOrEmpty(profile.Name); - - var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profileId, StringComparison.OrdinalIgnoreCase)); - if (current.Info.Type == DeviceProfileType.System) - { - throw new ArgumentException("System profiles can't be edited"); - } - - var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml"; - var path = Path.Join(UserProfilesPath, newFilename); - - if (!string.Equals(path, current.Path, StringComparison.Ordinal)) - { - lock (_profiles) - { - _profiles.Remove(current.Path); - } - } - - SaveProfile(profile, path, DeviceProfileType.User); - } - - private void SaveProfile(DeviceProfile profile, string path, DeviceProfileType type) - { - lock (_profiles) - { - _profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); - } - - SerializeToXml(profile, path); - } - - internal void SerializeToXml(DeviceProfile profile, string path) - { - _xmlSerializer.SerializeToFile(profile, path); - } - - /// - /// Recreates the object using serialization, to ensure it's not a subclass. - /// If it's a subclass it may not serialize properly to xml (different root element tag name). - /// - /// The device profile. - /// The re-serialized device profile. - private DeviceProfile ReserializeProfile(DeviceProfile profile) - { - if (profile.GetType() == typeof(DeviceProfile)) - { - return profile; - } - - var json = JsonSerializer.Serialize(profile, _jsonOptions); - - // Output can't be null if the input isn't null - return JsonSerializer.Deserialize(json, _jsonOptions)!; - } - - /// - public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) - { - var profile = GetProfile(headers) ?? GetDefaultProfile(); - - var serverId = _appHost.SystemId; - - return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml(); - } - - /// - public ImageStream? GetIcon(string filename) - { - var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase) - ? ImageFormat.Png - : ImageFormat.Jpg; - - var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant(); - var stream = _assembly.GetManifestResourceStream(resource); - if (stream is null) - { - return null; - } - - return new ImageStream(stream) - { - Format = format - }; - } - - private class InternalProfileInfo - { - internal InternalProfileInfo(DeviceProfileInfo info, string path) - { - Info = info; - Path = path; - } - - internal DeviceProfileInfo Info { get; } - - internal string Path { get; } - } - } -} diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj deleted file mode 100644 index 7336482e56..0000000000 --- a/Emby.Dlna/Emby.Dlna.csproj +++ /dev/null @@ -1,90 +0,0 @@ - - - - - {805844AB-E92F-45E6-9D99-4F6D48D129A5} - - - - - - - - - - - - - - - net8.0 - false - true - - - - false - - - - - - all - runtime; build; native; contentfiles; analyzers - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs deleted file mode 100644 index 635d2c47a1..0000000000 --- a/Emby.Dlna/EventSubscriptionResponse.cs +++ /dev/null @@ -1,22 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace Emby.Dlna -{ - public class EventSubscriptionResponse - { - public EventSubscriptionResponse(string content, string contentType) - { - Content = content; - ContentType = contentType; - Headers = new Dictionary(); - } - - public string Content { get; set; } - - public string ContentType { get; set; } - - public Dictionary Headers { get; } - } -} diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs deleted file mode 100644 index ecbbdf9df9..0000000000 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ /dev/null @@ -1,183 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net.Http; -using System.Net.Mime; -using System.Text; -using System.Threading.Tasks; -using Jellyfin.Extensions; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.Eventing -{ - public class DlnaEventManager : IDlnaEventManager - { - private readonly ConcurrentDictionary _subscriptions = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - private readonly ILogger _logger; - private readonly IHttpClientFactory _httpClientFactory; - - public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - _logger = logger; - } - - public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl) - { - var subscription = GetSubscription(subscriptionId, false); - if (subscription is not null) - { - subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300; - int timeoutSeconds = subscription.TimeoutSeconds; - subscription.SubscriptionTime = DateTime.UtcNow; - - _logger.LogDebug( - "Renewing event subscription for {0} with timeout of {1} to {2}", - subscription.NotificationType, - timeoutSeconds, - subscription.CallbackUrl); - - return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds); - } - - return new EventSubscriptionResponse(string.Empty, "text/plain"); - } - - public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) - { - var timeout = ParseTimeout(requestedTimeoutString) ?? 300; - var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - - _logger.LogDebug( - "Creating event subscription for {0} with timeout of {1} to {2}", - notificationType, - timeout, - callbackUrl); - - _subscriptions.TryAdd(id, new EventSubscription - { - Id = id, - CallbackUrl = callbackUrl, - SubscriptionTime = DateTime.UtcNow, - TimeoutSeconds = timeout, - NotificationType = notificationType - }); - - return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout); - } - - private int? ParseTimeout(string header) - { - if (!string.IsNullOrEmpty(header)) - { - // Starts with SECOND- - if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) - { - return val; - } - } - - return null; - } - - public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) - { - _logger.LogDebug("Cancelling event subscription {0}", subscriptionId); - - _subscriptions.TryRemove(subscriptionId, out _); - - return new EventSubscriptionResponse(string.Empty, "text/plain"); - } - - private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds) - { - var response = new EventSubscriptionResponse(string.Empty, "text/plain"); - - response.Headers["SID"] = subscriptionId; - response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(CultureInfo.InvariantCulture)) : requestedTimeoutString; - - return response; - } - - public EventSubscription GetSubscription(string id) - { - return GetSubscription(id, false); - } - - private EventSubscription GetSubscription(string id, bool throwOnMissing) - { - if (!_subscriptions.TryGetValue(id, out EventSubscription e) && throwOnMissing) - { - throw new ResourceNotFoundException("Event with Id " + id + " not found."); - } - - return e; - } - - public Task TriggerEvent(string notificationType, IDictionary stateVariables) - { - var subs = _subscriptions.Values - .Where(i => !i.IsExpired && string.Equals(notificationType, i.NotificationType, StringComparison.OrdinalIgnoreCase)); - - var tasks = subs.Select(i => TriggerEvent(i, stateVariables)); - - return Task.WhenAll(tasks); - } - - private async Task TriggerEvent(EventSubscription subscription, IDictionary stateVariables) - { - var builder = new StringBuilder(); - - builder.Append(""); - builder.Append(""); - foreach (var key in stateVariables.Keys) - { - builder.Append("") - .Append('<') - .Append(key) - .Append('>') - .Append(stateVariables[key]) - .Append("') - .Append(""); - } - - builder.Append(""); - - using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl); - options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml); - options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType); - options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange"); - options.Headers.TryAddWithoutValidation("SID", subscription.Id); - options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(CultureInfo.InvariantCulture)); - - try - { - using var response = await _httpClientFactory.CreateClient(NamedClient.DirectIp) - .SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - catch - { - // Already logged at lower levels - } - finally - { - subscription.IncrementTriggerCount(); - } - } - } -} diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs deleted file mode 100644 index 4fd7f81695..0000000000 --- a/Emby.Dlna/Eventing/EventSubscription.cs +++ /dev/null @@ -1,35 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; - -namespace Emby.Dlna.Eventing -{ - public class EventSubscription - { - public string Id { get; set; } - - public string CallbackUrl { get; set; } - - public string NotificationType { get; set; } - - public DateTime SubscriptionTime { get; set; } - - public int TimeoutSeconds { get; set; } - - public long TriggerCount { get; set; } - - public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow; - - public void IncrementTriggerCount() - { - if (TriggerCount == long.MaxValue) - { - TriggerCount = 0; - } - - TriggerCount++; - } - } -} diff --git a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs b/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs deleted file mode 100644 index 82c80070a5..0000000000 --- a/Emby.Dlna/Extensions/DlnaServiceCollectionExtensions.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Globalization; -using System.Net; -using System.Net.Http; -using System.Text; -using Emby.Dlna.ConnectionManager; -using Emby.Dlna.ContentDirectory; -using Emby.Dlna.Main; -using Emby.Dlna.MediaReceiverRegistrar; -using Emby.Dlna.Ssdp; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Net; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Rssdp.Infrastructure; - -namespace Emby.Dlna.Extensions; - -/// -/// Extension methods for adding DLNA services. -/// -public static class DlnaServiceCollectionExtensions -{ - /// - /// Adds DLNA services to the provided . - /// - /// The . - /// The . - public static void AddDlnaServices( - this IServiceCollection services, - IServerApplicationHost applicationHost) - { - services.AddHttpClient(NamedClient.Dlna, c => - { - c.DefaultRequestHeaders.UserAgent.ParseAdd( - string.Format( - CultureInfo.InvariantCulture, - "{0}/{1} UPnP/1.0 {2}/{3}", - Environment.OSVersion.Platform, - Environment.OSVersion, - applicationHost.Name, - applicationHost.ApplicationVersionString)); - - c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", applicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0 - c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", applicationHost.FriendlyName); // REVIEW: where does this come from? - }) - .ConfigurePrimaryHttpMessageHandler(_ => new SocketsHttpHandler - { - AutomaticDecompression = DecompressionMethods.All, - RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8 - }); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(provider => new SsdpCommunicationsServer( - provider.GetRequiredService(), - provider.GetRequiredService(), - provider.GetRequiredService>()) - { - IsShared = true - }); - - services.AddHostedService(); - } -} diff --git a/Emby.Dlna/IConnectionManager.cs b/Emby.Dlna/IConnectionManager.cs deleted file mode 100644 index 9f643a9e64..0000000000 --- a/Emby.Dlna/IConnectionManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -#pragma warning disable CS1591 - -namespace Emby.Dlna -{ - public interface IConnectionManager : IDlnaEventManager, IUpnpService - { - } -} diff --git a/Emby.Dlna/IContentDirectory.cs b/Emby.Dlna/IContentDirectory.cs deleted file mode 100644 index 10f4d63866..0000000000 --- a/Emby.Dlna/IContentDirectory.cs +++ /dev/null @@ -1,8 +0,0 @@ -#pragma warning disable CS1591 - -namespace Emby.Dlna -{ - public interface IContentDirectory : IDlnaEventManager, IUpnpService - { - } -} diff --git a/Emby.Dlna/IDlnaEventManager.cs b/Emby.Dlna/IDlnaEventManager.cs deleted file mode 100644 index bb1eeb963d..0000000000 --- a/Emby.Dlna/IDlnaEventManager.cs +++ /dev/null @@ -1,34 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -namespace Emby.Dlna -{ - public interface IDlnaEventManager - { - /// - /// Cancels the event subscription. - /// - /// The subscription identifier. - /// The response. - EventSubscriptionResponse CancelEventSubscription(string subscriptionId); - - /// - /// Renews the event subscription. - /// - /// The subscription identifier. - /// The notification type. - /// The requested timeout as a string. - /// The callback url. - /// The response. - EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl); - - /// - /// Creates the event subscription. - /// - /// The notification type. - /// The requested timeout as a string. - /// The callback url. - /// The response. - EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl); - } -} diff --git a/Emby.Dlna/IMediaReceiverRegistrar.cs b/Emby.Dlna/IMediaReceiverRegistrar.cs deleted file mode 100644 index 43e934b53a..0000000000 --- a/Emby.Dlna/IMediaReceiverRegistrar.cs +++ /dev/null @@ -1,8 +0,0 @@ -#pragma warning disable CS1591 - -namespace Emby.Dlna -{ - public interface IMediaReceiverRegistrar : IDlnaEventManager, IUpnpService - { - } -} diff --git a/Emby.Dlna/IUpnpService.cs b/Emby.Dlna/IUpnpService.cs deleted file mode 100644 index 9e78595675..0000000000 --- a/Emby.Dlna/IUpnpService.cs +++ /dev/null @@ -1,22 +0,0 @@ -#pragma warning disable CS1591 - -using System.Threading.Tasks; - -namespace Emby.Dlna -{ - public interface IUpnpService - { - /// - /// Gets the content directory XML. - /// - /// System.String. - string GetServiceXml(); - - /// - /// Processes the control request. - /// - /// The request. - /// ControlResponse. - Task ProcessControlRequestAsync(ControlRequest request); - } -} diff --git a/Emby.Dlna/Images/logo120.jpg b/Emby.Dlna/Images/logo120.jpg deleted file mode 100644 index c70f4db0de76cd2b0e92a0ecdf358620ee6cafb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5337 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 a2Uk5$qk9aw<2_9+>S~|z_ggLMZ1f-IbKFb- diff --git a/Emby.Dlna/Images/logo120.png b/Emby.Dlna/Images/logo120.png deleted file mode 100644 index 14f6c8d5f66010de7b3f132c554e3ab53d49001e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6201 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 XU2ajqLA}4W00000NkvXXu0mjfyde4m diff --git a/Emby.Dlna/Images/logo240.jpg b/Emby.Dlna/Images/logo240.jpg deleted file mode 100644 index 78a27f1b544ab929cdde18d4b7744019cf0267d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11483 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

d|iq~?-py%Wk#nmD3J3mB|JkBRL^yH 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 diff --git a/Emby.Dlna/Images/people48.jpg b/Emby.Dlna/Images/people48.jpg deleted file mode 100644 index 3ed287062b0d8cfa34aabbcd265b05ec4010a386..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 740 zcmex=``2_j6xdp@o1cgOJMMZh|#U;coyG6VInuyV4pa*FVB^NNrR z{vTivVqg+vWEN!ne}qAbfsuh32$29g2PZQN8&FPLfB|SLBNGc7OrDX6 znT3IsO^}0KQApU(QA9E{amp2?q(Y!#5ulIJ07z62Q}h-C4>Kds1AFo!#m|iK5^%{=j`>JUN~ze@6PPR=|Te24wWq0 z9#Fpb7xU{-vnf)ApDkaOUnu&|aJc)o$io3;s{g1tl z&4M1YlP9_Loneh)3gvHR|049thi}H61urdA8*~>iu~{S*q&8TywO-*jI&ku@0$a$b zA1dL&N0S0{ZGAjs>w3HwBB>guY&?B_rGB) z3*&R6bt^d2~REAob2xHXIEhuVWpl59^He{e8ah;VQeEO78?n85Vte8Xji agBMXFvPQ%$YT7?=y2cb@~aQxuu3w13(}UfFeA= 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 diff --git a/Emby.Dlna/Main/DlnaHost.cs b/Emby.Dlna/Main/DlnaHost.cs deleted file mode 100644 index 58db7c26fc..0000000000 --- a/Emby.Dlna/Main/DlnaHost.cs +++ /dev/null @@ -1,387 +0,0 @@ -#pragma warning disable CA1031 // Do not catch general exception types. - -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; -using Emby.Dlna.PlayTo; -using Emby.Dlna.Ssdp; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Globalization; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Rssdp; -using Rssdp.Infrastructure; - -namespace Emby.Dlna.Main; - -///

-/// A that manages a DLNA server. -/// -public sealed class DlnaHost : IHostedService, IDisposable -{ - private readonly ILogger _logger; - private readonly IServerConfigurationManager _config; - private readonly IServerApplicationHost _appHost; - private readonly ISessionManager _sessionManager; - private readonly IHttpClientFactory _httpClientFactory; - private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; - private readonly IDlnaManager _dlnaManager; - private readonly IImageProcessor _imageProcessor; - private readonly IUserDataManager _userDataManager; - private readonly ILocalizationManager _localization; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly IDeviceDiscovery _deviceDiscovery; - private readonly ISsdpCommunicationsServer _communicationsServer; - private readonly INetworkManager _networkManager; - private readonly object _syncLock = new(); - - private SsdpDevicePublisher? _publisher; - private PlayToManager? _manager; - private bool _disposed; - - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - /// The . - public DlnaHost( - IServerConfigurationManager config, - ILoggerFactory loggerFactory, - IServerApplicationHost appHost, - ISessionManager sessionManager, - IHttpClientFactory httpClientFactory, - ILibraryManager libraryManager, - IUserManager userManager, - IDlnaManager dlnaManager, - IImageProcessor imageProcessor, - IUserDataManager userDataManager, - ILocalizationManager localizationManager, - IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, - IMediaEncoder mediaEncoder, - ISsdpCommunicationsServer communicationsServer, - INetworkManager networkManager) - { - _config = config; - _appHost = appHost; - _sessionManager = sessionManager; - _httpClientFactory = httpClientFactory; - _libraryManager = libraryManager; - _userManager = userManager; - _dlnaManager = dlnaManager; - _imageProcessor = imageProcessor; - _userDataManager = userDataManager; - _localization = localizationManager; - _mediaSourceManager = mediaSourceManager; - _deviceDiscovery = deviceDiscovery; - _mediaEncoder = mediaEncoder; - _communicationsServer = communicationsServer; - _networkManager = networkManager; - _logger = loggerFactory.CreateLogger(); - } - - /// - public async Task StartAsync(CancellationToken cancellationToken) - { - var netConfig = _config.GetConfiguration(NetworkConfigurationStore.StoreKey); - if (_appHost.ListenWithHttps && netConfig.RequireHttps) - { - if (_config.GetDlnaConfiguration().EnableServer) - { - _logger.LogError("The DLNA specification does not support HTTPS."); - } - - // No use starting as dlna won't work, as we're running purely on HTTPS. - return; - } - - await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); - ReloadComponents(); - - _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated; - } - - /// - public Task StopAsync(CancellationToken cancellationToken) - { - Stop(); - - return Task.CompletedTask; - } - - /// - public void Dispose() - { - if (!_disposed) - { - Stop(); - _disposed = true; - } - } - - private void OnNamedConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs e) - { - if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) - { - ReloadComponents(); - } - } - - private void ReloadComponents() - { - var options = _config.GetDlnaConfiguration(); - StartDeviceDiscovery(); - - if (options.EnableServer) - { - StartDevicePublisher(options); - } - else - { - DisposeDevicePublisher(); - } - - if (options.EnablePlayTo) - { - StartPlayToManager(); - } - else - { - DisposePlayToManager(); - } - } - - private static string CreateUuid(string text) - { - if (!Guid.TryParse(text, out var guid)) - { - guid = text.GetMD5(); - } - - return guid.ToString("D", CultureInfo.InvariantCulture); - } - - private static void SetProperties(SsdpDevice device, string fullDeviceType) - { - var serviceParts = fullDeviceType - .Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase) - .Split(':'); - - device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-'); - device.DeviceClass = serviceParts[1]; - device.DeviceType = serviceParts[2]; - } - - private void StartDeviceDiscovery() - { - try - { - ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error starting device discovery"); - } - } - - private void StartDevicePublisher(Configuration.DlnaOptions options) - { - if (_publisher is not null) - { - return; - } - - try - { - _publisher = new SsdpDevicePublisher( - _communicationsServer, - Environment.OSVersion.Platform.ToString(), - // Can not use VersionString here since that includes OS and version - Environment.OSVersion.Version.ToString(), - _config.GetDlnaConfiguration().SendOnlyMatchedHost) - { - LogFunction = msg => _logger.LogDebug("{Msg}", msg), - SupportPnpRootDevice = false - }; - - RegisterServerEndpoints(); - - if (options.BlastAliveMessages) - { - _publisher.StartSendingAliveNotifications(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds)); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error registering endpoint"); - } - } - - private void RegisterServerEndpoints() - { - var udn = CreateUuid(_appHost.SystemId); - var descriptorUri = "/dlna/" + udn + "/description.xml"; - - // Only get bind addresses in LAN - // IPv6 is currently unsupported - var validInterfaces = _networkManager.GetInternalBindAddresses() - .Where(x => x.AddressFamily != AddressFamily.InterNetworkV6) - .ToList(); - - if (validInterfaces.Count == 0) - { - // No interfaces returned, fall back to loopback - validInterfaces = _networkManager.GetLoopbacks().ToList(); - } - - foreach (var intf in validInterfaces) - { - var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; - - _logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, intf.Address); - - var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(intf.Address, false) + descriptorUri); - - var device = new SsdpRootDevice - { - CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info. - Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document. - Address = intf.Address, - PrefixLength = NetworkUtils.MaskToCidr(intf.Subnet.Prefix), - FriendlyName = "Jellyfin", - Manufacturer = "Jellyfin", - ModelName = "Jellyfin Server", - Uuid = udn - // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. - }; - - SetProperties(device, fullService); - _publisher!.AddDevice(device); - - var embeddedDevices = new[] - { - "urn:schemas-upnp-org:service:ContentDirectory:1", - "urn:schemas-upnp-org:service:ConnectionManager:1", - // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" - }; - - foreach (var subDevice in embeddedDevices) - { - var embeddedDevice = new SsdpEmbeddedDevice - { - FriendlyName = device.FriendlyName, - Manufacturer = device.Manufacturer, - ModelName = device.ModelName, - Uuid = udn - // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. - }; - - SetProperties(embeddedDevice, subDevice); - device.AddDevice(embeddedDevice); - } - } - } - - private void StartPlayToManager() - { - lock (_syncLock) - { - if (_manager is not null) - { - return; - } - - try - { - _manager = new PlayToManager( - _logger, - _sessionManager, - _libraryManager, - _userManager, - _dlnaManager, - _appHost, - _imageProcessor, - _deviceDiscovery, - _httpClientFactory, - _userDataManager, - _localization, - _mediaSourceManager, - _mediaEncoder); - - _manager.Start(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error starting PlayTo manager"); - } - } - } - - private void DisposePlayToManager() - { - lock (_syncLock) - { - if (_manager is not null) - { - try - { - _logger.LogInformation("Disposing PlayToManager"); - _manager.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing PlayTo manager"); - } - - _manager = null; - } - } - } - - private void DisposeDevicePublisher() - { - if (_publisher is not null) - { - _logger.LogInformation("Disposing SsdpDevicePublisher"); - _publisher.Dispose(); - _publisher = null; - } - } - - private void Stop() - { - DisposeDevicePublisher(); - DisposePlayToManager(); - } -} diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs deleted file mode 100644 index d8fb127420..0000000000 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml; -using Emby.Dlna.Service; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.MediaReceiverRegistrar -{ - /// - /// Defines the . - /// - public class ControlHandler : BaseControlHandler - { - /// - /// Initializes a new instance of the class. - /// - /// The for use with the instance. - /// The for use with the instance. - public ControlHandler(IServerConfigurationManager config, ILogger logger) - : base(config, logger) - { - } - - /// - protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter) - { - if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase)) - { - HandleIsAuthorized(xmlWriter); - return; - } - - if (string.Equals(methodName, "IsValidated", StringComparison.OrdinalIgnoreCase)) - { - HandleIsValidated(xmlWriter); - return; - } - - throw new ResourceNotFoundException("Unexpected control request name: " + methodName); - } - - /// - /// Records that the handle is authorized in the xml stream. - /// - /// The . - private static void HandleIsAuthorized(XmlWriter xmlWriter) - => xmlWriter.WriteElementString("Result", "1"); - - /// - /// Records that the handle is validated in the xml stream. - /// - /// The . - private static void HandleIsValidated(XmlWriter xmlWriter) - => xmlWriter.WriteElementString("Result", "1"); - } -} diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs deleted file mode 100644 index a5aae515c4..0000000000 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Net.Http; -using System.Threading.Tasks; -using Emby.Dlna.Service; -using MediaBrowser.Controller.Configuration; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.MediaReceiverRegistrar -{ - /// - /// Defines the . - /// - public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar - { - private readonly IServerConfigurationManager _config; - - /// - /// Initializes a new instance of the class. - /// - /// The for use with the instance. - /// The for use with the instance. - /// The for use with the instance. - public MediaReceiverRegistrarService( - ILogger logger, - IHttpClientFactory httpClientFactory, - IServerConfigurationManager config) - : base(logger, httpClientFactory) - { - _config = config; - } - - /// - public string GetServiceXml() - { - return MediaReceiverRegistrarXmlBuilder.GetXml(); - } - - /// - public Task ProcessControlRequestAsync(ControlRequest request) - { - return new ControlHandler( - _config, - Logger) - .ProcessControlRequestAsync(request); - } - } -} diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs deleted file mode 100644 index f3789a791c..0000000000 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Collections.Generic; -using Emby.Dlna.Common; -using Emby.Dlna.Service; - -namespace Emby.Dlna.MediaReceiverRegistrar -{ - /// - /// Defines the . - /// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drmnd/5d37515e-7a63-4709-8258-8fd4e0ed4482. - /// - public static class MediaReceiverRegistrarXmlBuilder - { - /// - /// Retrieves an XML description of the X_MS_MediaReceiverRegistrar. - /// - /// An XML representation of this service. - public static string GetXml() - { - return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); - } - - /// - /// The a list of all the state variables for this invocation. - /// - /// The . - private static IEnumerable GetStateVariables() - { - var list = new List - { - new StateVariable - { - Name = "AuthorizationGrantedUpdateID", - DataType = "ui4", - SendsEvents = true - }, - - new StateVariable - { - Name = "A_ARG_TYPE_DeviceID", - DataType = "string", - SendsEvents = false - }, - - new StateVariable - { - Name = "AuthorizationDeniedUpdateID", - DataType = "ui4", - SendsEvents = true - }, - - new StateVariable - { - Name = "ValidationSucceededUpdateID", - DataType = "ui4", - SendsEvents = true - }, - - new StateVariable - { - Name = "A_ARG_TYPE_RegistrationRespMsg", - DataType = "bin.base64", - SendsEvents = false - }, - - new StateVariable - { - Name = "A_ARG_TYPE_RegistrationReqMsg", - DataType = "bin.base64", - SendsEvents = false - }, - - new StateVariable - { - Name = "ValidationRevokedUpdateID", - DataType = "ui4", - SendsEvents = true - }, - - new StateVariable - { - Name = "A_ARG_TYPE_Result", - DataType = "int", - SendsEvents = false - } - }; - - return list; - } - } -} diff --git a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs deleted file mode 100644 index 56788ae223..0000000000 --- a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System.Collections.Generic; -using Emby.Dlna.Common; - -namespace Emby.Dlna.MediaReceiverRegistrar -{ - /// - /// Defines the . - /// - public static class ServiceActionListBuilder - { - /// - /// Returns a list of services that this instance provides. - /// - /// An . - public static IEnumerable GetActions() - { - return new[] - { - GetIsValidated(), - GetIsAuthorized(), - GetRegisterDevice(), - GetGetAuthorizationDeniedUpdateID(), - GetGetAuthorizationGrantedUpdateID(), - GetGetValidationRevokedUpdateID(), - GetGetValidationSucceededUpdateID() - }; - } - - /// - /// Returns the action details for "IsValidated". - /// - /// The . - private static ServiceAction GetIsValidated() - { - var action = new ServiceAction - { - Name = "IsValidated" - }; - - action.ArgumentList.Add(new Argument - { - Name = "DeviceID", - Direction = "in" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out" - }); - - return action; - } - - /// - /// Returns the action details for "IsAuthorized". - /// - /// The . - private static ServiceAction GetIsAuthorized() - { - var action = new ServiceAction - { - Name = "IsAuthorized" - }; - - action.ArgumentList.Add(new Argument - { - Name = "DeviceID", - Direction = "in" - }); - - action.ArgumentList.Add(new Argument - { - Name = "Result", - Direction = "out" - }); - - return action; - } - - /// - /// Returns the action details for "RegisterDevice". - /// - /// The . - private static ServiceAction GetRegisterDevice() - { - var action = new ServiceAction - { - Name = "RegisterDevice" - }; - - action.ArgumentList.Add(new Argument - { - Name = "RegistrationReqMsg", - Direction = "in" - }); - - action.ArgumentList.Add(new Argument - { - Name = "RegistrationRespMsg", - Direction = "out" - }); - - return action; - } - - /// - /// Returns the action details for "GetValidationSucceededUpdateID". - /// - /// The . - private static ServiceAction GetGetValidationSucceededUpdateID() - { - var action = new ServiceAction - { - Name = "GetValidationSucceededUpdateID" - }; - - action.ArgumentList.Add(new Argument - { - Name = "ValidationSucceededUpdateID", - Direction = "out" - }); - - return action; - } - - /// - /// Returns the action details for "GetGetAuthorizationDeniedUpdateID". - /// - /// The . - private static ServiceAction GetGetAuthorizationDeniedUpdateID() - { - var action = new ServiceAction - { - Name = "GetAuthorizationDeniedUpdateID" - }; - - action.ArgumentList.Add(new Argument - { - Name = "AuthorizationDeniedUpdateID", - Direction = "out" - }); - - return action; - } - - /// - /// Returns the action details for "GetValidationRevokedUpdateID". - /// - /// The . - private static ServiceAction GetGetValidationRevokedUpdateID() - { - var action = new ServiceAction - { - Name = "GetValidationRevokedUpdateID" - }; - - action.ArgumentList.Add(new Argument - { - Name = "ValidationRevokedUpdateID", - Direction = "out" - }); - - return action; - } - - /// - /// Returns the action details for "GetAuthorizationGrantedUpdateID". - /// - /// The . - private static ServiceAction GetGetAuthorizationGrantedUpdateID() - { - var action = new ServiceAction - { - Name = "GetAuthorizationGrantedUpdateID" - }; - - action.ArgumentList.Add(new Argument - { - Name = "AuthorizationGrantedUpdateID", - Direction = "out" - }); - - return action; - } - } -} diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs deleted file mode 100644 index 18fa196508..0000000000 --- a/Emby.Dlna/PlayTo/Device.cs +++ /dev/null @@ -1,1264 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net.Http; -using System.Security; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using Emby.Dlna.Common; -using Emby.Dlna.Ssdp; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.PlayTo -{ - public class Device : IDisposable - { - private readonly IHttpClientFactory _httpClientFactory; - - private readonly ILogger _logger; - - private readonly object _timerLock = new object(); - private Timer? _timer; - private int _muteVol; - private int _volume; - private DateTime _lastVolumeRefresh; - private bool _volumeRefreshActive; - private int _connectFailureCount; - private bool _disposed; - - public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, ILogger logger) - { - Properties = deviceProperties; - _httpClientFactory = httpClientFactory; - _logger = logger; - } - - public event EventHandler? PlaybackStart; - - public event EventHandler? PlaybackProgress; - - public event EventHandler? PlaybackStopped; - - public event EventHandler? MediaChanged; - - public DeviceInfo Properties { get; set; } - - public bool IsMuted { get; set; } - - public int Volume - { - get - { - RefreshVolumeIfNeeded().GetAwaiter().GetResult(); - return _volume; - } - - set => _volume = value; - } - - public TimeSpan? Duration { get; set; } - - public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0); - - public TransportState TransportState { get; private set; } - - public bool IsPlaying => TransportState == TransportState.PLAYING; - - public bool IsPaused => TransportState == TransportState.PAUSED_PLAYBACK; - - public bool IsStopped => TransportState == TransportState.STOPPED; - - public Action? OnDeviceUnavailable { get; set; } - - private TransportCommands? AvCommands { get; set; } - - private TransportCommands? RendererCommands { get; set; } - - public UBaseObject? CurrentMediaInfo { get; private set; } - - public void Start() - { - _logger.LogDebug("Dlna Device.Start"); - _timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite); - } - - private Task RefreshVolumeIfNeeded() - { - if (_volumeRefreshActive - && DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5)) - { - _lastVolumeRefresh = DateTime.UtcNow; - return RefreshVolume(); - } - - return Task.CompletedTask; - } - - private async Task RefreshVolume(CancellationToken cancellationToken = default) - { - if (_disposed) - { - return; - } - - try - { - await GetVolume(cancellationToken).ConfigureAwait(false); - await GetMute(cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error updating device volume info for {DeviceName}", Properties.Name); - } - } - - private void RestartTimer(bool immediate = false) - { - lock (_timerLock) - { - if (_disposed) - { - return; - } - - _volumeRefreshActive = true; - - var time = immediate ? 100 : 10000; - _timer?.Change(time, Timeout.Infinite); - } - } - - /// - /// Restarts the timer in inactive mode. - /// - private void RestartTimerInactive() - { - lock (_timerLock) - { - if (_disposed) - { - return; - } - - _volumeRefreshActive = false; - - _timer?.Change(Timeout.Infinite, Timeout.Infinite); - } - } - - public Task VolumeDown(CancellationToken cancellationToken) - { - var sendVolume = Math.Max(Volume - 5, 0); - - return SetVolume(sendVolume, cancellationToken); - } - - public Task VolumeUp(CancellationToken cancellationToken) - { - var sendVolume = Math.Min(Volume + 5, 100); - - return SetVolume(sendVolume, cancellationToken); - } - - public Task ToggleMute(CancellationToken cancellationToken) - { - if (IsMuted) - { - return Unmute(cancellationToken); - } - - return Mute(cancellationToken); - } - - public async Task Mute(CancellationToken cancellationToken) - { - var success = await SetMute(true, cancellationToken).ConfigureAwait(true); - - if (!success) - { - await SetVolume(0, cancellationToken).ConfigureAwait(false); - } - } - - public async Task Unmute(CancellationToken cancellationToken) - { - var success = await SetMute(false, cancellationToken).ConfigureAwait(true); - - if (!success) - { - var sendVolume = _muteVol <= 0 ? 20 : _muteVol; - - await SetVolume(sendVolume, cancellationToken).ConfigureAwait(false); - } - } - - private DeviceService? GetServiceRenderingControl() - { - var services = Properties.Services; - - return services.FirstOrDefault(s => string.Equals(s.ServiceType, "urn:schemas-upnp-org:service:RenderingControl:1", StringComparison.OrdinalIgnoreCase)) ?? - services.FirstOrDefault(s => (s.ServiceType ?? string.Empty).StartsWith("urn:schemas-upnp-org:service:RenderingControl", StringComparison.OrdinalIgnoreCase)); - } - - private DeviceService? GetAvTransportService() - { - var services = Properties.Services; - - return services.FirstOrDefault(s => string.Equals(s.ServiceType, "urn:schemas-upnp-org:service:AVTransport:1", StringComparison.OrdinalIgnoreCase)) ?? - services.FirstOrDefault(s => (s.ServiceType ?? string.Empty).StartsWith("urn:schemas-upnp-org:service:AVTransport", StringComparison.OrdinalIgnoreCase)); - } - - private async Task SetMute(bool mute, CancellationToken cancellationToken) - { - var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - - var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); - if (command is null) - { - return false; - } - - var service = GetServiceRenderingControl(); - - if (service is null) - { - return false; - } - - _logger.LogDebug("Setting mute"); - var value = mute ? 1 : 0; - - await new DlnaHttpClient(_logger, _httpClientFactory) - .SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - rendererCommands!.BuildPost(command, service.ServiceType, value), // null checked above - cancellationToken: cancellationToken) - .ConfigureAwait(false); - - IsMuted = mute; - - return true; - } - - /// - /// Sets volume on a scale of 0-100. - /// - /// The volume on a scale of 0-100. - /// The cancellation token to cancel operation. - /// A representing the asynchronous operation. - public async Task SetVolume(int value, CancellationToken cancellationToken) - { - var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - - var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); - if (command is null) - { - return; - } - - var service = GetServiceRenderingControl() ?? throw new InvalidOperationException("Unable to find service"); - - // Set it early and assume it will succeed - // Remote control will perform better - Volume = value; - - await new DlnaHttpClient(_logger, _httpClientFactory) - .SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - rendererCommands!.BuildPost(command, service.ServiceType, value), // null checked above - cancellationToken: cancellationToken) - .ConfigureAwait(false); - } - - public async Task Seek(TimeSpan value, CancellationToken cancellationToken) - { - var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - - var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); - if (command is null) - { - return; - } - - var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service"); - await new DlnaHttpClient(_logger, _httpClientFactory) - .SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - avCommands!.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"), // null checked above - cancellationToken: cancellationToken) - .ConfigureAwait(false); - - RestartTimer(true); - } - - public async Task SetAvTransport(string url, string? header, string metaData, CancellationToken cancellationToken) - { - var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - - url = url.Replace("&", "&", StringComparison.Ordinal); - - _logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header); - - var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); - if (command is null) - { - return; - } - - var dictionary = new Dictionary - { - { "CurrentURI", url }, - { "CurrentURIMetaData", CreateDidlMeta(metaData) } - }; - - var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service"); - var post = avCommands!.BuildPost(command, service.ServiceType, url, dictionary); // null checked above - await new DlnaHttpClient(_logger, _httpClientFactory) - .SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - post, - header: header, - cancellationToken: cancellationToken) - .ConfigureAwait(false); - - await Task.Delay(50, cancellationToken).ConfigureAwait(false); - - try - { - await SetPlay(avCommands, cancellationToken).ConfigureAwait(false); - } - catch - { - // Some devices will throw an error if you tell it to play when it's already playing - // Others won't - } - - RestartTimer(true); - } - - /* - * SetNextAvTransport is used to specify to the DLNA device what is the next track to play. - * Without that information, the next track command on the device does not work. - */ - public async Task SetNextAvTransport(string url, string? header, string metaData, CancellationToken cancellationToken = default) - { - var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - - url = url.Replace("&", "&", StringComparison.Ordinal); - - _logger.LogDebug("{PropertyName} - SetNextAvTransport Uri: {Url} DlnaHeaders: {Header}", Properties.Name, url, header); - - var command = avCommands?.ServiceActions.FirstOrDefault(c => string.Equals(c.Name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase)); - if (command is null) - { - return; - } - - var dictionary = new Dictionary - { - { "NextURI", url }, - { "NextURIMetaData", CreateDidlMeta(metaData) } - }; - - var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service"); - var post = avCommands!.BuildPost(command, service.ServiceType, url, dictionary); // null checked above - await new DlnaHttpClient(_logger, _httpClientFactory) - .SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header, cancellationToken) - .ConfigureAwait(false); - } - - private static string CreateDidlMeta(string value) - { - if (string.IsNullOrEmpty(value)) - { - return string.Empty; - } - - return SecurityElement.Escape(value); - } - - private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken) - { - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play"); - if (command is null) - { - return Task.CompletedTask; - } - - var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service"); - return new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - avCommands.BuildPost(command, service.ServiceType, 1), - cancellationToken: cancellationToken); - } - - public async Task SetPlay(CancellationToken cancellationToken) - { - var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - if (avCommands is null) - { - return; - } - - await SetPlay(avCommands, cancellationToken).ConfigureAwait(false); - - RestartTimer(true); - } - - public async Task SetStop(CancellationToken cancellationToken) - { - var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - - var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); - if (command is null) - { - return; - } - - var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service"); - await new DlnaHttpClient(_logger, _httpClientFactory) - .SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - avCommands!.BuildPost(command, service.ServiceType, 1), // null checked above - cancellationToken: cancellationToken) - .ConfigureAwait(false); - - RestartTimer(true); - } - - public async Task SetPause(CancellationToken cancellationToken) - { - var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - - var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); - if (command is null) - { - return; - } - - var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service"); - await new DlnaHttpClient(_logger, _httpClientFactory) - .SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - avCommands!.BuildPost(command, service.ServiceType, 1), // null checked above - cancellationToken: cancellationToken) - .ConfigureAwait(false); - - TransportState = TransportState.PAUSED_PLAYBACK; - - RestartTimer(true); - } - - private async void TimerCallback(object? sender) - { - if (_disposed) - { - return; - } - - try - { - var cancellationToken = CancellationToken.None; - - var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - - if (avCommands is null) - { - return; - } - - var transportState = await GetTransportInfo(avCommands, cancellationToken).ConfigureAwait(false); - - if (_disposed) - { - return; - } - - if (transportState.HasValue) - { - // If we're not playing anything no need to get additional data - if (transportState.Value == TransportState.STOPPED) - { - UpdateMediaInfo(null, transportState.Value); - } - else - { - var tuple = await GetPositionInfo(avCommands, cancellationToken).ConfigureAwait(false); - - var currentObject = tuple.Track; - - if (tuple.Success && currentObject is null) - { - currentObject = await GetMediaInfo(avCommands, cancellationToken).ConfigureAwait(false); - } - - if (currentObject is not null) - { - UpdateMediaInfo(currentObject, transportState.Value); - } - } - - _connectFailureCount = 0; - - if (_disposed) - { - return; - } - - // If we're not playing anything make sure we don't get data more often than necessary to keep the Session alive - if (transportState.Value == TransportState.STOPPED) - { - RestartTimerInactive(); - } - else - { - RestartTimer(); - } - } - else - { - RestartTimerInactive(); - } - } - catch (Exception ex) - { - if (_disposed) - { - return; - } - - _logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name); - - _connectFailureCount++; - - if (_connectFailureCount >= 3) - { - var action = OnDeviceUnavailable; - if (action is not null) - { - _logger.LogDebug("Disposing device due to loss of connection"); - action(); - return; - } - } - - RestartTimerInactive(); - } - } - - private async Task GetVolume(CancellationToken cancellationToken) - { - if (_disposed) - { - return; - } - - var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - - var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); - if (command is null) - { - return; - } - - var service = GetServiceRenderingControl(); - - if (service is null) - { - return; - } - - var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - rendererCommands!.BuildPost(command, service.ServiceType), // null checked above - cancellationToken: cancellationToken).ConfigureAwait(false); - - if (result is null || result.Document is null) - { - return; - } - - var volume = result.Document.Descendants(UPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i is not null); - var volumeValue = volume?.Value; - - if (string.IsNullOrWhiteSpace(volumeValue)) - { - return; - } - - Volume = int.Parse(volumeValue, CultureInfo.InvariantCulture); - - if (Volume > 0) - { - _muteVol = Volume; - } - } - - private async Task GetMute(CancellationToken cancellationToken) - { - if (_disposed) - { - return; - } - - var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - - var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); - if (command is null) - { - return; - } - - var service = GetServiceRenderingControl(); - - if (service is null) - { - return; - } - - var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - rendererCommands!.BuildPost(command, service.ServiceType), // null checked above - cancellationToken: cancellationToken).ConfigureAwait(false); - - if (result is null || result.Document is null) - { - return; - } - - var valueNode = result.Document.Descendants(UPnpNamespaces.RenderingControl + "GetMuteResponse") - .Select(i => i.Element("CurrentMute")) - .FirstOrDefault(i => i is not null); - - IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase); - } - - private async Task GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken) - { - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo"); - if (command is null) - { - return null; - } - - var service = GetAvTransportService(); - if (service is null) - { - return null; - } - - var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - avCommands.BuildPost(command, service.ServiceType), - cancellationToken: cancellationToken).ConfigureAwait(false); - - if (result is null || result.Document is null) - { - return null; - } - - var transportState = - result.Document.Descendants(UPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i is not null); - - var transportStateValue = transportState?.Value; - - if (transportStateValue is not null - && Enum.TryParse(transportStateValue, true, out TransportState state)) - { - return state; - } - - return null; - } - - private async Task GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken) - { - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo"); - if (command is null) - { - return null; - } - - var service = GetAvTransportService(); - if (service is null) - { - throw new InvalidOperationException("Unable to find service"); - } - - var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - if (rendererCommands is null) - { - return null; - } - - var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - rendererCommands.BuildPost(command, service.ServiceType), - cancellationToken: cancellationToken).ConfigureAwait(false); - - if (result is null || result.Document is null) - { - return null; - } - - var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault(); - - if (track is null) - { - return null; - } - - var e = track.Element(UPnpNamespaces.Items) ?? track; - - var elementString = (string)e; - - if (!string.IsNullOrWhiteSpace(elementString)) - { - return UpnpContainer.Create(e); - } - - track = result.Document.Descendants("CurrentURI").FirstOrDefault(); - - if (track is null) - { - return null; - } - - e = track.Element(UPnpNamespaces.Items) ?? track; - - elementString = (string)e; - - if (!string.IsNullOrWhiteSpace(elementString)) - { - return new UBaseObject - { - Url = elementString - }; - } - - return null; - } - - private async Task<(bool Success, UBaseObject? Track)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken) - { - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo"); - if (command is null) - { - return (false, null); - } - - var service = GetAvTransportService(); - - if (service is null) - { - throw new InvalidOperationException("Unable to find service"); - } - - var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - - if (rendererCommands is null) - { - return (false, null); - } - - var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync( - Properties.BaseUrl, - service, - command.Name, - rendererCommands.BuildPost(command, service.ServiceType), - cancellationToken: cancellationToken).ConfigureAwait(false); - - if (result is null || result.Document is null) - { - return (false, null); - } - - var trackUriElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i is not null); - var trackUri = trackUriElem?.Value; - - var durationElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i is not null); - var duration = durationElem?.Value; - - if (!string.IsNullOrWhiteSpace(duration) - && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) - { - Duration = TimeSpan.Parse(duration, CultureInfo.InvariantCulture); - } - else - { - Duration = null; - } - - var positionElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i is not null); - var position = positionElem?.Value; - - if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) - { - Position = TimeSpan.Parse(position, CultureInfo.InvariantCulture); - } - - var track = result.Document.Descendants("TrackMetaData").FirstOrDefault(); - - if (track is null) - { - // If track is null, some vendors do this, use GetMediaInfo instead. - return (true, null); - } - - var trackString = (string)track; - - if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) - { - return (true, null); - } - - XElement? uPnpResponse = null; - - try - { - uPnpResponse = ParseResponse(trackString); - } - catch (Exception ex) - { - _logger.LogError(ex, "Uncaught exception while parsing xml"); - } - - if (uPnpResponse is null) - { - _logger.LogError("Failed to parse xml: \n {Xml}", trackString); - return (true, null); - } - - var e = uPnpResponse.Element(UPnpNamespaces.Items); - - var uTrack = CreateUBaseObject(e, trackUri); - - return (true, uTrack); - } - - private XElement? ParseResponse(string xml) - { - // Handle different variations sent back by devices. - try - { - return XElement.Parse(xml); - } - catch (XmlException) - { - } - - // first try to add a root node with a dlna namespace. - try - { - return XElement.Parse("" + xml + "") - .Descendants() - .First(); - } - catch (XmlException) - { - } - - // some devices send back invalid xml - try - { - return XElement.Parse(xml.Replace("&", "&", StringComparison.Ordinal)); - } - catch (XmlException) - { - } - - return null; - } - - private static UBaseObject CreateUBaseObject(XElement? container, string? trackUri) - { - ArgumentNullException.ThrowIfNull(container); - - var url = container.GetValue(UPnpNamespaces.Res); - - if (string.IsNullOrWhiteSpace(url)) - { - url = trackUri; - } - - return new UBaseObject - { - Id = container.GetAttributeValue(UPnpNamespaces.Id), - ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId), - Title = container.GetValue(UPnpNamespaces.Title), - IconUrl = container.GetValue(UPnpNamespaces.Artwork), - SecondText = string.Empty, - Url = url, - ProtocolInfo = GetProtocolInfo(container), - MetaData = container.ToString() - }; - } - - private static string[] GetProtocolInfo(XElement container) - { - ArgumentNullException.ThrowIfNull(container); - - var resElement = container.Element(UPnpNamespaces.Res); - - var info = resElement?.Attribute(UPnpNamespaces.ProtocolInfo); - - if (info is not null && !string.IsNullOrWhiteSpace(info.Value)) - { - return info.Value.Split(':'); - } - - return new string[4]; - } - - private async Task GetAVProtocolAsync(CancellationToken cancellationToken) - { - if (AvCommands is not null) - { - return AvCommands; - } - - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - - var avService = GetAvTransportService(); - if (avService is null) - { - return null; - } - - string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); - - var httpClient = new DlnaHttpClient(_logger, _httpClientFactory); - - var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); - if (document is null) - { - return null; - } - - AvCommands = TransportCommands.Create(document); - return AvCommands; - } - - private async Task GetRenderingProtocolAsync(CancellationToken cancellationToken) - { - if (RendererCommands is not null) - { - return RendererCommands; - } - - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - - var avService = GetServiceRenderingControl(); - ArgumentNullException.ThrowIfNull(avService); - - string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); - - var httpClient = new DlnaHttpClient(_logger, _httpClientFactory); - _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); - var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); - if (document is null) - { - return null; - } - - RendererCommands = TransportCommands.Create(document); - return RendererCommands; - } - - private string NormalizeUrl(string baseUrl, string url) - { - // If it's already a complete url, don't stick anything onto the front of it - if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - return url; - } - - if (!url.Contains('/', StringComparison.Ordinal)) - { - url = "/dmr/" + url; - } - - if (!url.StartsWith('/')) - { - url = "/" + url; - } - - return baseUrl + url; - } - - public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken) - { - var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory); - - var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); - if (document is null) - { - return null; - } - - var friendlyNames = new List(); - - var name = document.Descendants(UPnpNamespaces.Ud.GetName("friendlyName")).FirstOrDefault(); - if (name is not null && !string.IsNullOrWhiteSpace(name.Value)) - { - friendlyNames.Add(name.Value); - } - - var room = document.Descendants(UPnpNamespaces.Ud.GetName("roomName")).FirstOrDefault(); - if (room is not null && !string.IsNullOrWhiteSpace(room.Value)) - { - friendlyNames.Add(room.Value); - } - - var deviceProperties = new DeviceInfo() - { - Name = string.Join(' ', friendlyNames), - BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port) - }; - - var model = document.Descendants(UPnpNamespaces.Ud.GetName("modelName")).FirstOrDefault(); - if (model is not null) - { - deviceProperties.ModelName = model.Value; - } - - var modelNumber = document.Descendants(UPnpNamespaces.Ud.GetName("modelNumber")).FirstOrDefault(); - if (modelNumber is not null) - { - deviceProperties.ModelNumber = modelNumber.Value; - } - - var uuid = document.Descendants(UPnpNamespaces.Ud.GetName("UDN")).FirstOrDefault(); - if (uuid is not null) - { - deviceProperties.UUID = uuid.Value; - } - - var manufacturer = document.Descendants(UPnpNamespaces.Ud.GetName("manufacturer")).FirstOrDefault(); - if (manufacturer is not null) - { - deviceProperties.Manufacturer = manufacturer.Value; - } - - var manufacturerUrl = document.Descendants(UPnpNamespaces.Ud.GetName("manufacturerURL")).FirstOrDefault(); - if (manufacturerUrl is not null) - { - deviceProperties.ManufacturerUrl = manufacturerUrl.Value; - } - - var presentationUrl = document.Descendants(UPnpNamespaces.Ud.GetName("presentationURL")).FirstOrDefault(); - if (presentationUrl is not null) - { - deviceProperties.PresentationUrl = presentationUrl.Value; - } - - var modelUrl = document.Descendants(UPnpNamespaces.Ud.GetName("modelURL")).FirstOrDefault(); - if (modelUrl is not null) - { - deviceProperties.ModelUrl = modelUrl.Value; - } - - var serialNumber = document.Descendants(UPnpNamespaces.Ud.GetName("serialNumber")).FirstOrDefault(); - if (serialNumber is not null) - { - deviceProperties.SerialNumber = serialNumber.Value; - } - - var modelDescription = document.Descendants(UPnpNamespaces.Ud.GetName("modelDescription")).FirstOrDefault(); - if (modelDescription is not null) - { - deviceProperties.ModelDescription = modelDescription.Value; - } - - var icon = document.Descendants(UPnpNamespaces.Ud.GetName("icon")).FirstOrDefault(); - if (icon is not null) - { - deviceProperties.Icon = CreateIcon(icon); - } - - foreach (var services in document.Descendants(UPnpNamespaces.Ud.GetName("serviceList"))) - { - if (services is null) - { - continue; - } - - var servicesList = services.Descendants(UPnpNamespaces.Ud.GetName("service")); - if (servicesList is null) - { - continue; - } - - foreach (var element in servicesList) - { - var service = Create(element); - - if (service is not null) - { - deviceProperties.Services.Add(service); - } - } - } - - return new Device(deviceProperties, httpClientFactory, logger); - } - - private static DeviceIcon CreateIcon(XElement element) - { - ArgumentNullException.ThrowIfNull(element); - - var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width")); - var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height")); - - _ = int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var widthValue); - _ = int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var heightValue); - - return new DeviceIcon - { - Depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")) ?? string.Empty, - Height = heightValue, - MimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")) ?? string.Empty, - Url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")) ?? string.Empty, - Width = widthValue - }; - } - - private static DeviceService Create(XElement element) - => new DeviceService() - { - ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty, - EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty, - ScpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")) ?? string.Empty, - ServiceId = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")) ?? string.Empty, - ServiceType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")) ?? string.Empty - }; - - private void UpdateMediaInfo(UBaseObject? mediaInfo, TransportState state) - { - TransportState = state; - - var previousMediaInfo = CurrentMediaInfo; - CurrentMediaInfo = mediaInfo; - - if (mediaInfo is null) - { - if (previousMediaInfo is not null) - { - OnPlaybackStop(previousMediaInfo); - } - } - else if (previousMediaInfo is null) - { - if (state != TransportState.STOPPED) - { - OnPlaybackStart(mediaInfo); - } - } - else if (mediaInfo.Equals(previousMediaInfo)) - { - OnPlaybackProgress(mediaInfo); - } - else - { - OnMediaChanged(previousMediaInfo, mediaInfo); - } - } - - private void OnPlaybackStart(UBaseObject mediaInfo) - { - if (string.IsNullOrWhiteSpace(mediaInfo.Url)) - { - return; - } - - PlaybackStart?.Invoke(this, new PlaybackStartEventArgs(mediaInfo)); - } - - private void OnPlaybackProgress(UBaseObject mediaInfo) - { - if (string.IsNullOrWhiteSpace(mediaInfo.Url)) - { - return; - } - - PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs(mediaInfo)); - } - - private void OnPlaybackStop(UBaseObject mediaInfo) - { - PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs(mediaInfo)); - } - - private void OnMediaChanged(UBaseObject old, UBaseObject newMedia) - { - MediaChanged?.Invoke(this, new MediaChangedEventArgs(old, newMedia)); - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _timer?.Dispose(); - _timer = null; - Properties = null!; - } - - _disposed = true; - } - - /// - public override string ToString() - { - return string.Format(CultureInfo.InvariantCulture, "{0} - {1}", Properties.Name, Properties.BaseUrl); - } - } -} diff --git a/Emby.Dlna/PlayTo/DeviceInfo.cs b/Emby.Dlna/PlayTo/DeviceInfo.cs deleted file mode 100644 index 2acfff4eb6..0000000000 --- a/Emby.Dlna/PlayTo/DeviceInfo.cs +++ /dev/null @@ -1,66 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.Collections.Generic; -using Emby.Dlna.Common; -using MediaBrowser.Model.Dlna; - -namespace Emby.Dlna.PlayTo -{ - public class DeviceInfo - { - private readonly List _services = new List(); - private string _baseUrl = string.Empty; - - public DeviceInfo() - { - Name = "Generic Device"; - } - - public string UUID { get; set; } - - public string Name { get; set; } - - public string ModelName { get; set; } - - public string ModelNumber { get; set; } - - public string ModelDescription { get; set; } - - public string ModelUrl { get; set; } - - public string Manufacturer { get; set; } - - public string SerialNumber { get; set; } - - public string ManufacturerUrl { get; set; } - - public string PresentationUrl { get; set; } - - public string BaseUrl - { - get => _baseUrl; - set => _baseUrl = value; - } - - public DeviceIcon Icon { get; set; } - - public List Services => _services; - - public DeviceIdentification ToDeviceIdentification() - { - return new DeviceIdentification - { - Manufacturer = Manufacturer, - ModelName = ModelName, - ModelNumber = ModelNumber, - FriendlyName = Name, - ManufacturerUrl = ManufacturerUrl, - ModelUrl = ModelUrl, - ModelDescription = ModelDescription, - SerialNumber = SerialNumber - }; - } - } -} diff --git a/Emby.Dlna/PlayTo/DlnaHttpClient.cs b/Emby.Dlna/PlayTo/DlnaHttpClient.cs deleted file mode 100644 index 255c51f19a..0000000000 --- a/Emby.Dlna/PlayTo/DlnaHttpClient.cs +++ /dev/null @@ -1,137 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Globalization; -using System.IO; -using System.Net.Http; -using System.Net.Mime; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; -using Emby.Dlna.Common; -using MediaBrowser.Common.Net; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.PlayTo -{ - /// - /// Http client for Dlna PlayTo function. - /// - public partial class DlnaHttpClient - { - private readonly ILogger _logger; - private readonly IHttpClientFactory _httpClientFactory; - - public DlnaHttpClient(ILogger logger, IHttpClientFactory httpClientFactory) - { - _logger = logger; - _httpClientFactory = httpClientFactory; - } - - [GeneratedRegex("(&(?![a-z]*;))")] - private static partial Regex EscapeAmpersandRegex(); - - private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) - { - // If it's already a complete url, don't stick anything onto the front of it - if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - return serviceUrl; - } - - if (!serviceUrl.StartsWith('/')) - { - serviceUrl = "/" + serviceUrl; - } - - return baseUrl + serviceUrl; - } - - private async Task SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var client = _httpClientFactory.CreateClient(NamedClient.Dlna); - using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using (stream.ConfigureAwait(false)) - { - try - { - return await XDocument.LoadAsync( - stream, - LoadOptions.None, - cancellationToken).ConfigureAwait(false); - } - catch (XmlException) - { - // try correcting the Xml response with common errors - stream.Position = 0; - using StreamReader sr = new StreamReader(stream); - var xmlString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - - // find and replace unescaped ampersands (&) - xmlString = EscapeAmpersandRegex().Replace(xmlString, "&"); - - try - { - // retry reading Xml - using var xmlReader = new StringReader(xmlString); - return await XDocument.LoadAsync( - xmlReader, - LoadOptions.None, - cancellationToken).ConfigureAwait(false); - } - catch (XmlException ex) - { - _logger.LogError(ex, "Failed to parse response"); - _logger.LogDebug("Malformed response: {Content}\n", xmlString); - - return null; - } - } - } - } - - public async Task GetDataAsync(string url, CancellationToken cancellationToken) - { - using var request = new HttpRequestMessage(HttpMethod.Get, url); - - // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon - return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); - } - - public async Task SendCommandAsync( - string baseUrl, - DeviceService service, - string command, - string postData, - string? header = null, - CancellationToken cancellationToken = default) - { - using var request = new HttpRequestMessage(HttpMethod.Post, NormalizeServiceUrl(baseUrl, service.ControlUrl)) - { - Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml) - }; - - request.Headers.TryAddWithoutValidation( - "SOAPACTION", - string.Format( - CultureInfo.InvariantCulture, - "\"{0}#{1}\"", - service.ServiceType, - command)); - request.Headers.Pragma.ParseAdd("no-cache"); - - if (!string.IsNullOrEmpty(header)) - { - request.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header); - } - - // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon - return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs deleted file mode 100644 index 0f7a524d62..0000000000 --- a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs +++ /dev/null @@ -1,19 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace Emby.Dlna.PlayTo -{ - public class MediaChangedEventArgs : EventArgs - { - public MediaChangedEventArgs(UBaseObject oldMediaInfo, UBaseObject newMediaInfo) - { - OldMediaInfo = oldMediaInfo; - NewMediaInfo = newMediaInfo; - } - - public UBaseObject OldMediaInfo { get; set; } - - public UBaseObject NewMediaInfo { get; set; } - } -} diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs deleted file mode 100644 index f70ebf3ebc..0000000000 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ /dev/null @@ -1,980 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Emby.Dlna.Didl; -using Jellyfin.Data.Entities; -using Jellyfin.Data.Enums; -using Jellyfin.Data.Events; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Session; -using Microsoft.AspNetCore.WebUtilities; -using Microsoft.Extensions.Logging; -using Photo = MediaBrowser.Controller.Entities.Photo; - -namespace Emby.Dlna.PlayTo -{ - public class PlayToController : ISessionController, IDisposable - { - private readonly SessionInfo _session; - private readonly ISessionManager _sessionManager; - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IDlnaManager _dlnaManager; - private readonly IUserManager _userManager; - private readonly IImageProcessor _imageProcessor; - private readonly IUserDataManager _userDataManager; - private readonly ILocalizationManager _localization; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMediaEncoder _mediaEncoder; - - private readonly IDeviceDiscovery _deviceDiscovery; - private readonly string _serverAddress; - private readonly string? _accessToken; - - private readonly List _playlist = new List(); - private Device _device; - private int _currentPlaylistIndex; - - private bool _disposed; - - public PlayToController( - SessionInfo session, - ISessionManager sessionManager, - ILibraryManager libraryManager, - ILogger logger, - IDlnaManager dlnaManager, - IUserManager userManager, - IImageProcessor imageProcessor, - string serverAddress, - string? accessToken, - IDeviceDiscovery deviceDiscovery, - IUserDataManager userDataManager, - ILocalizationManager localization, - IMediaSourceManager mediaSourceManager, - IMediaEncoder mediaEncoder, - Device device) - { - _session = session; - _sessionManager = sessionManager; - _libraryManager = libraryManager; - _logger = logger; - _dlnaManager = dlnaManager; - _userManager = userManager; - _imageProcessor = imageProcessor; - _serverAddress = serverAddress; - _accessToken = accessToken; - _deviceDiscovery = deviceDiscovery; - _userDataManager = userDataManager; - _localization = localization; - _mediaSourceManager = mediaSourceManager; - _mediaEncoder = mediaEncoder; - - _device = device; - _device.OnDeviceUnavailable = OnDeviceUnavailable; - _device.PlaybackStart += OnDevicePlaybackStart; - _device.PlaybackProgress += OnDevicePlaybackProgress; - _device.PlaybackStopped += OnDevicePlaybackStopped; - _device.MediaChanged += OnDeviceMediaChanged; - - _device.Start(); - - _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft; - } - - public bool IsSessionActive => !_disposed; - - public bool SupportsMediaControl => IsSessionActive; - - /* - * Send a message to the DLNA device to notify what is the next track in the playlist. - */ - private async Task SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken) - { - if (currentPlayListItemIndex >= 0 && currentPlayListItemIndex < _playlist.Count - 1) - { - // The current playing item is indeed in the play list and we are not yet at the end of the playlist. - var nextItemIndex = currentPlayListItemIndex + 1; - var nextItem = _playlist[nextItemIndex]; - - // Send the SetNextAvTransport message. - await _device.SetNextAvTransport(nextItem.StreamUrl, GetDlnaHeaders(nextItem), nextItem.Didl, cancellationToken).ConfigureAwait(false); - } - } - - private void OnDeviceUnavailable() - { - try - { - _sessionManager.ReportSessionEnded(_session.Id); - } - catch (Exception ex) - { - // Could throw if the session is already gone - _logger.LogError(ex, "Error reporting the end of session {Id}", _session.Id); - } - } - - private void OnDeviceDiscoveryDeviceLeft(object? sender, GenericEventArgs e) - { - var info = e.Argument; - - if (!_disposed - && info.Headers.TryGetValue("USN", out string? usn) - && usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 - && (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 - || (info.Headers.TryGetValue("NT", out string? nt) - && nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1))) - { - OnDeviceUnavailable(); - } - } - - private async void OnDeviceMediaChanged(object? sender, MediaChangedEventArgs e) - { - if (_disposed || string.IsNullOrEmpty(e.OldMediaInfo.Url)) - { - return; - } - - try - { - var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item is not null) - { - var positionTicks = GetProgressPositionTicks(streamInfo); - - await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); - } - - streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item is null) - { - return; - } - - var newItemProgress = GetProgressInfo(streamInfo); - - await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false); - - // Send a message to the DLNA device to notify what is the next track in the playlist. - var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId.Equals(streamInfo.ItemId)); - if (currentItemIndex >= 0) - { - _currentPlaylistIndex = currentItemIndex; - } - - await SendNextTrackMessage(currentItemIndex, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reporting progress"); - } - } - - private async void OnDevicePlaybackStopped(object? sender, PlaybackStoppedEventArgs e) - { - if (_disposed) - { - return; - } - - try - { - var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); - - if (streamInfo.Item is null) - { - return; - } - - var positionTicks = GetProgressPositionTicks(streamInfo); - - await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); - - var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); - - var duration = mediaSource is null - ? _device.Duration?.Ticks - : mediaSource.RunTimeTicks; - - var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0; - - if (!playedToCompletion && duration.HasValue && positionTicks.HasValue) - { - double percent = positionTicks.Value; - percent /= duration.Value; - - playedToCompletion = Math.Abs(1 - percent) <= .1; - } - - if (playedToCompletion) - { - await SetPlaylistIndex(_currentPlaylistIndex + 1).ConfigureAwait(false); - } - else - { - _playlist.Clear(); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reporting playback stopped"); - } - } - - private async Task ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) - { - try - { - await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo - { - ItemId = streamInfo.ItemId, - SessionId = _session.Id, - PositionTicks = positionTicks, - MediaSourceId = streamInfo.MediaSourceId - }).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reporting progress"); - } - } - - private async void OnDevicePlaybackStart(object? sender, PlaybackStartEventArgs e) - { - if (_disposed) - { - return; - } - - try - { - var info = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); - - if (info.Item is not null) - { - var progress = GetProgressInfo(info); - - await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reporting progress"); - } - } - - private async void OnDevicePlaybackProgress(object? sender, PlaybackProgressEventArgs e) - { - if (_disposed) - { - return; - } - - try - { - var mediaUrl = e.MediaInfo.Url; - - if (string.IsNullOrWhiteSpace(mediaUrl)) - { - return; - } - - var info = StreamParams.ParseFromUrl(mediaUrl, _libraryManager, _mediaSourceManager); - - if (info.Item is not null) - { - var progress = GetProgressInfo(info); - - await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reporting progress"); - } - } - - private long? GetProgressPositionTicks(StreamParams info) - { - var ticks = _device.Position.Ticks; - - if (!EnableClientSideSeek(info)) - { - ticks += info.StartPositionTicks; - } - - return ticks; - } - - private PlaybackStartInfo GetProgressInfo(StreamParams info) - { - return new PlaybackStartInfo - { - ItemId = info.ItemId, - SessionId = _session.Id, - PositionTicks = GetProgressPositionTicks(info), - IsMuted = _device.IsMuted, - IsPaused = _device.IsPaused, - MediaSourceId = info.MediaSourceId, - AudioStreamIndex = info.AudioStreamIndex, - SubtitleStreamIndex = info.SubtitleStreamIndex, - VolumeLevel = _device.Volume, - - CanSeek = true, - - PlayMethod = info.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode - }; - } - - public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) - { - _logger.LogDebug("{0} - Received PlayRequest: {1}", _session.DeviceName, command.PlayCommand); - - var user = command.ControllingUserId.Equals(default) - ? null : - _userManager.GetUserById(command.ControllingUserId); - - var items = new List(); - foreach (var id in command.ItemIds) - { - AddItemFromId(id, items); - } - - var startIndex = command.StartIndex ?? 0; - int len = items.Count - startIndex; - if (startIndex > 0) - { - items = items.GetRange(startIndex, len); - } - - var playlist = new PlaylistItem[len]; - - // Not nullable enabled - so this is required. - playlist[0] = CreatePlaylistItem( - items[0], - user, - command.StartPositionTicks ?? 0, - command.MediaSourceId ?? string.Empty, - command.AudioStreamIndex, - command.SubtitleStreamIndex); - - for (int i = 1; i < len; i++) - { - playlist[i] = CreatePlaylistItem(items[i], user, 0, string.Empty, null, null); - } - - _logger.LogDebug("{0} - Playlist created", _session.DeviceName); - - if (command.PlayCommand == PlayCommand.PlayLast) - { - _playlist.AddRange(playlist); - } - - if (command.PlayCommand == PlayCommand.PlayNext) - { - _playlist.AddRange(playlist); - } - - if (!command.ControllingUserId.Equals(default)) - { - _sessionManager.LogSessionActivity( - _session.Client, - _session.ApplicationVersion, - _session.DeviceId, - _session.DeviceName, - _session.RemoteEndPoint, - user); - } - - return PlayItems(playlist, cancellationToken); - } - - private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) - { - switch (command.Command) - { - case PlaystateCommand.Stop: - _playlist.Clear(); - return _device.SetStop(CancellationToken.None); - - case PlaystateCommand.Pause: - return _device.SetPause(CancellationToken.None); - - case PlaystateCommand.Unpause: - return _device.SetPlay(CancellationToken.None); - - case PlaystateCommand.PlayPause: - return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None); - - case PlaystateCommand.Seek: - return Seek(command.SeekPositionTicks ?? 0); - - case PlaystateCommand.NextTrack: - return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken); - - case PlaystateCommand.PreviousTrack: - return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken); - } - - return Task.CompletedTask; - } - - private async Task Seek(long newPosition) - { - var media = _device.CurrentMediaInfo; - - if (media is not null) - { - var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager); - - if (info.Item is not null && !EnableClientSideSeek(info)) - { - var user = _session.UserId.Equals(default) - ? null - : _userManager.GetUserById(_session.UserId); - var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex); - - await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); - - // Send a message to the DLNA device to notify what is the next track in the play list. - var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false); - - return; - } - - await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); - } - } - - private bool EnableClientSideSeek(StreamParams info) - { - return info.IsDirectStream; - } - - private bool EnableClientSideSeek(StreamInfo info) - { - return info.IsDirectStream; - } - - private void AddItemFromId(Guid id, List list) - { - var item = _libraryManager.GetItemById(id); - if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video) - { - list.Add(item); - } - } - - private PlaylistItem CreatePlaylistItem( - BaseItem item, - User? user, - long startPostionTicks, - string? mediaSourceId, - int? audioStreamIndex, - int? subtitleStreamIndex) - { - var deviceInfo = _device.Properties; - - var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ?? - _dlnaManager.GetDefaultProfile(); - - var mediaSources = item is IHasMediaSources - ? _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray() - : Array.Empty(); - - var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); - playlistItem.StreamInfo.StartPositionTicks = startPostionTicks; - - playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken)); - - var itemXml = new DidlBuilder( - profile, - user, - _imageProcessor, - _serverAddress, - _accessToken, - _userDataManager, - _localization, - _mediaSourceManager, - _logger, - _mediaEncoder, - _libraryManager) - .GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); - - playlistItem.Didl = itemXml; - - return playlistItem; - } - - private string? GetDlnaHeaders(PlaylistItem item) - { - var profile = item.Profile; - var streamInfo = item.StreamInfo; - - if (streamInfo.MediaType == DlnaProfileType.Audio) - { - return ContentFeatureBuilder.BuildAudioHeader( - profile, - streamInfo.Container, - streamInfo.TargetAudioCodec.FirstOrDefault(), - streamInfo.TargetAudioBitrate, - streamInfo.TargetAudioSampleRate, - streamInfo.TargetAudioChannels, - streamInfo.TargetAudioBitDepth, - streamInfo.IsDirectStream, - streamInfo.RunTimeTicks ?? 0, - streamInfo.TranscodeSeekInfo); - } - - if (streamInfo.MediaType == DlnaProfileType.Video) - { - var list = ContentFeatureBuilder.BuildVideoHeader( - profile, - streamInfo.Container, - streamInfo.TargetVideoCodec.FirstOrDefault(), - streamInfo.TargetAudioCodec.FirstOrDefault(), - streamInfo.TargetWidth, - streamInfo.TargetHeight, - streamInfo.TargetVideoBitDepth, - streamInfo.TargetVideoBitrate, - streamInfo.TargetTimestamp, - streamInfo.IsDirectStream, - streamInfo.RunTimeTicks ?? 0, - streamInfo.TargetVideoProfile, - streamInfo.TargetVideoRangeType, - streamInfo.TargetVideoLevel, - streamInfo.TargetFramerate ?? 0, - streamInfo.TargetPacketLength, - streamInfo.TranscodeSeekInfo, - streamInfo.IsTargetAnamorphic, - streamInfo.IsTargetInterlaced, - streamInfo.TargetRefFrames, - streamInfo.TargetVideoStreamCount, - streamInfo.TargetAudioStreamCount, - streamInfo.TargetVideoCodecTag, - streamInfo.IsTargetAVC); - - return list.FirstOrDefault(); - } - - return null; - } - - private PlaylistItem GetPlaylistItem(BaseItem item, MediaSourceInfo[] mediaSources, DeviceProfile profile, string deviceId, string? mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex) - { - if (item.MediaType == MediaType.Video) - { - return new PlaylistItem - { - StreamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalVideoStream(new MediaOptions - { - ItemId = item.Id, - MediaSources = mediaSources, - Profile = profile, - DeviceId = deviceId, - MaxBitrate = profile.MaxStreamingBitrate, - MediaSourceId = mediaSourceId, - AudioStreamIndex = audioStreamIndex, - SubtitleStreamIndex = subtitleStreamIndex - }), - - Profile = profile - }; - } - - if (item.MediaType == MediaType.Audio) - { - return new PlaylistItem - { - StreamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalAudioStream(new MediaOptions - { - ItemId = item.Id, - MediaSources = mediaSources, - Profile = profile, - DeviceId = deviceId, - MaxBitrate = profile.MaxStreamingBitrate, - MediaSourceId = mediaSourceId - }), - - Profile = profile - }; - } - - if (item.MediaType == MediaType.Photo) - { - return PlaylistItemFactory.Create((Photo)item, profile); - } - - throw new ArgumentException("Unrecognized item type."); - } - - /// - /// Plays the items. - /// - /// The items. - /// The cancellation token. - /// true on success. - private async Task PlayItems(IEnumerable items, CancellationToken cancellationToken = default) - { - _playlist.Clear(); - _playlist.AddRange(items); - _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count); - - await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false); - return true; - } - - private async Task SetPlaylistIndex(int index, CancellationToken cancellationToken = default) - { - if (index < 0 || index >= _playlist.Count) - { - _playlist.Clear(); - await _device.SetStop(cancellationToken).ConfigureAwait(false); - return; - } - - _currentPlaylistIndex = index; - var currentitem = _playlist[index]; - - await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false); - - // Send a message to the DLNA device to notify what is the next track in the play list. - await SendNextTrackMessage(index, cancellationToken).ConfigureAwait(false); - - var streamInfo = currentitem.StreamInfo; - if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) - { - await SeekAfterTransportChange(streamInfo.StartPositionTicks, CancellationToken.None).ConfigureAwait(false); - } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _device.PlaybackStart -= OnDevicePlaybackStart; - _device.PlaybackProgress -= OnDevicePlaybackProgress; - _device.PlaybackStopped -= OnDevicePlaybackStopped; - _device.MediaChanged -= OnDeviceMediaChanged; - _deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft; - _device.OnDeviceUnavailable = null; - _device.Dispose(); - } - - _disposed = true; - } - - private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) - { - switch (command.Name) - { - case GeneralCommandType.VolumeDown: - return _device.VolumeDown(cancellationToken); - case GeneralCommandType.VolumeUp: - return _device.VolumeUp(cancellationToken); - case GeneralCommandType.Mute: - return _device.Mute(cancellationToken); - case GeneralCommandType.Unmute: - return _device.Unmute(cancellationToken); - case GeneralCommandType.ToggleMute: - return _device.ToggleMute(cancellationToken); - case GeneralCommandType.SetAudioStreamIndex: - if (command.Arguments.TryGetValue("Index", out string? index)) - { - if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) - { - return SetAudioStreamIndex(val); - } - - throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied."); - } - - throw new ArgumentException("SetAudioStreamIndex argument cannot be null"); - case GeneralCommandType.SetSubtitleStreamIndex: - if (command.Arguments.TryGetValue("Index", out index)) - { - if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) - { - return SetSubtitleStreamIndex(val); - } - - throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied."); - } - - throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null"); - case GeneralCommandType.SetVolume: - if (command.Arguments.TryGetValue("Volume", out string? vol)) - { - if (int.TryParse(vol, NumberStyles.Integer, CultureInfo.InvariantCulture, out var volume)) - { - return _device.SetVolume(volume, cancellationToken); - } - - throw new ArgumentException("Unsupported volume value supplied."); - } - - throw new ArgumentException("Volume argument cannot be null"); - default: - return Task.CompletedTask; - } - } - - private async Task SetAudioStreamIndex(int? newIndex) - { - var media = _device.CurrentMediaInfo; - - if (media is not null) - { - var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager); - - if (info.Item is not null) - { - var newPosition = GetProgressPositionTicks(info) ?? 0; - - var user = _session.UserId.Equals(default) - ? null - : _userManager.GetUserById(_session.UserId); - var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex); - - await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); - - // Send a message to the DLNA device to notify what is the next track in the play list. - var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false); - - if (EnableClientSideSeek(newItem.StreamInfo)) - { - await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); - } - } - } - } - - private async Task SetSubtitleStreamIndex(int? newIndex) - { - var media = _device.CurrentMediaInfo; - - if (media is not null) - { - var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager); - - if (info.Item is not null) - { - var newPosition = GetProgressPositionTicks(info) ?? 0; - - var user = _session.UserId.Equals(default) - ? null - : _userManager.GetUserById(_session.UserId); - var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex); - - await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); - - // Send a message to the DLNA device to notify what is the next track in the play list. - var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); - await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false); - - if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0) - { - await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); - } - } - } - } - - private async Task SeekAfterTransportChange(long positionTicks, CancellationToken cancellationToken) - { - const int MaxWait = 15000000; - const int Interval = 500; - - var currentWait = 0; - while (_device.TransportState != TransportState.PLAYING && currentWait < MaxWait) - { - await Task.Delay(Interval, cancellationToken).ConfigureAwait(false); - currentWait += Interval; - } - - await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false); - } - - private static int? GetIntValue(IReadOnlyDictionary values, string name) - { - var value = values.GetValueOrDefault(name); - - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) - { - return result; - } - - return null; - } - - private static long GetLongValue(IReadOnlyDictionary values, string name) - { - var value = values.GetValueOrDefault(name); - - if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) - { - return result; - } - - return 0; - } - - /// - public Task SendMessage(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - - return name switch - { - SessionMessageType.Play => SendPlayCommand((data as PlayRequest)!, cancellationToken), - SessionMessageType.Playstate => SendPlaystateCommand((data as PlaystateRequest)!, cancellationToken), - SessionMessageType.GeneralCommand => SendGeneralCommand((data as GeneralCommand)!, cancellationToken), - _ => Task.CompletedTask // Not supported or needed right now - }; - } - - private class StreamParams - { - private MediaSourceInfo? _mediaSource; - private IMediaSourceManager? _mediaSourceManager; - - public Guid ItemId { get; set; } - - public bool IsDirectStream { get; set; } - - public long StartPositionTicks { get; set; } - - public int? AudioStreamIndex { get; set; } - - public int? SubtitleStreamIndex { get; set; } - - public string? DeviceProfileId { get; set; } - - public string? DeviceId { get; set; } - - public string? MediaSourceId { get; set; } - - public string? LiveStreamId { get; set; } - - public BaseItem? Item { get; set; } - - public async Task GetMediaSource(CancellationToken cancellationToken) - { - if (_mediaSource is not null) - { - return _mediaSource; - } - - if (Item is not IHasMediaSources) - { - return null; - } - - if (_mediaSourceManager is not null) - { - _mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false); - } - - return _mediaSource; - } - - private static Guid GetItemId(string url) - { - ArgumentException.ThrowIfNullOrEmpty(url); - - var parts = url.Split('/'); - - for (var i = 0; i < parts.Length - 1; i++) - { - var part = parts[i]; - - if (string.Equals(part, "audio", StringComparison.OrdinalIgnoreCase) - || string.Equals(part, "videos", StringComparison.OrdinalIgnoreCase)) - { - if (Guid.TryParse(parts[i + 1], out var result)) - { - return result; - } - } - } - - return default; - } - - public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager) - { - ArgumentException.ThrowIfNullOrEmpty(url); - - var request = new StreamParams - { - ItemId = GetItemId(url) - }; - - if (request.ItemId.Equals(default)) - { - return request; - } - - var index = url.IndexOf('?', StringComparison.Ordinal); - if (index == -1) - { - return request; - } - - var query = url.Substring(index + 1); - Dictionary values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); - - request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId"); - request.DeviceId = values.GetValueOrDefault("DeviceId"); - request.MediaSourceId = values.GetValueOrDefault("MediaSourceId"); - request.LiveStreamId = values.GetValueOrDefault("LiveStreamId"); - request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); - request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); - request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); - request.StartPositionTicks = GetLongValue(values, "StartPositionTicks"); - - request.Item = libraryManager.GetItemById(request.ItemId); - - request._mediaSourceManager = mediaSourceManager; - - return request; - } - } - } -} diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs deleted file mode 100644 index b05e0a0957..0000000000 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ /dev/null @@ -1,258 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Globalization; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Jellyfin.Data.Events; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Session; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.PlayTo -{ - public sealed class PlayToManager : IDisposable - { - private readonly ILogger _logger; - private readonly ISessionManager _sessionManager; - - private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; - private readonly IDlnaManager _dlnaManager; - private readonly IServerApplicationHost _appHost; - private readonly IImageProcessor _imageProcessor; - private readonly IHttpClientFactory _httpClientFactory; - private readonly IUserDataManager _userDataManager; - private readonly ILocalizationManager _localization; - - private readonly IDeviceDiscovery _deviceDiscovery; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMediaEncoder _mediaEncoder; - - private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); - private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - private bool _disposed; - - public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) - { - _logger = logger; - _sessionManager = sessionManager; - _libraryManager = libraryManager; - _userManager = userManager; - _dlnaManager = dlnaManager; - _appHost = appHost; - _imageProcessor = imageProcessor; - _deviceDiscovery = deviceDiscovery; - _httpClientFactory = httpClientFactory; - _userDataManager = userDataManager; - _localization = localization; - _mediaSourceManager = mediaSourceManager; - _mediaEncoder = mediaEncoder; - } - - public void Start() - { - _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered; - } - - private async void OnDeviceDiscoveryDeviceDiscovered(object? sender, GenericEventArgs e) - { - if (_disposed) - { - return; - } - - var info = e.Argument; - - if (!info.Headers.TryGetValue("USN", out string? usn)) - { - usn = string.Empty; - } - - if (!info.Headers.TryGetValue("NT", out string? nt)) - { - nt = string.Empty; - } - - // It has to report that it's a media renderer - if (!usn.Contains("MediaRenderer:", StringComparison.OrdinalIgnoreCase) - && !nt.Contains("MediaRenderer:", StringComparison.OrdinalIgnoreCase)) - { - return; - } - - var cancellationToken = _disposeCancellationTokenSource.Token; - - await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (_disposed) - { - return; - } - - if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1)) - { - return; - } - - await AddDevice(info, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - } - catch (Exception ex) - { - _logger.LogError(ex, "Error creating PlayTo device."); - } - finally - { - _sessionLock.Release(); - } - } - - internal static string GetUuid(string usn) - { - const string UuidStr = "uuid:"; - const string UuidColonStr = "::"; - - var index = usn.IndexOf(UuidStr, StringComparison.OrdinalIgnoreCase); - if (index == -1) - { - return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture); - } - - ReadOnlySpan tmp = usn.AsSpan()[(index + UuidStr.Length)..]; - - index = tmp.IndexOf(UuidColonStr, StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - tmp = tmp[..index]; - } - - index = tmp.IndexOf('{'); - if (index != -1) - { - int endIndex = tmp.IndexOf('}'); - if (endIndex != -1) - { - tmp = tmp[(index + 1)..endIndex]; - } - } - - return tmp.ToString(); - } - - private async Task AddDevice(UpnpDeviceInfo info, CancellationToken cancellationToken) - { - var uri = info.Location; - _logger.LogDebug("Attempting to create PlayToController from location {0}", uri); - - if (info.Headers.TryGetValue("USN", out string? uuid)) - { - uuid = GetUuid(uuid); - } - else - { - uuid = uri.ToString().GetMD5().ToString("N", CultureInfo.InvariantCulture); - } - - var sessionInfo = await _sessionManager - .LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null) - .ConfigureAwait(false); - - var controller = sessionInfo.SessionControllers.OfType().FirstOrDefault(); - - if (controller is null) - { - var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false); - if (device is null) - { - _logger.LogError("Ignoring device as xml response is invalid."); - return; - } - - string deviceName = device.Properties.Name; - - _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName); - - string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIPAddress); - - controller = new PlayToController( - sessionInfo, - _sessionManager, - _libraryManager, - _logger, - _dlnaManager, - _userManager, - _imageProcessor, - serverAddress, - null, - _deviceDiscovery, - _userDataManager, - _localization, - _mediaSourceManager, - _mediaEncoder, - device); - - sessionInfo.AddController(controller); - - var profile = _dlnaManager.GetProfile(device.Properties.ToDeviceIdentification()) ?? - _dlnaManager.GetDefaultProfile(); - - _sessionManager.ReportCapabilities(sessionInfo.Id, new ClientCapabilities - { - PlayableMediaTypes = profile.GetSupportedMediaTypes(), - - SupportedCommands = new[] - { - GeneralCommandType.VolumeDown, - GeneralCommandType.VolumeUp, - GeneralCommandType.Mute, - GeneralCommandType.Unmute, - GeneralCommandType.ToggleMute, - GeneralCommandType.SetVolume, - GeneralCommandType.SetAudioStreamIndex, - GeneralCommandType.SetSubtitleStreamIndex, - GeneralCommandType.PlayMediaSource - }, - - SupportsMediaControl = true - }); - - _logger.LogInformation("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName); - } - } - - /// - public void Dispose() - { - _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered; - - try - { - _disposeCancellationTokenSource.Cancel(); - } - catch (Exception ex) - { - _logger.LogDebug(ex, "Error while disposing PlayToManager"); - } - - _sessionLock.Dispose(); - _disposeCancellationTokenSource.Dispose(); - - _disposed = true; - } - } -} diff --git a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs deleted file mode 100644 index c95d8b1e84..0000000000 --- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace Emby.Dlna.PlayTo -{ - public class PlaybackProgressEventArgs : EventArgs - { - public PlaybackProgressEventArgs(UBaseObject mediaInfo) - { - MediaInfo = mediaInfo; - } - - public UBaseObject MediaInfo { get; set; } - } -} diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs deleted file mode 100644 index 619c861ed9..0000000000 --- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace Emby.Dlna.PlayTo -{ - public class PlaybackStartEventArgs : EventArgs - { - public PlaybackStartEventArgs(UBaseObject mediaInfo) - { - MediaInfo = mediaInfo; - } - - public UBaseObject MediaInfo { get; set; } - } -} diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs deleted file mode 100644 index d0ec250591..0000000000 --- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace Emby.Dlna.PlayTo -{ - public class PlaybackStoppedEventArgs : EventArgs - { - public PlaybackStoppedEventArgs(UBaseObject mediaInfo) - { - MediaInfo = mediaInfo; - } - - public UBaseObject MediaInfo { get; set; } - } -} diff --git a/Emby.Dlna/PlayTo/PlaylistItem.cs b/Emby.Dlna/PlayTo/PlaylistItem.cs deleted file mode 100644 index 5056e69ae7..0000000000 --- a/Emby.Dlna/PlayTo/PlaylistItem.cs +++ /dev/null @@ -1,19 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using MediaBrowser.Model.Dlna; - -namespace Emby.Dlna.PlayTo -{ - public class PlaylistItem - { - public string StreamUrl { get; set; } - - public string Didl { get; set; } - - public StreamInfo StreamInfo { get; set; } - - public DeviceProfile Profile { get; set; } - } -} diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs deleted file mode 100644 index 53cd05cfda..0000000000 --- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs +++ /dev/null @@ -1,70 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System.IO; -using System.Linq; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Session; - -namespace Emby.Dlna.PlayTo -{ - public static class PlaylistItemFactory - { - public static PlaylistItem Create(Photo item, DeviceProfile profile) - { - var playlistItem = new PlaylistItem - { - StreamInfo = new StreamInfo - { - ItemId = item.Id, - MediaType = DlnaProfileType.Photo, - DeviceProfile = profile - }, - - Profile = profile - }; - - var directPlay = profile.DirectPlayProfiles - .FirstOrDefault(i => i.Type == DlnaProfileType.Photo && IsSupported(i, item)); - - if (directPlay is not null) - { - playlistItem.StreamInfo.PlayMethod = PlayMethod.DirectStream; - playlistItem.StreamInfo.Container = Path.GetExtension(item.Path); - - return playlistItem; - } - - var transcodingProfile = profile.TranscodingProfiles - .FirstOrDefault(i => i.Type == DlnaProfileType.Photo); - - if (transcodingProfile is not null) - { - playlistItem.StreamInfo.PlayMethod = PlayMethod.Transcode; - playlistItem.StreamInfo.Container = "." + transcodingProfile.Container.TrimStart('.'); - } - - return playlistItem; - } - - private static bool IsSupported(DirectPlayProfile profile, Photo item) - { - var mediaPath = item.Path; - - if (profile.Container.Length > 0) - { - // Check container type - var mediaContainer = (Path.GetExtension(mediaPath) ?? string.Empty).TrimStart('.'); - - if (!profile.SupportsContainer(mediaContainer)) - { - return false; - } - } - - return true; - } - } -} diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs deleted file mode 100644 index 6b2096d9dc..0000000000 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ /dev/null @@ -1,181 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Xml.Linq; -using Emby.Dlna.Common; -using Emby.Dlna.Ssdp; - -namespace Emby.Dlna.PlayTo -{ - public class TransportCommands - { - private const string CommandBase = "\r\n" + "" + "" + "" + "{2}" + "" + ""; - - public List StateVariables { get; } = new List(); - - public List ServiceActions { get; } = new List(); - - public static TransportCommands Create(XDocument document) - { - var command = new TransportCommands(); - - var actionList = document.Descendants(UPnpNamespaces.Svc + "actionList"); - - foreach (var container in actionList.Descendants(UPnpNamespaces.Svc + "action")) - { - command.ServiceActions.Add(ServiceActionFromXml(container)); - } - - var stateValues = document.Descendants(UPnpNamespaces.ServiceStateTable).FirstOrDefault(); - - if (stateValues is not null) - { - foreach (var container in stateValues.Elements(UPnpNamespaces.Svc + "stateVariable")) - { - command.StateVariables.Add(FromXml(container)); - } - } - - return command; - } - - private static ServiceAction ServiceActionFromXml(XElement container) - { - var serviceAction = new ServiceAction - { - Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, - }; - - var argumentList = serviceAction.ArgumentList; - - foreach (var arg in container.Descendants(UPnpNamespaces.Svc + "argument")) - { - argumentList.Add(ArgumentFromXml(arg)); - } - - return serviceAction; - } - - private static Argument ArgumentFromXml(XElement container) - { - ArgumentNullException.ThrowIfNull(container); - - return new Argument - { - Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, - Direction = container.GetValue(UPnpNamespaces.Svc + "direction") ?? string.Empty, - RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") ?? string.Empty - }; - } - - private static StateVariable FromXml(XElement container) - { - var allowedValues = Array.Empty(); - var element = container.Descendants(UPnpNamespaces.Svc + "allowedValueList") - .FirstOrDefault(); - - if (element is not null) - { - var values = element.Descendants(UPnpNamespaces.Svc + "allowedValue"); - - allowedValues = values.Select(child => child.Value).ToArray(); - } - - return new StateVariable - { - Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, - DataType = container.GetValue(UPnpNamespaces.Svc + "dataType") ?? string.Empty, - AllowedValues = allowedValues - }; - } - - public string BuildPost(ServiceAction action, string xmlNamespace) - { - var stateString = string.Empty; - - foreach (var arg in action.ArgumentList) - { - if (string.Equals(arg.Direction, "out", StringComparison.Ordinal)) - { - continue; - } - - if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal)) - { - stateString += BuildArgumentXml(arg, "0"); - } - else - { - stateString += BuildArgumentXml(arg, null); - } - } - - return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString); - } - - public string BuildPost(ServiceAction action, string xmlNamespace, object value, string commandParameter = "") - { - var stateString = string.Empty; - - foreach (var arg in action.ArgumentList) - { - if (string.Equals(arg.Direction, "out", StringComparison.Ordinal)) - { - continue; - } - - if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal)) - { - stateString += BuildArgumentXml(arg, "0"); - } - else - { - stateString += BuildArgumentXml(arg, value.ToString(), commandParameter); - } - } - - return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString); - } - - public string BuildPost(ServiceAction action, string xmlNamespace, object value, Dictionary dictionary) - { - var stateString = string.Empty; - - foreach (var arg in action.ArgumentList) - { - if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal)) - { - stateString += BuildArgumentXml(arg, "0"); - } - else if (dictionary.TryGetValue(arg.Name, out var argValue)) - { - stateString += BuildArgumentXml(arg, argValue); - } - else - { - stateString += BuildArgumentXml(arg, value.ToString()); - } - } - - return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString); - } - - private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "") - { - var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase)); - - if (state is not null) - { - var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ?? - (state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value); - - return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}", argument.Name, state.DataType, sendValue); - } - - return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}", argument.Name, value); - } - } -} diff --git a/Emby.Dlna/PlayTo/TransportState.cs b/Emby.Dlna/PlayTo/TransportState.cs deleted file mode 100644 index 0d6a78438c..0000000000 --- a/Emby.Dlna/PlayTo/TransportState.cs +++ /dev/null @@ -1,16 +0,0 @@ -#pragma warning disable CS1591 - -namespace Emby.Dlna.PlayTo -{ - /// - /// Core of the AVTransport service. It defines the conceptually top- - /// level state of the transport, for example, whether it is playing, recording, etc. - /// - public enum TransportState - { - STOPPED, - PLAYING, - TRANSITIONING, - PAUSED_PLAYBACK - } -} diff --git a/Emby.Dlna/PlayTo/UpnpContainer.cs b/Emby.Dlna/PlayTo/UpnpContainer.cs deleted file mode 100644 index 017d51e606..0000000000 --- a/Emby.Dlna/PlayTo/UpnpContainer.cs +++ /dev/null @@ -1,25 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Xml.Linq; -using Emby.Dlna.Ssdp; - -namespace Emby.Dlna.PlayTo -{ - public class UpnpContainer : UBaseObject - { - public static UBaseObject Create(XElement container) - { - ArgumentNullException.ThrowIfNull(container); - - return new UBaseObject - { - Id = container.GetAttributeValue(UPnpNamespaces.Id), - ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId), - Title = container.GetValue(UPnpNamespaces.Title), - IconUrl = container.GetValue(UPnpNamespaces.Artwork), - UpnpClass = container.GetValue(UPnpNamespaces.Class) - }; - } - } -} diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs deleted file mode 100644 index a8f451405c..0000000000 --- a/Emby.Dlna/PlayTo/uBaseObject.cs +++ /dev/null @@ -1,63 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using Jellyfin.Data.Enums; - -namespace Emby.Dlna.PlayTo -{ - public class UBaseObject - { - public string Id { get; set; } - - public string ParentId { get; set; } - - public string Title { get; set; } - - public string SecondText { get; set; } - - public string IconUrl { get; set; } - - public string MetaData { get; set; } - - public string Url { get; set; } - - public IReadOnlyList ProtocolInfo { get; set; } - - public string UpnpClass { get; set; } - - public string MediaType - { - get - { - var classType = UpnpClass ?? string.Empty; - - if (classType.Contains("Audio", StringComparison.Ordinal)) - { - return "Audio"; - } - - if (classType.Contains("Video", StringComparison.Ordinal)) - { - return "Video"; - } - - if (classType.Contains("image", StringComparison.Ordinal)) - { - return "Photo"; - } - - return null; - } - } - - public bool Equals(UBaseObject obj) - { - ArgumentNullException.ThrowIfNull(obj); - - return string.Equals(Id, obj.Id, StringComparison.Ordinal); - } - } -} diff --git a/Emby.Dlna/PlayTo/uPnpNamespaces.cs b/Emby.Dlna/PlayTo/uPnpNamespaces.cs deleted file mode 100644 index 5042d44938..0000000000 --- a/Emby.Dlna/PlayTo/uPnpNamespaces.cs +++ /dev/null @@ -1,67 +0,0 @@ -#pragma warning disable CS1591 - -using System.Xml.Linq; - -namespace Emby.Dlna.PlayTo -{ - public static class UPnpNamespaces - { - public static XNamespace Dc { get; } = "http://purl.org/dc/elements/1.1/"; - - public static XNamespace Ns { get; } = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; - - public static XNamespace Svc { get; } = "urn:schemas-upnp-org:service-1-0"; - - public static XNamespace Ud { get; } = "urn:schemas-upnp-org:device-1-0"; - - public static XNamespace UPnp { get; } = "urn:schemas-upnp-org:metadata-1-0/upnp/"; - - public static XNamespace RenderingControl { get; } = "urn:schemas-upnp-org:service:RenderingControl:1"; - - public static XNamespace AvTransport { get; } = "urn:schemas-upnp-org:service:AVTransport:1"; - - public static XNamespace ContentDirectory { get; } = "urn:schemas-upnp-org:service:ContentDirectory:1"; - - public static XName Containers { get; } = Ns + "container"; - - public static XName Items { get; } = Ns + "item"; - - public static XName Title { get; } = Dc + "title"; - - public static XName Creator { get; } = Dc + "creator"; - - public static XName Artist { get; } = UPnp + "artist"; - - public static XName Id { get; } = "id"; - - public static XName ParentId { get; } = "parentID"; - - public static XName Class { get; } = UPnp + "class"; - - public static XName Artwork { get; } = UPnp + "albumArtURI"; - - public static XName Description { get; } = Dc + "description"; - - public static XName LongDescription { get; } = UPnp + "longDescription"; - - public static XName Album { get; } = UPnp + "album"; - - public static XName Author { get; } = UPnp + "author"; - - public static XName Director { get; } = UPnp + "director"; - - public static XName PlayCount { get; } = UPnp + "playbackCount"; - - public static XName Tracknumber { get; } = UPnp + "originalTrackNumber"; - - public static XName Res { get; } = Ns + "res"; - - public static XName Duration { get; } = "duration"; - - public static XName ProtocolInfo { get; } = "protocolInfo"; - - public static XName ServiceStateTable { get; } = Svc + "serviceStateTable"; - - public static XName StateVariable { get; } = Svc + "stateVariable"; - } -} diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs deleted file mode 100644 index 54a0a87a89..0000000000 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ /dev/null @@ -1,179 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Globalization; -using MediaBrowser.Model.Dlna; - -namespace Emby.Dlna.Profiles -{ - [System.Xml.Serialization.XmlRoot("Profile")] - public class DefaultProfile : DeviceProfile - { - public DefaultProfile() - { - Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - Name = "Generic Device"; - - ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*"; - - Manufacturer = "Jellyfin"; - ModelDescription = "UPnP/AV 1.0 Compliant Media Server"; - ModelName = "Jellyfin Server"; - ModelNumber = "01"; - ModelUrl = "https://github.com/jellyfin/jellyfin"; - ManufacturerUrl = "https://github.com/jellyfin/jellyfin"; - - AlbumArtPn = "JPEG_SM"; - - MaxAlbumArtHeight = 480; - MaxAlbumArtWidth = 480; - - MaxIconWidth = 48; - MaxIconHeight = 48; - - MaxStreamingBitrate = 140000000; - MaxStaticBitrate = 140000000; - MusicStreamingTranscodingBitrate = 192000; - - EnableAlbumArtInDidl = false; - - TranscodingProfiles = new[] - { - new TranscodingProfile - { - Container = "mp3", - AudioCodec = "mp3", - Type = DlnaProfileType.Audio - }, - - new TranscodingProfile - { - Container = "ts", - Type = DlnaProfileType.Video, - AudioCodec = "aac", - VideoCodec = "h264" - }, - - new TranscodingProfile - { - Container = "jpeg", - Type = DlnaProfileType.Photo - } - }; - - DirectPlayProfiles = new[] - { - new DirectPlayProfile - { - // play all - Container = string.Empty, - Type = DlnaProfileType.Video - }, - - new DirectPlayProfile - { - // play all - Container = string.Empty, - Type = DlnaProfileType.Audio - } - }; - - SubtitleProfiles = new[] - { - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.External, - }, - - new SubtitleProfile - { - Format = "sub", - Method = SubtitleDeliveryMethod.External, - }, - - new SubtitleProfile - { - Format = "sup", - Method = SubtitleDeliveryMethod.External - }, - - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "ass", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "ssa", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "smi", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "dvdsub", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "pgs", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "pgssub", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "sub", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "sup", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "subrip", - Method = SubtitleDeliveryMethod.Embed - }, - - new SubtitleProfile - { - Format = "vtt", - Method = SubtitleDeliveryMethod.Embed - } - }; - - ResponseProfiles = new[] - { - new ResponseProfile - { - Container = "m4v", - Type = DlnaProfileType.Video, - MimeType = "video/mp4" - } - }; - } - } -} diff --git a/Emby.Dlna/Profiles/Xml/Default.xml b/Emby.Dlna/Profiles/Xml/Default.xml deleted file mode 100644 index 9460f9d5a1..0000000000 --- a/Emby.Dlna/Profiles/Xml/Default.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - Generic Device - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Denon AVR.xml b/Emby.Dlna/Profiles/Xml/Denon AVR.xml deleted file mode 100644 index 571786906c..0000000000 --- a/Emby.Dlna/Profiles/Xml/Denon AVR.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - Denon AVR - - Denon:\[AVR:.* - Denon - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml deleted file mode 100644 index eea0febfdc..0000000000 --- a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - DirecTV HD-DVR - - ^DIRECTV.*$ - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 10 - true - true - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml b/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml deleted file mode 100644 index 5b299577e1..0000000000 --- a/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - Dish Hopper-Joey - - Echostar Technologies LLC - http://www.echostar.com - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mp2t:http-get:*:video/mpeg:*,http-get:*:video/MP1S:*,http-get:*:video/mpeg2:*,http-get:*:video/mp4:*,http-get:*:video/x-matroska:*,http-get:*:audio/mpeg:*,http-get:*:audio/mpeg3:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/mp4a-latm:*,http-get:*:image/jpeg:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml deleted file mode 100644 index 20f5ba79bf..0000000000 --- a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - LG Smart TV - - LG.* - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 10 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml deleted file mode 100644 index d01e3a145e..0000000000 --- a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - Linksys DMA2100 - - DMA2100us - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Marantz.xml b/Emby.Dlna/Profiles/Xml/Marantz.xml deleted file mode 100644 index 0cc9c86e87..0000000000 --- a/Emby.Dlna/Profiles/Xml/Marantz.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - Marantz - - Marantz - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml deleted file mode 100644 index 9d5ddc3d1a..0000000000 --- a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - MediaMonkey - - MediaMonkey - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml deleted file mode 100644 index 8f766853bb..0000000000 --- a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - Panasonic Viera - - VIERA - Panasonic - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 10 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml deleted file mode 100644 index aa881d0147..0000000000 --- a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - Popcorn Hour - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml deleted file mode 100644 index 7160a9c2eb..0000000000 --- a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - Samsung Smart TV - - samsung.com - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - true - true - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml deleted file mode 100644 index c9b907e580..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - Sharp Smart TV - - Sharp - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - true - true - false - false - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml deleted file mode 100644 index 2c5614883c..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - Sony Blu-ray Player 2013 - - BDP-2013 - - - - - - - - - Microsoft Corporation - https://github.com/jellyfin/jellyfin - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml deleted file mode 100644 index 44f9821b3d..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - Sony Blu-ray Player 2014 - - BDP-2014 - - - - - - - - - Microsoft Corporation - https://github.com/jellyfin/jellyfin - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml deleted file mode 100644 index a7d17c1a07..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - Sony Blu-ray Player 2015 - - BDP-2015 - - - - - - - Microsoft Corporation - https://github.com/jellyfin/jellyfin - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml deleted file mode 100644 index b42b1e84fd..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - Sony Blu-ray Player 2016 - - BDP-2016 - - - - - - - Microsoft Corporation - https://github.com/jellyfin/jellyfin - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml deleted file mode 100644 index 46857caf09..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml +++ /dev/null @@ -1,115 +0,0 @@ - - - Sony Blu-ray Player - - Blu-ray Disc Player - Sony - - - - - - Microsoft Corporation - https://github.com/jellyfin/jellyfin - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/divx:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMABASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMAFULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/mp4:DLNA.ORG_PN=AAC_ISO_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/vnd.dlna.adts:DLNA.ORG_PN=AAC_ADTS_320;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/flac:DLNA.ORG_PN=FLAC;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:audio/ogg:DLNA.ORG_PN=OGG;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/png:DLNA.ORG_PN=PNG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/gif:DLNA.ORG_PN=GIF_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_JP_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-flv:DLNA.ORG_PN=FLV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-dvr:DLNA.ORG_PN=DVR_MS;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/wtv:DLNA.ORG_PN=WTV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/ogg:DLNA.ORG_PN=OGV;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/vnd.rn-realvideo:DLNA.ORG_PN=REAL_VIDEO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_P2_3GPP_SP_L0B_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_3GPP_P0_L10_AMR;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:video/3gpp:DLNA.ORG_PN=MPEG4_H263_MP4_P0_L10_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml deleted file mode 100644 index 1461db3117..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml +++ /dev/null @@ -1,133 +0,0 @@ - - - Sony Bravia (2010) - - KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].* - Sony - - - - - Microsoft Corporation - http://www.microsoft.com/ - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - http://www.microsoft.com/ - true - true - false - Audio,Photo,Video - JPEG_TN - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - 10 - http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=81500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=81500000000000000000000000000000 - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml deleted file mode 100644 index 7c5f2b1817..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml +++ /dev/null @@ -1,139 +0,0 @@ - - - Sony Bravia (2011) - - KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).* - Sony - - - - - Microsoft Corporation - http://www.microsoft.com/ - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - http://www.microsoft.com/ - true - true - false - Audio,Photo,Video - JPEG_TN - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - 10 - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml deleted file mode 100644 index 842a8fba33..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml +++ /dev/null @@ -1,115 +0,0 @@ - - - Sony Bravia (2012) - - KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).* - Sony - - - - - Microsoft Corporation - http://www.microsoft.com/ - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - http://www.microsoft.com/ - true - true - false - Audio,Photo,Video - JPEG_TN - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - 10 - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml deleted file mode 100644 index f1135c3fe3..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml +++ /dev/null @@ -1,114 +0,0 @@ - - - Sony Bravia (2013) - - KDL-[0-9]{2}[WR][5689][0-9]{2}A.* - Sony - - - - - Microsoft Corporation - http://www.microsoft.com/ - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - http://www.microsoft.com/ - true - true - false - Audio,Photo,Video - JPEG_TN - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - 10 - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml deleted file mode 100644 index 85c7868c66..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml +++ /dev/null @@ -1,114 +0,0 @@ - - - Sony Bravia (2014) - - (KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).* - Sony - - - - - Microsoft Corporation - http://www.microsoft.com/ - Windows Media Player Sharing - UPnP/AV 1.0 Compliant Media Server - 3.0 - http://www.microsoft.com/ - true - true - false - Audio,Photo,Video - JPEG_TN - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - 10 - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml deleted file mode 100644 index 129b188e2a..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - Sony PlayStation 3 - - PLAYSTATION 3 - - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - true - false - Audio,Photo,Video - JPEG_TN - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - 10 - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml deleted file mode 100644 index 592119305e..0000000000 --- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - Sony PlayStation 4 - - PLAYSTATION 4 - - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - true - false - Audio,Photo,Video - JPEG_TN - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - 10 - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/WDTV Live.xml b/Emby.Dlna/Profiles/Xml/WDTV Live.xml deleted file mode 100644 index ccb74ee646..0000000000 --- a/Emby.Dlna/Profiles/Xml/WDTV Live.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - WDTV Live - - WD TV - - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 5 - false - false - false - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/Xbox One.xml b/Emby.Dlna/Profiles/Xml/Xbox One.xml deleted file mode 100644 index 26a65bbd44..0000000000 --- a/Emby.Dlna/Profiles/Xml/Xbox One.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - Xbox One - - Xbox One - - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 40 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Profiles/Xml/foobar2000.xml b/Emby.Dlna/Profiles/Xml/foobar2000.xml deleted file mode 100644 index 5ce75ace55..0000000000 --- a/Emby.Dlna/Profiles/Xml/foobar2000.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - foobar2000 - - foobar - - - - - Jellyfin - https://github.com/jellyfin/jellyfin - Jellyfin Server - UPnP/AV 1.0 Compliant Media Server - 01 - https://github.com/jellyfin/jellyfin - false - false - false - Audio - JPEG_SM - 480 - 480 - 48 - 48 - 140000000 - 140000000 - 192000 - - http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:* - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Emby.Dlna/Properties/AssemblyInfo.cs b/Emby.Dlna/Properties/AssemblyInfo.cs deleted file mode 100644 index 606ffcf4fd..0000000000 --- a/Emby.Dlna/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Emby.Dlna")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] -[assembly: InternalsVisibleTo("Jellyfin.Dlna.Tests")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs deleted file mode 100644 index 69ef6f6456..0000000000 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ /dev/null @@ -1,358 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Security; -using System.Text; -using Emby.Dlna.Common; -using MediaBrowser.Model.Dlna; - -namespace Emby.Dlna.Server -{ - public class DescriptionXmlBuilder - { - private readonly DeviceProfile _profile; - - private readonly string _serverUdn; - private readonly string _serverAddress; - private readonly string _serverName; - private readonly string _serverId; - - public DescriptionXmlBuilder(DeviceProfile profile, string serverUdn, string serverAddress, string serverName, string serverId) - { - ArgumentException.ThrowIfNullOrEmpty(serverUdn); - ArgumentException.ThrowIfNullOrEmpty(serverAddress); - - _profile = profile; - _serverUdn = serverUdn; - _serverAddress = serverAddress; - _serverName = serverName; - _serverId = serverId; - } - - public string GetXml() - { - var builder = new StringBuilder(); - - builder.Append(""); - - builder.Append("'); - - builder.Append(""); - builder.Append("1"); - builder.Append("0"); - builder.Append(""); - - AppendDeviceInfo(builder); - - builder.Append(""); - - return builder.ToString(); - } - - private void AppendDeviceInfo(StringBuilder builder) - { - builder.Append(""); - AppendDeviceProperties(builder); - - AppendIconList(builder); - - builder.Append("") - .Append(SecurityElement.Escape(_serverAddress)) - .Append("/web/index.html"); - - AppendServiceList(builder); - builder.Append(""); - } - - private void AppendDeviceProperties(StringBuilder builder) - { - builder.Append(""); - - builder.Append("DMS-1.50"); - builder.Append("M-DMS-1.50"); - - builder.Append("urn:schemas-upnp-org:device:MediaServer:1"); - - builder.Append("") - .Append(SecurityElement.Escape(GetFriendlyName())) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(_profile.Manufacturer ?? string.Empty)) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(_profile.ManufacturerUrl ?? string.Empty)) - .Append(""); - - builder.Append("") - .Append(SecurityElement.Escape(_profile.ModelDescription ?? string.Empty)) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(_profile.ModelName ?? string.Empty)) - .Append(""); - - builder.Append("") - .Append(SecurityElement.Escape(_profile.ModelNumber ?? string.Empty)) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(_profile.ModelUrl ?? string.Empty)) - .Append(""); - - if (string.IsNullOrEmpty(_profile.SerialNumber)) - { - builder.Append("") - .Append(SecurityElement.Escape(_serverId)) - .Append(""); - } - else - { - builder.Append("") - .Append(SecurityElement.Escape(_profile.SerialNumber)) - .Append(""); - } - - builder.Append(""); - - builder.Append("uuid:") - .Append(SecurityElement.Escape(_serverUdn)) - .Append(""); - - if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags)) - { - builder.Append("") - .Append(SecurityElement.Escape(_profile.SonyAggregationFlags)) - .Append(""); - } - } - - internal string GetFriendlyName() - { - if (string.IsNullOrEmpty(_profile.FriendlyName)) - { - return _serverName; - } - - if (!_profile.FriendlyName.Contains("${HostName}", StringComparison.OrdinalIgnoreCase)) - { - return _profile.FriendlyName; - } - - var characterList = new List(); - - foreach (var c in _serverName) - { - if (char.IsLetterOrDigit(c) || c == '-') - { - characterList.Add(c); - } - } - - var serverName = string.Create( - characterList.Count, - characterList, - (dest, source) => - { - for (int i = 0; i < dest.Length; i++) - { - dest[i] = source[i]; - } - }); - - return _profile.FriendlyName.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase); - } - - private void AppendIconList(StringBuilder builder) - { - builder.Append(""); - - foreach (var icon in GetIcons()) - { - builder.Append(""); - - builder.Append("") - .Append(SecurityElement.Escape(icon.MimeType)) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture))) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture))) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(icon.Depth)) - .Append(""); - builder.Append("") - .Append(BuildUrl(icon.Url)) - .Append(""); - - builder.Append(""); - } - - builder.Append(""); - } - - private void AppendServiceList(StringBuilder builder) - { - builder.Append(""); - - foreach (var service in GetServices()) - { - builder.Append(""); - - builder.Append("") - .Append(SecurityElement.Escape(service.ServiceType)) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(service.ServiceId)) - .Append(""); - builder.Append("") - .Append(BuildUrl(service.ScpdUrl)) - .Append(""); - builder.Append("") - .Append(BuildUrl(service.ControlUrl)) - .Append(""); - builder.Append("") - .Append(BuildUrl(service.EventSubUrl)) - .Append(""); - - builder.Append(""); - } - - builder.Append(""); - } - - private string BuildUrl(string url) - { - if (string.IsNullOrEmpty(url)) - { - return string.Empty; - } - - url = _serverAddress.TrimEnd('/') + "/dlna/" + _serverUdn + "/" + url.TrimStart('/'); - - return SecurityElement.Escape(url); - } - - private IEnumerable GetIcons() - => new[] - { - new DeviceIcon - { - MimeType = "image/png", - Depth = "24", - Width = 240, - Height = 240, - Url = "icons/logo240.png" - }, - - new DeviceIcon - { - MimeType = "image/jpeg", - Depth = "24", - Width = 240, - Height = 240, - Url = "icons/logo240.jpg" - }, - - new DeviceIcon - { - MimeType = "image/png", - Depth = "24", - Width = 120, - Height = 120, - Url = "icons/logo120.png" - }, - - new DeviceIcon - { - MimeType = "image/jpeg", - Depth = "24", - Width = 120, - Height = 120, - Url = "icons/logo120.jpg" - }, - - new DeviceIcon - { - MimeType = "image/png", - Depth = "24", - Width = 48, - Height = 48, - Url = "icons/logo48.png" - }, - - new DeviceIcon - { - MimeType = "image/jpeg", - Depth = "24", - Width = 48, - Height = 48, - Url = "icons/logo48.jpg" - } - }; - - private IEnumerable GetServices() - { - var list = new List(); - - list.Add(new DeviceService - { - ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1", - ServiceId = "urn:upnp-org:serviceId:ContentDirectory", - ScpdUrl = "contentdirectory/contentdirectory.xml", - ControlUrl = "contentdirectory/control", - EventSubUrl = "contentdirectory/events" - }); - - list.Add(new DeviceService - { - ServiceType = "urn:schemas-upnp-org:service:ConnectionManager:1", - ServiceId = "urn:upnp-org:serviceId:ConnectionManager", - ScpdUrl = "connectionmanager/connectionmanager.xml", - ControlUrl = "connectionmanager/control", - EventSubUrl = "connectionmanager/events" - }); - - if (_profile.EnableMSMediaReceiverRegistrar) - { - list.Add(new DeviceService - { - ServiceType = "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", - ServiceId = "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar", - ScpdUrl = "mediareceiverregistrar/mediareceiverregistrar.xml", - ControlUrl = "mediareceiverregistrar/control", - EventSubUrl = "mediareceiverregistrar/events" - }); - } - - return list; - } - - public override string ToString() - { - return GetXml(); - } - } -} diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs deleted file mode 100644 index bff5307a49..0000000000 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ /dev/null @@ -1,242 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using System.Xml; -using Emby.Dlna.Didl; -using Jellyfin.Extensions; -using MediaBrowser.Controller.Configuration; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.Service -{ - public abstract class BaseControlHandler - { - private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/"; - - protected BaseControlHandler(IServerConfigurationManager config, ILogger logger) - { - Config = config; - Logger = logger; - } - - protected IServerConfigurationManager Config { get; } - - protected ILogger Logger { get; } - - public async Task ProcessControlRequestAsync(ControlRequest request) - { - try - { - LogRequest(request); - - var response = await ProcessControlRequestInternalAsync(request).ConfigureAwait(false); - LogResponse(response); - return response; - } - catch (Exception ex) - { - Logger.LogError(ex, "Error processing control request"); - - return ControlErrorHandler.GetResponse(ex); - } - } - - private async Task ProcessControlRequestInternalAsync(ControlRequest request) - { - ControlRequestInfo requestInfo; - - using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8)) - { - var readerSettings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true, - Async = true - }; - - using var reader = XmlReader.Create(streamReader, readerSettings); - requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false); - } - - Logger.LogDebug("Received control request {LocalName}, params: {@Headers}", requestInfo.LocalName, requestInfo.Headers); - - return CreateControlResponse(requestInfo); - } - - private ControlResponse CreateControlResponse(ControlRequestInfo requestInfo) - { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CloseOutput = false - }; - - StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8); - - using (var writer = XmlWriter.Create(builder, settings)) - { - writer.WriteStartDocument(true); - - writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv); - writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/"); - - writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv); - writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI); - - WriteResult(requestInfo.LocalName, requestInfo.Headers, writer); - - writer.WriteFullEndElement(); - writer.WriteFullEndElement(); - - writer.WriteFullEndElement(); - writer.WriteEndDocument(); - } - - var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=", StringComparison.Ordinal); - - var controlResponse = new ControlResponse(xml, true); - - controlResponse.Headers.Add("EXT", string.Empty); - - return controlResponse; - } - - private async Task ParseRequestAsync(XmlReader reader) - { - await reader.MoveToContentAsync().ConfigureAwait(false); - await reader.ReadAsync().ConfigureAwait(false); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - if (string.Equals(reader.LocalName, "Body", StringComparison.Ordinal)) - { - if (reader.IsEmptyElement) - { - await reader.ReadAsync().ConfigureAwait(false); - continue; - } - - using var subReader = reader.ReadSubtree(); - return await ParseBodyTagAsync(subReader).ConfigureAwait(false); - } - - await reader.SkipAsync().ConfigureAwait(false); - } - else - { - await reader.ReadAsync().ConfigureAwait(false); - } - } - - throw new EndOfStreamException("Stream ended but no body tag found."); - } - - private async Task ParseBodyTagAsync(XmlReader reader) - { - string? namespaceURI = null, localName = null; - - await reader.MoveToContentAsync().ConfigureAwait(false); - await reader.ReadAsync().ConfigureAwait(false); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - localName = reader.LocalName; - namespaceURI = reader.NamespaceURI; - - if (reader.IsEmptyElement) - { - await reader.ReadAsync().ConfigureAwait(false); - } - else - { - var result = new ControlRequestInfo(localName, namespaceURI); - using var subReader = reader.ReadSubtree(); - await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); - return result; - } - } - else - { - await reader.ReadAsync().ConfigureAwait(false); - } - } - - if (localName is not null && namespaceURI is not null) - { - return new ControlRequestInfo(localName, namespaceURI); - } - - throw new EndOfStreamException("Stream ended but no control found."); - } - - private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary headers) - { - await reader.MoveToContentAsync().ConfigureAwait(false); - await reader.ReadAsync().ConfigureAwait(false); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - // TODO: Should we be doing this here, or should it be handled earlier when decoding the request? - headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false); - } - else - { - await reader.ReadAsync().ConfigureAwait(false); - } - } - } - - protected abstract void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter); - - private void LogRequest(ControlRequest request) - { - if (!Config.GetDlnaConfiguration().EnableDebugLog) - { - return; - } - - Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers); - } - - private void LogResponse(ControlResponse response) - { - if (!Config.GetDlnaConfiguration().EnableDebugLog) - { - return; - } - - Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml); - } - - private class ControlRequestInfo - { - public ControlRequestInfo(string localName, string namespaceUri) - { - LocalName = localName; - NamespaceURI = namespaceUri; - Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - public string LocalName { get; set; } - - public string NamespaceURI { get; set; } - - public Dictionary Headers { get; } - } - } -} diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs deleted file mode 100644 index 67e7bf6a63..0000000000 --- a/Emby.Dlna/Service/BaseService.cs +++ /dev/null @@ -1,37 +0,0 @@ -#nullable disable -#pragma warning disable CS1591 - -using System.Net.Http; -using Emby.Dlna.Eventing; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.Service -{ - public class BaseService : IDlnaEventManager - { - protected BaseService(ILogger logger, IHttpClientFactory httpClientFactory) - { - Logger = logger; - EventManager = new DlnaEventManager(logger, httpClientFactory); - } - - protected IDlnaEventManager EventManager { get; } - - protected ILogger Logger { get; } - - public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) - { - return EventManager.CancelEventSubscription(subscriptionId); - } - - public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl) - { - return EventManager.RenewEventSubscription(subscriptionId, notificationType, requestedTimeoutString, callbackUrl); - } - - public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) - { - return EventManager.CreateEventSubscription(notificationType, requestedTimeoutString, callbackUrl); - } - } -} diff --git a/Emby.Dlna/Service/ControlErrorHandler.cs b/Emby.Dlna/Service/ControlErrorHandler.cs deleted file mode 100644 index 3e2cd6d2e4..0000000000 --- a/Emby.Dlna/Service/ControlErrorHandler.cs +++ /dev/null @@ -1,52 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.IO; -using System.Text; -using System.Xml; -using Emby.Dlna.Didl; - -namespace Emby.Dlna.Service -{ - public static class ControlErrorHandler - { - private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/"; - - public static ControlResponse GetResponse(Exception ex) - { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CloseOutput = false - }; - - StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8); - - using (var writer = XmlWriter.Create(builder, settings)) - { - writer.WriteStartDocument(true); - - writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv); - writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/"); - - writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv); - writer.WriteStartElement("SOAP-ENV", "Fault", NsSoapEnv); - - writer.WriteElementString("faultcode", "500"); - writer.WriteElementString("faultstring", ex.Message); - - writer.WriteStartElement("detail"); - writer.WriteRaw("401Invalid Action"); - writer.WriteFullEndElement(); - - writer.WriteFullEndElement(); - writer.WriteFullEndElement(); - - writer.WriteFullEndElement(); - writer.WriteEndDocument(); - } - - return new ControlResponse(builder.ToString(), false); - } - } -} diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs deleted file mode 100644 index 6e0bc6ad8b..0000000000 --- a/Emby.Dlna/Service/ServiceXmlBuilder.cs +++ /dev/null @@ -1,109 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using System.Security; -using System.Text; -using Emby.Dlna.Common; - -namespace Emby.Dlna.Service -{ - public class ServiceXmlBuilder - { - public string GetXml(IEnumerable actions, IEnumerable stateVariables) - { - var builder = new StringBuilder(); - - builder.Append(""); - builder.Append(""); - - builder.Append(""); - builder.Append("1"); - builder.Append("0"); - builder.Append(""); - - AppendActionList(builder, actions); - AppendServiceStateTable(builder, stateVariables); - - builder.Append(""); - - return builder.ToString(); - } - - private static void AppendActionList(StringBuilder builder, IEnumerable actions) - { - builder.Append(""); - - foreach (var item in actions) - { - builder.Append(""); - - builder.Append("") - .Append(SecurityElement.Escape(item.Name)) - .Append(""); - - builder.Append(""); - - foreach (var argument in item.ArgumentList) - { - builder.Append(""); - - builder.Append("") - .Append(SecurityElement.Escape(argument.Name)) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(argument.Direction)) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(argument.RelatedStateVariable)) - .Append(""); - - builder.Append(""); - } - - builder.Append(""); - - builder.Append(""); - } - - builder.Append(""); - } - - private static void AppendServiceStateTable(StringBuilder builder, IEnumerable stateVariables) - { - builder.Append(""); - - foreach (var item in stateVariables) - { - var sendEvents = item.SendsEvents ? "yes" : "no"; - - builder.Append(""); - - builder.Append("") - .Append(SecurityElement.Escape(item.Name)) - .Append(""); - builder.Append("") - .Append(SecurityElement.Escape(item.DataType)) - .Append(""); - - if (item.AllowedValues.Count > 0) - { - builder.Append(""); - foreach (var allowedValue in item.AllowedValues) - { - builder.Append("") - .Append(SecurityElement.Escape(allowedValue)) - .Append(""); - } - - builder.Append(""); - } - - builder.Append(""); - } - - builder.Append(""); - } - } -} diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs deleted file mode 100644 index 4fbbc38859..0000000000 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ /dev/null @@ -1,151 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Linq; -using Jellyfin.Data.Events; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Dlna; -using Rssdp; -using Rssdp.Infrastructure; - -namespace Emby.Dlna.Ssdp -{ - public sealed class DeviceDiscovery : IDeviceDiscovery, IDisposable - { - private readonly object _syncLock = new object(); - - private readonly IServerConfigurationManager _config; - - private SsdpDeviceLocator _deviceLocator; - private ISsdpCommunicationsServer _commsServer; - - private int _listenerCount; - private bool _disposed; - - public DeviceDiscovery(IServerConfigurationManager config) - { - _config = config; - } - - private event EventHandler> DeviceDiscoveredInternal; - - /// - public event EventHandler> DeviceDiscovered - { - add - { - lock (_syncLock) - { - _listenerCount++; - DeviceDiscoveredInternal += value; - } - - StartInternal(); - } - - remove - { - lock (_syncLock) - { - _listenerCount--; - DeviceDiscoveredInternal -= value; - } - } - } - - /// - public event EventHandler> DeviceLeft; - - // Call this method from somewhere in your code to start the search. - public void Start(ISsdpCommunicationsServer communicationsServer) - { - _commsServer = communicationsServer; - - StartInternal(); - } - - private void StartInternal() - { - lock (_syncLock) - { - if (_listenerCount > 0 && _deviceLocator is null && _commsServer is not null) - { - _deviceLocator = new SsdpDeviceLocator( - _commsServer, - Environment.OSVersion.Platform.ToString(), - // Can not use VersionString here since that includes OS and version - Environment.OSVersion.Version.ToString()); - - // (Optional) Set the filter so we only see notifications for devices we care about - // (can be any search target value i.e device type, uuid value etc - any value that appears in the - // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method). - // _DeviceLocator.NotificationFilter = "upnp:rootdevice"; - - // Connect our event handler so we process devices as they are found - _deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable; - _deviceLocator.DeviceUnavailable += OnDeviceLocatorDeviceUnavailable; - - var dueTime = TimeSpan.FromSeconds(5); - var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds); - - _deviceLocator.RestartBroadcastTimer(dueTime, interval); - } - } - } - - // Process each found device in the event handler - private void OnDeviceLocatorDeviceAvailable(object sender, DeviceAvailableEventArgs e) - { - var originalHeaders = e.DiscoveredDevice.ResponseHeaders; - - var headerDict = originalHeaders is null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); - - var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - - var args = new GenericEventArgs( - new UpnpDeviceInfo - { - Location = e.DiscoveredDevice.DescriptionLocation, - Headers = headers, - RemoteIPAddress = e.RemoteIPAddress - }); - - DeviceDiscoveredInternal?.Invoke(this, args); - } - - private void OnDeviceLocatorDeviceUnavailable(object sender, DeviceUnavailableEventArgs e) - { - var originalHeaders = e.DiscoveredDevice.ResponseHeaders; - - var headerDict = originalHeaders is null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); - - var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - - var args = new GenericEventArgs( - new UpnpDeviceInfo - { - Location = e.DiscoveredDevice.DescriptionLocation, - Headers = headers - }); - - DeviceLeft?.Invoke(this, args); - } - - /// - public void Dispose() - { - if (!_disposed) - { - _disposed = true; - if (_deviceLocator is not null) - { - _deviceLocator.Dispose(); - _deviceLocator = null; - } - } - } - } -} diff --git a/Emby.Dlna/Ssdp/SsdpExtensions.cs b/Emby.Dlna/Ssdp/SsdpExtensions.cs deleted file mode 100644 index d00eb02b46..0000000000 --- a/Emby.Dlna/Ssdp/SsdpExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -#pragma warning disable CS1591 - -using System.Linq; -using System.Xml.Linq; - -namespace Emby.Dlna.Ssdp -{ - public static class SsdpExtensions - { - public static string? GetValue(this XElement container, XName name) - { - var node = container.Element(name); - - return node?.Value; - } - - public static string? GetAttributeValue(this XElement container, XName name) - { - var node = container.Attribute(name); - - return node?.Value; - } - - public static string? GetDescendantValue(this XElement container, XName name) - => container.Descendants(name).FirstOrDefault()?.Value; - } -} diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4540ab205a..8955424099 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -13,7 +13,6 @@ using System.Net; using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; -using Emby.Dlna.Main; using Emby.Naming.Common; using Emby.Photos; using Emby.Server.Implementations.Channels; @@ -867,9 +866,6 @@ namespace Emby.Server.Implementations // MediaEncoding yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly; - // Dlna - yield return typeof(DlnaHost).Assembly; - // Local metadata yield return typeof(BoxSetXmlSaver).Assembly; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 905f36e43e..abe3871816 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -14,7 +14,6 @@ - diff --git a/Emby.Server.Implementations/SystemManager.cs b/Emby.Server.Implementations/SystemManager.cs index 2c477218fe..c4552474cb 100644 --- a/Emby.Server.Implementations/SystemManager.cs +++ b/Emby.Server.Implementations/SystemManager.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 49f5bf232c..aa7be9109e 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -4,7 +4,6 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.Text; -using Emby.Dlna.Extensions; using Jellyfin.Api.Middleware; using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.Networking.HappyEyeballs; @@ -122,7 +121,6 @@ namespace Jellyfin.Server .AddCheck>(nameof(JellyfinDbContext)); services.AddHlsPlaylistGenerator(); - services.AddDlnaServices(_serverApplicationHost); } /// diff --git a/Jellyfin.sln b/Jellyfin.sln index 6f23124542..cf656bcba6 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -25,8 +25,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" @@ -141,10 +139,6 @@ Global {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU