diff options
| author | ferferga <ferferga.fer@gmail.com> | 2020-10-19 17:28:07 +0200 |
|---|---|---|
| committer | ferferga <ferferga.fer@gmail.com> | 2020-10-19 17:28:07 +0200 |
| commit | 9fd01fade6ac971ba72a2e7dd54e2295f23839bc (patch) | |
| tree | 20a5ff4a1621e765fc93c0e53b149edb61ff3654 /Emby.Dlna | |
| parent | ba03ed65fe64b724b3e8b5b94b9cbe1075c61da2 (diff) | |
| parent | 49ac4c4044b1777dc3a25544aead7b0b15b953e8 (diff) | |
Remove "download images in advance" option
Diffstat (limited to 'Emby.Dlna')
83 files changed, 1597 insertions, 1899 deletions
diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs deleted file mode 100644 index 7fba2184a7..0000000000 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ /dev/null @@ -1,383 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Emby.Dlna.Main; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; - -namespace Emby.Dlna.Api -{ - [Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")] - [Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")] - public class GetDescriptionXml - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/contentdirectory/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")] - [Route("/Dlna/{UuId}/contentdirectory/contentdirectory", "GET", Summary = "Gets dlna content directory xml")] - public class GetContentDirectory - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/connectionmanager/connectionmanager.xml", "GET", Summary = "Gets dlna connection manager xml")] - [Route("/Dlna/{UuId}/connectionmanager/connectionmanager", "GET", Summary = "Gets dlna connection manager xml")] - public class GetConnnectionManager - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar.xml", "GET", Summary = "Gets dlna mediareceiverregistrar xml")] - [Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar", "GET", Summary = "Gets dlna mediareceiverregistrar xml")] - public class GetMediaReceiverRegistrar - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/contentdirectory/control", "POST", Summary = "Processes a control request")] - public class ProcessContentDirectoryControlRequest : IRequiresRequestStream - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/Dlna/{UuId}/connectionmanager/control", "POST", Summary = "Processes a control request")] - public class ProcessConnectionManagerControlRequest : IRequiresRequestStream - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/Dlna/{UuId}/mediareceiverregistrar/control", "POST", Summary = "Processes a control request")] - public class ProcessMediaReceiverRegistrarControlRequest : IRequiresRequestStream - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] - [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] - public class ProcessMediaReceiverRegistrarEventRequest - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/contentdirectory/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] - [Route("/Dlna/{UuId}/contentdirectory/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] - public class ProcessContentDirectoryEventRequest - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/connectionmanager/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] - [Route("/Dlna/{UuId}/connectionmanager/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] - public class ProcessConnectionManagerEventRequest - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/icons/{Filename}", "GET", Summary = "Gets a server icon")] - [Route("/Dlna/icons/{Filename}", "GET", Summary = "Gets a server icon")] - public class GetIcon - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UuId { get; set; } - - [ApiMember(Name = "Filename", Description = "The icon filename", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Filename { get; set; } - } - - public class DlnaServerService : IService, IRequiresRequest - { - private const string XMLContentType = "text/xml; charset=UTF-8"; - - private readonly IDlnaManager _dlnaManager; - private readonly IHttpResultFactory _resultFactory; - private readonly IServerConfigurationManager _configurationManager; - - public IRequest Request { get; set; } - - private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory; - - private IConnectionManager ConnectionManager => DlnaEntryPoint.Current.ConnectionManager; - - private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar; - - public DlnaServerService( - IDlnaManager dlnaManager, - IHttpResultFactory httpResultFactory, - IServerConfigurationManager configurationManager) - { - _dlnaManager = dlnaManager; - _resultFactory = httpResultFactory; - _configurationManager = configurationManager; - } - - private string GetHeader(string name) - { - return Request.Headers[name]; - } - - public object Get(GetDescriptionXml request) - { - var url = Request.AbsoluteUri; - var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); - var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress); - - var cacheLength = TimeSpan.FromDays(1); - var cacheKey = Request.RawUrl.GetMD5(); - var bytes = Encoding.UTF8.GetBytes(xml); - - return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes))); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetContentDirectory request) - { - var xml = ContentDirectory.GetServiceXml(); - - return _resultFactory.GetResult(Request, xml, XMLContentType); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetMediaReceiverRegistrar request) - { - var xml = MediaReceiverRegistrar.GetServiceXml(); - - return _resultFactory.GetResult(Request, xml, XMLContentType); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetConnnectionManager request) - { - var xml = ConnectionManager.GetServiceXml(); - - return _resultFactory.GetResult(Request, xml, XMLContentType); - } - - public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request) - { - var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false); - - return _resultFactory.GetResult(Request, response.Xml, XMLContentType); - } - - public async Task<object> Post(ProcessContentDirectoryControlRequest request) - { - var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false); - - return _resultFactory.GetResult(Request, response.Xml, XMLContentType); - } - - public async Task<object> Post(ProcessConnectionManagerControlRequest request) - { - var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false); - - return _resultFactory.GetResult(Request, response.Xml, XMLContentType); - } - - private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service) - { - var id = GetPathValue(2).ToString(); - - return service.ProcessControlRequestAsync(new ControlRequest - { - Headers = Request.Headers, - InputXml = requestStream, - TargetServerUuId = id, - RequestedUrl = Request.AbsoluteUri - }); - } - - // Copied from MediaBrowser.Api/BaseApiService.cs - // TODO: Remove code duplication - /// <summary> - /// Gets the path segment at the specified index. - /// </summary> - /// <param name="index">The index of the path segment.</param> - /// <returns>The path segment at the specified index.</returns> - /// <exception cref="IndexOutOfRangeException" >Path doesn't contain enough segments.</exception> - /// <exception cref="InvalidDataException" >Path doesn't start with the base url.</exception> - protected internal ReadOnlySpan<char> GetPathValue(int index) - { - static void ThrowIndexOutOfRangeException() - => throw new IndexOutOfRangeException("Path doesn't contain enough segments."); - - static void ThrowInvalidDataException() - => throw new InvalidDataException("Path doesn't start with the base url."); - - ReadOnlySpan<char> path = Request.PathInfo; - - // Remove the protocol part from the url - int pos = path.LastIndexOf("://"); - if (pos != -1) - { - path = path.Slice(pos + 3); - } - - // Remove the query string - pos = path.LastIndexOf('?'); - if (pos != -1) - { - path = path.Slice(0, pos); - } - - // Remove the domain - pos = path.IndexOf('/'); - if (pos != -1) - { - path = path.Slice(pos); - } - - // Remove base url - string baseUrl = _configurationManager.Configuration.BaseUrl; - int baseUrlLen = baseUrl.Length; - if (baseUrlLen != 0) - { - if (path.StartsWith(baseUrl, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(baseUrlLen); - } - else - { - // The path doesn't start with the base url, - // how did we get here? - ThrowInvalidDataException(); - } - } - - // Remove leading / - path = path.Slice(1); - - // Backwards compatibility - const string Emby = "emby/"; - if (path.StartsWith(Emby, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(Emby.Length); - } - - const string MediaBrowser = "mediabrowser/"; - if (path.StartsWith(MediaBrowser, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(MediaBrowser.Length); - } - - // Skip segments until we are at the right index - for (int i = 0; i < index; i++) - { - pos = path.IndexOf('/'); - if (pos == -1) - { - ThrowIndexOutOfRangeException(); - } - - path = path.Slice(pos + 1); - } - - // Remove the rest - pos = path.IndexOf('/'); - if (pos != -1) - { - path = path.Slice(0, pos); - } - - return path; - } - - public object Get(GetIcon request) - { - var contentType = "image/" + Path.GetExtension(request.Filename) - .TrimStart('.') - .ToLowerInvariant(); - - var cacheLength = TimeSpan.FromDays(365); - var cacheKey = Request.RawUrl.GetMD5(); - - return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream)); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Subscribe(ProcessContentDirectoryEventRequest request) - { - return ProcessEventRequest(ContentDirectory); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Subscribe(ProcessConnectionManagerEventRequest request) - { - return ProcessEventRequest(ConnectionManager); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request) - { - return ProcessEventRequest(MediaReceiverRegistrar); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Unsubscribe(ProcessContentDirectoryEventRequest request) - { - return ProcessEventRequest(ContentDirectory); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Unsubscribe(ProcessConnectionManagerEventRequest request) - { - return ProcessEventRequest(ConnectionManager); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request) - { - return ProcessEventRequest(MediaReceiverRegistrar); - } - - private object ProcessEventRequest(IEventManager eventManager) - { - var subscriptionId = GetHeader("SID"); - - if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase)) - { - var notificationType = GetHeader("NT"); - - var callback = GetHeader("CALLBACK"); - var timeoutString = GetHeader("TIMEOUT"); - - if (string.IsNullOrEmpty(notificationType)) - { - return GetSubscriptionResponse(eventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callback)); - } - - return GetSubscriptionResponse(eventManager.CreateEventSubscription(notificationType, timeoutString, callback)); - } - - return GetSubscriptionResponse(eventManager.CancelEventSubscription(subscriptionId)); - } - - private object GetSubscriptionResponse(EventSubscriptionResponse response) - { - return _resultFactory.GetResult(Request, response.Content, response.ContentType, response.Headers); - } - } -} diff --git a/Emby.Dlna/Api/DlnaService.cs b/Emby.Dlna/Api/DlnaService.cs deleted file mode 100644 index 5f984bb335..0000000000 --- a/Emby.Dlna/Api/DlnaService.cs +++ /dev/null @@ -1,88 +0,0 @@ -#pragma warning disable CS1591 - -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Services; - -namespace Emby.Dlna.Api -{ - [Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")] - public class GetProfileInfos : IReturn<DeviceProfileInfo[]> - { - } - - [Route("/Dlna/Profiles/{Id}", "DELETE", Summary = "Deletes a profile")] - public class DeleteProfile : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Dlna/Profiles/Default", "GET", Summary = "Gets the default profile")] - public class GetDefaultProfile : IReturn<DeviceProfile> - { - } - - [Route("/Dlna/Profiles/{Id}", "GET", Summary = "Gets a single profile")] - public class GetProfile : IReturn<DeviceProfile> - { - [ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Dlna/Profiles/{Id}", "POST", Summary = "Updates a profile")] - public class UpdateProfile : DeviceProfile, IReturnVoid - { - } - - [Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")] - public class CreateProfile : DeviceProfile, IReturnVoid - { - } - - [Authenticated(Roles = "Admin")] - public class DlnaService : IService - { - private readonly IDlnaManager _dlnaManager; - - public DlnaService(IDlnaManager dlnaManager) - { - _dlnaManager = dlnaManager; - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetProfileInfos request) - { - return _dlnaManager.GetProfileInfos().ToArray(); - } - - public object Get(GetProfile request) - { - return _dlnaManager.GetProfile(request.Id); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetDefaultProfile request) - { - return _dlnaManager.GetDefaultProfile(); - } - - public void Delete(DeleteProfile request) - { - _dlnaManager.DeleteProfile(request.Id); - } - - public void Post(UpdateProfile request) - { - _dlnaManager.UpdateProfile(request); - } - - public void Post(CreateProfile request) - { - _dlnaManager.CreateProfile(request); - } - } -} diff --git a/Emby.Dlna/Common/ServiceAction.cs b/Emby.Dlna/Common/ServiceAction.cs index db4f270633..d458d7f3f6 100644 --- a/Emby.Dlna/Common/ServiceAction.cs +++ b/Emby.Dlna/Common/ServiceAction.cs @@ -13,7 +13,7 @@ namespace Emby.Dlna.Common public string Name { get; set; } - public List<Argument> ArgumentList { get; set; } + public List<Argument> ArgumentList { get; } /// <inheritdoc /> public override string ToString() diff --git a/Emby.Dlna/Common/StateVariable.cs b/Emby.Dlna/Common/StateVariable.cs index a2c2bf5ddc..6daf7ab6b2 100644 --- a/Emby.Dlna/Common/StateVariable.cs +++ b/Emby.Dlna/Common/StateVariable.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; namespace Emby.Dlna.Common { @@ -17,7 +18,7 @@ namespace Emby.Dlna.Common public bool SendsEvents { get; set; } - public string[] AllowedValues { get; set; } + public IReadOnlyList<string> AllowedValues { get; set; } /// <inheritdoc /> public override string ToString() diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs index dba9019677..fc02e17515 100644 --- a/Emby.Dlna/ConfigurationExtension.cs +++ b/Emby.Dlna/ConfigurationExtension.cs @@ -1,7 +1,6 @@ #nullable enable #pragma warning disable CS1591 -using System.Collections.Generic; using Emby.Dlna.Configuration; using MediaBrowser.Common.Configuration; @@ -14,19 +13,4 @@ namespace Emby.Dlna return manager.GetConfiguration<DlnaOptions>("dlna"); } } - - public class DlnaConfigurationFactory : IConfigurationFactory - { - public IEnumerable<ConfigurationStore> GetConfigurations() - { - return new ConfigurationStore[] - { - new ConfigurationStore - { - Key = "dlna", - ConfigurationType = typeof (DlnaOptions) - } - }; - } - } } diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs index e32cc11bfa..f5a7eca720 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs @@ -1,30 +1,28 @@ #pragma warning disable CS1591 +using System.Net.Http; using System.Threading.Tasks; using Emby.Dlna.Service; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ConnectionManager { - public class ConnectionManager : BaseService, IConnectionManager + public class ConnectionManagerService : BaseService, IConnectionManager { private readonly IDlnaManager _dlna; - private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - public ConnectionManager( + public ConnectionManagerService( IDlnaManager dlna, IServerConfigurationManager config, - ILogger<ConnectionManager> logger, - IHttpClient httpClient) - : base(logger, httpClient) + ILogger<ConnectionManagerService> logger, + IHttpClientFactory httpClientFactory) + : base(logger, httpClientFactory) { _dlna = dlna; _config = config; - _logger = logger; } /// <inheritdoc /> @@ -39,7 +37,7 @@ namespace Emby.Dlna.ConnectionManager var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile(); - return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request); + return new ControlHandler(_config, Logger, profile).ProcessControlRequestAsync(request); } } } diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs index b31d437c3b..c8db5a3674 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs @@ -44,7 +44,7 @@ namespace Emby.Dlna.ConnectionManager DataType = "string", SendsEvents = false, - AllowedValues = new string[] + AllowedValues = new[] { "OK", "ContentFormatMismatch", @@ -67,7 +67,7 @@ namespace Emby.Dlna.ConnectionManager DataType = "string", SendsEvents = false, - AllowedValues = new string[] + AllowedValues = new[] { "Output", "Input" diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs index 64cd308a2a..5760f260cf 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs @@ -1,13 +1,15 @@ #pragma warning disable CS1591 using System; +using System.Linq; +using System.Net.Http; using System.Threading.Tasks; using Emby.Dlna.Service; -using MediaBrowser.Common.Net; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.TV; @@ -17,7 +19,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.ContentDirectory { - public class ContentDirectory : BaseService, IContentDirectory + public class ContentDirectoryService : BaseService, IContentDirectory { private readonly ILibraryManager _libraryManager; private readonly IImageProcessor _imageProcessor; @@ -31,14 +33,15 @@ namespace Emby.Dlna.ContentDirectory private readonly IMediaEncoder _mediaEncoder; private readonly ITVSeriesManager _tvSeriesManager; - public ContentDirectory(IDlnaManager dlna, + public ContentDirectoryService( + IDlnaManager dlna, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILibraryManager libraryManager, IServerConfigurationManager config, IUserManager userManager, - ILogger<ContentDirectory> logger, - IHttpClient httpClient, + ILogger<ContentDirectoryService> logger, + IHttpClientFactory httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, @@ -130,18 +133,13 @@ namespace Emby.Dlna.ContentDirectory foreach (var user in _userManager.Users) { - if (user.Policy.IsAdministrator) + if (user.HasPermission(PermissionKind.IsAdministrator)) { return user; } } - foreach (var user in _userManager.Users) - { - return user; - } - - return null; + return _userManager.Users.FirstOrDefault(); } } } diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs index 6db4d7cb66..743dcc5161 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs @@ -10,7 +10,8 @@ namespace Emby.Dlna.ContentDirectory { public string GetXml() { - return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), + return new ServiceXmlBuilder().GetXml( + new ServiceActionListBuilder().GetActions(), GetStateVariables()); } @@ -101,7 +102,7 @@ namespace Emby.Dlna.ContentDirectory DataType = "string", SendsEvents = false, - AllowedValues = new string[] + AllowedValues = new[] { "BrowseMetadata", "BrowseDirectChildren" diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 28888f031a..4b108b89ea 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -10,6 +10,8 @@ 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; @@ -17,7 +19,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -28,11 +29,22 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; +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 Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Dlna.ContentDirectory { 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 IServerConfigurationManager _config; @@ -40,11 +52,6 @@ namespace Emby.Dlna.ContentDirectory private readonly IUserViewManager _userViewManager; private readonly ITVSeriesManager _tvSeriesManager; - private const string NS_DC = "http://purl.org/dc/elements/1.1/"; - private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; - private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/"; - private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; - private readonly int _systemUpdateId; private readonly DidlBuilder _didlBuilder; @@ -174,7 +181,11 @@ namespace Emby.Dlna.ContentDirectory userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks; - _userDataManager.SaveUserData(_user, item, userdata, UserDataSaveReason.TogglePlayed, + _userDataManager.SaveUserData( + _user, + item, + userdata, + UserDataSaveReason.TogglePlayed, CancellationToken.None); } @@ -246,7 +257,7 @@ namespace Emby.Dlna.ContentDirectory var id = sparams["ObjectID"]; var flag = sparams["BrowseFlag"]; var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); - var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); + var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty)); var provided = 0; @@ -279,18 +290,17 @@ namespace Emby.Dlna.ContentDirectory using (var writer = XmlWriter.Create(builder, settings)) { - writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); + writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl); - writer.WriteAttributeString("xmlns", "dc", null, NS_DC); - writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); - writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); + 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; @@ -355,8 +365,8 @@ namespace Emby.Dlna.ContentDirectory private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId) { - var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", "")); - var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); + var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", string.Empty)); + var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty)); var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); // sort example: dc:title, dc:date @@ -390,11 +400,11 @@ namespace Emby.Dlna.ContentDirectory using (var writer = XmlWriter.Create(builder, settings)) { - writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); + writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl); - writer.WriteAttributeString("xmlns", "dc", null, NS_DC); - writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); - writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); + writer.WriteAttributeString("xmlns", "dc", null, NsDc); + writer.WriteAttributeString("xmlns", "dlna", null, NsDlna); + writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp); DidlBuilder.WriteXmlRootAttributes(_profile, writer); @@ -460,12 +470,12 @@ namespace Emby.Dlna.ContentDirectory } else if (search.SearchType == SearchType.Playlist) { - //items = items.OfType<Playlist>(); + // items = items.OfType<Playlist>(); isFolder = true; } else if (search.SearchType == SearchType.MusicAlbum) { - //items = items.OfType<MusicAlbum>(); + // items = items.OfType<MusicAlbum>(); isFolder = true; } @@ -731,7 +741,7 @@ namespace Emby.Dlna.ContentDirectory return GetGenres(item, user, query); } - var array = new ServerItem[] + var array = new[] { new ServerItem(item) { @@ -776,11 +786,14 @@ namespace Emby.Dlna.ContentDirectory }) .ToArray(); - return ApplyPaging(new QueryResult<ServerItem> - { - Items = folders, - TotalRecordCount = folders.Length - }, startIndex, limit); + return ApplyPaging( + new QueryResult<ServerItem> + { + Items = folders, + TotalRecordCount = folders.Length + }, + startIndex, + limit); } private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) @@ -920,7 +933,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query) { query.Recursive = true; - //query.Parent = parent; + // query.Parent = parent; query.SetUser(user); query.IncludeItemTypes = new[] { typeof(BoxSet).Name }; @@ -1115,7 +1128,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query) { query.Parent = null; - query.IncludeItemTypes = new[] { typeof(Playlist).Name }; + query.IncludeItemTypes = new[] { nameof(Playlist) }; query.SetUser(user); query.Recursive = true; @@ -1128,15 +1141,16 @@ namespace Emby.Dlna.ContentDirectory { query.OrderBy = Array.Empty<(string, SortOrder)>(); - var items = _userViewManager.GetLatestItems(new LatestItemsQuery - { - UserId = user.Id, - Limit = 50, - IncludeItemTypes = new[] { typeof(Audio).Name }, - ParentId = parent == null ? Guid.Empty : parent.Id, - GroupItems = true - - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); + var items = _userViewManager.GetLatestItems( + new LatestItemsQuery + { + UserId = user.Id, + Limit = 50, + IncludeItemTypes = new[] { nameof(Audio) }, + ParentId = parent?.Id ?? Guid.Empty, + GroupItems = true + }, + query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); } @@ -1145,13 +1159,15 @@ namespace Emby.Dlna.ContentDirectory { query.OrderBy = Array.Empty<(string, SortOrder)>(); - var result = _tvSeriesManager.GetNextUp(new NextUpQuery - { - Limit = query.Limit, - StartIndex = query.StartIndex, - UserId = query.User.Id - - }, new[] { parent }, query.DtoOptions); + var result = _tvSeriesManager.GetNextUp( + new NextUpQuery + { + Limit = query.Limit, + StartIndex = query.StartIndex, + UserId = query.User.Id + }, + new[] { parent }, + query.DtoOptions); return ToResult(result); } @@ -1160,15 +1176,16 @@ namespace Emby.Dlna.ContentDirectory { query.OrderBy = Array.Empty<(string, SortOrder)>(); - var items = _userViewManager.GetLatestItems(new LatestItemsQuery - { - UserId = user.Id, - Limit = 50, - IncludeItemTypes = new[] { typeof(Episode).Name }, - ParentId = parent == null ? Guid.Empty : parent.Id, - GroupItems = false - - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); + var items = _userViewManager.GetLatestItems( + new LatestItemsQuery + { + UserId = user.Id, + Limit = 50, + IncludeItemTypes = new[] { typeof(Episode).Name }, + ParentId = parent == null ? Guid.Empty : parent.Id, + GroupItems = false + }, + query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); } @@ -1177,15 +1194,16 @@ namespace Emby.Dlna.ContentDirectory { query.OrderBy = Array.Empty<(string, SortOrder)>(); - var items = _userViewManager.GetLatestItems(new LatestItemsQuery - { - UserId = user.Id, - Limit = 50, - IncludeItemTypes = new[] { typeof(Movie).Name }, - ParentId = parent == null ? Guid.Empty : parent.Id, - GroupItems = true - - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); + var items = _userViewManager.GetLatestItems( + new LatestItemsQuery + { + UserId = user.Id, + Limit = 50, + IncludeItemTypes = new[] { nameof(Movie) }, + ParentId = parent?.Id ?? Guid.Empty, + GroupItems = true + }, + query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); } @@ -1217,7 +1235,11 @@ namespace Emby.Dlna.ContentDirectory Recursive = true, ParentId = parentId, GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name }, + IncludeItemTypes = new[] + { + nameof(Movie), + nameof(Series) + }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions() @@ -1341,48 +1363,9 @@ namespace Emby.Dlna.ContentDirectory }; } - Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id); + Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id); return new ServerItem(_libraryManager.GetUserRootFolder()); } } - - internal class ServerItem - { - public BaseItem Item { get; set; } - public StubType? StubType { get; set; } - - public ServerItem(BaseItem item) - { - Item = item; - - if (item is IItemByName && !(item is Folder)) - { - StubType = Dlna.ContentDirectory.StubType.Folder; - } - } - } - - 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/ContentDirectory/ServerItem.cs b/Emby.Dlna/ContentDirectory/ServerItem.cs new file mode 100644 index 0000000000..e406054149 --- /dev/null +++ b/Emby.Dlna/ContentDirectory/ServerItem.cs @@ -0,0 +1,23 @@ +#pragma warning disable CS1591 + +using MediaBrowser.Controller.Entities; + +namespace Emby.Dlna.ContentDirectory +{ + internal class ServerItem + { + public ServerItem(BaseItem item) + { + Item = item; + + if (item is IItemByName && !(item is Folder)) + { + StubType = Dlna.ContentDirectory.StubType.Folder; + } + } + + public BaseItem Item { get; set; } + + public StubType? StubType { get; set; } + } +} diff --git a/Emby.Dlna/ContentDirectory/StubType.cs b/Emby.Dlna/ContentDirectory/StubType.cs new file mode 100644 index 0000000000..eee405d3e7 --- /dev/null +++ b/Emby.Dlna/ContentDirectory/StubType.cs @@ -0,0 +1,28 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1602 + +namespace Emby.Dlna.ContentDirectory +{ + 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 index a6e03b7e6a..4ea4e4e48c 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -7,17 +7,17 @@ namespace Emby.Dlna { public class ControlRequest { - public IHeaderDictionary Headers { get; set; } + 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; } - - public ControlRequest() - { - Headers = new HeaderDictionary(); - } } } diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs index 140ef9b463..d827eef26c 100644 --- a/Emby.Dlna/ControlResponse.cs +++ b/Emby.Dlna/ControlResponse.cs @@ -11,10 +11,16 @@ namespace Emby.Dlna Headers = new Dictionary<string, string>(); } - public IDictionary<string, string> Headers { get; set; } + public IDictionary<string, string> Headers { get; } public string Xml { get; set; } public bool IsSuccessful { get; set; } + + /// <inheritdoc /> + public override string ToString() + { + return Xml; + } } } diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index f7d840c623..5b8a89d8f3 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -6,14 +6,13 @@ using System.IO; using System.Linq; using System.Text; using System.Xml; -using Emby.Dlna.Configuration; using Emby.Dlna.ContentDirectory; +using Jellyfin.Data.Entities; 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.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Playlists; @@ -23,17 +22,24 @@ 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 readonly CultureInfo _usCulture = new CultureInfo("en-US"); + 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 const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; - private const string NS_DC = "http://purl.org/dc/elements/1.1/"; - private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; - private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/"; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly DeviceProfile _profile; private readonly IImageProcessor _imageProcessor; @@ -92,21 +98,21 @@ namespace Emby.Dlna.Didl { using (var writer = XmlWriter.Create(builder, settings)) { - //writer.WriteStartDocument(); + // writer.WriteStartDocument(); - writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); + writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl); - writer.WriteAttributeString("xmlns", "dc", null, NS_DC); - writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); - writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); - //didl.SetAttribute("xmlns:sec", NS_SEC); + 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(); + // writer.WriteEndDocument(); } return builder.ToString(); @@ -141,7 +147,7 @@ namespace Emby.Dlna.Didl { var clientId = GetClientId(item, null); - writer.WriteStartElement(string.Empty, "item", NS_DIDL); + writer.WriteStartElement(string.Empty, "item", NsDidl); writer.WriteAttributeString("restricted", "1"); writer.WriteAttributeString("id", clientId); @@ -201,7 +207,8 @@ namespace Emby.Dlna.Didl var targetWidth = streamInfo.TargetWidth; var targetHeight = streamInfo.TargetHeight; - var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container, + var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader( + streamInfo.Container, streamInfo.TargetVideoCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(), targetWidth, @@ -273,7 +280,7 @@ namespace Emby.Dlna.Didl } else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase)) { - writer.WriteStartElement(string.Empty, "res", NS_DIDL); + writer.WriteStartElement(string.Empty, "res", NsDidl); writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*"); @@ -282,7 +289,7 @@ namespace Emby.Dlna.Didl } else { - writer.WriteStartElement(string.Empty, "res", NS_DIDL); + writer.WriteStartElement(string.Empty, "res", NsDidl); var protocolInfo = string.Format( CultureInfo.InvariantCulture, "http-get:*:text/{0}:*", @@ -298,7 +305,7 @@ namespace Emby.Dlna.Didl private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo) { - writer.WriteStartElement(string.Empty, "res", NS_DIDL); + writer.WriteStartElement(string.Empty, "res", NsDidl); var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken)); @@ -358,7 +365,8 @@ namespace Emby.Dlna.Didl writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture)); } - var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container, + var mediaProfile = _profile.GetVideoMediaProfile( + streamInfo.Container, streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetVideoCodec.FirstOrDefault(), streamInfo.TargetAudioBitrate, @@ -421,7 +429,6 @@ namespace Emby.Dlna.Didl case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); case StubType.Series: return _localization.GetLocalizedString("Shows"); - default: break; } } @@ -520,7 +527,7 @@ namespace Emby.Dlna.Didl private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null) { - writer.WriteStartElement(string.Empty, "res", NS_DIDL); + writer.WriteStartElement(string.Empty, "res", NsDidl); if (streamInfo == null) { @@ -577,7 +584,8 @@ namespace Emby.Dlna.Didl writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); } - var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container, + var mediaProfile = _profile.GetAudioMediaProfile( + streamInfo.Container, streamInfo.TargetAudioCodec.FirstOrDefault(), targetChannels, targetAudioBitrate, @@ -590,7 +598,8 @@ namespace Emby.Dlna.Didl ? MimeTypes.GetMimeType(filename) : mediaProfile.MimeType; - var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container, + var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader( + streamInfo.Container, streamInfo.TargetAudioCodec.FirstOrDefault(), targetAudioBitrate, targetSampleRate, @@ -621,7 +630,7 @@ namespace Emby.Dlna.Didl public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null) { - writer.WriteStartElement(string.Empty, "container", NS_DIDL); + writer.WriteStartElement(string.Empty, "container", NsDidl); writer.WriteAttributeString("restricted", "1"); writer.WriteAttributeString("searchable", "1"); @@ -670,7 +679,7 @@ namespace Emby.Dlna.Didl return; } - MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null; + XmlAttribute secAttribute = null; foreach (var attribute in _profile.XmlRootAttributes) { if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase)) @@ -700,15 +709,15 @@ namespace Emby.Dlna.Didl } /// <summary> - /// Adds fields used by both items and folders + /// Adds fields used by both items and folders. /// </summary> 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")) + // if (filter.Contains("dc:title")) { - AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC); + AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NsDc); } WriteObjectClass(writer, item, itemStubType); @@ -717,7 +726,7 @@ namespace Emby.Dlna.Didl { if (item.PremiereDate.HasValue) { - AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NS_DC); + AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NsDc); } } @@ -725,13 +734,13 @@ namespace Emby.Dlna.Didl { foreach (var genre in item.Genres) { - AddValue(writer, "upnp", "genre", genre, NS_UPNP); + AddValue(writer, "upnp", "genre", genre, NsUpnp); } } foreach (var studio in item.Studios) { - AddValue(writer, "upnp", "publisher", studio, NS_UPNP); + AddValue(writer, "upnp", "publisher", studio, NsUpnp); } if (!(item is Folder)) @@ -742,27 +751,29 @@ namespace Emby.Dlna.Didl if (!string.IsNullOrWhiteSpace(desc)) { - AddValue(writer, "dc", "description", desc, NS_DC); + AddValue(writer, "dc", "description", desc, NsDc); } } - //if (filter.Contains("upnp:longDescription")) - //{ + + // if (filter.Contains("upnp:longDescription")) + // { // if (!string.IsNullOrWhiteSpace(item.Overview)) // { - // AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP); + // AddValue(writer, "upnp", "longDescription", item.Overview, NsUpnp); // } - //} + // } } if (!string.IsNullOrEmpty(item.OfficialRating)) { if (filter.Contains("dc:rating")) { - AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC); + AddValue(writer, "dc", "rating", item.OfficialRating, NsDc); } + if (filter.Contains("upnp:rating")) { - AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP); + AddValue(writer, "upnp", "rating", item.OfficialRating, NsUpnp); } } @@ -774,7 +785,7 @@ namespace Emby.Dlna.Didl // More types here // http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs - writer.WriteStartElement("upnp", "class", NS_UPNP); + writer.WriteStartElement("upnp", "class", NsUpnp); if (item.IsDisplayedAsFolder || stubType.HasValue) { @@ -875,7 +886,7 @@ namespace Emby.Dlna.Didl var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) ?? PersonType.Actor; - AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP); + AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NsUpnp); } } @@ -889,8 +900,8 @@ namespace Emby.Dlna.Didl { foreach (var artist in hasArtists.Artists) { - AddValue(writer, "upnp", "artist", artist, NS_UPNP); - AddValue(writer, "dc", "creator", artist, NS_DC); + 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 == null) @@ -910,16 +921,16 @@ namespace Emby.Dlna.Didl if (!string.IsNullOrWhiteSpace(item.Album)) { - AddValue(writer, "upnp", "album", item.Album, NS_UPNP); + AddValue(writer, "upnp", "album", item.Album, NsUpnp); } if (item.IndexNumber.HasValue) { - AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP); + AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp); if (item is Episode) { - AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP); + AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp); } } } @@ -928,7 +939,7 @@ namespace Emby.Dlna.Didl { try { - writer.WriteStartElement("upnp", "artist", NS_UPNP); + writer.WriteStartElement("upnp", "artist", NsUpnp); writer.WriteAttributeString("role", "AlbumArtist"); writer.WriteString(name); @@ -937,7 +948,7 @@ namespace Emby.Dlna.Didl } catch (XmlException ex) { - _logger.LogError(ex, "Error adding xml value: {value}", name); + _logger.LogError(ex, "Error adding xml value: {Value}", name); } } @@ -949,7 +960,7 @@ namespace Emby.Dlna.Didl } catch (XmlException ex) { - _logger.LogError(ex, "Error adding xml value: {value}", value); + _logger.LogError(ex, "Error adding xml value: {Value}", value); } } @@ -964,14 +975,14 @@ namespace Emby.Dlna.Didl var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg"); - writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP); - writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn); - writer.WriteString(albumartUrlInfo.Url); + writer.WriteStartElement("upnp", "albumArtURI", NsUpnp); + writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn); + writer.WriteString(albumartUrlInfo.url); writer.WriteFullEndElement(); // TOOD: Remove these default values var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg"); - writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url); + writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url); if (!_profile.EnableAlbumArtInDidl) { @@ -995,7 +1006,6 @@ namespace Emby.Dlna.Didl } AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN"); - } private void AddImageResElement( @@ -1015,12 +1025,12 @@ namespace Emby.Dlna.Didl var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format); - writer.WriteStartElement(string.Empty, "res", NS_DIDL); + 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 width = albumartUrlInfo.width ?? maxWidth; + var height = albumartUrlInfo.height ?? maxHeight; var contentFeatures = new ContentFeatureBuilder(_profile) .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn); @@ -1037,7 +1047,7 @@ namespace Emby.Dlna.Didl "resolution", string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height)); - writer.WriteString(albumartUrlInfo.Url); + writer.WriteString(albumartUrlInfo.url); writer.WriteFullEndElement(); } @@ -1048,10 +1058,12 @@ namespace Emby.Dlna.Didl { return GetImageInfo(item, ImageType.Primary); } + if (item.HasImage(ImageType.Thumb)) { return GetImageInfo(item, ImageType.Thumb); } + if (item.HasImage(ImageType.Backdrop)) { if (item is Channel) @@ -1131,29 +1143,15 @@ namespace Emby.Dlna.Didl if (width == 0 || height == 0) { - //_imageProcessor.GetImageSize(item, imageInfo); width = null; height = null; } - else if (width == -1 || height == -1) { width = null; height = null; } - //try - //{ - // var size = _imageProcessor.GetImageSize(imageInfo); - - // width = size.Width; - // height = size.Height; - //} - //catch - //{ - - //} - var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty) .TrimStart('.') .Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase); @@ -1170,30 +1168,6 @@ namespace Emby.Dlna.Didl }; } - private class ImageDownloadInfo - { - internal Guid ItemId; - internal string ImageTag; - internal ImageType Type; - - internal int? Width; - internal int? Height; - - internal bool IsDirectStream; - - internal string Format; - - internal ItemImageInfo ItemImageInfo; - } - - private class ImageUrlInfo - { - internal string Url; - - internal int? Width; - internal int? Height; - } - public static string GetClientId(BaseItem item, StubType? stubType) { return GetClientId(item.Id, stubType); @@ -1211,7 +1185,7 @@ namespace Emby.Dlna.Didl return id; } - private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format) + private (string url, int? width, int? height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format) { var url = string.Format( CultureInfo.InvariantCulture, @@ -1249,12 +1223,26 @@ namespace Emby.Dlna.Didl // just lie info.IsDirectStream = true; - return new ImageUrlInfo - { - Url = url, - Width = width, - Height = height - }; + 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 string Format { get; set; } + + internal ItemImageInfo ItemImageInfo { get; set; } } } } diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs index 412259e904..b58fdff2c9 100644 --- a/Emby.Dlna/Didl/Filter.cs +++ b/Emby.Dlna/Didl/Filter.cs @@ -12,7 +12,6 @@ namespace Emby.Dlna.Didl public Filter() : this("*") { - } public Filter(string filter) @@ -24,9 +23,7 @@ namespace Emby.Dlna.Didl public bool Contains(string field) { - // Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back. - return true; - //return _all || ListHelper.ContainsIgnoreCase(_fields, 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 index 896fe992bf..2b86ea333f 100644 --- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs +++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable CA1305 using System; using System.IO; @@ -29,7 +30,6 @@ namespace Emby.Dlna.Didl { } - public StringWriterWithEncoding(Encoding encoding) { _encoding = encoding; diff --git a/Emby.Dlna/DlnaConfigurationFactory.cs b/Emby.Dlna/DlnaConfigurationFactory.cs new file mode 100644 index 0000000000..4c6ca869aa --- /dev/null +++ b/Emby.Dlna/DlnaConfigurationFactory.cs @@ -0,0 +1,24 @@ +#nullable enable +#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<ConfigurationStore> GetConfigurations() + { + return new[] + { + new ConfigurationStore + { + Key = "dlna", + ConfigurationType = typeof(DlnaOptions) + } + }; + } + } +} diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 10f881fe76..1807ac6a13 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -31,7 +31,7 @@ namespace Emby.Dlna private readonly IApplicationPaths _appPaths; private readonly IXmlSerializer _xmlSerializer; private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger<DlnaManager> _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; @@ -49,16 +49,20 @@ namespace Emby.Dlna _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; _appPaths = appPaths; - _logger = loggerFactory.CreateLogger("Dlna"); + _logger = loggerFactory.CreateLogger<DlnaManager>(); _jsonSerializer = jsonSerializer; _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(); + await ExtractSystemProfilesAsync().ConfigureAwait(false); LoadProfiles(); } catch (Exception ex) @@ -88,7 +92,6 @@ namespace Emby.Dlna .Select(i => i.Item2) .ToList(); } - } public DeviceProfile GetDefaultProfile() @@ -123,83 +126,92 @@ namespace Emby.Dlna var builder = new StringBuilder(); builder.AppendLine("No matching device profile found. The default will need to be used."); - builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty)); - builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty)); - builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty)); - builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty)); - builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty)); - builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty)); - builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty)); - builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty)); - builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty)); + builder.Append("FriendlyName:").AppendLine(profile.FriendlyName); + builder.Append("Manufacturer:").AppendLine(profile.Manufacturer); + builder.Append("ManufacturerUrl:").AppendLine(profile.ManufacturerUrl); + builder.Append("ModelDescription:").AppendLine(profile.ModelDescription); + builder.Append("ModelName:").AppendLine(profile.ModelName); + builder.Append("ModelNumber:").AppendLine(profile.ModelNumber); + builder.Append("ModelUrl:").AppendLine(profile.ModelUrl); + builder.Append("SerialNumber:").AppendLine(profile.SerialNumber); _logger.LogInformation(builder.ToString()); } private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { - if (!string.IsNullOrEmpty(profileInfo.DeviceDescription)) - { - if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription)) - return false; - } - if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) { - if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) + if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) + { return false; + } } if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) { - if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) + if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) + { return false; + } } if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) { - if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) + if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) + { return false; + } } if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) { - if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) + if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) + { return false; + } } if (!string.IsNullOrEmpty(profileInfo.ModelName)) { - if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName)) + if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)) + { return false; + } } if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) { - if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) + if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) + { return false; + } } if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) { - if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) + if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) + { return false; + } } if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) { - if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) + if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) + { return false; + } } return true; } - private bool IsRegexMatch(string input, string pattern) + private bool IsRegexOrSubstringMatch(string input, string pattern) { try { - return Regex.IsMatch(input, pattern); + return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } catch (ArgumentException ex) { @@ -223,7 +235,7 @@ namespace Emby.Dlna } else { - var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray()); + var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value))); _logger.LogDebug("No matching device profile found. {0}", headerString); } @@ -251,7 +263,7 @@ namespace Emby.Dlna 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); + // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); return isMatch; case HeaderMatchType.Regex: return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase); @@ -263,10 +275,6 @@ namespace Emby.Dlna return false; } - private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user"); - - private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system"); - private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type) { try @@ -370,7 +378,7 @@ namespace Emby.Dlna foreach (var name in _assembly.GetManifestResourceNames()) { - if (!name.StartsWith(namespaceName)) + if (!name.StartsWith(namespaceName, StringComparison.Ordinal)) { continue; } @@ -389,7 +397,7 @@ namespace Emby.Dlna using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) { - await stream.CopyToAsync(fileStream); + await stream.CopyToAsync(fileStream).ConfigureAwait(false); } } } @@ -439,6 +447,7 @@ namespace Emby.Dlna { throw new ArgumentException("Profile is missing Id"); } + if (string.IsNullOrEmpty(profile.Name)) { throw new ArgumentException("Profile is missing Name"); @@ -464,6 +473,7 @@ namespace Emby.Dlna { _profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); } + SerializeToXml(profile, path); } @@ -474,10 +484,10 @@ namespace Emby.Dlna /// <summary> /// Recreates the object using serialization, to ensure it's not a subclass. - /// If it's a subclass it may not serlialize properly to xml (different root element tag name) + /// If it's a subclass it may not serlialize properly to xml (different root element tag name). /// </summary> - /// <param name="profile"></param> - /// <returns></returns> + /// <param name="profile">The device profile.</param> + /// <returns>The reserialized device profile.</returns> private DeviceProfile ReserializeProfile(DeviceProfile profile) { if (profile.GetType() == typeof(DeviceProfile)) @@ -490,16 +500,9 @@ namespace Emby.Dlna return _jsonSerializer.DeserializeFromString<DeviceProfile>(json); } - class InternalProfileInfo - { - internal DeviceProfileInfo Info { get; set; } - internal string Path { get; set; } - } - public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) { - var profile = GetProfile(headers) ?? - GetDefaultProfile(); + var profile = GetDefaultProfile(); var serverId = _appHost.SystemId; @@ -520,7 +523,15 @@ namespace Emby.Dlna Stream = _assembly.GetManifestResourceStream(resource) }; } + + private class InternalProfileInfo + { + internal DeviceProfileInfo Info { get; set; } + + internal string Path { get; set; } + } } + /* class DlnaProfileEntryPoint : IServerEntryPoint { @@ -566,9 +577,9 @@ namespace Emby.Dlna new Foobar2000Profile(), new SharpSmartTvProfile(), new MediaMonkeyProfile(), - //new Windows81Profile(), - //new WindowsMediaCenterProfile(), - //new WindowsPhoneProfile(), + // new Windows81Profile(), + // new WindowsMediaCenterProfile(), + // new WindowsPhoneProfile(), new DirectTvProfile(), new DishHopperJoeyProfile(), new DefaultProfile(), diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 42a5f95c14..6ed49944c0 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -20,7 +20,7 @@ <TargetFramework>netstandard2.1</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> - <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> <!-- Code Analyzers--> @@ -80,6 +80,7 @@ <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" /> <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" /> + <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" /> </ItemGroup> </Project> diff --git a/Emby.Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs index fd18343e62..1b1bd426c5 100644 --- a/Emby.Dlna/EventSubscriptionResponse.cs +++ b/Emby.Dlna/EventSubscriptionResponse.cs @@ -15,6 +15,6 @@ namespace Emby.Dlna public string ContentType { get; set; } - public Dictionary<string, string> Headers { get; set; } + public Dictionary<string, string> Headers { get; } } } diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index efbb53b644..7d8da86ef9 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -6,6 +6,7 @@ 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 MediaBrowser.Common.Extensions; @@ -14,35 +15,45 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.Eventing { - public class EventManager : IEventManager + public class DlnaEventManager : IDlnaEventManager { private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions = new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase); private readonly ILogger _logger; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; - public EventManager(ILogger logger, IHttpClient httpClient) + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory) { - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _logger = logger; } public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl) { var subscription = GetSubscription(subscriptionId, false); + if (subscription != null) + { + subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300; + int timeoutSeconds = subscription.TimeoutSeconds; + subscription.SubscriptionTime = DateTime.UtcNow; - 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); - _logger.LogDebug( - "Renewing event subscription for {0} with timeout of {1} to {2}", - subscription.NotificationType, - timeoutSeconds, - subscription.CallbackUrl); + return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds); + } - return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds); + return new EventSubscriptionResponse + { + Content = string.Empty, + ContentType = "text/plain" + }; } public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) @@ -50,7 +61,8 @@ namespace Emby.Dlna.Eventing 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}", + _logger.LogDebug( + "Creating event subscription for {0} with timeout of {1} to {2}", notificationType, timeout, callbackUrl); @@ -86,7 +98,7 @@ namespace Emby.Dlna.Eventing { _logger.LogDebug("Cancelling event subscription {0}", subscriptionId); - _subscriptions.TryRemove(subscriptionId, out EventSubscription sub); + _subscriptions.TryRemove(subscriptionId, out _); return new EventSubscriptionResponse { @@ -95,7 +107,6 @@ namespace Emby.Dlna.Eventing }; } - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds) { var response = new EventSubscriptionResponse @@ -144,33 +155,30 @@ namespace Emby.Dlna.Eventing builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">"); foreach (var key in stateVariables.Keys) { - builder.Append("<e:property>"); - builder.Append("<" + key + ">"); - builder.Append(stateVariables[key]); - builder.Append("</" + key + ">"); - builder.Append("</e:property>"); + builder.Append("<e:property>") + .Append('<') + .Append(key) + .Append('>') + .Append(stateVariables[key]) + .Append("</") + .Append(key) + .Append('>') + .Append("</e:property>"); } - builder.Append("</e:propertyset>"); - var options = new HttpRequestOptions - { - RequestContent = builder.ToString(), - RequestContentType = "text/xml", - Url = subscription.CallbackUrl, - BufferContent = false - }; + builder.Append("</e:propertyset>"); - options.RequestHeaders.Add("NT", subscription.NotificationType); - options.RequestHeaders.Add("NTS", "upnp:propchange"); - options.RequestHeaders.Add("SID", subscription.Id); - options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture)); + 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(_usCulture)); try { - using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false)) - { - - } + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs index 51eaee9d77..40d73ee0e5 100644 --- a/Emby.Dlna/Eventing/EventSubscription.cs +++ b/Emby.Dlna/Eventing/EventSubscription.cs @@ -7,10 +7,13 @@ 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; } diff --git a/Emby.Dlna/IConnectionManager.cs b/Emby.Dlna/IConnectionManager.cs index 7b4a33a98c..9f643a9e64 100644 --- a/Emby.Dlna/IConnectionManager.cs +++ b/Emby.Dlna/IConnectionManager.cs @@ -2,7 +2,7 @@ namespace Emby.Dlna { - public interface IConnectionManager : IEventManager, IUpnpService + public interface IConnectionManager : IDlnaEventManager, IUpnpService { } } diff --git a/Emby.Dlna/IContentDirectory.cs b/Emby.Dlna/IContentDirectory.cs index 83ef09c665..10f4d63866 100644 --- a/Emby.Dlna/IContentDirectory.cs +++ b/Emby.Dlna/IContentDirectory.cs @@ -2,7 +2,7 @@ namespace Emby.Dlna { - public interface IContentDirectory : IEventManager, IUpnpService + public interface IContentDirectory : IDlnaEventManager, IUpnpService { } } diff --git a/Emby.Dlna/IEventManager.cs b/Emby.Dlna/IDlnaEventManager.cs index 2872033892..33cf0896ba 100644 --- a/Emby.Dlna/IEventManager.cs +++ b/Emby.Dlna/IDlnaEventManager.cs @@ -2,22 +2,32 @@ namespace Emby.Dlna { - public interface IEventManager + public interface IDlnaEventManager { /// <summary> /// Cancels the event subscription. /// </summary> /// <param name="subscriptionId">The subscription identifier.</param> + /// <returns>The response.</returns> EventSubscriptionResponse CancelEventSubscription(string subscriptionId); /// <summary> /// Renews the event subscription. /// </summary> + /// <param name="subscriptionId">The subscription identifier.</param> + /// <param name="notificationType">The notification type.</param> + /// <param name="requestedTimeoutString">The requested timeout as a sting.</param> + /// <param name="callbackUrl">The callback url.</param> + /// <returns>The response.</returns> EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl); /// <summary> /// Creates the event subscription. /// </summary> + /// <param name="notificationType">The notification type.</param> + /// <param name="requestedTimeoutString">The requested timeout as a sting.</param> + /// <param name="callbackUrl">The callback url.</param> + /// <returns>The response.</returns> EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl); } } diff --git a/Emby.Dlna/IMediaReceiverRegistrar.cs b/Emby.Dlna/IMediaReceiverRegistrar.cs index b0376b6a99..43e934b53a 100644 --- a/Emby.Dlna/IMediaReceiverRegistrar.cs +++ b/Emby.Dlna/IMediaReceiverRegistrar.cs @@ -2,7 +2,7 @@ namespace Emby.Dlna { - public interface IMediaReceiverRegistrar : IEventManager, IUpnpService + public interface IMediaReceiverRegistrar : IDlnaEventManager, IUpnpService { } } diff --git a/Emby.Dlna/Images/logo240.jpg b/Emby.Dlna/Images/logo240.jpg Binary files differindex da1cb5e071..78a27f1b54 100644 --- a/Emby.Dlna/Images/logo240.jpg +++ b/Emby.Dlna/Images/logo240.jpg diff --git a/Emby.Dlna/Images/people48.png b/Emby.Dlna/Images/people48.png Binary files differindex 7fb25e6b39..dae5f6057f 100644 --- a/Emby.Dlna/Images/people48.png +++ b/Emby.Dlna/Images/people48.png diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index c5d60b2a05..40c2cc0e0a 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; +using System.Net.Http; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -30,15 +31,13 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Dlna.Main { - public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup + public sealed class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup { private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; + private readonly ILogger<DlnaEntryPoint> _logger; private readonly IServerApplicationHost _appHost; - - private PlayToManager _manager; private readonly ISessionManager _sessionManager; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDlnaManager _dlnaManager; @@ -47,29 +46,23 @@ namespace Emby.Dlna.Main private readonly ILocalizationManager _localization; private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IDeviceDiscovery _deviceDiscovery; - - private SsdpDevicePublisher _Publisher; - private readonly ISocketFactory _socketFactory; private readonly INetworkManager _networkManager; + private readonly object _syncLock = new object(); + private PlayToManager _manager; + private SsdpDevicePublisher _publisher; private ISsdpCommunicationsServer _communicationsServer; - internal IContentDirectory ContentDirectory { get; private set; } - - internal IConnectionManager ConnectionManager { get; private set; } - - internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } - - public static DlnaEntryPoint Current; + private bool _disposed; - public DlnaEntryPoint(IServerConfigurationManager config, + public DlnaEntryPoint( + IServerConfigurationManager config, ILoggerFactory loggerFactory, IServerApplicationHost appHost, ISessionManager sessionManager, - IHttpClient httpClient, + IHttpClientFactory httpClientFactory, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, @@ -87,7 +80,7 @@ namespace Emby.Dlna.Main _config = config; _appHost = appHost; _sessionManager = sessionManager; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _libraryManager = libraryManager; _userManager = userManager; _dlnaManager = dlnaManager; @@ -99,54 +92,62 @@ namespace Emby.Dlna.Main _mediaEncoder = mediaEncoder; _socketFactory = socketFactory; _networkManager = networkManager; - _logger = loggerFactory.CreateLogger("Dlna"); + _logger = loggerFactory.CreateLogger<DlnaEntryPoint>(); - ContentDirectory = new ContentDirectory.ContentDirectory( + ContentDirectory = new ContentDirectory.ContentDirectoryService( dlnaManager, userDataManager, imageProcessor, libraryManager, config, userManager, - loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(), - httpClient, + loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(), + httpClientFactory, localizationManager, mediaSourceManager, userViewManager, mediaEncoder, tvSeriesManager); - ConnectionManager = new ConnectionManager.ConnectionManager( + ConnectionManager = new ConnectionManager.ConnectionManagerService( dlnaManager, config, - loggerFactory.CreateLogger<ConnectionManager.ConnectionManager>(), - httpClient); + loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(), + httpClientFactory); - MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar( - loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrar>(), - httpClient, + MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService( + loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(), + httpClientFactory, config); Current = this; } + public static DlnaEntryPoint Current { get; private set; } + + public IContentDirectory ContentDirectory { get; private set; } + + public IConnectionManager ConnectionManager { get; private set; } + + public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } + public async Task RunAsync() { await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); - ReloadComponents(); + await ReloadComponents().ConfigureAwait(false); - _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; + _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated; } - void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) + private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) { if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) { - ReloadComponents(); + await ReloadComponents().ConfigureAwait(false); } } - private async void ReloadComponents() + private async Task ReloadComponents() { var options = _config.GetDlnaConfiguration(); @@ -180,7 +181,7 @@ namespace Emby.Dlna.Main var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows || OperatingSystem.Id == OperatingSystemId.Linux; - _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding) + _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding) { IsShared = true }; @@ -231,20 +232,22 @@ namespace Emby.Dlna.Main return; } - if (_Publisher != null) + if (_publisher != null) { return; } try { - _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost); - _Publisher.LogFunction = LogMessage; - _Publisher.SupportPnpRootDevice = false; + _publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost) + { + LogFunction = LogMessage, + SupportPnpRootDevice = false + }; await RegisterServerEndpoints().ConfigureAwait(false); - _Publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds)); + _publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds)); } catch (Exception ex) { @@ -266,6 +269,12 @@ namespace Emby.Dlna.Main continue; } + // Limit to LAN addresses only + if (!_networkManager.IsAddressInSubnets(address, true, true)) + { + continue; + } + var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); @@ -275,7 +284,7 @@ namespace Emby.Dlna.Main var device = new SsdpRootDevice { - CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info. + CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info. Location = uri, // Must point to the URL that serves your devices UPnP description document. Address = address, SubnetMask = _networkManager.GetLocalIpSubnetMask(address), @@ -287,13 +296,13 @@ namespace Emby.Dlna.Main }; SetProperies(device, fullService); - _Publisher.AddDevice(device); + _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" + // "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" }; foreach (var subDevice in embeddedDevices) @@ -319,12 +328,13 @@ namespace Emby.Dlna.Main { guid = text.GetMD5(); } + return guid.ToString("N", CultureInfo.InvariantCulture); } private void SetProperies(SsdpDevice device, string fullDeviceType) { - var service = fullDeviceType.Replace("urn:", string.Empty).Replace(":1", string.Empty); + var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase); var serviceParts = service.Split(':'); @@ -335,7 +345,6 @@ namespace Emby.Dlna.Main device.DeviceType = serviceParts[2]; } - private readonly object _syncLock = new object(); private void StartPlayToManager() { lock (_syncLock) @@ -347,7 +356,8 @@ namespace Emby.Dlna.Main try { - _manager = new PlayToManager(_logger, + _manager = new PlayToManager( + _logger, _sessionManager, _libraryManager, _userManager, @@ -355,7 +365,7 @@ namespace Emby.Dlna.Main _appHost, _imageProcessor, _deviceDiscovery, - _httpClient, + _httpClientFactory, _config, _userDataManager, _localization, @@ -386,13 +396,30 @@ namespace Emby.Dlna.Main { _logger.LogError(ex, "Error disposing PlayTo manager"); } + _manager = null; } } } + public void DisposeDevicePublisher() + { + if (_publisher != null) + { + _logger.LogInformation("Disposing SsdpDevicePublisher"); + _publisher.Dispose(); + _publisher = null; + } + } + + /// <inheritdoc /> public void Dispose() { + if (_disposed) + { + return; + } + DisposeDevicePublisher(); DisposePlayToManager(); DisposeDeviceDiscovery(); @@ -408,16 +435,8 @@ namespace Emby.Dlna.Main ConnectionManager = null; MediaReceiverRegistrar = null; Current = null; - } - public void DisposeDevicePublisher() - { - if (_Publisher != null) - { - _logger.LogInformation("Disposing SsdpDevicePublisher"); - _Publisher.Dispose(); - _Publisher = null; - } + _disposed = true; } } } diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 8bf0cd961b..464f71a6f1 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Xml; @@ -10,8 +8,16 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar { + /// <summary> + /// Defines the <see cref="ControlHandler" />. + /// </summary> public class ControlHandler : BaseControlHandler { + /// <summary> + /// Initializes a new instance of the <see cref="ControlHandler"/> class. + /// </summary> + /// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="ControlHandler"/> instance.</param> + /// <param name="logger">The <see cref="ILogger"/> for use with the <see cref="ControlHandler"/> instance.</param> public ControlHandler(IServerConfigurationManager config, ILogger logger) : base(config, logger) { @@ -35,9 +41,17 @@ namespace Emby.Dlna.MediaReceiverRegistrar throw new ResourceNotFoundException("Unexpected control request name: " + methodName); } + /// <summary> + /// Records that the handle is authorized in the xml stream. + /// </summary> + /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param> private static void HandleIsAuthorized(XmlWriter xmlWriter) => xmlWriter.WriteElementString("Result", "1"); + /// <summary> + /// Records that the handle is validated in the xml stream. + /// </summary> + /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param> private static void HandleIsValidated(XmlWriter xmlWriter) => xmlWriter.WriteElementString("Result", "1"); } diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs deleted file mode 100644 index 64dfc840a8..0000000000 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ /dev/null @@ -1,39 +0,0 @@ -#pragma warning disable CS1591 - -using System.Threading.Tasks; -using Emby.Dlna.Service; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using Microsoft.Extensions.Logging; - -namespace Emby.Dlna.MediaReceiverRegistrar -{ - public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar - { - private readonly IServerConfigurationManager _config; - - public MediaReceiverRegistrar( - ILogger<MediaReceiverRegistrar> logger, - IHttpClient httpClient, - IServerConfigurationManager config) - : base(logger, httpClient) - { - _config = config; - } - - /// <inheritdoc /> - public string GetServiceXml() - { - return new MediaReceiverRegistrarXmlBuilder().GetXml(); - } - - /// <inheritdoc /> - public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request) - { - return new ControlHandler( - _config, - Logger) - .ProcessControlRequestAsync(request); - } - } -} diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs new file mode 100644 index 0000000000..a5aae515c4 --- /dev/null +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs @@ -0,0 +1,46 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Emby.Dlna.Service; +using MediaBrowser.Controller.Configuration; +using Microsoft.Extensions.Logging; + +namespace Emby.Dlna.MediaReceiverRegistrar +{ + /// <summary> + /// Defines the <see cref="MediaReceiverRegistrarService" />. + /// </summary> + public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar + { + private readonly IServerConfigurationManager _config; + + /// <summary> + /// Initializes a new instance of the <see cref="MediaReceiverRegistrarService"/> class. + /// </summary> + /// <param name="logger">The <see cref="ILogger{MediaReceiverRegistrarService}"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param> + /// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param> + public MediaReceiverRegistrarService( + ILogger<MediaReceiverRegistrarService> logger, + IHttpClientFactory httpClientFactory, + IServerConfigurationManager config) + : base(logger, httpClientFactory) + { + _config = config; + } + + /// <inheritdoc /> + public string GetServiceXml() + { + return MediaReceiverRegistrarXmlBuilder.GetXml(); + } + + /// <inheritdoc /> + public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request) + { + return new ControlHandler( + _config, + Logger) + .ProcessControlRequestAsync(request); + } + } +} diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs index 8497025461..37840cd096 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs @@ -1,78 +1,89 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Emby.Dlna.Common; using Emby.Dlna.Service; +using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { - public class MediaReceiverRegistrarXmlBuilder + /// <summary> + /// Defines the <see cref="MediaReceiverRegistrarXmlBuilder" />. + /// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drmnd/5d37515e-7a63-4709-8258-8fd4e0ed4482. + /// </summary> + public static class MediaReceiverRegistrarXmlBuilder { - public string GetXml() + /// <summary> + /// Retrieves an XML description of the X_MS_MediaReceiverRegistrar. + /// </summary> + /// <returns>An XML representation of this service.</returns> + public static string GetXml() { - return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), - GetStateVariables()); + return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); } + /// <summary> + /// The a list of all the state variables for this invocation. + /// </summary> + /// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns> private static IEnumerable<StateVariable> GetStateVariables() { - var list = new List<StateVariable>(); - - list.Add(new StateVariable + var list = new List<StateVariable> { - Name = "AuthorizationGrantedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "AuthorizationGrantedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_DeviceID", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_DeviceID", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "AuthorizationDeniedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "AuthorizationDeniedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "ValidationSucceededUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "ValidationSucceededUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_RegistrationRespMsg", - DataType = "bin.base64", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_RegistrationRespMsg", + DataType = "bin.base64", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_RegistrationReqMsg", - DataType = "bin.base64", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_RegistrationReqMsg", + DataType = "bin.base64", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "ValidationRevokedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "ValidationRevokedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_Result", - DataType = "int", - SendsEvents = false - }); + 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 index 13545c6894..1dc9c79c14 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs @@ -1,13 +1,19 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Emby.Dlna.Common; +using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { - public class ServiceActionListBuilder + /// <summary> + /// Defines the <see cref="ServiceActionListBuilder" />. + /// </summary> + public static class ServiceActionListBuilder { - public IEnumerable<ServiceAction> GetActions() + /// <summary> + /// Returns a list of services that this instance provides. + /// </summary> + /// <returns>An <see cref="IEnumerable{ServiceAction}"/>.</returns> + public static IEnumerable<ServiceAction> GetActions() { return new[] { @@ -21,6 +27,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar }; } + /// <summary> + /// Returns the action details for "IsValidated". + /// </summary> + /// <returns>The <see cref="ServiceAction"/>.</returns> private static ServiceAction GetIsValidated() { var action = new ServiceAction @@ -43,6 +53,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// <summary> + /// Returns the action details for "IsAuthorized". + /// </summary> + /// <returns>The <see cref="ServiceAction"/>.</returns> private static ServiceAction GetIsAuthorized() { var action = new ServiceAction @@ -65,6 +79,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// <summary> + /// Returns the action details for "RegisterDevice". + /// </summary> + /// <returns>The <see cref="ServiceAction"/>.</returns> private static ServiceAction GetRegisterDevice() { var action = new ServiceAction @@ -87,6 +105,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// <summary> + /// Returns the action details for "GetValidationSucceededUpdateID". + /// </summary> + /// <returns>The <see cref="ServiceAction"/>.</returns> private static ServiceAction GetGetValidationSucceededUpdateID() { var action = new ServiceAction @@ -103,7 +125,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetAuthorizationDeniedUpdateID() + /// <summary> + /// Returns the action details for "GetGetAuthorizationDeniedUpdateID". + /// </summary> + /// <returns>The <see cref="ServiceAction"/>.</returns> + private static ServiceAction GetGetAuthorizationDeniedUpdateID() { var action = new ServiceAction { @@ -119,7 +145,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetValidationRevokedUpdateID() + /// <summary> + /// Returns the action details for "GetValidationRevokedUpdateID". + /// </summary> + /// <returns>The <see cref="ServiceAction"/>.</returns> + private static ServiceAction GetGetValidationRevokedUpdateID() { var action = new ServiceAction { @@ -135,7 +165,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetAuthorizationGrantedUpdateID() + /// <summary> + /// Returns the action details for "GetAuthorizationGrantedUpdateID". + /// </summary> + /// <returns>The <see cref="ServiceAction"/>.</returns> + private static ServiceAction GetGetAuthorizationGrantedUpdateID() { var action = new ServiceAction { diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 6abc3a82c3..c97acdb026 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -4,12 +4,13 @@ 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.Server; using Emby.Dlna.Ssdp; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -19,24 +20,48 @@ namespace Emby.Dlna.PlayTo { public class Device : IDisposable { - #region Fields & Properties + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + 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<PlaybackStartEventArgs> PlaybackStart; + + public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress; + + public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped; + + public event EventHandler<MediaChangedEventArgs> MediaChanged; public DeviceInfo Properties { get; set; } - private int _muteVol; public bool IsMuted { get; set; } - private int _volume; - public int Volume { get { - RefreshVolumeIfNeeded(); + RefreshVolumeIfNeeded().GetAwaiter().GetResult(); return _volume; } + set => _volume = value; } @@ -44,29 +69,21 @@ namespace Emby.Dlna.PlayTo public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0); - public TRANSPORTSTATE TransportState { get; private set; } + public TransportState TransportState { get; private set; } - public bool IsPlaying => TransportState == TRANSPORTSTATE.PLAYING; + public bool IsPlaying => TransportState == TransportState.Playing; - public bool IsPaused => TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK; + public bool IsPaused => TransportState == TransportState.Paused || TransportState == TransportState.PausedPlayback; - public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED; + public bool IsStopped => TransportState == TransportState.Stopped; - #endregion + public Action OnDeviceUnavailable { get; set; } - private readonly IHttpClient _httpClient; - private readonly ILogger _logger; - private readonly IServerConfigurationManager _config; + private TransportCommands AvCommands { get; set; } - public Action OnDeviceUnavailable { get; set; } + private TransportCommands RendererCommands { get; set; } - public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config) - { - Properties = deviceProperties; - _httpClient = httpClient; - _logger = logger; - _config = config; - } + public UBaseObject CurrentMediaInfo { get; private set; } public void Start() { @@ -74,26 +91,24 @@ namespace Emby.Dlna.PlayTo _timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite); } - private DateTime _lastVolumeRefresh; - private bool _volumeRefreshActive; - private void RefreshVolumeIfNeeded() + private Task RefreshVolumeIfNeeded() { - if (!_volumeRefreshActive) - { - return; - } - - if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5)) + if (_volumeRefreshActive + && DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5)) { _lastVolumeRefresh = DateTime.UtcNow; - RefreshVolume(CancellationToken.None); + return RefreshVolume(); } + + return Task.CompletedTask; } - private async void RefreshVolume(CancellationToken cancellationToken) + private async Task RefreshVolume(CancellationToken cancellationToken = default) { if (_disposed) + { return; + } try { @@ -106,7 +121,6 @@ namespace Emby.Dlna.PlayTo } } - private readonly object _timerLock = new object(); private void RestartTimer(bool immediate = false) { lock (_timerLock) @@ -141,8 +155,6 @@ namespace Emby.Dlna.PlayTo } } - #region Commanding - public Task VolumeDown(CancellationToken cancellationToken) { var sendVolume = Math.Max(Volume - 5, 0); @@ -211,7 +223,9 @@ namespace Emby.Dlna.PlayTo var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); if (command == null) + { return false; + } var service = GetServiceRenderingControl(); @@ -223,7 +237,7 @@ namespace Emby.Dlna.PlayTo _logger.LogDebug("Setting mute"); var value = mute ? 1 : 0; - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) .ConfigureAwait(false); IsMuted = mute; @@ -232,15 +246,20 @@ namespace Emby.Dlna.PlayTo } /// <summary> - /// Sets volume on a scale of 0-100 + /// Sets volume on a scale of 0-100. /// </summary> + /// <param name="value">The volume on a scale of 0-100.</param> + /// <param name="cancellationToken">The cancellation token to cancel operation.</param> + /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> 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 == null) + { return; + } var service = GetServiceRenderingControl(); @@ -253,7 +272,7 @@ namespace Emby.Dlna.PlayTo // Remote control will perform better Volume = value; - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) .ConfigureAwait(false); } @@ -263,7 +282,9 @@ namespace Emby.Dlna.PlayTo var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); if (command == null) + { return; + } var service = GetAvTransportService(); @@ -272,7 +293,7 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME")) + await new SsdpHttpClient(_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")) .ConfigureAwait(false); RestartTimer(true); @@ -282,18 +303,20 @@ namespace Emby.Dlna.PlayTo { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - url = url.Replace("&", "&"); + 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 == null) + { return; + } var dictionary = new Dictionary<string, string> { - {"CurrentURI", url}, - {"CurrentURIMetaData", CreateDidlMeta(metaData)} + { "CurrentURI", url }, + { "CurrentURIMetaData", CreateDidlMeta(metaData) } }; var service = GetAvTransportService(); @@ -304,7 +327,7 @@ namespace Emby.Dlna.PlayTo } var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header) .ConfigureAwait(false); await Task.Delay(50).ConfigureAwait(false); @@ -329,7 +352,7 @@ namespace Emby.Dlna.PlayTo return string.Empty; } - return DescriptionXmlBuilder.Escape(value); + return SecurityElement.Escape(value); } private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken) @@ -346,7 +369,7 @@ namespace Emby.Dlna.PlayTo throw new InvalidOperationException("Unable to find service"); } - return new SsdpHttpClient(_httpClient).SendCommandAsync( + return new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -375,7 +398,7 @@ namespace Emby.Dlna.PlayTo var service = GetAvTransportService(); - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); RestartTimer(true); @@ -393,19 +416,14 @@ namespace Emby.Dlna.PlayTo var service = GetAvTransportService(); - await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); - TransportState = TRANSPORTSTATE.PAUSED; + TransportState = TransportState.Paused; RestartTimer(true); } - #endregion - - #region Get data - - private int _connectFailureCount; private async void TimerCallback(object sender) { if (_disposed) @@ -434,7 +452,7 @@ namespace Emby.Dlna.PlayTo if (transportState.HasValue) { // If we're not playing anything no need to get additional data - if (transportState.Value == TRANSPORTSTATE.STOPPED) + if (transportState.Value == TransportState.Stopped) { UpdateMediaInfo(null, transportState.Value); } @@ -458,10 +476,12 @@ namespace Emby.Dlna.PlayTo _connectFailureCount = 0; if (_disposed) + { return; + } // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive - if (transportState.Value == TRANSPORTSTATE.STOPPED) + if (transportState.Value == TransportState.Stopped) { RestartTimerInactive(); } @@ -478,7 +498,9 @@ namespace Emby.Dlna.PlayTo catch (Exception ex) { if (_disposed) + { return; + } _logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name); @@ -494,6 +516,7 @@ namespace Emby.Dlna.PlayTo return; } } + RestartTimerInactive(); } } @@ -520,7 +543,7 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -532,7 +555,7 @@ namespace Emby.Dlna.PlayTo return; } - var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null); + var volume = result.Document.Descendants(UPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null); var volumeValue = volume?.Value; if (string.IsNullOrWhiteSpace(volumeValue)) @@ -570,7 +593,7 @@ namespace Emby.Dlna.PlayTo return; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -578,16 +601,18 @@ namespace Emby.Dlna.PlayTo cancellationToken: cancellationToken).ConfigureAwait(false); if (result == null || result.Document == null) + { return; + } - var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse") + var valueNode = result.Document.Descendants(UPnpNamespaces.RenderingControl + "GetMuteResponse") .Select(i => i.Element("CurrentMute")) .FirstOrDefault(i => i != null); IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase); } - private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken) + private async Task<TransportState?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo"); if (command == null) @@ -601,7 +626,7 @@ namespace Emby.Dlna.PlayTo return null; } - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -614,12 +639,12 @@ namespace Emby.Dlna.PlayTo } var transportState = - result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null); + result.Document.Descendants(UPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null); var transportStateValue = transportState?.Value; if (transportStateValue != null - && Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state)) + && Enum.TryParse(transportStateValue, true, out TransportState state)) { return state; } @@ -627,7 +652,7 @@ namespace Emby.Dlna.PlayTo return null; } - private async Task<uBaseObject> GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken) + private async Task<UBaseObject> GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo"); if (command == null) @@ -643,7 +668,7 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -662,7 +687,7 @@ namespace Emby.Dlna.PlayTo return null; } - var e = track.Element(uPnpNamespaces.items) ?? track; + var e = track.Element(UPnpNamespaces.Items) ?? track; var elementString = (string)e; @@ -678,13 +703,13 @@ namespace Emby.Dlna.PlayTo return null; } - e = track.Element(uPnpNamespaces.items) ?? track; + e = track.Element(UPnpNamespaces.Items) ?? track; elementString = (string)e; if (!string.IsNullOrWhiteSpace(elementString)) { - return new uBaseObject + return new UBaseObject { Url = elementString }; @@ -693,7 +718,7 @@ namespace Emby.Dlna.PlayTo return null; } - private async Task<(bool, uBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken) + private async Task<(bool, UBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo"); if (command == null) @@ -710,7 +735,7 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, command.Name, @@ -722,11 +747,11 @@ namespace Emby.Dlna.PlayTo return (false, null); } - var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null); - var trackUri = trackUriElem == null ? null : trackUriElem.Value; + var trackUriElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null); + var trackUri = trackUriElem?.Value; - var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null); - var duration = durationElem == null ? null : durationElem.Value; + var durationElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null); + var duration = durationElem?.Value; if (!string.IsNullOrWhiteSpace(duration) && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) @@ -738,8 +763,8 @@ namespace Emby.Dlna.PlayTo Duration = null; } - var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null); - var position = positionElem == null ? null : positionElem.Value; + var positionElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null); + var position = positionElem?.Value; if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { @@ -750,7 +775,7 @@ namespace Emby.Dlna.PlayTo if (track == null) { - //If track is null, some vendors do this, use GetMediaInfo instead + // If track is null, some vendors do this, use GetMediaInfo instead return (true, null); } @@ -778,7 +803,7 @@ namespace Emby.Dlna.PlayTo return (true, null); } - var e = uPnpResponse.Element(uPnpNamespaces.items); + var e = uPnpResponse.Element(UPnpNamespaces.Items); var uTrack = CreateUBaseObject(e, trackUri); @@ -794,7 +819,6 @@ namespace Emby.Dlna.PlayTo } catch (XmlException) { - } // first try to add a root node with a dlna namesapce @@ -806,43 +830,41 @@ namespace Emby.Dlna.PlayTo } catch (XmlException) { - } // some devices send back invalid xml try { - return XElement.Parse(xml.Replace("&", "&")); + return XElement.Parse(xml.Replace("&", "&", StringComparison.Ordinal)); } catch (XmlException) { - } return null; } - private static uBaseObject CreateUBaseObject(XElement container, string trackUri) + private static UBaseObject CreateUBaseObject(XElement container, string trackUri) { if (container == null) { throw new ArgumentNullException(nameof(container)); } - var url = container.GetValue(uPnpNamespaces.Res); + var url = container.GetValue(UPnpNamespaces.Res); if (string.IsNullOrWhiteSpace(url)) { url = trackUri; } - return new uBaseObject + return new UBaseObject { - Id = container.GetAttributeValue(uPnpNamespaces.Id), - ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId), - Title = container.GetValue(uPnpNamespaces.title), - IconUrl = container.GetValue(uPnpNamespaces.Artwork), - SecondText = "", + 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() @@ -856,11 +878,11 @@ namespace Emby.Dlna.PlayTo throw new ArgumentNullException(nameof(container)); } - var resElement = container.Element(uPnpNamespaces.Res); + var resElement = container.Element(UPnpNamespaces.Res); if (resElement != null) { - var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo); + var info = resElement.Attribute(UPnpNamespaces.ProtocolInfo); if (info != null && !string.IsNullOrWhiteSpace(info.Value)) { @@ -871,10 +893,6 @@ namespace Emby.Dlna.PlayTo return new string[4]; } - #endregion - - #region From XML - private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken) { if (AvCommands != null) @@ -895,7 +913,7 @@ namespace Emby.Dlna.PlayTo string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); - var httpClient = new SsdpHttpClient(_httpClient); + var httpClient = new SsdpHttpClient(_httpClientFactory); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); @@ -923,7 +941,7 @@ namespace Emby.Dlna.PlayTo string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); - var httpClient = new SsdpHttpClient(_httpClient); + var httpClient = new SsdpHttpClient(_httpClientFactory); _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); @@ -939,12 +957,12 @@ namespace Emby.Dlna.PlayTo return url; } - if (!url.Contains("/")) + if (!url.Contains('/', StringComparison.Ordinal)) { url = "/dmr/" + url; } - if (!url.StartsWith("/")) + if (!url.StartsWith("/", StringComparison.Ordinal)) { url = "/" + url; } @@ -952,25 +970,21 @@ namespace Emby.Dlna.PlayTo return baseUrl + url; } - private TransportCommands AvCommands { get; set; } - - private TransportCommands RendererCommands { get; set; } - - public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken) + public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken) { - var ssdpHttpClient = new SsdpHttpClient(httpClient); + var ssdpHttpClient = new SsdpHttpClient(httpClientFactory); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); var friendlyNames = new List<string>(); - var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault(); + var name = document.Descendants(UPnpNamespaces.Ud.GetName("friendlyName")).FirstOrDefault(); if (name != null && !string.IsNullOrWhiteSpace(name.Value)) { friendlyNames.Add(name.Value); } - var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault(); + var room = document.Descendants(UPnpNamespaces.Ud.GetName("roomName")).FirstOrDefault(); if (room != null && !string.IsNullOrWhiteSpace(room.Value)) { friendlyNames.Add(room.Value); @@ -979,77 +993,77 @@ namespace Emby.Dlna.PlayTo var deviceProperties = new DeviceInfo() { Name = string.Join(" ", friendlyNames), - BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port) + BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port) }; - var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault(); + var model = document.Descendants(UPnpNamespaces.Ud.GetName("modelName")).FirstOrDefault(); if (model != null) { deviceProperties.ModelName = model.Value; } - var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault(); + var modelNumber = document.Descendants(UPnpNamespaces.Ud.GetName("modelNumber")).FirstOrDefault(); if (modelNumber != null) { deviceProperties.ModelNumber = modelNumber.Value; } - var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault(); + var uuid = document.Descendants(UPnpNamespaces.Ud.GetName("UDN")).FirstOrDefault(); if (uuid != null) { deviceProperties.UUID = uuid.Value; } - var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault(); + var manufacturer = document.Descendants(UPnpNamespaces.Ud.GetName("manufacturer")).FirstOrDefault(); if (manufacturer != null) { deviceProperties.Manufacturer = manufacturer.Value; } - var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault(); + var manufacturerUrl = document.Descendants(UPnpNamespaces.Ud.GetName("manufacturerURL")).FirstOrDefault(); if (manufacturerUrl != null) { deviceProperties.ManufacturerUrl = manufacturerUrl.Value; } - var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault(); + var presentationUrl = document.Descendants(UPnpNamespaces.Ud.GetName("presentationURL")).FirstOrDefault(); if (presentationUrl != null) { deviceProperties.PresentationUrl = presentationUrl.Value; } - var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault(); + var modelUrl = document.Descendants(UPnpNamespaces.Ud.GetName("modelURL")).FirstOrDefault(); if (modelUrl != null) { deviceProperties.ModelUrl = modelUrl.Value; } - var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault(); + var serialNumber = document.Descendants(UPnpNamespaces.Ud.GetName("serialNumber")).FirstOrDefault(); if (serialNumber != null) { deviceProperties.SerialNumber = serialNumber.Value; } - var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault(); + var modelDescription = document.Descendants(UPnpNamespaces.Ud.GetName("modelDescription")).FirstOrDefault(); if (modelDescription != null) { deviceProperties.ModelDescription = modelDescription.Value; } - var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault(); + var icon = document.Descendants(UPnpNamespaces.Ud.GetName("icon")).FirstOrDefault(); if (icon != null) { deviceProperties.Icon = CreateIcon(icon); } - foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList"))) + foreach (var services in document.Descendants(UPnpNamespaces.Ud.GetName("serviceList"))) { if (services == null) { continue; } - var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service")); + var servicesList = services.Descendants(UPnpNamespaces.Ud.GetName("service")); if (servicesList == null) { continue; @@ -1066,12 +1080,9 @@ namespace Emby.Dlna.PlayTo } } - return new Device(deviceProperties, httpClient, logger, config); + return new Device(deviceProperties, httpClientFactory, logger); } - #endregion - - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static DeviceIcon CreateIcon(XElement element) { if (element == null) @@ -1079,11 +1090,11 @@ namespace Emby.Dlna.PlayTo throw new ArgumentNullException(nameof(element)); } - var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype")); - var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width")); - var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height")); - var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth")); - var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url")); + var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")); + var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width")); + var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height")); + var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")); + var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")); var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture); var heightValue = int.Parse(height, NumberStyles.Integer, UsCulture); @@ -1100,11 +1111,11 @@ namespace Emby.Dlna.PlayTo private static DeviceService Create(XElement element) { - var type = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceType")); - var id = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceId")); - var scpdUrl = element.GetDescendantValue(uPnpNamespaces.ud.GetName("SCPDURL")); - var controlURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("controlURL")); - var eventSubURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("eventSubURL")); + var type = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")); + var id = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")); + var scpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")); + var controlURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")); + var eventSubURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")); return new DeviceService { @@ -1116,14 +1127,7 @@ namespace Emby.Dlna.PlayTo }; } - public event EventHandler<PlaybackStartEventArgs> PlaybackStart; - public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress; - public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped; - public event EventHandler<MediaChangedEventArgs> MediaChanged; - - public uBaseObject CurrentMediaInfo { get; private set; } - - private void UpdateMediaInfo(uBaseObject mediaInfo, TRANSPORTSTATE state) + private void UpdateMediaInfo(UBaseObject mediaInfo, TransportState state) { TransportState = state; @@ -1132,7 +1136,7 @@ namespace Emby.Dlna.PlayTo if (previousMediaInfo == null && mediaInfo != null) { - if (state != TRANSPORTSTATE.STOPPED) + if (state != TransportState.Stopped) { OnPlaybackStart(mediaInfo); } @@ -1151,7 +1155,7 @@ namespace Emby.Dlna.PlayTo } } - private void OnPlaybackStart(uBaseObject mediaInfo) + private void OnPlaybackStart(UBaseObject mediaInfo) { if (string.IsNullOrWhiteSpace(mediaInfo.Url)) { @@ -1164,7 +1168,7 @@ namespace Emby.Dlna.PlayTo }); } - private void OnPlaybackProgress(uBaseObject mediaInfo) + private void OnPlaybackProgress(UBaseObject mediaInfo) { if (string.IsNullOrWhiteSpace(mediaInfo.Url)) { @@ -1177,7 +1181,7 @@ namespace Emby.Dlna.PlayTo }); } - private void OnPlaybackStop(uBaseObject mediaInfo) + private void OnPlaybackStop(UBaseObject mediaInfo) { PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs { @@ -1185,7 +1189,7 @@ namespace Emby.Dlna.PlayTo }); } - private void OnMediaChanged(uBaseObject old, uBaseObject newMedia) + private void OnMediaChanged(UBaseObject old, UBaseObject newMedia) { MediaChanged?.Invoke(this, new MediaChangedEventArgs { @@ -1194,16 +1198,17 @@ namespace Emby.Dlna.PlayTo }); } - #region IDisposable - - bool _disposed; - + /// <inheritdoc /> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> protected virtual void Dispose(bool disposing) { if (_disposed) @@ -1222,11 +1227,10 @@ namespace Emby.Dlna.PlayTo _disposed = true; } - #endregion - + /// <inheritdoc /> public override string ToString() { - return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl); + 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 index f3aaaebc4a..d3daab9e0a 100644 --- a/Emby.Dlna/PlayTo/DeviceInfo.cs +++ b/Emby.Dlna/PlayTo/DeviceInfo.cs @@ -8,6 +8,9 @@ namespace Emby.Dlna.PlayTo { public class DeviceInfo { + private readonly List<DeviceService> _services = new List<DeviceService>(); + private string _baseUrl = string.Empty; + public DeviceInfo() { Name = "Generic Device"; @@ -33,7 +36,6 @@ namespace Emby.Dlna.PlayTo public string PresentationUrl { get; set; } - private string _baseUrl = string.Empty; public string BaseUrl { get => _baseUrl; @@ -42,7 +44,6 @@ namespace Emby.Dlna.PlayTo public DeviceIcon Icon { get; set; } - private readonly List<DeviceService> _services = new List<DeviceService>(); public List<DeviceService> Services => _services; public DeviceIdentification ToDeviceIdentification() diff --git a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs new file mode 100644 index 0000000000..dabd079afd --- /dev/null +++ b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs @@ -0,0 +1,13 @@ +#pragma warning disable CS1591 + +using System; + +namespace Emby.Dlna.PlayTo +{ + public class MediaChangedEventArgs : EventArgs + { + public UBaseObject OldMediaInfo { get; set; } + + public UBaseObject NewMediaInfo { get; set; } + } +} diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 9d7c0d3659..a5b8e2b3ce 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -7,6 +7,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -17,11 +19,11 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; 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 { @@ -29,7 +31,6 @@ namespace Emby.Dlna.PlayTo { private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); - private Device _device; private readonly SessionInfo _session; private readonly ISessionManager _sessionManager; private readonly ILibraryManager _libraryManager; @@ -48,6 +49,7 @@ namespace Emby.Dlna.PlayTo private readonly string _accessToken; private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>(); + private Device _device; private int _currentPlaylistIndex; private bool _disposed; @@ -146,11 +148,14 @@ namespace Emby.Dlna.PlayTo { var positionTicks = GetProgressPositionTicks(streamInfo); - ReportPlaybackStopped(streamInfo, positionTicks); + await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); } streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item == null) return; + if (streamInfo.Item == null) + { + return; + } var newItemProgress = GetProgressInfo(streamInfo); @@ -173,11 +178,14 @@ namespace Emby.Dlna.PlayTo { var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item == null) return; + if (streamInfo.Item == null) + { + return; + } var positionTicks = GetProgressPositionTicks(streamInfo); - ReportPlaybackStopped(streamInfo, positionTicks); + await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); @@ -185,7 +193,7 @@ namespace Emby.Dlna.PlayTo (_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) : mediaSource.RunTimeTicks; - var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0); + var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0; if (!playedToCompletion && duration.HasValue && positionTicks.HasValue) { @@ -210,7 +218,7 @@ namespace Emby.Dlna.PlayTo } } - private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) + private async Task ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) { try { @@ -220,7 +228,6 @@ namespace Emby.Dlna.PlayTo SessionId = _session.Id, PositionTicks = positionTicks, MediaSourceId = streamInfo.MediaSourceId - }).ConfigureAwait(false); } catch (Exception ex) @@ -365,8 +372,13 @@ namespace Emby.Dlna.PlayTo if (!command.ControllingUserId.Equals(Guid.Empty)) { - _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId, - _session.DeviceName, _session.RemoteEndPoint, user); + _sessionManager.LogSessionActivity( + _session.Client, + _session.ApplicationVersion, + _session.DeviceId, + _session.DeviceName, + _session.RemoteEndPoint, + user); } return PlayItems(playlist, cancellationToken); @@ -418,6 +430,7 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); return; } + await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); } } @@ -441,7 +454,13 @@ namespace Emby.Dlna.PlayTo } } - private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex) + private PlaylistItem CreatePlaylistItem( + BaseItem item, + User user, + long startPostionTicks, + string mediaSourceId, + int? audioStreamIndex, + int? subtitleStreamIndex) { var deviceInfo = _device.Properties; @@ -484,42 +503,44 @@ namespace Emby.Dlna.PlayTo if (streamInfo.MediaType == DlnaProfileType.Audio) { return new ContentFeatureBuilder(profile) - .BuildAudioHeader(streamInfo.Container, - streamInfo.TargetAudioCodec.FirstOrDefault(), - streamInfo.TargetAudioBitrate, - streamInfo.TargetAudioSampleRate, - streamInfo.TargetAudioChannels, - streamInfo.TargetAudioBitDepth, - streamInfo.IsDirectStream, - streamInfo.RunTimeTicks ?? 0, - streamInfo.TranscodeSeekInfo); + .BuildAudioHeader( + 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 = new ContentFeatureBuilder(profile) - .BuildVideoHeader(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.TargetVideoLevel, - streamInfo.TargetFramerate ?? 0, - streamInfo.TargetPacketLength, - streamInfo.TranscodeSeekInfo, - streamInfo.IsTargetAnamorphic, - streamInfo.IsTargetInterlaced, - streamInfo.TargetRefFrames, - streamInfo.TargetVideoStreamCount, - streamInfo.TargetAudioStreamCount, - streamInfo.TargetVideoCodecTag, - streamInfo.IsTargetAVC); + .BuildVideoHeader( + 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.TargetVideoLevel, + streamInfo.TargetFramerate ?? 0, + streamInfo.TargetPacketLength, + streamInfo.TranscodeSeekInfo, + streamInfo.IsTargetAnamorphic, + streamInfo.IsTargetInterlaced, + streamInfo.TargetRefFrames, + streamInfo.TargetVideoStreamCount, + streamInfo.TargetAudioStreamCount, + streamInfo.TargetVideoCodecTag, + streamInfo.IsTargetAVC); return list.Count == 0 ? null : list[0]; } @@ -619,6 +640,10 @@ namespace Emby.Dlna.PlayTo GC.SuppressFinalize(this); } + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> protected virtual void Dispose(bool disposing) { if (_disposed) @@ -644,68 +669,57 @@ namespace Emby.Dlna.PlayTo private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) { - if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType)) - { - switch (commandType) - { - 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: + 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, _usCulture, out var val)) { - if (command.Arguments.TryGetValue("Index", out string arg)) - { - if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var val)) - { - return SetAudioStreamIndex(val); - } + return SetAudioStreamIndex(val); + } - throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied."); - } + throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied."); + } - throw new ArgumentException("SetAudioStreamIndex argument cannot be null"); - } - case GeneralCommandType.SetSubtitleStreamIndex: + throw new ArgumentException("SetAudioStreamIndex argument cannot be null"); + case GeneralCommandType.SetSubtitleStreamIndex: + if (command.Arguments.TryGetValue("Index", out index)) + { + if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) { - if (command.Arguments.TryGetValue("Index", out string arg)) - { - if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var val)) - { - return SetSubtitleStreamIndex(val); - } + return SetSubtitleStreamIndex(val); + } - throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied."); - } + throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied."); + } - throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null"); - } - case GeneralCommandType.SetVolume: + 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, _usCulture, out var volume)) { - if (command.Arguments.TryGetValue("Volume", out string arg)) - { - if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var volume)) - { - return _device.SetVolume(volume, cancellationToken); - } + return _device.SetVolume(volume, cancellationToken); + } - throw new ArgumentException("Unsupported volume value supplied."); - } + throw new ArgumentException("Unsupported volume value supplied."); + } - throw new ArgumentException("Volume argument cannot be null"); - } - default: - return Task.CompletedTask; - } + throw new ArgumentException("Volume argument cannot be null"); + default: + return Task.CompletedTask; } - - return Task.CompletedTask; } private async Task SetAudioStreamIndex(int? newIndex) @@ -763,7 +777,7 @@ namespace Emby.Dlna.PlayTo const int maxWait = 15000000; const int interval = 500; var currentWait = 0; - while (_device.TransportState != TRANSPORTSTATE.PLAYING && currentWait < maxWait) + while (_device.TransportState != TransportState.Playing && currentWait < maxWait) { await Task.Delay(interval).ConfigureAwait(false); currentWait += interval; @@ -772,8 +786,67 @@ namespace Emby.Dlna.PlayTo await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false); } + private static int? GetIntValue(IReadOnlyDictionary<string, string> 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<string, string> values, string name) + { + var value = values.GetValueOrDefault(name); + + if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) + { + return result; + } + + return 0; + } + + /// <inheritdoc /> + public Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + + if (_device == null) + { + return Task.CompletedTask; + } + + if (name == SessionMessageType.Play) + { + return SendPlayCommand(data as PlayRequest, cancellationToken); + } + + if (name == SessionMessageType.PlayState) + { + return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); + } + + if (name == SessionMessageType.GeneralCommand) + { + return SendGeneralCommand(data as GeneralCommand, cancellationToken); + } + + // Not supported or needed right now + return Task.CompletedTask; + } + private class StreamParams { + private MediaSourceInfo mediaSource; + private IMediaSourceManager _mediaSourceManager; + public Guid ItemId { get; set; } public bool IsDirectStream { get; set; } @@ -785,21 +858,20 @@ namespace Emby.Dlna.PlayTo 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; } - private MediaSourceInfo MediaSource; - - private IMediaSourceManager _mediaSourceManager; public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken) { - if (MediaSource != null) + if (mediaSource != null) { - return MediaSource; + return mediaSource; } var hasMediaSources = Item as IHasMediaSources; @@ -809,9 +881,12 @@ namespace Emby.Dlna.PlayTo return null; } - MediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false); + if (_mediaSourceManager != null) + { + mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false); + } - return MediaSource; + return mediaSource; } private static Guid GetItemId(string url) @@ -883,61 +958,5 @@ namespace Emby.Dlna.PlayTo return request; } } - - private static int? GetIntValue(IReadOnlyDictionary<string, string> 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<string, string> values, string name) - { - var value = values.GetValueOrDefault(name); - - if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) - { - return result; - } - - return 0; - } - - /// <inheritdoc /> - public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken) - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - - if (_device == null) - { - return Task.CompletedTask; - } - - if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) - { - return SendPlayCommand(data as PlayRequest, cancellationToken); - } - - if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) - { - return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); - } - - if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) - { - return SendGeneralCommand(data as GeneralCommand, cancellationToken); - } - - // Not supported or needed right now - return Task.CompletedTask; - } } } diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index bbedd1485c..e93aef3043 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -4,8 +4,10 @@ using System; using System.Globalization; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -16,7 +18,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -33,7 +34,7 @@ namespace Emby.Dlna.PlayTo private readonly IDlnaManager _dlnaManager; private readonly IServerApplicationHost _appHost; private readonly IImageProcessor _imageProcessor; - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IServerConfigurationManager _config; private readonly IUserDataManager _userDataManager; private readonly ILocalizationManager _localization; @@ -46,7 +47,7 @@ namespace Emby.Dlna.PlayTo private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) + public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) { _logger = logger; _sessionManager = sessionManager; @@ -56,7 +57,7 @@ namespace Emby.Dlna.PlayTo _appHost = appHost; _imageProcessor = imageProcessor; _deviceDiscovery = deviceDiscovery; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _config = config; _userDataManager = userDataManager; _localization = localization; @@ -78,17 +79,23 @@ namespace Emby.Dlna.PlayTo var info = e.Argument; - if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty; + if (!info.Headers.TryGetValue("USN", out string usn)) + { + usn = string.Empty; + } - if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty; + if (!info.Headers.TryGetValue("NT", out string nt)) + { + nt = string.Empty; + } string location = info.Location.ToString(); // It has to report that it's a media renderer if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && - nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1) + nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1) { - //_logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location); + // _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location); return; } @@ -112,7 +119,6 @@ namespace Emby.Dlna.PlayTo } catch (OperationCanceledException) { - } catch (Exception ex) { @@ -124,24 +130,21 @@ namespace Emby.Dlna.PlayTo } } - private string GetUuid(string usn) + private static string GetUuid(string usn) { - var found = false; - var index = usn.IndexOf("uuid:", StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - usn = usn.Substring(index); - found = true; - } - index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase); + const string UuidStr = "uuid:"; + const string UuidColonStr = "::"; + + var index = usn.IndexOf(UuidStr, StringComparison.OrdinalIgnoreCase); if (index != -1) { - usn = usn.Substring(0, index); + return usn.Substring(index + UuidStr.Length); } - if (found) + index = usn.IndexOf(UuidColonStr, StringComparison.OrdinalIgnoreCase); + if (index != -1) { - return usn; + usn = usn.Substring(0, index + UuidColonStr.Length); } return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture); @@ -168,7 +171,7 @@ namespace Emby.Dlna.PlayTo if (controller == null) { - var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, cancellationToken).ConfigureAwait(false); + var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false); string deviceName = device.Properties.Name; @@ -184,21 +187,22 @@ namespace Emby.Dlna.PlayTo serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress); } - controller = new PlayToController(sessionInfo, - _sessionManager, - _libraryManager, - _logger, - _dlnaManager, - _userManager, - _imageProcessor, - serverAddress, - null, - _deviceDiscovery, - _userDataManager, - _localization, - _mediaSourceManager, - _config, - _mediaEncoder); + controller = new PlayToController( + sessionInfo, + _sessionManager, + _libraryManager, + _logger, + _dlnaManager, + _userManager, + _imageProcessor, + serverAddress, + null, + _deviceDiscovery, + _userDataManager, + _localization, + _mediaSourceManager, + _config, + _mediaEncoder); sessionInfo.AddController(controller); @@ -211,17 +215,17 @@ namespace Emby.Dlna.PlayTo { PlayableMediaTypes = profile.GetSupportedMediaTypes(), - SupportedCommands = new string[] + SupportedCommands = new[] { - GeneralCommandType.VolumeDown.ToString(), - GeneralCommandType.VolumeUp.ToString(), - GeneralCommandType.Mute.ToString(), - GeneralCommandType.Unmute.ToString(), - GeneralCommandType.ToggleMute.ToString(), - GeneralCommandType.SetVolume.ToString(), - GeneralCommandType.SetAudioStreamIndex.ToString(), - GeneralCommandType.SetSubtitleStreamIndex.ToString(), - GeneralCommandType.PlayMediaSource.ToString() + GeneralCommandType.VolumeDown, + GeneralCommandType.VolumeUp, + GeneralCommandType.Mute, + GeneralCommandType.Unmute, + GeneralCommandType.ToggleMute, + GeneralCommandType.SetVolume, + GeneralCommandType.SetAudioStreamIndex, + GeneralCommandType.SetSubtitleStreamIndex, + GeneralCommandType.PlayMediaSource }, SupportsMediaControl = true @@ -240,9 +244,9 @@ namespace Emby.Dlna.PlayTo { _disposeCancellationTokenSource.Cancel(); } - catch + catch (Exception ex) { - + _logger.LogDebug(ex, "Error while disposing PlayToManager"); } _sessionLock.Dispose(); diff --git a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs index 795618df23..d14617c8a0 100644 --- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs @@ -6,6 +6,6 @@ namespace Emby.Dlna.PlayTo { public class PlaybackProgressEventArgs : EventArgs { - public uBaseObject MediaInfo { get; set; } + public UBaseObject MediaInfo { get; set; } } } diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs index 27883ca32a..3f8d552636 100644 --- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs @@ -6,6 +6,6 @@ namespace Emby.Dlna.PlayTo { public class PlaybackStartEventArgs : EventArgs { - public uBaseObject MediaInfo { get; set; } + public UBaseObject MediaInfo { get; set; } } } diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs index 3b169e5993..deeb47918d 100644 --- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs @@ -6,12 +6,6 @@ namespace Emby.Dlna.PlayTo { public class PlaybackStoppedEventArgs : EventArgs { - public uBaseObject MediaInfo { get; set; } - } - - public class MediaChangedEventArgs : EventArgs - { - public uBaseObject OldMediaInfo { get; set; } - public uBaseObject NewMediaInfo { get; set; } + public UBaseObject MediaInfo { get; set; } } } diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 8c13620075..c8c36fc972 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -4,6 +4,8 @@ using System; using System.Globalization; using System.IO; using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -20,11 +22,11 @@ namespace Emby.Dlna.PlayTo private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly IHttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; - public SsdpHttpClient(IHttpClient httpClient) + public SsdpHttpClient(IHttpClientFactory httpClientFactory) { - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; } public async Task<XDocument> SendCommandAsync( @@ -36,20 +38,18 @@ namespace Emby.Dlna.PlayTo CancellationToken cancellationToken = default) { var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); - using (var response = await PostSoapDataAsync( - url, - $"\"{service.ServiceType}#{command}\"", - postData, - header, - cancellationToken) - .ConfigureAwait(false)) - using (var stream = response.Content) - using (var reader = new StreamReader(stream, Encoding.UTF8)) - { - return XDocument.Parse( - await reader.ReadToEndAsync().ConfigureAwait(false), - LoadOptions.PreserveWhitespace); - } + using var response = await PostSoapDataAsync( + url, + $"\"{service.ServiceType}#{command}\"", + postData, + header, + cancellationToken) + .ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = new StreamReader(stream, Encoding.UTF8); + return XDocument.Parse( + await reader.ReadToEndAsync().ConfigureAwait(false), + LoadOptions.PreserveWhitespace); } private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) @@ -76,50 +76,32 @@ namespace Emby.Dlna.PlayTo int eventport, int timeOut = 3600) { - var options = new HttpRequestOptions - { - Url = url, - UserAgent = USERAGENT, - LogErrorResponseBody = true, - BufferContent = false, - }; - - options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture); - options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"; - options.RequestHeaders["NT"] = "upnp:event"; - options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture); - - using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false)) - { - - } + using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url); + options.Headers.UserAgent.ParseAdd(USERAGENT); + options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture)); + options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"); + options.Headers.TryAddWithoutValidation("NT", "upnp:event"); + options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture)); + + using var response = await _httpClientFactory.CreateClient(NamedClient.Default) + .SendAsync(options, HttpCompletionOption.ResponseHeadersRead) + .ConfigureAwait(false); } public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken) { - var options = new HttpRequestOptions - { - Url = url, - UserAgent = USERAGENT, - LogErrorResponseBody = true, - BufferContent = false, - - CancellationToken = cancellationToken - }; - - options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName; - - using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false)) - using (var stream = response.Content) - using (var reader = new StreamReader(stream, Encoding.UTF8)) - { - return XDocument.Parse( - await reader.ReadToEndAsync().ConfigureAwait(false), - LoadOptions.PreserveWhitespace); - } + using var options = new HttpRequestMessage(HttpMethod.Get, url); + options.Headers.UserAgent.ParseAdd(USERAGENT); + options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); + using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = new StreamReader(stream, Encoding.UTF8); + return XDocument.Parse( + await reader.ReadToEndAsync().ConfigureAwait(false), + LoadOptions.PreserveWhitespace); } - private Task<HttpResponseInfo> PostSoapDataAsync( + private async Task<HttpResponseMessage> PostSoapDataAsync( string url, string soapAction, string postData, @@ -131,29 +113,20 @@ namespace Emby.Dlna.PlayTo soapAction = $"\"{soapAction}\""; } - var options = new HttpRequestOptions - { - Url = url, - UserAgent = USERAGENT, - LogErrorResponseBody = true, - BufferContent = false, - - CancellationToken = cancellationToken - }; - - options.RequestHeaders["SOAPAction"] = soapAction; - options.RequestHeaders["Pragma"] = "no-cache"; - options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName; + using var options = new HttpRequestMessage(HttpMethod.Post, url); + options.Headers.UserAgent.ParseAdd(USERAGENT); + options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction); + options.Headers.TryAddWithoutValidation("Pragma", "no-cache"); + options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName); if (!string.IsNullOrEmpty(header)) { - options.RequestHeaders["contentFeatures.dlna.org"] = header; + options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header); } - options.RequestContentType = "text/xml"; - options.RequestContent = postData; + options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml); - return _httpClient.Post(options); + return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); } } } diff --git a/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs b/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs deleted file mode 100644 index 7daefeca86..0000000000 --- a/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs +++ /dev/null @@ -1,13 +0,0 @@ -#pragma warning disable CS1591 - -namespace Emby.Dlna.PlayTo -{ - public enum TRANSPORTSTATE - { - STOPPED, - PLAYING, - TRANSITIONING, - PAUSED_PLAYBACK, - PAUSED - } -} diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index c0ce3ab6e9..fda17a8b41 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Xml.Linq; using Emby.Dlna.Common; @@ -11,36 +12,30 @@ namespace Emby.Dlna.PlayTo { public class TransportCommands { + private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>"; private List<StateVariable> _stateVariables = new List<StateVariable>(); - public List<StateVariable> StateVariables - { - get => _stateVariables; - set => _stateVariables = value; - } - private List<ServiceAction> _serviceActions = new List<ServiceAction>(); - public List<ServiceAction> ServiceActions - { - get => _serviceActions; - set => _serviceActions = value; - } + + public List<StateVariable> StateVariables => _stateVariables; + + public List<ServiceAction> ServiceActions => _serviceActions; public static TransportCommands Create(XDocument document) { var command = new TransportCommands(); - var actionList = document.Descendants(uPnpNamespaces.svc + "actionList"); + var actionList = document.Descendants(UPnpNamespaces.Svc + "actionList"); - foreach (var container in actionList.Descendants(uPnpNamespaces.svc + "action")) + foreach (var container in actionList.Descendants(UPnpNamespaces.Svc + "action")) { command.ServiceActions.Add(ServiceActionFromXml(container)); } - var stateValues = document.Descendants(uPnpNamespaces.ServiceStateTable).FirstOrDefault(); + var stateValues = document.Descendants(UPnpNamespaces.ServiceStateTable).FirstOrDefault(); if (stateValues != null) { - foreach (var container in stateValues.Elements(uPnpNamespaces.svc + "stateVariable")) + foreach (var container in stateValues.Elements(UPnpNamespaces.Svc + "stateVariable")) { command.StateVariables.Add(FromXml(container)); } @@ -51,19 +46,19 @@ namespace Emby.Dlna.PlayTo private static ServiceAction ServiceActionFromXml(XElement container) { - var argumentList = new List<Argument>(); + var serviceAction = new ServiceAction + { + Name = container.GetValue(UPnpNamespaces.Svc + "name"), + }; - foreach (var arg in container.Descendants(uPnpNamespaces.svc + "argument")) + var argumentList = serviceAction.ArgumentList; + + foreach (var arg in container.Descendants(UPnpNamespaces.Svc + "argument")) { argumentList.Add(ArgumentFromXml(arg)); } - return new ServiceAction - { - Name = container.GetValue(uPnpNamespaces.svc + "name"), - - ArgumentList = argumentList - }; + return serviceAction; } private static Argument ArgumentFromXml(XElement container) @@ -75,29 +70,29 @@ namespace Emby.Dlna.PlayTo return new Argument { - Name = container.GetValue(uPnpNamespaces.svc + "name"), - Direction = container.GetValue(uPnpNamespaces.svc + "direction"), - RelatedStateVariable = container.GetValue(uPnpNamespaces.svc + "relatedStateVariable") + Name = container.GetValue(UPnpNamespaces.Svc + "name"), + Direction = container.GetValue(UPnpNamespaces.Svc + "direction"), + RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") }; } private static StateVariable FromXml(XElement container) { var allowedValues = new List<string>(); - var element = container.Descendants(uPnpNamespaces.svc + "allowedValueList") + var element = container.Descendants(UPnpNamespaces.Svc + "allowedValueList") .FirstOrDefault(); if (element != null) { - var values = element.Descendants(uPnpNamespaces.svc + "allowedValue"); + var values = element.Descendants(UPnpNamespaces.Svc + "allowedValue"); allowedValues.AddRange(values.Select(child => child.Value)); } return new StateVariable { - Name = container.GetValue(uPnpNamespaces.svc + "name"), - DataType = container.GetValue(uPnpNamespaces.svc + "dataType"), + Name = container.GetValue(UPnpNamespaces.Svc + "name"), + DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"), AllowedValues = allowedValues.ToArray() }; } @@ -123,7 +118,7 @@ namespace Emby.Dlna.PlayTo } } - return string.Format(CommandBase, action.Name, xmlNamespace, stateString); + return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString); } public string BuildPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "") @@ -147,7 +142,7 @@ namespace Emby.Dlna.PlayTo } } - return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); + return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString); } public string BuildPost(ServiceAction action, string xmlNamesapce, object value, Dictionary<string, string> dictionary) @@ -170,7 +165,7 @@ namespace Emby.Dlna.PlayTo } } - return string.Format(CommandBase, action.Name, xmlNamesapce, stateString); + return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString); } private string BuildArgumentXml(Argument argument, string value, string commandParameter = "") @@ -180,15 +175,12 @@ namespace Emby.Dlna.PlayTo if (state != null) { var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ?? - state.AllowedValues.FirstOrDefault() ?? - value; + (state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value); - return string.Format("<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue); + return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue); } - return string.Format("<{0}>{1}</{0}>", argument.Name, value); + return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value); } - - private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>"; } } diff --git a/Emby.Dlna/PlayTo/TransportState.cs b/Emby.Dlna/PlayTo/TransportState.cs new file mode 100644 index 0000000000..7068a5d24d --- /dev/null +++ b/Emby.Dlna/PlayTo/TransportState.cs @@ -0,0 +1,14 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1602 + +namespace Emby.Dlna.PlayTo +{ + public enum TransportState + { + Stopped, + Playing, + Transitioning, + PausedPlayback, + Paused + } +} diff --git a/Emby.Dlna/PlayTo/UpnpContainer.cs b/Emby.Dlna/PlayTo/UpnpContainer.cs index e2d7a10f02..05f27603fb 100644 --- a/Emby.Dlna/PlayTo/UpnpContainer.cs +++ b/Emby.Dlna/PlayTo/UpnpContainer.cs @@ -6,22 +6,22 @@ using Emby.Dlna.Ssdp; namespace Emby.Dlna.PlayTo { - public class UpnpContainer : uBaseObject + public class UpnpContainer : UBaseObject { - public static uBaseObject Create(XElement container) + public static UBaseObject Create(XElement container) { if (container == null) { throw new ArgumentNullException(nameof(container)); } - return new uBaseObject + 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.uClass) + 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 index a8ed5692c9..0d9478e42e 100644 --- a/Emby.Dlna/PlayTo/uBaseObject.cs +++ b/Emby.Dlna/PlayTo/uBaseObject.cs @@ -1,10 +1,11 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; namespace Emby.Dlna.PlayTo { - public class uBaseObject + public class UBaseObject { public string Id { get; set; } @@ -20,20 +21,10 @@ namespace Emby.Dlna.PlayTo public string Url { get; set; } - public string[] ProtocolInfo { get; set; } + public IReadOnlyList<string> ProtocolInfo { get; set; } public string UpnpClass { get; set; } - public bool Equals(uBaseObject obj) - { - if (obj == null) - { - throw new ArgumentNullException(nameof(obj)); - } - - return string.Equals(Id, obj.Id); - } - public string MediaType { get @@ -44,10 +35,12 @@ namespace Emby.Dlna.PlayTo { return MediaBrowser.Model.Entities.MediaType.Audio; } + if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1) { return MediaBrowser.Model.Entities.MediaType.Video; } + if (classType.IndexOf("image", StringComparison.Ordinal) != -1) { return MediaBrowser.Model.Entities.MediaType.Photo; @@ -56,5 +49,15 @@ namespace Emby.Dlna.PlayTo return null; } } + + public bool Equals(UBaseObject obj) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + + return string.Equals(Id, obj.Id, StringComparison.Ordinal); + } } } diff --git a/Emby.Dlna/PlayTo/uPnpNamespaces.cs b/Emby.Dlna/PlayTo/uPnpNamespaces.cs index dc65cdf43c..5042d44938 100644 --- a/Emby.Dlna/PlayTo/uPnpNamespaces.cs +++ b/Emby.Dlna/PlayTo/uPnpNamespaces.cs @@ -4,38 +4,64 @@ using System.Xml.Linq; namespace Emby.Dlna.PlayTo { - public class uPnpNamespaces + public static class UPnpNamespaces { - public static XNamespace dc = "http://purl.org/dc/elements/1.1/"; - public static XNamespace ns = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; - public static XNamespace svc = "urn:schemas-upnp-org:service-1-0"; - public static XNamespace ud = "urn:schemas-upnp-org:device-1-0"; - public static XNamespace upnp = "urn:schemas-upnp-org:metadata-1-0/upnp/"; - public static XNamespace RenderingControl = "urn:schemas-upnp-org:service:RenderingControl:1"; - public static XNamespace AvTransport = "urn:schemas-upnp-org:service:AVTransport:1"; - public static XNamespace ContentDirectory = "urn:schemas-upnp-org:service:ContentDirectory:1"; - - public static XName containers = ns + "container"; - public static XName items = ns + "item"; - public static XName title = dc + "title"; - public static XName creator = dc + "creator"; - public static XName artist = upnp + "artist"; - public static XName Id = "id"; - public static XName ParentId = "parentID"; - public static XName uClass = upnp + "class"; - public static XName Artwork = upnp + "albumArtURI"; - public static XName Description = dc + "description"; - public static XName LongDescription = upnp + "longDescription"; - public static XName Album = upnp + "album"; - public static XName Author = upnp + "author"; - public static XName Director = upnp + "director"; - public static XName PlayCount = upnp + "playbackCount"; - public static XName Tracknumber = upnp + "originalTrackNumber"; - public static XName Res = ns + "res"; - public static XName Duration = "duration"; - public static XName ProtocolInfo = "protocolInfo"; - - public static XName ServiceStateTable = svc + "serviceStateTable"; - public static XName StateVariable = svc + "stateVariable"; + 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 index 2347ebd0d3..d4af72b626 100644 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ b/Emby.Dlna/Profiles/DefaultProfile.cs @@ -64,14 +64,14 @@ namespace Emby.Dlna.Profiles new DirectPlayProfile { // play all - Container = "", + Container = string.Empty, Type = DlnaProfileType.Video }, new DirectPlayProfile { // play all - Container = "", + Container = string.Empty, Type = DlnaProfileType.Audio } }; @@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles public void AddXmlRootAttribute(string name, string value) { - var atts = XmlRootAttributes ?? new XmlAttribute[] { }; + var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>(); var list = atts.ToList(); list.Add(new XmlAttribute diff --git a/Emby.Dlna/Profiles/DenonAvrProfile.cs b/Emby.Dlna/Profiles/DenonAvrProfile.cs index 73a87c499e..a5ba0f36cc 100644 --- a/Emby.Dlna/Profiles/DenonAvrProfile.cs +++ b/Emby.Dlna/Profiles/DenonAvrProfile.cs @@ -28,7 +28,7 @@ namespace Emby.Dlna.Profiles }, }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = System.Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/DirectTvProfile.cs b/Emby.Dlna/Profiles/DirectTvProfile.cs index 5ca388167b..f6f98b07d8 100644 --- a/Emby.Dlna/Profiles/DirectTvProfile.cs +++ b/Emby.Dlna/Profiles/DirectTvProfile.cs @@ -123,7 +123,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = System.Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs index 942e369309..2a7524a6a2 100644 --- a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs +++ b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs @@ -24,7 +24,7 @@ namespace Emby.Dlna.Profiles { Match = HeaderMatchType.Substring, Name = "User-Agent", - Value ="Zip_" + Value = "Zip_" } } }; @@ -81,7 +81,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -124,7 +124,7 @@ namespace Emby.Dlna.Profiles new CodecProfile { Type = CodecType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -161,7 +161,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "ac3,he-aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -177,7 +177,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -192,7 +192,7 @@ namespace Emby.Dlna.Profiles new CodecProfile { Type = CodecType.VideoAudio, - Conditions = new [] + Conditions = new[] { // The device does not have any audio switching capabilities new ProfileCondition diff --git a/Emby.Dlna/Profiles/Foobar2000Profile.cs b/Emby.Dlna/Profiles/Foobar2000Profile.cs index ea3de686a6..68e959770a 100644 --- a/Emby.Dlna/Profiles/Foobar2000Profile.cs +++ b/Emby.Dlna/Profiles/Foobar2000Profile.cs @@ -72,7 +72,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = System.Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/LgTvProfile.cs b/Emby.Dlna/Profiles/LgTvProfile.cs index 02301764c0..fbb368d3e3 100644 --- a/Emby.Dlna/Profiles/LgTvProfile.cs +++ b/Emby.Dlna/Profiles/LgTvProfile.cs @@ -84,7 +84,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -191,7 +191,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] + ResponseProfiles = new[] { new ResponseProfile { diff --git a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs index 1b1423520c..1a510dfecf 100644 --- a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs +++ b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs @@ -32,7 +32,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] + ResponseProfiles = new[] { new ResponseProfile { diff --git a/Emby.Dlna/Profiles/MarantzProfile.cs b/Emby.Dlna/Profiles/MarantzProfile.cs index 6cfcc3b824..24078ab29c 100644 --- a/Emby.Dlna/Profiles/MarantzProfile.cs +++ b/Emby.Dlna/Profiles/MarantzProfile.cs @@ -37,7 +37,7 @@ namespace Emby.Dlna.Profiles }, }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = System.Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs index 7161af7386..28d639b6db 100644 --- a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs +++ b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System; using MediaBrowser.Model.Dlna; namespace Emby.Dlna.Profiles @@ -37,7 +38,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs index 44c35e1425..0d536acf39 100644 --- a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs +++ b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs @@ -138,7 +138,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Profiles/PopcornHourProfile.cs b/Emby.Dlna/Profiles/PopcornHourProfile.cs index 9e9f6966fc..7fbf8c1649 100644 --- a/Emby.Dlna/Profiles/PopcornHourProfile.cs +++ b/Emby.Dlna/Profiles/PopcornHourProfile.cs @@ -93,8 +93,8 @@ namespace Emby.Dlna.Profiles new CodecProfile { Type = CodecType.Video, - Codec="h264", - Conditions = new [] + Codec = "h264", + Conditions = new[] { new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline"), new ProfileCondition @@ -122,7 +122,7 @@ namespace Emby.Dlna.Profiles new CodecProfile { Type = CodecType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -150,7 +150,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -166,7 +166,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Audio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -182,7 +182,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Audio, Codec = "mp3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -202,7 +202,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] + ResponseProfiles = new[] { new ResponseProfile { diff --git a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs index 4ff2ab9be2..ddbebba2a4 100644 --- a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs +++ b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs @@ -139,7 +139,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs index 42b066d52b..765c125045 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System; using MediaBrowser.Model.Dlna; namespace Emby.Dlna.Profiles @@ -149,7 +150,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -177,7 +178,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -196,7 +197,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs index fbdf2c18e8..390c8a3e43 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System; using MediaBrowser.Model.Dlna; namespace Emby.Dlna.Profiles @@ -149,7 +150,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -177,7 +178,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -196,7 +197,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs index ce32179a1b..25adc4d022 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System; using MediaBrowser.Model.Dlna; namespace Emby.Dlna.Profiles @@ -137,7 +138,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -165,7 +166,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -184,7 +185,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs index aa1721d398..0a39a5f401 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System; using MediaBrowser.Model.Dlna; namespace Emby.Dlna.Profiles @@ -137,7 +138,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -165,7 +166,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -184,7 +185,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles } }; - ResponseProfiles = new ResponseProfile[] { }; + ResponseProfiles = Array.Empty<ResponseProfile>(); } } } diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs index ecdd2e7a4e..05c8ab1c14 100644 --- a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs +++ b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs @@ -114,7 +114,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -156,7 +156,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -172,7 +172,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -191,7 +191,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -217,7 +217,7 @@ namespace Emby.Dlna.Profiles VideoCodec = "h264,mpeg4,vc1", AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", + OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", Type = DlnaProfileType.Video }, diff --git a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs index 68365ba4ae..8ab4acd1be 100644 --- a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs @@ -102,13 +102,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", + OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -128,13 +128,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/mpeg", - OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", + OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -148,28 +148,28 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", + OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "ts,mpegts", - VideoCodec="mpeg2video", + VideoCodec = "mpeg2video", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", + OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "mpeg", - VideoCodec="mpeg1video,mpeg2video", + VideoCodec = "mpeg1video,mpeg2video", MimeType = "video/mpeg", - OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", + OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL", Type = DlnaProfileType.Video } }; @@ -180,7 +180,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -204,7 +204,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -243,7 +243,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "mpeg2video", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -275,7 +275,7 @@ namespace Emby.Dlna.Profiles new CodecProfile { Type = CodecType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -303,7 +303,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -319,7 +319,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -341,7 +341,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "mp3,mp2", - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs index b34af04a50..42d253394c 100644 --- a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs @@ -120,7 +120,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -143,13 +143,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", + OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -169,13 +169,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/mpeg", - OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", + OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -189,28 +189,28 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", + OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "ts,mpegts", - VideoCodec="mpeg2video", + VideoCodec = "mpeg2video", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", + OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "mpeg", - VideoCodec="mpeg1video,mpeg2video", + VideoCodec = "mpeg1video,mpeg2video", MimeType = "video/mpeg", - OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", + OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL", Type = DlnaProfileType.Video }, new ResponseProfile @@ -227,7 +227,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -266,7 +266,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "mpeg2video", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -298,7 +298,7 @@ namespace Emby.Dlna.Profiles new CodecProfile { Type = CodecType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -326,7 +326,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -364,7 +364,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "mp3,mp2", - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs index 0e75d0cb5e..0598e83422 100644 --- a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs @@ -131,13 +131,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", + OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -157,13 +157,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/mpeg", - OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", + OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -177,28 +177,28 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", + OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "ts,mpegts", - VideoCodec="mpeg2video", + VideoCodec = "mpeg2video", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", + OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "mpeg", - VideoCodec="mpeg1video,mpeg2video", + VideoCodec = "mpeg1video,mpeg2video", MimeType = "video/mpeg", - OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", + OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL", Type = DlnaProfileType.Video }, new ResponseProfile @@ -215,7 +215,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -282,7 +282,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "mp3,mp2", - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs index 3300863c90..3d90a1e72d 100644 --- a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs @@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -187,13 +187,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", + OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -213,13 +213,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/mpeg", - OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", + OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -233,28 +233,28 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", + OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "ts,mpegts", - VideoCodec="mpeg2video", + VideoCodec = "mpeg2video", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", + OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "mpeg", - VideoCodec="mpeg1video,mpeg2video", + VideoCodec = "mpeg1video,mpeg2video", MimeType = "video/mpeg", - OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", + OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL", Type = DlnaProfileType.Video }, new ResponseProfile @@ -265,14 +265,13 @@ namespace Emby.Dlna.Profiles } }; - CodecProfiles = new[] { new CodecProfile { Type = CodecType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -300,7 +299,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "mp3,mp2", - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs index 4e833441cc..9188f73ef1 100644 --- a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs @@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -187,13 +187,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", + OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -213,13 +213,13 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/mpeg", - OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", + OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", Type = DlnaProfileType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -233,28 +233,28 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "ts,mpegts", - VideoCodec="h264", - AudioCodec="ac3,aac,mp3", + VideoCodec = "h264", + AudioCodec = "ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", + OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "ts,mpegts", - VideoCodec="mpeg2video", + VideoCodec = "mpeg2video", MimeType = "video/vnd.dlna.mpeg-tts", - OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", + OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO", Type = DlnaProfileType.Video }, new ResponseProfile { Container = "mpeg", - VideoCodec="mpeg1video,mpeg2video", + VideoCodec = "mpeg1video,mpeg2video", MimeType = "video/mpeg", - OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", + OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL", Type = DlnaProfileType.Video }, new ResponseProfile @@ -265,14 +265,13 @@ namespace Emby.Dlna.Profiles } }; - CodecProfiles = new[] { new CodecProfile { Type = CodecType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -300,7 +299,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "mp3,mp2", - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Profiles/SonyPs3Profile.cs b/Emby.Dlna/Profiles/SonyPs3Profile.cs index 7f72356bdc..d56b1df507 100644 --- a/Emby.Dlna/Profiles/SonyPs3Profile.cs +++ b/Emby.Dlna/Profiles/SonyPs3Profile.cs @@ -108,7 +108,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -133,7 +133,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -176,7 +176,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -201,7 +201,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "wmapro", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -217,7 +217,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -235,7 +235,7 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "mp4,mov", - AudioCodec="aac", + AudioCodec = "aac", MimeType = "video/mp4", Type = DlnaProfileType.Video }, @@ -244,7 +244,7 @@ namespace Emby.Dlna.Profiles { Container = "avi", MimeType = "video/divx", - OrgPn="AVI", + OrgPn = "AVI", Type = DlnaProfileType.Video }, diff --git a/Emby.Dlna/Profiles/SonyPs4Profile.cs b/Emby.Dlna/Profiles/SonyPs4Profile.cs index 411bfe2b0c..db56094e2b 100644 --- a/Emby.Dlna/Profiles/SonyPs4Profile.cs +++ b/Emby.Dlna/Profiles/SonyPs4Profile.cs @@ -110,7 +110,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -135,7 +135,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -178,7 +178,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "ac3", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -203,7 +203,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "wmapro", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -219,7 +219,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -237,7 +237,7 @@ namespace Emby.Dlna.Profiles new ResponseProfile { Container = "mp4,mov", - AudioCodec="aac", + AudioCodec = "aac", MimeType = "video/mp4", Type = DlnaProfileType.Video }, @@ -246,7 +246,7 @@ namespace Emby.Dlna.Profiles { Container = "avi", MimeType = "video/divx", - OrgPn="AVI", + OrgPn = "AVI", Type = DlnaProfileType.Video }, diff --git a/Emby.Dlna/Profiles/WdtvLiveProfile.cs b/Emby.Dlna/Profiles/WdtvLiveProfile.cs index 2de9a8cd9e..937ca0f425 100644 --- a/Emby.Dlna/Profiles/WdtvLiveProfile.cs +++ b/Emby.Dlna/Profiles/WdtvLiveProfile.cs @@ -20,7 +20,7 @@ namespace Emby.Dlna.Profiles Headers = new[] { - new HttpHeaderInfo {Name = "User-Agent", Value = "alphanetworks", Match = HeaderMatchType.Substring}, + new HttpHeaderInfo { Name = "User-Agent", Value = "alphanetworks", Match = HeaderMatchType.Substring }, new HttpHeaderInfo { Name = "User-Agent", @@ -168,7 +168,7 @@ namespace Emby.Dlna.Profiles { Type = DlnaProfileType.Photo, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -193,7 +193,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -221,7 +221,7 @@ namespace Emby.Dlna.Profiles Type = CodecType.VideoAudio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Profiles/XboxOneProfile.cs b/Emby.Dlna/Profiles/XboxOneProfile.cs index 2cbe4e6acb..84d8184a22 100644 --- a/Emby.Dlna/Profiles/XboxOneProfile.cs +++ b/Emby.Dlna/Profiles/XboxOneProfile.cs @@ -119,7 +119,7 @@ namespace Emby.Dlna.Profiles Type = DlnaProfileType.Video, Container = "mp4,mov", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -138,7 +138,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "mpeg4", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -187,7 +187,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "h264", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -236,7 +236,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.Video, Codec = "wmv2,wmv3,vc1", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -284,7 +284,7 @@ namespace Emby.Dlna.Profiles new CodecProfile { Type = CodecType.Video, - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -307,7 +307,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "ac3,wmav2,wmapro", - Conditions = new [] + Conditions = new[] { new ProfileCondition { @@ -323,7 +323,7 @@ namespace Emby.Dlna.Profiles { Type = CodecType.VideoAudio, Codec = "aac", - Conditions = new [] + Conditions = new[] { new ProfileCondition { diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 5ecc81a2f1..bca9e81cd0 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -4,6 +4,7 @@ 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; @@ -64,10 +65,10 @@ namespace Emby.Dlna.Server foreach (var att in attributes) { - builder.AppendFormat(" {0}=\"{1}\"", att.Name, att.Value); + builder.AppendFormat(CultureInfo.InvariantCulture, " {0}=\"{1}\"", att.Name, att.Value); } - builder.Append(">"); + builder.Append('>'); builder.Append("<specVersion>"); builder.Append("<major>1</major>"); @@ -76,7 +77,9 @@ namespace Emby.Dlna.Server if (!EnableAbsoluteUrls) { - builder.Append("<URLBase>" + Escape(_serverAddress) + "</URLBase>"); + builder.Append("<URLBase>") + .Append(SecurityElement.Escape(_serverAddress)) + .Append("</URLBase>"); } AppendDeviceInfo(builder); @@ -93,86 +96,14 @@ namespace Emby.Dlna.Server AppendIconList(builder); - builder.Append("<presentationURL>" + Escape(_serverAddress) + "/web/index.html</presentationURL>"); + builder.Append("<presentationURL>") + .Append(SecurityElement.Escape(_serverAddress)) + .Append("/web/index.html</presentationURL>"); AppendServiceList(builder); builder.Append("</device>"); } - private static readonly char[] s_escapeChars = new char[] - { - '<', - '>', - '"', - '\'', - '&' - }; - - private static readonly string[] s_escapeStringPairs = new[] - { - "<", - "<", - ">", - ">", - "\"", - """, - "'", - "'", - "&", - "&" - }; - - private static string GetEscapeSequence(char c) - { - int num = s_escapeStringPairs.Length; - for (int i = 0; i < num; i += 2) - { - string text = s_escapeStringPairs[i]; - string result = s_escapeStringPairs[i + 1]; - if (text[0] == c) - { - return result; - } - } - return c.ToString(CultureInfo.InvariantCulture); - } - - /// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary> - /// <returns>The input string with invalid characters replaced.</returns> - /// <param name="str">The string within which to escape invalid characters. </param> - public static string Escape(string str) - { - if (str == null) - { - return null; - } - - StringBuilder stringBuilder = null; - int length = str.Length; - int num = 0; - while (true) - { - int num2 = str.IndexOfAny(s_escapeChars, num); - if (num2 == -1) - { - break; - } - if (stringBuilder == null) - { - stringBuilder = new StringBuilder(); - } - stringBuilder.Append(str, num, num2 - num); - stringBuilder.Append(GetEscapeSequence(str[num2])); - num = num2 + 1; - } - if (stringBuilder == null) - { - return str; - } - stringBuilder.Append(str, num, length - num); - return stringBuilder.ToString(); - } - private void AppendDeviceProperties(StringBuilder builder) { builder.Append("<dlna:X_DLNACAP/>"); @@ -182,32 +113,54 @@ namespace Emby.Dlna.Server builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>"); - builder.Append("<friendlyName>" + Escape(GetFriendlyName()) + "</friendlyName>"); - builder.Append("<manufacturer>" + Escape(_profile.Manufacturer ?? string.Empty) + "</manufacturer>"); - builder.Append("<manufacturerURL>" + Escape(_profile.ManufacturerUrl ?? string.Empty) + "</manufacturerURL>"); - - builder.Append("<modelDescription>" + Escape(_profile.ModelDescription ?? string.Empty) + "</modelDescription>"); - builder.Append("<modelName>" + Escape(_profile.ModelName ?? string.Empty) + "</modelName>"); - - builder.Append("<modelNumber>" + Escape(_profile.ModelNumber ?? string.Empty) + "</modelNumber>"); - builder.Append("<modelURL>" + Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>"); + builder.Append("<friendlyName>") + .Append(SecurityElement.Escape(GetFriendlyName())) + .Append("</friendlyName>"); + builder.Append("<manufacturer>") + .Append(SecurityElement.Escape(_profile.Manufacturer ?? string.Empty)) + .Append("</manufacturer>"); + builder.Append("<manufacturerURL>") + .Append(SecurityElement.Escape(_profile.ManufacturerUrl ?? string.Empty)) + .Append("</manufacturerURL>"); + + builder.Append("<modelDescription>") + .Append(SecurityElement.Escape(_profile.ModelDescription ?? string.Empty)) + .Append("</modelDescription>"); + builder.Append("<modelName>") + .Append(SecurityElement.Escape(_profile.ModelName ?? string.Empty)) + .Append("</modelName>"); + + builder.Append("<modelNumber>") + .Append(SecurityElement.Escape(_profile.ModelNumber ?? string.Empty)) + .Append("</modelNumber>"); + builder.Append("<modelURL>") + .Append(SecurityElement.Escape(_profile.ModelUrl ?? string.Empty)) + .Append("</modelURL>"); if (string.IsNullOrEmpty(_profile.SerialNumber)) { - builder.Append("<serialNumber>" + Escape(_serverId) + "</serialNumber>"); + builder.Append("<serialNumber>") + .Append(SecurityElement.Escape(_serverId)) + .Append("</serialNumber>"); } else { - builder.Append("<serialNumber>" + Escape(_profile.SerialNumber) + "</serialNumber>"); + builder.Append("<serialNumber>") + .Append(SecurityElement.Escape(_profile.SerialNumber)) + .Append("</serialNumber>"); } builder.Append("<UPC/>"); - builder.Append("<UDN>uuid:" + Escape(_serverUdn) + "</UDN>"); + builder.Append("<UDN>uuid:") + .Append(SecurityElement.Escape(_serverUdn)) + .Append("</UDN>"); if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags)) { - builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">" + Escape(_profile.SonyAggregationFlags) + "</av:aggregationFlags>"); + builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">") + .Append(SecurityElement.Escape(_profile.SonyAggregationFlags)) + .Append("</av:aggregationFlags>"); } } @@ -245,11 +198,21 @@ namespace Emby.Dlna.Server { builder.Append("<icon>"); - builder.Append("<mimetype>" + Escape(icon.MimeType ?? string.Empty) + "</mimetype>"); - builder.Append("<width>" + Escape(icon.Width.ToString(_usCulture)) + "</width>"); - builder.Append("<height>" + Escape(icon.Height.ToString(_usCulture)) + "</height>"); - builder.Append("<depth>" + Escape(icon.Depth ?? string.Empty) + "</depth>"); - builder.Append("<url>" + BuildUrl(icon.Url) + "</url>"); + builder.Append("<mimetype>") + .Append(SecurityElement.Escape(icon.MimeType ?? string.Empty)) + .Append("</mimetype>"); + builder.Append("<width>") + .Append(SecurityElement.Escape(icon.Width.ToString(_usCulture))) + .Append("</width>"); + builder.Append("<height>") + .Append(SecurityElement.Escape(icon.Height.ToString(_usCulture))) + .Append("</height>"); + builder.Append("<depth>") + .Append(SecurityElement.Escape(icon.Depth ?? string.Empty)) + .Append("</depth>"); + builder.Append("<url>") + .Append(BuildUrl(icon.Url)) + .Append("</url>"); builder.Append("</icon>"); } @@ -265,11 +228,21 @@ namespace Emby.Dlna.Server { builder.Append("<service>"); - builder.Append("<serviceType>" + Escape(service.ServiceType ?? string.Empty) + "</serviceType>"); - builder.Append("<serviceId>" + Escape(service.ServiceId ?? string.Empty) + "</serviceId>"); - builder.Append("<SCPDURL>" + BuildUrl(service.ScpdUrl) + "</SCPDURL>"); - builder.Append("<controlURL>" + BuildUrl(service.ControlUrl) + "</controlURL>"); - builder.Append("<eventSubURL>" + BuildUrl(service.EventSubUrl) + "</eventSubURL>"); + builder.Append("<serviceType>") + .Append(SecurityElement.Escape(service.ServiceType ?? string.Empty)) + .Append("</serviceType>"); + builder.Append("<serviceId>") + .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty)) + .Append("</serviceId>"); + builder.Append("<SCPDURL>") + .Append(BuildUrl(service.ScpdUrl)) + .Append("</SCPDURL>"); + builder.Append("<controlURL>") + .Append(BuildUrl(service.ControlUrl)) + .Append("</controlURL>"); + builder.Append("<eventSubURL>") + .Append(BuildUrl(service.EventSubUrl)) + .Append("</eventSubURL>"); builder.Append("</service>"); } @@ -293,7 +266,7 @@ namespace Emby.Dlna.Server url = _serverAddress.TrimEnd('/') + url; } - return Escape(url); + return SecurityElement.Escape(url); } private IEnumerable<DeviceIcon> GetIcons() diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 161a3434c5..4b0bbba969 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -15,10 +15,7 @@ namespace Emby.Dlna.Service { public abstract class BaseControlHandler { - private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"; - - protected IServerConfigurationManager Config { get; } - protected ILogger Logger { get; } + private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/"; protected BaseControlHandler(IServerConfigurationManager config, ILogger logger) { @@ -26,6 +23,10 @@ namespace Emby.Dlna.Service Logger = logger; } + protected IServerConfigurationManager Config { get; } + + protected ILogger Logger { get; } + public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request) { try @@ -59,10 +60,8 @@ namespace Emby.Dlna.Service Async = true }; - using (var reader = XmlReader.Create(streamReader, readerSettings)) - { - requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false); - } + using var reader = XmlReader.Create(streamReader, readerSettings); + requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false); } Logger.LogDebug("Received control request {0}", requestInfo.LocalName); @@ -79,10 +78,10 @@ namespace Emby.Dlna.Service { writer.WriteStartDocument(true); - writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV); - writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/"); + writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv); + writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/"); - writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV); + writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv); writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI); WriteResult(requestInfo.LocalName, requestInfo.Headers, writer); @@ -123,10 +122,8 @@ namespace Emby.Dlna.Service { if (!reader.IsEmptyElement) { - using (var subReader = reader.ReadSubtree()) - { - return await ParseBodyTagAsync(subReader).ConfigureAwait(false); - } + using var subReader = reader.ReadSubtree(); + return await ParseBodyTagAsync(subReader).ConfigureAwait(false); } else { @@ -135,6 +132,7 @@ namespace Emby.Dlna.Service break; } + default: { await reader.SkipAsync().ConfigureAwait(false); @@ -148,12 +146,12 @@ namespace Emby.Dlna.Service } } - return new ControlRequestInfo(); + throw new EndOfStreamException("Stream ended but no body tag found."); } private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader) { - var result = new ControlRequestInfo(); + string namespaceURI = null, localName = null; await reader.MoveToContentAsync().ConfigureAwait(false); await reader.ReadAsync().ConfigureAwait(false); @@ -163,16 +161,14 @@ namespace Emby.Dlna.Service { if (reader.NodeType == XmlNodeType.Element) { - result.LocalName = reader.LocalName; - result.NamespaceURI = reader.NamespaceURI; + localName = reader.LocalName; + namespaceURI = reader.NamespaceURI; if (!reader.IsEmptyElement) { - using (var subReader = reader.ReadSubtree()) - { - await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); - return result; - } + var result = new ControlRequestInfo(localName, namespaceURI); + using var subReader = reader.ReadSubtree(); + await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); } else { @@ -185,7 +181,12 @@ namespace Emby.Dlna.Service } } - return result; + if (localName != null && namespaceURI != null) + { + return new ControlRequestInfo(localName, namespaceURI); + } + + throw new EndOfStreamException("Stream ended but no control found."); } private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers) @@ -208,13 +209,6 @@ namespace Emby.Dlna.Service } } - private class ControlRequestInfo - { - public string LocalName { get; set; } - public string NamespaceURI { get; set; } - public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - } - protected abstract void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter); private void LogRequest(ControlRequest request) @@ -236,5 +230,21 @@ namespace Emby.Dlna.Service 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<string, string>(StringComparer.OrdinalIgnoreCase); + } + + public string LocalName { get; set; } + + public string NamespaceURI { get; set; } + + public Dictionary<string, string> Headers { get; } + } } } diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs index 3704bedcd5..a97c4d63a6 100644 --- a/Emby.Dlna/Service/BaseService.cs +++ b/Emby.Dlna/Service/BaseService.cs @@ -1,25 +1,23 @@ #pragma warning disable CS1591 +using System.Net.Http; using Emby.Dlna.Eventing; -using MediaBrowser.Common.Net; using Microsoft.Extensions.Logging; namespace Emby.Dlna.Service { - public class BaseService : IEventManager + public class BaseService : IDlnaEventManager { - protected IEventManager EventManager; - protected IHttpClient HttpClient; - protected ILogger Logger; - - protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient) + protected BaseService(ILogger<BaseService> logger, IHttpClientFactory httpClientFactory) { Logger = logger; - HttpClient = httpClient; - - EventManager = new EventManager(Logger, HttpClient); + EventManager = new DlnaEventManager(logger, httpClientFactory); } + protected IDlnaEventManager EventManager { get; } + + protected ILogger Logger { get; } + public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) { return EventManager.CancelEventSubscription(subscriptionId); diff --git a/Emby.Dlna/Service/ControlErrorHandler.cs b/Emby.Dlna/Service/ControlErrorHandler.cs index 047e9f0142..f2b5dd9ca8 100644 --- a/Emby.Dlna/Service/ControlErrorHandler.cs +++ b/Emby.Dlna/Service/ControlErrorHandler.cs @@ -10,7 +10,7 @@ namespace Emby.Dlna.Service { public static class ControlErrorHandler { - private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"; + private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/"; public static ControlResponse GetResponse(Exception ex) { @@ -26,11 +26,11 @@ namespace Emby.Dlna.Service { writer.WriteStartDocument(true); - writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV); - writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/"); + writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv); + writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/"); - writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV); - writer.WriteStartElement("SOAP-ENV", "Fault", NS_SOAPENV); + writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv); + writer.WriteStartElement("SOAP-ENV", "Fault", NsSoapEnv); writer.WriteElementString("faultcode", "500"); writer.WriteElementString("faultstring", ex.Message); diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs index 62ffd9e42a..1e56d09b29 100644 --- a/Emby.Dlna/Service/ServiceXmlBuilder.cs +++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs @@ -1,9 +1,9 @@ #pragma warning disable CS1591 using System.Collections.Generic; +using System.Security; using System.Text; using Emby.Dlna.Common; -using Emby.Dlna.Server; namespace Emby.Dlna.Service { @@ -37,7 +37,9 @@ namespace Emby.Dlna.Service { builder.Append("<action>"); - builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>"); + builder.Append("<name>") + .Append(SecurityElement.Escape(item.Name ?? string.Empty)) + .Append("</name>"); builder.Append("<argumentList>"); @@ -45,9 +47,15 @@ namespace Emby.Dlna.Service { builder.Append("<argument>"); - builder.Append("<name>" + DescriptionXmlBuilder.Escape(argument.Name ?? string.Empty) + "</name>"); - builder.Append("<direction>" + DescriptionXmlBuilder.Escape(argument.Direction ?? string.Empty) + "</direction>"); - builder.Append("<relatedStateVariable>" + DescriptionXmlBuilder.Escape(argument.RelatedStateVariable ?? string.Empty) + "</relatedStateVariable>"); + builder.Append("<name>") + .Append(SecurityElement.Escape(argument.Name ?? string.Empty)) + .Append("</name>"); + builder.Append("<direction>") + .Append(SecurityElement.Escape(argument.Direction ?? string.Empty)) + .Append("</direction>"); + builder.Append("<relatedStateVariable>") + .Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty)) + .Append("</relatedStateVariable>"); builder.Append("</argument>"); } @@ -68,18 +76,27 @@ namespace Emby.Dlna.Service { var sendEvents = item.SendsEvents ? "yes" : "no"; - builder.Append("<stateVariable sendEvents=\"" + sendEvents + "\">"); + builder.Append("<stateVariable sendEvents=\"") + .Append(sendEvents) + .Append("\">"); - builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>"); - builder.Append("<dataType>" + DescriptionXmlBuilder.Escape(item.DataType ?? string.Empty) + "</dataType>"); + builder.Append("<name>") + .Append(SecurityElement.Escape(item.Name ?? string.Empty)) + .Append("</name>"); + builder.Append("<dataType>") + .Append(SecurityElement.Escape(item.DataType ?? string.Empty)) + .Append("</dataType>"); - if (item.AllowedValues.Length > 0) + if (item.AllowedValues.Count > 0) { builder.Append("<allowedValueList>"); foreach (var allowedValue in item.AllowedValues) { - builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>"); + builder.Append("<allowedValue>") + .Append(SecurityElement.Escape(allowedValue)) + .Append("</allowedValue>"); } + builder.Append("</allowedValueList>"); } diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index f95b8ce7de..8c7d961f3e 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using Rssdp; using Rssdp.Infrastructure; @@ -17,9 +17,17 @@ namespace Emby.Dlna.Ssdp 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<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal; /// <inheritdoc /> @@ -49,15 +57,6 @@ namespace Emby.Dlna.Ssdp /// <inheritdoc /> public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft; - private SsdpDeviceLocator _deviceLocator; - - private ISsdpCommunicationsServer _commsServer; - - public DeviceDiscovery(IServerConfigurationManager config) - { - _config = config; - } - // Call this method from somewhere in your code to start the search. public void Start(ISsdpCommunicationsServer communicationsServer) { @@ -77,7 +76,7 @@ namespace Emby.Dlna.Ssdp // (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"; + // _DeviceLocator.NotificationFilter = "upnp:rootdevice"; // Connect our event handler so we process devices as they are found _deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable; @@ -100,15 +99,13 @@ namespace Emby.Dlna.Ssdp var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - var args = new GenericEventArgs<UpnpDeviceInfo> - { - Argument = new UpnpDeviceInfo + var args = new GenericEventArgs<UpnpDeviceInfo>( + new UpnpDeviceInfo { Location = e.DiscoveredDevice.DescriptionLocation, Headers = headers, LocalIpAddress = e.LocalIpAddress - } - }; + }); DeviceDiscoveredInternal?.Invoke(this, args); } @@ -121,14 +118,12 @@ namespace Emby.Dlna.Ssdp var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - var args = new GenericEventArgs<UpnpDeviceInfo> - { - Argument = new UpnpDeviceInfo + var args = new GenericEventArgs<UpnpDeviceInfo>( + new UpnpDeviceInfo { Location = e.DiscoveredDevice.DescriptionLocation, Headers = headers - } - }; + }); DeviceLeft?.Invoke(this, args); } diff --git a/Emby.Dlna/Ssdp/Extensions.cs b/Emby.Dlna/Ssdp/SsdpExtensions.cs index 10c1f321be..e7a52f168f 100644 --- a/Emby.Dlna/Ssdp/Extensions.cs +++ b/Emby.Dlna/Ssdp/SsdpExtensions.cs @@ -1,33 +1,27 @@ #pragma warning disable CS1591 +using System.Linq; using System.Xml.Linq; namespace Emby.Dlna.Ssdp { - public static class Extensions + public static class SsdpExtensions { public static string GetValue(this XElement container, XName name) { var node = container.Element(name); - return node == null ? null : node.Value; + return node?.Value; } public static string GetAttributeValue(this XElement container, XName name) { var node = container.Attribute(name); - return node == null ? null : node.Value; + return node?.Value; } public static string GetDescendantValue(this XElement container, XName name) - { - foreach (var node in container.Descendants(name)) - { - return node.Value; - } - - return null; - } + => container.Descendants(name).FirstOrDefault()?.Value; } } |
