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 c70f4db0de..0000000000 Binary files a/Emby.Dlna/Images/logo120.jpg and /dev/null differ diff --git a/Emby.Dlna/Images/logo120.png b/Emby.Dlna/Images/logo120.png deleted file mode 100644 index 14f6c8d5f6..0000000000 Binary files a/Emby.Dlna/Images/logo120.png and /dev/null differ diff --git a/Emby.Dlna/Images/logo240.jpg b/Emby.Dlna/Images/logo240.jpg deleted file mode 100644 index 78a27f1b54..0000000000 Binary files a/Emby.Dlna/Images/logo240.jpg and /dev/null differ diff --git a/Emby.Dlna/Images/logo240.png b/Emby.Dlna/Images/logo240.png deleted file mode 100644 index ff50314d44..0000000000 Binary files a/Emby.Dlna/Images/logo240.png and /dev/null differ diff --git a/Emby.Dlna/Images/logo48.jpg b/Emby.Dlna/Images/logo48.jpg deleted file mode 100644 index 269bcf5894..0000000000 Binary files a/Emby.Dlna/Images/logo48.jpg and /dev/null differ diff --git a/Emby.Dlna/Images/logo48.png b/Emby.Dlna/Images/logo48.png deleted file mode 100644 index d6b5fd1df1..0000000000 Binary files a/Emby.Dlna/Images/logo48.png and /dev/null differ diff --git a/Emby.Dlna/Images/people48.jpg b/Emby.Dlna/Images/people48.jpg deleted file mode 100644 index 3ed287062b..0000000000 Binary files a/Emby.Dlna/Images/people48.jpg and /dev/null differ diff --git a/Emby.Dlna/Images/people48.png b/Emby.Dlna/Images/people48.png deleted file mode 100644 index dae5f6057f..0000000000 Binary files a/Emby.Dlna/Images/people48.png and /dev/null differ diff --git a/Emby.Dlna/Images/people480.jpg b/Emby.Dlna/Images/people480.jpg deleted file mode 100644 index 01a3162068..0000000000 Binary files a/Emby.Dlna/Images/people480.jpg and /dev/null differ diff --git a/Emby.Dlna/Images/people480.png b/Emby.Dlna/Images/people480.png deleted file mode 100644 index 800a3d8041..0000000000 Binary files a/Emby.Dlna/Images/people480.png and /dev/null differ 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