Merge remote-tracking branch 'upstream/master' into 3.1.7

pull/3886/head
crobibero 4 years ago
commit 9626101c9f

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

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

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

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

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

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

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

@ -40,6 +40,11 @@ namespace Emby.Dlna.ContentDirectory
{
public class ControlHandler : BaseControlHandler
{
private const string NsDc = "http://purl.org/dc/elements/1.1/";
private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private readonly ILibraryManager _libraryManager;
private readonly IUserDataManager _userDataManager;
private readonly IServerConfigurationManager _config;
@ -47,11 +52,6 @@ namespace Emby.Dlna.ContentDirectory
private readonly IUserViewManager _userViewManager;
private readonly ITVSeriesManager _tvSeriesManager;
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private readonly int _systemUpdateId;
private readonly DidlBuilder _didlBuilder;
@ -181,7 +181,11 @@ namespace Emby.Dlna.ContentDirectory
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
_userDataManager.SaveUserData(_user, item, userdata, UserDataSaveReason.TogglePlayed,
_userDataManager.SaveUserData(
_user,
item,
userdata,
UserDataSaveReason.TogglePlayed,
CancellationToken.None);
}
@ -253,7 +257,7 @@ namespace Emby.Dlna.ContentDirectory
var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"];
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
var provided = 0;
@ -286,18 +290,17 @@ namespace Emby.Dlna.ContentDirectory
using (var writer = XmlWriter.Create(builder, settings))
{
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
writer.WriteAttributeString("xmlns", "dc", null, NsDc);
writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
var serverItem = GetItemFromObjectId(id);
var item = serverItem.Item;
if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
{
totalCount = 1;
@ -362,8 +365,8 @@ namespace Emby.Dlna.ContentDirectory
private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
{
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", ""));
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", string.Empty));
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
// sort example: dc:title, dc:date
@ -397,11 +400,11 @@ namespace Emby.Dlna.ContentDirectory
using (var writer = XmlWriter.Create(builder, settings))
{
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
writer.WriteAttributeString("xmlns", "dc", null, NsDc);
writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
@ -783,11 +786,14 @@ namespace Emby.Dlna.ContentDirectory
})
.ToArray();
return ApplyPaging(new QueryResult<ServerItem>
{
Items = folders,
TotalRecordCount = folders.Length
}, startIndex, limit);
return ApplyPaging(
new QueryResult<ServerItem>
{
Items = folders,
TotalRecordCount = folders.Length
},
startIndex,
limit);
}
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
@ -1135,14 +1141,16 @@ namespace Emby.Dlna.ContentDirectory
{
query.OrderBy = Array.Empty<(string, SortOrder)>();
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
{
UserId = user.Id,
Limit = 50,
IncludeItemTypes = new[] { nameof(Audio) },
ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
var items = _userViewManager.GetLatestItems(
new LatestItemsQuery
{
UserId = user.Id,
Limit = 50,
IncludeItemTypes = new[] { nameof(Audio) },
ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true
},
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
}
@ -1151,12 +1159,15 @@ namespace Emby.Dlna.ContentDirectory
{
query.OrderBy = Array.Empty<(string, SortOrder)>();
var result = _tvSeriesManager.GetNextUp(new NextUpQuery
{
Limit = query.Limit,
StartIndex = query.StartIndex,
UserId = query.User.Id
}, new[] { parent }, query.DtoOptions);
var result = _tvSeriesManager.GetNextUp(
new NextUpQuery
{
Limit = query.Limit,
StartIndex = query.StartIndex,
UserId = query.User.Id
},
new[] { parent },
query.DtoOptions);
return ToResult(result);
}
@ -1165,14 +1176,16 @@ namespace Emby.Dlna.ContentDirectory
{
query.OrderBy = Array.Empty<(string, SortOrder)>();
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
{
UserId = user.Id,
Limit = 50,
IncludeItemTypes = new[] { typeof(Episode).Name },
ParentId = parent == null ? Guid.Empty : parent.Id,
GroupItems = false
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
var items = _userViewManager.GetLatestItems(
new LatestItemsQuery
{
UserId = user.Id,
Limit = 50,
IncludeItemTypes = new[] { typeof(Episode).Name },
ParentId = parent == null ? Guid.Empty : parent.Id,
GroupItems = false
},
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
}
@ -1183,13 +1196,14 @@ namespace Emby.Dlna.ContentDirectory
var items = _userViewManager.GetLatestItems(
new LatestItemsQuery
{
UserId = user.Id,
Limit = 50,
IncludeItemTypes = new[] { nameof(Movie) },
ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
{
UserId = user.Id,
Limit = 50,
IncludeItemTypes = new[] { nameof(Movie) },
ParentId = parent?.Id ?? Guid.Empty,
GroupItems = true
},
query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
}
@ -1354,44 +1368,4 @@ namespace Emby.Dlna.ContentDirectory
return new ServerItem(_libraryManager.GetUserRootFolder());
}
}
internal class ServerItem
{
public BaseItem Item { get; set; }
public StubType? StubType { get; set; }
public ServerItem(BaseItem item)
{
Item = item;
if (item is IItemByName && !(item is Folder))
{
StubType = Dlna.ContentDirectory.StubType.Folder;
}
}
}
public enum StubType
{
Folder = 0,
Latest = 2,
Playlists = 3,
Albums = 4,
AlbumArtists = 5,
Artists = 6,
Songs = 7,
Genres = 8,
FavoriteSongs = 9,
FavoriteArtists = 10,
FavoriteAlbums = 11,
ContinueWatching = 12,
Movies = 13,
Collections = 14,
Favorites = 15,
NextUp = 16,
Series = 17,
FavoriteSeries = 18,
FavoriteEpisodes = 19
}
}

@ -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 IHeaderDictionary Headers { get; set; }
public ControlRequest(IHeaderDictionary headers)
{
Headers = headers;
}
public IHeaderDictionary Headers { get; }
public Stream InputXml { get; set; }
public string TargetServerUuId { get; set; }
public string RequestedUrl { get; set; }
public ControlRequest()
{
Headers = new HeaderDictionary();
}
}
}

@ -11,10 +11,16 @@ namespace Emby.Dlna
Headers = new Dictionary<string, string>();
}
public IDictionary<string, string> Headers { get; set; }
public IDictionary<string, string> Headers { get; }
public string Xml { get; set; }
public bool IsSuccessful { get; set; }
/// <inheritdoc />
public override string ToString()
{
return Xml;
}
}
}

@ -34,12 +34,12 @@ namespace Emby.Dlna.Didl
{
public class DidlBuilder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NsDc = "http://purl.org/dc/elements/1.1/";
private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly DeviceProfile _profile;
private readonly IImageProcessor _imageProcessor;
@ -100,11 +100,11 @@ namespace Emby.Dlna.Didl
{
// writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
writer.WriteAttributeString("xmlns", "dc", null, NsDc);
writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
// didl.SetAttribute("xmlns:sec", NS_SEC);
WriteXmlRootAttributes(_profile, writer);
@ -147,7 +147,7 @@ namespace Emby.Dlna.Didl
{
var clientId = GetClientId(item, null);
writer.WriteStartElement(string.Empty, "item", NS_DIDL);
writer.WriteStartElement(string.Empty, "item", NsDidl);
writer.WriteAttributeString("restricted", "1");
writer.WriteAttributeString("id", clientId);
@ -207,7 +207,8 @@ namespace Emby.Dlna.Didl
var targetWidth = streamInfo.TargetWidth;
var targetHeight = streamInfo.TargetHeight;
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(
streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetWidth,
@ -279,7 +280,7 @@ namespace Emby.Dlna.Didl
}
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
writer.WriteStartElement(string.Empty, "res", NsDidl);
writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
@ -288,7 +289,7 @@ namespace Emby.Dlna.Didl
}
else
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
writer.WriteStartElement(string.Empty, "res", NsDidl);
var protocolInfo = string.Format(
CultureInfo.InvariantCulture,
"http-get:*:text/{0}:*",
@ -304,7 +305,7 @@ namespace Emby.Dlna.Didl
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
writer.WriteStartElement(string.Empty, "res", NsDidl);
var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken));
@ -526,7 +527,7 @@ namespace Emby.Dlna.Didl
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
writer.WriteStartElement(string.Empty, "res", NsDidl);
if (streamInfo == null)
{
@ -583,7 +584,8 @@ namespace Emby.Dlna.Didl
writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
var mediaProfile = _profile.GetAudioMediaProfile(
streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetChannels,
targetAudioBitrate,
@ -596,7 +598,8 @@ namespace Emby.Dlna.Didl
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(
streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetAudioBitrate,
targetSampleRate,
@ -627,7 +630,7 @@ namespace Emby.Dlna.Didl
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
{
writer.WriteStartElement(string.Empty, "container", NS_DIDL);
writer.WriteStartElement(string.Empty, "container", NsDidl);
writer.WriteAttributeString("restricted", "1");
writer.WriteAttributeString("searchable", "1");
@ -714,7 +717,7 @@ namespace Emby.Dlna.Didl
// MediaMonkey for example won't display content without a title
// if (filter.Contains("dc:title"))
{
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NsDc);
}
WriteObjectClass(writer, item, itemStubType);
@ -723,7 +726,7 @@ namespace Emby.Dlna.Didl
{
if (item.PremiereDate.HasValue)
{
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NS_DC);
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NsDc);
}
}
@ -731,13 +734,13 @@ namespace Emby.Dlna.Didl
{
foreach (var genre in item.Genres)
{
AddValue(writer, "upnp", "genre", genre, NS_UPNP);
AddValue(writer, "upnp", "genre", genre, NsUpnp);
}
}
foreach (var studio in item.Studios)
{
AddValue(writer, "upnp", "publisher", studio, NS_UPNP);
AddValue(writer, "upnp", "publisher", studio, NsUpnp);
}
if (!(item is Folder))
@ -748,28 +751,29 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(desc))
{
AddValue(writer, "dc", "description", desc, NS_DC);
AddValue(writer, "dc", "description", desc, NsDc);
}
}
// if (filter.Contains("upnp:longDescription"))
//{
// {
// if (!string.IsNullOrWhiteSpace(item.Overview))
// {
// AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP);
// AddValue(writer, "upnp", "longDescription", item.Overview, NsUpnp);
// }
//}
// }
}
if (!string.IsNullOrEmpty(item.OfficialRating))
{
if (filter.Contains("dc:rating"))
{
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
AddValue(writer, "dc", "rating", item.OfficialRating, NsDc);
}
if (filter.Contains("upnp:rating"))
{
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
AddValue(writer, "upnp", "rating", item.OfficialRating, NsUpnp);
}
}
@ -781,7 +785,7 @@ namespace Emby.Dlna.Didl
// More types here
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
writer.WriteStartElement("upnp", "class", NS_UPNP);
writer.WriteStartElement("upnp", "class", NsUpnp);
if (item.IsDisplayedAsFolder || stubType.HasValue)
{
@ -882,7 +886,7 @@ namespace Emby.Dlna.Didl
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
?? PersonType.Actor;
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NsUpnp);
}
}
@ -896,8 +900,8 @@ namespace Emby.Dlna.Didl
{
foreach (var artist in hasArtists.Artists)
{
AddValue(writer, "upnp", "artist", artist, NS_UPNP);
AddValue(writer, "dc", "creator", artist, NS_DC);
AddValue(writer, "upnp", "artist", artist, NsUpnp);
AddValue(writer, "dc", "creator", artist, NsDc);
// If it doesn't support album artists (musicvideo), then tag as both
if (hasAlbumArtists == null)
@ -917,16 +921,16 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(item.Album))
{
AddValue(writer, "upnp", "album", item.Album, NS_UPNP);
AddValue(writer, "upnp", "album", item.Album, NsUpnp);
}
if (item.IndexNumber.HasValue)
{
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp);
if (item is Episode)
{
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp);
}
}
}
@ -935,7 +939,7 @@ namespace Emby.Dlna.Didl
{
try
{
writer.WriteStartElement("upnp", "artist", NS_UPNP);
writer.WriteStartElement("upnp", "artist", NsUpnp);
writer.WriteAttributeString("role", "AlbumArtist");
writer.WriteString(name);
@ -971,14 +975,14 @@ namespace Emby.Dlna.Didl
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.Url);
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.url);
writer.WriteFullEndElement();
// TOOD: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url);
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
if (!_profile.EnableAlbumArtInDidl)
{
@ -1021,12 +1025,12 @@ namespace Emby.Dlna.Didl
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
writer.WriteStartElement(string.Empty, "res", NsDidl);
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
// rather than using a larger one when available
var width = albumartUrlInfo.Width ?? maxWidth;
var height = albumartUrlInfo.Height ?? maxHeight;
var width = albumartUrlInfo.width ?? maxWidth;
var height = albumartUrlInfo.height ?? maxHeight;
var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
@ -1043,7 +1047,7 @@ namespace Emby.Dlna.Didl
"resolution",
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
writer.WriteString(albumartUrlInfo.Url);
writer.WriteString(albumartUrlInfo.url);
writer.WriteFullEndElement();
}
@ -1139,7 +1143,6 @@ namespace Emby.Dlna.Didl
if (width == 0 || height == 0)
{
// _imageProcessor.GetImageSize(item, imageInfo);
width = null;
height = null;
}
@ -1149,18 +1152,6 @@ namespace Emby.Dlna.Didl
height = null;
}
// try
//{
// var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width;
// height = size.Height;
//}
// catch
//{
//}
var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty)
.TrimStart('.')
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
@ -1177,30 +1168,6 @@ namespace Emby.Dlna.Didl
};
}
private class ImageDownloadInfo
{
internal Guid ItemId;
internal string ImageTag;
internal ImageType Type;
internal int? Width;
internal int? Height;
internal bool IsDirectStream;
internal string Format;
internal ItemImageInfo ItemImageInfo;
}
private class ImageUrlInfo
{
internal string Url;
internal int? Width;
internal int? Height;
}
public static string GetClientId(BaseItem item, StubType? stubType)
{
return GetClientId(item.Id, stubType);
@ -1218,7 +1185,7 @@ namespace Emby.Dlna.Didl
return id;
}
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
private (string url, int? width, int? height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
{
var url = string.Format(
CultureInfo.InvariantCulture,
@ -1256,12 +1223,26 @@ namespace Emby.Dlna.Didl
// just lie
info.IsDirectStream = true;
return new ImageUrlInfo
{
Url = url,
Width = width,
Height = height
};
return (url, width, height);
}
private class ImageDownloadInfo
{
internal Guid ItemId { get; set; }
internal string ImageTag { get; set; }
internal ImageType Type { get; set; }
internal int? Width { get; set; }
internal int? Height { get; set; }
internal bool IsDirectStream { get; set; }
internal string Format { get; set; }
internal ItemImageInfo ItemImageInfo { get; set; }
}
}
}

@ -23,9 +23,7 @@ namespace Emby.Dlna.Didl
public bool Contains(string field)
{
// Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back.
return true;
// return _all || ListHelper.ContainsIgnoreCase(_fields, field);
return _all || Array.Exists(_fields, x => x.Equals(field, StringComparison.OrdinalIgnoreCase));
}
}
}

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

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

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

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

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

@ -8,16 +8,26 @@ namespace Emby.Dlna
/// Cancels the event subscription.
/// </summary>
/// <param name="subscriptionId">The subscription identifier.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse CancelEventSubscription(string subscriptionId);
/// <summary>
/// Renews the event subscription.
/// </summary>
/// <param name="subscriptionId">The subscription identifier.</param>
/// <param name="notificationType">The notification type.</param>
/// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
/// <param name="callbackUrl">The callback url.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl);
/// <summary>
/// Creates the event subscription.
/// </summary>
/// <param name="notificationType">The notification type.</param>
/// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
/// <param name="callbackUrl">The callback url.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
}
}

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

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

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

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

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

@ -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 Device _device;
private readonly SessionInfo _session;
private readonly ISessionManager _sessionManager;
private readonly ILibraryManager _libraryManager;
@ -50,6 +49,7 @@ namespace Emby.Dlna.PlayTo
private readonly string _accessToken;
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
private Device _device;
private int _currentPlaylistIndex;
private bool _disposed;
@ -372,8 +372,13 @@ namespace Emby.Dlna.PlayTo
if (!command.ControllingUserId.Equals(Guid.Empty))
{
_sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
_session.DeviceName, _session.RemoteEndPoint, user);
_sessionManager.LogSessionActivity(
_session.Client,
_session.ApplicationVersion,
_session.DeviceId,
_session.DeviceName,
_session.RemoteEndPoint,
user);
}
return PlayItems(playlist, cancellationToken);
@ -498,42 +503,44 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.MediaType == DlnaProfileType.Audio)
{
return new ContentFeatureBuilder(profile)
.BuildAudioHeader(streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetAudioBitrate,
streamInfo.TargetAudioSampleRate,
streamInfo.TargetAudioChannels,
streamInfo.TargetAudioBitDepth,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo);
.BuildAudioHeader(
streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetAudioBitrate,
streamInfo.TargetAudioSampleRate,
streamInfo.TargetAudioChannels,
streamInfo.TargetAudioBitDepth,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo);
}
if (streamInfo.MediaType == DlnaProfileType.Video)
{
var list = new ContentFeatureBuilder(profile)
.BuildVideoHeader(streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetWidth,
streamInfo.TargetHeight,
streamInfo.TargetVideoBitDepth,
streamInfo.TargetVideoBitrate,
streamInfo.TargetTimestamp,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
streamInfo.TranscodeSeekInfo,
streamInfo.IsTargetAnamorphic,
streamInfo.IsTargetInterlaced,
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount,
streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC);
.BuildVideoHeader(
streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetWidth,
streamInfo.TargetHeight,
streamInfo.TargetVideoBitDepth,
streamInfo.TargetVideoBitrate,
streamInfo.TargetTimestamp,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
streamInfo.TranscodeSeekInfo,
streamInfo.IsTargetAnamorphic,
streamInfo.IsTargetInterlaced,
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount,
streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC);
return list.Count == 0 ? null : list[0];
}
@ -633,6 +640,10 @@ namespace Emby.Dlna.PlayTo
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and optionally managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
@ -673,48 +684,41 @@ namespace Emby.Dlna.PlayTo
case GeneralCommandType.ToggleMute:
return _device.ToggleMute(cancellationToken);
case GeneralCommandType.SetAudioStreamIndex:
if (command.Arguments.TryGetValue("Index", out string index))
{
if (command.Arguments.TryGetValue("Index", out string arg))
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
{
if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var val))
{
return SetAudioStreamIndex(val);
}
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
return SetAudioStreamIndex(val);
}
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
}
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
case GeneralCommandType.SetSubtitleStreamIndex:
if (command.Arguments.TryGetValue("Index", out index))
{
if (command.Arguments.TryGetValue("Index", out string arg))
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
{
if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var val))
{
return SetSubtitleStreamIndex(val);
}
throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
return SetSubtitleStreamIndex(val);
}
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
}
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
case GeneralCommandType.SetVolume:
if (command.Arguments.TryGetValue("Volume", out string vol))
{
if (command.Arguments.TryGetValue("Volume", out string arg))
if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
{
if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var volume))
{
return _device.SetVolume(volume, cancellationToken);
}
throw new ArgumentException("Unsupported volume value supplied.");
return _device.SetVolume(volume, cancellationToken);
}
throw new ArgumentException("Volume argument cannot be null");
throw new ArgumentException("Unsupported volume value supplied.");
}
throw new ArgumentException("Volume argument cannot be null");
default:
return Task.CompletedTask;
}
@ -778,7 +782,7 @@ namespace Emby.Dlna.PlayTo
const int maxWait = 15000000;
const int interval = 500;
var currentWait = 0;
while (_device.TransportState != TRANSPORTSTATE.PLAYING && currentWait < maxWait)
while (_device.TransportState != TransportState.Playing && currentWait < maxWait)
{
await Task.Delay(interval).ConfigureAwait(false);
currentWait += interval;
@ -787,8 +791,67 @@ namespace Emby.Dlna.PlayTo
await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false);
}
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
{
var value = values.GetValueOrDefault(name);
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
return result;
}
return null;
}
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
{
var value = values.GetValueOrDefault(name);
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
return result;
}
return 0;
}
/// <inheritdoc />
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
if (_device == null)
{
return Task.CompletedTask;
}
if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
{
return SendPlayCommand(data as PlayRequest, cancellationToken);
}
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
{
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
}
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
{
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
}
// Not supported or needed right now
return Task.CompletedTask;
}
private class StreamParams
{
private MediaSourceInfo mediaSource;
private IMediaSourceManager _mediaSourceManager;
public Guid ItemId { get; set; }
public bool IsDirectStream { get; set; }
@ -809,15 +872,11 @@ namespace Emby.Dlna.PlayTo
public BaseItem Item { get; set; }
private MediaSourceInfo MediaSource;
private IMediaSourceManager _mediaSourceManager;
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
{
if (MediaSource != null)
if (mediaSource != null)
{
return MediaSource;
return mediaSource;
}
var hasMediaSources = Item as IHasMediaSources;
@ -827,9 +886,9 @@ namespace Emby.Dlna.PlayTo
return null;
}
MediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
return MediaSource;
return mediaSource;
}
private static Guid GetItemId(string url)
@ -901,61 +960,5 @@ namespace Emby.Dlna.PlayTo
return request;
}
}
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
{
var value = values.GetValueOrDefault(name);
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
return result;
}
return null;
}
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
{
var value = values.GetValueOrDefault(name);
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
{
return result;
}
return 0;
}
/// <inheritdoc />
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
if (_device == null)
{
return Task.CompletedTask;
}
if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
{
return SendPlayCommand(data as PlayRequest, cancellationToken);
}
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
{
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
}
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
{
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
}
// Not supported or needed right now
return Task.CompletedTask;
}
}
}

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

@ -6,6 +6,6 @@ namespace Emby.Dlna.PlayTo
{
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 uBaseObject MediaInfo { get; set; }
public UBaseObject MediaInfo { get; set; }
}
}

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

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

@ -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
{
public class UpnpContainer : uBaseObject
public class UpnpContainer : UBaseObject
{
public static uBaseObject Create(XElement container)
public static UBaseObject Create(XElement container)
{
if (container == null)
{
throw new ArgumentNullException(nameof(container));
}
return new uBaseObject
return new UBaseObject
{
Id = container.GetAttributeValue(uPnpNamespaces.Id),
ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
Title = container.GetValue(uPnpNamespaces.title),
IconUrl = container.GetValue(uPnpNamespaces.Artwork),
UpnpClass = container.GetValue(uPnpNamespaces.uClass)
Id = container.GetAttributeValue(UPnpNamespaces.Id),
ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId),
Title = container.GetValue(UPnpNamespaces.Title),
IconUrl = container.GetValue(UPnpNamespaces.Artwork),
UpnpClass = container.GetValue(UPnpNamespaces.Class)
};
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -102,13 +102,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -128,13 +128,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg",
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -148,28 +148,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="mpeg2video",
VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video",
VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video
}
};
@ -180,7 +180,7 @@ namespace Emby.Dlna.Profiles
{
Type = DlnaProfileType.Photo,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -204,7 +204,7 @@ namespace Emby.Dlna.Profiles
{
Type = CodecType.Video,
Codec = "h264",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -243,7 +243,7 @@ namespace Emby.Dlna.Profiles
{
Type = CodecType.Video,
Codec = "mpeg2video",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -275,7 +275,7 @@ namespace Emby.Dlna.Profiles
new CodecProfile
{
Type = CodecType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -303,7 +303,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio,
Codec = "ac3",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -319,7 +319,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio,
Codec = "aac",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -341,7 +341,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio,
Codec = "mp3,mp2",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{

@ -120,7 +120,7 @@ namespace Emby.Dlna.Profiles
{
Type = DlnaProfileType.Photo,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -143,13 +143,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -169,13 +169,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg",
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -189,28 +189,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="mpeg2video",
VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video",
VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video
},
new ResponseProfile
@ -227,7 +227,7 @@ namespace Emby.Dlna.Profiles
{
Type = CodecType.Video,
Codec = "h264",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -266,7 +266,7 @@ namespace Emby.Dlna.Profiles
{
Type = CodecType.Video,
Codec = "mpeg2video",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -298,7 +298,7 @@ namespace Emby.Dlna.Profiles
new CodecProfile
{
Type = CodecType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -326,7 +326,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio,
Codec = "ac3",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -364,7 +364,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio,
Codec = "mp3,mp2",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{

@ -131,13 +131,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -157,13 +157,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg",
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -177,28 +177,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="mpeg2video",
VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video",
VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video
},
new ResponseProfile
@ -215,7 +215,7 @@ namespace Emby.Dlna.Profiles
{
Type = DlnaProfileType.Photo,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -282,7 +282,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio,
Codec = "mp3,mp2",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{

@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
{
Type = DlnaProfileType.Photo,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -187,13 +187,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -213,13 +213,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg",
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -233,28 +233,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="mpeg2video",
VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video",
VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video
},
new ResponseProfile
@ -265,14 +265,13 @@ namespace Emby.Dlna.Profiles
}
};
CodecProfiles = new[]
{
new CodecProfile
{
Type = CodecType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -300,7 +299,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio,
Codec = "mp3,mp2",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{

@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
{
Type = DlnaProfileType.Photo,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -187,13 +187,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -213,13 +213,13 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/mpeg",
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
Type = DlnaProfileType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -233,28 +233,28 @@ namespace Emby.Dlna.Profiles
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="h264",
AudioCodec="ac3,aac,mp3",
VideoCodec = "h264",
AudioCodec = "ac3,aac,mp3",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "ts,mpegts",
VideoCodec="mpeg2video",
VideoCodec = "mpeg2video",
MimeType = "video/vnd.dlna.mpeg-tts",
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
Type = DlnaProfileType.Video
},
new ResponseProfile
{
Container = "mpeg",
VideoCodec="mpeg1video,mpeg2video",
VideoCodec = "mpeg1video,mpeg2video",
MimeType = "video/mpeg",
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
Type = DlnaProfileType.Video
},
new ResponseProfile
@ -265,14 +265,13 @@ namespace Emby.Dlna.Profiles
}
};
CodecProfiles = new[]
{
new CodecProfile
{
Type = CodecType.Video,
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{
@ -300,7 +299,7 @@ namespace Emby.Dlna.Profiles
Type = CodecType.VideoAudio,
Codec = "mp3,mp2",
Conditions = new []
Conditions = new[]
{
new ProfileCondition
{

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

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

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

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

@ -15,11 +15,7 @@ namespace Emby.Dlna.Service
{
public abstract class BaseControlHandler
{
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
protected IServerConfigurationManager Config { get; }
protected ILogger Logger { get; }
private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
{
@ -27,6 +23,10 @@ namespace Emby.Dlna.Service
Logger = logger;
}
protected IServerConfigurationManager Config { get; }
protected ILogger Logger { get; }
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
try
@ -80,10 +80,10 @@ namespace Emby.Dlna.Service
{
writer.WriteStartDocument(true);
writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV);
writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV);
writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
@ -210,15 +210,6 @@ namespace Emby.Dlna.Service
}
}
private class ControlRequestInfo
{
public string LocalName { get; set; }
public string NamespaceURI { get; set; }
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
protected abstract void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter);
private void LogRequest(ControlRequest request)
@ -240,5 +231,14 @@ namespace Emby.Dlna.Service
Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
}
private class ControlRequestInfo
{
public string LocalName { get; set; }
public string NamespaceURI { get; set; }
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
}

@ -8,10 +8,6 @@ namespace Emby.Dlna.Service
{
public class BaseService : IEventManager
{
protected IEventManager EventManager;
protected IHttpClient HttpClient;
protected ILogger Logger;
protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
{
Logger = logger;
@ -20,6 +16,12 @@ namespace Emby.Dlna.Service
EventManager = new EventManager(logger, HttpClient);
}
protected IEventManager EventManager { get; }
protected IHttpClient HttpClient { get; }
protected ILogger Logger { get; }
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
{
return EventManager.CancelEventSubscription(subscriptionId);

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

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

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

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

@ -1,3 +1,5 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
@ -22,7 +24,7 @@ namespace Emby.Server.Implementations.AppBase
{
object configuration;
byte[] buffer = null;
byte[]? buffer = null;
// Use try/catch to avoid the extra file system lookup using File.Exists
try
@ -36,19 +38,23 @@ namespace Emby.Server.Implementations.AppBase
configuration = Activator.CreateInstance(type);
}
using var stream = new MemoryStream();
using var stream = new MemoryStream(buffer?.Length ?? 0);
xmlSerializer.SerializeToStream(configuration, stream);
// Take the object we just got and serialize it back to bytes
var newBytes = stream.ToArray();
byte[] newBytes = stream.GetBuffer();
int newBytesLen = (int)stream.Length;
// If the file didn't exist before, or if something has changed, re-save
if (buffer == null || !buffer.SequenceEqual(newBytes))
if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
// Save it after load in case we got new items
File.WriteAllBytes(path, newBytes);
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
fs.Write(newBytes, 0, newBytesLen);
}
}
return configuration;

@ -632,6 +632,9 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
serviceCollection.AddSingleton<TranscodingJobHelper>();
serviceCollection.AddScoped<MediaInfoHelper>();
serviceCollection.AddScoped<AudioHelper>();
serviceCollection.AddScoped<DynamicHlsHelper>();
}
/// <summary>

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

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

@ -1,6 +1,5 @@
using System.Collections.Generic;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.Updates;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Emby.Server.Implementations
@ -19,7 +18,8 @@ namespace Emby.Server.Implementations
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
{ PlaylistsAllowDuplicatesKey, bool.TrueString },
{ BindToUnixSocketKey, bool.FalseString }
};
}
}

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

@ -73,25 +73,6 @@ namespace Emby.Server.Implementations.Dto
_livetvManagerFactory = livetvManagerFactory;
}
/// <summary>
/// Converts a BaseItem to a DTOBaseItem.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fields">The fields.</param>
/// <param name="user">The user.</param>
/// <param name="owner">The owner.</param>
/// <returns>Task{DtoBaseItem}.</returns>
/// <exception cref="ArgumentNullException">item</exception>
public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null)
{
var options = new DtoOptions
{
Fields = fields
};
return GetBaseItemDto(item, options, user, owner);
}
/// <inheritdoc />
public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
{
@ -443,17 +424,6 @@ namespace Emby.Server.Implementations.Dto
return folder.GetChildCount(user);
}
/// <summary>
/// Gets client-side Id of a server-side BaseItem.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">item</exception>
public string GetDtoId(BaseItem item)
{
return item.Id.ToString("N", CultureInfo.InvariantCulture);
}
private static void SetBookProperties(BaseItemDto dto, Book item)
{
dto.SeriesName = item.SeriesName;
@ -484,6 +454,11 @@ namespace Emby.Server.Implementations.Dto
}
}
private string GetDtoId(BaseItem item)
{
return item.Id.ToString("N", CultureInfo.InvariantCulture);
}
private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
{
if (!string.IsNullOrEmpty(item.Album))
@ -513,19 +488,6 @@ namespace Emby.Server.Implementations.Dto
.ToArray();
}
private string GetImageCacheTag(BaseItem item, ImageType type)
{
try
{
return _imageProcessor.GetImageCacheTag(item, type);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting {type} image info", type);
return null;
}
}
private string GetImageCacheTag(BaseItem item, ItemImageInfo image)
{
try

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

@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires))
if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out _))
{
responseHeaders[HeaderNames.Expires] = "0";
}
@ -326,7 +326,8 @@ namespace Emby.Server.Implementations.HttpServer
return GetHttpResult(request, ms, contentType, true, responseHeaders);
}
private IHasHeaders GetCompressedResult(byte[] content,
private IHasHeaders GetCompressedResult(
byte[] content,
string requestedCompressionType,
IDictionary<string, string> responseHeaders,
bool isHeadRequest,

@ -95,13 +95,13 @@ namespace Emby.Server.Implementations.HttpServer
if (bytes != null)
{
await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
await responseStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
}
else
{
using (var src = SourceStream)
{
await src.CopyToAsync(responseStream).ConfigureAwait(false);
await src.CopyToAsync(responseStream, cancellationToken).ConfigureAwait(false);
}
}
}

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

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

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

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

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

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

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

@ -19,8 +19,7 @@ namespace Emby.Server.Implementations.LiveTv
public class LiveTvMediaSourceProvider : IMediaSourceProvider
{
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char StreamIdDelimeter = '_';
private const string StreamIdDelimeterString = "_";
private const char StreamIdDelimiter = '_';
private readonly ILiveTvManager _liveTvManager;
private readonly ILogger<LiveTvMediaSourceProvider> _logger;
@ -47,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv
}
}
return Task.FromResult<IEnumerable<MediaSourceInfo>>(Array.Empty<MediaSourceInfo>());
return Task.FromResult(Enumerable.Empty<MediaSourceInfo>());
}
private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
@ -98,7 +97,7 @@ namespace Emby.Server.Implementations.LiveTv
source.Id ?? string.Empty
};
source.OpenToken = string.Join(StreamIdDelimeterString, openKeys);
source.OpenToken = string.Join(StreamIdDelimiter, openKeys);
}
// Dummy this up so that direct play checks can still run
@ -116,7 +115,7 @@ namespace Emby.Server.Implementations.LiveTv
/// <inheritdoc />
public async Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
var keys = openToken.Split(StreamIdDelimiter, 3);
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);

@ -1,10 +1,10 @@
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@ -14,7 +14,7 @@ using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
@ -23,17 +23,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
protected readonly IServerConfigurationManager Config;
protected readonly ILogger<BaseTunerHost> Logger;
protected IJsonSerializer JsonSerializer;
protected readonly IFileSystem FileSystem;
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
private readonly IMemoryCache _memoryCache;
protected BaseTunerHost(IServerConfigurationManager config, ILogger<BaseTunerHost> logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
protected BaseTunerHost(IServerConfigurationManager config, ILogger<BaseTunerHost> logger, IFileSystem fileSystem, IMemoryCache memoryCache)
{
Config = config;
Logger = logger;
JsonSerializer = jsonSerializer;
_memoryCache = memoryCache;
FileSystem = fileSystem;
}
@ -44,23 +42,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken)
{
ChannelCache cache = null;
var key = tuner.Id;
if (enableCache && !string.IsNullOrEmpty(key) && _channelCache.TryGetValue(key, out cache))
if (enableCache && !string.IsNullOrEmpty(key) && _memoryCache.TryGetValue(key, out List<ChannelInfo> cache))
{
return cache.Channels.ToList();
return cache;
}
var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
var list = result.ToList();
var list = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
// logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
if (!string.IsNullOrEmpty(key) && list.Count > 0)
{
cache = cache ?? new ChannelCache();
cache.Channels = list;
_channelCache.AddOrUpdate(key, cache, (k, v) => cache);
_memoryCache.Set(key, list);
}
return list;
@ -95,7 +89,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
try
{
Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
JsonSerializer.SerializeToFile(channels, channelCacheFile);
await using var writeStream = File.OpenWrite(channelCacheFile);
await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false);
}
catch (IOException)
{
@ -110,7 +105,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
try
{
var channels = JsonSerializer.DeserializeFromFile<List<ChannelInfo>>(channelCacheFile);
await using var readStream = File.OpenRead(channelCacheFile);
var channels = await JsonSerializer.DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken)
.ConfigureAwait(false);
list.AddRange(channels);
}
catch (IOException)
@ -233,10 +230,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
return Config.GetConfiguration<LiveTvOptions>("livetv");
}
private class ChannelCache
{
public List<ChannelInfo> Channels;
}
}
}

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@ -23,7 +24,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@ -36,17 +37,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly INetworkManager _networkManager;
private readonly IStreamHelper _streamHelper;
private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
public HdHomerunHost(
IServerConfigurationManager config,
ILogger<HdHomerunHost> logger,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IHttpClient httpClient,
IServerApplicationHost appHost,
ISocketFactory socketFactory,
INetworkManager networkManager,
IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
IStreamHelper streamHelper,
IMemoryCache memoryCache)
: base(config, logger, fileSystem, memoryCache)
{
_httpClient = httpClient;
_appHost = appHost;
@ -75,18 +78,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
BufferContent = false
};
using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
using (var stream = response.Content)
{
var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
if (info.ImportFavoritesOnly)
{
lineup = lineup.Where(i => i.Favorite).ToList();
}
using var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
await using var stream = response.Content;
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false) ?? new List<Channels>();
return lineup.Where(i => !i.DRM).ToList();
if (info.ImportFavoritesOnly)
{
lineup = lineup.Where(i => i.Favorite).ToList();
}
return lineup.Where(i => !i.DRM).ToList();
}
private class HdHomerunChannelInfo : ChannelInfo
@ -114,7 +116,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}).Cast<ChannelInfo>().ToList();
}
private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
private async Task<DiscoverResponse> GetModelInfo(TunerHostInfo info, bool throwAllExceptions, CancellationToken cancellationToken)
{
var cacheKey = info.Id;
@ -132,35 +133,35 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
using var response = await _httpClient.SendAsync(
new HttpRequestOptions
{
Url = string.Format("{0}/discover.json", GetApiUrl(info)),
Url = string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)),
CancellationToken = cancellationToken,
BufferContent = false
}, HttpMethod.Get).ConfigureAwait(false))
using (var stream = response.Content)
{
var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
}, HttpMethod.Get).ConfigureAwait(false);
await using var stream = response.Content;
var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (!string.IsNullOrEmpty(cacheKey))
if (!string.IsNullOrEmpty(cacheKey))
{
lock (_modelCache)
{
lock (_modelCache)
{
_modelCache[cacheKey] = discoverResponse;
}
_modelCache[cacheKey] = discoverResponse;
}
return discoverResponse;
}
return discoverResponse;
}
catch (HttpException ex)
{
if (!throwAllExceptions && ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
if (!throwAllExceptions && ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
{
var defaultValue = "HDHR";
const string DefaultValue = "HDHR";
var response = new DiscoverResponse
{
ModelNumber = defaultValue
ModelNumber = DefaultValue
};
if (!string.IsNullOrEmpty(cacheKey))
{
@ -182,12 +183,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
{
Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
CancellationToken = cancellationToken,
BufferContent = false
}, HttpMethod.Get).ConfigureAwait(false))
using (var response = await _httpClient.SendAsync(
new HttpRequestOptions()
{
Url = string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)),
CancellationToken = cancellationToken,
BufferContent = false
},
HttpMethod.Get).ConfigureAwait(false))
using (var stream = response.Content)
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
{
@ -730,7 +733,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// Need a way to set the Receive timeout on the socket otherwise this might never timeout?
try
{
await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken);
await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken).ConfigureAwait(false);
var receiveBuffer = new byte[8192];
while (!cancellationToken.IsCancellationRequested)

@ -18,7 +18,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
@ -36,13 +36,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
IServerConfigurationManager config,
IMediaSourceManager mediaSourceManager,
ILogger<M3UTunerHost> logger,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IHttpClient httpClient,
IServerApplicationHost appHost,
INetworkManager networkManager,
IStreamHelper streamHelper)
: base(config, logger, jsonSerializer, fileSystem)
IStreamHelper streamHelper,
IMemoryCache memoryCache)
: base(config, logger, fileSystem, memoryCache)
{
_httpClient = httpClient;
_appHost = appHost;

@ -1,12 +1,12 @@
{
"DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
"DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
"Collections": "সংকলন",
"Collections": "কলেক্শন",
"ChapterNameValue": "অধ্যায় {0}",
"Channels": "চ্যানেল",
"CameraImageUploadedFrom": "একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে {0} থেকে",
"CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে",
"Books": "বই",
"AuthenticationSucceededWithUserName": "{0} যাচাই সফল",
"AuthenticationSucceededWithUserName": "{0} অনুমোদন সফল",
"Artists": "শিল্পীরা",
"Application": "অ্যাপ্লিকেশন",
"Albums": "অ্যালবামগুলো",
@ -14,13 +14,13 @@
"HeaderFavoriteArtists": "প্রিয় শিল্পীরা",
"HeaderFavoriteAlbums": "প্রিয় এলবামগুলো",
"HeaderContinueWatching": "দেখতে থাকুন",
"HeaderCameraUploads": "ক্যামেরার আপলোডগুলো",
"HeaderAlbumArtists": "এলবামের শিল্পী",
"Genres": "ঘরানা",
"HeaderCameraUploads": "ক্যামেরার আপলোড সমূহ",
"HeaderAlbumArtists": "এলবাম শিল্পী",
"Genres": "জেনার",
"Folders": "ফোল্ডারগুলো",
"Favorites": "ফেভারিটগুলো",
"Favorites": "পছন্দসমূহ",
"FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে",
"AppDeviceValues": "প: {0}, ডিভাইস: {0}",
"AppDeviceValues": "অ্যাপ: {0}, ডিভাইস: {0}",
"VersionNumber": "সংস্করণ {0}",
"ValueSpecialEpisodeName": "বিশেষ - {0}",
"ValueHasBeenAddedToLibrary": "আপনার লাইব্রেরিতে {0} যোগ করা হয়েছে",
@ -74,20 +74,20 @@
"NameInstallFailed": "{0} ইন্সটল ব্যর্থ",
"MusicVideos": "গানের ভিডিও",
"Music": "গান",
"Movies": "সিনেমা",
"Movies": "চলচ্চিত্র",
"MixedContent": "মিশ্র কন্টেন্ট",
"MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন হালনাগাদ করা হয়েছে",
"HeaderRecordingGroups": "রেকর্ডিং গ্রুপ",
"MessageNamedServerConfigurationUpdatedWithValue": "সার্ভারের {0} কনফিগারেসন অংশ আপডেট করা হয়েছে",
"MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে হালনাগাদ করা হয়েছে",
"MessageApplicationUpdated": "জেলিফিন সার্ভার হালনাগাদ করা হয়েছে",
"Latest": "একদম নতুন",
"MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন আপডেট করা হয়েছে",
"HeaderRecordingGroups": "রেকর্ডিং দল",
"MessageNamedServerConfigurationUpdatedWithValue": "সার্ভারের {0} কনফিগারেসনের অংশ আপডেট করা হয়েছে",
"MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে আপডেট করা হয়েছে",
"MessageApplicationUpdated": "জেলিফিন সার্ভার আপডেট করা হয়েছে",
"Latest": "সর্বশেষ",
"LabelRunningTimeValue": "চলার সময়: {0}",
"LabelIpAddressValue": "আইপি ঠিকানা: {0}",
"LabelIpAddressValue": "আইপি এড্রেস: {0}",
"ItemRemovedWithName": "{0} লাইব্রেরি থেকে বাদ দেয়া হয়েছে",
"ItemAddedWithName": "{0} লাইব্রেরিতে যোগ করা হয়েছে",
"Inherit": "থেকে পাওয়া",
"HomeVideos": "বাসার ভিডিও",
"HomeVideos": "হোম ভিডিও",
"HeaderNextUp": "এরপরে আসছে",
"HeaderLiveTV": "লাইভ টিভি",
"HeaderFavoriteSongs": "প্রিয় গানগুলো",

@ -1,14 +1,14 @@
{
"Albums": "Album",
"AuthenticationSucceededWithUserName": "{0} berhasil diautentikasi",
"AppDeviceValues": "Aplikasi: {0}, Alat: {1}",
"AppDeviceValues": "Aplikasi : {0}, Alat : {1}",
"LabelRunningTimeValue": "Waktu berjalan: {0}",
"MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
"Latest": "Terbaru",
"LabelIpAddressValue": "Alamat IP: {0}",
"ItemRemovedWithName": "{0} sudah dikeluarkan dari perpustakaan",
"ItemAddedWithName": "{0} sudah dimasukkan ke dalam perpustakaan",
"ItemRemovedWithName": "{0} sudah dikeluarkan dari pustaka",
"ItemAddedWithName": "{0} telah dimasukkan ke dalam pustaka",
"Inherit": "Warisan",
"HomeVideos": "Video Rumah",
"HeaderRecordingGroups": "Grup Rekaman",
@ -19,8 +19,8 @@
"HeaderFavoriteEpisodes": "Episode Favorit",
"HeaderFavoriteArtists": "Artis Favorit",
"HeaderFavoriteAlbums": "Album Favorit",
"HeaderContinueWatching": "Masih Melihat",
"HeaderCameraUploads": "Uplod Kamera",
"HeaderContinueWatching": "Lanjutkan Menonton",
"HeaderCameraUploads": "Unggahan Kamera",
"HeaderAlbumArtists": "Album Artis",
"Genres": "Genre",
"Folders": "Folder",
@ -32,11 +32,11 @@
"ChapterNameValue": "Bagian {0}",
"Channels": "Saluran",
"TvShows": "Seri TV",
"SubtitleDownloadFailureFromForItem": "Talop gagal diunduh dari {0} untuk {1}",
"StartupEmbyServerIsLoading": "Peladen Jellyfin sedang dimuat. Silakan coba kembali beberapa saat lagi.",
"SubtitleDownloadFailureFromForItem": "Subtitel gagal diunduh dari {0} untuk {1}",
"StartupEmbyServerIsLoading": "Server Jellyfin sedang dimuat. Silakan coba lagi nanti.",
"Songs": "Lagu",
"Playlists": "Daftar putar",
"NotificationOptionPluginUninstalled": "Plugin dilepas",
"NotificationOptionPluginUninstalled": "Plugin dihapus",
"MusicVideos": "Video musik",
"VersionNumber": "Versi {0}",
"ValueSpecialEpisodeName": "Spesial - {0}",
@ -65,7 +65,7 @@
"Photos": "Foto",
"NotificationOptionUserLockedOut": "Pengguna terkunci",
"NotificationOptionTaskFailed": "Kegagalan tugas terjadwal",
"NotificationOptionServerRestartRequired": "Restart peladen dibutuhkan",
"NotificationOptionServerRestartRequired": "Muat ulang server dibutuhkan",
"NotificationOptionPluginUpdateInstalled": "Pembaruan plugin terpasang",
"NotificationOptionPluginInstalled": "Plugin terpasang",
"NotificationOptionPluginError": "Kegagalan plugin",
@ -74,14 +74,14 @@
"NotificationOptionCameraImageUploaded": "Gambar kamera terunggah",
"NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang",
"NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia",
"NewVersionIsAvailable": "Sebuah versi baru dari Peladen Jellyfin tersedia untuk diunduh.",
"NewVersionIsAvailable": "Versi baru dari Jellyfin Server tersedia untuk diunduh.",
"NameSeasonUnknown": "Musim tak diketahui",
"NameSeasonNumber": "Musim {0}",
"NameInstallFailed": "{0} instalasi gagal",
"NameInstallFailed": "{0} penginstalan gagal",
"Music": "Musik",
"Movies": "Film",
"MessageServerConfigurationUpdated": "Konfigurasi peladen telah diperbarui",
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi peladen bagian {0} telah diperbarui",
"MessageServerConfigurationUpdated": "Konfigurasi server telah diperbarui",
"MessageNamedServerConfigurationUpdatedWithValue": "Bagian konfigurasi server {0} telah diperbarui",
"FailedLoginAttemptWithUserName": "Percobaan login gagal dari {0}",
"CameraImageUploadedFrom": "Sebuah gambar baru telah diunggah dari {0}",
"DeviceOfflineWithName": "{0} telah terputus",
@ -90,6 +90,28 @@
"NotificationOptionVideoPlayback": "Pemutaran video dimulai",
"NotificationOptionAudioPlaybackStopped": "Pemutaran audio berhenti",
"NotificationOptionAudioPlayback": "Pemutaran audio dimulai",
"MixedContent": "Konten campur",
"PluginUninstalledWithName": "{0} telah dihapus"
"MixedContent": "Konten campuran",
"PluginUninstalledWithName": "{0} telah dihapus",
"TaskRefreshChapterImagesDescription": "Membuat gambar mini untuk video yang memiliki bagian.",
"TaskRefreshChapterImages": "Ekstrak Gambar Bagian",
"TaskCleanCacheDescription": "Menghapus file cache yang tidak lagi dibutuhkan oleh sistem.",
"TaskCleanCache": "Bersihkan Cache Direktori",
"TasksLibraryCategory": "Pustaka",
"TasksMaintenanceCategory": "Perbaikan",
"TasksApplicationCategory": "Aplikasi",
"TaskRefreshPeopleDescription": "Memperbarui metadata untuk aktor dan sutradara di pustaka media Anda.",
"TaskRefreshLibraryDescription": "Memindai Pustaka media Anda untuk mencari file baru dan memperbarui metadata.",
"TasksChannelsCategory": "Saluran Online",
"TaskDownloadMissingSubtitlesDescription": "Mencari di internet untuk subtitle yang hilang berdasarkan konfigurasi metadata.",
"TaskDownloadMissingSubtitles": "Unduh subtitle yang hilang",
"TaskRefreshChannelsDescription": "Segarkan informasi saluran internet.",
"TaskRefreshChannels": "Segarkan Saluran",
"TaskCleanTranscodeDescription": "Menghapus file transcode yang berumur lebih dari satu hari.",
"TaskCleanTranscode": "Bersihkan Direktori Transcode",
"TaskUpdatePluginsDescription": "Unduh dan instal pembaruan untuk plugin yang dikonfigurasi untuk memperbarui secara otomatis.",
"TaskUpdatePlugins": "Perbarui Plugin",
"TaskRefreshPeople": "Muat ulang Orang",
"TaskCleanLogsDescription": "Menghapus file log yang lebih dari {0} hari.",
"TaskCleanLogs": "Bersihkan Log Direktori",
"TaskRefreshLibrary": "Pindai Pustaka Media"
}

@ -102,11 +102,11 @@
"TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin che sono stati configurati per essere aggiornati contemporaneamente.",
"TaskUpdatePlugins": "Aggiorna i Plugin",
"TaskRefreshPeopleDescription": "Aggiorna i metadati per gli attori e registi nella tua libreria multimediale.",
"TaskRefreshPeople": "Aggiorna persone",
"TaskRefreshPeople": "Aggiornamento Persone",
"TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.",
"TaskCleanLogs": "Pulisci la cartella dei log",
"TaskRefreshLibraryDescription": "Analizza la tua libreria multimediale per nuovi file e rinnova i metadati.",
"TaskRefreshLibrary": "Analizza la libreria dei contenuti multimediali",
"TaskRefreshLibrary": "Scan Librerie",
"TaskRefreshChapterImagesDescription": "Crea le thumbnail per i video che hanno capitoli.",
"TaskRefreshChapterImages": "Estrai immagini capitolo",
"TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.",

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

@ -45,7 +45,7 @@
"TvShows": "தொலைக்காட்சித் தொடர்கள்",
"Sync": "ஒத்திசைவு",
"StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
"Songs": "பாடடுகள்",
"Songs": "பாட்கள்",
"Shows": "தொடர்கள்",
"ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
"ScheduledTaskStartedWithName": "{0} துவங்கியது",
@ -93,7 +93,25 @@
"Channels": "சேனல்கள்",
"Books": "புத்தகங்கள்",
"AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
"Artists": "கலைஞர்கள்",
"Artists": "கலைஞர்",
"Application": "செயலி",
"Albums": "ஆல்பங்கள்"
"Albums": "ஆல்பங்கள்",
"NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
"MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது",
"TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
"UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
"SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0 } இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
"TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
"TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
"TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
"TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.",
"TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
"TaskCleanLogs": "பதிவு அடைவு சுத்தம் செய்யுங்கள்",
"TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.",
"TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
"ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
"UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
"HomeVideos": "முகப்பு வீடியோக்கள்",
"UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது",
"UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
}

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

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

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

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

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.Serialization
@ -53,10 +54,11 @@ namespace Emby.Server.Implementations.Serialization
/// <param name="stream">The stream.</param>
public void SerializeToStream(object obj, Stream stream)
{
using (var writer = new XmlTextWriter(stream, null))
using (var writer = new StreamWriter(stream, null, IODefaults.StreamWriterBufferSize, true))
using (var textWriter = new XmlTextWriter(writer))
{
writer.Formatting = Formatting.Indented;
SerializeToWriter(obj, writer);
textWriter.Formatting = Formatting.Indented;
SerializeToWriter(obj, textWriter);
}
}
@ -95,7 +97,7 @@ namespace Emby.Server.Implementations.Serialization
/// <returns>System.Object.</returns>
public object DeserializeFromBytes(Type type, byte[] buffer)
{
using (var stream = new MemoryStream(buffer))
using (var stream = new MemoryStream(buffer, 0, buffer.Length, false, true))
{
return DeserializeFromStream(type, stream);
}

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

@ -1,93 +1,32 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
namespace Jellyfin.Api.Controllers
{
/// <summary>
/// The audio controller.
/// </summary>
// TODO: In order to autheneticate this in the future, Dlna playback will require updating
// TODO: In order to authenticate this in the future, Dlna playback will require updating
public class AudioController : BaseJellyfinApiController
{
private readonly IDlnaManager _dlnaManager;
private readonly IAuthorizationContext _authContext;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _configuration;
private readonly IDeviceManager _deviceManager;
private readonly TranscodingJobHelper _transcodingJobHelper;
private readonly IHttpClientFactory _httpClientFactory;
private readonly AudioHelper _audioHelper;
private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive;
/// <summary>
/// Initializes a new instance of the <see cref="AudioController"/> class.
/// </summary>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
/// <param name="userManger">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
public AudioController(
IDlnaManager dlnaManager,
IUserManager userManger,
IAuthorizationContext authorizationContext,
ILibraryManager libraryManager,
IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
ISubtitleEncoder subtitleEncoder,
IConfiguration configuration,
IDeviceManager deviceManager,
TranscodingJobHelper transcodingJobHelper,
IHttpClientFactory httpClientFactory)
/// <param name="audioHelper">Instance of <see cref="AudioHelper"/>.</param>
public AudioController(AudioHelper audioHelper)
{
_dlnaManager = dlnaManager;
_authContext = authorizationContext;
_userManager = userManger;
_libraryManager = libraryManager;
_mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_subtitleEncoder = subtitleEncoder;
_configuration = configuration;
_deviceManager = deviceManager;
_transcodingJobHelper = transcodingJobHelper;
_httpClientFactory = httpClientFactory;
_audioHelper = audioHelper;
}
/// <summary>
@ -144,9 +83,9 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Audio stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("{itemId}/{stream=stream}.{container?}", Name = "GetAudioStreamByContainer")]
[HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")]
[HttpGet("{itemId}/stream", Name = "GetAudioStream")]
[HttpHead("{itemId}/{stream=stream}.{container?}", Name = "HeadAudioStreamByContainer")]
[HttpHead("{itemId}/stream.{container}", Name = "HeadAudioStreamByContainer")]
[HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetAudioStream(
@ -200,10 +139,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] EncodingContext? context,
[FromQuery] Dictionary<string, string>? streamOptions)
{
bool isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
var cancellationTokenSource = new CancellationTokenSource();
StreamingRequestDto streamingRequest = new StreamingRequestDto
{
Id = itemId,
@ -257,97 +192,7 @@ namespace Jellyfin.Api.Controllers
StreamOptions = streamOptions
};
using var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
Request,
_authContext,
_mediaSourceManager,
_userManager,
_libraryManager,
_serverConfigurationManager,
_mediaEncoder,
_fileSystem,
_subtitleEncoder,
_configuration,
_dlnaManager,
_deviceManager,
_transcodingJobHelper,
_transcodingJobType,
cancellationTokenSource.Token)
.ConfigureAwait(false);
if (@static.HasValue && @static.Value && state.DirectStreamProvider != null)
{
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
{
AllowEndOfFile = false
}.WriteToAsync(Response.Body, CancellationToken.None)
.ConfigureAwait(false);
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
}
// Static remote stream
if (@static.HasValue && @static.Value && state.InputProtocol == MediaProtocol.Http)
{
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
using var httpClient = _httpClientFactory.CreateClient();
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, httpClient).ConfigureAwait(false);
}
if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File)
{
return BadRequest($"Input protocol {state.InputProtocol} cannot be streamed statically");
}
var outputPath = state.OutputFilePath;
var outputPathExists = System.IO.File.Exists(outputPath);
var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
var isTranscodeCached = outputPathExists && transcodingJob != null;
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, (@static.HasValue && @static.Value) || isTranscodeCached, startTimeTicks, Request, _dlnaManager);
// Static stream
if (@static.HasValue && @static.Value)
{
var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);
if (state.MediaSource.IsInfiniteStream)
{
await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
{
AllowEndOfFile = false
}.WriteToAsync(Response.Body, CancellationToken.None)
.ConfigureAwait(false);
return File(Response.Body, contentType);
}
return FileStreamResponseHelpers.GetStaticFileResult(
state.MediaPath,
contentType,
isHeadRequest,
this);
}
// Need to start ffmpeg (because media can't be returned directly)
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
var encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
var ffmpegCommandLineArguments = encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
return await FileStreamResponseHelpers.GetTranscodedFile(
state,
isHeadRequest,
this,
_transcodingJobHelper,
ffmpegCommandLineArguments,
Request,
_transcodingJobType,
cancellationTokenSource).ConfigureAwait(false);
return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false);
}
}
}

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

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

Loading…
Cancel
Save