diff options
Diffstat (limited to 'Emby.Dlna')
36 files changed, 299 insertions, 178 deletions
diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs index e63a858605..5ceeb55300 100644 --- a/Emby.Dlna/Configuration/DlnaOptions.cs +++ b/Emby.Dlna/Configuration/DlnaOptions.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 namespace Emby.Dlna.Configuration diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs index fc02e17515..3ca43052a4 100644 --- a/Emby.Dlna/ConfigurationExtension.cs +++ b/Emby.Dlna/ConfigurationExtension.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using Emby.Dlna.Configuration; diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index 2f8d197a7a..1a1790ee6a 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -31,7 +31,7 @@ namespace Emby.Dlna.ConnectionManager } /// <inheritdoc /> - protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter) + protected override void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter) { if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs index 2f3107450c..7b8c504409 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 27f1fdabad..27c5b22680 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -1,5 +1,6 @@ +#nullable disable + using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -7,7 +8,6 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; -using Emby.Dlna.Configuration; using Emby.Dlna.Didl; using Emby.Dlna.Service; using Jellyfin.Data.Entities; @@ -121,7 +121,7 @@ namespace Emby.Dlna.ContentDirectory } /// <inheritdoc /> - protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter) + protected override void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter) { if (xmlWriter == null) { @@ -201,8 +201,8 @@ namespace Emby.Dlna.ContentDirectory /// <summary> /// Adds a "XSetBookmark" element to the xml document. /// </summary> - /// <param name="sparams">The <see cref="IDictionary"/>.</param> - private void HandleXSetBookmark(IDictionary<string, string> sparams) + /// <param name="sparams">The method parameters.</param> + private void HandleXSetBookmark(IReadOnlyDictionary<string, string> sparams) { var id = sparams["ObjectID"]; @@ -306,34 +306,17 @@ namespace Emby.Dlna.ContentDirectory } /// <summary> - /// Returns the value in the key of the dictionary, or defaultValue if it doesn't exist. - /// </summary> - /// <param name="sparams">The <see cref="IDictionary"/>.</param> - /// <param name="key">The key.</param> - /// <param name="defaultValue">The defaultValue.</param> - /// <returns>The <see cref="string"/>.</returns> - public static string GetValueOrDefault(IDictionary<string, string> sparams, string key, string defaultValue) - { - if (sparams != null && sparams.TryGetValue(key, out string val)) - { - return val; - } - - return defaultValue; - } - - /// <summary> /// Builds the "Browse" xml response. /// </summary> /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param> - /// <param name="sparams">The <see cref="IDictionary"/>.</param> + /// <param name="sparams">The method parameters.</param> /// <param name="deviceId">The device Id to use.</param> - private void HandleBrowse(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId) + private void HandleBrowse(XmlWriter xmlWriter, IReadOnlyDictionary<string, string> sparams, string deviceId) { var id = sparams["ObjectID"]; var flag = sparams["BrowseFlag"]; - var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); - var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty)); + var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); + var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty)); var provided = 0; @@ -435,9 +418,9 @@ namespace Emby.Dlna.ContentDirectory /// Builds the response to the "X_BrowseByLetter request. /// </summary> /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param> - /// <param name="sparams">The <see cref="IDictionary"/>.</param> + /// <param name="sparams">The method parameters.</param> /// <param name="deviceId">The device id.</param> - private void HandleXBrowseByLetter(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId) + private void HandleXBrowseByLetter(XmlWriter xmlWriter, IReadOnlyDictionary<string, string> sparams, string deviceId) { // TODO: Implement this method HandleSearch(xmlWriter, sparams, deviceId); @@ -447,13 +430,13 @@ namespace Emby.Dlna.ContentDirectory /// Builds a response to the "Search" request. /// </summary> /// <param name="xmlWriter">The xmlWriter<see cref="XmlWriter"/>.</param> - /// <param name="sparams">The sparams<see cref="IDictionary"/>.</param> + /// <param name="sparams">The method parameters.</param> /// <param name="deviceId">The deviceId<see cref="string"/>.</param> - private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId) + private void HandleSearch(XmlWriter xmlWriter, IReadOnlyDictionary<string, string> sparams, string deviceId) { - 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", "*")); + var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", string.Empty)); + var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty)); + var filter = new Filter(sparams.GetValueOrDefault("Filter", "*")); // sort example: dc:title, dc:date diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index 4ea4e4e48c..8ee6325e9e 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs index d827eef26c..a7f2d4a73b 100644 --- a/Emby.Dlna/ControlResponse.cs +++ b/Emby.Dlna/ControlResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 8b50d47fbd..2982ce97e1 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -208,7 +210,8 @@ namespace Emby.Dlna.Didl var targetWidth = streamInfo.TargetWidth; var targetHeight = streamInfo.TargetHeight; - var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader( + var contentFeatureList = ContentFeatureBuilder.BuildVideoHeader( + _profile, streamInfo.Container, streamInfo.TargetVideoCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(), @@ -599,7 +602,8 @@ namespace Emby.Dlna.Didl ? MimeTypes.GetMimeType(filename) : mediaProfile.MimeType; - var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader( + var contentFeatures = ContentFeatureBuilder.BuildAudioHeader( + _profile, streamInfo.Container, streamInfo.TargetAudioCodec.FirstOrDefault(), targetAudioBitrate, @@ -974,15 +978,28 @@ namespace Emby.Dlna.Didl return; } - var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg"); + // TODO: Remove these default values + var albumArtUrlInfo = GetImageUrl( + imageInfo, + _profile.MaxAlbumArtWidth ?? 10000, + _profile.MaxAlbumArtHeight ?? 10000, + "jpg"); writer.WriteStartElement("upnp", "albumArtURI", NsUpnp); - writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn); - writer.WriteString(albumartUrlInfo.url); + if (!string.IsNullOrEmpty(_profile.AlbumArtPn)) + { + 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"); + // TODO: Remove these default values + var iconUrlInfo = GetImageUrl( + imageInfo, + _profile.MaxIconWidth ?? 48, + _profile.MaxIconHeight ?? 48, + "jpg"); writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url); if (!_profile.EnableAlbumArtInDidl) @@ -1033,8 +1050,7 @@ namespace Emby.Dlna.Didl var width = albumartUrlInfo.width ?? maxWidth; var height = albumartUrlInfo.height ?? maxHeight; - var contentFeatures = new ContentFeatureBuilder(_profile) - .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn); + var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn); writer.WriteAttributeString( "protocolInfo", @@ -1206,8 +1222,7 @@ namespace Emby.Dlna.Didl if (width.HasValue && height.HasValue) { - var newSize = DrawingUtils.Resize( - new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight); + var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight); width = newSize.Width; height = newSize.Height; diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs index 2b86ea333f..b66f53ece2 100644 --- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs +++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs @@ -9,7 +9,7 @@ namespace Emby.Dlna.Didl { public class StringWriterWithEncoding : StringWriter { - private readonly Encoding _encoding; + private readonly Encoding? _encoding; public StringWriterWithEncoding() { diff --git a/Emby.Dlna/DlnaConfigurationFactory.cs b/Emby.Dlna/DlnaConfigurationFactory.cs index 4c6ca869aa..6cc6b73a0c 100644 --- a/Emby.Dlna/DlnaConfigurationFactory.cs +++ b/Emby.Dlna/DlnaConfigurationFactory.cs @@ -1,4 +1,3 @@ -#nullable enable #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 9ab3240388..a1b1067040 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -36,7 +38,7 @@ namespace Emby.Dlna private readonly ILogger<DlnaManager> _logger; private readonly IServerApplicationHost _appHost; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; - private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal); @@ -111,7 +113,7 @@ namespace Emby.Dlna if (profile != null) { - _logger.LogDebug("Found matching device profile: {0}", profile.Name); + _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name); } else { @@ -126,92 +128,57 @@ namespace Emby.Dlna var builder = new StringBuilder(); builder.AppendLine("No matching device profile found. The default will need to be used."); - 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); + 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) + /// <summary> + /// Attempts to match a device with a profile. + /// Rules: + /// - If the profile field has no value, the field matches irregardless of its contents. + /// - the profile field can be an exact match, or a reg exp. + /// </summary> + /// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param> + /// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param> + /// <returns><b>True</b> if they match.</returns> + public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) { - if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) - { - if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) - { - if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) - { - if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) - { - if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ModelName)) - { - if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) - { - if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) - { - return false; - } - } + return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName) + && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer) + && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl) + && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription) + && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName) + && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber) + && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl) + && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber); + } - if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) + private bool IsRegexOrSubstringMatch(string input, string pattern) + { + if (string.IsNullOrEmpty(pattern)) { - if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) - { - return false; - } + // In profile identification: An empty pattern matches anything. + return true; } - if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) + if (string.IsNullOrEmpty(input)) { - if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) - { - return false; - } + // The profile contains a value, and the device doesn't. + return false; } - return true; - } - - private bool IsRegexOrSubstringMatch(string input, string pattern) - { try { - return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + return input.Equals(pattern, StringComparison.OrdinalIgnoreCase) + || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } catch (ArgumentException ex) { @@ -333,7 +300,12 @@ namespace Emby.Dlna throw new ArgumentNullException(nameof(id)); } - var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase)); + var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase)); + + if (info == null) + { + return null; + } return ParseProfileFile(info.Path, info.Info.Type); } @@ -395,7 +367,8 @@ namespace Emby.Dlna { Directory.CreateDirectory(systemProfilesPath); - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 8b057a0950..a40578e403 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -21,11 +21,11 @@ <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> </PropertyGroup> <!-- Code Analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> diff --git a/Emby.Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs index 1b1bd426c5..8c82dcbf68 100644 --- a/Emby.Dlna/EventSubscriptionResponse.cs +++ b/Emby.Dlna/EventSubscriptionResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index ff81e83b5e..2e672b886b 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs index 40d73ee0e5..4fd7f81695 100644 --- a/Emby.Dlna/Eventing/EventSubscription.cs +++ b/Emby.Dlna/Eventing/EventSubscription.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index ec87160293..0309926abb 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -5,7 +7,6 @@ using System.Globalization; using System.Linq; using System.Net.Http; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; using Emby.Dlna.Ssdp; @@ -128,7 +129,8 @@ namespace Emby.Dlna.Main _netConfig = config.GetConfiguration<NetworkConfiguration>("network"); _disabled = appHost.ListenWithHttps && _netConfig.RequireHttps; - if (_disabled) + + if (_disabled && _config.GetDlnaConfiguration().EnableServer) { _logger.LogError("The DLNA specification does not support HTTPS."); } @@ -316,7 +318,7 @@ namespace Emby.Dlna.Main _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri); - if (_appHost.PublishedServerUrl == null) + if (!string.IsNullOrEmpty(_appHost.PublishedServerUrl)) { // DLNA will only work over http, so we must reset to http:// : {port}. uri.Scheme = "http"; diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 464f71a6f1..d8fb127420 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -24,7 +24,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar } /// <inheritdoc /> - protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter) + protected override void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter) { if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs index 37840cd096..f3789a791c 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Emby.Dlna.Common; using Emby.Dlna.Service; -using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 7bf7047fbb..6c580d15bd 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -219,7 +221,7 @@ namespace Emby.Dlna.PlayTo { var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); + var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); if (command == null) { return false; @@ -259,7 +261,7 @@ namespace Emby.Dlna.PlayTo { var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); + var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); if (command == null) { return; @@ -290,7 +292,7 @@ namespace Emby.Dlna.PlayTo { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); + var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); if (command == null) { return; @@ -323,7 +325,7 @@ namespace Emby.Dlna.PlayTo _logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header); - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); + var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); if (command == null) { return; @@ -368,6 +370,42 @@ namespace Emby.Dlna.PlayTo RestartTimer(true); } + /* + * SetNextAvTransport is used to specify to the DLNA device what is the next track to play. + * Without that information, the next track command on the device does not work. + */ + public async Task SetNextAvTransport(string url, string header, string metaData, CancellationToken cancellationToken = default) + { + var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + + url = url.Replace("&", "&", StringComparison.Ordinal); + + _logger.LogDebug("{PropertyName} - SetNextAvTransport Uri: {Url} DlnaHeaders: {Header}", Properties.Name, url, header); + + var command = avCommands.ServiceActions.FirstOrDefault(c => string.Equals(c.Name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase)); + if (command == null) + { + return; + } + + var dictionary = new Dictionary<string, string> + { + { "NextURI", url }, + { "NextURIMetaData", CreateDidlMeta(metaData) } + }; + + var service = GetAvTransportService(); + + if (service == null) + { + throw new InvalidOperationException("Unable to find service"); + } + + var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); + await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header, cancellationToken) + .ConfigureAwait(false); + } + private static string CreateDidlMeta(string value) { if (string.IsNullOrEmpty(value)) @@ -403,6 +441,10 @@ namespace Emby.Dlna.PlayTo public async Task SetPlay(CancellationToken cancellationToken) { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); + if (avCommands == null) + { + return; + } await SetPlay(avCommands, cancellationToken).ConfigureAwait(false); @@ -413,7 +455,7 @@ namespace Emby.Dlna.PlayTo { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); + var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); if (command == null) { return; @@ -437,7 +479,7 @@ namespace Emby.Dlna.PlayTo { var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); + var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); if (command == null) { return; @@ -565,7 +607,7 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); + var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); if (command == null) { return; @@ -615,7 +657,7 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); - var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); + var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); if (command == null) { return; @@ -702,6 +744,10 @@ namespace Emby.Dlna.PlayTo } var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + if (rendererCommands == null) + { + return null; + } var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, @@ -770,6 +816,11 @@ namespace Emby.Dlna.PlayTo var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); + if (rendererCommands == null) + { + return (false, null); + } + var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync( Properties.BaseUrl, service, @@ -951,6 +1002,10 @@ namespace Emby.Dlna.PlayTo var httpClient = new SsdpHttpClient(_httpClientFactory); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); + if (document == null) + { + return null; + } AvCommands = TransportCommands.Create(document); return AvCommands; @@ -979,6 +1034,10 @@ namespace Emby.Dlna.PlayTo var httpClient = new SsdpHttpClient(_httpClientFactory); _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); + if (document == null) + { + return null; + } RendererCommands = TransportCommands.Create(document); return RendererCommands; @@ -1010,6 +1069,10 @@ namespace Emby.Dlna.PlayTo var ssdpHttpClient = new SsdpHttpClient(httpClientFactory); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); + if (document == null) + { + return null; + } var friendlyNames = new List<string>(); diff --git a/Emby.Dlna/PlayTo/DeviceInfo.cs b/Emby.Dlna/PlayTo/DeviceInfo.cs index d3daab9e0a..2acfff4eb6 100644 --- a/Emby.Dlna/PlayTo/DeviceInfo.cs +++ b/Emby.Dlna/PlayTo/DeviceInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs index dabd079afd..2bc4d8cc24 100644 --- a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs +++ b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index e4923b9eb0..0e49fd2c02 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -102,6 +104,22 @@ namespace Emby.Dlna.PlayTo _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft; } + /* + * Send a message to the DLNA device to notify what is the next track in the playlist. + */ + private async Task SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken) + { + if (currentPlayListItemIndex >= 0 && currentPlayListItemIndex < _playlist.Count - 1) + { + // The current playing item is indeed in the play list and we are not yet at the end of the playlist. + var nextItemIndex = currentPlayListItemIndex + 1; + var nextItem = _playlist[nextItemIndex]; + + // Send the SetNextAvTransport message. + await _device.SetNextAvTransport(nextItem.StreamUrl, GetDlnaHeaders(nextItem), nextItem.Didl, cancellationToken).ConfigureAwait(false); + } + } + private void OnDeviceUnavailable() { try @@ -132,7 +150,7 @@ namespace Emby.Dlna.PlayTo private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e) { - if (_disposed) + if (_disposed || string.IsNullOrEmpty(e.OldMediaInfo.Url)) { return; } @@ -156,6 +174,15 @@ namespace Emby.Dlna.PlayTo var newItemProgress = GetProgressInfo(streamInfo); await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false); + + // Send a message to the DLNA device to notify what is the next track in the playlist. + var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId == streamInfo.ItemId); + if (currentItemIndex >= 0) + { + _currentPlaylistIndex = currentItemIndex; + } + + await SendNextTrackMessage(currentItemIndex, CancellationToken.None); } catch (Exception ex) { @@ -425,6 +452,11 @@ namespace Emby.Dlna.PlayTo var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex); await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); + + // Send a message to the DLNA device to notify what is the next track in the play list. + var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); + await SendNextTrackMessage(newItemIndex, CancellationToken.None); + return; } @@ -499,8 +531,8 @@ namespace Emby.Dlna.PlayTo if (streamInfo.MediaType == DlnaProfileType.Audio) { - return new ContentFeatureBuilder(profile) - .BuildAudioHeader( + return ContentFeatureBuilder.BuildAudioHeader( + profile, streamInfo.Container, streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetAudioBitrate, @@ -514,8 +546,8 @@ namespace Emby.Dlna.PlayTo if (streamInfo.MediaType == DlnaProfileType.Video) { - var list = new ContentFeatureBuilder(profile) - .BuildVideoHeader( + var list = ContentFeatureBuilder.BuildVideoHeader( + profile, streamInfo.Container, streamInfo.TargetVideoCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(), @@ -623,6 +655,9 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false); + // Send a message to the DLNA device to notify what is the next track in the play list. + await SendNextTrackMessage(index, cancellationToken); + var streamInfo = currentitem.StreamInfo; if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) { @@ -736,6 +771,10 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); + // Send a message to the DLNA device to notify what is the next track in the play list. + var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); + await SendNextTrackMessage(newItemIndex, CancellationToken.None); + if (EnableClientSideSeek(newItem.StreamInfo)) { await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); @@ -761,6 +800,10 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); + // Send a message to the DLNA device to notify what is the next track in the play list. + var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl); + await SendNextTrackMessage(newItemIndex, CancellationToken.None); + if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0) { await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); @@ -943,11 +986,7 @@ namespace Emby.Dlna.PlayTo request.DeviceId = values.GetValueOrDefault("DeviceId"); request.MediaSourceId = values.GetValueOrDefault("MediaSourceId"); request.LiveStreamId = values.GetValueOrDefault("LiveStreamId"); - - // Be careful, IsDirectStream==true by default (Static != false or not in query). - // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true. - request.IsDirectStream = !string.Equals("false", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); - + request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); request.StartPositionTicks = GetLongValue(values, "StartPositionTicks"); diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index a6793a7081..35bf5927c4 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -178,12 +180,17 @@ namespace Emby.Dlna.PlayTo if (controller == null) { var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false); + if (device == null) + { + _logger.LogError("Ignoring device as xml response is invalid."); + return; + } string deviceName = device.Properties.Name; _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName); - string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress); + string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIpAddress); controller = new PlayToController( sessionInfo, diff --git a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs index d14617c8a0..c7d2b28df8 100644 --- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs index 3f8d552636..f8a14f411f 100644 --- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs index deeb47918d..6661f92ac7 100644 --- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/PlayTo/PlaylistItem.cs b/Emby.Dlna/PlayTo/PlaylistItem.cs index 85846166cf..5056e69ae7 100644 --- a/Emby.Dlna/PlayTo/PlaylistItem.cs +++ b/Emby.Dlna/PlayTo/PlaylistItem.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Model.Dlna; diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs index e28840a896..6574913032 100644 --- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.IO; diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 557bc69a73..f14f73bb6f 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -1,8 +1,9 @@ +#nullable disable + #pragma warning disable CS1591 using System; using System.Globalization; -using System.IO; using System.Net.Http; using System.Net.Mime; using System.Text; @@ -45,10 +46,10 @@ namespace Emby.Dlna.PlayTo cancellationToken) .ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - using var reader = new StreamReader(stream, Encoding.UTF8); - return XDocument.Parse( - await reader.ReadToEndAsync().ConfigureAwait(false), - LoadOptions.PreserveWhitespace); + return await XDocument.LoadAsync( + stream, + LoadOptions.PreserveWhitespace, + cancellationToken).ConfigureAwait(false); } private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) @@ -94,10 +95,17 @@ namespace Emby.Dlna.PlayTo 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(cancellationToken).ConfigureAwait(false); - using var reader = new StreamReader(stream, Encoding.UTF8); - return XDocument.Parse( - await reader.ReadToEndAsync().ConfigureAwait(false), - LoadOptions.PreserveWhitespace); + try + { + return await XDocument.LoadAsync( + stream, + LoadOptions.PreserveWhitespace, + cancellationToken).ConfigureAwait(false); + } + catch + { + return null; + } } private async Task<HttpResponseMessage> PostSoapDataAsync( diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs index 0865968ad1..b58669355d 100644 --- a/Emby.Dlna/PlayTo/TransportCommands.cs +++ b/Emby.Dlna/PlayTo/TransportCommands.cs @@ -13,12 +13,10 @@ 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>(); - private List<ServiceAction> _serviceActions = new List<ServiceAction>(); - public List<StateVariable> StateVariables => _stateVariables; + public List<StateVariable> StateVariables { get; } = new List<StateVariable>(); - public List<ServiceAction> ServiceActions => _serviceActions; + public List<ServiceAction> ServiceActions { get; } = new List<ServiceAction>(); public static TransportCommands Create(XDocument document) { @@ -48,7 +46,7 @@ namespace Emby.Dlna.PlayTo { var serviceAction = new ServiceAction { - Name = container.GetValue(UPnpNamespaces.Svc + "name"), + Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, }; var argumentList = serviceAction.ArgumentList; @@ -70,9 +68,9 @@ 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") ?? string.Empty, + Direction = container.GetValue(UPnpNamespaces.Svc + "direction") ?? string.Empty, + RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") ?? string.Empty }; } @@ -91,8 +89,8 @@ namespace Emby.Dlna.PlayTo return new StateVariable { - Name = container.GetValue(UPnpNamespaces.Svc + "name"), - DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"), + Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty, + DataType = container.GetValue(UPnpNamespaces.Svc + "dataType") ?? string.Empty, AllowedValues = allowedValues }; } @@ -168,7 +166,7 @@ namespace Emby.Dlna.PlayTo return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString); } - private string BuildArgumentXml(Argument argument, string value, string commandParameter = "") + private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "") { var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase)); diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs index 0d9478e42e..02d2da58d6 100644 --- a/Emby.Dlna/PlayTo/uBaseObject.cs +++ b/Emby.Dlna/PlayTo/uBaseObject.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs index d4af72b626..8eaf12ba9a 100644 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ b/Emby.Dlna/Profiles/DefaultProfile.cs @@ -1,5 +1,7 @@ #pragma warning disable CS1591 +using System; +using System.Globalization; using System.Linq; using MediaBrowser.Model.Dlna; @@ -10,6 +12,7 @@ namespace Emby.Dlna.Profiles { public DefaultProfile() { + Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); Name = "Generic Device"; ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*"; diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 09525aae4e..3f3dfccd3a 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -250,7 +250,8 @@ namespace Emby.Dlna.Server url = _serverAddress.TrimEnd('/') + "/dlna/" + _serverUdn + "/" + url.TrimStart('/'); - return SecurityElement.Escape(url); + // TODO: @bond remove null-coalescing operator when https://github.com/dotnet/runtime/pull/52442 is merged/released + return SecurityElement.Escape(url) ?? string.Empty; } private IEnumerable<DeviceIcon> GetIcons() diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 8d2486fee6..904c23d997 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -47,7 +47,7 @@ namespace Emby.Dlna.Service private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request) { - ControlRequestInfo requestInfo = null; + ControlRequestInfo? requestInfo = null; using (var streamReader = new StreamReader(request.InputXml, Encoding.UTF8)) { @@ -151,7 +151,7 @@ namespace Emby.Dlna.Service private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader) { - string namespaceURI = null, localName = null; + string? namespaceURI = null, localName = null; await reader.MoveToContentAsync().ConfigureAwait(false); await reader.ReadAsync().ConfigureAwait(false); @@ -210,7 +210,7 @@ namespace Emby.Dlna.Service } } - protected abstract void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter); + protected abstract void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter); private void LogRequest(ControlRequest request) { diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 8c7d961f3e..391dda1479 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; @@ -69,7 +71,7 @@ namespace Emby.Dlna.Ssdp { lock (_syncLock) { - if (_listenerCount > 0 && _deviceLocator == null) + if (_listenerCount > 0 && _deviceLocator == null && _commsServer != null) { _deviceLocator = new SsdpDeviceLocator(_commsServer); @@ -104,7 +106,7 @@ namespace Emby.Dlna.Ssdp { Location = e.DiscoveredDevice.DescriptionLocation, Headers = headers, - LocalIpAddress = e.LocalIpAddress + RemoteIpAddress = e.RemoteIpAddress }); DeviceDiscoveredInternal?.Invoke(this, args); diff --git a/Emby.Dlna/Ssdp/SsdpExtensions.cs b/Emby.Dlna/Ssdp/SsdpExtensions.cs index e7a52f168f..d00eb02b46 100644 --- a/Emby.Dlna/Ssdp/SsdpExtensions.cs +++ b/Emby.Dlna/Ssdp/SsdpExtensions.cs @@ -7,21 +7,21 @@ namespace Emby.Dlna.Ssdp { public static class SsdpExtensions { - public static string GetValue(this XElement container, XName name) + public static string? GetValue(this XElement container, XName name) { var node = container.Element(name); return node?.Value; } - public static string GetAttributeValue(this XElement container, XName name) + public static string? GetAttributeValue(this XElement container, XName name) { var node = container.Attribute(name); return node?.Value; } - public static string GetDescendantValue(this XElement container, XName name) + public static string? GetDescendantValue(this XElement container, XName name) => container.Descendants(name).FirstOrDefault()?.Value; } } |
