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/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 2bcd5eab29..a3484f43e9 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,7 +1,5 @@ using System; -using System.Linq; using System.Net; -using System.Net.NetworkInformation; using System.Net.Sockets; using MediaBrowser.Model.Net; @@ -37,83 +35,5 @@ namespace Emby.Server.Implementations.Net throw; } } - - /// - public Socket CreateSsdpUdpSocket(IPData bindInterface, int localPort) - { - var interfaceAddress = bindInterface.Address; - ArgumentNullException.ThrowIfNull(interfaceAddress); - - if (localPort < 0) - { - throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); - } - - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - try - { - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - socket.Bind(new IPEndPoint(interfaceAddress, localPort)); - - return socket; - } - catch - { - socket.Dispose(); - - throw; - } - } - - /// - public Socket CreateUdpMulticastSocket(IPAddress multicastAddress, IPData bindInterface, int multicastTimeToLive, int localPort) - { - var bindIPAddress = bindInterface.Address; - ArgumentNullException.ThrowIfNull(multicastAddress); - ArgumentNullException.ThrowIfNull(bindIPAddress); - - if (multicastTimeToLive <= 0) - { - throw new ArgumentException("multicastTimeToLive cannot be zero or less.", nameof(multicastTimeToLive)); - } - - if (localPort < 0) - { - throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); - } - - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - - try - { - socket.MulticastLoopback = false; - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true); - socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); - - if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) - { - socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress)); - socket.Bind(new IPEndPoint(multicastAddress, localPort)); - } - else - { - // Only create socket if interface supports multicast - var interfaceIndex = bindInterface.Index; - var interfaceIndexSwapped = IPAddress.HostToNetworkOrder(interfaceIndex); - - socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex)); - socket.Bind(new IPEndPoint(bindIPAddress, localPort)); - } - - return socket; - } - catch - { - socket.Dispose(); - - throw; - } - } } } 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.Api/Attributes/DlnaEnabledAttribute.cs b/Jellyfin.Api/Attributes/DlnaEnabledAttribute.cs deleted file mode 100644 index d3a6ac9c81..0000000000 --- a/Jellyfin.Api/Attributes/DlnaEnabledAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Emby.Dlna; -using MediaBrowser.Controller.Configuration; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.DependencyInjection; - -namespace Jellyfin.Api.Attributes; - -/// -public sealed class DlnaEnabledAttribute : ActionFilterAttribute -{ - /// - public override void OnActionExecuting(ActionExecutingContext context) - { - var serverConfigurationManager = context.HttpContext.RequestServices.GetRequiredService(); - - var enabled = serverConfigurationManager.GetDlnaConfiguration().EnableServer; - - if (!enabled) - { - context.Result = new StatusCodeResult(StatusCodes.Status503ServiceUnavailable); - } - } -} diff --git a/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs b/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs deleted file mode 100644 index cbd32ed822..0000000000 --- a/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace Jellyfin.Api.Attributes; - -/// -/// Identifies an action that supports the HTTP GET method. -/// -public sealed class HttpSubscribeAttribute : HttpMethodAttribute -{ - private static readonly IEnumerable _supportedMethods = new[] { "SUBSCRIBE" }; - - /// - /// Initializes a new instance of the class. - /// - public HttpSubscribeAttribute() - : base(_supportedMethods) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The route template. May not be null. - public HttpSubscribeAttribute(string template) - : base(_supportedMethods, template) - => ArgumentNullException.ThrowIfNull(template); -} diff --git a/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs b/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs deleted file mode 100644 index f4a6dcdaf9..0000000000 --- a/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace Jellyfin.Api.Attributes; - -/// -/// Identifies an action that supports the HTTP GET method. -/// -public sealed class HttpUnsubscribeAttribute : HttpMethodAttribute -{ - private static readonly IEnumerable _supportedMethods = new[] { "UNSUBSCRIBE" }; - - /// - /// Initializes a new instance of the class. - /// - public HttpUnsubscribeAttribute() - : base(_supportedMethods) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The route template. May not be null. - public HttpUnsubscribeAttribute(string template) - : base(_supportedMethods, template) - => ArgumentNullException.ThrowIfNull(template); -} diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 968193a6f8..5bc5330861 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -15,7 +15,6 @@ namespace Jellyfin.Api.Controllers; /// /// The audio controller. /// -// TODO: In order to authenticate this in the future, Dlna playback will require updating public class AudioController : BaseJellyfinApiController { private readonly AudioHelper _audioHelper; @@ -95,7 +94,7 @@ public class AudioController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -147,7 +146,6 @@ public class AudioController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -260,7 +258,7 @@ public class AudioController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -312,7 +310,6 @@ public class AudioController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, diff --git a/Jellyfin.Api/Controllers/DlnaController.cs b/Jellyfin.Api/Controllers/DlnaController.cs deleted file mode 100644 index 79a41ce3b4..0000000000 --- a/Jellyfin.Api/Controllers/DlnaController.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Jellyfin.Api.Constants; -using MediaBrowser.Common.Api; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Model.Dlna; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Jellyfin.Api.Controllers; - -/// -/// Dlna Controller. -/// -[Authorize(Policy = Policies.RequiresElevation)] -public class DlnaController : BaseJellyfinApiController -{ - private readonly IDlnaManager _dlnaManager; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - public DlnaController(IDlnaManager dlnaManager) - { - _dlnaManager = dlnaManager; - } - - /// - /// Get profile infos. - /// - /// Device profile infos returned. - /// An containing the device profile infos. - [HttpGet("ProfileInfos")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetProfileInfos() - { - return Ok(_dlnaManager.GetProfileInfos()); - } - - /// - /// Gets the default profile. - /// - /// Default device profile returned. - /// An containing the default profile. - [HttpGet("Profiles/Default")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetDefaultProfile() - { - return _dlnaManager.GetDefaultProfile(); - } - - /// - /// Gets a single profile. - /// - /// Profile Id. - /// Device profile returned. - /// Device profile not found. - /// An containing the profile on success, or a if device profile not found. - [HttpGet("Profiles/{profileId}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetProfile([FromRoute, Required] string profileId) - { - var profile = _dlnaManager.GetProfile(profileId); - if (profile is null) - { - return NotFound(); - } - - return profile; - } - - /// - /// Deletes a profile. - /// - /// Profile id. - /// Device profile deleted. - /// Device profile not found. - /// A on success, or a if profile not found. - [HttpDelete("Profiles/{profileId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteProfile([FromRoute, Required] string profileId) - { - var existingDeviceProfile = _dlnaManager.GetProfile(profileId); - if (existingDeviceProfile is null) - { - return NotFound(); - } - - _dlnaManager.DeleteProfile(profileId); - return NoContent(); - } - - /// - /// Creates a profile. - /// - /// Device profile. - /// Device profile created. - /// A . - [HttpPost("Profiles")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult CreateProfile([FromBody] DeviceProfile deviceProfile) - { - _dlnaManager.CreateProfile(deviceProfile); - return NoContent(); - } - - /// - /// Updates a profile. - /// - /// Profile id. - /// Device profile. - /// Device profile updated. - /// Device profile not found. - /// A on success, or a if profile not found. - [HttpPost("Profiles/{profileId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateProfile([FromRoute, Required] string profileId, [FromBody] DeviceProfile deviceProfile) - { - var existingDeviceProfile = _dlnaManager.GetProfile(profileId); - if (existingDeviceProfile is null) - { - return NotFound(); - } - - _dlnaManager.UpdateProfile(profileId, deviceProfile); - return NoContent(); - } -} diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs deleted file mode 100644 index ce8d910ffd..0000000000 --- a/Jellyfin.Api/Controllers/DlnaServerController.cs +++ /dev/null @@ -1,330 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Net.Mime; -using System.Threading.Tasks; -using Emby.Dlna; -using Jellyfin.Api.Attributes; -using Jellyfin.Api.Constants; -using MediaBrowser.Common.Api; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Model.Net; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Jellyfin.Api.Controllers; - -/// -/// Dlna Server Controller. -/// -[Route("Dlna")] -[DlnaEnabled] -[Authorize(Policy = Policies.AnonymousLanAccessPolicy)] -public class DlnaServerController : BaseJellyfinApiController -{ - private readonly IDlnaManager _dlnaManager; - private readonly IContentDirectory _contentDirectory; - private readonly IConnectionManager _connectionManager; - private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - public DlnaServerController( - IDlnaManager dlnaManager, - IContentDirectory contentDirectory, - IConnectionManager connectionManager, - IMediaReceiverRegistrar mediaReceiverRegistrar) - { - _dlnaManager = dlnaManager; - _contentDirectory = contentDirectory; - _connectionManager = connectionManager; - _mediaReceiverRegistrar = mediaReceiverRegistrar; - } - - /// - /// Get Description Xml. - /// - /// Server UUID. - /// Description xml returned. - /// DLNA is disabled. - /// An containing the description xml. - [HttpGet("{serverId}/description")] - [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - public ActionResult GetDescriptionXml([FromRoute, Required] string serverId) - { - var url = GetAbsoluteUri(); - var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); - var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress); - return Ok(xml); - } - - /// - /// Gets Dlna content directory xml. - /// - /// Server UUID. - /// Dlna content directory returned. - /// DLNA is disabled. - /// An containing the dlna content directory xml. - [HttpGet("{serverId}/ContentDirectory")] - [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")] - [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] - public ActionResult GetContentDirectory([FromRoute, Required] string serverId) - { - return Ok(_contentDirectory.GetServiceXml()); - } - - /// - /// Gets Dlna media receiver registrar xml. - /// - /// Server UUID. - /// Dlna media receiver registrar xml returned. - /// DLNA is disabled. - /// Dlna media receiver registrar xml. - [HttpGet("{serverId}/MediaReceiverRegistrar")] - [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")] - [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] - public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId) - { - return Ok(_mediaReceiverRegistrar.GetServiceXml()); - } - - /// - /// Gets Dlna media receiver registrar xml. - /// - /// Server UUID. - /// Dlna media receiver registrar xml returned. - /// DLNA is disabled. - /// Dlna media receiver registrar xml. - [HttpGet("{serverId}/ConnectionManager")] - [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")] - [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] - public ActionResult GetConnectionManager([FromRoute, Required] string serverId) - { - return Ok(_connectionManager.GetServiceXml()); - } - - /// - /// Process a content directory control request. - /// - /// Server UUID. - /// Request processed. - /// DLNA is disabled. - /// Control response. - [HttpPost("{serverId}/ContentDirectory/Control")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - public async Task> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId) - { - return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false); - } - - /// - /// Process a connection manager control request. - /// - /// Server UUID. - /// Request processed. - /// DLNA is disabled. - /// Control response. - [HttpPost("{serverId}/ConnectionManager/Control")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - public async Task> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId) - { - return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false); - } - - /// - /// Process a media receiver registrar control request. - /// - /// Server UUID. - /// Request processed. - /// DLNA is disabled. - /// Control response. - [HttpPost("{serverId}/MediaReceiverRegistrar/Control")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - public async Task> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId) - { - return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); - } - - /// - /// Processes an event subscription request. - /// - /// Server UUID. - /// Request processed. - /// DLNA is disabled. - /// Event subscription response. - [HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")] - [HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")] - [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - public ActionResult ProcessMediaReceiverRegistrarEventRequest(string serverId) - { - return ProcessEventRequest(_mediaReceiverRegistrar); - } - - /// - /// Processes an event subscription request. - /// - /// Server UUID. - /// Request processed. - /// DLNA is disabled. - /// Event subscription response. - [HttpSubscribe("{serverId}/ContentDirectory/Events")] - [HttpUnsubscribe("{serverId}/ContentDirectory/Events")] - [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - public ActionResult ProcessContentDirectoryEventRequest(string serverId) - { - return ProcessEventRequest(_contentDirectory); - } - - /// - /// Processes an event subscription request. - /// - /// Server UUID. - /// Request processed. - /// DLNA is disabled. - /// Event subscription response. - [HttpSubscribe("{serverId}/ConnectionManager/Events")] - [HttpUnsubscribe("{serverId}/ConnectionManager/Events")] - [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [Produces(MediaTypeNames.Text.Xml)] - [ProducesFile(MediaTypeNames.Text.Xml)] - public ActionResult ProcessConnectionManagerEventRequest(string serverId) - { - return ProcessEventRequest(_connectionManager); - } - - /// - /// Gets a server icon. - /// - /// Server UUID. - /// The icon filename. - /// Request processed. - /// Not Found. - /// DLNA is disabled. - /// Icon stream. - [HttpGet("{serverId}/icons/{fileName}")] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [ProducesImageFile] - public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName) - { - return GetIconInternal(fileName); - } - - /// - /// Gets a server icon. - /// - /// The icon filename. - /// Icon stream. - /// Request processed. - /// Not Found. - /// DLNA is disabled. - [HttpGet("icons/{fileName}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] - [ProducesImageFile] - public ActionResult GetIcon([FromRoute, Required] string fileName) - { - return GetIconInternal(fileName); - } - - private ActionResult GetIconInternal(string fileName) - { - var icon = _dlnaManager.GetIcon(fileName); - if (icon is null) - { - return NotFound(); - } - - return File(icon.Stream, MimeTypes.GetMimeType(fileName)); - } - - private string GetAbsoluteUri() - { - return $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Request.Path}"; - } - - private Task ProcessControlRequestInternalAsync(string id, Stream requestStream, IUpnpService service) - { - return service.ProcessControlRequestAsync(new ControlRequest(Request.Headers) - { - InputXml = requestStream, - TargetServerUuId = id, - RequestedUrl = GetAbsoluteUri() - }); - } - - private EventSubscriptionResponse ProcessEventRequest(IDlnaEventManager dlnaEventManager) - { - var subscriptionId = Request.Headers["SID"]; - if (string.Equals(Request.Method, "subscribe", StringComparison.OrdinalIgnoreCase)) - { - var notificationType = Request.Headers["NT"]; - var callback = Request.Headers["CALLBACK"]; - var timeoutString = Request.Headers["TIMEOUT"]; - - if (string.IsNullOrEmpty(notificationType)) - { - return dlnaEventManager.RenewEventSubscription( - subscriptionId, - notificationType, - timeoutString, - callback); - } - - return dlnaEventManager.CreateEventSubscription(notificationType, timeoutString, callback); - } - - return dlnaEventManager.CancelEventSubscription(subscriptionId); - } -} diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 38953dc21f..9e9c610cc5 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -17,8 +17,6 @@ using Jellyfin.Extensions; using Jellyfin.MediaEncoding.Hls.Playlist; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Encoder; @@ -49,12 +47,10 @@ public class DynamicHlsController : BaseJellyfinApiController private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; - private readonly IDlnaManager _dlnaManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; - private readonly IDeviceManager _deviceManager; private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ILogger _logger; private readonly EncodingHelper _encodingHelper; @@ -67,12 +63,10 @@ public class DynamicHlsController : BaseJellyfinApiController /// /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the class. /// Instance of the interface. /// Instance of . @@ -81,12 +75,10 @@ public class DynamicHlsController : BaseJellyfinApiController public DynamicHlsController( ILibraryManager libraryManager, IUserManager userManager, - IDlnaManager dlnaManager, IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, - IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, ILogger logger, DynamicHlsHelper dynamicHlsHelper, @@ -95,12 +87,10 @@ public class DynamicHlsController : BaseJellyfinApiController { _libraryManager = libraryManager; _userManager = userManager; - _dlnaManager = dlnaManager; _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; _fileSystem = fileSystem; - _deviceManager = deviceManager; _transcodingJobHelper = transcodingJobHelper; _logger = logger; _dynamicHlsHelper = dynamicHlsHelper; @@ -176,7 +166,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -231,7 +221,6 @@ public class DynamicHlsController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -294,8 +283,6 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _dlnaManager, - _deviceManager, _transcodingJobHelper, TranscodingJobType, cancellationToken) @@ -422,7 +409,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -477,7 +464,6 @@ public class DynamicHlsController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -594,7 +580,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -647,7 +633,6 @@ public class DynamicHlsController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -760,7 +745,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -814,7 +799,6 @@ public class DynamicHlsController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -928,7 +912,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -981,7 +965,6 @@ public class DynamicHlsController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -1105,7 +1088,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -1161,7 +1144,6 @@ public class DynamicHlsController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -1286,7 +1268,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -1341,7 +1323,6 @@ public class DynamicHlsController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -1402,8 +1383,6 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _dlnaManager, - _deviceManager, _transcodingJobHelper, TranscodingJobType, cancellationTokenSource.Token) @@ -1442,8 +1421,6 @@ public class DynamicHlsController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _dlnaManager, - _deviceManager, _transcodingJobHelper, TranscodingJobType, cancellationToken) diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 7aa5d01e23..5d9868eb9f 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -16,8 +16,6 @@ using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -42,11 +40,9 @@ public class VideosController : BaseJellyfinApiController private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; - private readonly IDlnaManager _dlnaManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IDeviceManager _deviceManager; private readonly TranscodingJobHelper _transcodingJobHelper; private readonly IHttpClientFactory _httpClientFactory; private readonly EncodingHelper _encodingHelper; @@ -59,11 +55,9 @@ public class VideosController : BaseJellyfinApiController /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the class. /// Instance of the interface. /// Instance of . @@ -71,11 +65,9 @@ public class VideosController : BaseJellyfinApiController ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService, - IDlnaManager dlnaManager, IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, IHttpClientFactory httpClientFactory, EncodingHelper encodingHelper) @@ -83,11 +75,9 @@ public class VideosController : BaseJellyfinApiController _libraryManager = libraryManager; _userManager = userManager; _dtoService = dtoService; - _dlnaManager = dlnaManager; _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _deviceManager = deviceManager; _transcodingJobHelper = transcodingJobHelper; _httpClientFactory = httpClientFactory; _encodingHelper = encodingHelper; @@ -324,7 +314,7 @@ public class VideosController : BaseJellyfinApiController [FromQuery] bool? @static, [FromQuery] string? @params, [FromQuery] string? tag, - [FromQuery] string? deviceProfileId, + [FromQuery, ParameterObsolete] string? deviceProfileId, [FromQuery] string? playSessionId, [FromQuery] string? segmentContainer, [FromQuery] int? segmentLength, @@ -381,7 +371,6 @@ public class VideosController : BaseJellyfinApiController Static = @static ?? false, Params = @params, Tag = tag, - DeviceProfileId = deviceProfileId, PlaySessionId = playSessionId, SegmentContainer = segmentContainer, SegmentLength = segmentLength, @@ -438,8 +427,6 @@ public class VideosController : BaseJellyfinApiController _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _dlnaManager, - _deviceManager, _transcodingJobHelper, _transcodingJobType, cancellationTokenSource.Token) @@ -447,8 +434,6 @@ public class VideosController : BaseJellyfinApiController if (@static.HasValue && @static.Value && state.DirectStreamProvider is not null) { - StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, state.Request.StartTimeTicks, Request, _dlnaManager); - var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfo(streamingRequest.LiveStreamId); if (liveStreamInfo is null) { @@ -463,8 +448,6 @@ public class VideosController : BaseJellyfinApiController // Static remote stream if (@static.HasValue && @static.Value && state.InputProtocol == MediaProtocol.Http) { - StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, state.Request.StartTimeTicks, Request, _dlnaManager); - var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, httpClient, HttpContext).ConfigureAwait(false); } @@ -475,12 +458,6 @@ public class VideosController : BaseJellyfinApiController } var outputPath = state.OutputFilePath; - var outputPathExists = System.IO.File.Exists(outputPath); - - var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); - var isTranscodeCached = outputPathExists && transcodingJob is not null; - - StreamingHelpers.AddDlnaHeaders(state, Response.Headers, (@static.HasValue && @static.Value) || isTranscodeCached, state.Request.StartTimeTicks, Request, _dlnaManager); // Static stream if (@static.HasValue && @static.Value) diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index 2b18c389d7..926ce99dd8 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -7,8 +7,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.MediaInfo; @@ -23,13 +21,11 @@ namespace Jellyfin.Api.Helpers; /// public class AudioHelper { - private readonly IDlnaManager _dlnaManager; private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IDeviceManager _deviceManager; private readonly TranscodingJobHelper _transcodingJobHelper; private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpContextAccessor _httpContextAccessor; @@ -38,37 +34,31 @@ public class AudioHelper /// /// Initializes a new instance of the class. /// - /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of . /// Instance of the interface. /// Instance of the interface. /// Instance of . public AudioHelper( - IDlnaManager dlnaManager, IUserManager userManager, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor, EncodingHelper encodingHelper) { - _dlnaManager = dlnaManager; _userManager = userManager; _libraryManager = libraryManager; _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _deviceManager = deviceManager; _transcodingJobHelper = transcodingJobHelper; _httpClientFactory = httpClientFactory; _httpContextAccessor = httpContextAccessor; @@ -104,8 +94,6 @@ public class AudioHelper _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _dlnaManager, - _deviceManager, _transcodingJobHelper, transcodingJobType, cancellationTokenSource.Token) @@ -113,8 +101,6 @@ public class AudioHelper if (streamingRequest.Static && state.DirectStreamProvider is not null) { - StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); - var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfo(streamingRequest.LiveStreamId); if (liveStreamInfo is null) { @@ -129,8 +115,6 @@ public class AudioHelper // Static remote stream if (streamingRequest.Static && state.InputProtocol == MediaProtocol.Http) { - StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); - var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false); } @@ -141,12 +125,6 @@ public class AudioHelper } var outputPath = state.OutputFilePath; - var outputPathExists = File.Exists(outputPath); - - var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); - var isTranscodeCached = outputPathExists && transcodingJob is not null; - - StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, streamingRequest.Static || isTranscodeCached, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); // Static stream if (streamingRequest.Static) diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index a8df628f0d..05f7d44bf0 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -16,8 +16,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Trickplay; @@ -38,11 +36,9 @@ public class DynamicHlsHelper { private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; - private readonly IDlnaManager _dlnaManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IDeviceManager _deviceManager; private readonly TranscodingJobHelper _transcodingJobHelper; private readonly INetworkManager _networkManager; private readonly ILogger _logger; @@ -55,11 +51,9 @@ public class DynamicHlsHelper /// /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of . /// Instance of the interface. /// Instance of the interface. @@ -69,11 +63,9 @@ public class DynamicHlsHelper public DynamicHlsHelper( ILibraryManager libraryManager, IUserManager userManager, - IDlnaManager dlnaManager, IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, INetworkManager networkManager, ILogger logger, @@ -83,11 +75,9 @@ public class DynamicHlsHelper { _libraryManager = libraryManager; _userManager = userManager; - _dlnaManager = dlnaManager; _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _deviceManager = deviceManager; _transcodingJobHelper = transcodingJobHelper; _networkManager = networkManager; _logger = logger; @@ -140,8 +130,6 @@ public class DynamicHlsHelper _serverConfigurationManager, _mediaEncoder, _encodingHelper, - _dlnaManager, - _deviceManager, _transcodingJobHelper, transcodingJobType, cancellationTokenSource.Token) diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 7d9a389312..71c62b2356 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -12,15 +12,12 @@ using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Jellyfin.Api.Helpers; @@ -41,8 +38,6 @@ public static class StreamingHelpers /// Instance of the interface. /// Instance of the interface. /// Instance of . - /// Instance of the interface. - /// Instance of the interface. /// Initialized . /// The . /// The . @@ -56,21 +51,11 @@ public static class StreamingHelpers IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, EncodingHelper encodingHelper, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, TranscodingJobType transcodingJobType, CancellationToken cancellationToken) { var httpRequest = httpContext.Request; - // Parse the DLNA time seek header - if (!streamingRequest.StartTimeTicks.HasValue) - { - var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"]; - - streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString()); - } - if (!string.IsNullOrWhiteSpace(streamingRequest.Params)) { ParseParams(streamingRequest); @@ -89,16 +74,11 @@ public static class StreamingHelpers streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url); } - var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) || - streamingRequest.StreamOptions.ContainsKey("dlnaheaders") || - string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase); - var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper) { Request = streamingRequest, RequestedUrl = url, - UserAgent = httpRequest.Headers[HeaderNames.UserAgent], - EnableDlnaHeaders = enableDlnaHeaders + UserAgent = httpRequest.Headers[HeaderNames.UserAgent] }; var userId = httpContext.User.GetUserId(); @@ -243,8 +223,6 @@ public static class StreamingHelpers } } - ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static); - var ext = string.IsNullOrWhiteSpace(state.OutputContainer) ? GetOutputFileExtension(state, mediaSource) : ("." + state.OutputContainer); @@ -254,123 +232,6 @@ public static class StreamingHelpers return state; } - /// - /// Adds the dlna headers. - /// - /// The state. - /// The response headers. - /// if set to true [is statically streamed]. - /// The start time in ticks. - /// The . - /// Instance of the interface. - public static void AddDlnaHeaders( - StreamState state, - IHeaderDictionary responseHeaders, - bool isStaticallyStreamed, - long? startTimeTicks, - HttpRequest request, - IDlnaManager dlnaManager) - { - if (!state.EnableDlnaHeaders) - { - return; - } - - var profile = state.DeviceProfile; - - StringValues transferMode = request.Headers["transferMode.dlna.org"]; - responseHeaders.Append("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString()); - responseHeaders.Append("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"); - - if (state.RunTimeTicks.HasValue) - { - if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase)) - { - var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds; - responseHeaders.Append("MediaInfo.sec", string.Format( - CultureInfo.InvariantCulture, - "SEC_Duration={0};", - Convert.ToInt32(ms))); - } - - if (!isStaticallyStreamed && profile is not null) - { - AddTimeSeekResponseHeaders(state, responseHeaders, startTimeTicks); - } - } - - profile ??= dlnaManager.GetDefaultProfile(); - - var audioCodec = state.ActualOutputAudioCodec; - - if (!state.IsVideoRequest) - { - responseHeaders.Append("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader( - profile, - state.OutputContainer, - audioCodec, - state.OutputAudioBitrate, - state.OutputAudioSampleRate, - state.OutputAudioChannels, - state.OutputAudioBitDepth, - isStaticallyStreamed, - state.RunTimeTicks, - state.TranscodeSeekInfo)); - } - else - { - var videoCodec = state.ActualOutputVideoCodec; - - responseHeaders.Append( - "contentFeatures.dlna.org", - ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty); - } - } - - /// - /// Parses the time seek header. - /// - /// The time seek header string. - /// A nullable representing the seek time in ticks. - private static long? ParseTimeSeekHeader(ReadOnlySpan value) - { - if (value.IsEmpty) - { - return null; - } - - const string npt = "npt="; - if (!value.StartsWith(npt, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Invalid timeseek header"); - } - - var index = value.IndexOf('-'); - value = index == -1 - ? value.Slice(npt.Length) - : value.Slice(npt.Length, index - npt.Length); - if (!value.Contains(':')) - { - // Parses npt times in the format of '417.33' - if (double.TryParse(value, CultureInfo.InvariantCulture, out var seconds)) - { - return TimeSpan.FromSeconds(seconds).Ticks; - } - - throw new ArgumentException("Invalid timeseek header"); - } - - try - { - // Parses npt times in the format of '10:19:25.7' - return TimeSpan.Parse(value, CultureInfo.InvariantCulture).Ticks; - } - catch - { - throw new ArgumentException("Invalid timeseek header"); - } - } - /// /// Parses query parameters as StreamOptions. /// @@ -393,29 +254,6 @@ public static class StreamingHelpers return streamOptions; } - /// - /// Adds the dlna time seek headers to the response. - /// - /// The current . - /// The of the response. - /// The start time in ticks. - private static void AddTimeSeekResponseHeaders(StreamState state, IHeaderDictionary responseHeaders, long? startTimeTicks) - { - var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks!.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture); - var startSeconds = TimeSpan.FromTicks(startTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture); - - responseHeaders.Append("TimeSeekRange.dlna.org", string.Format( - CultureInfo.InvariantCulture, - "npt={0}-{1}/{1}", - startSeconds, - runtimeSeconds)); - responseHeaders.Append("X-AvailableSeekRange", string.Format( - CultureInfo.InvariantCulture, - "1 npt={0}-{1}", - startSeconds, - runtimeSeconds)); - } - /// /// Gets the output file extension. /// @@ -519,79 +357,6 @@ public static class StreamingHelpers return Path.Combine(folder, filename + ext); } - private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static) - { - if (!string.IsNullOrWhiteSpace(deviceProfileId)) - { - state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); - - if (state.DeviceProfile is null) - { - var caps = deviceManager.GetCapabilities(deviceProfileId); - state.DeviceProfile = caps is null ? dlnaManager.GetProfile(request.Headers) : caps.DeviceProfile; - } - } - - var profile = state.DeviceProfile; - - if (profile is null) - { - // Don't use settings from the default profile. - // Only use a specific profile if it was requested. - return; - } - - var audioCodec = state.ActualOutputAudioCodec; - var videoCodec = state.ActualOutputVideoCodec; - - var mediaProfile = !state.IsVideoRequest - ? profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) - : profile.GetVideoMediaProfile( - state.OutputContainer, - audioCodec, - videoCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetVideoProfile, - state.TargetVideoRangeType, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TargetTimestamp, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC); - - if (mediaProfile is not null) - { - state.MimeType = mediaProfile.MimeType; - } - - if (!(@static.HasValue && @static.Value)) - { - var transcodingProfile = !state.IsVideoRequest ? profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) : profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec); - - if (transcodingProfile is not null) - { - state.EstimateContentLength = transcodingProfile.EstimateContentLength; - // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; - state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - - if (state.VideoRequest is not null) - { - state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps; - state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; - } - } - } - } - /// /// Parses the parameters. /// @@ -619,7 +384,7 @@ public static class StreamingHelpers switch (i) { case 0: - request.DeviceProfileId = val; + // DeviceProfileId break; case 1: request.DeviceId = val; diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 2473fb288a..d8fd70ac3c 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -18,7 +18,7 @@ - + diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs index f249f3bc6c..cc1f9163e5 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs @@ -138,16 +138,6 @@ public class StreamState : EncodingJobInfo, IDisposable /// public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - /// - /// Gets or sets a value indicating whether to enable dlna headers. - /// - public bool EnableDlnaHeaders { get; set; } - - /// - /// Gets or sets the device profile. - /// - public DeviceProfile? DeviceProfile { get; set; } - /// /// Gets or sets the transcoding job. /// diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs index 389d6006d0..a357498d4c 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs @@ -7,11 +7,6 @@ namespace Jellyfin.Api.Models.StreamingDtos; /// public class StreamingRequestDto : BaseEncodingJobOptions { - /// - /// Gets or sets the device profile. - /// - public string? DeviceProfileId { get; set; } - /// /// Gets or sets the params. /// 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 cad23fc5ee..60a857a2ea 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -23,10 +23,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" 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}" @@ -65,8 +61,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jellyfin.Networking\Jellyfin.Networking.csproj", "{0A3FCC4D-C714-4072-B90F-E374A15F9FF9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}" @@ -139,14 +133,6 @@ Global {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {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 @@ -199,10 +185,6 @@ Global {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A3FCC4D-C714-4072-B90F-E374A15F9FF9}.Release|Any CPU.Build.0 = Release|Any CPU - {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Release|Any CPU.Build.0 = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -262,7 +244,6 @@ Global {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs index 128034eb8f..62b87d9f57 100644 --- a/MediaBrowser.Model/Net/ISocketFactory.cs +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -14,22 +14,4 @@ public interface ISocketFactory /// The local port to bind to. /// A new unicast socket using the specified local port number. Socket CreateUdpBroadcastSocket(int localPort); - - /// - /// Creates a new unicast socket using the specified local port number. - /// - /// The bind interface. - /// The local port to bind to. - /// A new unicast socket using the specified local port number. - Socket CreateSsdpUdpSocket(IPData bindInterface, int localPort); - - /// - /// Creates a new multicast socket using the specified multicast IP address, multicast time to live and local port. - /// - /// The multicast IP address to bind to. - /// The bind interface. - /// The multicast time to live value. Actually a maximum number of network hops for UDP packets. - /// The local port to bind to. - /// A new multicast socket using the specfied bind interface, multicast address, multicast time to live and port. - Socket CreateUdpMulticastSocket(IPAddress multicastAddress, IPData bindInterface, int multicastTimeToLive, int localPort); } diff --git a/RSSDP/DeviceAvailableEventArgs.cs b/RSSDP/DeviceAvailableEventArgs.cs deleted file mode 100644 index f933f258be..0000000000 --- a/RSSDP/DeviceAvailableEventArgs.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Net; - -namespace Rssdp -{ - /// - /// Event arguments for the event. - /// - public sealed class DeviceAvailableEventArgs : EventArgs - { - public IPAddress RemoteIPAddress { get; set; } - - private readonly DiscoveredSsdpDevice _DiscoveredDevice; - - private readonly bool _IsNewlyDiscovered; - - /// - /// Full constructor. - /// - /// A instance representing the available device. - /// A boolean value indicating whether or not this device came from the cache. See for more detail. - /// Thrown if the parameter is null. - public DeviceAvailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool isNewlyDiscovered) - { - if (discoveredDevice == null) - { - throw new ArgumentNullException(nameof(discoveredDevice)); - } - - _DiscoveredDevice = discoveredDevice; - _IsNewlyDiscovered = isNewlyDiscovered; - } - - /// - /// Returns true if the device was discovered due to an alive notification, or a search and was not already in the cache. Returns false if the item came from the cache but matched the current search request. - /// - public bool IsNewlyDiscovered - { - get { return _IsNewlyDiscovered; } - } - - /// - /// A reference to a instance containing the discovered details and allowing access to the full device description. - /// - public DiscoveredSsdpDevice DiscoveredDevice - { - get { return _DiscoveredDevice; } - } - } -} diff --git a/RSSDP/DeviceEventArgs.cs b/RSSDP/DeviceEventArgs.cs deleted file mode 100644 index 2455ccbfad..0000000000 --- a/RSSDP/DeviceEventArgs.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; - -namespace Rssdp -{ - /// - /// Event arguments for the and events. - /// - public sealed class DeviceEventArgs : EventArgs - { - private readonly SsdpDevice _Device; - - /// - /// Constructs a new instance for the specified . - /// - /// The associated with the event this argument class is being used for. - /// Thrown if the argument is null. - public DeviceEventArgs(SsdpDevice device) - { - if (device == null) - { - throw new ArgumentNullException(nameof(device)); - } - - _Device = device; - } - - /// - /// Returns the instance the event being raised for. - /// - public SsdpDevice Device - { - get { return _Device; } - } - } -} diff --git a/RSSDP/DeviceUnavailableEventArgs.cs b/RSSDP/DeviceUnavailableEventArgs.cs deleted file mode 100644 index ca25152027..0000000000 --- a/RSSDP/DeviceUnavailableEventArgs.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; - -namespace Rssdp -{ - /// - /// Event arguments for the event. - /// - public sealed class DeviceUnavailableEventArgs : EventArgs - { - private readonly DiscoveredSsdpDevice _DiscoveredDevice; - - private readonly bool _Expired; - - /// - /// Full constructor. - /// - /// A instance representing the device that has become unavailable. - /// A boolean value indicating whether this device is unavailable because it expired, or because it explicitly sent a byebye notification.. See for more detail. - /// Thrown if the parameter is null. - public DeviceUnavailableEventArgs(DiscoveredSsdpDevice discoveredDevice, bool expired) - { - if (discoveredDevice == null) - { - throw new ArgumentNullException(nameof(discoveredDevice)); - } - - _DiscoveredDevice = discoveredDevice; - _Expired = expired; - } - - /// - /// Returns true if the device is considered unavailable because it's cached information expired before a new alive notification or search result was received. Returns false if the device is unavailable because it sent an explicit notification of it's unavailability. - /// - public bool Expired - { - get { return _Expired; } - } - - /// - /// A reference to a instance containing the discovery details of the removed device. - /// - public DiscoveredSsdpDevice DiscoveredDevice - { - get { return _DiscoveredDevice; } - } - } -} diff --git a/RSSDP/DiscoveredSsdpDevice.cs b/RSSDP/DiscoveredSsdpDevice.cs deleted file mode 100644 index 322bd55e57..0000000000 --- a/RSSDP/DiscoveredSsdpDevice.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Net.Http.Headers; - -namespace Rssdp -{ - /// - /// Represents a discovered device, containing basic information about the device and the location of it's full device description document. Also provides convenience methods for retrieving the device description document. - /// - /// - /// - public sealed class DiscoveredSsdpDevice - { - private DateTimeOffset _AsAt; - - /// - /// Sets or returns the type of notification, being either a uuid, device type, service type or upnp:rootdevice. - /// - public string NotificationType { get; set; } - - /// - /// Sets or returns the universal service name (USN) of the device. - /// - public string Usn { get; set; } - - /// - /// Sets or returns a URL pointing to the device description document for this device. - /// - public Uri DescriptionLocation { get; set; } - - /// - /// Sets or returns the length of time this information is valid for (from the time). - /// - public TimeSpan CacheLifetime { get; set; } - - /// - /// Sets or returns the date and time this information was received. - /// - public DateTimeOffset AsAt - { - get { return _AsAt; } - - set - { - if (_AsAt != value) - { - _AsAt = value; - } - } - } - - /// - /// Returns the headers from the SSDP device response message. - /// - public HttpHeaders ResponseHeaders { get; set; } - - /// - /// Returns true if this device information has expired, based on the current date/time, and the & properties. - /// - /// - public bool IsExpired() - { - return this.CacheLifetime == TimeSpan.Zero || this.AsAt.Add(this.CacheLifetime) <= DateTimeOffset.Now; - } - - /// - /// Returns the device's value. - /// - /// A string containing the device's universal service name. - public override string ToString() - { - return this.Usn; - } - } -} diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs deleted file mode 100644 index 5d7da4124e..0000000000 --- a/RSSDP/DisposableManagedObjectBase.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text; - -namespace Rssdp.Infrastructure -{ - /// - /// Correctly implements the interface and pattern for an object containing only managed resources, and adds a few common niceties not on the interface such as an property. - /// - public abstract class DisposableManagedObjectBase : IDisposable - { - /// - /// Override this method and dispose any objects you own the lifetime of if disposing is true; - /// - /// True if managed objects should be disposed, if false, only unmanaged resources should be released. - protected abstract void Dispose(bool disposing); - - /// - /// Throws and if the property is true. - /// - /// - /// Thrown if the property is true. - /// - protected virtual void ThrowIfDisposed() - { - if (this.IsDisposed) - { - throw new ObjectDisposedException(this.GetType().FullName); - } - } - - /// - /// Sets or returns a boolean indicating whether or not this instance has been disposed. - /// - /// - public bool IsDisposed - { - get; - private set; - } - - public string BuildMessage(string header, Dictionary values) - { - var builder = new StringBuilder(); - - const string ArgFormat = "{0}: {1}\r\n"; - - builder.AppendFormat(CultureInfo.InvariantCulture, "{0}\r\n", header); - - foreach (var pair in values) - { - builder.AppendFormat(CultureInfo.InvariantCulture, ArgFormat, pair.Key, pair.Value); - } - - builder.Append("\r\n"); - - return builder.ToString(); - } - - /// - /// Disposes this object instance and all internally managed resources. - /// - /// - /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behavior of derived classes. - /// - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfere with the dispose process.")] - public void Dispose() - { - IsDisposed = true; - - Dispose(true); - } - } -} diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs deleted file mode 100644 index 1949a9df33..0000000000 --- a/RSSDP/HttpParserBase.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; - -namespace Rssdp.Infrastructure -{ - /// - /// A base class for the and classes. Not intended for direct use. - /// - /// - public abstract class HttpParserBase where T : new() - { - private readonly string[] LineTerminators = new string[] { "\r\n", "\n" }; - private readonly char[] SeparatorCharacters = new char[] { ',', ';' }; - - /// - /// Parses the provided into either a or object. - /// - /// A string containing the HTTP message to parse. - /// Either a or object containing the parsed data. - public abstract T Parse(string data); - - /// - /// Parses a string containing either an HTTP request or response into a or object. - /// - /// A or object representing the parsed message. - /// A reference to the collection for the object. - /// A string containing the data to be parsed. - /// An object containing the content of the parsed message. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Honestly, it's fine. MemoryStream doesn't mind.")] - protected virtual void Parse(T message, System.Net.Http.Headers.HttpHeaders headers, string data) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - if (data.Length == 0) - { - throw new ArgumentException("data cannot be an empty string.", nameof(data)); - } - - if (!LineTerminators.Any(data.Contains)) - { - throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data)); - } - - using (var retVal = new ByteArrayContent(Array.Empty())) - { - var lines = data.Split(LineTerminators, StringSplitOptions.None); - - // First line is the 'request' line containing http protocol details like method, uri, http version etc. - ParseStatusLine(lines[0], message); - - ParseHeaders(headers, retVal.Headers, lines); - } - } - - /// - /// Used to parse the first line of an HTTP request or response and assign the values to the appropriate properties on the . - /// - /// The first line of the HTTP message to be parsed. - /// Either a or to assign the parsed values to. - protected abstract void ParseStatusLine(string data, T message); - - /// - /// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false). - /// - /// A string containing the name of the header to return the type of. - protected abstract bool IsContentHeader(string headerName); - - /// - /// Parses the HTTP version text from an HTTP request or response status line and returns a object representing the parsed values. - /// - /// A string containing the HTTP version, from the message status line. - /// A object containing the parsed version data. - protected Version ParseHttpVersion(string versionData) - { - if (versionData == null) - { - throw new ArgumentNullException(nameof(versionData)); - } - - var versionSeparatorIndex = versionData.IndexOf('/', StringComparison.Ordinal); - if (versionSeparatorIndex <= 0 || versionSeparatorIndex == versionData.Length) - { - throw new ArgumentException("request header line is invalid. Http Version not supplied or incorrect format.", nameof(versionData)); - } - - return Version.Parse(versionData.Substring(versionSeparatorIndex + 1)); - } - - /// - /// Parses a line from an HTTP request or response message containing a header name and value pair. - /// - /// A string containing the data to be parsed. - /// A reference to a collection to which the parsed header will be added. - /// A reference to a collection for the message content, to which the parsed header will be added. - private void ParseHeader(string line, System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders) - { - // Header format is - // name: value - var headerKeySeparatorIndex = line.IndexOf(':', StringComparison.Ordinal); - var headerName = line.Substring(0, headerKeySeparatorIndex).Trim(); - var headerValue = line.Substring(headerKeySeparatorIndex + 1).Trim(); - - // Not sure how to determine where request headers and content headers begin, - // at least not without a known set of headers (general headers first the content headers) - // which seems like a bad way of doing it. So we'll assume if it's a known content header put it there - // else use request headers. - - var values = ParseValues(headerValue); - var headersToAddTo = IsContentHeader(headerName) ? contentHeaders : headers; - - if (values.Count > 1) - { - headersToAddTo.TryAddWithoutValidation(headerName, values); - } - else - { - headersToAddTo.TryAddWithoutValidation(headerName, values[0]); - } - } - - private int ParseHeaders(System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders, string[] lines) - { - // Blank line separates headers from content, so read headers until we find blank line. - int lineIndex = 1; - string line = null, nextLine = null; - while (lineIndex + 1 < lines.Length && !String.IsNullOrEmpty((line = lines[lineIndex++]))) - { - // If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability. - // Combine these lines into a single comma separated style header for easier parsing. - while (lineIndex < lines.Length && !String.IsNullOrEmpty((nextLine = lines[lineIndex]))) - { - if (nextLine.Length > 0 && Char.IsWhiteSpace(nextLine[0])) - { - line += "," + nextLine.TrimStart(); - lineIndex++; - } - else - { - break; - } - } - - ParseHeader(line, headers, contentHeaders); - } - - return lineIndex; - } - - private List ParseValues(string headerValue) - { - // This really should be better and match the HTTP 1.1 spec, - // but this should actually be good enough for SSDP implementations - // I think. - var values = new List(); - - if (headerValue == "\"\"") - { - values.Add(string.Empty); - return values; - } - - var indexOfSeparator = headerValue.IndexOfAny(SeparatorCharacters); - if (indexOfSeparator <= 0) - { - values.Add(headerValue); - } - else - { - var segments = headerValue.Split(SeparatorCharacters); - if (headerValue.Contains('"', StringComparison.Ordinal)) - { - for (int segmentIndex = 0; segmentIndex < segments.Length; segmentIndex++) - { - var segment = segments[segmentIndex]; - if (segment.Trim().StartsWith("\"", StringComparison.OrdinalIgnoreCase)) - { - segment = CombineQuotedSegments(segments, ref segmentIndex, segment); - } - - values.Add(segment); - } - } - else - { - values.AddRange(segments); - } - } - - return values; - } - - private string CombineQuotedSegments(string[] segments, ref int segmentIndex, string segment) - { - var trimmedSegment = segment.Trim(); - for (int index = segmentIndex; index < segments.Length; index++) - { - if (trimmedSegment == "\"\"" || - ( - trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase) - && !trimmedSegment.EndsWith("\"\"", StringComparison.OrdinalIgnoreCase) - && !trimmedSegment.EndsWith("\\\"", StringComparison.OrdinalIgnoreCase)) - ) - { - segmentIndex = index; - return trimmedSegment.Substring(1, trimmedSegment.Length - 2); - } - - if (index + 1 < segments.Length) - { - trimmedSegment += "," + segments[index + 1].TrimEnd(); - } - } - - segmentIndex = segments.Length; - if (trimmedSegment.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && trimmedSegment.EndsWith("\"", StringComparison.OrdinalIgnoreCase)) - { - return trimmedSegment.Substring(1, trimmedSegment.Length - 2); - } - - return trimmedSegment; - } - } -} diff --git a/RSSDP/HttpRequestParser.cs b/RSSDP/HttpRequestParser.cs deleted file mode 100644 index fab70eae2c..0000000000 --- a/RSSDP/HttpRequestParser.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Net.Http; -using Jellyfin.Extensions; - -namespace Rssdp.Infrastructure -{ - /// - /// Parses a string into a or throws an exception. - /// - public sealed class HttpRequestParser : HttpParserBase - { - private readonly string[] ContentHeaderNames = new string[] - { - "Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified" - }; - - /// - /// Parses the specified data into a instance. - /// - /// A string containing the data to parse. - /// A instance containing the parsed data. - public override HttpRequestMessage Parse(string data) - { - HttpRequestMessage retVal = null; - - try - { - retVal = new HttpRequestMessage(); - - Parse(retVal, retVal.Headers, data); - - return retVal; - } - finally - { - retVal?.Dispose(); - } - } - - /// - /// Used to parse the first line of an HTTP request or response and assign the values to the appropriate properties on the . - /// - /// The first line of the HTTP message to be parsed. - /// Either a or to assign the parsed values to. - protected override void ParseStatusLine(string data, HttpRequestMessage message) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - - var parts = data.Split(' '); - if (parts.Length < 2) - { - throw new ArgumentException("Status line is invalid. Insufficient status parts.", nameof(data)); - } - - message.Method = new HttpMethod(parts[0].Trim()); - if (Uri.TryCreate(parts[1].Trim(), UriKind.RelativeOrAbsolute, out var requestUri)) - { - message.RequestUri = requestUri; - } - else - { - System.Diagnostics.Debug.WriteLine(parts[1]); - } - - if (parts.Length >= 3) - { - message.Version = ParseHttpVersion(parts[2].Trim()); - } - } - - /// - /// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false). - /// - /// A string containing the name of the header to return the type of. - protected override bool IsContentHeader(string headerName) - { - return ContentHeaderNames.Contains(headerName, StringComparison.OrdinalIgnoreCase); - } - } -} diff --git a/RSSDP/HttpResponseParser.cs b/RSSDP/HttpResponseParser.cs deleted file mode 100644 index c570c84cbb..0000000000 --- a/RSSDP/HttpResponseParser.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using Jellyfin.Extensions; - -namespace Rssdp.Infrastructure -{ - /// - /// Parses a string into a or throws an exception. - /// - public sealed class HttpResponseParser : HttpParserBase - { - private readonly string[] ContentHeaderNames = new string[] - { - "Allow", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type", "Expires", "Last-Modified" - }; - - /// - /// Parses the specified data into a instance. - /// - /// A string containing the data to parse. - /// A instance containing the parsed data. - public override HttpResponseMessage Parse(string data) - { - HttpResponseMessage retVal = null; - try - { - retVal = new HttpResponseMessage(); - - Parse(retVal, retVal.Headers, data); - - return retVal; - } - catch - { - retVal?.Dispose(); - - throw; - } - } - - /// - /// Returns a boolean indicating whether the specified HTTP header name represents a content header (true), or a message header (false). - /// - /// A string containing the name of the header to return the type of. - /// A boolean, true if th specified header relates to HTTP content, otherwise false. - protected override bool IsContentHeader(string headerName) - { - return ContentHeaderNames.Contains(headerName, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Used to parse the first line of an HTTP request or response and assign the values to the appropriate properties on the . - /// - /// The first line of the HTTP message to be parsed. - /// Either a or to assign the parsed values to. - protected override void ParseStatusLine(string data, HttpResponseMessage message) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - - var parts = data.Split(' '); - if (parts.Length < 2) - { - throw new ArgumentException("data status line is invalid. Insufficient status parts.", nameof(data)); - } - - message.Version = ParseHttpVersion(parts[0].Trim()); - - if (!Int32.TryParse(parts[1].Trim(), out var statusCode)) - { - throw new ArgumentException("data status line is invalid. Status code is not a valid integer.", nameof(data)); - } - - message.StatusCode = (HttpStatusCode)statusCode; - - if (parts.Length >= 3) - { - message.ReasonPhrase = parts[2].Trim(); - } - } - } -} diff --git a/RSSDP/IEnumerableExtensions.cs b/RSSDP/IEnumerableExtensions.cs deleted file mode 100644 index 1f0daad3e1..0000000000 --- a/RSSDP/IEnumerableExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Rssdp.Infrastructure -{ - internal static class IEnumerableExtensions - { - public static IEnumerable SelectManyRecursive(this IEnumerable source, Func> selector) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (selector == null) - { - throw new ArgumentNullException(nameof(selector)); - } - - return !source.Any() ? source : - source.Concat( - source - .SelectMany(i => selector(i).EmptyIfNull()) - .SelectManyRecursive(selector) - ); - } - - public static IEnumerable EmptyIfNull(this IEnumerable source) - { - return source ?? Enumerable.Empty(); - } - } -} diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs deleted file mode 100644 index 95b0a1c704..0000000000 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace Rssdp.Infrastructure -{ - /// - /// Interface for a component that manages network communication (sending and receiving HTTPU messages) for the SSDP protocol. - /// - public interface ISsdpCommunicationsServer : IDisposable - { - /// - /// Raised when a HTTPU request message is received by a socket (unicast or multicast). - /// - event EventHandler RequestReceived; - - /// - /// Raised when an HTTPU response message is received by a socket (unicast or multicast). - /// - event EventHandler ResponseReceived; - - /// - /// Causes the server to begin listening for multicast messages, being SSDP search requests and notifications. - /// - void BeginListeningForMulticast(); - - /// - /// Causes the server to stop listening for multicast messages, being SSDP search requests and notifications. - /// - void StopListeningForMulticast(); - - /// - /// Sends a message to a particular address (uni or multicast) and port. - /// - Task SendMessage(byte[] messageData, IPEndPoint destination, IPAddress fromLocalIPAddress, CancellationToken cancellationToken); - - /// - /// Sends a message to the SSDP multicast address and port. - /// - Task SendMulticastMessage(string message, IPAddress fromLocalIPAddress, CancellationToken cancellationToken); - Task SendMulticastMessage(string message, int sendCount, IPAddress fromLocalIPAddress, CancellationToken cancellationToken); - - /// - /// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple and/or instances. - /// - /// - /// If true, disposing an instance of a or a will not dispose this comms server instance. The calling code is responsible for managing the lifetime of the server. - /// - bool IsShared { get; set; } - } -} diff --git a/RSSDP/ISsdpDeviceLocator.cs b/RSSDP/ISsdpDeviceLocator.cs deleted file mode 100644 index 4df166cd26..0000000000 --- a/RSSDP/ISsdpDeviceLocator.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; - -namespace Rssdp.Infrastructure -{ - /// - /// Interface for components that discover the existence of SSDP devices. - /// - /// - /// Discovering devices includes explicit search requests as well as listening for broadcast status notifications. - /// - /// - /// - /// - public interface ISsdpDeviceLocator - { - /// - /// Event raised when a device becomes available or is found by a search request. - /// - /// - /// - /// - /// - event EventHandler DeviceAvailable; - - /// - /// Event raised when a device explicitly notifies of shutdown or a device expires from the cache. - /// - /// - /// - /// - /// - event EventHandler DeviceUnavailable; - - /// - /// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the or events. - /// - /// - /// Device alive/byebye notifications whose NT header does not match this filter value will still be captured and cached internally, but will not raise events about device availability. Usually used with either a device type of uuid NT header value. - /// Example filters follow; - /// upnp:rootdevice - /// urn:schemas-upnp-org:device:WANDevice:1 - /// "uuid:9F15356CC-95FA-572E-0E99-85B456BD3012" - /// - /// - /// - /// - /// - string NotificationFilter - { - get; - set; - } - - /// - /// Asynchronously performs a search for all devices using the default search timeout, and returns an awaitable task that can be used to retrieve the results. - /// - /// A task whose result is an of instances, representing all found devices. - System.Threading.Tasks.Task> SearchAsync(); - - /// - /// Performs a search for the specified search target (criteria) and default search timeout. - /// - /// The criteria for the search. Value can be; - /// - /// Root devicesupnp:rootdevice - /// Specific device by UUIDuuid:<device uuid> - /// Device typeFully qualified device type starting with urn: i.e urn:schemas-upnp-org:Basic:1 - /// - /// - /// A task whose result is an of instances, representing all found devices. - System.Threading.Tasks.Task> SearchAsync(string searchTarget); - - /// - /// Performs a search for the specified search target (criteria) and search timeout. - /// - /// The criteria for the search. Value can be; - /// - /// Root devicesupnp:rootdevice - /// Specific device by UUIDuuid:<device uuid> - /// Device typeA device namespace and type in format of urn:<device namespace>:device:<device type>:<device version> i.e urn:schemas-upnp-org:device:Basic:1 - /// Service typeA service namespace and type in format of urn:<service namespace>:service:<servicetype>:<service version> i.e urn:my-namespace:service:MyCustomService:1 - /// - /// - /// The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 is recommended by the UPnP 1.1 specification. Specify TimeSpan.Zero to return only devices already in the cache. - /// - /// By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implementing these services if you know the service type. - /// - /// A task whose result is an of instances, representing all found devices. - System.Threading.Tasks.Task> SearchAsync(string searchTarget, TimeSpan searchWaitTime); - - /// - /// Performs a search for all devices using the specified search timeout. - /// - /// The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 is recommended by the UPnP 1.1 specification. Specify TimeSpan.Zero to return only devices already in the cache. - /// A task whose result is an of instances, representing all found devices. - System.Threading.Tasks.Task> SearchAsync(TimeSpan searchWaitTime); - - /// - /// Starts listening for broadcast notifications of service availability. - /// - /// - /// When called the system will listen for 'alive' and 'byebye' notifications. This can speed up searching, as well as provide dynamic notification of new devices appearing on the network, and previously discovered devices disappearing. - /// - /// - /// - /// - /// - void StartListeningForNotifications(); - - /// - /// Stops listening for broadcast notifications of service availability. - /// - /// - /// Does nothing if this instance is not already listening for notifications. - /// - /// Throw if the property is true. - /// - /// - /// - /// - void StopListeningForNotifications(); - } -} diff --git a/RSSDP/ISsdpDevicePublisher.cs b/RSSDP/ISsdpDevicePublisher.cs deleted file mode 100644 index 96c15443d4..0000000000 --- a/RSSDP/ISsdpDevicePublisher.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Threading.Tasks; - -namespace Rssdp.Infrastructure -{ - /// - /// Interface for components that publish the existence of SSDP devices. - /// - /// - /// Publishing a device includes sending notifications (alive and byebye) as well as responding to search requests when appropriate. - /// - /// - /// - public interface ISsdpDevicePublisher - { - /// - /// Adds a device (and it's children) to the list of devices being published by this server, making them discoverable to SSDP clients. - /// - /// The instance to add. - /// An awaitable . - void AddDevice(SsdpRootDevice device); - - /// - /// Removes a device (and it's children) from the list of devices being published by this server, making them undiscoverable. - /// - /// The instance to add. - /// An awaitable . - Task RemoveDevice(SsdpRootDevice device); - - /// - /// Returns a read only list of devices being published by this instance. - /// - /// - System.Collections.Generic.IEnumerable Devices { get; } - } -} diff --git a/RSSDP/LICENSE b/RSSDP/LICENSE deleted file mode 100644 index aabeb93af0..0000000000 --- a/RSSDP/LICENSE +++ /dev/null @@ -1,4 +0,0 @@ -RSSDP - -Copyright (c) 2015 Troy Willmot -Copyright (c) 2015-2018 Luke Pulverenti diff --git a/RSSDP/Properties/AssemblyInfo.cs b/RSSDP/Properties/AssemblyInfo.cs deleted file mode 100644 index 55f7b6a834..0000000000 --- a/RSSDP/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// 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("RSSDP")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2015 Troy Willmot. Code released under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -[assembly: AssemblyVersion("1.0.3.0")] -[assembly: AssemblyFileVersion("2019.1.20.3")] diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj deleted file mode 100644 index 3f24de4e65..0000000000 --- a/RSSDP/RSSDP.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - {21002819-C39A-4D3E-BE83-2A276A77FB1F} - - - - - - - - - net8.0 - false - AllDisabledByDefault - disable - CA2016 - - - diff --git a/RSSDP/RequestReceivedEventArgs.cs b/RSSDP/RequestReceivedEventArgs.cs deleted file mode 100644 index b8b2249e42..0000000000 --- a/RSSDP/RequestReceivedEventArgs.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; - -namespace Rssdp.Infrastructure -{ - /// - /// Provides arguments for the event. - /// - public sealed class RequestReceivedEventArgs : EventArgs - { - private readonly HttpRequestMessage _Message; - - private readonly IPEndPoint _ReceivedFrom; - - public IPAddress LocalIPAddress { get; private set; } - - /// - /// Full constructor. - /// - public RequestReceivedEventArgs(HttpRequestMessage message, IPEndPoint receivedFrom, IPAddress localIPAddress) - { - _Message = message; - _ReceivedFrom = receivedFrom; - LocalIPAddress = localIPAddress; - } - - /// - /// The that was received. - /// - public HttpRequestMessage Message - { - get { return _Message; } - } - - /// - /// The the request came from. - /// - public IPEndPoint ReceivedFrom - { - get { return _ReceivedFrom; } - } - } -} diff --git a/RSSDP/ResponseReceivedEventArgs.cs b/RSSDP/ResponseReceivedEventArgs.cs deleted file mode 100644 index e87ba14524..0000000000 --- a/RSSDP/ResponseReceivedEventArgs.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; - -namespace Rssdp.Infrastructure -{ - /// - /// Provides arguments for the event. - /// - public sealed class ResponseReceivedEventArgs : EventArgs - { - public IPAddress LocalIPAddress { get; set; } - - private readonly HttpResponseMessage _Message; - - private readonly IPEndPoint _ReceivedFrom; - - /// - /// Full constructor. - /// - public ResponseReceivedEventArgs(HttpResponseMessage message, IPEndPoint receivedFrom) - { - _Message = message; - _ReceivedFrom = receivedFrom; - } - - /// - /// The that was received. - /// - public HttpResponseMessage Message - { - get { return _Message; } - } - - /// - /// The the response came from. - /// - public IPEndPoint ReceivedFrom - { - get { return _ReceivedFrom; } - } - } -} diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs deleted file mode 100644 index 42563e2edb..0000000000 --- a/RSSDP/SsdpCommunicationsServer.cs +++ /dev/null @@ -1,523 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; - -namespace Rssdp.Infrastructure -{ - /// - /// Provides the platform independent logic for publishing device existence and responding to search requests. - /// - public sealed class SsdpCommunicationsServer : DisposableManagedObjectBase, ISsdpCommunicationsServer - { - /* We could technically use one socket listening on port 1900 for everything. - * This should get both multicast (notifications) and unicast (search response) messages, however - * this often doesn't work under Windows because the MS SSDP service is running. If that service - * is running then it will steal the unicast messages and we will never see search responses. - * Since stopping the service would be a bad idea (might not be allowed security wise and might - * break other apps running on the system) the only other work around is to use two sockets. - * - * We use one group of sockets to listen for/receive notifications and search requests (_MulticastListenSockets). - * We use a second group, bound to a different local port, to send search requests and listen for - * responses (_SendSockets). The responses are sent to the local ports these sockets are bound to, - * which aren't port 1900 so the MS service doesn't steal them. While the caller can specify a local - * port to use, we will default to 0 which allows the underlying system to auto-assign a free port. - */ - - private object _BroadcastListenSocketSynchroniser = new(); - private List _MulticastListenSockets; - - private object _SendSocketSynchroniser = new(); - private List _sendSockets; - - private HttpRequestParser _RequestParser; - private HttpResponseParser _ResponseParser; - private readonly ILogger _logger; - private ISocketFactory _SocketFactory; - private readonly INetworkManager _networkManager; - - private int _LocalPort; - private int _MulticastTtl; - - private bool _IsShared; - - /// - /// Raised when a HTTPU request message is received by a socket (unicast or multicast). - /// - public event EventHandler RequestReceived; - - /// - /// Raised when an HTTPU response message is received by a socket (unicast or multicast). - /// - public event EventHandler ResponseReceived; - - /// - /// Minimum constructor. - /// - /// The argument is null. - public SsdpCommunicationsServer( - ISocketFactory socketFactory, - INetworkManager networkManager, - ILogger logger) - : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger) - { - - } - - /// - /// Full constructor. - /// - /// The argument is null. - /// The argument is less than or equal to zero. - public SsdpCommunicationsServer( - ISocketFactory socketFactory, - int localPort, - int multicastTimeToLive, - INetworkManager networkManager, - ILogger logger) - { - if (socketFactory is null) - { - throw new ArgumentNullException(nameof(socketFactory)); - } - - if (multicastTimeToLive <= 0) - { - throw new ArgumentOutOfRangeException(nameof(multicastTimeToLive), "multicastTimeToLive must be greater than zero."); - } - - _BroadcastListenSocketSynchroniser = new(); - _SendSocketSynchroniser = new(); - - _LocalPort = localPort; - _SocketFactory = socketFactory; - - _RequestParser = new(); - _ResponseParser = new(); - - _MulticastTtl = multicastTimeToLive; - _networkManager = networkManager; - _logger = logger; - } - - /// - /// Causes the server to begin listening for multicast messages, being SSDP search requests and notifications. - /// - /// Thrown if the property is true (because has been called previously). - public void BeginListeningForMulticast() - { - ThrowIfDisposed(); - - lock (_BroadcastListenSocketSynchroniser) - { - if (_MulticastListenSockets is null) - { - try - { - _MulticastListenSockets = CreateMulticastSocketsAndListen(); - } - catch (SocketException ex) - { - _logger.LogError("Failed to bind to multicast address: {Message}. DLNA will be unavailable", ex.Message); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in BeginListeningForMulticast"); - } - } - } - } - - /// - /// Causes the server to stop listening for multicast messages, being SSDP search requests and notifications. - /// - /// Thrown if the property is true (because has been called previously). - public void StopListeningForMulticast() - { - lock (_BroadcastListenSocketSynchroniser) - { - if (_MulticastListenSockets is not null) - { - _logger.LogInformation("{0} disposing _BroadcastListenSocket", GetType().Name); - foreach (var socket in _MulticastListenSockets) - { - socket.Dispose(); - } - - _MulticastListenSockets = null; - } - } - } - - /// - /// Sends a message to a particular address (uni or multicast) and port. - /// - public async Task SendMessage(byte[] messageData, IPEndPoint destination, IPAddress fromlocalIPAddress, CancellationToken cancellationToken) - { - if (messageData is null) - { - throw new ArgumentNullException(nameof(messageData)); - } - - ThrowIfDisposed(); - - var sockets = GetSendSockets(fromlocalIPAddress, destination); - - if (sockets.Count == 0) - { - return; - } - - // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP. - for (var i = 0; i < SsdpConstants.UdpResendCount; i++) - { - var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination, cancellationToken)).ToArray(); - await Task.WhenAll(tasks).ConfigureAwait(false); - - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - } - - private async Task SendFromSocket(Socket socket, byte[] messageData, IPEndPoint destination, CancellationToken cancellationToken) - { - try - { - await socket.SendToAsync(messageData, destination, cancellationToken).ConfigureAwait(false); - } - catch (ObjectDisposedException) - { - } - catch (OperationCanceledException) - { - } - catch (Exception ex) - { - var localIP = ((IPEndPoint)socket.LocalEndPoint).Address; - _logger.LogError(ex, "Error sending socket message from {0} to {1}", localIP.ToString(), destination.ToString()); - } - } - - private List GetSendSockets(IPAddress fromlocalIPAddress, IPEndPoint destination) - { - EnsureSendSocketCreated(); - - lock (_SendSocketSynchroniser) - { - var sockets = _sendSockets.Where(s => s.AddressFamily == fromlocalIPAddress.AddressFamily); - - // Send from the Any socket and the socket with the matching address - if (fromlocalIPAddress.AddressFamily == AddressFamily.InterNetwork) - { - sockets = sockets.Where(s => ((IPEndPoint)s.LocalEndPoint).Address.Equals(IPAddress.Any) - || ((IPEndPoint)s.LocalEndPoint).Address.Equals(fromlocalIPAddress)); - - // If sending to the loopback address, filter the socket list as well - if (destination.Address.Equals(IPAddress.Loopback)) - { - sockets = sockets.Where(s => ((IPEndPoint)s.LocalEndPoint).Address.Equals(IPAddress.Any) - || ((IPEndPoint)s.LocalEndPoint).Address.Equals(IPAddress.Loopback)); - } - } - else if (fromlocalIPAddress.AddressFamily == AddressFamily.InterNetworkV6) - { - sockets = sockets.Where(s => ((IPEndPoint)s.LocalEndPoint).Address.Equals(IPAddress.IPv6Any) - || ((IPEndPoint)s.LocalEndPoint).Address.Equals(fromlocalIPAddress)); - - // If sending to the loopback address, filter the socket list as well - if (destination.Address.Equals(IPAddress.IPv6Loopback)) - { - sockets = sockets.Where(s => ((IPEndPoint)s.LocalEndPoint).Address.Equals(IPAddress.IPv6Any) - || ((IPEndPoint)s.LocalEndPoint).Address.Equals(IPAddress.IPv6Loopback)); - } - } - - return sockets.ToList(); - } - } - - public Task SendMulticastMessage(string message, IPAddress fromlocalIPAddress, CancellationToken cancellationToken) - { - return SendMulticastMessage(message, SsdpConstants.UdpResendCount, fromlocalIPAddress, cancellationToken); - } - - /// - /// Sends a message to the SSDP multicast address and port. - /// - public async Task SendMulticastMessage(string message, int sendCount, IPAddress fromlocalIPAddress, CancellationToken cancellationToken) - { - if (message is null) - { - throw new ArgumentNullException(nameof(message)); - } - - byte[] messageData = Encoding.UTF8.GetBytes(message); - - ThrowIfDisposed(); - - cancellationToken.ThrowIfCancellationRequested(); - - EnsureSendSocketCreated(); - - // SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP. - for (var i = 0; i < sendCount; i++) - { - await SendMessageIfSocketNotDisposed( - messageData, - new IPEndPoint( - IPAddress.Parse(SsdpConstants.MulticastLocalAdminAddress), - SsdpConstants.MulticastPort), - fromlocalIPAddress, - cancellationToken).ConfigureAwait(false); - - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Stops listening for search responses on the local, unicast socket. - /// - /// Thrown if the property is true (because has been called previously). - public void StopListeningForResponses() - { - lock (_SendSocketSynchroniser) - { - if (_sendSockets is not null) - { - var sockets = _sendSockets.ToList(); - _sendSockets = null; - - _logger.LogInformation("{0} Disposing {1} sendSockets", GetType().Name, sockets.Count); - - foreach (var socket in sockets) - { - var socketAddress = ((IPEndPoint)socket.LocalEndPoint).Address; - _logger.LogInformation("{0} disposing sendSocket from {1}", GetType().Name, socketAddress); - socket.Dispose(); - } - } - } - } - - /// - /// Gets or sets a boolean value indicating whether or not this instance is shared amongst multiple and/or instances. - /// - /// - /// If true, disposing an instance of a or a will not dispose this comms server instance. The calling code is responsible for managing the lifetime of the server. - /// - public bool IsShared - { - get { return _IsShared; } - - set { _IsShared = value; } - } - - /// - /// Stops listening for requests, disposes this instance and all internal resources. - /// - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - StopListeningForMulticast(); - - StopListeningForResponses(); - } - } - - private Task SendMessageIfSocketNotDisposed(byte[] messageData, IPEndPoint destination, IPAddress fromlocalIPAddress, CancellationToken cancellationToken) - { - var sockets = _sendSockets; - if (sockets is not null) - { - sockets = sockets.ToList(); - - var tasks = sockets.Where(s => fromlocalIPAddress is null || fromlocalIPAddress.Equals(((IPEndPoint)s.LocalEndPoint).Address)) - .Select(s => SendFromSocket(s, messageData, destination, cancellationToken)); - return Task.WhenAll(tasks); - } - - return Task.CompletedTask; - } - - private List CreateMulticastSocketsAndListen() - { - var sockets = new List(); - var multicastGroupAddress = IPAddress.Parse(SsdpConstants.MulticastLocalAdminAddress); - - // IPv6 is currently unsupported - var validInterfaces = _networkManager.GetInternalBindAddresses() - .Where(x => x.Address is not null) - .Where(x => x.SupportsMulticast) - .Where(x => x.AddressFamily == AddressFamily.InterNetwork) - .DistinctBy(x => x.Index); - - foreach (var intf in validInterfaces) - { - try - { - var socket = _SocketFactory.CreateUdpMulticastSocket(multicastGroupAddress, intf, _MulticastTtl, SsdpConstants.MulticastPort); - _ = ListenToSocketInternal(socket); - sockets.Add(socket); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to create SSDP UDP multicast socket for {0} on interface {1} (index {2})", intf.Address, intf.Name, intf.Index); - } - } - - return sockets; - } - - private List CreateSendSockets() - { - var sockets = new List(); - - // IPv6 is currently unsupported - var validInterfaces = _networkManager.GetInternalBindAddresses() - .Where(x => x.Address is not null) - .Where(x => x.SupportsMulticast) - .Where(x => x.AddressFamily == AddressFamily.InterNetwork); - - if (OperatingSystem.IsMacOS()) - { - // Manually remove loopback on macOS due to https://github.com/dotnet/runtime/issues/24340 - validInterfaces = validInterfaces.Where(x => !x.Address.Equals(IPAddress.Loopback)); - } - - foreach (var intf in validInterfaces) - { - try - { - var socket = _SocketFactory.CreateSsdpUdpSocket(intf, _LocalPort); - _ = ListenToSocketInternal(socket); - sockets.Add(socket); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to create SSDP UDP sender socket for {0} on interface {1} (index {2})", intf.Address, intf.Name, intf.Index); - } - } - - return sockets; - } - - private async Task ListenToSocketInternal(Socket socket) - { - var cancelled = false; - var receiveBuffer = new byte[8192]; - - while (!cancelled && !IsDisposed) - { - try - { - var result = await socket.ReceiveMessageFromAsync(receiveBuffer, new IPEndPoint(IPAddress.Any, _LocalPort), CancellationToken.None).ConfigureAwait(false); - - if (result.ReceivedBytes > 0) - { - var remoteEndpoint = (IPEndPoint)result.RemoteEndPoint; - var localEndpointAdapter = _networkManager.GetAllBindInterfaces().First(a => a.Index == result.PacketInformation.Interface); - - ProcessMessage( - Encoding.UTF8.GetString(receiveBuffer, 0, result.ReceivedBytes), - remoteEndpoint, - localEndpointAdapter.Address); - } - } - catch (ObjectDisposedException) - { - cancelled = true; - } - catch (TaskCanceledException) - { - cancelled = true; - } - } - } - - private void EnsureSendSocketCreated() - { - if (_sendSockets is null) - { - lock (_SendSocketSynchroniser) - { - _sendSockets ??= CreateSendSockets(); - } - } - } - - private void ProcessMessage(string data, IPEndPoint endPoint, IPAddress receivedOnlocalIPAddress) - { - // Responses start with the HTTP version, prefixed with HTTP/ while - // requests start with a method which can vary and might be one we haven't - // seen/don't know. We'll check if this message is a request or a response - // by checking for the HTTP/ prefix on the start of the message. - _logger.LogDebug("Received data from {From} on {Port} at {Address}:\n{Data}", endPoint.Address, endPoint.Port, receivedOnlocalIPAddress, data); - if (data.StartsWith("HTTP/", StringComparison.OrdinalIgnoreCase)) - { - HttpResponseMessage responseMessage = null; - try - { - responseMessage = _ResponseParser.Parse(data); - } - catch (ArgumentException) - { - // Ignore invalid packets. - } - - if (responseMessage is not null) - { - OnResponseReceived(responseMessage, endPoint, receivedOnlocalIPAddress); - } - } - else - { - HttpRequestMessage requestMessage = null; - try - { - requestMessage = _RequestParser.Parse(data); - } - catch (ArgumentException) - { - // Ignore invalid packets. - } - - if (requestMessage is not null) - { - OnRequestReceived(requestMessage, endPoint, receivedOnlocalIPAddress); - } - } - } - - private void OnRequestReceived(HttpRequestMessage data, IPEndPoint remoteEndPoint, IPAddress receivedOnlocalIPAddress) - { - // SSDP specification says only * is currently used but other uri's might - // be implemented in the future and should be ignored unless understood. - // Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11 - if (data.RequestUri.ToString() != "*") - { - return; - } - - var handlers = RequestReceived; - handlers?.Invoke(this, new RequestReceivedEventArgs(data, remoteEndPoint, receivedOnlocalIPAddress)); - } - - private void OnResponseReceived(HttpResponseMessage data, IPEndPoint endPoint, IPAddress localIPAddress) - { - var handlers = ResponseReceived; - handlers?.Invoke(this, new ResponseReceivedEventArgs(data, endPoint) - { - LocalIPAddress = localIPAddress - }); - } - } -} diff --git a/RSSDP/SsdpConstants.cs b/RSSDP/SsdpConstants.cs deleted file mode 100644 index 442f2b8f84..0000000000 --- a/RSSDP/SsdpConstants.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Rssdp.Infrastructure -{ - /// - /// Provides constants for common values related to the SSDP protocols. - /// - public static class SsdpConstants - { - - /// - /// Multicast IP Address used for SSDP multicast messages. Values is 239.255.255.250. - /// - public const string MulticastLocalAdminAddress = "239.255.255.250"; - /// - /// The UDP port used for SSDP multicast messages. Values is 1900. - /// - public const int MulticastPort = 1900; - /// - /// The default multicase TTL for SSDP multicast messages. Value is 4. - /// - public const int SsdpDefaultMulticastTimeToLive = 4; - - internal const string MSearchMethod = "M-SEARCH"; - - internal const string SsdpDiscoverMessage = "ssdp:discover"; - internal const string SsdpDiscoverAllSTHeader = "ssdp:all"; - - internal const string SsdpDeviceDescriptionXmlNamespace = "urn:schemas-upnp-org:device-1-0"; - - internal const string ServerVersion = "1.0"; - - /// - /// Default buffer size for receiving SSDP broadcasts. Value is 8192 (bytes). - /// - public const int DefaultUdpSocketBufferSize = 8192; - /// - /// The maximum possible buffer size for a UDP message. Value is 65507 (bytes). - /// - public const int MaxUdpSocketBufferSize = 65507; // Max possible UDP packet size on IPv4 without using 'jumbograms'. - - /// - /// Namespace/prefix for UPnP device types. Values is schemas-upnp-org. - /// - public const string UpnpDeviceTypeNamespace = "schemas-upnp-org"; - /// - /// UPnP Root Device type. Value is upnp:rootdevice. - /// - public const string UpnpDeviceTypeRootDevice = "upnp:rootdevice"; - /// - /// The value is used by Windows Explorer for device searches instead of the UPNPDeviceTypeRootDevice constant. - /// Not sure why (different spec, bug, alternate protocol etc). Used to enable Windows Explorer support. - /// - public const string PnpDeviceTypeRootDevice = "pnp:rootdevice"; - /// - /// UPnP Basic Device type. Value is Basic. - /// - public const string UpnpDeviceTypeBasicDevice = "Basic"; - - internal const string SsdpKeepAliveNotification = "ssdp:alive"; - internal const string SsdpByeByeNotification = "ssdp:byebye"; - - internal const int UdpResendCount = 3; - } -} diff --git a/RSSDP/SsdpDevice.cs b/RSSDP/SsdpDevice.cs deleted file mode 100644 index 569d733ea0..0000000000 --- a/RSSDP/SsdpDevice.cs +++ /dev/null @@ -1,355 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using Rssdp.Infrastructure; - -namespace Rssdp -{ - /// - /// Base class representing the common details of a (root or embedded) device, either to be published or that has been located. - /// - /// - /// Do not derive new types directly from this class. New device classes should derive from either or . - /// - /// - /// - public abstract class SsdpDevice - { - private string _Udn; - private string _DeviceType; - private string _DeviceTypeNamespace; - private int _DeviceVersion; - - private IList _Devices; - - /// - /// Raised when a new child device is added. - /// - /// - /// - public event EventHandler DeviceAdded; - - /// - /// Raised when a child device is removed. - /// - /// - /// - public event EventHandler DeviceRemoved; - - /// - /// Derived type constructor, allows constructing a device with no parent. Should only be used from derived types that are or inherit from . - /// - protected SsdpDevice() - { - _DeviceTypeNamespace = SsdpConstants.UpnpDeviceTypeNamespace; - _DeviceType = SsdpConstants.UpnpDeviceTypeBasicDevice; - _DeviceVersion = 1; - - _Devices = new List(); - this.Devices = new ReadOnlyCollection(_Devices); - } - - public SsdpRootDevice ToRootDevice() - { - var device = this; - - var rootDevice = device as SsdpRootDevice; - if (rootDevice == null) - { - rootDevice = ((SsdpEmbeddedDevice)device).RootDevice; - } - - return rootDevice; - } - - /// - /// Sets or returns the core device type (not including namespace, version etc.). Required. - /// - /// Defaults to the UPnP basic device type. - /// - /// - /// - public string DeviceType - { - get - { - return _DeviceType; - } - - set - { - _DeviceType = value; - } - } - - public string DeviceClass { get; set; } - - /// - /// Sets or returns the namespace for the of this device. Optional, but defaults to UPnP schema so should be changed if is not a UPnP device type. - /// - /// Defaults to the UPnP standard namespace. - /// - /// - /// - public string DeviceTypeNamespace - { - get - { - return _DeviceTypeNamespace; - } - - set - { - _DeviceTypeNamespace = value; - } - } - - /// - /// Sets or returns the version of the device type. Optional, defaults to 1. - /// - /// Defaults to a value of 1. - /// - /// - /// - public int DeviceVersion - { - get - { - return _DeviceVersion; - } - - set - { - _DeviceVersion = value; - } - } - - /// - /// Returns the full device type string. - /// - /// - /// The format used is urn::device:: - /// - public string FullDeviceType - { - get - { - return String.Format( - CultureInfo.InvariantCulture, - "urn:{0}:{3}:{1}:{2}", - this.DeviceTypeNamespace ?? String.Empty, - this.DeviceType ?? String.Empty, - this.DeviceVersion, - this.DeviceClass ?? "device"); - } - } - - /// - /// Sets or returns the universally unique identifier for this device (without the uuid: prefix). Required. - /// - /// - /// Must be the same over time for a specific device instance (i.e. must survive reboots). - /// For UPnP 1.0 this can be any unique string. For UPnP 1.1 this should be a 128 bit number formatted in a specific way, preferably generated using the time and MAC based algorithm. See section 1.1.4 of http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf for details. - /// Technically this library implements UPnP 1.0, so any value is allowed, but we advise using UPnP 1.1 compatible values for good behaviour and forward compatibility with future versions. - /// - public string Uuid { get; set; } - - /// - /// Returns (or sets*) a unique device name for this device. Optional, not recommended to be explicitly set. - /// - /// - /// * In general you should not explicitly set this property. If it is not set (or set to null/empty string) the property will return a UDN value that is correct as per the UPnP specification, based on the other device properties. - /// The setter is provided to allow for devices that do not correctly follow the specification (when we discover them), rather than to intentionally deviate from the specification. - /// If a value is explicitly set, it is used verbatim, and so any prefix (such as uuid:) must be provided in the value. - /// - public string Udn - { - get - { - if (String.IsNullOrEmpty(_Udn) && !String.IsNullOrEmpty(this.Uuid)) - { - return "uuid:" + this.Uuid; - } - - return _Udn; - } - - set - { - _Udn = value; - } - } - - /// - /// Sets or returns a friendly/display name for this device on the network. Something the user can identify the device/instance by, i.e Lounge Main Light. Required. - /// - /// A short description for the end user. - public string FriendlyName { get; set; } - - /// - /// Sets or returns the name of the manufacturer of this device. Required. - /// - public string Manufacturer { get; set; } - - /// - /// Sets or returns a URL to the manufacturers web site. Optional. - /// - public Uri ManufacturerUrl { get; set; } - - /// - /// Sets or returns a description of this device model. Recommended. - /// - /// A long description for the end user. - public string ModelDescription { get; set; } - - /// - /// Sets or returns the name of this model. Required. - /// - public string ModelName { get; set; } - - /// - /// Sets or returns the number of this model. Recommended. - /// - public string ModelNumber { get; set; } - - /// - /// Sets or returns a URL to a web page with details of this device model. Optional. - /// - /// - /// Optional. May be relative to base URL. - /// - public Uri ModelUrl { get; set; } - - /// - /// Sets or returns the serial number for this device. Recommended. - /// - public string SerialNumber { get; set; } - - /// - /// Sets or returns the universal product code of the device, if any. Optional. - /// - /// - /// If not blank, must be exactly 12 numeric digits. - /// - public string Upc { get; set; } - - /// - /// Sets or returns the URL to a web page that can be used to configure/manager/use the device. Recommended. - /// - /// - /// May be relative to base URL. - /// - public Uri PresentationUrl { get; set; } - - /// - /// Returns a read-only enumerable set of objects representing children of this device. Child devices are optional. - /// - /// - /// - public IList Devices - { - get; - private set; - } - - /// - /// Adds a child device to the collection. - /// - /// The instance to add. - /// - /// If the device is already a member of the collection, this method does nothing. - /// Also sets the property of the added device and all descendant devices to the relevant instance. - /// - /// Thrown if the argument is null. - /// Thrown if the is already associated with a different instance than used in this tree. Can occur if you try to add the same device instance to more than one tree. Also thrown if you try to add a device to itself. - /// - public void AddDevice(SsdpEmbeddedDevice device) - { - if (device == null) - { - throw new ArgumentNullException(nameof(device)); - } - - if (device.RootDevice != null && device.RootDevice != this.ToRootDevice()) - { - throw new InvalidOperationException("This device is already associated with a different root device (has been added as a child in another branch)."); - } - - if (device == this) - { - throw new InvalidOperationException("Can't add device to itself."); - } - - bool wasAdded = false; - lock (_Devices) - { - device.RootDevice = this.ToRootDevice(); - _Devices.Add(device); - wasAdded = true; - } - - if (wasAdded) - { - OnDeviceAdded(device); - } - } - - /// - /// Removes a child device from the collection. - /// - /// The instance to remove. - /// - /// If the device is not a member of the collection, this method does nothing. - /// Also sets the property to null for the removed device and all descendant devices. - /// - /// Thrown if the argument is null. - /// - public void RemoveDevice(SsdpEmbeddedDevice device) - { - if (device == null) - { - throw new ArgumentNullException(nameof(device)); - } - - bool wasRemoved = false; - lock (_Devices) - { - wasRemoved = _Devices.Remove(device); - if (wasRemoved) - { - device.RootDevice = null; - } - } - - if (wasRemoved) - { - OnDeviceRemoved(device); - } - } - - /// - /// Raises the event. - /// - /// The instance added to the collection. - /// - /// - protected virtual void OnDeviceAdded(SsdpEmbeddedDevice device) - { - var handlers = this.DeviceAdded; - handlers?.Invoke(this, new DeviceEventArgs(device)); - } - - /// - /// Raises the event. - /// - /// The instance removed from the collection. - /// - /// - protected virtual void OnDeviceRemoved(SsdpEmbeddedDevice device) - { - var handlers = this.DeviceRemoved; - handlers?.Invoke(this, new DeviceEventArgs(device)); - } - } -} diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs deleted file mode 100644 index d6fad4b9d4..0000000000 --- a/RSSDP/SsdpDeviceLocator.cs +++ /dev/null @@ -1,626 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -namespace Rssdp.Infrastructure -{ - /// - /// Allows you to search the network for a particular device, device types, or UPnP service types. Also listenings for broadcast notifications of device availability and raises events to indicate changes in status. - /// - public class SsdpDeviceLocator : DisposableManagedObjectBase - { - private List _Devices; - private ISsdpCommunicationsServer _CommunicationsServer; - - private Timer _BroadcastTimer; - private object _timerLock = new(); - - private string _OSName; - - private string _OSVersion; - - private readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4); - private readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1); - - /// - /// Default constructor. - /// - public SsdpDeviceLocator( - ISsdpCommunicationsServer communicationsServer, - string osName, - string osVersion) - { - ArgumentNullException.ThrowIfNull(communicationsServer); - ArgumentNullException.ThrowIfNullOrEmpty(osName); - ArgumentNullException.ThrowIfNullOrEmpty(osVersion); - - _OSName = osName; - _OSVersion = osVersion; - _CommunicationsServer = communicationsServer; - _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; - - _Devices = new List(); - } - - /// - /// Raised for when - /// - /// An 'alive' notification is received that a device, regardless of whether or not that device is not already in the cache or has previously raised this event. - /// For each item found during a device (cached or not), allowing clients to respond to found devices before the entire search is complete. - /// Only if the notification type matches the property. By default the filter is null, meaning all notifications raise events (regardless of ant - /// - /// This event may be raised from a background thread, if interacting with UI or other objects with specific thread affinity invoking to the relevant thread is required. - /// - /// - /// - /// - /// - public event EventHandler DeviceAvailable; - - /// - /// Raised when a notification is received that indicates a device has shutdown or otherwise become unavailable. - /// - /// - /// Devices *should* broadcast these types of notifications, but not all devices do and sometimes (in the event of power loss for example) it might not be possible for a device to do so. You should also implement error handling when trying to contact a device, even if RSSDP is reporting that device as available. - /// This event is only raised if the notification type matches the property. A null or empty string for the will be treated as no filter and raise the event for all notifications. - /// The property may contain either a fully complete instance, or one containing just a USN and NotificationType property. Full information is available if the device was previously discovered and cached, but only partial information if a byebye notification was received for a previously unseen or expired device. - /// This event may be raised from a background thread, if interacting with UI or other objects with specific thread affinity invoking to the relevant thread is required. - /// - /// - /// - /// - /// - public event EventHandler DeviceUnavailable; - - public void RestartBroadcastTimer(TimeSpan dueTime, TimeSpan period) - { - lock (_timerLock) - { - if (_BroadcastTimer is null) - { - _BroadcastTimer = new Timer(OnBroadcastTimerCallback, null, dueTime, period); - } - else - { - _BroadcastTimer.Change(dueTime, period); - } - } - } - - public void DisposeBroadcastTimer() - { - lock (_timerLock) - { - if (_BroadcastTimer is not null) - { - _BroadcastTimer.Dispose(); - _BroadcastTimer = null; - } - } - } - - private async void OnBroadcastTimerCallback(object state) - { - if (IsDisposed) - { - return; - } - - StartListeningForNotifications(); - RemoveExpiredDevicesFromCache(); - - try - { - await SearchAsync(CancellationToken.None).ConfigureAwait(false); - } - catch (Exception) - { - } - } - - /// - /// Performs a search for all devices using the default search timeout. - /// - private Task SearchAsync(CancellationToken cancellationToken) - { - return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, DefaultSearchWaitTime, cancellationToken); - } - - /// - /// Performs a search for the specified search target (criteria) and default search timeout. - /// - /// The criteria for the search. Value can be; - /// - /// Root devicesupnp:rootdevice - /// Specific device by UUIDuuid:<device uuid> - /// Device typeFully qualified device type starting with urn: i.e urn:schemas-upnp-org:Basic:1 - /// - /// - private Task SearchAsync(string searchTarget) - { - return SearchAsync(searchTarget, DefaultSearchWaitTime, CancellationToken.None); - } - - /// - /// Performs a search for all devices using the specified search timeout. - /// - /// The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 seconds is recommended by the UPnP 1.1 specification, this method requires the value be greater 1 second if it is not zero. Specify TimeSpan.Zero to return only devices already in the cache. - private Task SearchAsync(TimeSpan searchWaitTime) - { - return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, searchWaitTime, CancellationToken.None); - } - - private Task SearchAsync(string searchTarget, TimeSpan searchWaitTime, CancellationToken cancellationToken) - { - if (searchTarget is null) - { - throw new ArgumentNullException(nameof(searchTarget)); - } - - if (searchTarget.Length == 0) - { - throw new ArgumentException("searchTarget cannot be an empty string.", nameof(searchTarget)); - } - - if (searchWaitTime.TotalSeconds < 0) - { - throw new ArgumentException("searchWaitTime must be a positive time."); - } - - if (searchWaitTime.TotalSeconds > 0 && searchWaitTime.TotalSeconds <= 1) - { - throw new ArgumentException("searchWaitTime must be zero (if you are not using the result and relying entirely in the events), or greater than one second."); - } - - ThrowIfDisposed(); - - return BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime), cancellationToken); - } - - /// - /// Starts listening for broadcast notifications of service availability. - /// - /// - /// When called the system will listen for 'alive' and 'byebye' notifications. This can speed up searching, as well as provide dynamic notification of new devices appearing on the network, and previously discovered devices disappearing. - /// - /// - /// - /// - /// Throw if the ty is true. - public void StartListeningForNotifications() - { - _CommunicationsServer.RequestReceived -= CommsServer_RequestReceived; - _CommunicationsServer.RequestReceived += CommsServer_RequestReceived; - _CommunicationsServer.BeginListeningForMulticast(); - } - - /// - /// Stops listening for broadcast notifications of service availability. - /// - /// - /// Does nothing if this instance is not already listening for notifications. - /// - /// - /// - /// - /// Throw if the property is true. - public void StopListeningForNotifications() - { - ThrowIfDisposed(); - - _CommunicationsServer.RequestReceived -= CommsServer_RequestReceived; - } - - /// - /// Raises the event. - /// - /// - protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IPAddress) - { - if (IsDisposed) - { - return; - } - - var handlers = DeviceAvailable; - handlers?.Invoke(this, new DeviceAvailableEventArgs(device, isNewDevice) - { - RemoteIPAddress = IPAddress - }); - } - - /// - /// Raises the event. - /// - /// A representing the device that is no longer available. - /// True if the device expired from the cache without being renewed, otherwise false to indicate the device explicitly notified us it was being shutdown. - /// - protected virtual void OnDeviceUnavailable(DiscoveredSsdpDevice device, bool expired) - { - if (IsDisposed) - { - return; - } - - var handlers = DeviceUnavailable; - handlers?.Invoke(this, new DeviceUnavailableEventArgs(device, expired)); - } - - /// - /// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the or events. - /// - /// - /// Device alive/byebye notifications whose NT header does not match this filter value will still be captured and cached internally, but will not raise events about device availability. Usually used with either a device type of uuid NT header value. - /// If the value is null or empty string then, all notifications are reported. - /// Example filters follow; - /// upnp:rootdevice - /// urn:schemas-upnp-org:device:WANDevice:1 - /// uuid:9F15356CC-95FA-572E-0E99-85B456BD3012 - /// - /// - /// - /// - /// - public string NotificationFilter - { - get; - set; - } - - /// - /// Disposes this object and all internal resources. Stops listening for all network messages. - /// - /// True if managed resources should be disposed, or false is only unmanaged resources should be cleaned up. - protected override void Dispose(bool disposing) - { - if (disposing) - { - DisposeBroadcastTimer(); - - var commsServer = _CommunicationsServer; - _CommunicationsServer = null; - if (commsServer is not null) - { - commsServer.ResponseReceived -= CommsServer_ResponseReceived; - commsServer.RequestReceived -= CommsServer_RequestReceived; - } - } - } - - private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress IPAddress) - { - bool isNewDevice = false; - lock (_Devices) - { - var existingDevice = FindExistingDeviceNotification(_Devices, device.NotificationType, device.Usn); - if (existingDevice is null) - { - _Devices.Add(device); - isNewDevice = true; - } - else - { - _Devices.Remove(existingDevice); - _Devices.Add(device); - } - } - - DeviceFound(device, isNewDevice, IPAddress); - } - - private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IPAddress) - { - if (!NotificationTypeMatchesFilter(device)) - { - return; - } - - OnDeviceAvailable(device, isNewDevice, IPAddress); - } - - private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device) - { - return String.IsNullOrEmpty(this.NotificationFilter) - || this.NotificationFilter == SsdpConstants.SsdpDiscoverAllSTHeader - || device.NotificationType == this.NotificationFilter; - } - - private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue, CancellationToken cancellationToken) - { - const string header = "M-SEARCH * HTTP/1.1"; - - var values = new Dictionary(StringComparer.OrdinalIgnoreCase); - - values["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort); - values["USER-AGENT"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion); - values["MAN"] = "\"ssdp:discover\""; - - // Search target - values["ST"] = "ssdp:all"; - - // Seconds to delay response - values["MX"] = "3"; - - var message = BuildMessage(header, values); - - return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken); - } - - private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress IPAddress) - { - if (!message.IsSuccessStatusCode) - { - return; - } - - var location = GetFirstHeaderUriValue("Location", message); - if (location is not null) - { - var device = new DiscoveredSsdpDevice() - { - DescriptionLocation = location, - Usn = GetFirstHeaderStringValue("USN", message), - NotificationType = GetFirstHeaderStringValue("ST", message), - CacheLifetime = CacheAgeFromHeader(message.Headers.CacheControl), - AsAt = DateTimeOffset.Now, - ResponseHeaders = message.Headers - }; - - AddOrUpdateDiscoveredDevice(device, IPAddress); - } - } - - private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress IPAddress) - { - if (string.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0) - { - return; - } - - var notificationType = GetFirstHeaderStringValue("NTS", message); - if (string.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0) - { - ProcessAliveNotification(message, IPAddress); - } - else if (string.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0) - { - ProcessByeByeNotification(message); - } - } - - private void ProcessAliveNotification(HttpRequestMessage message, IPAddress IPAddress) - { - var location = GetFirstHeaderUriValue("Location", message); - if (location is not null) - { - var device = new DiscoveredSsdpDevice() - { - DescriptionLocation = location, - Usn = GetFirstHeaderStringValue("USN", message), - NotificationType = GetFirstHeaderStringValue("NT", message), - CacheLifetime = CacheAgeFromHeader(message.Headers.CacheControl), - AsAt = DateTimeOffset.Now, - ResponseHeaders = message.Headers - }; - - AddOrUpdateDiscoveredDevice(device, IPAddress); - } - } - - private void ProcessByeByeNotification(HttpRequestMessage message) - { - var notficationType = GetFirstHeaderStringValue("NT", message); - if (!string.IsNullOrEmpty(notficationType)) - { - var usn = GetFirstHeaderStringValue("USN", message); - - if (!DeviceDied(usn, false)) - { - var deadDevice = new DiscoveredSsdpDevice() - { - AsAt = DateTime.UtcNow, - CacheLifetime = TimeSpan.Zero, - DescriptionLocation = null, - NotificationType = GetFirstHeaderStringValue("NT", message), - Usn = usn, - ResponseHeaders = message.Headers - }; - - if (NotificationTypeMatchesFilter(deadDevice)) - { - OnDeviceUnavailable(deadDevice, false); - } - } - } - } - - private string GetFirstHeaderStringValue(string headerName, HttpResponseMessage message) - { - string retVal = null; - if (message.Headers.Contains(headerName)) - { - message.Headers.TryGetValues(headerName, out var values); - if (values is not null) - { - retVal = values.FirstOrDefault(); - } - } - - return retVal; - } - - private string GetFirstHeaderStringValue(string headerName, HttpRequestMessage message) - { - string retVal = null; - if (message.Headers.Contains(headerName)) - { - message.Headers.TryGetValues(headerName, out var values); - if (values is not null) - { - retVal = values.FirstOrDefault(); - } - } - - return retVal; - } - - private Uri GetFirstHeaderUriValue(string headerName, HttpRequestMessage request) - { - string value = null; - if (request.Headers.Contains(headerName)) - { - request.Headers.TryGetValues(headerName, out var values); - if (values is not null) - { - value = values.FirstOrDefault(); - } - } - - Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var retVal); - return retVal; - } - - private Uri GetFirstHeaderUriValue(string headerName, HttpResponseMessage response) - { - string value = null; - if (response.Headers.Contains(headerName)) - { - response.Headers.TryGetValues(headerName, out var values); - if (values is not null) - { - value = values.FirstOrDefault(); - } - } - - Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var retVal); - return retVal; - } - - private TimeSpan CacheAgeFromHeader(System.Net.Http.Headers.CacheControlHeaderValue headerValue) - { - if (headerValue is null) - { - return TimeSpan.Zero; - } - - return headerValue.MaxAge ?? headerValue.SharedMaxAge ?? TimeSpan.Zero; - } - - private void RemoveExpiredDevicesFromCache() - { - DiscoveredSsdpDevice[] expiredDevices = null; - lock (_Devices) - { - expiredDevices = (from device in _Devices where device.IsExpired() select device).ToArray(); - - foreach (var device in expiredDevices) - { - if (IsDisposed) - { - return; - } - - _Devices.Remove(device); - } - } - - // Don't do this inside lock because DeviceDied raises an event - // which means public code may execute during lock and cause - // problems. - foreach (var expiredUsn in (from expiredDevice in expiredDevices select expiredDevice.Usn).Distinct()) - { - if (IsDisposed) - { - return; - } - - DeviceDied(expiredUsn, true); - } - } - - private bool DeviceDied(string deviceUsn, bool expired) - { - List existingDevices = null; - lock (_Devices) - { - existingDevices = FindExistingDeviceNotifications(_Devices, deviceUsn); - foreach (var existingDevice in existingDevices) - { - if (IsDisposed) - { - return true; - } - - _Devices.Remove(existingDevice); - } - } - - if (existingDevices is not null && existingDevices.Count > 0) - { - foreach (var removedDevice in existingDevices) - { - if (NotificationTypeMatchesFilter(removedDevice)) - { - OnDeviceUnavailable(removedDevice, expired); - } - } - - return true; - } - - return false; - } - - private TimeSpan SearchTimeToMXValue(TimeSpan searchWaitTime) - { - if (searchWaitTime.TotalSeconds < 2 || searchWaitTime == TimeSpan.Zero) - { - return OneSecond; - } - - return searchWaitTime.Subtract(OneSecond); - } - - private DiscoveredSsdpDevice FindExistingDeviceNotification(IEnumerable devices, string notificationType, string usn) - { - foreach (var d in devices) - { - if (d.NotificationType == notificationType && d.Usn == usn) - { - return d; - } - } - - return null; - } - - private List FindExistingDeviceNotifications(IList devices, string usn) - { - var list = new List(); - - foreach (var d in devices) - { - if (d.Usn == usn) - { - list.Add(d); - } - } - - return list; - } - - private void CommsServer_ResponseReceived(object sender, ResponseReceivedEventArgs e) - { - ProcessSearchResponseMessage(e.Message, e.LocalIPAddress); - } - - private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e) - { - ProcessNotificationMessage(e.Message, e.ReceivedFrom.Address); - } - } -} diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs deleted file mode 100644 index 0ac9cc9a13..0000000000 --- a/RSSDP/SsdpDevicePublisher.cs +++ /dev/null @@ -1,623 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Rssdp.Infrastructure -{ - /// - /// Provides the platform independent logic for publishing SSDP devices (notifications and search responses). - /// - public class SsdpDevicePublisher : DisposableManagedObjectBase, ISsdpDevicePublisher - { - private ISsdpCommunicationsServer _CommsServer; - private string _OSName; - private string _OSVersion; - private bool _sendOnlyMatchedHost; - - private bool _SupportPnpRootDevice; - - private IList _Devices; - private IReadOnlyList _ReadOnlyDevices; - - private Timer _RebroadcastAliveNotificationsTimer; - - private IDictionary _RecentSearchRequests; - - private Random _Random; - - /// - /// Default constructor. - /// - public SsdpDevicePublisher( - ISsdpCommunicationsServer communicationsServer, - string osName, - string osVersion, - bool sendOnlyMatchedHost) - { - ArgumentNullException.ThrowIfNull(communicationsServer); - ArgumentNullException.ThrowIfNullOrEmpty(osName); - ArgumentNullException.ThrowIfNullOrEmpty(osVersion); - - _SupportPnpRootDevice = true; - _Devices = new List(); - _ReadOnlyDevices = new ReadOnlyCollection(_Devices); - _RecentSearchRequests = new Dictionary(StringComparer.OrdinalIgnoreCase); - _Random = new Random(); - - _CommsServer = communicationsServer; - _CommsServer.RequestReceived += CommsServer_RequestReceived; - _OSName = osName; - _OSVersion = osVersion; - _sendOnlyMatchedHost = sendOnlyMatchedHost; - - _CommsServer.BeginListeningForMulticast(); - - // Send alive notification once on creation - SendAllAliveNotifications(null); - } - - public void StartSendingAliveNotifications(TimeSpan interval) - { - _RebroadcastAliveNotificationsTimer = new Timer(SendAllAliveNotifications, null, TimeSpan.FromSeconds(5), interval); - } - - /// - /// Adds a device (and it's children) to the list of devices being published by this server, making them discoverable to SSDP clients. - /// - /// - /// Adding a device causes "alive" notification messages to be sent immediately, or very soon after. Ensure your device/description service is running before adding the device object here. - /// Devices added here with a non-zero cache life time will also have notifications broadcast periodically. - /// This method ignores duplicate device adds (if the same device instance is added multiple times, the second and subsequent add calls do nothing). - /// - /// The instance to add. - /// Thrown if the argument is null. - /// Thrown if the contains property values that are not acceptable to the UPnP 1.0 specification. - public void AddDevice(SsdpRootDevice device) - { - if (device is null) - { - throw new ArgumentNullException(nameof(device)); - } - - ThrowIfDisposed(); - - bool wasAdded = false; - lock (_Devices) - { - if (!_Devices.Contains(device)) - { - _Devices.Add(device); - wasAdded = true; - } - } - - if (wasAdded) - { - WriteTrace("Device Added", device); - - SendAliveNotifications(device, true, CancellationToken.None); - } - } - - /// - /// Removes a device (and it's children) from the list of devices being published by this server, making them undiscoverable. - /// - /// - /// Removing a device causes "byebye" notification messages to be sent immediately, advising clients of the device/service becoming unavailable. We recommend removing the device from the published list before shutting down the actual device/service, if possible. - /// This method does nothing if the device was not found in the collection. - /// - /// The instance to add. - /// Thrown if the argument is null. - public async Task RemoveDevice(SsdpRootDevice device) - { - if (device is null) - { - throw new ArgumentNullException(nameof(device)); - } - - bool wasRemoved = false; - lock (_Devices) - { - if (_Devices.Contains(device)) - { - _Devices.Remove(device); - wasRemoved = true; - } - } - - if (wasRemoved) - { - WriteTrace("Device Removed", device); - - await SendByeByeNotifications(device, true, CancellationToken.None).ConfigureAwait(false); - } - } - - /// - /// Returns a read only list of devices being published by this instance. - /// - public IEnumerable Devices - { - get - { - return _ReadOnlyDevices; - } - } - - /// - /// If true (default) treats root devices as both upnp:rootdevice and pnp:rootdevice types. - /// - /// - /// Enabling this option will cause devices to show up in Microsoft Windows Explorer's network screens (if discovery is enabled etc.). Windows Explorer appears to search only for pnp:rootdeivce and not upnp:rootdevice. - /// If false, the system will only use upnp:rootdevice for notification broadcasts and and search responses, which is correct according to the UPnP/SSDP spec. - /// - public bool SupportPnpRootDevice - { - get { return _SupportPnpRootDevice; } - - set - { - _SupportPnpRootDevice = value; - } - } - - /// - /// Stops listening for requests, stops sending periodic broadcasts, disposes all internal resources. - /// - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - DisposeRebroadcastTimer(); - - var commsServer = _CommsServer; - if (commsServer is not null) - { - commsServer.RequestReceived -= this.CommsServer_RequestReceived; - } - - var tasks = Devices.ToList().Select(RemoveDevice).ToArray(); - Task.WaitAll(tasks); - - _CommsServer = null; - if (commsServer is not null) - { - if (!commsServer.IsShared) - { - commsServer.Dispose(); - } - } - - _RecentSearchRequests = null; - } - } - - private void ProcessSearchRequest( - string mx, - string searchTarget, - IPEndPoint remoteEndPoint, - IPAddress receivedOnlocalIPAddress, - CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(searchTarget)) - { - WriteTrace(string.Format(CultureInfo.InvariantCulture, "Invalid search request received From {0}, Target is null/empty.", remoteEndPoint.ToString())); - return; - } - - // WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget)); - - if (IsDuplicateSearchRequest(searchTarget, remoteEndPoint)) - { - // WriteTrace("Search Request is Duplicate, ignoring."); - return; - } - - // Wait on random interval up to MX, as per SSDP spec. - // Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120. - // Using 16 as minimum as that's often the minimum system clock frequency anyway. - if (String.IsNullOrEmpty(mx)) - { - // Windows Explorer is poorly behaved and doesn't supply an MX header value. - // if (this.SupportPnpRootDevice) - mx = "1"; - // else - // return; - } - - if (!int.TryParse(mx, out var maxWaitInterval) || maxWaitInterval <= 0) - { - return; - } - - if (maxWaitInterval > 120) - { - maxWaitInterval = _Random.Next(0, 120); - } - - // Do not block synchronously as that may tie up a threadpool thread for several seconds. - Task.Delay(_Random.Next(16, maxWaitInterval * 1000), cancellationToken).ContinueWith((parentTask) => - { - // Copying devices to local array here to avoid threading issues/enumerator exceptions. - IEnumerable devices = null; - lock (_Devices) - { - if (string.Compare(SsdpConstants.SsdpDiscoverAllSTHeader, searchTarget, StringComparison.OrdinalIgnoreCase) == 0) - { - devices = GetAllDevicesAsFlatEnumerable().ToArray(); - } - else if (string.Compare(SsdpConstants.UpnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0 || (SupportPnpRootDevice && String.Compare(SsdpConstants.PnpDeviceTypeRootDevice, searchTarget, StringComparison.OrdinalIgnoreCase) == 0)) - { - devices = _Devices.ToArray(); - } - else if (searchTarget.Trim().StartsWith("uuid:", StringComparison.OrdinalIgnoreCase)) - { - devices = GetAllDevicesAsFlatEnumerable().Where(d => string.Compare(d.Uuid, searchTarget.Substring(5), StringComparison.OrdinalIgnoreCase) == 0).ToArray(); - } - else if (searchTarget.StartsWith("urn:", StringComparison.OrdinalIgnoreCase)) - { - devices = GetAllDevicesAsFlatEnumerable().Where(d => string.Compare(d.FullDeviceType, searchTarget, StringComparison.OrdinalIgnoreCase) == 0).ToArray(); - } - } - - if (devices is not null) - { - // WriteTrace(String.Format("Sending {0} search responses", deviceList.Count)); - - foreach (var device in devices) - { - var root = device.ToRootDevice(); - - if (!_sendOnlyMatchedHost || root.Address.Equals(receivedOnlocalIPAddress)) - { - SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIPAddress, cancellationToken); - } - } - } - }, cancellationToken); - } - - private IEnumerable GetAllDevicesAsFlatEnumerable() - { - return _Devices.Union(_Devices.SelectManyRecursive((d) => d.Devices)); - } - - private void SendDeviceSearchResponses( - SsdpDevice device, - IPEndPoint endPoint, - IPAddress receivedOnlocalIPAddress, - CancellationToken cancellationToken) - { - bool isRootDevice = (device as SsdpRootDevice) is not null; - if (isRootDevice) - { - SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIPAddress, cancellationToken); - if (SupportPnpRootDevice) - { - SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIPAddress, cancellationToken); - } - } - - SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIPAddress, cancellationToken); - - SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint, receivedOnlocalIPAddress, cancellationToken); - } - - private string GetUsn(string udn, string fullDeviceType) - { - return string.Format(CultureInfo.InvariantCulture, "{0}::{1}", udn, fullDeviceType); - } - - private async void SendSearchResponse( - string searchTarget, - SsdpDevice device, - string uniqueServiceName, - IPEndPoint endPoint, - IPAddress receivedOnlocalIPAddress, - CancellationToken cancellationToken) - { - const string header = "HTTP/1.1 200 OK"; - - var rootDevice = device.ToRootDevice(); - var values = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - ["EXT"] = "", - ["DATE"] = DateTime.UtcNow.ToString("r"), - ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort), - ["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds, - ["ST"] = searchTarget, - ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion), - ["USN"] = uniqueServiceName, - ["LOCATION"] = rootDevice.Location.ToString() - }; - - var message = BuildMessage(header, values); - - try - { - await _CommsServer.SendMessage( - Encoding.UTF8.GetBytes(message), - endPoint, - receivedOnlocalIPAddress, - cancellationToken) - .ConfigureAwait(false); - } - catch (Exception) - { - } - - // WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device); - } - - private bool IsDuplicateSearchRequest(string searchTarget, IPEndPoint endPoint) - { - var isDuplicateRequest = false; - - var newRequest = new SearchRequest() { EndPoint = endPoint, SearchTarget = searchTarget, Received = DateTime.UtcNow }; - lock (_RecentSearchRequests) - { - if (_RecentSearchRequests.ContainsKey(newRequest.Key)) - { - var lastRequest = _RecentSearchRequests[newRequest.Key]; - if (lastRequest.IsOld()) - { - _RecentSearchRequests[newRequest.Key] = newRequest; - } - else - { - isDuplicateRequest = true; - } - } - else - { - _RecentSearchRequests.Add(newRequest.Key, newRequest); - if (_RecentSearchRequests.Count > 10) - { - CleanUpRecentSearchRequestsAsync(); - } - } - } - - return isDuplicateRequest; - } - - private void CleanUpRecentSearchRequestsAsync() - { - lock (_RecentSearchRequests) - { - foreach (var requestKey in (from r in _RecentSearchRequests where r.Value.IsOld() select r.Key).ToArray()) - { - _RecentSearchRequests.Remove(requestKey); - } - } - } - - private void SendAllAliveNotifications(object state) - { - try - { - if (IsDisposed) - { - return; - } - - // WriteTrace("Begin Sending Alive Notifications For All Devices"); - - SsdpRootDevice[] devices; - lock (_Devices) - { - devices = _Devices.ToArray(); - } - - foreach (var device in devices) - { - if (IsDisposed) - { - return; - } - - SendAliveNotifications(device, true, CancellationToken.None); - } - - // WriteTrace("Completed Sending Alive Notifications For All Devices"); - } - catch (ObjectDisposedException ex) - { - WriteTrace("Publisher stopped, exception " + ex.Message); - Dispose(); - } - } - - private void SendAliveNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken) - { - if (isRoot) - { - SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken); - if (SupportPnpRootDevice) - { - SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), cancellationToken); - } - } - - SendAliveNotification(device, device.Udn, device.Udn, cancellationToken); - SendAliveNotification(device, device.FullDeviceType, GetUsn(device.Udn, device.FullDeviceType), cancellationToken); - - foreach (var childDevice in device.Devices) - { - SendAliveNotifications(childDevice, false, cancellationToken); - } - } - - private void SendAliveNotification(SsdpDevice device, string notificationType, string uniqueServiceName, CancellationToken cancellationToken) - { - var rootDevice = device.ToRootDevice(); - - const string header = "NOTIFY * HTTP/1.1"; - - var values = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - // If needed later for non-server devices, these headers will need to be dynamic - ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort), - ["DATE"] = DateTime.UtcNow.ToString("r"), - ["CACHE-CONTROL"] = "max-age = " + rootDevice.CacheLifetime.TotalSeconds, - ["LOCATION"] = rootDevice.Location.ToString(), - ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion), - ["NTS"] = "ssdp:alive", - ["NT"] = notificationType, - ["USN"] = uniqueServiceName - }; - - var message = BuildMessage(header, values); - - _CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken); - - // WriteTrace(String.Format("Sent alive notification"), device); - } - - private Task SendByeByeNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken) - { - var tasks = new List(); - if (isRoot) - { - tasks.Add(SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken)); - if (SupportPnpRootDevice) - { - tasks.Add(SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice"), cancellationToken)); - } - } - - tasks.Add(SendByeByeNotification(device, device.Udn, device.Udn, cancellationToken)); - tasks.Add(SendByeByeNotification(device, String.Format(CultureInfo.InvariantCulture, "urn:{0}", device.FullDeviceType), GetUsn(device.Udn, device.FullDeviceType), cancellationToken)); - - foreach (var childDevice in device.Devices) - { - tasks.Add(SendByeByeNotifications(childDevice, false, cancellationToken)); - } - - return Task.WhenAll(tasks); - } - - private Task SendByeByeNotification(SsdpDevice device, string notificationType, string uniqueServiceName, CancellationToken cancellationToken) - { - const string header = "NOTIFY * HTTP/1.1"; - - var values = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - // If needed later for non-server devices, these headers will need to be dynamic - ["HOST"] = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", SsdpConstants.MulticastLocalAdminAddress, SsdpConstants.MulticastPort), - ["DATE"] = DateTime.UtcNow.ToString("r"), - ["SERVER"] = string.Format(CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 RSSDP/{2}", _OSName, _OSVersion, SsdpConstants.ServerVersion), - ["NTS"] = "ssdp:byebye", - ["NT"] = notificationType, - ["USN"] = uniqueServiceName - }; - - var message = BuildMessage(header, values); - - var sendCount = IsDisposed ? 1 : 3; - WriteTrace(string.Format(CultureInfo.InvariantCulture, "Sent byebye notification"), device); - return _CommsServer.SendMulticastMessage(message, sendCount, _sendOnlyMatchedHost ? device.ToRootDevice().Address : null, cancellationToken); - } - - private void DisposeRebroadcastTimer() - { - var timer = _RebroadcastAliveNotificationsTimer; - _RebroadcastAliveNotificationsTimer = null; - timer?.Dispose(); - } - - private TimeSpan GetMinimumNonZeroCacheLifetime() - { - var nonzeroCacheLifetimesQuery = ( - from device - in _Devices - where device.CacheLifetime != TimeSpan.Zero - select device.CacheLifetime).ToList(); - - if (nonzeroCacheLifetimesQuery.Any()) - { - return nonzeroCacheLifetimesQuery.Min(); - } - - return TimeSpan.Zero; - } - - private string GetFirstHeaderValue(System.Net.Http.Headers.HttpRequestHeaders httpRequestHeaders, string headerName) - { - string retVal = null; - if (httpRequestHeaders.TryGetValues(headerName, out var values) && values is not null) - { - retVal = values.FirstOrDefault(); - } - - return retVal; - } - - public Action LogFunction { get; set; } - - private void WriteTrace(string text) - { - LogFunction?.Invoke(text); - // System.Diagnostics.Debug.WriteLine(text, "SSDP Publisher"); - } - - private void WriteTrace(string text, SsdpDevice device) - { - var rootDevice = device as SsdpRootDevice; - if (rootDevice is not null) - { - WriteTrace(text + " " + device.DeviceType + " - " + device.Uuid + " - " + rootDevice.Location); - } - else - { - WriteTrace(text + " " + device.DeviceType + " - " + device.Uuid); - } - } - - private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e) - { - if (this.IsDisposed) - { - return; - } - - if (string.Equals(e.Message.Method.Method, SsdpConstants.MSearchMethod, StringComparison.OrdinalIgnoreCase)) - { - // According to SSDP/UPnP spec, ignore message if missing these headers. - // Edit: But some devices do it anyway - // if (!e.Message.Headers.Contains("MX")) - // WriteTrace("Ignoring search request - missing MX header."); - // else if (!e.Message.Headers.Contains("MAN")) - // WriteTrace("Ignoring search request - missing MAN header."); - // else - ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIPAddress, CancellationToken.None); - } - } - - private class SearchRequest - { - public IPEndPoint EndPoint { get; set; } - - public DateTime Received { get; set; } - - public string SearchTarget { get; set; } - - public string Key - { - get { return this.SearchTarget + ":" + this.EndPoint; } - } - - public bool IsOld() - { - return DateTime.UtcNow.Subtract(this.Received).TotalMilliseconds > 500; - } - } - } -} diff --git a/RSSDP/SsdpEmbeddedDevice.cs b/RSSDP/SsdpEmbeddedDevice.cs deleted file mode 100644 index f1a5981118..0000000000 --- a/RSSDP/SsdpEmbeddedDevice.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Rssdp -{ - /// - /// Represents a device that is a descendant of a instance. - /// - public class SsdpEmbeddedDevice : SsdpDevice - { - private SsdpRootDevice _RootDevice; - - /// - /// Default constructor. - /// - public SsdpEmbeddedDevice() - { - } - - /// - /// Returns the that is this device's first ancestor. If this device is itself an , then returns a reference to itself. - /// - public SsdpRootDevice RootDevice - { - get - { - return _RootDevice; - } - - internal set - { - _RootDevice = value; - lock (this.Devices) - { - foreach (var embeddedDevice in this.Devices) - { - ((SsdpEmbeddedDevice)embeddedDevice).RootDevice = _RootDevice; - } - } - } - } - } -} diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs deleted file mode 100644 index 5ecb1f86f6..0000000000 --- a/RSSDP/SsdpRootDevice.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Net; - -namespace Rssdp -{ - /// - /// Represents a 'root' device, a device that has no parent. Used for publishing devices and for the root device in a tree of discovered devices. - /// - /// - /// Child (embedded) devices are represented by the in the property. - /// Root devices contain some information that applies to the whole device tree and is therefore not present on child devices, such as and . - /// - public class SsdpRootDevice : SsdpDevice - { - private Uri _UrlBase; - - /// - /// Default constructor. - /// - public SsdpRootDevice() : base() - { - } - - /// - /// Specifies how long clients can cache this device's details for. Optional but defaults to which means no-caching. Recommended value is half an hour. - /// - /// - /// Specify to indicate no caching allowed. - /// Also used to specify how often to rebroadcast alive notifications. - /// The UPnP/SSDP specifications indicate this should not be less than 1800 seconds (half an hour), but this is not enforced by this library. - /// - public TimeSpan CacheLifetime - { - get; set; - } - - /// - /// Gets or sets the URL used to retrieve the description document for this device/tree. Required. - /// - public Uri Location { get; set; } - - /// - /// Gets or sets the Address used to check if the received message from same interface with this device/tree. Required. - /// - public IPAddress Address { get; set; } - - /// - /// Gets or sets the prefix length used to check if the received message from same interface with this device/tree. Required. - /// - public byte PrefixLength { get; set; } - - /// - /// The base URL to use for all relative url's provided in other properties (and those of child devices). Optional. - /// - /// - /// Defines the base URL. Used to construct fully-qualified URLs. All relative URLs that appear elsewhere in the description are combined with this base URL. If URLBase is empty or not given, the base URL is the URL from which the device description was retrieved (which is the preferred implementation; use of URLBase is no longer recommended). Specified by UPnP vendor. Single URL. - /// - public Uri UrlBase - { - get - { - return _UrlBase ?? this.Location; - } - - set - { - _UrlBase = value; - } - } - } -} diff --git a/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs deleted file mode 100644 index 78a956f5f8..0000000000 --- a/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -using Emby.Dlna; -using Emby.Dlna.PlayTo; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using Moq; -using Xunit; - -namespace Jellyfin.Dlna.Tests -{ - public class DlnaManagerTests - { - private DlnaManager GetManager() - { - var xmlSerializer = new Mock(); - var fileSystem = new Mock(); - var appPaths = new Mock(); - var loggerFactory = new Mock(); - var appHost = new Mock(); - - return new DlnaManager(xmlSerializer.Object, fileSystem.Object, appPaths.Object, loggerFactory.Object, appHost.Object); - } - - [Fact] - public void IsMatch_GivenMatchingName_ReturnsTrue() - { - var device = new DeviceInfo() - { - Name = "My Device", - Manufacturer = "LG Electronics", - ManufacturerUrl = "http://www.lge.com", - ModelDescription = "LG WebOSTV DMRplus", - ModelName = "LG TV", - ModelNumber = "1.0", - }; - - var profile = new DeviceProfile() - { - Name = "Test Profile", - FriendlyName = "My Device", - Manufacturer = "LG Electronics", - ManufacturerUrl = "http://www.lge.com", - ModelDescription = "LG WebOSTV DMRplus", - ModelName = "LG TV", - ModelNumber = "1.0", - Identification = new() - { - FriendlyName = "My Device", - Manufacturer = "LG Electronics", - ManufacturerUrl = "http://www.lge.com", - ModelDescription = "LG WebOSTV DMRplus", - ModelName = "LG TV", - ModelNumber = "1.0", - } - }; - - var profile2 = new DeviceProfile() - { - Name = "Test Profile", - FriendlyName = "My Device", - Identification = new DeviceIdentification() - { - FriendlyName = "My Device", - } - }; - - var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification); - var deviceMatch2 = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); - - Assert.True(deviceMatch); - Assert.True(deviceMatch2); - } - - [Fact] - public void IsMatch_GivenNamesAndManufacturersDoNotMatch_ReturnsFalse() - { - var device = new DeviceInfo() - { - Name = "My Device", - Manufacturer = "JVC" - }; - - var profile = new DeviceProfile() - { - Name = "Test Profile", - FriendlyName = "My Device", - Manufacturer = "LG Electronics", - ManufacturerUrl = "http://www.lge.com", - ModelDescription = "LG WebOSTV DMRplus", - ModelName = "LG TV", - ModelNumber = "1.0", - Identification = new() - { - FriendlyName = "My Device", - Manufacturer = "LG Electronics", - ManufacturerUrl = "http://www.lge.com", - ModelDescription = "LG WebOSTV DMRplus", - ModelName = "LG TV", - ModelNumber = "1.0", - } - }; - - var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); - - Assert.False(deviceMatch); - } - - [Fact] - public void IsMatch_GivenNamesAndRegExMatch_ReturnsTrue() - { - var device = new DeviceInfo() - { - Name = "My Device" - }; - - var profile = new DeviceProfile() - { - Name = "Test Profile", - FriendlyName = "My .*", - Identification = new() - }; - - var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification); - - Assert.True(deviceMatch); - } - } -} diff --git a/tests/Jellyfin.Dlna.Tests/GetUuidTests.cs b/tests/Jellyfin.Dlna.Tests/GetUuidTests.cs deleted file mode 100644 index 7655e3f7ce..0000000000 --- a/tests/Jellyfin.Dlna.Tests/GetUuidTests.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Emby.Dlna.PlayTo; -using Xunit; - -namespace Jellyfin.Dlna.Tests -{ - public static class GetUuidTests - { - [Theory] - [InlineData("uuid:fc4ec57e-b051-11db-88f8-0060085db3f6::urn:schemas-upnp-org:device:WANDevice:1", "fc4ec57e-b051-11db-88f8-0060085db3f6")] - [InlineData("uuid:IGD{8c80f73f-4ba0-45fa-835d-042505d052be}000000000000", "8c80f73f-4ba0-45fa-835d-042505d052be")] - [InlineData("uuid:IGD{8c80f73f-4ba0-45fa-835d-042505d052be}000000000000::urn:schemas-upnp-org:device:InternetGatewayDevice:1", "8c80f73f-4ba0-45fa-835d-042505d052be")] - [InlineData("uuid:00000000-0000-0000-0000-000000000000::upnp:rootdevice", "00000000-0000-0000-0000-000000000000")] - [InlineData("uuid:fc4ec57e-b051-11db-88f8-0060085db3f6", "fc4ec57e-b051-11db-88f8-0060085db3f6")] - public static void GetUuid_Valid_Success(string usn, string uuid) - => Assert.Equal(uuid, PlayToManager.GetUuid(usn)); - } -} diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj deleted file mode 100644 index 69677ce424..0000000000 --- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - diff --git a/tests/Jellyfin.Dlna.Tests/Server/DescriptionXmlBuilderTests.cs b/tests/Jellyfin.Dlna.Tests/Server/DescriptionXmlBuilderTests.cs deleted file mode 100644 index c9018fe2f4..0000000000 --- a/tests/Jellyfin.Dlna.Tests/Server/DescriptionXmlBuilderTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Emby.Dlna.Server; -using MediaBrowser.Model.Dlna; -using Xunit; - -namespace Jellyfin.Dlna.Server.Tests; - -public class DescriptionXmlBuilderTests -{ - [Fact] - public void GetFriendlyName_EmptyProfile_ReturnsServerName() - { - const string ServerName = "Test Server Name"; - var builder = new DescriptionXmlBuilder(new DeviceProfile(), "serverUdn", "localhost", ServerName, string.Empty); - Assert.Equal(ServerName, builder.GetFriendlyName()); - } - - [Fact] - public void GetFriendlyName_FriendlyName_ReturnsFriendlyName() - { - const string FriendlyName = "Friendly Neighborhood Test Server"; - var builder = new DescriptionXmlBuilder( - new DeviceProfile() - { - FriendlyName = FriendlyName - }, - "serverUdn", - "localhost", - "Test Server Name", - string.Empty); - Assert.Equal(FriendlyName, builder.GetFriendlyName()); - } - - [Fact] - public void GetFriendlyName_FriendlyNameInterpolation_ReturnsFriendlyName() - { - var builder = new DescriptionXmlBuilder( - new DeviceProfile() - { - FriendlyName = "Friendly Neighborhood ${HostName}" - }, - "serverUdn", - "localhost", - "Test Server Name", - string.Empty); - Assert.Equal("Friendly Neighborhood TestServerName", builder.GetFriendlyName()); - } -} diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs deleted file mode 100644 index e5d5e785cb..0000000000 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DlnaControllerTests.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Http.Json; -using System.Net.Mime; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; -using Jellyfin.Extensions.Json; -using MediaBrowser.Model.Dlna; -using Xunit; -using Xunit.Priority; - -namespace Jellyfin.Server.Integration.Tests.Controllers -{ - [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] - public sealed class DlnaControllerTests : IClassFixture - { - private const string NonExistentProfile = "1322f35b8f2c434dad3cc07c9b97dbd1"; - private readonly JellyfinApplicationFactory _factory; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - private static string? _accessToken; - private static string? _newDeviceProfileId; - - public DlnaControllerTests(JellyfinApplicationFactory factory) - { - _factory = factory; - } - - [Fact] - [Priority(0)] - public async Task GetProfile_DoesNotExist_NotFound() - { - var client = _factory.CreateClient(); - client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); - - using var response = await client.GetAsync("/Dlna/Profiles/" + NonExistentProfile); - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - [Priority(0)] - public async Task DeleteProfile_DoesNotExist_NotFound() - { - var client = _factory.CreateClient(); - client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); - - using var response = await client.DeleteAsync("/Dlna/Profiles/" + NonExistentProfile); - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - [Priority(0)] - public async Task UpdateProfile_DoesNotExist_NotFound() - { - var client = _factory.CreateClient(); - client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); - - var deviceProfile = new DeviceProfile() - { - Name = "ThisProfileDoesNotExist" - }; - - using var response = await client.PostAsJsonAsync("/Dlna/Profiles/" + NonExistentProfile, deviceProfile, _jsonOptions); - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - [Priority(1)] - public async Task CreateProfile_Valid_NoContent() - { - var client = _factory.CreateClient(); - client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); - - var deviceProfile = new DeviceProfile() - { - Name = "ThisProfileIsNew" - }; - - using var response = await client.PostAsJsonAsync("/Dlna/Profiles", deviceProfile, _jsonOptions); - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - } - - [Fact] - [Priority(2)] - public async Task GetProfileInfos_Valid_ContainsThisProfileIsNew() - { - var client = _factory.CreateClient(); - client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); - - using var response = await client.GetAsync("/Dlna/ProfileInfos"); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); - Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); - - var profiles = await response.Content.ReadFromJsonAsync(_jsonOptions); - - var newProfile = profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsNew", StringComparison.Ordinal)); - Assert.NotNull(newProfile); - _newDeviceProfileId = newProfile!.Id; - } - - [Fact] - [Priority(3)] - public async Task UpdateProfile_Valid_NoContent() - { - var client = _factory.CreateClient(); - client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); - - var updatedProfile = new DeviceProfile() - { - Name = "ThisProfileIsUpdated", - Id = _newDeviceProfileId - }; - - using var postResponse = await client.PostAsJsonAsync("/Dlna/Profiles/" + _newDeviceProfileId, updatedProfile, _jsonOptions); - Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode); - - // Verify that the profile got updated - using var response = await client.GetAsync("/Dlna/ProfileInfos"); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); - Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); - - var profiles = await response.Content.ReadFromJsonAsync(_jsonOptions); - - Assert.Null(profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsNew", StringComparison.Ordinal))); - var newProfile = profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsUpdated", StringComparison.Ordinal)); - Assert.NotNull(newProfile); - _newDeviceProfileId = newProfile!.Id; - } - - [Fact] - [Priority(5)] - public async Task DeleteProfile_Valid_NoContent() - { - var client = _factory.CreateClient(); - client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client)); - - using var deleteResponse = await client.DeleteAsync("/Dlna/Profiles/" + _newDeviceProfileId); - Assert.Equal(HttpStatusCode.NoContent, deleteResponse.StatusCode); - - // Verify that the profile got deleted - using var response = await client.GetAsync("/Dlna/ProfileInfos"); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType); - Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet); - - var profiles = await response.Content.ReadFromJsonAsync(_jsonOptions); - - Assert.Null(profiles?.FirstOrDefault(x => string.Equals(x.Name, "ThisProfileIsUpdated", StringComparison.Ordinal))); - } - } -}