浏览代码

Merge branch 'master' into event-rewrite-1

# Conflicts:
#	Emby.Dlna/Emby.Dlna.csproj
#	Emby.Dlna/Eventing/DlnaEventManager.cs
#	Emby.Dlna/Service/BaseService.cs
#	Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
#	MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs
Patrick Barron 4 年之前
父节点
当前提交
9fa4fff15d
共有 100 个文件被更改,包括 1307 次插入1199 次删除
  1. 1 1
      Emby.Dlna/Common/ServiceAction.cs
  2. 2 1
      Emby.Dlna/Common/StateVariable.cs
  3. 0 16
      Emby.Dlna/ConfigurationExtension.cs
  4. 4 6
      Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
  5. 2 2
      Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
  6. 3 3
      Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
  7. 3 2
      Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
  8. 66 92
      Emby.Dlna/ContentDirectory/ControlHandler.cs
  9. 23 0
      Emby.Dlna/ContentDirectory/ServerItem.cs
  10. 28 0
      Emby.Dlna/ContentDirectory/StubType.cs
  11. 6 6
      Emby.Dlna/ControlRequest.cs
  12. 1 1
      Emby.Dlna/ControlResponse.cs
  13. 69 88
      Emby.Dlna/Didl/DidlBuilder.cs
  14. 1 3
      Emby.Dlna/Didl/Filter.cs
  15. 1 1
      Emby.Dlna/Didl/StringWriterWithEncoding.cs
  16. 24 0
      Emby.Dlna/DlnaConfigurationFactory.cs
  17. 16 15
      Emby.Dlna/DlnaManager.cs
  18. 1 1
      Emby.Dlna/Emby.Dlna.csproj
  19. 1 1
      Emby.Dlna/EventSubscriptionResponse.cs
  20. 5 3
      Emby.Dlna/Eventing/DlnaEventManager.cs
  21. 10 0
      Emby.Dlna/IDlnaEventManager.cs
  22. 33 23
      Emby.Dlna/Main/DlnaEntryPoint.cs
  23. 3 3
      Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs
  24. 2 1
      Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs
  25. 116 109
      Emby.Dlna/PlayTo/Device.cs
  26. 3 2
      Emby.Dlna/PlayTo/DeviceInfo.cs
  27. 13 0
      Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
  28. 126 123
      Emby.Dlna/PlayTo/PlayToController.cs
  29. 28 27
      Emby.Dlna/PlayTo/PlayToManager.cs
  30. 1 1
      Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
  31. 1 1
      Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
  32. 1 8
      Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
  33. 0 13
      Emby.Dlna/PlayTo/TRANSPORTSTATE.cs
  34. 31 39
      Emby.Dlna/PlayTo/TransportCommands.cs
  35. 14 0
      Emby.Dlna/PlayTo/TransportState.cs
  36. 8 8
      Emby.Dlna/PlayTo/UpnpContainer.cs
  37. 13 12
      Emby.Dlna/PlayTo/uBaseObject.cs
  38. 58 32
      Emby.Dlna/PlayTo/uPnpNamespaces.cs
  39. 2 2
      Emby.Dlna/Profiles/DefaultProfile.cs
  40. 6 6
      Emby.Dlna/Profiles/DishHopperJoeyProfile.cs
  41. 2 2
      Emby.Dlna/Profiles/LgTvProfile.cs
  42. 1 1
      Emby.Dlna/Profiles/LinksysDMA2100Profile.cs
  43. 1 1
      Emby.Dlna/Profiles/PanasonicVieraProfile.cs
  44. 7 7
      Emby.Dlna/Profiles/PopcornHourProfile.cs
  45. 1 1
      Emby.Dlna/Profiles/SamsungSmartTvProfile.cs
  46. 3 3
      Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
  47. 3 3
      Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
  48. 3 3
      Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
  49. 3 3
      Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
  50. 5 5
      Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs
  51. 22 22
      Emby.Dlna/Profiles/SonyBravia2010Profile.cs
  52. 21 21
      Emby.Dlna/Profiles/SonyBravia2011Profile.cs
  53. 17 17
      Emby.Dlna/Profiles/SonyBravia2012Profile.cs
  54. 18 19
      Emby.Dlna/Profiles/SonyBravia2013Profile.cs
  55. 18 19
      Emby.Dlna/Profiles/SonyBravia2014Profile.cs
  56. 7 7
      Emby.Dlna/Profiles/SonyPs3Profile.cs
  57. 7 7
      Emby.Dlna/Profiles/SonyPs4Profile.cs
  58. 4 4
      Emby.Dlna/Profiles/WdtvLiveProfile.cs
  59. 7 7
      Emby.Dlna/Profiles/XboxOneProfile.cs
  60. 17 17
      Emby.Dlna/Service/BaseControlHandler.cs
  61. 10 8
      Emby.Dlna/Service/BaseService.cs
  62. 5 5
      Emby.Dlna/Service/ControlErrorHandler.cs
  63. 1 1
      Emby.Dlna/Service/ServiceXmlBuilder.cs
  64. 8 9
      Emby.Dlna/Ssdp/DeviceDiscovery.cs
  65. 1 1
      Emby.Dlna/Ssdp/SsdpExtensions.cs
  66. 15 6
      Emby.Server.Implementations/Channels/ChannelManager.cs
  67. 20 31
      Emby.Server.Implementations/Collections/CollectionManager.cs
  68. 10 4
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  69. 1 1
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  70. 3 24
      Emby.Server.Implementations/IO/LibraryMonitor.cs
  71. 35 0
      Emby.Server.Implementations/IO/LibraryMonitorStartup.cs
  72. 16 25
      Emby.Server.Implementations/Library/LibraryManager.cs
  73. 1 1
      Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
  74. 1 1
      Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
  75. 1 1
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  76. 2 3
      Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
  77. 64 58
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  78. 13 10
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  79. 1 1
      Emby.Server.Implementations/Localization/Core/id.json
  80. 1 1
      Emby.Server.Implementations/Localization/Core/ru.json
  81. 10 10
      Emby.Server.Implementations/Playlists/PlaylistManager.cs
  82. 75 72
      Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  83. 1 0
      Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
  84. 32 32
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
  85. 4 4
      Emby.Server.Implementations/Services/ServicePath.cs
  86. 2 2
      Jellyfin.Api/Controllers/AudioController.cs
  87. 8 7
      Jellyfin.Api/Controllers/CollectionController.cs
  88. 1 1
      Jellyfin.Api/Controllers/ConfigurationController.cs
  89. 1 2
      Jellyfin.Api/Controllers/DlnaServerController.cs
  90. 1 1
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  91. 8 8
      Jellyfin.Api/Controllers/ImageController.cs
  92. 4 3
      Jellyfin.Api/Controllers/ItemUpdateController.cs
  93. 2 2
      Jellyfin.Api/Controllers/MediaInfoController.cs
  94. 7 7
      Jellyfin.Api/Controllers/PlaylistsController.cs
  95. 1 1
      Jellyfin.Api/Controllers/RemoteImageController.cs
  96. 6 10
      Jellyfin.Api/Controllers/SessionController.cs
  97. 11 16
      Jellyfin.Api/Controllers/StartupController.cs
  98. 2 2
      Jellyfin.Api/Controllers/UniversalAudioController.cs
  99. 7 7
      Jellyfin.Api/Controllers/VideosController.cs
  100. 22 0
      Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs

+ 1 - 1
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()

+ 2 - 1
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()

+ 0 - 16
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)
-                }
-            };
-        }
-    }
 }

+ 4 - 6
Emby.Dlna/ConnectionManager/ConnectionManager.cs → Emby.Dlna/ConnectionManager/ConnectionManagerService.cs

@@ -9,22 +9,20 @@ 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,
+            ILogger<ConnectionManagerService> logger,
             IHttpClient httpClient)
             : base(logger, httpClient)
         {
             _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);
         }
     }
 }

+ 2 - 2
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"

+ 3 - 3
Emby.Dlna/ContentDirectory/ContentDirectory.cs → Emby.Dlna/ContentDirectory/ContentDirectoryService.cs

@@ -19,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;
@@ -33,14 +33,14 @@ namespace Emby.Dlna.ContentDirectory
         private readonly IMediaEncoder _mediaEncoder;
         private readonly ITVSeriesManager _tvSeriesManager;
 
-        public ContentDirectory(
+        public ContentDirectoryService(
             IDlnaManager dlna,
             IUserDataManager userDataManager,
             IImageProcessor imageProcessor,
             ILibraryManager libraryManager,
             IServerConfigurationManager config,
             IUserManager userManager,
-            ILogger<ContentDirectory> logger,
+            ILogger<ContentDirectoryService> logger,
             IHttpClient httpClient,
             ILocalizationManager localization,
             IMediaSourceManager mediaSourceManager,

+ 3 - 2
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"

+ 66 - 92
Emby.Dlna/ContentDirectory/ControlHandler.cs

@@ -40,6 +40,11 @@ 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;
@@ -47,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;
@@ -181,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);
         }
 
@@ -253,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;
 
@@ -286,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;
@@ -362,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
@@ -397,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);
 
@@ -783,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)
@@ -1135,14 +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[] { nameof(Audio) },
-                ParentId = parent?.Id ?? Guid.Empty,
-                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);
         }
@@ -1151,12 +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);
         }
@@ -1165,14 +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);
         }
@@ -1183,13 +1196,14 @@ namespace Emby.Dlna.ContentDirectory
 
             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();
+                {
+                    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);
         }
@@ -1354,44 +1368,4 @@ namespace Emby.Dlna.ContentDirectory
             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
-    }
 }

+ 23 - 0
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; }
+    }
+}

+ 28 - 0
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
+    }
+}

+ 6 - 6
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();
-        }
     }
 }

+ 1 - 1
Emby.Dlna/ControlResponse.cs

@@ -11,7 +11,7 @@ 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; }
 

+ 69 - 88
Emby.Dlna/Didl/DidlBuilder.cs

@@ -34,12 +34,12 @@ 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;
@@ -100,11 +100,11 @@ namespace Emby.Dlna.Didl
                 {
                     // 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);
+                    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);
@@ -147,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);
@@ -207,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,
@@ -279,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:*");
 
@@ -288,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}:*",
@@ -304,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));
 
@@ -526,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)
             {
@@ -583,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,
@@ -596,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,
@@ -627,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");
@@ -714,7 +717,7 @@ namespace Emby.Dlna.Didl
             // MediaMonkey for example won't display content without a 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);
@@ -723,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);
                 }
             }
 
@@ -731,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))
@@ -748,28 +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 (!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);
                 }
             }
 
@@ -781,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)
             {
@@ -882,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);
             }
         }
 
@@ -896,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)
@@ -917,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);
                 }
             }
         }
@@ -935,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);
@@ -971,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)
             {
@@ -1021,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);
@@ -1043,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();
         }
@@ -1139,7 +1143,6 @@ namespace Emby.Dlna.Didl
 
             if (width == 0 || height == 0)
             {
-                // _imageProcessor.GetImageSize(item, imageInfo);
                 width = null;
                 height = null;
             }
@@ -1149,18 +1152,6 @@ namespace Emby.Dlna.Didl
                 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);
@@ -1177,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);
@@ -1218,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,
@@ -1256,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; }
         }
     }
 }

+ 1 - 3
Emby.Dlna/Didl/Filter.cs

@@ -23,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));
         }
     }
 }

+ 1 - 1
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;

+ 24 - 0
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)
+                }
+            };
+        }
+    }
+}

+ 16 - 15
Emby.Dlna/DlnaManager.cs

@@ -54,11 +54,15 @@ namespace Emby.Dlna
             _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)
@@ -240,7 +244,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);
             }
 
@@ -280,10 +284,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
@@ -495,8 +495,8 @@ namespace Emby.Dlna
         /// 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).
         /// </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))
@@ -509,13 +509,6 @@ namespace Emby.Dlna
             return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
         }
 
-        private 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) ??
@@ -540,7 +533,15 @@ namespace Emby.Dlna
                 Stream = _assembly.GetManifestResourceStream(resource)
             };
         }
+
+        private class InternalProfileInfo
+        {
+            internal DeviceProfileInfo Info { get; set; }
+
+            internal string Path { get; set; }
+        }
     }
+
     /*
     class DlnaProfileEntryPoint : IServerEntryPoint
     {

+ 1 - 1
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-->

+ 1 - 1
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; }
     }
 }

+ 5 - 3
Emby.Dlna/Eventing/DlnaEventManager.cs

@@ -22,6 +22,8 @@ namespace Emby.Dlna.Eventing
         private readonly ILogger _logger;
         private readonly IHttpClient _httpClient;
 
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
         public DlnaEventManager(ILogger logger, IHttpClient httpClient)
         {
             _httpClient = httpClient;
@@ -58,7 +60,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);
@@ -94,7 +97,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
             {
@@ -103,7 +106,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

+ 10 - 0
Emby.Dlna/IDlnaEventManager.cs

@@ -8,16 +8,26 @@ namespace Emby.Dlna
         /// 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);
     }
 }

+ 33 - 23
Emby.Dlna/Main/DlnaEntryPoint.cs

@@ -30,7 +30,7 @@ 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<DlnaEntryPoint> _logger;
@@ -54,13 +54,7 @@ namespace Emby.Dlna.Main
         private SsdpDevicePublisher _publisher;
         private ISsdpCommunicationsServer _communicationsServer;
 
-        public IContentDirectory ContentDirectory { get; private set; }
-
-        public IConnectionManager ConnectionManager { get; private set; }
-
-        public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
-
-        public static DlnaEntryPoint Current;
+        private bool _disposed;
 
         public DlnaEntryPoint(
             IServerConfigurationManager config,
@@ -99,14 +93,14 @@ namespace Emby.Dlna.Main
             _networkManager = networkManager;
             _logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
 
-            ContentDirectory = new ContentDirectory.ContentDirectory(
+            ContentDirectory = new ContentDirectory.ContentDirectoryService(
                 dlnaManager,
                 userDataManager,
                 imageProcessor,
                 libraryManager,
                 config,
                 userManager,
-                loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(),
+                loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
                 httpClient,
                 localizationManager,
                 mediaSourceManager,
@@ -114,19 +108,27 @@ namespace Emby.Dlna.Main
                 mediaEncoder,
                 tvSeriesManager);
 
-            ConnectionManager = new ConnectionManager.ConnectionManager(
+            ConnectionManager = new ConnectionManager.ConnectionManagerService(
                 dlnaManager,
                 config,
-                loggerFactory.CreateLogger<ConnectionManager.ConnectionManager>(),
+                loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
                 httpClient);
 
-            MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(
-                loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrar>(),
+            MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
+                loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
                 httpClient,
                 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);
@@ -399,8 +401,24 @@ namespace Emby.Dlna.Main
             }
         }
 
+        public void DisposeDevicePublisher()
+        {
+            if (_publisher != null)
+            {
+                _logger.LogInformation("Disposing SsdpDevicePublisher");
+                _publisher.Dispose();
+                _publisher = null;
+            }
+        }
+
+        /// <inheritdoc />
         public void Dispose()
         {
+            if (_disposed)
+            {
+                return;
+            }
+
             DisposeDevicePublisher();
             DisposePlayToManager();
             DisposeDeviceDiscovery();
@@ -416,16 +434,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;
         }
     }
 }

+ 3 - 3
Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs → Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs

@@ -8,12 +8,12 @@ using Microsoft.Extensions.Logging;
 
 namespace Emby.Dlna.MediaReceiverRegistrar
 {
-    public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar
+    public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar
     {
         private readonly IServerConfigurationManager _config;
 
-        public MediaReceiverRegistrar(
-            ILogger<MediaReceiverRegistrar> logger,
+        public MediaReceiverRegistrarService(
+            ILogger<MediaReceiverRegistrarService> logger,
             IHttpClient httpClient,
             IServerConfigurationManager config)
             : base(logger, httpClient)

+ 2 - 1
Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs

@@ -10,7 +10,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
     {
         public string GetXml()
         {
-            return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(),
+            return new ServiceXmlBuilder().GetXml(
+                new ServiceActionListBuilder().GetActions(),
                 GetStateVariables());
         }
 

+ 116 - 109
Emby.Dlna/PlayTo/Device.cs

@@ -19,15 +19,40 @@ namespace Emby.Dlna.PlayTo
 {
     public class Device : IDisposable
     {
+        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+        private readonly IHttpClient _httpClient;
+
+        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, IHttpClient httpClient, ILogger logger)
+        {
+            Properties = deviceProperties;
+            _httpClient = httpClient;
+            _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
@@ -43,29 +68,21 @@ namespace Emby.Dlna.PlayTo
 
         public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
 
-        public TRANSPORTSTATE TransportState { get; private set; }
-
-        public bool IsPlaying => TransportState == TRANSPORTSTATE.PLAYING;
+        public TransportState TransportState { get; private set; }
 
-        public bool IsPaused => TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK;
+        public bool IsPlaying => TransportState == TransportState.Playing;
 
-        public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
+        public bool IsPaused => TransportState == TransportState.Paused || TransportState == TransportState.PausedPlayback;
 
-        private readonly IHttpClient _httpClient;
+        public bool IsStopped => TransportState == TransportState.Stopped;
 
-        private readonly ILogger _logger;
+        public Action OnDeviceUnavailable { get; set; }
 
-        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()
         {
@@ -73,8 +90,6 @@ namespace Emby.Dlna.PlayTo
             _timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
         }
 
-        private DateTime _lastVolumeRefresh;
-        private bool _volumeRefreshActive;
         private Task RefreshVolumeIfNeeded()
         {
             if (_volumeRefreshActive
@@ -105,7 +120,6 @@ namespace Emby.Dlna.PlayTo
             }
         }
 
-        private readonly object _timerLock = new object();
         private void RestartTimer(bool immediate = false)
         {
             lock (_timerLock)
@@ -233,6 +247,9 @@ namespace Emby.Dlna.PlayTo
         /// <summary>
         /// 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);
@@ -275,7 +292,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(_httpClient).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);
@@ -285,7 +302,7 @@ namespace Emby.Dlna.PlayTo
         {
             var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
 
-            url = url.Replace("&", "&amp;");
+            url = url.Replace("&", "&amp;", StringComparison.Ordinal);
 
             _logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
 
@@ -297,8 +314,8 @@ namespace Emby.Dlna.PlayTo
 
             var dictionary = new Dictionary<string, string>
             {
-                {"CurrentURI", url},
-                {"CurrentURIMetaData", CreateDidlMeta(metaData)}
+                { "CurrentURI", url },
+                { "CurrentURIMetaData", CreateDidlMeta(metaData) }
             };
 
             var service = GetAvTransportService();
@@ -401,13 +418,11 @@ namespace Emby.Dlna.PlayTo
             await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
                 .ConfigureAwait(false);
 
-            TransportState = TRANSPORTSTATE.PAUSED;
+            TransportState = TransportState.Paused;
 
             RestartTimer(true);
         }
 
-        private int _connectFailureCount;
-
         private async void TimerCallback(object sender)
         {
             if (_disposed)
@@ -436,7 +451,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);
                     }
@@ -465,7 +480,7 @@ namespace Emby.Dlna.PlayTo
                     }
 
                     // 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();
                     }
@@ -539,7 +554,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))
@@ -589,14 +604,14 @@ namespace Emby.Dlna.PlayTo
                 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)
@@ -623,12 +638,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;
             }
@@ -636,7 +651,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)
@@ -671,7 +686,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;
 
@@ -687,13 +702,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
                 };
@@ -702,7 +717,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)
@@ -731,11 +746,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))
@@ -747,8 +762,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))
             {
@@ -787,7 +802,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);
 
@@ -819,7 +834,7 @@ namespace Emby.Dlna.PlayTo
             // some devices send back invalid xml
             try
             {
-                return XElement.Parse(xml.Replace("&", "&amp;"));
+                return XElement.Parse(xml.Replace("&", "&amp;", StringComparison.Ordinal));
             }
             catch (XmlException)
             {
@@ -828,27 +843,27 @@ namespace Emby.Dlna.PlayTo
             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()
@@ -862,11 +877,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))
                 {
@@ -941,12 +956,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;
             }
@@ -954,11 +969,7 @@ 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, IHttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
         {
             var ssdpHttpClient = new SsdpHttpClient(httpClient);
 
@@ -966,13 +977,13 @@ namespace Emby.Dlna.PlayTo
 
             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);
@@ -981,77 +992,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;
@@ -1068,10 +1079,9 @@ namespace Emby.Dlna.PlayTo
                 }
             }
 
-            return new Device(deviceProperties, httpClient, logger, config);
+            return new Device(deviceProperties, httpClient, logger);
         }
 
-        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
         private static DeviceIcon CreateIcon(XElement element)
         {
             if (element == null)
@@ -1079,11 +1089,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 +1110,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 +1126,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 +1135,7 @@ namespace Emby.Dlna.PlayTo
 
             if (previousMediaInfo == null && mediaInfo != null)
             {
-                if (state != TRANSPORTSTATE.STOPPED)
+                if (state != TransportState.Stopped)
                 {
                     OnPlaybackStart(mediaInfo);
                 }
@@ -1151,7 +1154,7 @@ namespace Emby.Dlna.PlayTo
             }
         }
 
-        private void OnPlaybackStart(uBaseObject mediaInfo)
+        private void OnPlaybackStart(UBaseObject mediaInfo)
         {
             if (string.IsNullOrWhiteSpace(mediaInfo.Url))
             {
@@ -1164,7 +1167,7 @@ namespace Emby.Dlna.PlayTo
             });
         }
 
-        private void OnPlaybackProgress(uBaseObject mediaInfo)
+        private void OnPlaybackProgress(UBaseObject mediaInfo)
         {
             if (string.IsNullOrWhiteSpace(mediaInfo.Url))
             {
@@ -1177,7 +1180,7 @@ namespace Emby.Dlna.PlayTo
             });
         }
 
-        private void OnPlaybackStop(uBaseObject mediaInfo)
+        private void OnPlaybackStop(UBaseObject mediaInfo)
         {
             PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
             {
@@ -1185,7 +1188,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,14 +1197,17 @@ namespace Emby.Dlna.PlayTo
             });
         }
 
-        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)
@@ -1220,9 +1226,10 @@ namespace Emby.Dlna.PlayTo
             _disposed = true;
         }
 
+        /// <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);
         }
     }
 }

+ 3 - 2
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()

+ 13 - 0
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; }
+    }
+}

+ 126 - 123
Emby.Dlna/PlayTo/PlayToController.cs

@@ -31,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;
@@ -50,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;
@@ -372,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);
@@ -498,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];
             }
@@ -633,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)
@@ -673,48 +684,41 @@ namespace Emby.Dlna.PlayTo
                     case GeneralCommandType.ToggleMute:
                         return _device.ToggleMute(cancellationToken);
                     case GeneralCommandType.SetAudioStreamIndex:
+                        if (command.Arguments.TryGetValue("Index", out string index))
                         {
-                            if (command.Arguments.TryGetValue("Index", out string arg))
+                            if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
                             {
-                                if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var val))
-                                {
-                                    return SetAudioStreamIndex(val);
-                                }
-
-                                throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
+                                return SetAudioStreamIndex(val);
                             }
 
-                            throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
+                            throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
                         }
+
+                        throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
                     case GeneralCommandType.SetSubtitleStreamIndex:
+                        if (command.Arguments.TryGetValue("Index", out index))
                         {
-                            if (command.Arguments.TryGetValue("Index", out string arg))
+                            if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
                             {
-                                if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var val))
-                                {
-                                    return SetSubtitleStreamIndex(val);
-                                }
-
-                                throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
+                                return SetSubtitleStreamIndex(val);
                             }
 
-                            throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
+                            throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
                         }
+
+                        throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
                     case GeneralCommandType.SetVolume:
+                        if (command.Arguments.TryGetValue("Volume", out string vol))
                         {
-                            if (command.Arguments.TryGetValue("Volume", out string arg))
+                            if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
                             {
-                                if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var volume))
-                                {
-                                    return _device.SetVolume(volume, cancellationToken);
-                                }
-
-                                throw new ArgumentException("Unsupported volume value supplied.");
+                                return _device.SetVolume(volume, cancellationToken);
                             }
 
-                            throw new ArgumentException("Volume argument cannot be null");
+                            throw new ArgumentException("Unsupported volume value supplied.");
                         }
 
+                        throw new ArgumentException("Volume argument cannot be null");
                     default:
                         return Task.CompletedTask;
                 }
@@ -778,7 +782,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;
@@ -787,8 +791,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>(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;
+        }
+
         private class StreamParams
         {
+            private MediaSourceInfo mediaSource;
+            private IMediaSourceManager _mediaSourceManager;
+
             public Guid ItemId { get; set; }
 
             public bool IsDirectStream { get; set; }
@@ -809,15 +872,11 @@ namespace Emby.Dlna.PlayTo
 
             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;
@@ -827,9 +886,9 @@ namespace Emby.Dlna.PlayTo
                     return null;
                 }
 
-                MediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
+                mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
 
-                return MediaSource;
+                return mediaSource;
             }
 
             private static Guid GetItemId(string url)
@@ -901,61 +960,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;
-        }
     }
 }

+ 28 - 27
Emby.Dlna/PlayTo/PlayToManager.cs

@@ -92,7 +92,7 @@ namespace Emby.Dlna.PlayTo
 
             // 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);
                 return;
@@ -174,7 +174,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, _httpClient, _logger, cancellationToken).ConfigureAwait(false);
 
                 string deviceName = device.Properties.Name;
 
@@ -192,20 +192,20 @@ namespace Emby.Dlna.PlayTo
 
                 controller = new PlayToController(
                     sessionInfo,
-                   _sessionManager,
-                   _libraryManager,
-                   _logger,
-                   _dlnaManager,
-                   _userManager,
-                   _imageProcessor,
-                   serverAddress,
-                   null,
-                   _deviceDiscovery,
-                   _userDataManager,
-                   _localization,
-                   _mediaSourceManager,
-                   _config,
-                   _mediaEncoder);
+                    _sessionManager,
+                    _libraryManager,
+                    _logger,
+                    _dlnaManager,
+                    _userManager,
+                    _imageProcessor,
+                    serverAddress,
+                    null,
+                    _deviceDiscovery,
+                    _userDataManager,
+                    _localization,
+                    _mediaSourceManager,
+                    _config,
+                    _mediaEncoder);
 
                 sessionInfo.AddController(controller);
 
@@ -218,17 +218,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.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()
                     },
 
                     SupportsMediaControl = true
@@ -247,8 +247,9 @@ namespace Emby.Dlna.PlayTo
             {
                 _disposeCancellationTokenSource.Cancel();
             }
-            catch
+            catch (Exception ex)
             {
+                _logger.LogDebug(ex, "Error while disposing PlayToManager");
             }
 
             _sessionLock.Dispose();

+ 1 - 1
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; }
     }
 }

+ 1 - 1
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; }
     }
 }

+ 1 - 8
Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs

@@ -6,13 +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; }
     }
 }

+ 0 - 13
Emby.Dlna/PlayTo/TRANSPORTSTATE.cs

@@ -1,13 +0,0 @@
-#pragma warning disable CS1591
-
-namespace Emby.Dlna.PlayTo
-{
-    public enum TRANSPORTSTATE
-    {
-        STOPPED,
-        PLAYING,
-        TRANSITIONING,
-        PAUSED_PLAYBACK,
-        PAUSED
-    }
-}

+ 31 - 39
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>";
     }
 }

+ 14 - 0
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
+    }
+}

+ 8 - 8
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)
             };
         }
     }

+ 13 - 12
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
@@ -58,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);
+        }
     }
 }

+ 58 - 32
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";
     }
 }

+ 2 - 2
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
                 }
             };

+ 6 - 6
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

+ 2 - 2
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
                 {

+ 1 - 1
Emby.Dlna/Profiles/LinksysDMA2100Profile.cs

@@ -32,7 +32,7 @@ namespace Emby.Dlna.Profiles
                 }
             };
 
-            ResponseProfiles = new ResponseProfile[]
+            ResponseProfiles = new[]
             {
                 new ResponseProfile
                 {

+ 1 - 1
Emby.Dlna/Profiles/PanasonicVieraProfile.cs

@@ -138,7 +138,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = DlnaProfileType.Photo,
 
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {

+ 7 - 7
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
                 {

+ 1 - 1
Emby.Dlna/Profiles/SamsungSmartTvProfile.cs

@@ -139,7 +139,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = DlnaProfileType.Photo,
 
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {

+ 3 - 3
Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs

@@ -150,7 +150,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
                         {
@@ -197,7 +197,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = DlnaProfileType.Photo,
 
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {

+ 3 - 3
Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs

@@ -150,7 +150,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
                         {
@@ -197,7 +197,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = DlnaProfileType.Photo,
 
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {

+ 3 - 3
Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs

@@ -138,7 +138,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = CodecType.Video,
                     Codec = "h264",
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {
@@ -166,7 +166,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = CodecType.VideoAudio,
                     Codec = "ac3",
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {
@@ -185,7 +185,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = DlnaProfileType.Photo,
 
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {

+ 3 - 3
Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs

@@ -138,7 +138,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = CodecType.Video,
                     Codec = "h264",
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {
@@ -166,7 +166,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = CodecType.VideoAudio,
                     Codec = "ac3",
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {
@@ -185,7 +185,7 @@ namespace Emby.Dlna.Profiles
                 {
                     Type = DlnaProfileType.Photo,
 
-                    Conditions = new []
+                    Conditions = new[]
                     {
                         new ProfileCondition
                         {

+ 5 - 5
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
                 },
 

+ 22 - 22
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
                         {

+ 21 - 21
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
                         {

+ 17 - 17
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
                         {

+ 18 - 19
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
                         {

+ 18 - 19
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
                         {

+ 7 - 7
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
                 },
 

+ 7 - 7
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
                 },
 

+ 4 - 4
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
                         {

+ 7 - 7
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
                         {

+ 17 - 17
Emby.Dlna/Service/BaseControlHandler.cs

@@ -15,11 +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)
         {
@@ -27,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
@@ -80,10 +80,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);
@@ -210,15 +210,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)
@@ -240,5 +231,14 @@ namespace Emby.Dlna.Service
 
             Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
         }
+
+        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);
+        }
     }
 }

+ 10 - 8
Emby.Dlna/Service/BaseService.cs

@@ -8,31 +8,33 @@ namespace Emby.Dlna.Service
 {
     public class BaseService : IDlnaEventManager
     {
-        protected IDlnaEventManager _dlnaEventManager;
-        protected IHttpClient HttpClient;
-        protected ILogger Logger;
-
         protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
         {
             Logger = logger;
             HttpClient = httpClient;
 
-            _dlnaEventManager = new DlnaEventManager(logger, HttpClient);
+            EventManager = new DlnaEventManager(logger, HttpClient);
         }
 
+        protected IDlnaEventManager EventManager { get; }
+
+        protected IHttpClient HttpClient { get; }
+
+        protected ILogger Logger { get; }
+
         public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
         {
-            return _dlnaEventManager.CancelEventSubscription(subscriptionId);
+            return EventManager.CancelEventSubscription(subscriptionId);
         }
 
         public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string timeoutString, string callbackUrl)
         {
-            return _dlnaEventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callbackUrl);
+            return EventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callbackUrl);
         }
 
         public EventSubscriptionResponse CreateEventSubscription(string notificationType, string timeoutString, string callbackUrl)
         {
-            return _dlnaEventManager.CreateEventSubscription(notificationType, timeoutString, callbackUrl);
+            return EventManager.CreateEventSubscription(notificationType, timeoutString, callbackUrl);
         }
     }
 }

+ 5 - 5
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);

+ 1 - 1
Emby.Dlna/Service/ServiceXmlBuilder.cs

@@ -87,7 +87,7 @@ namespace Emby.Dlna.Service
                     .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)

+ 8 - 9
Emby.Dlna/Ssdp/DeviceDiscovery.cs

@@ -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)
         {

+ 1 - 1
Emby.Dlna/Ssdp/Extensions.cs → Emby.Dlna/Ssdp/SsdpExtensions.cs

@@ -5,7 +5,7 @@ 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)
         {

+ 15 - 6
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -746,12 +746,21 @@ namespace Emby.Server.Implementations.Channels
             // null if came from cache
             if (itemsResult != null)
             {
-                var internalItems = itemsResult.Items
-                    .Select(i => GetChannelItemEntity(i, channelProvider, channel.Id, parentItem, cancellationToken))
-                    .ToArray();
+                var items = itemsResult.Items;
+                var itemsLen = items.Count;
+                var internalItems = new Guid[itemsLen];
+                for (int i = 0; i < itemsLen; i++)
+                {
+                    internalItems[i] = (await GetChannelItemEntityAsync(
+                        items[i],
+                        channelProvider,
+                        channel.Id,
+                        parentItem,
+                        cancellationToken).ConfigureAwait(false)).Id;
+                }
 
                 var existingIds = _libraryManager.GetItemIds(query);
-                var deadIds = existingIds.Except(internalItems.Select(i => i.Id))
+                var deadIds = existingIds.Except(internalItems)
                     .ToArray();
 
                 foreach (var deadId in deadIds)
@@ -963,7 +972,7 @@ namespace Emby.Server.Implementations.Channels
             return item;
         }
 
-        private BaseItem GetChannelItemEntity(ChannelItemInfo info, IChannel channelProvider, Guid internalChannelId, BaseItem parentFolder, CancellationToken cancellationToken)
+        private async Task<BaseItem> GetChannelItemEntityAsync(ChannelItemInfo info, IChannel channelProvider, Guid internalChannelId, BaseItem parentFolder, CancellationToken cancellationToken)
         {
             var parentFolderId = parentFolder.Id;
 
@@ -1165,7 +1174,7 @@ namespace Emby.Server.Implementations.Channels
             }
             else if (forceUpdate)
             {
-                item.UpdateToRepository(ItemUpdateType.None, cancellationToken);
+                await item.UpdateToRepositoryAsync(ItemUpdateType.None, cancellationToken).ConfigureAwait(false);
             }
 
             if ((isNew || forceUpdate) && info.Type == ChannelItemType.Media)

+ 20 - 31
Emby.Server.Implementations/Collections/CollectionManager.cs

@@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Collections
         }
 
         /// <inheritdoc />
-        public BoxSet CreateCollection(CollectionCreationOptions options)
+        public async Task<BoxSet> CreateCollectionAsync(CollectionCreationOptions options)
         {
             var name = options.Name;
 
@@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.Collections
             // This could cause it to get re-resolved as a plain folder
             var folderName = _fileSystem.GetValidFilename(name) + " [boxset]";
 
-            var parentFolder = GetCollectionsFolder(true).GetAwaiter().GetResult();
+            var parentFolder = await GetCollectionsFolder(true).ConfigureAwait(false);
 
             if (parentFolder == null)
             {
@@ -169,12 +169,16 @@ namespace Emby.Server.Implementations.Collections
 
                 if (options.ItemIdList.Length > 0)
                 {
-                    AddToCollection(collection.Id, options.ItemIdList, false, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
-                    {
-                        // The initial adding of items is going to create a local metadata file
-                        // This will cause internet metadata to be skipped as a result
-                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh
-                    });
+                    await AddToCollectionAsync(
+                        collection.Id,
+                        options.ItemIdList.Select(x => new Guid(x)),
+                        false,
+                        new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+                        {
+                            // The initial adding of items is going to create a local metadata file
+                            // This will cause internet metadata to be skipped as a result
+                            MetadataRefreshMode = MetadataRefreshMode.FullRefresh
+                        }).ConfigureAwait(false);
                 }
                 else
                 {
@@ -197,18 +201,10 @@ namespace Emby.Server.Implementations.Collections
         }
 
         /// <inheritdoc />
-        public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
-        {
-            AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
-        }
+        public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids)
+            => AddToCollectionAsync(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
 
-        /// <inheritdoc />
-        public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
-        {
-            AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
-        }
-
-        private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
+        private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
         {
             var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
             if (collection == null)
@@ -224,15 +220,14 @@ namespace Emby.Server.Implementations.Collections
 
             foreach (var id in ids)
             {
-                var guidId = new Guid(id);
-                var item = _libraryManager.GetItemById(guidId);
+                var item = _libraryManager.GetItemById(id);
 
                 if (item == null)
                 {
                     throw new ArgumentException("No item exists with the supplied Id");
                 }
 
-                if (!currentLinkedChildrenIds.Contains(guidId))
+                if (!currentLinkedChildrenIds.Contains(id))
                 {
                     itemList.Add(item);
 
@@ -249,7 +244,7 @@ namespace Emby.Server.Implementations.Collections
 
                 collection.UpdateRatingToItems(linkedChildrenList);
 
-                collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
                 refreshOptions.ForceSave = true;
                 _providerManager.QueueRefresh(collection.Id, refreshOptions, RefreshPriority.High);
@@ -266,13 +261,7 @@ namespace Emby.Server.Implementations.Collections
         }
 
         /// <inheritdoc />
-        public void RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds)
-        {
-            RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i)));
-        }
-
-        /// <inheritdoc />
-        public void RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
+        public async Task RemoveFromCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds)
         {
             var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
 
@@ -309,7 +298,7 @@ namespace Emby.Server.Implementations.Collections
                 collection.LinkedChildren = collection.LinkedChildren.Except(list).ToArray();
             }
 
-            collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             _providerManager.QueueRefresh(
                 collection.Id,
                 new MetadataRefreshOptions(new DirectoryService(_fileSystem))

+ 10 - 4
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -90,6 +90,9 @@ namespace Emby.Server.Implementations.Data
             _typeMapper = new TypeMapper();
             _jsonOptions = JsonDefaults.GetOptions();
 
+            // GetItem throws NotSupportedException with this enabled, so hardcode false.
+            _jsonOptions.IgnoreNullValues = false;
+
             DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
         }
 
@@ -4308,7 +4311,7 @@ namespace Emby.Server.Implementations.Data
                 whereClauses.Add("ProductionYear=@Years");
                 if (statement != null)
                 {
-                    statement.TryBind("@Years", query.Years[0].ToString());
+                    statement.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
                 }
             }
             else if (query.Years.Length > 1)
@@ -4560,13 +4563,13 @@ namespace Emby.Server.Implementations.Data
             if (query.AncestorIds.Length > 1)
             {
                 var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
-                whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
+                whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
             }
 
             if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
             {
                 var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
-                whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
+                whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
                 if (statement != null)
                 {
                     statement.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
@@ -5170,7 +5173,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                     insertText.Append(',');
                 }
 
-                insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture));
+                insertText.AppendFormat(
+                    CultureInfo.InvariantCulture,
+                    "(@ItemId, @AncestorId{0}, @AncestorIdText{0})",
+                    i.ToString(CultureInfo.InvariantCulture));
             }
 
             using (var statement = PrepareStatement(db, insertText.ToString()))

+ 1 - 1
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -41,7 +41,7 @@
     <PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
     <PackageReference Include="sharpcompress" Version="0.26.0" />
     <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
-    <PackageReference Include="DotNet.Glob" Version="3.0.9" />
+    <PackageReference Include="DotNet.Glob" Version="3.1.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 3 - 24
Emby.Server.Implementations/IO/LibraryMonitor.cs

@@ -6,12 +6,11 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
+using Emby.Server.Implementations.Library;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Model.IO;
-using Emby.Server.Implementations.Library;
 using Microsoft.Extensions.Logging;
 
 namespace Emby.Server.Implementations.IO
@@ -38,6 +37,8 @@ namespace Emby.Server.Implementations.IO
         /// </summary>
         private readonly ConcurrentDictionary<string, string> _tempIgnoredPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
+        private bool _disposed = false;
+
         /// <summary>
         /// Add the path to our temporary ignore list.  Use when writing to a path within our listening scope.
         /// </summary>
@@ -492,8 +493,6 @@ namespace Emby.Server.Implementations.IO
             }
         }
 
-        private bool _disposed = false;
-
         /// <summary>
         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
         /// </summary>
@@ -522,24 +521,4 @@ namespace Emby.Server.Implementations.IO
             _disposed = true;
         }
     }
-
-    public class LibraryMonitorStartup : IServerEntryPoint
-    {
-        private readonly ILibraryMonitor _monitor;
-
-        public LibraryMonitorStartup(ILibraryMonitor monitor)
-        {
-            _monitor = monitor;
-        }
-
-        public Task RunAsync()
-        {
-            _monitor.Start();
-            return Task.CompletedTask;
-        }
-
-        public void Dispose()
-        {
-        }
-    }
 }

+ 35 - 0
Emby.Server.Implementations/IO/LibraryMonitorStartup.cs

@@ -0,0 +1,35 @@
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Plugins;
+
+namespace Emby.Server.Implementations.IO
+{
+    /// <summary>
+    /// <see cref="IServerEntryPoint" /> which is responsible for starting the library monitor.
+    /// </summary>
+    public sealed class LibraryMonitorStartup : IServerEntryPoint
+    {
+        private readonly ILibraryMonitor _monitor;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LibraryMonitorStartup"/> class.
+        /// </summary>
+        /// <param name="monitor">The library monitor.</param>
+        public LibraryMonitorStartup(ILibraryMonitor monitor)
+        {
+            _monitor = monitor;
+        }
+
+        /// <inheritdoc />
+        public Task RunAsync()
+        {
+            _monitor.Start();
+            return Task.CompletedTask;
+        }
+
+        /// <inheritdoc />
+        public void Dispose()
+        {
+        }
+    }
+}

+ 16 - 25
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -729,7 +729,7 @@ namespace Emby.Server.Implementations.Library
             Directory.CreateDirectory(rootFolderPath);
 
             var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
-                             ((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
+                             ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
                              .DeepCopy<Folder, AggregateFolder>();
 
             // In case program data folder was moved
@@ -771,7 +771,7 @@ namespace Emby.Server.Implementations.Library
             if (folder.ParentId != rootFolder.Id)
             {
                 folder.ParentId = rootFolder.Id;
-                folder.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
+                folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
             }
 
             rootFolder.AddVirtualChild(folder);
@@ -1868,7 +1868,8 @@ namespace Emby.Server.Implementations.Library
             return image.Path != null && !image.IsLocalFile;
         }
 
-        public void UpdateImages(BaseItem item, bool forceUpdate = false)
+        /// <inheritdoc />
+        public async Task UpdateImagesAsync(BaseItem item, bool forceUpdate = false)
         {
             if (item == null)
             {
@@ -1891,7 +1892,7 @@ namespace Emby.Server.Implementations.Library
                     try
                     {
                         var index = item.GetImageIndex(img);
-                        image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult();
+                        image = await ConvertImageToLocal(item, img, index).ConfigureAwait(false);
                     }
                     catch (ArgumentException)
                     {
@@ -1913,7 +1914,7 @@ namespace Emby.Server.Implementations.Library
                 }
                 catch (Exception ex)
                 {
-                    _logger.LogError(ex, "Cannnot get image dimensions for {0}", image.Path);
+                    _logger.LogError(ex, "Cannot get image dimensions for {0}", image.Path);
                     image.Width = 0;
                     image.Height = 0;
                     continue;
@@ -1943,10 +1944,8 @@ namespace Emby.Server.Implementations.Library
             RegisterItem(item);
         }
 
-        /// <summary>
-        /// Updates the item.
-        /// </summary>
-        public void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+        /// <inheritdoc />
+        public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
         {
             foreach (var item in items)
             {
@@ -1957,7 +1956,7 @@ namespace Emby.Server.Implementations.Library
 
                 item.DateLastSaved = DateTime.UtcNow;
 
-                UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
+                await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
             }
 
             _itemRepository.SaveItems(items, cancellationToken);
@@ -1991,17 +1990,9 @@ namespace Emby.Server.Implementations.Library
             }
         }
 
-        /// <summary>
-        /// Updates the item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="parent">The parent item.</param>
-        /// <param name="updateReason">The update reason.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
-        {
-            UpdateItems(new[] { item }, parent, updateReason, cancellationToken);
-        }
+        /// <inheritdoc />
+        public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+            => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
 
         /// <summary>
         /// Reports the item removed.
@@ -2233,7 +2224,7 @@ namespace Emby.Server.Implementations.Library
 
             if (refresh)
             {
-                item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
+                item.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
                 ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
             }
 
@@ -2420,7 +2411,7 @@ namespace Emby.Server.Implementations.Library
             if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
             {
                 item.ViewType = viewType;
-                item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
             }
 
             var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
@@ -2902,7 +2893,7 @@ namespace Emby.Server.Implementations.Library
 
                     await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
 
-                    item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+                    await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
 
                     return item.GetImageInfo(image.Type, imageIndex);
                 }
@@ -2920,7 +2911,7 @@ namespace Emby.Server.Implementations.Library
 
             // Remove this image to prevent it from retrying over and over
             item.RemoveImage(image);
-            item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+            await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
 
             throw new InvalidOperationException();
         }

+ 1 - 1
Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs

@@ -230,7 +230,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
             if (filters.Count > 0)
             {
-                output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray()));
+                output += string.Format(CultureInfo.InvariantCulture, " -vf \"{0}\"", string.Join(",", filters.ToArray()));
             }
 
             return output;

+ 1 - 1
Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs

@@ -5,7 +5,7 @@ using MediaBrowser.Controller.Plugins;
 
 namespace Emby.Server.Implementations.LiveTv.EmbyTV
 {
-    public class EntryPoint : IServerEntryPoint
+    public sealed class EntryPoint : IServerEntryPoint
     {
         /// <inheritdoc />
         public Task RunAsync()

+ 1 - 1
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -929,7 +929,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
         private static string NormalizeName(string value)
         {
-            return value.Replace(" ", string.Empty).Replace("-", string.Empty);
+            return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
         }
 
         public class ScheduleDirect

+ 2 - 3
Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs

@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                     && !programInfo.IsRepeat
                     && (programInfo.EpisodeNumber ?? 0) == 0)
                 {
-                    programInfo.ShowId = programInfo.ShowId + programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture);
+                    programInfo.ShowId += programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture);
                 }
             }
             else
@@ -246,7 +246,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             }
 
             // Construct an id from the channel and start date
-            programInfo.Id = string.Format("{0}_{1:O}", program.ChannelId, program.StartDate);
+            programInfo.Id = string.Format(CultureInfo.InvariantCulture, "{0}_{1:O}", program.ChannelId, program.StartDate);
 
             if (programInfo.IsMovie)
             {
@@ -296,7 +296,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 Name = c.DisplayName,
                 ImageUrl = c.Icon != null && !string.IsNullOrEmpty(c.Icon.Source) ? c.Icon.Source : null,
                 Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number
-
             }).ToList();
         }
     }

+ 64 - 58
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -41,6 +41,7 @@ namespace Emby.Server.Implementations.LiveTv
     /// </summary>
     public class LiveTvManager : ILiveTvManager, IDisposable
     {
+        private const int MaxGuideDays = 14;
         private const string ExternalServiceTag = "ExternalServiceId";
 
         private const string EtagKey = "ProgramEtag";
@@ -421,7 +422,7 @@ namespace Emby.Server.Implementations.LiveTv
             }
         }
 
-        private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
+        private async Task<LiveTvChannel> GetChannelAsync(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
         {
             var parentFolderId = parentFolder.Id;
             var isNew = false;
@@ -511,7 +512,7 @@ namespace Emby.Server.Implementations.LiveTv
             }
             else if (forceUpdate)
             {
-                _libraryManager.UpdateItem(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken);
+                await _libraryManager.UpdateItemAsync(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
             }
 
             return item;
@@ -560,7 +561,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             item.Audio = info.Audio;
             item.ChannelId = channel.Id;
-            item.CommunityRating = item.CommunityRating ?? info.CommunityRating;
+            item.CommunityRating ??= info.CommunityRating;
             if ((item.CommunityRating ?? 0).Equals(0))
             {
                 item.CommunityRating = null;
@@ -645,8 +646,8 @@ namespace Emby.Server.Implementations.LiveTv
             item.IsSeries = isSeries;
 
             item.Name = info.Name;
-            item.OfficialRating = item.OfficialRating ?? info.OfficialRating;
-            item.Overview = item.Overview ?? info.Overview;
+            item.OfficialRating ??= info.OfficialRating;
+            item.Overview ??= info.Overview;
             item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
             item.ProviderIds = info.ProviderIds;
 
@@ -683,19 +684,23 @@ namespace Emby.Server.Implementations.LiveTv
             {
                 if (!string.IsNullOrWhiteSpace(info.ImagePath))
                 {
-                    item.SetImage(new ItemImageInfo
-                    {
-                        Path = info.ImagePath,
-                        Type = ImageType.Primary
-                    }, 0);
+                    item.SetImage(
+                        new ItemImageInfo
+                        {
+                            Path = info.ImagePath,
+                            Type = ImageType.Primary
+                        },
+                        0);
                 }
                 else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
                 {
-                    item.SetImage(new ItemImageInfo
-                    {
-                        Path = info.ImageUrl,
-                        Type = ImageType.Primary
-                    }, 0);
+                    item.SetImage(
+                        new ItemImageInfo
+                        {
+                            Path = info.ImageUrl,
+                            Type = ImageType.Primary
+                        },
+                        0);
                 }
             }
 
@@ -703,11 +708,13 @@ namespace Emby.Server.Implementations.LiveTv
             {
                 if (!string.IsNullOrWhiteSpace(info.ThumbImageUrl))
                 {
-                    item.SetImage(new ItemImageInfo
-                    {
-                        Path = info.ThumbImageUrl,
-                        Type = ImageType.Thumb
-                    }, 0);
+                    item.SetImage(
+                        new ItemImageInfo
+                        {
+                            Path = info.ThumbImageUrl,
+                            Type = ImageType.Thumb
+                        },
+                        0);
                 }
             }
 
@@ -715,11 +722,13 @@ namespace Emby.Server.Implementations.LiveTv
             {
                 if (!string.IsNullOrWhiteSpace(info.LogoImageUrl))
                 {
-                    item.SetImage(new ItemImageInfo
-                    {
-                        Path = info.LogoImageUrl,
-                        Type = ImageType.Logo
-                    }, 0);
+                    item.SetImage(
+                        new ItemImageInfo
+                        {
+                            Path = info.LogoImageUrl,
+                            Type = ImageType.Logo
+                        },
+                        0);
                 }
             }
 
@@ -727,11 +736,13 @@ namespace Emby.Server.Implementations.LiveTv
             {
                 if (!string.IsNullOrWhiteSpace(info.BackdropImageUrl))
                 {
-                    item.SetImage(new ItemImageInfo
-                    {
-                        Path = info.BackdropImageUrl,
-                        Type = ImageType.Backdrop
-                    }, 0);
+                    item.SetImage(
+                        new ItemImageInfo
+                        {
+                            Path = info.BackdropImageUrl,
+                            Type = ImageType.Backdrop
+                        },
+                        0);
                 }
             }
 
@@ -786,7 +797,6 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (query.OrderBy.Count == 0)
             {
-
                 // Unless something else was specified, order by start date to take advantage of a specialized index
                 query.OrderBy = new[]
                 {
@@ -824,7 +834,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
             {
-                var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
+                var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
                 var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N", CultureInfo.InvariantCulture), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
                 if (seriesTimer != null)
                 {
@@ -847,13 +857,11 @@ namespace Emby.Server.Implementations.LiveTv
 
             var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
 
-            var result = new QueryResult<BaseItemDto>
+            return new QueryResult<BaseItemDto>
             {
                 Items = returnArray,
                 TotalRecordCount = queryResult.TotalRecordCount
             };
-
-            return result;
         }
 
         public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
@@ -1121,7 +1129,7 @@ namespace Emby.Server.Implementations.LiveTv
 
                 try
                 {
-                    var item = GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken);
+                    var item = await GetChannelAsync(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken).ConfigureAwait(false);
 
                     list.Add(item);
                 }
@@ -1138,7 +1146,7 @@ namespace Emby.Server.Implementations.LiveTv
                 double percent = numComplete;
                 percent /= allChannelsList.Count;
 
-                progress.Report(5 * percent + 10);
+                progress.Report((5 * percent) + 10);
             }
 
             progress.Report(15);
@@ -1173,7 +1181,6 @@ namespace Emby.Server.Implementations.LiveTv
 
                     var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
                     {
-
                         IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
                         ChannelIds = new Guid[] { currentChannel.Id },
                         DtoOptions = new DtoOptions(true)
@@ -1214,7 +1221,11 @@ namespace Emby.Server.Implementations.LiveTv
 
                     if (updatedPrograms.Count > 0)
                     {
-                        _libraryManager.UpdateItems(updatedPrograms, currentChannel, ItemUpdateType.MetadataImport, cancellationToken);
+                        await _libraryManager.UpdateItemsAsync(
+                            updatedPrograms,
+                            currentChannel,
+                            ItemUpdateType.MetadataImport,
+                            cancellationToken).ConfigureAwait(false);
                     }
 
                     currentChannel.IsMovie = isMovie;
@@ -1227,7 +1238,7 @@ namespace Emby.Server.Implementations.LiveTv
                         currentChannel.AddTag("Kids");
                     }
 
-                    currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
+                    await currentChannel.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
                     await currentChannel.RefreshMetadata(
                         new MetadataRefreshOptions(new DirectoryService(_fileSystem))
                         {
@@ -1298,8 +1309,6 @@ namespace Emby.Server.Implementations.LiveTv
             }
         }
 
-        private const int MaxGuideDays = 14;
-
         private double GetGuideDays()
         {
             var config = GetConfiguration();
@@ -1712,7 +1721,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (timer == null)
             {
-                throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
+                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "Timer with Id {0} not found", id));
             }
 
             var service = GetService(timer.ServiceName);
@@ -1731,7 +1740,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (timer == null)
             {
-                throw new ResourceNotFoundException(string.Format("SeriesTimer with Id {0} not found", id));
+                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "SeriesTimer with Id {0} not found", id));
             }
 
             var service = GetService(timer.ServiceName);
@@ -1743,10 +1752,12 @@ namespace Emby.Server.Implementations.LiveTv
 
         public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
         {
-            var results = await GetTimers(new TimerQuery
-            {
-                Id = id
-            }, cancellationToken).ConfigureAwait(false);
+            var results = await GetTimers(
+                new TimerQuery
+                {
+                    Id = id
+                },
+                cancellationToken).ConfigureAwait(false);
 
             return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
         }
@@ -1794,10 +1805,7 @@ namespace Emby.Server.Implementations.LiveTv
             }
 
             var returnArray = timers
-                .Select(i =>
-                {
-                    return i.Item1;
-                })
+                .Select(i => i.Item1)
                 .ToArray();
 
             return new QueryResult<SeriesTimerInfo>
@@ -1968,7 +1976,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             if (service == null)
             {
-                service = _services.First();
+                service = _services[0];
             }
 
             var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
@@ -1994,9 +2002,7 @@ namespace Emby.Server.Implementations.LiveTv
         {
             var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
 
-            var obj = _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
-
-            return obj;
+            return _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
         }
 
         public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
@@ -2125,6 +2131,7 @@ namespace Emby.Server.Implementations.LiveTv
         public void Dispose()
         {
             Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
         private bool _disposed = false;
@@ -2447,8 +2454,7 @@ namespace Emby.Server.Implementations.LiveTv
                 .SelectMany(i => i.Locations)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .Select(i => _libraryManager.FindByPath(i, true))
-                .Where(i => i != null)
-                .Where(i => i.IsVisibleStandalone(user))
+                .Where(i => i != null && i.IsVisibleStandalone(user))
                 .SelectMany(i => _libraryManager.GetCollectionFolders(i))
                 .GroupBy(x => x.Id)
                 .Select(x => x.First())

+ 13 - 10
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -37,6 +37,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         private readonly INetworkManager _networkManager;
         private readonly IStreamHelper _streamHelper;
 
+        private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
+
         public HdHomerunHost(
             IServerConfigurationManager config,
             ILogger<HdHomerunHost> logger,
@@ -114,7 +116,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             }).Cast<ChannelInfo>().ToList();
         }
 
-        private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
         private async Task<DiscoverResponse> GetModelInfo(TunerHostInfo info, bool throwAllExceptions, CancellationToken cancellationToken)
         {
             var cacheKey = info.Id;
@@ -157,10 +158,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
                 if (!throwAllExceptions && ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
                 {
-                    var defaultValue = "HDHR";
+                    const string DefaultValue = "HDHR";
                     var response = new DiscoverResponse
                     {
-                        ModelNumber = defaultValue
+                        ModelNumber = DefaultValue
                     };
                     if (!string.IsNullOrEmpty(cacheKey))
                     {
@@ -182,12 +183,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         {
             var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
 
-            using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
-            {
-                Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
-                CancellationToken = cancellationToken,
-                BufferContent = false
-            }, HttpMethod.Get).ConfigureAwait(false))
+            using (var response = await _httpClient.SendAsync(
+                new HttpRequestOptions()
+                {
+                    Url = string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)),
+                    CancellationToken = cancellationToken,
+                    BufferContent = false
+                },
+                HttpMethod.Get).ConfigureAwait(false))
             using (var stream = response.Content)
             using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
             {
@@ -730,7 +733,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 // Need a way to set the Receive timeout on the socket otherwise this might never timeout?
                 try
                 {
-                    await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken);
+                    await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken).ConfigureAwait(false);
                     var receiveBuffer = new byte[8192];
 
                     while (!cancellationToken.IsCancellationRequested)

+ 1 - 1
Emby.Server.Implementations/Localization/Core/id.json

@@ -1,7 +1,7 @@
 {
     "Albums": "Album",
     "AuthenticationSucceededWithUserName": "{0} berhasil diautentikasi",
-    "AppDeviceValues": "Aplikasi: {0}, Alat: {1}",
+    "AppDeviceValues": "Aplikasi : {0}, Alat : {1}",
     "LabelRunningTimeValue": "Waktu berjalan: {0}",
     "MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
     "MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",

+ 1 - 1
Emby.Server.Implementations/Localization/Core/ru.json

@@ -21,7 +21,7 @@
     "HeaderFavoriteAlbums": "Избранные альбомы",
     "HeaderFavoriteArtists": "Избранные исполнители",
     "HeaderFavoriteEpisodes": "Избранные эпизоды",
-    "HeaderFavoriteShows": "Избранные передачи",
+    "HeaderFavoriteShows": "Избранные сериалы",
     "HeaderFavoriteSongs": "Избранные композиции",
     "HeaderLiveTV": "Эфир",
     "HeaderNextUp": "Очередное",

+ 10 - 10
Emby.Server.Implementations/Playlists/PlaylistManager.cs

@@ -152,10 +152,10 @@ namespace Emby.Server.Implementations.Playlists
 
                 if (options.ItemIdList.Length > 0)
                 {
-                    AddToPlaylistInternal(playlist.Id.ToString("N", CultureInfo.InvariantCulture), options.ItemIdList, user, new DtoOptions(false)
+                    await AddToPlaylistInternal(playlist.Id, options.ItemIdList, user, new DtoOptions(false)
                     {
                         EnableImages = true
-                    });
+                    }).ConfigureAwait(false);
                 }
 
                 return new PlaylistCreationResult(playlist.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -184,17 +184,17 @@ namespace Emby.Server.Implementations.Playlists
             return Playlist.GetPlaylistItems(playlistMediaType, items, user, options);
         }
 
-        public void AddToPlaylist(string playlistId, ICollection<Guid> itemIds, Guid userId)
+        public Task AddToPlaylistAsync(Guid playlistId, ICollection<Guid> itemIds, Guid userId)
         {
             var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId);
 
-            AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
+            return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
             {
                 EnableImages = true
             });
         }
 
-        private void AddToPlaylistInternal(string playlistId, ICollection<Guid> newItemIds, User user, DtoOptions options)
+        private async Task AddToPlaylistInternal(Guid playlistId, ICollection<Guid> newItemIds, User user, DtoOptions options)
         {
             // Retrieve the existing playlist
             var playlist = _libraryManager.GetItemById(playlistId) as Playlist
@@ -238,7 +238,7 @@ namespace Emby.Server.Implementations.Playlists
 
             // Update the playlist in the repository
             playlist.LinkedChildren = newLinkedChildren;
-            playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             // Update the playlist on disk
             if (playlist.IsFile)
@@ -256,7 +256,7 @@ namespace Emby.Server.Implementations.Playlists
                 RefreshPriority.High);
         }
 
-        public void RemoveFromPlaylist(string playlistId, IEnumerable<string> entryIds)
+        public async Task RemoveFromPlaylistAsync(string playlistId, IEnumerable<string> entryIds)
         {
             if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
             {
@@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.Playlists
                 .Select(i => i.Item1)
                 .ToArray();
 
-            playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             if (playlist.IsFile)
             {
@@ -289,7 +289,7 @@ namespace Emby.Server.Implementations.Playlists
                 RefreshPriority.High);
         }
 
-        public void MoveItem(string playlistId, string entryId, int newIndex)
+        public async Task MoveItemAsync(string playlistId, string entryId, int newIndex)
         {
             if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
             {
@@ -322,7 +322,7 @@ namespace Emby.Server.Implementations.Playlists
 
             playlist.LinkedChildren = newList.ToArray();
 
-            playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             if (playlist.IsFile)
             {

+ 75 - 72
Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -21,37 +21,53 @@ namespace Emby.Server.Implementations.ScheduledTasks
     /// </summary>
     public class ScheduledTaskWorker : IScheduledTaskWorker
     {
-        public event EventHandler<GenericEventArgs<double>> TaskProgress;
-
-        /// <summary>
-        /// Gets the scheduled task.
-        /// </summary>
-        /// <value>The scheduled task.</value>
-        public IScheduledTask ScheduledTask { get; private set; }
-
         /// <summary>
         /// Gets or sets the json serializer.
         /// </summary>
         /// <value>The json serializer.</value>
-        private IJsonSerializer JsonSerializer { get; set; }
+        private readonly IJsonSerializer _jsonSerializer;
 
         /// <summary>
         /// Gets or sets the application paths.
         /// </summary>
         /// <value>The application paths.</value>
-        private IApplicationPaths ApplicationPaths { get; set; }
+        private readonly IApplicationPaths _applicationPaths;
 
         /// <summary>
-        /// Gets the logger.
+        /// Gets or sets the logger.
         /// </summary>
         /// <value>The logger.</value>
-        private ILogger Logger { get; set; }
+        private readonly ILogger _logger;
 
         /// <summary>
-        /// Gets the task manager.
+        /// Gets or sets the task manager.
         /// </summary>
         /// <value>The task manager.</value>
-        private ITaskManager TaskManager { get; set; }
+        private readonly ITaskManager _taskManager;
+
+        /// <summary>
+        /// The _last execution result sync lock.
+        /// </summary>
+        private readonly object _lastExecutionResultSyncLock = new object();
+
+        private bool _readFromFile = false;
+
+        /// <summary>
+        /// The _last execution result.
+        /// </summary>
+        private TaskResult _lastExecutionResult;
+
+        private Task _currentTask;
+
+        /// <summary>
+        /// The _triggers.
+        /// </summary>
+        private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
+
+        /// <summary>
+        /// The _id.
+        /// </summary>
+        private string _id;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
@@ -70,7 +86,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// or
         /// jsonSerializer
         /// or
-        /// logger
+        /// logger.
         /// </exception>
         public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger)
         {
@@ -100,23 +116,22 @@ namespace Emby.Server.Implementations.ScheduledTasks
             }
 
             ScheduledTask = scheduledTask;
-            ApplicationPaths = applicationPaths;
-            TaskManager = taskManager;
-            JsonSerializer = jsonSerializer;
-            Logger = logger;
+            _applicationPaths = applicationPaths;
+            _taskManager = taskManager;
+            _jsonSerializer = jsonSerializer;
+            _logger = logger;
 
             InitTriggerEvents();
         }
 
-        private bool _readFromFile = false;
-        /// <summary>
-        /// The _last execution result.
-        /// </summary>
-        private TaskResult _lastExecutionResult;
+        public event EventHandler<GenericEventArgs<double>> TaskProgress;
+
         /// <summary>
-        /// The _last execution result sync lock.
+        /// Gets the scheduled task.
         /// </summary>
-        private readonly object _lastExecutionResultSyncLock = new object();
+        /// <value>The scheduled task.</value>
+        public IScheduledTask ScheduledTask { get; private set; }
+
         /// <summary>
         /// Gets the last execution result.
         /// </summary>
@@ -135,11 +150,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
                         {
                             try
                             {
-                                _lastExecutionResult = JsonSerializer.DeserializeFromFile<TaskResult>(path);
+                                _lastExecutionResult = _jsonSerializer.DeserializeFromFile<TaskResult>(path);
                             }
                             catch (Exception ex)
                             {
-                                Logger.LogError(ex, "Error deserializing {File}", path);
+                                _logger.LogError(ex, "Error deserializing {File}", path);
                             }
                         }
 
@@ -159,7 +174,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
                 lock (_lastExecutionResultSyncLock)
                 {
-                    JsonSerializer.SerializeToFile(value, path);
+                    _jsonSerializer.SerializeToFile(value, path);
                 }
             }
         }
@@ -183,7 +198,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         public string Category => ScheduledTask.Category;
 
         /// <summary>
-        /// Gets the current cancellation token.
+        /// Gets or sets the current cancellation token.
         /// </summary>
         /// <value>The current cancellation token source.</value>
         private CancellationTokenSource CurrentCancellationTokenSource { get; set; }
@@ -220,12 +235,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         public double? CurrentProgress { get; private set; }
 
         /// <summary>
-        /// The _triggers.
-        /// </summary>
-        private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers;
-
-        /// <summary>
-        /// Gets the triggers that define when the task will run.
+        /// Gets or sets the triggers that define when the task will run.
         /// </summary>
         /// <value>The triggers.</value>
         private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
@@ -254,7 +264,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// Gets the triggers that define when the task will run.
         /// </summary>
         /// <value>The triggers.</value>
-        /// <exception cref="ArgumentNullException">value</exception>
+        /// <exception cref="ArgumentNullException"><c>value</c> is <c>null</c>.</exception>
         public TaskTriggerInfo[] Triggers
         {
             get
@@ -279,11 +289,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
             }
         }
 
-        /// <summary>
-        /// The _id.
-        /// </summary>
-        private string _id;
-
         /// <summary>
         /// Gets the unique id.
         /// </summary>
@@ -324,9 +329,9 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
                 trigger.Stop();
 
-                trigger.Triggered -= trigger_Triggered;
-                trigger.Triggered += trigger_Triggered;
-                trigger.Start(LastExecutionResult, Logger, Name, isApplicationStartup);
+                trigger.Triggered -= OnTriggerTriggered;
+                trigger.Triggered += OnTriggerTriggered;
+                trigger.Start(LastExecutionResult, _logger, Name, isApplicationStartup);
             }
         }
 
@@ -335,7 +340,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// </summary>
         /// <param name="sender">The source of the event.</param>
         /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
-        async void trigger_Triggered(object sender, EventArgs e)
+        private async void OnTriggerTriggered(object sender, EventArgs e)
         {
             var trigger = (ITaskTrigger)sender;
 
@@ -346,19 +351,17 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 return;
             }
 
-            Logger.LogInformation("{0} fired for task: {1}", trigger.GetType().Name, Name);
+            _logger.LogInformation("{0} fired for task: {1}", trigger.GetType().Name, Name);
 
             trigger.Stop();
 
-            TaskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions);
+            _taskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions);
 
             await Task.Delay(1000).ConfigureAwait(false);
 
-            trigger.Start(LastExecutionResult, Logger, Name, false);
+            trigger.Start(LastExecutionResult, _logger, Name, false);
         }
 
-        private Task _currentTask;
-
         /// <summary>
         /// Executes the task.
         /// </summary>
@@ -394,9 +397,9 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
             CurrentCancellationTokenSource = new CancellationTokenSource();
 
-            Logger.LogInformation("Executing {0}", Name);
+            _logger.LogInformation("Executing {0}", Name);
 
-            ((TaskManager)TaskManager).OnTaskExecuting(this);
+            ((TaskManager)_taskManager).OnTaskExecuting(this);
 
             progress.ProgressChanged += OnProgressChanged;
 
@@ -422,7 +425,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
             }
             catch (Exception ex)
             {
-                Logger.LogError(ex, "Error");
+                _logger.LogError(ex, "Error");
 
                 failureException = ex;
 
@@ -475,7 +478,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         {
             if (State == TaskState.Running)
             {
-                Logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name);
+                _logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name);
                 CurrentCancellationTokenSource.Cancel();
             }
         }
@@ -486,7 +489,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// <returns>System.String.</returns>
         private string GetScheduledTasksConfigurationDirectory()
         {
-            return Path.Combine(ApplicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
+            return Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
         }
 
         /// <summary>
@@ -495,7 +498,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// <returns>System.String.</returns>
         private string GetScheduledTasksDataDirectory()
         {
-            return Path.Combine(ApplicationPaths.DataPath, "ScheduledTasks");
+            return Path.Combine(_applicationPaths.DataPath, "ScheduledTasks");
         }
 
         /// <summary>
@@ -534,7 +537,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
             TaskTriggerInfo[] list = null;
             if (File.Exists(path))
             {
-                list = JsonSerializer.DeserializeFromFile<TaskTriggerInfo[]>(path);
+                list = _jsonSerializer.DeserializeFromFile<TaskTriggerInfo[]>(path);
             }
 
             // Return defaults if file doesn't exist.
@@ -570,7 +573,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
             Directory.CreateDirectory(Path.GetDirectoryName(path));
 
-            JsonSerializer.SerializeToFile(triggers, path);
+            _jsonSerializer.SerializeToFile(triggers, path);
         }
 
         /// <summary>
@@ -584,7 +587,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         {
             var elapsedTime = endTime - startTime;
 
-            Logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds);
+            _logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds);
 
             var result = new TaskResult
             {
@@ -605,7 +608,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
             LastExecutionResult = result;
 
-            ((TaskManager)TaskManager).OnTaskCompleted(this, result);
+            ((TaskManager)_taskManager).OnTaskCompleted(this, result);
         }
 
         /// <summary>
@@ -614,6 +617,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         public void Dispose()
         {
             Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
         /// <summary>
@@ -634,12 +638,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 {
                     try
                     {
-                        Logger.LogInformation(Name + ": Cancelling");
+                        _logger.LogInformation(Name + ": Cancelling");
                         token.Cancel();
                     }
                     catch (Exception ex)
                     {
-                        Logger.LogError(ex, "Error calling CancellationToken.Cancel();");
+                        _logger.LogError(ex, "Error calling CancellationToken.Cancel();");
                     }
                 }
 
@@ -648,21 +652,21 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 {
                     try
                     {
-                        Logger.LogInformation(Name + ": Waiting on Task");
+                        _logger.LogInformation(Name + ": Waiting on Task");
                         var exited = Task.WaitAll(new[] { task }, 2000);
 
                         if (exited)
                         {
-                            Logger.LogInformation(Name + ": Task exited");
+                            _logger.LogInformation(Name + ": Task exited");
                         }
                         else
                         {
-                            Logger.LogInformation(Name + ": Timed out waiting for task to stop");
+                            _logger.LogInformation(Name + ": Timed out waiting for task to stop");
                         }
                     }
                     catch (Exception ex)
                     {
-                        Logger.LogError(ex, "Error calling Task.WaitAll();");
+                        _logger.LogError(ex, "Error calling Task.WaitAll();");
                     }
                 }
 
@@ -670,12 +674,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 {
                     try
                     {
-                        Logger.LogDebug(Name + ": Disposing CancellationToken");
+                        _logger.LogDebug(Name + ": Disposing CancellationToken");
                         token.Dispose();
                     }
                     catch (Exception ex)
                     {
-                        Logger.LogError(ex, "Error calling CancellationToken.Dispose();");
+                        _logger.LogError(ex, "Error calling CancellationToken.Dispose();");
                     }
                 }
 
@@ -691,8 +695,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// </summary>
         /// <param name="info">The info.</param>
         /// <returns>BaseTaskTrigger.</returns>
-        /// <exception cref="ArgumentNullException"></exception>
-        /// <exception cref="ArgumentException">Invalid trigger type:  + info.Type</exception>
+        /// <exception cref="ArgumentException">Invalid trigger type:  + info.Type.</exception>
         private ITaskTrigger GetTrigger(TaskTriggerInfo info)
         {
             var options = new TaskOptions
@@ -764,7 +767,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
             foreach (var triggerInfo in InternalTriggers)
             {
                 var trigger = triggerInfo.Item2;
-                trigger.Triggered -= trigger_Triggered;
+                trigger.Triggered -= OnTriggerTriggered;
                 trigger.Stop();
             }
         }

+ 1 - 0
Emby.Server.Implementations/ScheduledTasks/TaskManager.cs

@@ -207,6 +207,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
         public void Dispose()
         {
             Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
         /// <summary>

+ 32 - 32
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs

@@ -1,12 +1,13 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Tasks;
-using MediaBrowser.Model.Globalization;
 
 namespace Emby.Server.Implementations.ScheduledTasks.Tasks
 {
@@ -15,12 +16,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
     /// </summary>
     public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
     {
-        /// <summary>
-        /// Gets or sets the configuration manager.
-        /// </summary>
-        /// <value>The configuration manager.</value>
-        private IConfigurationManager ConfigurationManager { get; set; }
-
+        private readonly IConfigurationManager _configurationManager;
         private readonly IFileSystem _fileSystem;
         private readonly ILocalizationManager _localization;
 
@@ -32,18 +28,43 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
         /// <param name="localization">The localization manager.</param>
         public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization)
         {
-            ConfigurationManager = configurationManager;
+            _configurationManager = configurationManager;
             _fileSystem = fileSystem;
             _localization = localization;
         }
 
+        /// <inheritdoc />
+        public string Name => _localization.GetLocalizedString("TaskCleanLogs");
+
+        /// <inheritdoc />
+        public string Description => string.Format(
+            CultureInfo.InvariantCulture,
+            _localization.GetLocalizedString("TaskCleanLogsDescription"),
+            _configurationManager.CommonConfiguration.LogFileRetentionDays);
+
+        /// <inheritdoc />
+        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+
+        /// <inheritdoc />
+        public string Key => "CleanLogFiles";
+
+        /// <inheritdoc />
+        public bool IsHidden => false;
+
+        /// <inheritdoc />
+        public bool IsEnabled => true;
+
+        /// <inheritdoc />
+        public bool IsLogged => true;
+
         /// <summary>
         /// Creates the triggers that define when the task will run.
         /// </summary>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         {
-            return new[] {
+            return new[]
+            {
                 new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
             };
         }
@@ -57,10 +78,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
         public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
             // Delete log files more than n days old
-            var minDateModified = DateTime.UtcNow.AddDays(-ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
+            var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays);
 
             // Only delete the .txt log files, the *.log files created by serilog get managed by itself
-            var filesToDelete = _fileSystem.GetFiles(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, new[] { ".txt" }, true, true)
+            var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, new[] { ".txt" }, true, true)
                           .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
                           .ToList();
 
@@ -83,26 +104,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
 
             return Task.CompletedTask;
         }
-
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskCleanLogs");
-
-        /// <inheritdoc />
-        public string Description => string.Format(_localization.GetLocalizedString("TaskCleanLogsDescription"), ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
-
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
-
-        /// <inheritdoc />
-        public string Key => "CleanLogFiles";
-
-        /// <inheritdoc />
-        public bool IsHidden => false;
-
-        /// <inheritdoc />
-        public bool IsEnabled => true;
-
-        /// <inheritdoc />
-        public bool IsLogged => true;
     }
 }

+ 4 - 4
Emby.Server.Implementations/Services/ServicePath.cs

@@ -80,8 +80,8 @@ namespace Emby.Server.Implementations.Services
 
         public static List<string> GetFirstMatchWildCardHashKeys(string[] pathPartsForMatching)
         {
-            const string hashPrefix = WildCard + PathSeperator;
-            return GetPotentialMatchesWithPrefix(hashPrefix, pathPartsForMatching);
+            const string HashPrefix = WildCard + PathSeperator;
+            return GetPotentialMatchesWithPrefix(HashPrefix, pathPartsForMatching);
         }
 
         private static List<string> GetPotentialMatchesWithPrefix(string hashPrefix, string[] pathPartsForMatching)
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Services
             {
                 list.Add(hashPrefix + part);
 
-                if (part.IndexOf(ComponentSeperator) == -1)
+                if (part.IndexOf(ComponentSeperator, StringComparison.Ordinal) == -1)
                 {
                     continue;
                 }
@@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.Services
                 }
 
                 if (component.IndexOf(VariablePrefix, StringComparison.OrdinalIgnoreCase) != -1
-                    && component.IndexOf(ComponentSeperator) != -1)
+                    && component.IndexOf(ComponentSeperator, StringComparison.Ordinal) != -1)
                 {
                     hasSeparators.Add(true);
                     componentsList.AddRange(component.Split(ComponentSeperator));

+ 2 - 2
Jellyfin.Api/Controllers/AudioController.cs

@@ -83,9 +83,9 @@ namespace Jellyfin.Api.Controllers
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <response code="200">Audio stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
-        [HttpGet("{itemId}/{stream=stream}.{container?}", Name = "GetAudioStreamByContainer")]
+        [HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")]
         [HttpGet("{itemId}/stream", Name = "GetAudioStream")]
-        [HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadAudioStreamByContainer")]
+        [HttpHead("{itemId}/stream.{container}", Name = "HeadAudioStreamByContainer")]
         [HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetAudioStream(

+ 8 - 7
Jellyfin.Api/Controllers/CollectionController.cs

@@ -1,5 +1,6 @@
 using System;
 using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
@@ -51,7 +52,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="CollectionCreationOptions"/> with information about the new collection.</returns>
         [HttpPost]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<CollectionCreationResult> CreateCollection(
+        public async Task<ActionResult<CollectionCreationResult>> CreateCollection(
             [FromQuery] string? name,
             [FromQuery] string? ids,
             [FromQuery] Guid? parentId,
@@ -59,14 +60,14 @@ namespace Jellyfin.Api.Controllers
         {
             var userId = _authContext.GetAuthorizationInfo(Request).UserId;
 
-            var item = _collectionManager.CreateCollection(new CollectionCreationOptions
+            var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
             {
                 IsLocked = isLocked,
                 Name = name,
                 ParentId = parentId,
                 ItemIdList = RequestHelpers.Split(ids, ',', true),
                 UserIds = new[] { userId }
-            });
+            }).ConfigureAwait(false);
 
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
@@ -87,9 +88,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("{collectionId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+        public async Task<ActionResult> AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
         {
-            _collectionManager.AddToCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
+            await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
             return NoContent();
         }
 
@@ -102,9 +103,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpDelete("{collectionId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+        public async Task<ActionResult> RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
         {
-            _collectionManager.RemoveFromCollection(collectionId, RequestHelpers.Split(itemIds, ',', true));
+            await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
             return NoContent();
         }
     }

+ 1 - 1
Jellyfin.Api/Controllers/ConfigurationController.cs

@@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("MediaEncoder/Path")]
         [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult UpdateMediaEncoderPath([FromForm, Required] MediaEncoderPathDto mediaEncoderPath)
+        public ActionResult UpdateMediaEncoderPath([FromBody, Required] MediaEncoderPathDto mediaEncoderPath)
         {
             _mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
             return NoContent();

+ 1 - 2
Jellyfin.Api/Controllers/DlnaServerController.cs

@@ -221,9 +221,8 @@ namespace Jellyfin.Api.Controllers
 
         private Task<ControlResponse> ProcessControlRequestInternalAsync(string id, Stream requestStream, IUpnpService service)
         {
-            return service.ProcessControlRequestAsync(new ControlRequest
+            return service.ProcessControlRequestAsync(new ControlRequest(Request.Headers)
             {
-                Headers = Request.Headers,
                 InputXml = requestStream,
                 TargetServerUuId = id,
                 RequestedUrl = GetAbsoluteUri()

+ 1 - 1
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -1356,7 +1356,7 @@ namespace Jellyfin.Api.Controllers
 
             return string.Format(
                 CultureInfo.InvariantCulture,
-                "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
+                "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
                 inputModifier,
                 _encodingHelper.GetInputArgument(state, encodingOptions),
                 threads,

+ 8 - 8
Jellyfin.Api/Controllers/ImageController.cs

@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
             user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
 
             await _providerManager
-                .SaveImage(user, memoryStream, mimeType, user.ProfileImage.Path)
+                .SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
                 .ConfigureAwait(false);
             await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
 
@@ -174,7 +174,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteItemImage(
+        public async Task<ActionResult> DeleteItemImage(
             [FromRoute] Guid itemId,
             [FromRoute] ImageType imageType,
             [FromRoute] int? imageIndex = null)
@@ -185,7 +185,7 @@ namespace Jellyfin.Api.Controllers
                 return NotFound();
             }
 
-            item.DeleteImage(imageType, imageIndex ?? 0);
+            await item.DeleteImageAsync(imageType, imageIndex ?? 0).ConfigureAwait(false);
             return NoContent();
         }
 
@@ -218,7 +218,7 @@ namespace Jellyfin.Api.Controllers
             // Handle image/png; charset=utf-8
             var mimeType = Request.ContentType.Split(';').FirstOrDefault();
             await _providerManager.SaveImage(item, Request.Body, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
-            item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+            await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
 
             return NoContent();
         }
@@ -237,7 +237,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult UpdateItemImageIndex(
+        public async Task<ActionResult> UpdateItemImageIndex(
             [FromRoute] Guid itemId,
             [FromRoute] ImageType imageType,
             [FromRoute] int imageIndex,
@@ -249,7 +249,7 @@ namespace Jellyfin.Api.Controllers
                 return NotFound();
             }
 
-            item.SwapImages(imageType, imageIndex, newIndex);
+            await item.SwapImagesAsync(imageType, imageIndex, newIndex).ConfigureAwait(false);
             return NoContent();
         }
 
@@ -264,7 +264,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<IEnumerable<ImageInfo>> GetItemImageInfos([FromRoute] Guid itemId)
+        public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute] Guid itemId)
         {
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
@@ -281,7 +281,7 @@ namespace Jellyfin.Api.Controllers
                 return list;
             }
 
-            _libraryManager.UpdateImages(item); // this makes sure dimensions and hashes are correct
+            await _libraryManager.UpdateImagesAsync(item).ConfigureAwait(false); // this makes sure dimensions and hashes are correct
 
             foreach (var image in itemImages)
             {

+ 4 - 3
Jellyfin.Api/Controllers/ItemUpdateController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Threading;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -67,7 +68,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/{itemId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request)
+        public async Task<ActionResult> UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request)
         {
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
@@ -101,7 +102,7 @@ namespace Jellyfin.Api.Controllers
 
             item.OnMetadataChanged();
 
-            item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             if (isLockedChanged && item.IsFolder)
             {
@@ -110,7 +111,7 @@ namespace Jellyfin.Api.Controllers
                 foreach (var child in folder.GetRecursiveChildren())
                 {
                     child.IsLocked = newLockData;
-                    child.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                    await child.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
                 }
             }
 

+ 2 - 2
Jellyfin.Api/Controllers/MediaInfoController.cs

@@ -269,9 +269,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("LiveStreams/Close")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult CloseLiveStream([FromQuery, Required] string? liveStreamId)
+        public async Task<ActionResult> CloseLiveStream([FromQuery, Required] string? liveStreamId)
         {
-            _mediaSourceManager.CloseLiveStream(liveStreamId).GetAwaiter().GetResult();
+            await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
             return NoContent();
         }
 

+ 7 - 7
Jellyfin.Api/Controllers/PlaylistsController.cs

@@ -83,12 +83,12 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         [HttpPost("{playlistId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult AddToPlaylist(
-            [FromRoute] string? playlistId,
+        public async Task<ActionResult> AddToPlaylist(
+            [FromRoute] Guid playlistId,
             [FromQuery] string? ids,
             [FromQuery] Guid? userId)
         {
-            _playlistManager.AddToPlaylist(playlistId, RequestHelpers.GetGuids(ids), userId ?? Guid.Empty);
+            await _playlistManager.AddToPlaylistAsync(playlistId, RequestHelpers.GetGuids(ids), userId ?? Guid.Empty).ConfigureAwait(false);
             return NoContent();
         }
 
@@ -102,12 +102,12 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult MoveItem(
+        public async Task<ActionResult> MoveItem(
             [FromRoute] string? playlistId,
             [FromRoute] string? itemId,
             [FromRoute] int newIndex)
         {
-            _playlistManager.MoveItem(playlistId, itemId, newIndex);
+            await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
             return NoContent();
         }
 
@@ -120,9 +120,9 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         [HttpDelete("{playlistId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds)
+        public async Task<ActionResult> RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds)
         {
-            _playlistManager.RemoveFromPlaylist(playlistId, RequestHelpers.Split(entryIds, ',', true));
+            await _playlistManager.RemoveFromPlaylistAsync(playlistId, RequestHelpers.Split(entryIds, ',', true)).ConfigureAwait(false);
             return NoContent();
         }
 

+ 1 - 1
Jellyfin.Api/Controllers/RemoteImageController.cs

@@ -221,7 +221,7 @@ namespace Jellyfin.Api.Controllers
             await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None)
                 .ConfigureAwait(false);
 
-            item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+            await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
             return NoContent();
         }
 

+ 6 - 10
Jellyfin.Api/Controllers/SessionController.cs

@@ -153,7 +153,6 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemIds">The ids of the items to play, comma delimited.</param>
         /// <param name="startPositionTicks">The starting position of the first item.</param>
         /// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
-        /// <param name="playRequest">The <see cref="PlayRequest"/>.</param>
         /// <response code="204">Instruction sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         [HttpPost("Sessions/{sessionId}/Playing")]
@@ -163,17 +162,14 @@ namespace Jellyfin.Api.Controllers
             [FromRoute, Required] string? sessionId,
             [FromQuery] Guid[] itemIds,
             [FromQuery] long? startPositionTicks,
-            [FromQuery] PlayCommand playCommand,
-            [FromBody, Required] PlayRequest playRequest)
+            [FromQuery] PlayCommand playCommand)
         {
-            if (playRequest == null)
+            var playRequest = new PlayRequest
             {
-                throw new ArgumentException("Request Body may not be null");
-            }
-
-            playRequest.ItemIds = itemIds;
-            playRequest.StartPositionTicks = startPositionTicks;
-            playRequest.PlayCommand = playCommand;
+                ItemIds = itemIds,
+                StartPositionTicks = startPositionTicks,
+                PlayCommand = playCommand
+            };
 
             _sessionManager.SendPlayCommand(
                 RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id,

+ 11 - 16
Jellyfin.Api/Controllers/StartupController.cs

@@ -1,3 +1,4 @@
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
@@ -64,21 +65,16 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Sets the initial startup wizard configuration.
         /// </summary>
-        /// <param name="uiCulture">The UI language culture.</param>
-        /// <param name="metadataCountryCode">The metadata country code.</param>
-        /// <param name="preferredMetadataLanguage">The preferred language for metadata.</param>
+        /// <param name="startupConfiguration">The updated startup configuration.</param>
         /// <response code="204">Configuration saved.</response>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("Configuration")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult UpdateInitialConfiguration(
-            [FromForm] string? uiCulture,
-            [FromForm] string? metadataCountryCode,
-            [FromForm] string? preferredMetadataLanguage)
+        public ActionResult UpdateInitialConfiguration([FromBody, Required] StartupConfigurationDto startupConfiguration)
         {
-            _config.Configuration.UICulture = uiCulture;
-            _config.Configuration.MetadataCountryCode = metadataCountryCode;
-            _config.Configuration.PreferredMetadataLanguage = preferredMetadataLanguage;
+            _config.Configuration.UICulture = startupConfiguration.UICulture;
+            _config.Configuration.MetadataCountryCode = startupConfiguration.MetadataCountryCode;
+            _config.Configuration.PreferredMetadataLanguage = startupConfiguration.PreferredMetadataLanguage;
             _config.SaveConfiguration();
             return NoContent();
         }
@@ -86,16 +82,15 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Sets remote access and UPnP.
         /// </summary>
-        /// <param name="enableRemoteAccess">Enable remote access.</param>
-        /// <param name="enableAutomaticPortMapping">Enable UPnP.</param>
+        /// <param name="startupRemoteAccessDto">The startup remote access dto.</param>
         /// <response code="204">Configuration saved.</response>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("RemoteAccess")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public ActionResult SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping)
+        public ActionResult SetRemoteAccess([FromBody, Required] StartupRemoteAccessDto startupRemoteAccessDto)
         {
-            _config.Configuration.EnableRemoteAccess = enableRemoteAccess;
-            _config.Configuration.EnableUPnP = enableAutomaticPortMapping;
+            _config.Configuration.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess;
+            _config.Configuration.EnableUPnP = startupRemoteAccessDto.EnableAutomaticPortMapping;
             _config.SaveConfiguration();
             return NoContent();
         }
@@ -131,7 +126,7 @@ namespace Jellyfin.Api.Controllers
         /// </returns>
         [HttpPost("User")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> UpdateStartupUser([FromForm] StartupUserDto startupUserDto)
+        public async Task<ActionResult> UpdateStartupUser([FromBody] StartupUserDto startupUserDto)
         {
             var user = _userManager.Users.First();
 

+ 2 - 2
Jellyfin.Api/Controllers/UniversalAudioController.cs

@@ -85,9 +85,9 @@ namespace Jellyfin.Api.Controllers
         /// <response code="302">Redirected to remote audio stream.</response>
         /// <returns>A <see cref="Task"/> containing the audio file.</returns>
         [HttpGet("Audio/{itemId}/universal")]
-        [HttpGet("Audio/{itemId}/{universal=universal}.{container?}", Name = "GetUniversalAudioStream_2")]
+        [HttpGet("Audio/{itemId}/universal.{container}", Name = "GetUniversalAudioStream_2")]
         [HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
-        [HttpHead("Audio/{itemId}/{universal=universal}.{container?}", Name = "HeadUniversalAudioStream_2")]
+        [HttpHead("Audio/{itemId}/universal.{container}", Name = "HeadUniversalAudioStream_2")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status302Found)]

+ 7 - 7
Jellyfin.Api/Controllers/VideosController.cs

@@ -161,7 +161,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteAlternateSources([FromRoute] Guid itemId)
+        public async Task<ActionResult> DeleteAlternateSources([FromRoute] Guid itemId)
         {
             var video = (Video)_libraryManager.GetItemById(itemId);
 
@@ -180,12 +180,12 @@ namespace Jellyfin.Api.Controllers
                 link.SetPrimaryVersionId(null);
                 link.LinkedAlternateVersions = Array.Empty<LinkedChild>();
 
-                link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                await link.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             }
 
             video.LinkedAlternateVersions = Array.Empty<LinkedChild>();
             video.SetPrimaryVersionId(null);
-            video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await video.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
             return NoContent();
         }
@@ -201,7 +201,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
-        public ActionResult MergeVersions([FromQuery, Required] string? itemIds)
+        public async Task<ActionResult> MergeVersions([FromQuery, Required] string? itemIds)
         {
             var items = RequestHelpers.Split(itemIds, ',', true)
                 .Select(i => _libraryManager.GetItemById(i))
@@ -239,7 +239,7 @@ namespace Jellyfin.Api.Controllers
             {
                 item.SetPrimaryVersionId(primaryVersion.Id.ToString("N", CultureInfo.InvariantCulture));
 
-                item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
                 list.Add(new LinkedChild
                 {
@@ -258,12 +258,12 @@ namespace Jellyfin.Api.Controllers
                 if (item.LinkedAlternateVersions.Length > 0)
                 {
                     item.LinkedAlternateVersions = Array.Empty<LinkedChild>();
-                    item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+                    await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
                 }
             }
 
             primaryVersion.LinkedAlternateVersions = list.ToArray();
-            primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+            await primaryVersion.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             return NoContent();
         }
 

+ 22 - 0
Jellyfin.Api/Models/StartupDtos/StartupRemoteAccessDto.cs

@@ -0,0 +1,22 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Jellyfin.Api.Models.StartupDtos
+{
+    /// <summary>
+    /// Startup remote access dto.
+    /// </summary>
+    public class StartupRemoteAccessDto
+    {
+        /// <summary>
+        /// Gets or sets a value indicating whether enable remote access.
+        /// </summary>
+        [Required]
+        public bool EnableRemoteAccess { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether enable automatic port mapping.
+        /// </summary>
+        [Required]
+        public bool EnableAutomaticPortMapping { get; set; }
+    }
+}

部分文件因为文件数量过多而无法显示