mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-08 23:00:51 +02:00
Merge branch 'master' into warn24
This commit is contained in:
commit
6292a9e4e9
|
@ -19,7 +19,7 @@ namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||||
{
|
{
|
||||||
return new ConfigurationStore[]
|
return new[]
|
||||||
{
|
{
|
||||||
new ConfigurationStore
|
new ConfigurationStore
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace Emby.Dlna.ConnectionManager
|
||||||
DataType = "string",
|
DataType = "string",
|
||||||
SendsEvents = false,
|
SendsEvents = false,
|
||||||
|
|
||||||
AllowedValues = new string[]
|
AllowedValues = new[]
|
||||||
{
|
{
|
||||||
"OK",
|
"OK",
|
||||||
"ContentFormatMismatch",
|
"ContentFormatMismatch",
|
||||||
|
@ -67,7 +67,7 @@ namespace Emby.Dlna.ConnectionManager
|
||||||
DataType = "string",
|
DataType = "string",
|
||||||
SendsEvents = false,
|
SendsEvents = false,
|
||||||
|
|
||||||
AllowedValues = new string[]
|
AllowedValues = new[]
|
||||||
{
|
{
|
||||||
"Output",
|
"Output",
|
||||||
"Input"
|
"Input"
|
||||||
|
|
|
@ -10,7 +10,8 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
public string GetXml()
|
public string GetXml()
|
||||||
{
|
{
|
||||||
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(),
|
return new ServiceXmlBuilder().GetXml(
|
||||||
|
new ServiceActionListBuilder().GetActions(),
|
||||||
GetStateVariables());
|
GetStateVariables());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
DataType = "string",
|
DataType = "string",
|
||||||
SendsEvents = false,
|
SendsEvents = false,
|
||||||
|
|
||||||
AllowedValues = new string[]
|
AllowedValues = new[]
|
||||||
{
|
{
|
||||||
"BrowseMetadata",
|
"BrowseMetadata",
|
||||||
"BrowseDirectChildren"
|
"BrowseDirectChildren"
|
||||||
|
|
|
@ -253,7 +253,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
var id = sparams["ObjectID"];
|
var id = sparams["ObjectID"];
|
||||||
var flag = sparams["BrowseFlag"];
|
var flag = sparams["BrowseFlag"];
|
||||||
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
|
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
|
||||||
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
|
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
|
||||||
|
|
||||||
var provided = 0;
|
var provided = 0;
|
||||||
|
|
||||||
|
@ -362,8 +362,8 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
|
private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
|
||||||
{
|
{
|
||||||
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", ""));
|
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", string.Empty));
|
||||||
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
|
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
|
||||||
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
|
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
|
||||||
|
|
||||||
// sort example: dc:title, dc:date
|
// sort example: dc:title, dc:date
|
||||||
|
|
|
@ -7,6 +7,11 @@ namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public class ControlRequest
|
public class ControlRequest
|
||||||
{
|
{
|
||||||
|
public ControlRequest()
|
||||||
|
{
|
||||||
|
Headers = new HeaderDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
public IHeaderDictionary Headers { get; set; }
|
public IHeaderDictionary Headers { get; set; }
|
||||||
|
|
||||||
public Stream InputXml { get; set; }
|
public Stream InputXml { get; set; }
|
||||||
|
@ -14,10 +19,5 @@ namespace Emby.Dlna
|
||||||
public string TargetServerUuId { get; set; }
|
public string TargetServerUuId { get; set; }
|
||||||
|
|
||||||
public string RequestedUrl { get; set; }
|
public string RequestedUrl { get; set; }
|
||||||
|
|
||||||
public ControlRequest()
|
|
||||||
{
|
|
||||||
Headers = new HeaderDictionary();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,12 +34,12 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
public class DidlBuilder
|
public class DidlBuilder
|
||||||
{
|
{
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
||||||
|
private const string NsDc = "http://purl.org/dc/elements/1.1/";
|
||||||
|
private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||||
|
private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
|
||||||
|
|
||||||
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
|
|
||||||
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
|
||||||
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
|
|
||||||
|
|
||||||
private readonly DeviceProfile _profile;
|
private readonly DeviceProfile _profile;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
@ -100,11 +100,11 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
// writer.WriteStartDocument();
|
// writer.WriteStartDocument();
|
||||||
|
|
||||||
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
writer.WriteAttributeString("xmlns", "dc", null, NsDc);
|
||||||
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
|
||||||
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
|
||||||
// didl.SetAttribute("xmlns:sec", NS_SEC);
|
// didl.SetAttribute("xmlns:sec", NS_SEC);
|
||||||
|
|
||||||
WriteXmlRootAttributes(_profile, writer);
|
WriteXmlRootAttributes(_profile, writer);
|
||||||
|
@ -147,7 +147,7 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
var clientId = GetClientId(item, null);
|
var clientId = GetClientId(item, null);
|
||||||
|
|
||||||
writer.WriteStartElement(string.Empty, "item", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "item", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("restricted", "1");
|
writer.WriteAttributeString("restricted", "1");
|
||||||
writer.WriteAttributeString("id", clientId);
|
writer.WriteAttributeString("id", clientId);
|
||||||
|
@ -207,7 +207,8 @@ namespace Emby.Dlna.Didl
|
||||||
var targetWidth = streamInfo.TargetWidth;
|
var targetWidth = streamInfo.TargetWidth;
|
||||||
var targetHeight = streamInfo.TargetHeight;
|
var targetHeight = streamInfo.TargetHeight;
|
||||||
|
|
||||||
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
|
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
targetWidth,
|
targetWidth,
|
||||||
|
@ -279,7 +280,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
|
writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
|
||||||
|
|
||||||
|
@ -288,7 +289,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
var protocolInfo = string.Format(
|
var protocolInfo = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"http-get:*:text/{0}:*",
|
"http-get:*:text/{0}:*",
|
||||||
|
@ -304,7 +305,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
|
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
|
|
||||||
var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken));
|
var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken));
|
||||||
|
|
||||||
|
@ -526,7 +527,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
|
|
||||||
if (streamInfo == null)
|
if (streamInfo == null)
|
||||||
{
|
{
|
||||||
|
@ -583,7 +584,8 @@ namespace Emby.Dlna.Didl
|
||||||
writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
|
writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
|
var mediaProfile = _profile.GetAudioMediaProfile(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
targetChannels,
|
targetChannels,
|
||||||
targetAudioBitrate,
|
targetAudioBitrate,
|
||||||
|
@ -596,7 +598,8 @@ namespace Emby.Dlna.Didl
|
||||||
? MimeTypes.GetMimeType(filename)
|
? MimeTypes.GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
targetAudioBitrate,
|
targetAudioBitrate,
|
||||||
targetSampleRate,
|
targetSampleRate,
|
||||||
|
@ -627,7 +630,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "container", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "container", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("restricted", "1");
|
writer.WriteAttributeString("restricted", "1");
|
||||||
writer.WriteAttributeString("searchable", "1");
|
writer.WriteAttributeString("searchable", "1");
|
||||||
|
@ -714,7 +717,7 @@ namespace Emby.Dlna.Didl
|
||||||
// MediaMonkey for example won't display content without a title
|
// MediaMonkey for example won't display content without a title
|
||||||
// if (filter.Contains("dc:title"))
|
// if (filter.Contains("dc:title"))
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
|
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NsDc);
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteObjectClass(writer, item, itemStubType);
|
WriteObjectClass(writer, item, itemStubType);
|
||||||
|
@ -723,7 +726,7 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
if (item.PremiereDate.HasValue)
|
if (item.PremiereDate.HasValue)
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NS_DC);
|
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NsDc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -731,13 +734,13 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
foreach (var genre in item.Genres)
|
foreach (var genre in item.Genres)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "genre", genre, NS_UPNP);
|
AddValue(writer, "upnp", "genre", genre, NsUpnp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var studio in item.Studios)
|
foreach (var studio in item.Studios)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "publisher", studio, NS_UPNP);
|
AddValue(writer, "upnp", "publisher", studio, NsUpnp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(item is Folder))
|
if (!(item is Folder))
|
||||||
|
@ -748,14 +751,15 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(desc))
|
if (!string.IsNullOrWhiteSpace(desc))
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "description", desc, NS_DC);
|
AddValue(writer, "dc", "description", desc, NsDc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (filter.Contains("upnp:longDescription"))
|
// if (filter.Contains("upnp:longDescription"))
|
||||||
// {
|
// {
|
||||||
// if (!string.IsNullOrWhiteSpace(item.Overview))
|
// if (!string.IsNullOrWhiteSpace(item.Overview))
|
||||||
// {
|
// {
|
||||||
// AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP);
|
// AddValue(writer, "upnp", "longDescription", item.Overview, NsUpnp);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
@ -764,12 +768,12 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
if (filter.Contains("dc:rating"))
|
if (filter.Contains("dc:rating"))
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
|
AddValue(writer, "dc", "rating", item.OfficialRating, NsDc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.Contains("upnp:rating"))
|
if (filter.Contains("upnp:rating"))
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
|
AddValue(writer, "upnp", "rating", item.OfficialRating, NsUpnp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,7 +785,7 @@ namespace Emby.Dlna.Didl
|
||||||
// More types here
|
// More types here
|
||||||
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
|
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
|
||||||
|
|
||||||
writer.WriteStartElement("upnp", "class", NS_UPNP);
|
writer.WriteStartElement("upnp", "class", NsUpnp);
|
||||||
|
|
||||||
if (item.IsDisplayedAsFolder || stubType.HasValue)
|
if (item.IsDisplayedAsFolder || stubType.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -882,7 +886,7 @@ namespace Emby.Dlna.Didl
|
||||||
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
||||||
?? PersonType.Actor;
|
?? PersonType.Actor;
|
||||||
|
|
||||||
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
|
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NsUpnp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -896,8 +900,8 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
foreach (var artist in hasArtists.Artists)
|
foreach (var artist in hasArtists.Artists)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "artist", artist, NS_UPNP);
|
AddValue(writer, "upnp", "artist", artist, NsUpnp);
|
||||||
AddValue(writer, "dc", "creator", artist, NS_DC);
|
AddValue(writer, "dc", "creator", artist, NsDc);
|
||||||
|
|
||||||
// If it doesn't support album artists (musicvideo), then tag as both
|
// If it doesn't support album artists (musicvideo), then tag as both
|
||||||
if (hasAlbumArtists == null)
|
if (hasAlbumArtists == null)
|
||||||
|
@ -917,16 +921,16 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(item.Album))
|
if (!string.IsNullOrWhiteSpace(item.Album))
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "album", item.Album, NS_UPNP);
|
AddValue(writer, "upnp", "album", item.Album, NsUpnp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.IndexNumber.HasValue)
|
if (item.IndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
|
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp);
|
||||||
|
|
||||||
if (item is Episode)
|
if (item is Episode)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
|
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -935,7 +939,7 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
writer.WriteStartElement("upnp", "artist", NS_UPNP);
|
writer.WriteStartElement("upnp", "artist", NsUpnp);
|
||||||
writer.WriteAttributeString("role", "AlbumArtist");
|
writer.WriteAttributeString("role", "AlbumArtist");
|
||||||
|
|
||||||
writer.WriteString(name);
|
writer.WriteString(name);
|
||||||
|
@ -971,14 +975,14 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
|
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
|
||||||
|
|
||||||
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
|
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
|
||||||
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
|
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
||||||
writer.WriteString(albumartUrlInfo.Url);
|
writer.WriteString(albumartUrlInfo.Url);
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
|
|
||||||
// TOOD: Remove these default values
|
// TOOD: Remove these default values
|
||||||
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
|
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
|
||||||
writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url);
|
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.Url);
|
||||||
|
|
||||||
if (!_profile.EnableAlbumArtInDidl)
|
if (!_profile.EnableAlbumArtInDidl)
|
||||||
{
|
{
|
||||||
|
@ -1021,7 +1025,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
|
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
|
||||||
|
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
|
|
||||||
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
|
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
|
||||||
// rather than using a larger one when available
|
// rather than using a larger one when available
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
#pragma warning disable CA1305
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -29,7 +30,6 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public StringWriterWithEncoding(Encoding encoding)
|
public StringWriterWithEncoding(Encoding encoding)
|
||||||
{
|
{
|
||||||
_encoding = encoding;
|
_encoding = encoding;
|
||||||
|
|
|
@ -541,6 +541,7 @@ namespace Emby.Dlna
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
class DlnaProfileEntryPoint : IServerEntryPoint
|
class DlnaProfileEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,6 +22,8 @@ namespace Emby.Dlna.Eventing
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
|
|
||||||
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
public EventManager(ILogger logger, IHttpClient httpClient)
|
public EventManager(ILogger logger, IHttpClient httpClient)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
@ -58,7 +60,8 @@ namespace Emby.Dlna.Eventing
|
||||||
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
||||||
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
_logger.LogDebug("Creating event subscription for {0} with timeout of {1} to {2}",
|
_logger.LogDebug(
|
||||||
|
"Creating event subscription for {0} with timeout of {1} to {2}",
|
||||||
notificationType,
|
notificationType,
|
||||||
timeout,
|
timeout,
|
||||||
callbackUrl);
|
callbackUrl);
|
||||||
|
@ -94,7 +97,7 @@ namespace Emby.Dlna.Eventing
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
|
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
|
||||||
|
|
||||||
_subscriptions.TryRemove(subscriptionId, out EventSubscription sub);
|
_subscriptions.TryRemove(subscriptionId, out _);
|
||||||
|
|
||||||
return new EventSubscriptionResponse
|
return new EventSubscriptionResponse
|
||||||
{
|
{
|
||||||
|
@ -103,7 +106,6 @@ namespace Emby.Dlna.Eventing
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
|
||||||
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
|
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
|
||||||
{
|
{
|
||||||
var response = new EventSubscriptionResponse
|
var response = new EventSubscriptionResponse
|
||||||
|
|
|
@ -54,13 +54,7 @@ namespace Emby.Dlna.Main
|
||||||
private SsdpDevicePublisher _publisher;
|
private SsdpDevicePublisher _publisher;
|
||||||
private ISsdpCommunicationsServer _communicationsServer;
|
private ISsdpCommunicationsServer _communicationsServer;
|
||||||
|
|
||||||
public IContentDirectory ContentDirectory { get; private set; }
|
private bool _disposed;
|
||||||
|
|
||||||
public IConnectionManager ConnectionManager { get; private set; }
|
|
||||||
|
|
||||||
public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
|
|
||||||
|
|
||||||
public static DlnaEntryPoint Current { get; private set; }
|
|
||||||
|
|
||||||
public DlnaEntryPoint(
|
public DlnaEntryPoint(
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
|
@ -127,6 +121,14 @@ namespace Emby.Dlna.Main
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DlnaEntryPoint Current { get; private set; }
|
||||||
|
|
||||||
|
public IContentDirectory ContentDirectory { get; private set; }
|
||||||
|
|
||||||
|
public IConnectionManager ConnectionManager { get; private set; }
|
||||||
|
|
||||||
|
public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
|
||||||
|
|
||||||
public async Task RunAsync()
|
public async Task RunAsync()
|
||||||
{
|
{
|
||||||
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
||||||
|
@ -399,11 +401,40 @@ namespace Emby.Dlna.Main
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DisposeDevicePublisher()
|
||||||
|
{
|
||||||
|
if (_publisher != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Disposing SsdpDevicePublisher");
|
||||||
|
_publisher.Dispose();
|
||||||
|
_publisher = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
{
|
{
|
||||||
DisposeDevicePublisher();
|
DisposeDevicePublisher();
|
||||||
DisposePlayToManager();
|
DisposePlayToManager();
|
||||||
DisposeDeviceDiscovery();
|
DisposeDeviceDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
if (_communicationsServer != null)
|
if (_communicationsServer != null)
|
||||||
{
|
{
|
||||||
|
@ -416,16 +447,8 @@ namespace Emby.Dlna.Main
|
||||||
ConnectionManager = null;
|
ConnectionManager = null;
|
||||||
MediaReceiverRegistrar = null;
|
MediaReceiverRegistrar = null;
|
||||||
Current = null;
|
Current = null;
|
||||||
}
|
|
||||||
|
|
||||||
public void DisposeDevicePublisher()
|
_disposed = true;
|
||||||
{
|
|
||||||
if (_publisher != null)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Disposing SsdpDevicePublisher");
|
|
||||||
_publisher.Dispose();
|
|
||||||
_publisher = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
{
|
{
|
||||||
public string GetXml()
|
public string GetXml()
|
||||||
{
|
{
|
||||||
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(),
|
return new ServiceXmlBuilder().GetXml(
|
||||||
|
new ServiceActionListBuilder().GetActions(),
|
||||||
GetStateVariables());
|
GetStateVariables());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,13 +45,13 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
|
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
|
||||||
|
|
||||||
public TRANSPORTSTATE TransportState { get; private set; }
|
public TransportState TransportState { get; private set; }
|
||||||
|
|
||||||
public bool IsPlaying => TransportState == TRANSPORTSTATE.PLAYING;
|
public bool IsPlaying => TransportState == TransportState.Playing;
|
||||||
|
|
||||||
public bool IsPaused => TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK;
|
public bool IsPaused => TransportState == TransportState.Paused || TransportState == TransportState.PausedPlayback;
|
||||||
|
|
||||||
public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
|
public bool IsStopped => TransportState == TransportState.Stopped;
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
|
|
||||||
|
@ -400,7 +400,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
TransportState = TRANSPORTSTATE.PAUSED;
|
TransportState = TransportState.Paused;
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
}
|
}
|
||||||
|
@ -435,7 +435,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
if (transportState.HasValue)
|
if (transportState.HasValue)
|
||||||
{
|
{
|
||||||
// If we're not playing anything no need to get additional data
|
// If we're not playing anything no need to get additional data
|
||||||
if (transportState.Value == TRANSPORTSTATE.STOPPED)
|
if (transportState.Value == TransportState.Stopped)
|
||||||
{
|
{
|
||||||
UpdateMediaInfo(null, transportState.Value);
|
UpdateMediaInfo(null, transportState.Value);
|
||||||
}
|
}
|
||||||
|
@ -464,7 +464,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
|
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
|
||||||
if (transportState.Value == TRANSPORTSTATE.STOPPED)
|
if (transportState.Value == TransportState.Stopped)
|
||||||
{
|
{
|
||||||
RestartTimerInactive();
|
RestartTimerInactive();
|
||||||
}
|
}
|
||||||
|
@ -595,7 +595,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase);
|
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
private async Task<TransportState?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
|
@ -627,7 +627,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
var transportStateValue = transportState?.Value;
|
var transportStateValue = transportState?.Value;
|
||||||
|
|
||||||
if (transportStateValue != null
|
if (transportStateValue != null
|
||||||
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
&& Enum.TryParse(transportStateValue, true, out TransportState state))
|
||||||
{
|
{
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -1121,7 +1121,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public uBaseObject CurrentMediaInfo { get; private set; }
|
public uBaseObject CurrentMediaInfo { get; private set; }
|
||||||
|
|
||||||
private void UpdateMediaInfo(uBaseObject mediaInfo, TRANSPORTSTATE state)
|
private void UpdateMediaInfo(uBaseObject mediaInfo, TransportState state)
|
||||||
{
|
{
|
||||||
TransportState = state;
|
TransportState = state;
|
||||||
|
|
||||||
|
@ -1130,7 +1130,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (previousMediaInfo == null && mediaInfo != null)
|
if (previousMediaInfo == null && mediaInfo != null)
|
||||||
{
|
{
|
||||||
if (state != TRANSPORTSTATE.STOPPED)
|
if (state != TransportState.Stopped)
|
||||||
{
|
{
|
||||||
OnPlaybackStart(mediaInfo);
|
OnPlaybackStart(mediaInfo);
|
||||||
}
|
}
|
||||||
|
@ -1200,6 +1200,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases unmanaged and optionally managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
|
|
@ -8,6 +8,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class DeviceInfo
|
public class DeviceInfo
|
||||||
{
|
{
|
||||||
|
private readonly List<DeviceService> _services = new List<DeviceService>();
|
||||||
|
private string _baseUrl = string.Empty;
|
||||||
|
|
||||||
public DeviceInfo()
|
public DeviceInfo()
|
||||||
{
|
{
|
||||||
Name = "Generic Device";
|
Name = "Generic Device";
|
||||||
|
@ -33,7 +36,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public string PresentationUrl { get; set; }
|
public string PresentationUrl { get; set; }
|
||||||
|
|
||||||
private string _baseUrl = string.Empty;
|
|
||||||
public string BaseUrl
|
public string BaseUrl
|
||||||
{
|
{
|
||||||
get => _baseUrl;
|
get => _baseUrl;
|
||||||
|
@ -42,7 +44,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public DeviceIcon Icon { get; set; }
|
public DeviceIcon Icon { get; set; }
|
||||||
|
|
||||||
private readonly List<DeviceService> _services = new List<DeviceService>();
|
|
||||||
public List<DeviceService> Services => _services;
|
public List<DeviceService> Services => _services;
|
||||||
|
|
||||||
public DeviceIdentification ToDeviceIdentification()
|
public DeviceIdentification ToDeviceIdentification()
|
||||||
|
|
13
Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
Normal file
13
Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Emby.Dlna.PlayTo
|
||||||
|
{
|
||||||
|
public class MediaChangedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public uBaseObject OldMediaInfo { get; set; }
|
||||||
|
|
||||||
|
public uBaseObject NewMediaInfo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||||
|
|
||||||
private Device _device;
|
|
||||||
private readonly SessionInfo _session;
|
private readonly SessionInfo _session;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
@ -50,6 +49,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
private readonly string _accessToken;
|
private readonly string _accessToken;
|
||||||
|
|
||||||
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
|
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
|
||||||
|
private Device _device;
|
||||||
private int _currentPlaylistIndex;
|
private int _currentPlaylistIndex;
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
@ -372,8 +372,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (!command.ControllingUserId.Equals(Guid.Empty))
|
if (!command.ControllingUserId.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
_sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
|
_sessionManager.LogSessionActivity(
|
||||||
_session.DeviceName, _session.RemoteEndPoint, user);
|
_session.Client,
|
||||||
|
_session.ApplicationVersion,
|
||||||
|
_session.DeviceId,
|
||||||
|
_session.DeviceName,
|
||||||
|
_session.RemoteEndPoint, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlayItems(playlist, cancellationToken);
|
return PlayItems(playlist, cancellationToken);
|
||||||
|
@ -633,6 +637,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases unmanaged and optionally managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
@ -778,7 +786,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
const int maxWait = 15000000;
|
const int maxWait = 15000000;
|
||||||
const int interval = 500;
|
const int interval = 500;
|
||||||
var currentWait = 0;
|
var currentWait = 0;
|
||||||
while (_device.TransportState != TRANSPORTSTATE.PLAYING && currentWait < maxWait)
|
while (_device.TransportState != TransportState.Playing && currentWait < maxWait)
|
||||||
{
|
{
|
||||||
await Task.Delay(interval).ConfigureAwait(false);
|
await Task.Delay(interval).ConfigureAwait(false);
|
||||||
currentWait += interval;
|
currentWait += interval;
|
||||||
|
@ -789,6 +797,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
private class StreamParams
|
private class StreamParams
|
||||||
{
|
{
|
||||||
|
private MediaSourceInfo mediaSource;
|
||||||
|
private IMediaSourceManager _mediaSourceManager;
|
||||||
|
|
||||||
public Guid ItemId { get; set; }
|
public Guid ItemId { get; set; }
|
||||||
|
|
||||||
public bool IsDirectStream { get; set; }
|
public bool IsDirectStream { get; set; }
|
||||||
|
@ -809,15 +820,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public BaseItem Item { get; set; }
|
public BaseItem Item { get; set; }
|
||||||
|
|
||||||
private MediaSourceInfo MediaSource;
|
|
||||||
|
|
||||||
private IMediaSourceManager _mediaSourceManager;
|
|
||||||
|
|
||||||
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
|
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (MediaSource != null)
|
if (mediaSource != null)
|
||||||
{
|
{
|
||||||
return MediaSource;
|
return mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasMediaSources = Item as IHasMediaSources;
|
var hasMediaSources = Item as IHasMediaSources;
|
||||||
|
@ -827,9 +834,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
|
mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return MediaSource;
|
return mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Guid GetItemId(string url)
|
private static Guid GetItemId(string url)
|
||||||
|
|
|
@ -218,7 +218,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
PlayableMediaTypes = profile.GetSupportedMediaTypes(),
|
PlayableMediaTypes = profile.GetSupportedMediaTypes(),
|
||||||
|
|
||||||
SupportedCommands = new string[]
|
SupportedCommands = new[]
|
||||||
{
|
{
|
||||||
GeneralCommandType.VolumeDown.ToString(),
|
GeneralCommandType.VolumeDown.ToString(),
|
||||||
GeneralCommandType.VolumeUp.ToString(),
|
GeneralCommandType.VolumeUp.ToString(),
|
||||||
|
@ -247,8 +247,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
_disposeCancellationTokenSource.Cancel();
|
_disposeCancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.LogDebug(ex, "Error while disposing PlayToManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
_sessionLock.Dispose();
|
_sessionLock.Dispose();
|
||||||
|
|
|
@ -8,11 +8,4 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public uBaseObject MediaInfo { get; set; }
|
public uBaseObject MediaInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MediaChangedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public uBaseObject OldMediaInfo { get; set; }
|
|
||||||
|
|
||||||
public uBaseObject NewMediaInfo { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
|
||||||
{
|
|
||||||
public enum TRANSPORTSTATE
|
|
||||||
{
|
|
||||||
STOPPED,
|
|
||||||
PLAYING,
|
|
||||||
TRANSITIONING,
|
|
||||||
PAUSED_PLAYBACK,
|
|
||||||
PAUSED
|
|
||||||
}
|
|
||||||
}
|
|
13
Emby.Dlna/PlayTo/TransportState.cs
Normal file
13
Emby.Dlna/PlayTo/TransportState.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
namespace Emby.Dlna.PlayTo
|
||||||
|
{
|
||||||
|
public enum TransportState
|
||||||
|
{
|
||||||
|
Stopped,
|
||||||
|
Playing,
|
||||||
|
Transitioning,
|
||||||
|
PausedPlayback,
|
||||||
|
Paused
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,16 +24,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public string UpnpClass { get; set; }
|
public string UpnpClass { get; set; }
|
||||||
|
|
||||||
public bool Equals(uBaseObject obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(Id, obj.Id, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string MediaType
|
public string MediaType
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -58,5 +48,15 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Equals(uBaseObject obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(Id, obj.Id, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,14 +64,14 @@ namespace Emby.Dlna.Profiles
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
{
|
{
|
||||||
// play all
|
// play all
|
||||||
Container = "",
|
Container = string.Empty,
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
{
|
{
|
||||||
// play all
|
// play all
|
||||||
Container = "",
|
Container = string.Empty,
|
||||||
Type = DlnaProfileType.Audio
|
Type = DlnaProfileType.Audio
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -191,7 +191,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[]
|
ResponseProfiles = new[]
|
||||||
{
|
{
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[]
|
ResponseProfiles = new[]
|
||||||
{
|
{
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
|
|
|
@ -202,7 +202,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[]
|
ResponseProfiles = new[]
|
||||||
{
|
{
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
|
|
|
@ -265,7 +265,6 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
CodecProfiles = new[]
|
CodecProfiles = new[]
|
||||||
{
|
{
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
|
|
|
@ -265,7 +265,6 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
CodecProfiles = new[]
|
CodecProfiles = new[]
|
||||||
{
|
{
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
|
|
|
@ -15,11 +15,7 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
public abstract class BaseControlHandler
|
public abstract class BaseControlHandler
|
||||||
{
|
{
|
||||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||||
|
|
||||||
protected IServerConfigurationManager Config { get; }
|
|
||||||
|
|
||||||
protected ILogger Logger { get; }
|
|
||||||
|
|
||||||
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||||
{
|
{
|
||||||
|
@ -27,6 +23,10 @@ namespace Emby.Dlna.Service
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IServerConfigurationManager Config { get; }
|
||||||
|
|
||||||
|
protected ILogger Logger { get; }
|
||||||
|
|
||||||
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -80,10 +80,10 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
writer.WriteStartDocument(true);
|
writer.WriteStartDocument(true);
|
||||||
|
|
||||||
writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
|
||||||
writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
|
writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
|
||||||
|
|
||||||
writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
|
||||||
writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
|
writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
|
||||||
|
|
||||||
WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
|
WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
|
||||||
|
|
|
@ -8,31 +8,31 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
public class BaseService : IEventManager
|
public class BaseService : IEventManager
|
||||||
{
|
{
|
||||||
protected IEventManager EventManager;
|
protected IEventManager _eventManager;
|
||||||
protected IHttpClient HttpClient;
|
protected IHttpClient _httpClient;
|
||||||
protected ILogger Logger;
|
protected ILogger Logger;
|
||||||
|
|
||||||
protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
|
protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
HttpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
|
||||||
EventManager = new EventManager(logger, HttpClient);
|
_eventManager = new EventManager(logger, _httpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
||||||
{
|
{
|
||||||
return EventManager.CancelEventSubscription(subscriptionId);
|
return _eventManager.CancelEventSubscription(subscriptionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string timeoutString, string callbackUrl)
|
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string timeoutString, string callbackUrl)
|
||||||
{
|
{
|
||||||
return EventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callbackUrl);
|
return _eventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callbackUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string timeoutString, string callbackUrl)
|
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string timeoutString, string callbackUrl)
|
||||||
{
|
{
|
||||||
return EventManager.CreateEventSubscription(notificationType, timeoutString, callbackUrl);
|
return _eventManager.CreateEventSubscription(notificationType, timeoutString, callbackUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
public static class ControlErrorHandler
|
public static class ControlErrorHandler
|
||||||
{
|
{
|
||||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||||
|
|
||||||
public static ControlResponse GetResponse(Exception ex)
|
public static ControlResponse GetResponse(Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -26,11 +26,11 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
writer.WriteStartDocument(true);
|
writer.WriteStartDocument(true);
|
||||||
|
|
||||||
writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
|
||||||
writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
|
writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
|
||||||
|
|
||||||
writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
|
||||||
writer.WriteStartElement("SOAP-ENV", "Fault", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Fault", NsSoapEnv);
|
||||||
|
|
||||||
writer.WriteElementString("faultcode", "500");
|
writer.WriteElementString("faultcode", "500");
|
||||||
writer.WriteElementString("faultstring", ex.Message);
|
writer.WriteElementString("faultstring", ex.Message);
|
||||||
|
|
|
@ -17,9 +17,17 @@ namespace Emby.Dlna.Ssdp
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
|
private SsdpDeviceLocator _deviceLocator;
|
||||||
|
private ISsdpCommunicationsServer _commsServer;
|
||||||
|
|
||||||
private int _listenerCount;
|
private int _listenerCount;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
|
public DeviceDiscovery(IServerConfigurationManager config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
|
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -49,15 +57,6 @@ namespace Emby.Dlna.Ssdp
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
||||||
|
|
||||||
private SsdpDeviceLocator _deviceLocator;
|
|
||||||
|
|
||||||
private ISsdpCommunicationsServer _commsServer;
|
|
||||||
|
|
||||||
public DeviceDiscovery(IServerConfigurationManager config)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this method from somewhere in your code to start the search.
|
// Call this method from somewhere in your code to start the search.
|
||||||
public void Start(ISsdpCommunicationsServer communicationsServer)
|
public void Start(ISsdpCommunicationsServer communicationsServer)
|
||||||
{
|
{
|
||||||
|
|
|
@ -87,7 +87,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
"hevc_videotoolbox"
|
"hevc_videotoolbox"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try and use the individual library versions to determine a FFmpeg version
|
// These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
|
||||||
|
private static readonly IReadOnlyDictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
|
||||||
|
{
|
||||||
|
{ "libavutil", new Version(56, 14) },
|
||||||
|
{ "libavcodec", new Version(58, 18) },
|
||||||
|
{ "libavformat", new Version(58, 12) },
|
||||||
|
{ "libavdevice", new Version(58, 3) },
|
||||||
|
{ "libavfilter", new Version(7, 16) },
|
||||||
|
{ "libswscale", new Version(5, 1) },
|
||||||
|
{ "libswresample", new Version(3, 1) },
|
||||||
|
{ "libpostproc", new Version(55, 1) }
|
||||||
|
};
|
||||||
|
|
||||||
// This lookup table is to be maintained with the following command line:
|
// This lookup table is to be maintained with the following command line:
|
||||||
// $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
|
// $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
|
||||||
private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
|
private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
|
||||||
|
@ -156,32 +168,36 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
// Work out what the version under test is
|
// Work out what the version under test is
|
||||||
var version = GetFFmpegVersion(versionOutput);
|
var version = GetFFmpegVersion(versionOutput);
|
||||||
|
|
||||||
_logger.LogInformation("Found ffmpeg version {0}", version != null ? version.ToString() : "unknown");
|
_logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown");
|
||||||
|
|
||||||
if (version == null)
|
if (version == null)
|
||||||
{
|
{
|
||||||
if (MinVersion != null && MaxVersion != null) // Version is unknown
|
if (MaxVersion != null) // Version is unknown
|
||||||
{
|
{
|
||||||
if (MinVersion == MaxVersion)
|
if (MinVersion == MaxVersion)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("FFmpeg validation: We recommend ffmpeg version {0}", MinVersion);
|
_logger.LogWarning("FFmpeg validation: We recommend version {MinVersion}", MinVersion);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning("FFmpeg validation: We recommend a minimum of {0} and maximum of {1}", MinVersion, MaxVersion);
|
_logger.LogWarning("FFmpeg validation: We recommend a minimum of {MinVersion} and maximum of {MaxVersion}", MinVersion, MaxVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("FFmpeg validation: We recommend minimum version {MinVersion}", MinVersion);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (MinVersion != null && version < MinVersion) // Version is below what we recommend
|
else if (version < MinVersion) // Version is below what we recommend
|
||||||
{
|
{
|
||||||
_logger.LogWarning("FFmpeg validation: The minimum recommended ffmpeg version is {0}", MinVersion);
|
_logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (MaxVersion != null && version > MaxVersion) // Version is above what we recommend
|
else if (MaxVersion != null && version > MaxVersion) // Version is above what we recommend
|
||||||
{
|
{
|
||||||
_logger.LogWarning("FFmpeg validation: The maximum recommended ffmpeg version is {0}", MaxVersion);
|
_logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,12 +214,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// Using the output from "ffmpeg -version" work out the FFmpeg version.
|
/// Using the output from "ffmpeg -version" work out the FFmpeg version.
|
||||||
/// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
|
/// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
|
||||||
/// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
|
/// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
|
||||||
/// If that fails then we use one of the main libraries to determine if it's new/older than the latest
|
/// If that fails then we test the libraries to determine if they're newer than our minimum versions.
|
||||||
/// we have stored.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="output">The output from "ffmpeg -version".</param>
|
/// <param name="output">The output from "ffmpeg -version".</param>
|
||||||
/// <returns>The FFmpeg version.</returns>
|
/// <returns>The FFmpeg version.</returns>
|
||||||
internal static Version GetFFmpegVersion(string output)
|
internal Version GetFFmpegVersion(string output)
|
||||||
{
|
{
|
||||||
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
||||||
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
|
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
|
||||||
|
@ -214,12 +229,50 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create a reduced version string and lookup key from dictionary
|
if (!TryGetFFmpegLibraryVersions(output, out string versionString, out IReadOnlyDictionary<string, Version> versionMap))
|
||||||
var reducedVersion = GetLibrariesVersionString(output);
|
{
|
||||||
|
_logger.LogError("No ffmpeg library versions found");
|
||||||
|
|
||||||
// Try to lookup the string and return Key, otherwise if not found returns null
|
return null;
|
||||||
return _ffmpegVersionMap.TryGetValue(reducedVersion, out Version version) ? version : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First try to lookup the full version string
|
||||||
|
if (_ffmpegVersionMap.TryGetValue(versionString, out Version version))
|
||||||
|
{
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try to test for minimum library versions
|
||||||
|
return TestMinimumFFmpegLibraryVersions(versionMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Version TestMinimumFFmpegLibraryVersions(IReadOnlyDictionary<string, Version> versionMap)
|
||||||
|
{
|
||||||
|
var allVersionsValidated = true;
|
||||||
|
|
||||||
|
foreach (var minimumVersion in _ffmpegMinimumLibraryVersions)
|
||||||
|
{
|
||||||
|
if (versionMap.TryGetValue(minimumVersion.Key, out var foundVersion))
|
||||||
|
{
|
||||||
|
if (foundVersion >= minimumVersion.Value)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Found {Library} version {FoundVersion} ({MinimumVersion})", minimumVersion.Key, foundVersion, minimumVersion.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Found {Library} version {FoundVersion} lower than recommended version {MinimumVersion}", minimumVersion.Key, foundVersion, minimumVersion.Value);
|
||||||
|
allVersionsValidated = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError("{Library} version not found", minimumVersion.Key);
|
||||||
|
allVersionsValidated = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allVersionsValidated ? MinVersion : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -228,23 +281,35 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="output">The 'ffmpeg -version' output.</param>
|
/// <param name="output">The 'ffmpeg -version' output.</param>
|
||||||
/// <returns>The library names and major.minor version numbers.</returns>
|
/// <returns>The library names and major.minor version numbers.</returns>
|
||||||
private static string GetLibrariesVersionString(string output)
|
private static bool TryGetFFmpegLibraryVersions(string output, out string versionString, out IReadOnlyDictionary<string, Version> versionMap)
|
||||||
{
|
{
|
||||||
var rc = new StringBuilder(144);
|
var sb = new StringBuilder(144);
|
||||||
foreach (Match m in Regex.Matches(
|
|
||||||
|
var map = new Dictionary<string, Version>();
|
||||||
|
|
||||||
|
foreach (Match match in Regex.Matches(
|
||||||
output,
|
output,
|
||||||
@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
|
@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
|
||||||
RegexOptions.Multiline))
|
RegexOptions.Multiline))
|
||||||
{
|
{
|
||||||
rc.Append(m.Groups["name"])
|
sb.Append(match.Groups["name"])
|
||||||
.Append('=')
|
.Append('=')
|
||||||
.Append(m.Groups["major"])
|
.Append(match.Groups["major"])
|
||||||
.Append('.')
|
.Append('.')
|
||||||
.Append(m.Groups["minor"])
|
.Append(match.Groups["minor"])
|
||||||
.Append(',');
|
.Append(',');
|
||||||
|
|
||||||
|
var str = $"{match.Groups["major"]}.{match.Groups["minor"]}";
|
||||||
|
|
||||||
|
var version = Version.Parse(str);
|
||||||
|
|
||||||
|
map.Add(match.Groups["name"].Value, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc.Length == 0 ? null : rc.ToString();
|
versionString = sb.ToString();
|
||||||
|
versionMap = map;
|
||||||
|
|
||||||
|
return sb.Length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<string> GetHwaccelTypes()
|
private IEnumerable<string> GetHwaccelTypes()
|
||||||
|
|
|
@ -196,9 +196,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
_logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
|
_logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDo - Enable the ffmpeg validator. At the moment any version can be used.
|
|
||||||
rc = true;
|
|
||||||
|
|
||||||
_ffmpegPath = path;
|
_ffmpegPath = path;
|
||||||
EncoderLocation = location;
|
EncoderLocation = location;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||||
[ClassData(typeof(GetFFmpegVersionTestData))]
|
[ClassData(typeof(GetFFmpegVersionTestData))]
|
||||||
public void GetFFmpegVersionTest(string versionOutput, Version? version)
|
public void GetFFmpegVersionTest(string versionOutput, Version? version)
|
||||||
{
|
{
|
||||||
Assert.Equal(version, EncoderValidator.GetFFmpegVersion(versionOutput));
|
var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
|
||||||
|
Assert.Equal(version, val.GetFFmpegVersion(versionOutput));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -22,7 +23,7 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||||
[InlineData(EncoderValidatorTestsData.FFmpegV42Output, true)]
|
[InlineData(EncoderValidatorTestsData.FFmpegV42Output, true)]
|
||||||
[InlineData(EncoderValidatorTestsData.FFmpegV414Output, true)]
|
[InlineData(EncoderValidatorTestsData.FFmpegV414Output, true)]
|
||||||
[InlineData(EncoderValidatorTestsData.FFmpegV404Output, true)]
|
[InlineData(EncoderValidatorTestsData.FFmpegV404Output, true)]
|
||||||
[InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, false)]
|
[InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, true)]
|
||||||
public void ValidateVersionInternalTest(string versionOutput, bool valid)
|
public void ValidateVersionInternalTest(string versionOutput, bool valid)
|
||||||
{
|
{
|
||||||
var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
|
var val = new EncoderValidator(new NullLogger<EncoderValidatorTests>());
|
||||||
|
@ -38,7 +39,7 @@ namespace Jellyfin.MediaEncoding.Tests
|
||||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
|
||||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
|
||||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
|
||||||
yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null };
|
yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, new Version(4, 0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
Loading…
Reference in a new issue