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
pull/3910/head
Patrick Barron 4 years ago
commit 9fa4fff15d

@ -13,7 +13,7 @@ namespace Emby.Dlna.Common
public string Name { get; set; } public string Name { get; set; }
public List<Argument> ArgumentList { get; set; } public List<Argument> ArgumentList { get; }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()

@ -1,6 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic;
namespace Emby.Dlna.Common namespace Emby.Dlna.Common
{ {
@ -17,7 +18,7 @@ namespace Emby.Dlna.Common
public bool SendsEvents { get; set; } public bool SendsEvents { get; set; }
public string[] AllowedValues { get; set; } public IReadOnlyList<string> AllowedValues { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()

@ -1,7 +1,6 @@
#nullable enable #nullable enable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Configuration; using Emby.Dlna.Configuration;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -14,19 +13,4 @@ namespace Emby.Dlna
return manager.GetConfiguration<DlnaOptions>("dlna"); return manager.GetConfiguration<DlnaOptions>("dlna");
} }
} }
public class DlnaConfigurationFactory : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new ConfigurationStore[]
{
new ConfigurationStore
{
Key = "dlna",
ConfigurationType = typeof (DlnaOptions)
}
};
}
}
} }

@ -9,22 +9,20 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager namespace Emby.Dlna.ConnectionManager
{ {
public class ConnectionManager : BaseService, IConnectionManager public class ConnectionManagerService : BaseService, IConnectionManager
{ {
private readonly IDlnaManager _dlna; private readonly IDlnaManager _dlna;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public ConnectionManager( public ConnectionManagerService(
IDlnaManager dlna, IDlnaManager dlna,
IServerConfigurationManager config, IServerConfigurationManager config,
ILogger<ConnectionManager> logger, ILogger<ConnectionManagerService> logger,
IHttpClient httpClient) IHttpClient httpClient)
: base(logger, httpClient) : base(logger, httpClient)
{ {
_dlna = dlna; _dlna = dlna;
_config = config; _config = config;
_logger = logger;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -39,7 +37,7 @@ namespace Emby.Dlna.ConnectionManager
var profile = _dlna.GetProfile(request.Headers) ?? var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile(); _dlna.GetDefaultProfile();
return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request); return new ControlHandler(_config, Logger, profile).ProcessControlRequestAsync(request);
} }
} }
} }

@ -44,7 +44,7 @@ namespace Emby.Dlna.ConnectionManager
DataType = "string", DataType = "string",
SendsEvents = false, SendsEvents = false,
AllowedValues = new string[] AllowedValues = new[]
{ {
"OK", "OK",
"ContentFormatMismatch", "ContentFormatMismatch",
@ -67,7 +67,7 @@ namespace Emby.Dlna.ConnectionManager
DataType = "string", DataType = "string",
SendsEvents = false, SendsEvents = false,
AllowedValues = new string[] AllowedValues = new[]
{ {
"Output", "Output",
"Input" "Input"

@ -19,7 +19,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ContentDirectory namespace Emby.Dlna.ContentDirectory
{ {
public class ContentDirectory : BaseService, IContentDirectory public class ContentDirectoryService : BaseService, IContentDirectory
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
@ -33,14 +33,14 @@ namespace Emby.Dlna.ContentDirectory
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ITVSeriesManager _tvSeriesManager; private readonly ITVSeriesManager _tvSeriesManager;
public ContentDirectory( public ContentDirectoryService(
IDlnaManager dlna, IDlnaManager dlna,
IUserDataManager userDataManager, IUserDataManager userDataManager,
IImageProcessor imageProcessor, IImageProcessor imageProcessor,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IServerConfigurationManager config, IServerConfigurationManager config,
IUserManager userManager, IUserManager userManager,
ILogger<ContentDirectory> logger, ILogger<ContentDirectoryService> logger,
IHttpClient httpClient, IHttpClient httpClient,
ILocalizationManager localization, ILocalizationManager localization,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,

@ -10,7 +10,8 @@ namespace Emby.Dlna.ContentDirectory
{ {
public string GetXml() public string GetXml()
{ {
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), return new ServiceXmlBuilder().GetXml(
new ServiceActionListBuilder().GetActions(),
GetStateVariables()); GetStateVariables());
} }
@ -101,7 +102,7 @@ namespace Emby.Dlna.ContentDirectory
DataType = "string", DataType = "string",
SendsEvents = false, SendsEvents = false,
AllowedValues = new string[] AllowedValues = new[]
{ {
"BrowseMetadata", "BrowseMetadata",
"BrowseDirectChildren" "BrowseDirectChildren"

@ -40,6 +40,11 @@ namespace Emby.Dlna.ContentDirectory
{ {
public class ControlHandler : BaseControlHandler 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 ILibraryManager _libraryManager;
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
@ -47,11 +52,6 @@ namespace Emby.Dlna.ContentDirectory
private readonly IUserViewManager _userViewManager; private readonly IUserViewManager _userViewManager;
private readonly ITVSeriesManager _tvSeriesManager; 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 int _systemUpdateId;
private readonly DidlBuilder _didlBuilder; private readonly DidlBuilder _didlBuilder;
@ -181,7 +181,11 @@ namespace Emby.Dlna.ContentDirectory
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks; userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
_userDataManager.SaveUserData(_user, item, userdata, UserDataSaveReason.TogglePlayed, _userDataManager.SaveUserData(
_user,
item,
userdata,
UserDataSaveReason.TogglePlayed,
CancellationToken.None); CancellationToken.None);
} }
@ -253,7 +257,7 @@ namespace Emby.Dlna.ContentDirectory
var id = sparams["ObjectID"]; var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"]; var flag = sparams["BrowseFlag"];
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); 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; var provided = 0;
@ -286,18 +290,17 @@ namespace Emby.Dlna.ContentDirectory
using (var writer = XmlWriter.Create(builder, settings)) 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", "dc", null, NsDc);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
DidlBuilder.WriteXmlRootAttributes(_profile, writer); DidlBuilder.WriteXmlRootAttributes(_profile, writer);
var serverItem = GetItemFromObjectId(id); var serverItem = GetItemFromObjectId(id);
var item = serverItem.Item; var item = serverItem.Item;
if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal)) if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
{ {
totalCount = 1; totalCount = 1;
@ -362,8 +365,8 @@ namespace Emby.Dlna.ContentDirectory
private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId) private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
{ {
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", "")); var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", string.Empty));
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*")); var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
// sort example: dc:title, dc:date // sort example: dc:title, dc:date
@ -397,11 +400,11 @@ namespace Emby.Dlna.ContentDirectory
using (var writer = XmlWriter.Create(builder, settings)) 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", "dc", null, NsDc);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
DidlBuilder.WriteXmlRootAttributes(_profile, writer); DidlBuilder.WriteXmlRootAttributes(_profile, writer);
@ -783,11 +786,14 @@ namespace Emby.Dlna.ContentDirectory
}) })
.ToArray(); .ToArray();
return ApplyPaging(new QueryResult<ServerItem> return ApplyPaging(
{ new QueryResult<ServerItem>
Items = folders, {
TotalRecordCount = folders.Length Items = folders,
}, startIndex, limit); TotalRecordCount = folders.Length
},
startIndex,
limit);
} }
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? 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)>(); query.OrderBy = Array.Empty<(string, SortOrder)>();
var items = _userViewManager.GetLatestItems(new LatestItemsQuery var items = _userViewManager.GetLatestItems(
{ new LatestItemsQuery
UserId = user.Id, {
Limit = 50, UserId = user.Id,
IncludeItemTypes = new[] { nameof(Audio) }, Limit = 50,
ParentId = parent?.Id ?? Guid.Empty, IncludeItemTypes = new[] { nameof(Audio) },
GroupItems = true ParentId = parent?.Id ?? Guid.Empty,
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); GroupItems = true
},
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
} }
@ -1151,12 +1159,15 @@ namespace Emby.Dlna.ContentDirectory
{ {
query.OrderBy = Array.Empty<(string, SortOrder)>(); query.OrderBy = Array.Empty<(string, SortOrder)>();
var result = _tvSeriesManager.GetNextUp(new NextUpQuery var result = _tvSeriesManager.GetNextUp(
{ new NextUpQuery
Limit = query.Limit, {
StartIndex = query.StartIndex, Limit = query.Limit,
UserId = query.User.Id StartIndex = query.StartIndex,
}, new[] { parent }, query.DtoOptions); UserId = query.User.Id
},
new[] { parent },
query.DtoOptions);
return ToResult(result); return ToResult(result);
} }
@ -1165,14 +1176,16 @@ namespace Emby.Dlna.ContentDirectory
{ {
query.OrderBy = Array.Empty<(string, SortOrder)>(); query.OrderBy = Array.Empty<(string, SortOrder)>();
var items = _userViewManager.GetLatestItems(new LatestItemsQuery var items = _userViewManager.GetLatestItems(
{ new LatestItemsQuery
UserId = user.Id, {
Limit = 50, UserId = user.Id,
IncludeItemTypes = new[] { typeof(Episode).Name }, Limit = 50,
ParentId = parent == null ? Guid.Empty : parent.Id, IncludeItemTypes = new[] { typeof(Episode).Name },
GroupItems = false ParentId = parent == null ? Guid.Empty : parent.Id,
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); GroupItems = false
},
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
} }
@ -1183,13 +1196,14 @@ namespace Emby.Dlna.ContentDirectory
var items = _userViewManager.GetLatestItems( var items = _userViewManager.GetLatestItems(
new LatestItemsQuery new LatestItemsQuery
{ {
UserId = user.Id, UserId = user.Id,
Limit = 50, Limit = 50,
IncludeItemTypes = new[] { nameof(Movie) }, IncludeItemTypes = new[] { nameof(Movie) },
ParentId = parent?.Id ?? Guid.Empty, ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true GroupItems = true
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); },
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
} }
@ -1354,44 +1368,4 @@ namespace Emby.Dlna.ContentDirectory
return new ServerItem(_libraryManager.GetUserRootFolder()); 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
}
} }

@ -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; }
}
}

@ -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
}
}

@ -7,17 +7,17 @@ namespace Emby.Dlna
{ {
public class ControlRequest public class ControlRequest
{ {
public IHeaderDictionary Headers { get; set; } public ControlRequest(IHeaderDictionary headers)
{
Headers = headers;
}
public IHeaderDictionary Headers { get; }
public Stream InputXml { get; set; } public Stream InputXml { get; set; }
public string TargetServerUuId { get; set; } public string TargetServerUuId { get; set; }
public string RequestedUrl { get; set; } public string RequestedUrl { get; set; }
public ControlRequest()
{
Headers = new HeaderDictionary();
}
} }
} }

@ -11,7 +11,7 @@ namespace Emby.Dlna
Headers = new Dictionary<string, string>(); Headers = new Dictionary<string, string>();
} }
public IDictionary<string, string> Headers { get; set; } public IDictionary<string, string> Headers { get; }
public string Xml { get; set; } public string Xml { get; set; }

@ -34,12 +34,12 @@ namespace Emby.Dlna.Didl
{ {
public class DidlBuilder 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 readonly CultureInfo _usCulture = new CultureInfo("en-US");
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 DeviceProfile _profile; private readonly DeviceProfile _profile;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
@ -100,11 +100,11 @@ namespace Emby.Dlna.Didl
{ {
// writer.WriteStartDocument(); // writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC); writer.WriteAttributeString("xmlns", "dc", null, NsDc);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
// didl.SetAttribute("xmlns:sec", NS_SEC); // didl.SetAttribute("xmlns:sec", NS_SEC);
WriteXmlRootAttributes(_profile, writer); WriteXmlRootAttributes(_profile, writer);
@ -147,7 +147,7 @@ namespace Emby.Dlna.Didl
{ {
var clientId = GetClientId(item, null); var clientId = GetClientId(item, null);
writer.WriteStartElement(string.Empty, "item", NS_DIDL); writer.WriteStartElement(string.Empty, "item", NsDidl);
writer.WriteAttributeString("restricted", "1"); writer.WriteAttributeString("restricted", "1");
writer.WriteAttributeString("id", clientId); writer.WriteAttributeString("id", clientId);
@ -207,7 +207,8 @@ namespace Emby.Dlna.Didl
var targetWidth = streamInfo.TargetWidth; var targetWidth = streamInfo.TargetWidth;
var targetHeight = streamInfo.TargetHeight; var targetHeight = streamInfo.TargetHeight;
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container, var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(
streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(), streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(),
targetWidth, targetWidth,
@ -279,7 +280,7 @@ namespace Emby.Dlna.Didl
} }
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase)) 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:*"); writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
@ -288,7 +289,7 @@ namespace Emby.Dlna.Didl
} }
else else
{ {
writer.WriteStartElement(string.Empty, "res", NS_DIDL); writer.WriteStartElement(string.Empty, "res", NsDidl);
var protocolInfo = string.Format( var protocolInfo = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"http-get:*:text/{0}:*", "http-get:*:text/{0}:*",
@ -304,7 +305,7 @@ namespace Emby.Dlna.Didl
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo) 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)); 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) 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) if (streamInfo == null)
{ {
@ -583,7 +584,8 @@ namespace Emby.Dlna.Didl
writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
} }
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container, var mediaProfile = _profile.GetAudioMediaProfile(
streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(),
targetChannels, targetChannels,
targetAudioBitrate, targetAudioBitrate,
@ -596,7 +598,8 @@ namespace Emby.Dlna.Didl
? MimeTypes.GetMimeType(filename) ? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType; : mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container, var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(
streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(),
targetAudioBitrate, targetAudioBitrate,
targetSampleRate, 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) 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("restricted", "1");
writer.WriteAttributeString("searchable", "1"); writer.WriteAttributeString("searchable", "1");
@ -714,7 +717,7 @@ namespace Emby.Dlna.Didl
// MediaMonkey for example won't display content without a title // MediaMonkey for example won't display content without a title
// if (filter.Contains("dc:title")) // if (filter.Contains("dc:title"))
{ {
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC); AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NsDc);
} }
WriteObjectClass(writer, item, itemStubType); WriteObjectClass(writer, item, itemStubType);
@ -723,7 +726,7 @@ namespace Emby.Dlna.Didl
{ {
if (item.PremiereDate.HasValue) 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) 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) foreach (var studio in item.Studios)
{ {
AddValue(writer, "upnp", "publisher", studio, NS_UPNP); AddValue(writer, "upnp", "publisher", studio, NsUpnp);
} }
if (!(item is Folder)) if (!(item is Folder))
@ -748,28 +751,29 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(desc)) if (!string.IsNullOrWhiteSpace(desc))
{ {
AddValue(writer, "dc", "description", desc, NS_DC); AddValue(writer, "dc", "description", desc, NsDc);
} }
} }
// if (filter.Contains("upnp:longDescription")) // if (filter.Contains("upnp:longDescription"))
//{ // {
// if (!string.IsNullOrWhiteSpace(item.Overview)) // 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 (!string.IsNullOrEmpty(item.OfficialRating))
{ {
if (filter.Contains("dc:rating")) 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")) 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 // More types here
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs // 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) 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)) var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
?? PersonType.Actor; ?? 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) foreach (var artist in hasArtists.Artists)
{ {
AddValue(writer, "upnp", "artist", artist, NS_UPNP); AddValue(writer, "upnp", "artist", artist, NsUpnp);
AddValue(writer, "dc", "creator", artist, NS_DC); AddValue(writer, "dc", "creator", artist, NsDc);
// If it doesn't support album artists (musicvideo), then tag as both // If it doesn't support album artists (musicvideo), then tag as both
if (hasAlbumArtists == null) if (hasAlbumArtists == null)
@ -917,16 +921,16 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(item.Album)) if (!string.IsNullOrWhiteSpace(item.Album))
{ {
AddValue(writer, "upnp", "album", item.Album, NS_UPNP); AddValue(writer, "upnp", "album", item.Album, NsUpnp);
} }
if (item.IndexNumber.HasValue) 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) 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 try
{ {
writer.WriteStartElement("upnp", "artist", NS_UPNP); writer.WriteStartElement("upnp", "artist", NsUpnp);
writer.WriteAttributeString("role", "AlbumArtist"); writer.WriteAttributeString("role", "AlbumArtist");
writer.WriteString(name); writer.WriteString(name);
@ -971,14 +975,14 @@ namespace Emby.Dlna.Didl
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg"); var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP); writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn); writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.Url); writer.WriteString(albumartUrlInfo.url);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
// TOOD: Remove these default values // TOOD: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg"); 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) if (!_profile.EnableAlbumArtInDidl)
{ {
@ -1021,12 +1025,12 @@ namespace Emby.Dlna.Didl
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format); 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 // 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 // rather than using a larger one when available
var width = albumartUrlInfo.Width ?? maxWidth; var width = albumartUrlInfo.width ?? maxWidth;
var height = albumartUrlInfo.Height ?? maxHeight; var height = albumartUrlInfo.height ?? maxHeight;
var contentFeatures = new ContentFeatureBuilder(_profile) var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn); .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
@ -1043,7 +1047,7 @@ namespace Emby.Dlna.Didl
"resolution", "resolution",
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height)); string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
writer.WriteString(albumartUrlInfo.Url); writer.WriteString(albumartUrlInfo.url);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
} }
@ -1139,7 +1143,6 @@ namespace Emby.Dlna.Didl
if (width == 0 || height == 0) if (width == 0 || height == 0)
{ {
// _imageProcessor.GetImageSize(item, imageInfo);
width = null; width = null;
height = null; height = null;
} }
@ -1149,18 +1152,6 @@ namespace Emby.Dlna.Didl
height = null; height = null;
} }
// try
//{
// var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width;
// height = size.Height;
//}
// catch
//{
//}
var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty) var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty)
.TrimStart('.') .TrimStart('.')
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase); .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) public static string GetClientId(BaseItem item, StubType? stubType)
{ {
return GetClientId(item.Id, stubType); return GetClientId(item.Id, stubType);
@ -1218,7 +1185,7 @@ namespace Emby.Dlna.Didl
return id; 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( var url = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
@ -1256,12 +1223,26 @@ namespace Emby.Dlna.Didl
// just lie // just lie
info.IsDirectStream = true; info.IsDirectStream = true;
return new ImageUrlInfo return (url, width, height);
{ }
Url = url,
Width = width, private class ImageDownloadInfo
Height = height {
}; 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; }
} }
} }
} }

@ -23,9 +23,7 @@ namespace Emby.Dlna.Didl
public bool Contains(string field) 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 _all || Array.Exists(_fields, x => x.Equals(field, StringComparison.OrdinalIgnoreCase));
return true;
// return _all || ListHelper.ContainsIgnoreCase(_fields, field);
} }
} }
} }

@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable CA1305
using System; using System;
using System.IO; using System.IO;
@ -29,7 +30,6 @@ namespace Emby.Dlna.Didl
{ {
} }
public StringWriterWithEncoding(Encoding encoding) public StringWriterWithEncoding(Encoding encoding)
{ {
_encoding = encoding; _encoding = encoding;

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

@ -54,11 +54,15 @@ namespace Emby.Dlna
_appHost = appHost; _appHost = appHost;
} }
private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
public async Task InitProfilesAsync() public async Task InitProfilesAsync()
{ {
try try
{ {
await ExtractSystemProfilesAsync(); await ExtractSystemProfilesAsync().ConfigureAwait(false);
LoadProfiles(); LoadProfiles();
} }
catch (Exception ex) catch (Exception ex)
@ -240,7 +244,7 @@ namespace Emby.Dlna
} }
else 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); _logger.LogDebug("No matching device profile found. {0}", headerString);
} }
@ -280,10 +284,6 @@ namespace Emby.Dlna
return false; 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) private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
{ {
try try
@ -495,8 +495,8 @@ namespace Emby.Dlna
/// Recreates the object using serialization, to ensure it's not a subclass. /// Recreates the object using serialization, to ensure it's not a subclass.
/// If it's a subclass it may not serlialize properly to xml (different root element tag name). /// If it's a subclass it may not serlialize properly to xml (different root element tag name).
/// </summary> /// </summary>
/// <param name="profile"></param> /// <param name="profile">The device profile.</param>
/// <returns></returns> /// <returns>The reserialized device profile.</returns>
private DeviceProfile ReserializeProfile(DeviceProfile profile) private DeviceProfile ReserializeProfile(DeviceProfile profile)
{ {
if (profile.GetType() == typeof(DeviceProfile)) if (profile.GetType() == typeof(DeviceProfile))
@ -509,13 +509,6 @@ namespace Emby.Dlna
return _jsonSerializer.DeserializeFromString<DeviceProfile>(json); 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) public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{ {
var profile = GetProfile(headers) ?? var profile = GetProfile(headers) ??
@ -540,7 +533,15 @@ namespace Emby.Dlna
Stream = _assembly.GetManifestResourceStream(resource) Stream = _assembly.GetManifestResourceStream(resource)
}; };
} }
private class InternalProfileInfo
{
internal DeviceProfileInfo Info { get; set; }
internal string Path { get; set; }
}
} }
/* /*
class DlnaProfileEntryPoint : IServerEntryPoint class DlnaProfileEntryPoint : IServerEntryPoint
{ {

@ -20,7 +20,7 @@
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<!-- Code Analyzers--> <!-- Code Analyzers-->

@ -15,6 +15,6 @@ namespace Emby.Dlna
public string ContentType { get; set; } public string ContentType { get; set; }
public Dictionary<string, string> Headers { get; set; } public Dictionary<string, string> Headers { get; }
} }
} }

@ -22,6 +22,8 @@ namespace Emby.Dlna.Eventing
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public DlnaEventManager(ILogger logger, IHttpClient httpClient) public DlnaEventManager(ILogger logger, IHttpClient httpClient)
{ {
_httpClient = httpClient; _httpClient = httpClient;
@ -58,7 +60,8 @@ namespace Emby.Dlna.Eventing
var timeout = ParseTimeout(requestedTimeoutString) ?? 300; var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); 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, notificationType,
timeout, timeout,
callbackUrl); callbackUrl);
@ -94,7 +97,7 @@ namespace Emby.Dlna.Eventing
{ {
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId); _logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
_subscriptions.TryRemove(subscriptionId, out EventSubscription sub); _subscriptions.TryRemove(subscriptionId, out _);
return new EventSubscriptionResponse 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) private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
{ {
var response = new EventSubscriptionResponse var response = new EventSubscriptionResponse

@ -8,16 +8,26 @@ namespace Emby.Dlna
/// Cancels the event subscription. /// Cancels the event subscription.
/// </summary> /// </summary>
/// <param name="subscriptionId">The subscription identifier.</param> /// <param name="subscriptionId">The subscription identifier.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse CancelEventSubscription(string subscriptionId); EventSubscriptionResponse CancelEventSubscription(string subscriptionId);
/// <summary> /// <summary>
/// Renews the event subscription. /// Renews the event subscription.
/// </summary> /// </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); EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl);
/// <summary> /// <summary>
/// Creates the event subscription. /// Creates the event subscription.
/// </summary> /// </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); EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
} }
} }

@ -30,7 +30,7 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Dlna.Main namespace Emby.Dlna.Main
{ {
public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup public sealed class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILogger<DlnaEntryPoint> _logger; private readonly ILogger<DlnaEntryPoint> _logger;
@ -54,13 +54,7 @@ namespace Emby.Dlna.Main
private SsdpDevicePublisher _publisher; private SsdpDevicePublisher _publisher;
private ISsdpCommunicationsServer _communicationsServer; private ISsdpCommunicationsServer _communicationsServer;
public IContentDirectory ContentDirectory { get; private set; } private bool _disposed;
public IConnectionManager ConnectionManager { get; private set; }
public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
public static DlnaEntryPoint Current;
public DlnaEntryPoint( public DlnaEntryPoint(
IServerConfigurationManager config, IServerConfigurationManager config,
@ -99,14 +93,14 @@ namespace Emby.Dlna.Main
_networkManager = networkManager; _networkManager = networkManager;
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>(); _logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
ContentDirectory = new ContentDirectory.ContentDirectory( ContentDirectory = new ContentDirectory.ContentDirectoryService(
dlnaManager, dlnaManager,
userDataManager, userDataManager,
imageProcessor, imageProcessor,
libraryManager, libraryManager,
config, config,
userManager, userManager,
loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(), loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
httpClient, httpClient,
localizationManager, localizationManager,
mediaSourceManager, mediaSourceManager,
@ -114,19 +108,27 @@ namespace Emby.Dlna.Main
mediaEncoder, mediaEncoder,
tvSeriesManager); tvSeriesManager);
ConnectionManager = new ConnectionManager.ConnectionManager( ConnectionManager = new ConnectionManager.ConnectionManagerService(
dlnaManager, dlnaManager,
config, config,
loggerFactory.CreateLogger<ConnectionManager.ConnectionManager>(), loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
httpClient); httpClient);
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar( MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrar>(), loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
httpClient, httpClient,
config); config);
Current = this; 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() public async Task RunAsync()
{ {
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); 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() public void Dispose()
{ {
if (_disposed)
{
return;
}
DisposeDevicePublisher(); DisposeDevicePublisher();
DisposePlayToManager(); DisposePlayToManager();
DisposeDeviceDiscovery(); DisposeDeviceDiscovery();
@ -416,16 +434,8 @@ namespace Emby.Dlna.Main
ConnectionManager = null; ConnectionManager = null;
MediaReceiverRegistrar = null; MediaReceiverRegistrar = null;
Current = null; Current = null;
}
public void DisposeDevicePublisher() _disposed = true;
{
if (_publisher != null)
{
_logger.LogInformation("Disposing SsdpDevicePublisher");
_publisher.Dispose();
_publisher = null;
}
} }
} }
} }

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

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

@ -19,15 +19,40 @@ namespace Emby.Dlna.PlayTo
{ {
public class Device : IDisposable 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 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; } public DeviceInfo Properties { get; set; }
private int _muteVol;
public bool IsMuted { get; set; } public bool IsMuted { get; set; }
private int _volume;
public int Volume public int Volume
{ {
get get
@ -43,29 +68,21 @@ namespace Emby.Dlna.PlayTo
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0); public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
public TRANSPORTSTATE TransportState { get; private set; } public TransportState TransportState { get; private set; }
public bool IsPlaying => TransportState == TRANSPORTSTATE.PLAYING;
public bool 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) public UBaseObject CurrentMediaInfo { get; private set; }
{
Properties = deviceProperties;
_httpClient = httpClient;
_logger = logger;
_config = config;
}
public void Start() public void Start()
{ {
@ -73,8 +90,6 @@ namespace Emby.Dlna.PlayTo
_timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite); _timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
} }
private DateTime _lastVolumeRefresh;
private bool _volumeRefreshActive;
private Task RefreshVolumeIfNeeded() private Task RefreshVolumeIfNeeded()
{ {
if (_volumeRefreshActive if (_volumeRefreshActive
@ -105,7 +120,6 @@ namespace Emby.Dlna.PlayTo
} }
} }
private readonly object _timerLock = new object();
private void RestartTimer(bool immediate = false) private void RestartTimer(bool immediate = false)
{ {
lock (_timerLock) lock (_timerLock)
@ -233,6 +247,9 @@ namespace Emby.Dlna.PlayTo
/// <summary> /// <summary>
/// Sets volume on a scale of 0-100. /// Sets volume on a scale of 0-100.
/// </summary> /// </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) public async Task SetVolume(int value, CancellationToken cancellationToken)
{ {
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
@ -275,7 +292,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service"); 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); .ConfigureAwait(false);
RestartTimer(true); RestartTimer(true);
@ -285,7 +302,7 @@ namespace Emby.Dlna.PlayTo
{ {
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false); 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); _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> var dictionary = new Dictionary<string, string>
{ {
{"CurrentURI", url}, { "CurrentURI", url },
{"CurrentURIMetaData", CreateDidlMeta(metaData)} { "CurrentURIMetaData", CreateDidlMeta(metaData) }
}; };
var service = GetAvTransportService(); 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)) await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false); .ConfigureAwait(false);
TransportState = TRANSPORTSTATE.PAUSED; TransportState = TransportState.Paused;
RestartTimer(true); RestartTimer(true);
} }
private int _connectFailureCount;
private async void TimerCallback(object sender) private async void TimerCallback(object sender)
{ {
if (_disposed) if (_disposed)
@ -436,7 +451,7 @@ namespace Emby.Dlna.PlayTo
if (transportState.HasValue) if (transportState.HasValue)
{ {
// If we're not playing anything no need to get additional data // 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); 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 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(); RestartTimerInactive();
} }
@ -539,7 +554,7 @@ namespace Emby.Dlna.PlayTo
return; 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; var volumeValue = volume?.Value;
if (string.IsNullOrWhiteSpace(volumeValue)) if (string.IsNullOrWhiteSpace(volumeValue))
@ -589,14 +604,14 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse") var valueNode = result.Document.Descendants(UPnpNamespaces.RenderingControl + "GetMuteResponse")
.Select(i => i.Element("CurrentMute")) .Select(i => i.Element("CurrentMute"))
.FirstOrDefault(i => i != null); .FirstOrDefault(i => i != null);
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase); 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"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
if (command == null) if (command == null)
@ -623,12 +638,12 @@ namespace Emby.Dlna.PlayTo
} }
var transportState = 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; var transportStateValue = transportState?.Value;
if (transportStateValue != null if (transportStateValue != null
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state)) && Enum.TryParse(transportStateValue, true, out TransportState state))
{ {
return state; return state;
} }
@ -636,7 +651,7 @@ namespace Emby.Dlna.PlayTo
return null; 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"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
if (command == null) if (command == null)
@ -671,7 +686,7 @@ namespace Emby.Dlna.PlayTo
return null; return null;
} }
var e = track.Element(uPnpNamespaces.items) ?? track; var e = track.Element(UPnpNamespaces.Items) ?? track;
var elementString = (string)e; var elementString = (string)e;
@ -687,13 +702,13 @@ namespace Emby.Dlna.PlayTo
return null; return null;
} }
e = track.Element(uPnpNamespaces.items) ?? track; e = track.Element(UPnpNamespaces.Items) ?? track;
elementString = (string)e; elementString = (string)e;
if (!string.IsNullOrWhiteSpace(elementString)) if (!string.IsNullOrWhiteSpace(elementString))
{ {
return new uBaseObject return new UBaseObject
{ {
Url = elementString Url = elementString
}; };
@ -702,7 +717,7 @@ namespace Emby.Dlna.PlayTo
return null; 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"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
if (command == null) if (command == null)
@ -731,11 +746,11 @@ namespace Emby.Dlna.PlayTo
return (false, null); return (false, null);
} }
var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != 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 trackUri = trackUriElem?.Value;
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null); 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 duration = durationElem?.Value;
if (!string.IsNullOrWhiteSpace(duration) if (!string.IsNullOrWhiteSpace(duration)
&& !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
@ -747,8 +762,8 @@ namespace Emby.Dlna.PlayTo
Duration = null; Duration = null;
} }
var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != 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 position = positionElem?.Value;
if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
{ {
@ -787,7 +802,7 @@ namespace Emby.Dlna.PlayTo
return (true, null); return (true, null);
} }
var e = uPnpResponse.Element(uPnpNamespaces.items); var e = uPnpResponse.Element(UPnpNamespaces.Items);
var uTrack = CreateUBaseObject(e, trackUri); var uTrack = CreateUBaseObject(e, trackUri);
@ -819,7 +834,7 @@ namespace Emby.Dlna.PlayTo
// some devices send back invalid xml // some devices send back invalid xml
try try
{ {
return XElement.Parse(xml.Replace("&", "&amp;")); return XElement.Parse(xml.Replace("&", "&amp;", StringComparison.Ordinal));
} }
catch (XmlException) catch (XmlException)
{ {
@ -828,27 +843,27 @@ namespace Emby.Dlna.PlayTo
return null; return null;
} }
private static uBaseObject CreateUBaseObject(XElement container, string trackUri) private static UBaseObject CreateUBaseObject(XElement container, string trackUri)
{ {
if (container == null) if (container == null)
{ {
throw new ArgumentNullException(nameof(container)); throw new ArgumentNullException(nameof(container));
} }
var url = container.GetValue(uPnpNamespaces.Res); var url = container.GetValue(UPnpNamespaces.Res);
if (string.IsNullOrWhiteSpace(url)) if (string.IsNullOrWhiteSpace(url))
{ {
url = trackUri; url = trackUri;
} }
return new uBaseObject return new UBaseObject
{ {
Id = container.GetAttributeValue(uPnpNamespaces.Id), Id = container.GetAttributeValue(UPnpNamespaces.Id),
ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId), ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId),
Title = container.GetValue(uPnpNamespaces.title), Title = container.GetValue(UPnpNamespaces.Title),
IconUrl = container.GetValue(uPnpNamespaces.Artwork), IconUrl = container.GetValue(UPnpNamespaces.Artwork),
SecondText = "", SecondText = string.Empty,
Url = url, Url = url,
ProtocolInfo = GetProtocolInfo(container), ProtocolInfo = GetProtocolInfo(container),
MetaData = container.ToString() MetaData = container.ToString()
@ -862,11 +877,11 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentNullException(nameof(container)); throw new ArgumentNullException(nameof(container));
} }
var resElement = container.Element(uPnpNamespaces.Res); var resElement = container.Element(UPnpNamespaces.Res);
if (resElement != null) if (resElement != null)
{ {
var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo); var info = resElement.Attribute(UPnpNamespaces.ProtocolInfo);
if (info != null && !string.IsNullOrWhiteSpace(info.Value)) if (info != null && !string.IsNullOrWhiteSpace(info.Value))
{ {
@ -941,12 +956,12 @@ namespace Emby.Dlna.PlayTo
return url; return url;
} }
if (!url.Contains("/")) if (!url.Contains('/', StringComparison.Ordinal))
{ {
url = "/dmr/" + url; url = "/dmr/" + url;
} }
if (!url.StartsWith("/")) if (!url.StartsWith("/", StringComparison.Ordinal))
{ {
url = "/" + url; url = "/" + url;
} }
@ -954,11 +969,7 @@ namespace Emby.Dlna.PlayTo
return baseUrl + url; return baseUrl + url;
} }
private TransportCommands AvCommands { get; set; } public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger, CancellationToken cancellationToken)
private TransportCommands RendererCommands { get; set; }
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken)
{ {
var ssdpHttpClient = new SsdpHttpClient(httpClient); var ssdpHttpClient = new SsdpHttpClient(httpClient);
@ -966,13 +977,13 @@ namespace Emby.Dlna.PlayTo
var friendlyNames = new List<string>(); 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)) if (name != null && !string.IsNullOrWhiteSpace(name.Value))
{ {
friendlyNames.Add(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)) if (room != null && !string.IsNullOrWhiteSpace(room.Value))
{ {
friendlyNames.Add(room.Value); friendlyNames.Add(room.Value);
@ -981,77 +992,77 @@ namespace Emby.Dlna.PlayTo
var deviceProperties = new DeviceInfo() var deviceProperties = new DeviceInfo()
{ {
Name = string.Join(" ", friendlyNames), 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) if (model != null)
{ {
deviceProperties.ModelName = model.Value; 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) if (modelNumber != null)
{ {
deviceProperties.ModelNumber = modelNumber.Value; 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) if (uuid != null)
{ {
deviceProperties.UUID = uuid.Value; 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) if (manufacturer != null)
{ {
deviceProperties.Manufacturer = manufacturer.Value; 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) if (manufacturerUrl != null)
{ {
deviceProperties.ManufacturerUrl = manufacturerUrl.Value; 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) if (presentationUrl != null)
{ {
deviceProperties.PresentationUrl = presentationUrl.Value; 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) if (modelUrl != null)
{ {
deviceProperties.ModelUrl = modelUrl.Value; 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) if (serialNumber != null)
{ {
deviceProperties.SerialNumber = serialNumber.Value; 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) if (modelDescription != null)
{ {
deviceProperties.ModelDescription = modelDescription.Value; 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) if (icon != null)
{ {
deviceProperties.Icon = CreateIcon(icon); 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) if (services == null)
{ {
continue; continue;
} }
var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service")); var servicesList = services.Descendants(UPnpNamespaces.Ud.GetName("service"));
if (servicesList == null) if (servicesList == null)
{ {
continue; 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) private static DeviceIcon CreateIcon(XElement element)
{ {
if (element == null) if (element == null)
@ -1079,11 +1089,11 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentNullException(nameof(element)); throw new ArgumentNullException(nameof(element));
} }
var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype")); var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype"));
var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width")); var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height")); var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth")); var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth"));
var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url")); var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url"));
var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture); var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture);
var heightValue = int.Parse(height, 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) private static DeviceService Create(XElement element)
{ {
var type = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceType")); var type = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType"));
var id = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceId")); var id = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId"));
var scpdUrl = element.GetDescendantValue(uPnpNamespaces.ud.GetName("SCPDURL")); var scpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL"));
var controlURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("controlURL")); var controlURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL"));
var eventSubURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("eventSubURL")); var eventSubURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL"));
return new DeviceService return new DeviceService
{ {
@ -1116,14 +1126,7 @@ namespace Emby.Dlna.PlayTo
}; };
} }
public event EventHandler<PlaybackStartEventArgs> PlaybackStart; private void UpdateMediaInfo(UBaseObject mediaInfo, TransportState state)
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)
{ {
TransportState = state; TransportState = state;
@ -1132,7 +1135,7 @@ namespace Emby.Dlna.PlayTo
if (previousMediaInfo == null && mediaInfo != null) if (previousMediaInfo == null && mediaInfo != null)
{ {
if (state != TRANSPORTSTATE.STOPPED) if (state != TransportState.Stopped)
{ {
OnPlaybackStart(mediaInfo); 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)) 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)) 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 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 MediaChanged?.Invoke(this, new MediaChangedEventArgs
{ {
@ -1194,14 +1197,17 @@ namespace Emby.Dlna.PlayTo
}); });
} }
bool _disposed; /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); 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) protected virtual void Dispose(bool disposing)
{ {
if (_disposed) if (_disposed)
@ -1220,9 +1226,10 @@ namespace Emby.Dlna.PlayTo
_disposed = true; _disposed = true;
} }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl); return string.Format(CultureInfo.InvariantCulture, "{0} - {1}", Properties.Name, Properties.BaseUrl);
} }
} }
} }

@ -8,6 +8,9 @@ namespace Emby.Dlna.PlayTo
{ {
public class DeviceInfo public class DeviceInfo
{ {
private readonly List<DeviceService> _services = new List<DeviceService>();
private string _baseUrl = string.Empty;
public DeviceInfo() public DeviceInfo()
{ {
Name = "Generic Device"; Name = "Generic Device";
@ -33,7 +36,6 @@ namespace Emby.Dlna.PlayTo
public string PresentationUrl { get; set; } public string PresentationUrl { get; set; }
private string _baseUrl = string.Empty;
public string BaseUrl public string BaseUrl
{ {
get => _baseUrl; get => _baseUrl;
@ -42,7 +44,6 @@ namespace Emby.Dlna.PlayTo
public DeviceIcon Icon { get; set; } public DeviceIcon Icon { get; set; }
private readonly List<DeviceService> _services = new List<DeviceService>();
public List<DeviceService> Services => _services; public List<DeviceService> Services => _services;
public DeviceIdentification ToDeviceIdentification() public DeviceIdentification ToDeviceIdentification()

@ -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; }
}
}

@ -31,7 +31,6 @@ namespace Emby.Dlna.PlayTo
{ {
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private Device _device;
private readonly SessionInfo _session; private readonly SessionInfo _session;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -50,6 +49,7 @@ namespace Emby.Dlna.PlayTo
private readonly string _accessToken; private readonly string _accessToken;
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>(); private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
private Device _device;
private int _currentPlaylistIndex; private int _currentPlaylistIndex;
private bool _disposed; private bool _disposed;
@ -372,8 +372,13 @@ namespace Emby.Dlna.PlayTo
if (!command.ControllingUserId.Equals(Guid.Empty)) if (!command.ControllingUserId.Equals(Guid.Empty))
{ {
_sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId, _sessionManager.LogSessionActivity(
_session.DeviceName, _session.RemoteEndPoint, user); _session.Client,
_session.ApplicationVersion,
_session.DeviceId,
_session.DeviceName,
_session.RemoteEndPoint,
user);
} }
return PlayItems(playlist, cancellationToken); return PlayItems(playlist, cancellationToken);
@ -498,42 +503,44 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.MediaType == DlnaProfileType.Audio) if (streamInfo.MediaType == DlnaProfileType.Audio)
{ {
return new ContentFeatureBuilder(profile) return new ContentFeatureBuilder(profile)
.BuildAudioHeader(streamInfo.Container, .BuildAudioHeader(
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.Container,
streamInfo.TargetAudioBitrate, streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetAudioSampleRate, streamInfo.TargetAudioBitrate,
streamInfo.TargetAudioChannels, streamInfo.TargetAudioSampleRate,
streamInfo.TargetAudioBitDepth, streamInfo.TargetAudioChannels,
streamInfo.IsDirectStream, streamInfo.TargetAudioBitDepth,
streamInfo.RunTimeTicks ?? 0, streamInfo.IsDirectStream,
streamInfo.TranscodeSeekInfo); streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo);
} }
if (streamInfo.MediaType == DlnaProfileType.Video) if (streamInfo.MediaType == DlnaProfileType.Video)
{ {
var list = new ContentFeatureBuilder(profile) var list = new ContentFeatureBuilder(profile)
.BuildVideoHeader(streamInfo.Container, .BuildVideoHeader(
streamInfo.TargetVideoCodec.FirstOrDefault(), streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetWidth, streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetHeight, streamInfo.TargetWidth,
streamInfo.TargetVideoBitDepth, streamInfo.TargetHeight,
streamInfo.TargetVideoBitrate, streamInfo.TargetVideoBitDepth,
streamInfo.TargetTimestamp, streamInfo.TargetVideoBitrate,
streamInfo.IsDirectStream, streamInfo.TargetTimestamp,
streamInfo.RunTimeTicks ?? 0, streamInfo.IsDirectStream,
streamInfo.TargetVideoProfile, streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoLevel, streamInfo.TargetVideoProfile,
streamInfo.TargetFramerate ?? 0, streamInfo.TargetVideoLevel,
streamInfo.TargetPacketLength, streamInfo.TargetFramerate ?? 0,
streamInfo.TranscodeSeekInfo, streamInfo.TargetPacketLength,
streamInfo.IsTargetAnamorphic, streamInfo.TranscodeSeekInfo,
streamInfo.IsTargetInterlaced, streamInfo.IsTargetAnamorphic,
streamInfo.TargetRefFrames, streamInfo.IsTargetInterlaced,
streamInfo.TargetVideoStreamCount, streamInfo.TargetRefFrames,
streamInfo.TargetAudioStreamCount, streamInfo.TargetVideoStreamCount,
streamInfo.TargetVideoCodecTag, streamInfo.TargetAudioStreamCount,
streamInfo.IsTargetAVC); streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC);
return list.Count == 0 ? null : list[0]; return list.Count == 0 ? null : list[0];
} }
@ -633,6 +640,10 @@ namespace Emby.Dlna.PlayTo
GC.SuppressFinalize(this); 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) protected virtual void Dispose(bool disposing)
{ {
if (_disposed) if (_disposed)
@ -673,48 +684,41 @@ namespace Emby.Dlna.PlayTo
case GeneralCommandType.ToggleMute: case GeneralCommandType.ToggleMute:
return _device.ToggleMute(cancellationToken); return _device.ToggleMute(cancellationToken);
case GeneralCommandType.SetAudioStreamIndex: 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);
{
return SetAudioStreamIndex(val);
}
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
} }
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: 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);
{
return SetSubtitleStreamIndex(val);
}
throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
} }
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: 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);
{
return _device.SetVolume(volume, cancellationToken);
}
throw new ArgumentException("Unsupported volume value supplied.");
} }
throw new ArgumentException("Volume argument cannot be null"); throw new ArgumentException("Unsupported volume value supplied.");
} }
throw new ArgumentException("Volume argument cannot be null");
default: default:
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -778,7 +782,7 @@ namespace Emby.Dlna.PlayTo
const int maxWait = 15000000; const int maxWait = 15000000;
const int interval = 500; const int interval = 500;
var currentWait = 0; var currentWait = 0;
while (_device.TransportState != TRANSPORTSTATE.PLAYING && currentWait < maxWait) while (_device.TransportState != TransportState.Playing && currentWait < maxWait)
{ {
await Task.Delay(interval).ConfigureAwait(false); await Task.Delay(interval).ConfigureAwait(false);
currentWait += interval; currentWait += interval;
@ -787,8 +791,67 @@ namespace Emby.Dlna.PlayTo
await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false); 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 class StreamParams
{ {
private MediaSourceInfo mediaSource;
private IMediaSourceManager _mediaSourceManager;
public Guid ItemId { get; set; } public Guid ItemId { get; set; }
public bool IsDirectStream { get; set; } public bool IsDirectStream { get; set; }
@ -809,15 +872,11 @@ namespace Emby.Dlna.PlayTo
public BaseItem Item { get; set; } public BaseItem Item { get; set; }
private MediaSourceInfo MediaSource;
private IMediaSourceManager _mediaSourceManager;
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken) public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
{ {
if (MediaSource != null) if (mediaSource != null)
{ {
return MediaSource; return mediaSource;
} }
var hasMediaSources = Item as IHasMediaSources; var hasMediaSources = Item as IHasMediaSources;
@ -827,9 +886,9 @@ namespace Emby.Dlna.PlayTo
return null; 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) private static Guid GetItemId(string url)
@ -901,61 +960,5 @@ namespace Emby.Dlna.PlayTo
return request; 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;
}
} }
} }

@ -92,7 +92,7 @@ namespace Emby.Dlna.PlayTo
// It has to report that it's a media renderer // It has to report that it's a media renderer
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1) nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
{ {
// _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location); // _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
return; return;
@ -174,7 +174,7 @@ namespace Emby.Dlna.PlayTo
if (controller == null) 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; string deviceName = device.Properties.Name;
@ -192,20 +192,20 @@ namespace Emby.Dlna.PlayTo
controller = new PlayToController( controller = new PlayToController(
sessionInfo, sessionInfo,
_sessionManager, _sessionManager,
_libraryManager, _libraryManager,
_logger, _logger,
_dlnaManager, _dlnaManager,
_userManager, _userManager,
_imageProcessor, _imageProcessor,
serverAddress, serverAddress,
null, null,
_deviceDiscovery, _deviceDiscovery,
_userDataManager, _userDataManager,
_localization, _localization,
_mediaSourceManager, _mediaSourceManager,
_config, _config,
_mediaEncoder); _mediaEncoder);
sessionInfo.AddController(controller); sessionInfo.AddController(controller);
@ -218,17 +218,17 @@ namespace Emby.Dlna.PlayTo
{ {
PlayableMediaTypes = profile.GetSupportedMediaTypes(), PlayableMediaTypes = profile.GetSupportedMediaTypes(),
SupportedCommands = new string[] SupportedCommands = new[]
{ {
GeneralCommandType.VolumeDown.ToString(), GeneralCommandType.VolumeDown.ToString(),
GeneralCommandType.VolumeUp.ToString(), GeneralCommandType.VolumeUp.ToString(),
GeneralCommandType.Mute.ToString(), GeneralCommandType.Mute.ToString(),
GeneralCommandType.Unmute.ToString(), GeneralCommandType.Unmute.ToString(),
GeneralCommandType.ToggleMute.ToString(), GeneralCommandType.ToggleMute.ToString(),
GeneralCommandType.SetVolume.ToString(), GeneralCommandType.SetVolume.ToString(),
GeneralCommandType.SetAudioStreamIndex.ToString(), GeneralCommandType.SetAudioStreamIndex.ToString(),
GeneralCommandType.SetSubtitleStreamIndex.ToString(), GeneralCommandType.SetSubtitleStreamIndex.ToString(),
GeneralCommandType.PlayMediaSource.ToString() GeneralCommandType.PlayMediaSource.ToString()
}, },
SupportsMediaControl = true SupportsMediaControl = true
@ -247,8 +247,9 @@ namespace Emby.Dlna.PlayTo
{ {
_disposeCancellationTokenSource.Cancel(); _disposeCancellationTokenSource.Cancel();
} }
catch catch (Exception ex)
{ {
_logger.LogDebug(ex, "Error while disposing PlayToManager");
} }
_sessionLock.Dispose(); _sessionLock.Dispose();

@ -6,6 +6,6 @@ namespace Emby.Dlna.PlayTo
{ {
public class PlaybackProgressEventArgs : EventArgs public class PlaybackProgressEventArgs : EventArgs
{ {
public uBaseObject MediaInfo { get; set; } public UBaseObject MediaInfo { get; set; }
} }
} }

@ -6,6 +6,6 @@ namespace Emby.Dlna.PlayTo
{ {
public class PlaybackStartEventArgs : EventArgs public class PlaybackStartEventArgs : EventArgs
{ {
public uBaseObject MediaInfo { get; set; } public UBaseObject MediaInfo { get; set; }
} }
} }

@ -6,13 +6,6 @@ namespace Emby.Dlna.PlayTo
{ {
public class PlaybackStoppedEventArgs : EventArgs public class PlaybackStoppedEventArgs : EventArgs
{ {
public uBaseObject MediaInfo { get; set; } public UBaseObject MediaInfo { get; set; }
}
public class MediaChangedEventArgs : EventArgs
{
public uBaseObject OldMediaInfo { get; set; }
public uBaseObject NewMediaInfo { get; set; }
} }
} }

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

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Emby.Dlna.Common; using Emby.Dlna.Common;
@ -11,36 +12,30 @@ namespace Emby.Dlna.PlayTo
{ {
public class TransportCommands public class TransportCommands
{ {
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
private List<StateVariable> _stateVariables = new List<StateVariable>(); private List<StateVariable> _stateVariables = new List<StateVariable>();
public List<StateVariable> StateVariables
{
get => _stateVariables;
set => _stateVariables = value;
}
private List<ServiceAction> _serviceActions = new List<ServiceAction>(); private List<ServiceAction> _serviceActions = new List<ServiceAction>();
public List<ServiceAction> ServiceActions
{ public List<StateVariable> StateVariables => _stateVariables;
get => _serviceActions;
set => _serviceActions = value; public List<ServiceAction> ServiceActions => _serviceActions;
}
public static TransportCommands Create(XDocument document) public static TransportCommands Create(XDocument document)
{ {
var command = new TransportCommands(); 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)); command.ServiceActions.Add(ServiceActionFromXml(container));
} }
var stateValues = document.Descendants(uPnpNamespaces.ServiceStateTable).FirstOrDefault(); var stateValues = document.Descendants(UPnpNamespaces.ServiceStateTable).FirstOrDefault();
if (stateValues != null) 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)); command.StateVariables.Add(FromXml(container));
} }
@ -51,19 +46,19 @@ namespace Emby.Dlna.PlayTo
private static ServiceAction ServiceActionFromXml(XElement container) 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)); argumentList.Add(ArgumentFromXml(arg));
} }
return new ServiceAction return serviceAction;
{
Name = container.GetValue(uPnpNamespaces.svc + "name"),
ArgumentList = argumentList
};
} }
private static Argument ArgumentFromXml(XElement container) private static Argument ArgumentFromXml(XElement container)
@ -75,29 +70,29 @@ namespace Emby.Dlna.PlayTo
return new Argument return new Argument
{ {
Name = container.GetValue(uPnpNamespaces.svc + "name"), Name = container.GetValue(UPnpNamespaces.Svc + "name"),
Direction = container.GetValue(uPnpNamespaces.svc + "direction"), Direction = container.GetValue(UPnpNamespaces.Svc + "direction"),
RelatedStateVariable = container.GetValue(uPnpNamespaces.svc + "relatedStateVariable") RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable")
}; };
} }
private static StateVariable FromXml(XElement container) private static StateVariable FromXml(XElement container)
{ {
var allowedValues = new List<string>(); var allowedValues = new List<string>();
var element = container.Descendants(uPnpNamespaces.svc + "allowedValueList") var element = container.Descendants(UPnpNamespaces.Svc + "allowedValueList")
.FirstOrDefault(); .FirstOrDefault();
if (element != null) if (element != null)
{ {
var values = element.Descendants(uPnpNamespaces.svc + "allowedValue"); var values = element.Descendants(UPnpNamespaces.Svc + "allowedValue");
allowedValues.AddRange(values.Select(child => child.Value)); allowedValues.AddRange(values.Select(child => child.Value));
} }
return new StateVariable return new StateVariable
{ {
Name = container.GetValue(uPnpNamespaces.svc + "name"), Name = container.GetValue(UPnpNamespaces.Svc + "name"),
DataType = container.GetValue(uPnpNamespaces.svc + "dataType"), DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"),
AllowedValues = allowedValues.ToArray() 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 = "") 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) 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 = "") private string BuildArgumentXml(Argument argument, string value, string commandParameter = "")
@ -180,15 +175,12 @@ namespace Emby.Dlna.PlayTo
if (state != null) if (state != null)
{ {
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ?? var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
state.AllowedValues.FirstOrDefault() ?? (state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
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>";
} }
} }

@ -0,0 +1,14 @@
#pragma warning disable CS1591
#pragma warning disable SA1602
namespace Emby.Dlna.PlayTo
{
public enum TransportState
{
Stopped,
Playing,
Transitioning,
PausedPlayback,
Paused
}
}

@ -6,22 +6,22 @@ using Emby.Dlna.Ssdp;
namespace Emby.Dlna.PlayTo 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) if (container == null)
{ {
throw new ArgumentNullException(nameof(container)); throw new ArgumentNullException(nameof(container));
} }
return new uBaseObject return new UBaseObject
{ {
Id = container.GetAttributeValue(uPnpNamespaces.Id), Id = container.GetAttributeValue(UPnpNamespaces.Id),
ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId), ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId),
Title = container.GetValue(uPnpNamespaces.title), Title = container.GetValue(UPnpNamespaces.Title),
IconUrl = container.GetValue(uPnpNamespaces.Artwork), IconUrl = container.GetValue(UPnpNamespaces.Artwork),
UpnpClass = container.GetValue(uPnpNamespaces.uClass) UpnpClass = container.GetValue(UPnpNamespaces.Class)
}; };
} }
} }

@ -1,10 +1,11 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {
public class uBaseObject public class UBaseObject
{ {
public string Id { get; set; } public string Id { get; set; }
@ -20,20 +21,10 @@ namespace Emby.Dlna.PlayTo
public string Url { get; set; } public string Url { get; set; }
public string[] ProtocolInfo { get; set; } public IReadOnlyList<string> ProtocolInfo { get; set; }
public string UpnpClass { 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 public string MediaType
{ {
get get
@ -58,5 +49,15 @@ namespace Emby.Dlna.PlayTo
return null; return null;
} }
} }
public bool Equals(UBaseObject obj)
{
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
return string.Equals(Id, obj.Id, StringComparison.Ordinal);
}
} }
} }

@ -4,38 +4,64 @@ using System.Xml.Linq;
namespace Emby.Dlna.PlayTo 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 Dc { get; } = "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 Ns { get; } = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
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 Svc { get; } = "urn:schemas-upnp-org:service-1-0";
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 Ud { get; } = "urn:schemas-upnp-org:device-1-0";
public static XNamespace ContentDirectory = "urn:schemas-upnp-org:service:ContentDirectory:1";
public static XNamespace UPnp { get; } = "urn:schemas-upnp-org:metadata-1-0/upnp/";
public static XName containers = ns + "container";
public static XName items = ns + "item"; public static XNamespace RenderingControl { get; } = "urn:schemas-upnp-org:service:RenderingControl:1";
public static XName title = dc + "title";
public static XName creator = dc + "creator"; public static XNamespace AvTransport { get; } = "urn:schemas-upnp-org:service:AVTransport:1";
public static XName artist = upnp + "artist";
public static XName Id = "id"; public static XNamespace ContentDirectory { get; } = "urn:schemas-upnp-org:service:ContentDirectory:1";
public static XName ParentId = "parentID";
public static XName uClass = upnp + "class"; public static XName Containers { get; } = Ns + "container";
public static XName Artwork = upnp + "albumArtURI";
public static XName Description = dc + "description"; public static XName Items { get; } = Ns + "item";
public static XName LongDescription = upnp + "longDescription";
public static XName Album = upnp + "album"; public static XName Title { get; } = Dc + "title";
public static XName Author = upnp + "author";
public static XName Director = upnp + "director"; public static XName Creator { get; } = Dc + "creator";
public static XName PlayCount = upnp + "playbackCount";
public static XName Tracknumber = upnp + "originalTrackNumber"; public static XName Artist { get; } = UPnp + "artist";
public static XName Res = ns + "res";
public static XName Duration = "duration"; public static XName Id { get; } = "id";
public static XName ProtocolInfo = "protocolInfo";
public static XName ParentId { get; } = "parentID";
public static XName ServiceStateTable = svc + "serviceStateTable";
public static XName StateVariable = svc + "stateVariable"; 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";
} }
} }

@ -64,14 +64,14 @@ namespace Emby.Dlna.Profiles
new DirectPlayProfile new DirectPlayProfile
{ {
// play all // play all
Container = "", Container = string.Empty,
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
new DirectPlayProfile new DirectPlayProfile
{ {
// play all // play all
Container = "", Container = string.Empty,
Type = DlnaProfileType.Audio Type = DlnaProfileType.Audio
} }
}; };

@ -24,7 +24,7 @@ namespace Emby.Dlna.Profiles
{ {
Match = HeaderMatchType.Substring, Match = HeaderMatchType.Substring,
Name = "User-Agent", Name = "User-Agent",
Value ="Zip_" Value = "Zip_"
} }
} }
}; };
@ -81,7 +81,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -124,7 +124,7 @@ namespace Emby.Dlna.Profiles
new CodecProfile new CodecProfile
{ {
Type = CodecType.Video, Type = CodecType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -161,7 +161,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "ac3,he-aac", Codec = "ac3,he-aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -177,7 +177,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -192,7 +192,7 @@ namespace Emby.Dlna.Profiles
new CodecProfile new CodecProfile
{ {
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Conditions = new [] Conditions = new[]
{ {
// The device does not have any audio switching capabilities // The device does not have any audio switching capabilities
new ProfileCondition new ProfileCondition

@ -84,7 +84,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -191,7 +191,7 @@ namespace Emby.Dlna.Profiles
} }
}; };
ResponseProfiles = new ResponseProfile[] ResponseProfiles = new[]
{ {
new ResponseProfile new ResponseProfile
{ {

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

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

@ -93,8 +93,8 @@ namespace Emby.Dlna.Profiles
new CodecProfile new CodecProfile
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec="h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline"), new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline"),
new ProfileCondition new ProfileCondition
@ -122,7 +122,7 @@ namespace Emby.Dlna.Profiles
new CodecProfile new CodecProfile
{ {
Type = CodecType.Video, Type = CodecType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -150,7 +150,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -166,7 +166,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Audio, Type = CodecType.Audio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -182,7 +182,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Audio, Type = CodecType.Audio,
Codec = "mp3", Codec = "mp3",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -202,7 +202,7 @@ namespace Emby.Dlna.Profiles
} }
}; };
ResponseProfiles = new ResponseProfile[] ResponseProfiles = new[]
{ {
new ResponseProfile new ResponseProfile
{ {

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

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

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

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

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

@ -114,7 +114,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -156,7 +156,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "ac3", Codec = "ac3",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -172,7 +172,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -191,7 +191,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -217,7 +217,7 @@ namespace Emby.Dlna.Profiles
VideoCodec = "h264,mpeg4,vc1", VideoCodec = "h264,mpeg4,vc1",
AudioCodec = "ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },

@ -102,13 +102,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -128,13 +128,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -148,28 +148,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="mpeg2video", VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "mpeg", Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video", VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg", MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
} }
}; };
@ -180,7 +180,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -204,7 +204,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -243,7 +243,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "mpeg2video", Codec = "mpeg2video",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -275,7 +275,7 @@ namespace Emby.Dlna.Profiles
new CodecProfile new CodecProfile
{ {
Type = CodecType.Video, Type = CodecType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -303,7 +303,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "ac3", Codec = "ac3",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -319,7 +319,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -341,7 +341,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "mp3,mp2", Codec = "mp3,mp2",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {

@ -120,7 +120,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -143,13 +143,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -169,13 +169,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -189,28 +189,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="mpeg2video", VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "mpeg", Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video", VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg", MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
@ -227,7 +227,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -266,7 +266,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "mpeg2video", Codec = "mpeg2video",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -298,7 +298,7 @@ namespace Emby.Dlna.Profiles
new CodecProfile new CodecProfile
{ {
Type = CodecType.Video, Type = CodecType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -326,7 +326,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "ac3", Codec = "ac3",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -364,7 +364,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "mp3,mp2", Codec = "mp3,mp2",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {

@ -131,13 +131,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -157,13 +157,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -177,28 +177,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="mpeg2video", VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "mpeg", Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video", VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg", MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
@ -215,7 +215,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -282,7 +282,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "mp3,mp2", Codec = "mp3,mp2",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {

@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -187,13 +187,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -213,13 +213,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -233,28 +233,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="mpeg2video", VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "mpeg", Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video", VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg", MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
@ -265,14 +265,13 @@ namespace Emby.Dlna.Profiles
} }
}; };
CodecProfiles = new[] CodecProfiles = new[]
{ {
new CodecProfile new CodecProfile
{ {
Type = CodecType.Video, Type = CodecType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -300,7 +299,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "mp3,mp2", Codec = "mp3,mp2",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {

@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -187,13 +187,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -213,13 +213,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg", 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, Type = DlnaProfileType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -233,28 +233,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="h264", VideoCodec = "h264",
AudioCodec="ac3,aac,mp3", AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "ts,mpegts", Container = "ts,mpegts",
VideoCodec="mpeg2video", VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts", 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 Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
{ {
Container = "mpeg", Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video", VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg", MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL", OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
new ResponseProfile new ResponseProfile
@ -265,14 +265,13 @@ namespace Emby.Dlna.Profiles
} }
}; };
CodecProfiles = new[] CodecProfiles = new[]
{ {
new CodecProfile new CodecProfile
{ {
Type = CodecType.Video, Type = CodecType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -300,7 +299,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "mp3,mp2", Codec = "mp3,mp2",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {

@ -108,7 +108,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -133,7 +133,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.Video, Type = CodecType.Video,
Codec = "h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -176,7 +176,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "ac3", Codec = "ac3",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -201,7 +201,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "wmapro", Codec = "wmapro",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -217,7 +217,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -235,7 +235,7 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "mp4,mov", Container = "mp4,mov",
AudioCodec="aac", AudioCodec = "aac",
MimeType = "video/mp4", MimeType = "video/mp4",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
@ -244,7 +244,7 @@ namespace Emby.Dlna.Profiles
{ {
Container = "avi", Container = "avi",
MimeType = "video/divx", MimeType = "video/divx",
OrgPn="AVI", OrgPn = "AVI",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },

@ -110,7 +110,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -135,7 +135,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.Video, Type = CodecType.Video,
Codec = "h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -178,7 +178,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "ac3", Codec = "ac3",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -203,7 +203,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "wmapro", Codec = "wmapro",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -219,7 +219,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -237,7 +237,7 @@ namespace Emby.Dlna.Profiles
new ResponseProfile new ResponseProfile
{ {
Container = "mp4,mov", Container = "mp4,mov",
AudioCodec="aac", AudioCodec = "aac",
MimeType = "video/mp4", MimeType = "video/mp4",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },
@ -246,7 +246,7 @@ namespace Emby.Dlna.Profiles
{ {
Container = "avi", Container = "avi",
MimeType = "video/divx", MimeType = "video/divx",
OrgPn="AVI", OrgPn = "AVI",
Type = DlnaProfileType.Video Type = DlnaProfileType.Video
}, },

@ -20,7 +20,7 @@ namespace Emby.Dlna.Profiles
Headers = new[] Headers = new[]
{ {
new HttpHeaderInfo {Name = "User-Agent", Value = "alphanetworks", Match = HeaderMatchType.Substring}, new HttpHeaderInfo { Name = "User-Agent", Value = "alphanetworks", Match = HeaderMatchType.Substring },
new HttpHeaderInfo new HttpHeaderInfo
{ {
Name = "User-Agent", Name = "User-Agent",
@ -168,7 +168,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = DlnaProfileType.Photo, Type = DlnaProfileType.Photo,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -193,7 +193,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.Video, Type = CodecType.Video,
Codec = "h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -221,7 +221,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {

@ -119,7 +119,7 @@ namespace Emby.Dlna.Profiles
Type = DlnaProfileType.Video, Type = DlnaProfileType.Video,
Container = "mp4,mov", Container = "mp4,mov",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -138,7 +138,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "mpeg4", Codec = "mpeg4",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -187,7 +187,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "h264", Codec = "h264",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -236,7 +236,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.Video, Type = CodecType.Video,
Codec = "wmv2,wmv3,vc1", Codec = "wmv2,wmv3,vc1",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -284,7 +284,7 @@ namespace Emby.Dlna.Profiles
new CodecProfile new CodecProfile
{ {
Type = CodecType.Video, Type = CodecType.Video,
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -307,7 +307,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "ac3,wmav2,wmapro", Codec = "ac3,wmav2,wmapro",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {
@ -323,7 +323,7 @@ namespace Emby.Dlna.Profiles
{ {
Type = CodecType.VideoAudio, Type = CodecType.VideoAudio,
Codec = "aac", Codec = "aac",
Conditions = new [] Conditions = new[]
{ {
new ProfileCondition new ProfileCondition
{ {

@ -15,11 +15,7 @@ namespace Emby.Dlna.Service
{ {
public abstract class BaseControlHandler public abstract class BaseControlHandler
{ {
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"; private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
protected IServerConfigurationManager Config { get; }
protected ILogger Logger { get; }
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger) protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
{ {
@ -27,6 +23,10 @@ namespace Emby.Dlna.Service
Logger = logger; Logger = logger;
} }
protected IServerConfigurationManager Config { get; }
protected ILogger Logger { get; }
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request) public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{ {
try try
@ -80,10 +80,10 @@ namespace Emby.Dlna.Service
{ {
writer.WriteStartDocument(true); writer.WriteStartDocument(true);
writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV); writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/"); 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); writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
WriteResult(requestInfo.LocalName, requestInfo.Headers, writer); 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); protected abstract void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter);
private void LogRequest(ControlRequest request) 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); 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);
}
} }
} }

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

@ -10,7 +10,7 @@ namespace Emby.Dlna.Service
{ {
public static class ControlErrorHandler 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) public static ControlResponse GetResponse(Exception ex)
{ {
@ -26,11 +26,11 @@ namespace Emby.Dlna.Service
{ {
writer.WriteStartDocument(true); writer.WriteStartDocument(true);
writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV); writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/"); 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("SOAP-ENV", "Fault", NS_SOAPENV); writer.WriteStartElement("SOAP-ENV", "Fault", NsSoapEnv);
writer.WriteElementString("faultcode", "500"); writer.WriteElementString("faultcode", "500");
writer.WriteElementString("faultstring", ex.Message); writer.WriteElementString("faultstring", ex.Message);

@ -87,7 +87,7 @@ namespace Emby.Dlna.Service
.Append(SecurityElement.Escape(item.DataType ?? string.Empty)) .Append(SecurityElement.Escape(item.DataType ?? string.Empty))
.Append("</dataType>"); .Append("</dataType>");
if (item.AllowedValues.Length > 0) if (item.AllowedValues.Count > 0)
{ {
builder.Append("<allowedValueList>"); builder.Append("<allowedValueList>");
foreach (var allowedValue in item.AllowedValues) foreach (var allowedValue in item.AllowedValues)

@ -17,9 +17,17 @@ namespace Emby.Dlna.Ssdp
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private SsdpDeviceLocator _deviceLocator;
private ISsdpCommunicationsServer _commsServer;
private int _listenerCount; private int _listenerCount;
private bool _disposed; private bool _disposed;
public DeviceDiscovery(IServerConfigurationManager config)
{
_config = config;
}
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal; private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
/// <inheritdoc /> /// <inheritdoc />
@ -49,15 +57,6 @@ namespace Emby.Dlna.Ssdp
/// <inheritdoc /> /// <inheritdoc />
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft; 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. // Call this method from somewhere in your code to start the search.
public void Start(ISsdpCommunicationsServer communicationsServer) public void Start(ISsdpCommunicationsServer communicationsServer)
{ {

@ -5,7 +5,7 @@ using System.Xml.Linq;
namespace Emby.Dlna.Ssdp namespace Emby.Dlna.Ssdp
{ {
public static class Extensions public static class SsdpExtensions
{ {
public static string GetValue(this XElement container, XName name) public static string GetValue(this XElement container, XName name)
{ {

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

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

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

@ -41,7 +41,7 @@
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />
<PackageReference Include="sharpcompress" Version="0.26.0" /> <PackageReference Include="sharpcompress" Version="0.26.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.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>
<ItemGroup> <ItemGroup>

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

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

@ -729,7 +729,7 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(rootFolderPath); Directory.CreateDirectory(rootFolderPath);
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))) ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
.DeepCopy<Folder, AggregateFolder>(); .DeepCopy<Folder, AggregateFolder>();
// In case program data folder was moved // In case program data folder was moved
@ -771,7 +771,7 @@ namespace Emby.Server.Implementations.Library
if (folder.ParentId != rootFolder.Id) if (folder.ParentId != rootFolder.Id)
{ {
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); rootFolder.AddVirtualChild(folder);
@ -1868,7 +1868,8 @@ namespace Emby.Server.Implementations.Library
return image.Path != null && !image.IsLocalFile; 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) if (item == null)
{ {
@ -1891,7 +1892,7 @@ namespace Emby.Server.Implementations.Library
try try
{ {
var index = item.GetImageIndex(img); var index = item.GetImageIndex(img);
image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult(); image = await ConvertImageToLocal(item, img, index).ConfigureAwait(false);
} }
catch (ArgumentException) catch (ArgumentException)
{ {
@ -1913,7 +1914,7 @@ namespace Emby.Server.Implementations.Library
} }
catch (Exception ex) 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.Width = 0;
image.Height = 0; image.Height = 0;
continue; continue;
@ -1943,10 +1944,8 @@ namespace Emby.Server.Implementations.Library
RegisterItem(item); RegisterItem(item);
} }
/// <summary> /// <inheritdoc />
/// Updates the item. public async Task UpdateItemsAsync(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
/// </summary>
public void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{ {
foreach (var item in items) foreach (var item in items)
{ {
@ -1957,7 +1956,7 @@ namespace Emby.Server.Implementations.Library
item.DateLastSaved = DateTime.UtcNow; item.DateLastSaved = DateTime.UtcNow;
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate); await UpdateImagesAsync(item, updateReason >= ItemUpdateType.ImageUpdate).ConfigureAwait(false);
} }
_itemRepository.SaveItems(items, cancellationToken); _itemRepository.SaveItems(items, cancellationToken);
@ -1991,17 +1990,9 @@ namespace Emby.Server.Implementations.Library
} }
} }
/// <summary> /// <inheritdoc />
/// Updates the item. public Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
/// </summary> => UpdateItemsAsync(new[] { item }, parent, updateReason, cancellationToken);
/// <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);
}
/// <summary> /// <summary>
/// Reports the item removed. /// Reports the item removed.
@ -2233,7 +2224,7 @@ namespace Emby.Server.Implementations.Library
if (refresh) 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); 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)) if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
{ {
item.ViewType = viewType; 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; 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); 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); 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 // Remove this image to prevent it from retrying over and over
item.RemoveImage(image); item.RemoveImage(image);
item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); await item.UpdateToRepositoryAsync(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
throw new InvalidOperationException(); throw new InvalidOperationException();
} }

@ -230,7 +230,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (filters.Count > 0) 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; return output;

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

@ -929,7 +929,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private static string NormalizeName(string value) 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 public class ScheduleDirect

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

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

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

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

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

@ -152,10 +152,10 @@ namespace Emby.Server.Implementations.Playlists
if (options.ItemIdList.Length > 0) 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 EnableImages = true
}); }).ConfigureAwait(false);
} }
return new PlaylistCreationResult(playlist.Id.ToString("N", CultureInfo.InvariantCulture)); 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); 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); 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 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 // Retrieve the existing playlist
var playlist = _libraryManager.GetItemById(playlistId) as Playlist var playlist = _libraryManager.GetItemById(playlistId) as Playlist
@ -238,7 +238,7 @@ namespace Emby.Server.Implementations.Playlists
// Update the playlist in the repository // Update the playlist in the repository
playlist.LinkedChildren = newLinkedChildren; playlist.LinkedChildren = newLinkedChildren;
playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
// Update the playlist on disk // Update the playlist on disk
if (playlist.IsFile) if (playlist.IsFile)
@ -256,7 +256,7 @@ namespace Emby.Server.Implementations.Playlists
RefreshPriority.High); 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)) if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
{ {
@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.Playlists
.Select(i => i.Item1) .Select(i => i.Item1)
.ToArray(); .ToArray();
playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
if (playlist.IsFile) if (playlist.IsFile)
{ {
@ -289,7 +289,7 @@ namespace Emby.Server.Implementations.Playlists
RefreshPriority.High); 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)) if (!(_libraryManager.GetItemById(playlistId) is Playlist playlist))
{ {
@ -322,7 +322,7 @@ namespace Emby.Server.Implementations.Playlists
playlist.LinkedChildren = newList.ToArray(); playlist.LinkedChildren = newList.ToArray();
playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
if (playlist.IsFile) if (playlist.IsFile)
{ {

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

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

@ -1,12 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{ {
@ -15,12 +16,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
/// </summary> /// </summary>
public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
{ {
/// <summary> private readonly IConfigurationManager _configurationManager;
/// Gets or sets the configuration manager.
/// </summary>
/// <value>The configuration manager.</value>
private IConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
@ -32,18 +28,43 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
/// <param name="localization">The localization manager.</param> /// <param name="localization">The localization manager.</param>
public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization) public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization)
{ {
ConfigurationManager = configurationManager; _configurationManager = configurationManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_localization = localization; _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> /// <summary>
/// Creates the triggers that define when the task will run. /// Creates the triggers that define when the task will run.
/// </summary> /// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns> /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{ {
return new[] { return new[]
{
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} 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) public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{ {
// Delete log files more than n days old // 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 // 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) .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
.ToList(); .ToList();
@ -83,26 +104,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
return Task.CompletedTask; 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;
} }
} }

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

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

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

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

@ -221,9 +221,8 @@ namespace Jellyfin.Api.Controllers
private Task<ControlResponse> ProcessControlRequestInternalAsync(string id, Stream requestStream, IUpnpService service) 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, InputXml = requestStream,
TargetServerUuId = id, TargetServerUuId = id,
RequestedUrl = GetAbsoluteUri() RequestedUrl = GetAbsoluteUri()

@ -1356,7 +1356,7 @@ namespace Jellyfin.Api.Controllers
return string.Format( return string.Format(
CultureInfo.InvariantCulture, 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, inputModifier,
_encodingHelper.GetInputArgument(state, encodingOptions), _encodingHelper.GetInputArgument(state, encodingOptions),
threads, threads,

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

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

@ -269,9 +269,9 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("LiveStreams/Close")] [HttpPost("LiveStreams/Close")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [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(); return NoContent();
} }

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

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

@ -153,7 +153,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemIds">The ids of the items to play, comma delimited.</param> /// <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="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="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> /// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Playing")] [HttpPost("Sessions/{sessionId}/Playing")]
@ -163,17 +162,14 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string? sessionId, [FromRoute, Required] string? sessionId,
[FromQuery] Guid[] itemIds, [FromQuery] Guid[] itemIds,
[FromQuery] long? startPositionTicks, [FromQuery] long? startPositionTicks,
[FromQuery] PlayCommand playCommand, [FromQuery] PlayCommand playCommand)
[FromBody, Required] PlayRequest playRequest)
{ {
if (playRequest == null) var playRequest = new PlayRequest
{ {
throw new ArgumentException("Request Body may not be null"); ItemIds = itemIds,
} StartPositionTicks = startPositionTicks,
PlayCommand = playCommand
playRequest.ItemIds = itemIds; };
playRequest.StartPositionTicks = startPositionTicks;
playRequest.PlayCommand = playCommand;
_sessionManager.SendPlayCommand( _sessionManager.SendPlayCommand(
RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id,

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

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

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

@ -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; }
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save