make dlna project portable

This commit is contained in:
Luke Pulverenti 2016-11-04 04:31:05 -04:00
parent c29394a81a
commit 6d250c4050
54 changed files with 1324 additions and 1042 deletions

View file

@ -28,11 +28,13 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using Emby.Common.Implementations.Cryptography;
using Emby.Common.Implementations.Diagnostics;
using Emby.Common.Implementations.Net;
using Emby.Common.Implementations.Threading;
using MediaBrowser.Common;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Threading;
@ -153,6 +155,7 @@ namespace Emby.Common.Implementations
protected IProcessFactory ProcessFactory { get; private set; }
protected ITimerFactory TimerFactory { get; private set; }
protected ISocketFactory SocketFactory { get; private set; }
/// <summary>
/// Gets the name.
@ -549,6 +552,9 @@ return null;
TimerFactory = new TimerFactory();
RegisterSingleInstance(TimerFactory);
SocketFactory = new SocketFactory(null);
RegisterSingleInstance(SocketFactory);
RegisterSingleInstance(CryptographyProvider);
return Task.FromResult(true);

View file

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Emby.Common.Implementations.Net
{
/// <summary>
/// Correclty implements the <see cref="IDisposable"/> interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an <see cref="IsDisposed"/> property.
/// </summary>
public abstract class DisposableManagedObjectBase : IDisposable
{
#region Public Methods
/// <summary>
/// Override this method and dispose any objects you own the lifetime of if disposing is true;
/// </summary>
/// <param name="disposing">True if managed objects should be disposed, if false, only unmanaged resources should be released.</param>
protected abstract void Dispose(bool disposing);
/// <summary>
/// Throws and <see cref="System.ObjectDisposedException"/> if the <see cref="IsDisposed"/> property is true.
/// </summary>
/// <seealso cref="IsDisposed"/>
/// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="IsDisposed"/> property is true.</exception>
/// <seealso cref="Dispose()"/>
protected virtual void ThrowIfDisposed()
{
if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName);
}
#endregion
#region Public Properties
/// <summary>
/// Sets or returns a boolean indicating whether or not this instance has been disposed.
/// </summary>
/// <seealso cref="Dispose()"/>
public bool IsDisposed
{
get;
private set;
}
#endregion
#region IDisposable Members
/// <summary>
/// Disposes this object instance and all internally managed resources.
/// </summary>
/// <remarks>
/// <para>Sets the <see cref="IsDisposed"/> property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes.</para>
/// </remarks>
/// <seealso cref="IsDisposed"/>
public void Dispose()
{
try
{
IsDisposed = true;
Dispose(true);
}
finally
{
GC.SuppressFinalize(this);
}
}
#endregion
}
}

View file

@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
namespace Emby.Common.Implementations.Net
{
public class SocketFactory : ISocketFactory
{
// THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS
// Be careful to check any changes compile and work for all platform projects it is shared in.
// Not entirely happy with this. Would have liked to have done something more generic/reusable,
// but that wasn't really the point so kept to YAGNI principal for now, even if the
// interfaces are a bit ugly, specific and make assumptions.
/// <summary>
/// Used by RSSDP components to create implementations of the <see cref="IUdpSocket"/> interface, to perform platform agnostic socket communications.
/// </summary>
private IPAddress _LocalIP;
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="localIP">A string containing the IP address of the local network adapter to bind sockets to. Null or empty string will use <see cref="IPAddress.Any"/>.</param>
public SocketFactory(string localIP)
{
if (String.IsNullOrEmpty(localIP))
_LocalIP = IPAddress.Any;
else
_LocalIP = IPAddress.Parse(localIP);
}
#region ISocketFactory Members
/// <summary>
/// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
/// </summary>
/// <param name="localPort">An integer specifying the local port to bind the socket to.</param>
/// <returns>An implementation of the <see cref="IUdpSocket"/> interface used by RSSDP components to perform socket operations.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")]
public IUdpSocket CreateUdpSocket(int localPort)
{
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
try
{
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), _LocalIP));
return new UdpSocket(retVal, localPort, _LocalIP.ToString());
}
catch
{
if (retVal != null)
retVal.Dispose();
throw;
}
}
/// <summary>
/// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port.
/// </summary>
/// <param name="ipAddress">The multicast IP address to make the socket a member of.</param>
/// <param name="multicastTimeToLive">The multicast time to live value for the socket.</param>
/// <param name="localPort">The number of the local port to bind to.</param>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")]
public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
{
if (ipAddress == null) throw new ArgumentNullException("ipAddress");
if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress");
if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive");
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
#if NETSTANDARD1_3
// The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket
// See https://github.com/dotnet/corefx/pull/11509 for more details
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
{
retVal.ExclusiveAddressUse = false;
}
#else
retVal.ExclusiveAddressUse = false;
#endif
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP));
retVal.MulticastLoopback = true;
return new UdpSocket(retVal, localPort, _LocalIP.ToString());
}
catch
{
if (retVal != null)
retVal.Dispose();
throw;
}
}
#endregion
}
}

View file

@ -1,15 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Rssdp.Infrastructure;
using MediaBrowser.Model.Net;
namespace Rssdp
namespace Emby.Common.Implementations.Net
{
// THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS
// Be careful to check any changes compile and work for all platform projects it is shared in.
@ -48,7 +46,7 @@ namespace Rssdp
#region IUdpSocket Members
public System.Threading.Tasks.Task<ReceivedUdpData> ReceiveAsync()
public Task<ReceivedUdpData> ReceiveAsync()
{
ThrowIfDisposed();
@ -76,7 +74,7 @@ namespace Rssdp
return tcs.Task;
}
public Task SendTo(byte[] messageData, UdpEndPoint endPoint)
public Task SendTo(byte[] messageData, IpEndPointInfo endPoint)
{
ThrowIfDisposed();
@ -84,14 +82,14 @@ namespace Rssdp
if (endPoint == null) throw new ArgumentNullException("endPoint");
#if NETSTANDARD1_6
_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port));
_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port));
return Task.FromResult(true);
#else
var taskSource = new TaskCompletionSource<bool>();
try
{
_Socket.BeginSendTo(messageData, 0, messageData.Length, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IPAddress), endPoint.Port), result =>
_Socket.BeginSendTo(messageData, 0, messageData.Length, SocketFlags.None, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port), result =>
{
try
{
@ -166,11 +164,7 @@ namespace Rssdp
{
Buffer = state.Buffer,
ReceivedBytes = bytesRead,
ReceivedFrom = new UdpEndPoint()
{
IPAddress = ipEndPoint.Address.ToString(),
Port = ipEndPoint.Port
}
ReceivedFrom = ToIpEndPointInfo(ipEndPoint)
}
);
}
@ -191,6 +185,25 @@ namespace Rssdp
}
}
private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
{
if (endpoint == null)
{
return null;
}
return new IpEndPointInfo
{
IpAddress = new IpAddressInfo
{
Address = endpoint.Address.ToString(),
IsIpv6 = endpoint.AddressFamily == AddressFamily.InterNetworkV6
},
Port = endpoint.Port
};
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")]
private void ProcessResponse(IAsyncResult asyncResult)
{
@ -206,11 +219,7 @@ namespace Rssdp
{
Buffer = state.Buffer,
ReceivedBytes = bytesRead,
ReceivedFrom = new UdpEndPoint()
{
IPAddress = ipEndPoint.Address.ToString(),
Port = ipEndPoint.Port
}
ReceivedFrom = ToIpEndPointInfo(ipEndPoint)
}
);
}
@ -245,7 +254,7 @@ namespace Rssdp
}
public EndPoint EndPoint;
public byte[] Buffer = new byte[SsdpConstants.DefaultUdpSocketBufferSize];
public byte[] Buffer = new byte[8192];
public System.Net.Sockets.Socket Socket { get; private set; }
@ -256,4 +265,4 @@ namespace Rssdp
#endregion
}
}
}

View file

@ -14,5 +14,13 @@ namespace Emby.Common.Implementations.Reflection
#endif
return type.GetTypeInfo().Assembly.GetManifestResourceStream(resource);
}
public string[] GetManifestResourceNames(Type type)
{
#if NET46
return type.Assembly.GetManifestResourceNames();
#endif
return type.GetTypeInfo().Assembly.GetManifestResourceNames();
}
}
}

View file

@ -4,6 +4,7 @@ using MediaBrowser.Controller.Dlna;
using Emby.Dlna.Service;
using MediaBrowser.Model.Logging;
using System.Collections.Generic;
using MediaBrowser.Model.Xml;
namespace Emby.Dlna.ConnectionManager
{
@ -12,13 +13,15 @@ namespace Emby.Dlna.ConnectionManager
private readonly IDlnaManager _dlna;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
: base(logger, httpClient)
{
_dlna = dlna;
_config = config;
_logger = logger;
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
public string GetServiceXml(IDictionary<string, string> headers)
@ -31,7 +34,7 @@ namespace Emby.Dlna.ConnectionManager
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
return new ControlHandler(_logger, profile, _config).ProcessControlRequest(request);
return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request);
}
}
}

View file

@ -6,6 +6,7 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Xml;
namespace Emby.Dlna.ConnectionManager
{
@ -13,12 +14,6 @@ namespace Emby.Dlna.ConnectionManager
{
private readonly DeviceProfile _profile;
public ControlHandler(ILogger logger, DeviceProfile profile, IServerConfigurationManager config)
: base(config, logger)
{
_profile = profile;
}
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
{
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
@ -37,5 +32,10 @@ namespace Emby.Dlna.ConnectionManager
{ "Sink", "" }
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory)
{
_profile = profile;
}
}
}

View file

@ -13,6 +13,7 @@ using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Xml;
namespace Emby.Dlna.ContentDirectory
{
@ -29,6 +30,7 @@ namespace Emby.Dlna.ContentDirectory
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IUserViewManager _userViewManager;
private readonly Func<IMediaEncoder> _mediaEncoder;
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
public ContentDirectory(IDlnaManager dlna,
IUserDataManager userDataManager,
@ -37,7 +39,7 @@ namespace Emby.Dlna.ContentDirectory
IServerConfigurationManager config,
IUserManager userManager,
ILogger logger,
IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func<IMediaEncoder> mediaEncoder)
IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func<IMediaEncoder> mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
: base(logger, httpClient)
{
_dlna = dlna;
@ -51,6 +53,7 @@ namespace Emby.Dlna.ContentDirectory
_mediaSourceManager = mediaSourceManager;
_userViewManager = userViewManager;
_mediaEncoder = mediaEncoder;
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
private int SystemUpdateId
@ -93,7 +96,8 @@ namespace Emby.Dlna.ContentDirectory
_channelManager,
_mediaSourceManager,
_userViewManager,
_mediaEncoder())
_mediaEncoder(),
XmlReaderSettingsFactory)
.ProcessControlRequest(request);
}

View file

@ -17,6 +17,7 @@ using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
@ -24,6 +25,7 @@ using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Xml;
namespace Emby.Dlna.ContentDirectory
{
@ -35,7 +37,6 @@ namespace Emby.Dlna.ContentDirectory
private readonly IServerConfigurationManager _config;
private readonly User _user;
private readonly IUserViewManager _userViewManager;
private readonly IMediaEncoder _mediaEncoder;
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/";
@ -49,8 +50,8 @@ namespace Emby.Dlna.ContentDirectory
private readonly DeviceProfile _profile;
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder)
: base(config, logger)
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
: base(config, logger, xmlReaderSettingsFactory)
{
_libraryManager = libraryManager;
_userDataManager = userDataManager;
@ -58,11 +59,10 @@ namespace Emby.Dlna.ContentDirectory
_systemUpdateId = systemUpdateId;
_channelManager = channelManager;
_userViewManager = userViewManager;
_mediaEncoder = mediaEncoder;
_profile = profile;
_config = config;
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, _mediaEncoder);
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, mediaEncoder);
}
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
@ -209,73 +209,92 @@ namespace Emby.Dlna.ContentDirectory
start = startVal;
}
//var root = GetItem(id) as IMediaFolder;
var result = new XmlDocument();
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
CloseOutput = false,
OmitXmlDeclaration = true,
ConformanceLevel = ConformanceLevel.Fragment
};
var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
didl.SetAttribute("xmlns:dc", NS_DC);
didl.SetAttribute("xmlns:dlna", NS_DLNA);
didl.SetAttribute("xmlns:upnp", NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
result.AppendChild(didl);
var serverItem = GetItemFromObjectId(id, user);
var item = serverItem.Item;
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
int totalCount;
if (string.Equals(flag, "BrowseMetadata"))
using (XmlWriter writer = XmlWriter.Create(builder, settings))
{
totalCount = 1;
//writer.WriteStartDocument();
if (item.IsFolder || serverItem.StubType.HasValue)
{
var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false));
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id));
}
else
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
foreach (var att in _profile.XmlRootAttributes)
{
result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, item, null, null, deviceId, filter));
writer.WriteAttributeString(att.Name, att.Value);
}
provided++;
}
else
{
var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false));
totalCount = childrenResult.TotalRecordCount;
var serverItem = GetItemFromObjectId(id, user);
var item = serverItem.Item;
provided = childrenResult.Items.Length;
foreach (var i in childrenResult.Items)
if (string.Equals(flag, "BrowseMetadata"))
{
var childItem = i.Item;
var displayStubType = i.StubType;
totalCount = 1;
if (childItem.IsFolder || displayStubType.HasValue)
if (item.IsFolder || serverItem.StubType.HasValue)
{
var childCount = (await GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0).ConfigureAwait(false))
.TotalRecordCount;
var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false));
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, childItem, displayStubType, item, childCount, filter));
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
}
else
{
result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, childItem, item, serverItem.StubType, deviceId, filter));
_didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, item, null, null, deviceId, filter);
}
provided++;
}
else
{
var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false));
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Length;
foreach (var i in childrenResult.Items)
{
var childItem = i.Item;
var displayStubType = i.StubType;
if (childItem.IsFolder || displayStubType.HasValue)
{
var childCount = (await GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0).ConfigureAwait(false))
.TotalRecordCount;
_didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
}
else
{
_didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, childItem, item, serverItem.StubType, deviceId, filter);
}
}
}
writer.WriteEndElement();
//writer.WriteEndDocument();
}
var resXML = result.OuterXml;
var resXML = builder.ToString();
return new List<KeyValuePair<string, string>>
{
new KeyValuePair<string,string>("Result", resXML),
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
};
{
new KeyValuePair<string,string>("Result", resXML),
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
};
}
private async Task<IEnumerable<KeyValuePair<string, string>>> HandleSearch(Headers sparams, User user, string deviceId)
@ -303,55 +322,72 @@ namespace Emby.Dlna.ContentDirectory
start = startVal;
}
//var root = GetItem(id) as IMediaFolder;
var result = new XmlDocument();
var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
didl.SetAttribute("xmlns:dc", NS_DC);
didl.SetAttribute("xmlns:dlna", NS_DLNA);
didl.SetAttribute("xmlns:upnp", NS_UPNP);
foreach (var att in _profile.XmlRootAttributes)
var settings = new XmlWriterSettings
{
didl.SetAttribute(att.Name, att.Value);
Encoding = Encoding.UTF8,
CloseOutput = false,
OmitXmlDeclaration = true,
ConformanceLevel = ConformanceLevel.Fragment
};
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
int totalCount = 0;
int provided = 0;
using (XmlWriter writer = XmlWriter.Create(builder, settings))
{
//writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
foreach (var att in _profile.XmlRootAttributes)
{
writer.WriteAttributeString(att.Name, att.Value);
}
var serverItem = GetItemFromObjectId(sparams["ContainerID"], user);
var item = serverItem.Item;
var childrenResult = (await GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount).ConfigureAwait(false));
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Length;
foreach (var i in childrenResult.Items)
{
if (i.IsFolder)
{
var childCount = (await GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0).ConfigureAwait(false))
.TotalRecordCount;
_didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
}
else
{
_didlBuilder.WriteItemElement(_config.GetDlnaConfiguration(), writer, i, item, serverItem.StubType, deviceId, filter);
}
}
writer.WriteEndElement();
//writer.WriteEndDocument();
}
result.AppendChild(didl);
var serverItem = GetItemFromObjectId(sparams["ContainerID"], user);
var item = serverItem.Item;
var childrenResult = (await GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount).ConfigureAwait(false));
var totalCount = childrenResult.TotalRecordCount;
var provided = childrenResult.Items.Length;
foreach (var i in childrenResult.Items)
{
if (i.IsFolder)
{
var childCount = (await GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0).ConfigureAwait(false))
.TotalRecordCount;
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, i, null, item, childCount, filter));
}
else
{
result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, i, item, serverItem.StubType, deviceId, filter));
}
}
var resXML = result.OuterXml;
var resXML = builder.ToString();
return new List<KeyValuePair<string, string>>
{
new KeyValuePair<string,string>("Result", resXML),
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
};
{
new KeyValuePair<string,string>("Result", resXML),
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
};
}
private Task<QueryResult<BaseItem>> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)

View file

@ -17,6 +17,7 @@ using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
@ -62,50 +63,66 @@ namespace Emby.Dlna.Didl
public string GetItemDidl(DlnaOptions options, BaseItem item, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
{
var result = new XmlDocument();
var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
didl.SetAttribute("xmlns:dc", NS_DC);
didl.SetAttribute("xmlns:dlna", NS_DLNA);
didl.SetAttribute("xmlns:upnp", NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
foreach (var att in _profile.XmlRootAttributes)
var settings = new XmlWriterSettings
{
didl.SetAttribute(att.Name, att.Value);
Encoding = Encoding.UTF8,
CloseOutput = false,
OmitXmlDeclaration = true,
ConformanceLevel = ConformanceLevel.Fragment
};
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
using (XmlWriter writer = XmlWriter.Create(builder, settings))
{
//writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
foreach (var att in _profile.XmlRootAttributes)
{
writer.WriteAttributeString(att.Name, att.Value);
}
WriteItemElement(options, writer, item, context, null, deviceId, filter, streamInfo);
writer.WriteEndElement();
//writer.WriteEndDocument();
}
result.AppendChild(didl);
result.DocumentElement.AppendChild(GetItemElement(options, result, item, context, null, deviceId, filter, streamInfo));
return result.DocumentElement.OuterXml;
return builder.ToString();
}
public XmlElement GetItemElement(DlnaOptions options, XmlDocument doc, BaseItem item, BaseItem context, StubType? contextStubType, string deviceId, Filter filter, StreamInfo streamInfo = null)
public void WriteItemElement(DlnaOptions options, XmlWriter writer, BaseItem item, BaseItem context, StubType? contextStubType, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
var clientId = GetClientId(item, null);
var element = doc.CreateElement(string.Empty, "item", NS_DIDL);
element.SetAttribute("restricted", "1");
element.SetAttribute("id", clientId);
writer.WriteStartElement(string.Empty, "item", NS_DIDL);
writer.WriteAttributeString("restricted", "1");
writer.WriteAttributeString("id", clientId);
if (context != null)
{
element.SetAttribute("parentID", GetClientId(context, contextStubType));
writer.WriteAttributeString("parentID", GetClientId(context, contextStubType));
}
else
{
var parent = item.DisplayParentId;
if (parent.HasValue)
{
element.SetAttribute("parentID", GetClientId(parent.Value, null));
writer.WriteAttributeString("parentID", GetClientId(parent.Value, null));
}
}
//AddBookmarkInfo(item, user, element);
AddGeneralProperties(item, null, context, element, filter);
AddGeneralProperties(item, null, context, writer, filter);
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
@ -116,17 +133,16 @@ namespace Emby.Dlna.Didl
{
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
AddAudioResource(options, element, hasMediaSources, deviceId, filter, streamInfo);
AddAudioResource(options, writer, hasMediaSources, deviceId, filter, streamInfo);
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
AddVideoResource(options, element, hasMediaSources, deviceId, filter, streamInfo);
AddVideoResource(options, writer, hasMediaSources, deviceId, filter, streamInfo);
}
}
AddCover(item, context, null, element);
return element;
AddCover(item, context, null, writer);
writer.WriteEndElement();
}
private ILogger GetStreamBuilderLogger(DlnaOptions options)
@ -139,7 +155,7 @@ namespace Emby.Dlna.Didl
return new NullLogger();
}
private void AddVideoResource(DlnaOptions options, XmlElement container, IHasMediaSources video, string deviceId, Filter filter, StreamInfo streamInfo = null)
private void AddVideoResource(DlnaOptions options, XmlWriter writer, IHasMediaSources video, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
if (streamInfo == null)
{
@ -182,14 +198,14 @@ namespace Emby.Dlna.Didl
foreach (var contentFeature in contentFeatureList)
{
AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo);
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
}
foreach (var subtitle in streamInfo.GetSubtitleProfiles(false, _serverAddress, _accessToken))
{
if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
{
var subtitleAdded = AddSubtitleElement(container, subtitle);
var subtitleAdded = AddSubtitleElement(writer, subtitle);
if (subtitleAdded && _profile.EnableSingleSubtitleLimit)
{
@ -199,7 +215,7 @@ namespace Emby.Dlna.Didl
}
}
private bool AddSubtitleElement(XmlElement container, SubtitleStreamInfo info)
private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info)
{
var subtitleProfile = _profile.SubtitleProfiles
.FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External);
@ -216,52 +232,45 @@ namespace Emby.Dlna.Didl
// <sec:CaptionInfoEx sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfoEx>
// <sec:CaptionInfo sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfo>
var res = container.OwnerDocument.CreateElement("CaptionInfoEx", "sec");
writer.WriteStartElement("sec", "CaptionInfoEx", null);
writer.WriteAttributeString("sec", "type", null, info.Format.ToLower());
res.InnerText = info.Url;
//// TODO: attribute needs SEC:
res.SetAttribute("type", "sec", info.Format.ToLower());
container.AppendChild(res);
writer.WriteString(info.Url);
writer.WriteEndElement();
}
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
res.InnerText = info.Url;
writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
res.SetAttribute("protocolInfo", "http-get:*:smi/caption:*");
container.AppendChild(res);
writer.WriteString(info.Url);
writer.WriteEndElement();
}
else
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
res.InnerText = info.Url;
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLower());
res.SetAttribute("protocolInfo", protocolInfo);
writer.WriteAttributeString("protocolInfo", protocolInfo);
container.AppendChild(res);
writer.WriteString(info.Url);
writer.WriteEndElement();
}
return true;
}
private void AddVideoResource(XmlElement container, IHasMediaSources video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
private void AddVideoResource(XmlWriter writer, IHasMediaSources video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var url = streamInfo.ToDlnaUrl(_serverAddress, _accessToken);
res.InnerText = url;
var mediaSource = streamInfo.MediaSource;
if (mediaSource.RunTimeTicks.HasValue)
{
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
}
if (filter.Contains("res@size"))
@ -272,7 +281,7 @@ namespace Emby.Dlna.Didl
if (size.HasValue)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
writer.WriteAttributeString("size", size.Value.ToString(_usCulture));
}
}
}
@ -286,25 +295,25 @@ namespace Emby.Dlna.Didl
if (targetChannels.HasValue)
{
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
}
if (filter.Contains("res@resolution"))
{
if (targetWidth.HasValue && targetHeight.HasValue)
{
res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
}
}
if (targetSampleRate.HasValue)
{
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
}
if (totalBitrate.HasValue)
{
res.SetAttribute("bitrate", totalBitrate.Value.ToString(_usCulture));
writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
@ -332,13 +341,15 @@ namespace Emby.Dlna.Didl
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
res.SetAttribute("protocolInfo", String.Format(
writer.WriteAttributeString("protocolInfo", String.Format(
"http-get:*:{0}:{1}",
mimeType,
contentFeatures
));
container.AppendChild(res);
writer.WriteString(url);
writer.WriteEndElement();
}
private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
@ -382,32 +393,30 @@ namespace Emby.Dlna.Didl
return item.Name;
}
private void AddAudioResource(DlnaOptions options, XmlElement container, IHasMediaSources audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
private void AddAudioResource(DlnaOptions options, XmlWriter writer, IHasMediaSources audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
if (streamInfo == null)
{
var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions
{
ItemId = GetClientId(audio),
MediaSources = sources,
Profile = _profile,
DeviceId = deviceId
});
{
ItemId = GetClientId(audio),
MediaSources = sources,
Profile = _profile,
DeviceId = deviceId
});
}
var url = streamInfo.ToDlnaUrl(_serverAddress, _accessToken);
res.InnerText = url;
var mediaSource = streamInfo.MediaSource;
if (mediaSource.RunTimeTicks.HasValue)
{
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
}
if (filter.Contains("res@size"))
@ -418,7 +427,7 @@ namespace Emby.Dlna.Didl
if (size.HasValue)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
writer.WriteAttributeString("size", size.Value.ToString(_usCulture));
}
}
}
@ -429,17 +438,17 @@ namespace Emby.Dlna.Didl
if (targetChannels.HasValue)
{
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
}
if (targetSampleRate.HasValue)
{
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
}
if (targetAudioBitrate.HasValue)
{
res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
@ -462,13 +471,15 @@ namespace Emby.Dlna.Didl
streamInfo.RunTimeTicks,
streamInfo.TranscodeSeekInfo);
res.SetAttribute("protocolInfo", String.Format(
writer.WriteAttributeString("protocolInfo", String.Format(
"http-get:*:{0}:{1}",
mimeType,
contentFeatures
));
container.AppendChild(res);
writer.WriteString(url);
writer.WriteEndElement();
}
public static bool IsIdRoot(string id)
@ -486,47 +497,48 @@ namespace Emby.Dlna.Didl
return false;
}
public XmlElement GetFolderElement(XmlDocument doc, 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)
{
var container = doc.CreateElement(string.Empty, "container", NS_DIDL);
container.SetAttribute("restricted", "0");
container.SetAttribute("searchable", "1");
container.SetAttribute("childCount", childCount.ToString(_usCulture));
writer.WriteStartElement(string.Empty, "container", NS_DIDL);
writer.WriteAttributeString("restricted", "0");
writer.WriteAttributeString("searchable", "1");
writer.WriteAttributeString("childCount", childCount.ToString(_usCulture));
var clientId = GetClientId(folder, stubType);
if (string.Equals(requestedId, "0"))
{
container.SetAttribute("id", "0");
container.SetAttribute("parentID", "-1");
writer.WriteAttributeString("id", "0");
writer.WriteAttributeString("parentID", "-1");
}
else
{
container.SetAttribute("id", clientId);
writer.WriteAttributeString("id", clientId);
if (context != null)
{
container.SetAttribute("parentID", GetClientId(context, null));
writer.WriteAttributeString("parentID", GetClientId(context, null));
}
else
{
var parent = folder.DisplayParentId;
if (!parent.HasValue)
{
container.SetAttribute("parentID", "0");
writer.WriteAttributeString("parentID", "0");
}
else
{
container.SetAttribute("parentID", GetClientId(parent.Value, null));
writer.WriteAttributeString("parentID", GetClientId(parent.Value, null));
}
}
}
AddCommonFields(folder, stubType, null, container, filter);
AddCommonFields(folder, stubType, null, writer, filter);
AddCover(folder, context, stubType, container);
AddCover(folder, context, stubType, writer);
return container;
writer.WriteEndElement();
}
//private void AddBookmarkInfo(BaseItem item, User user, XmlElement element)
@ -544,27 +556,22 @@ namespace Emby.Dlna.Didl
/// <summary>
/// Adds fields used by both items and folders
/// </summary>
/// <param name="item">The item.</param>
/// <param name="itemStubType">Type of the item stub.</param>
/// <param name="context">The context.</param>
/// <param name="element">The element.</param>
/// <param name="filter">The filter.</param>
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlElement element, Filter filter)
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{
// Don't filter on dc:title because not all devices will include it in the filter
// MediaMonkey for example won't display content without a title
//if (filter.Contains("dc:title"))
{
AddValue(element, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
}
element.AppendChild(CreateObjectClass(element.OwnerDocument, item, itemStubType));
WriteObjectClass(writer, item, itemStubType);
if (filter.Contains("dc:date"))
{
if (item.PremiereDate.HasValue)
{
AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
}
}
@ -572,13 +579,13 @@ namespace Emby.Dlna.Didl
{
foreach (var genre in item.Genres)
{
AddValue(element, "upnp", "genre", genre, NS_UPNP);
AddValue(writer, "upnp", "genre", genre, NS_UPNP);
}
}
foreach (var studio in item.Studios)
{
AddValue(element, "upnp", "publisher", studio, NS_UPNP);
AddValue(writer, "upnp", "publisher", studio, NS_UPNP);
}
if (filter.Contains("dc:description"))
@ -592,14 +599,14 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(desc))
{
AddValue(element, "dc", "description", desc, NS_DC);
AddValue(writer, "dc", "description", desc, NS_DC);
}
}
if (filter.Contains("upnp:longDescription"))
{
if (!string.IsNullOrWhiteSpace(item.Overview))
{
AddValue(element, "upnp", "longDescription", item.Overview, NS_UPNP);
AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP);
}
}
@ -607,23 +614,23 @@ namespace Emby.Dlna.Didl
{
if (filter.Contains("dc:rating"))
{
AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
}
if (filter.Contains("upnp:rating"))
{
AddValue(element, "upnp", "rating", item.OfficialRating, NS_UPNP);
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
}
}
AddPeople(item, element);
AddPeople(item, writer);
}
private XmlElement CreateObjectClass(XmlDocument result, BaseItem item, StubType? stubType)
private void WriteObjectClass(XmlWriter writer, BaseItem item, StubType? stubType)
{
// More types here
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
writer.WriteStartElement("upnp", "class", NS_UPNP);
if (item.IsFolder || stubType.HasValue)
{
@ -653,48 +660,48 @@ namespace Emby.Dlna.Didl
}
}
objectClass.InnerText = classType ?? "object.container.storageFolder";
writer.WriteString(classType ?? "object.container.storageFolder");
}
else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
objectClass.InnerText = "object.item.audioItem.musicTrack";
writer.WriteString("object.item.audioItem.musicTrack");
}
else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{
objectClass.InnerText = "object.item.imageItem.photo";
writer.WriteString("object.item.imageItem.photo");
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
if (!_profile.RequiresPlainVideoItems && item is Movie)
{
objectClass.InnerText = "object.item.videoItem.movie";
writer.WriteString("object.item.videoItem.movie");
}
else if (!_profile.RequiresPlainVideoItems && item is MusicVideo)
{
objectClass.InnerText = "object.item.videoItem.musicVideoClip";
writer.WriteString("object.item.videoItem.musicVideoClip");
}
else
{
objectClass.InnerText = "object.item.videoItem";
writer.WriteString("object.item.videoItem");
}
}
else if (item is MusicGenre)
{
objectClass.InnerText = _profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre";
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre");
}
else if (item is Genre || item is GameGenre)
{
objectClass.InnerText = _profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre";
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre");
}
else
{
objectClass.InnerText = "object.item";
writer.WriteString("object.item");
}
return objectClass;
writer.WriteEndElement();
}
private void AddPeople(BaseItem item, XmlElement element)
private void AddPeople(BaseItem item, XmlWriter writer)
{
var types = new[]
{
@ -718,7 +725,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(element, "upnp", type.ToLower(), actor.Name, NS_UPNP);
AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP);
index++;
@ -729,9 +736,9 @@ namespace Emby.Dlna.Didl
}
}
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlElement element, Filter filter)
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{
AddCommonFields(item, itemStubType, context, element, filter);
AddCommonFields(item, itemStubType, context, writer, filter);
var audio = item as Audio;
@ -739,17 +746,17 @@ namespace Emby.Dlna.Didl
{
foreach (var artist in audio.Artists)
{
AddValue(element, "upnp", "artist", artist, NS_UPNP);
AddValue(writer, "upnp", "artist", artist, NS_UPNP);
}
if (!string.IsNullOrEmpty(audio.Album))
{
AddValue(element, "upnp", "album", audio.Album, NS_UPNP);
AddValue(writer, "upnp", "album", audio.Album, NS_UPNP);
}
foreach (var artist in audio.AlbumArtists)
{
AddAlbumArtist(element, artist);
AddAlbumArtist(writer, artist);
}
}
@ -759,12 +766,12 @@ namespace Emby.Dlna.Didl
{
foreach (var artist in album.AlbumArtists)
{
AddAlbumArtist(element, artist);
AddValue(element, "upnp", "artist", artist, NS_UPNP);
AddAlbumArtist(writer, artist);
AddValue(writer, "upnp", "artist", artist, NS_UPNP);
}
foreach (var artist in album.Artists)
{
AddValue(element, "upnp", "artist", artist, NS_UPNP);
AddValue(writer, "upnp", "artist", artist, NS_UPNP);
}
}
@ -774,37 +781,37 @@ namespace Emby.Dlna.Didl
{
foreach (var artist in musicVideo.Artists)
{
AddValue(element, "upnp", "artist", artist, NS_UPNP);
AddAlbumArtist(element, artist);
AddValue(writer, "upnp", "artist", artist, NS_UPNP);
AddAlbumArtist(writer, artist);
}
if (!string.IsNullOrEmpty(musicVideo.Album))
{
AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP);
AddValue(writer, "upnp", "album", musicVideo.Album, NS_UPNP);
}
}
if (item.IndexNumber.HasValue)
{
AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
if (item is Episode)
{
AddValue(element, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
}
}
}
private void AddAlbumArtist(XmlElement elem, string name)
private void AddAlbumArtist(XmlWriter writer, string name)
{
try
{
var newNode = elem.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP);
newNode.InnerText = name;
writer.WriteStartElement("upnp", "artist", NS_UPNP);
writer.WriteAttributeString("role", "AlbumArtist");
newNode.SetAttribute("role", "AlbumArtist");
writer.WriteString(name);
elem.AppendChild(newNode);
writer.WriteEndElement();
}
catch (XmlException)
{
@ -812,13 +819,11 @@ namespace Emby.Dlna.Didl
}
}
private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri)
private void AddValue(XmlWriter writer, string prefix, string name, string value, string namespaceUri)
{
try
{
var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri);
date.InnerText = value;
elem.AppendChild(date);
writer.WriteElementString(prefix, name, namespaceUri, value);
}
catch (XmlException)
{
@ -826,11 +831,11 @@ namespace Emby.Dlna.Didl
}
}
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlElement element)
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
{
if (stubType.HasValue && stubType.Value == StubType.People)
{
AddEmbeddedImageAsCover("people", element);
AddEmbeddedImageAsCover("people", writer);
return;
}
@ -860,8 +865,6 @@ namespace Emby.Dlna.Didl
return;
}
var result = element.OwnerDocument;
var playbackPercentage = 0;
var unplayedCount = 0;
@ -891,18 +894,14 @@ namespace Emby.Dlna.Didl
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, playbackPercentage, unplayedCount, "jpg");
var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
profile.InnerText = _profile.AlbumArtPn;
icon.SetAttributeNode(profile);
icon.InnerText = albumartUrlInfo.Url;
element.AppendChild(icon);
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.Url);
writer.WriteEndElement();
// TOOD: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, playbackPercentage, unplayedCount, "jpg");
icon = result.CreateElement("upnp", "icon", NS_UPNP);
icon.InnerText = iconUrlInfo.Url;
element.AppendChild(icon);
writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url);
if (!_profile.EnableAlbumArtInDidl)
{
@ -916,36 +915,30 @@ namespace Emby.Dlna.Didl
}
}
AddImageResElement(item, element, 160, 160, playbackPercentage, unplayedCount, "jpg", "JPEG_TN");
AddImageResElement(item, writer, 160, 160, playbackPercentage, unplayedCount, "jpg", "JPEG_TN");
if (!_profile.EnableSingleAlbumArtLimit)
{
AddImageResElement(item, element, 4096, 4096, playbackPercentage, unplayedCount, "jpg", "JPEG_LRG");
AddImageResElement(item, element, 1024, 768, playbackPercentage, unplayedCount, "jpg", "JPEG_MED");
AddImageResElement(item, element, 640, 480, playbackPercentage, unplayedCount, "jpg", "JPEG_SM");
AddImageResElement(item, element, 4096, 4096, playbackPercentage, unplayedCount, "png", "PNG_LRG");
AddImageResElement(item, element, 160, 160, playbackPercentage, unplayedCount, "png", "PNG_TN");
AddImageResElement(item, writer, 4096, 4096, playbackPercentage, unplayedCount, "jpg", "JPEG_LRG");
AddImageResElement(item, writer, 1024, 768, playbackPercentage, unplayedCount, "jpg", "JPEG_MED");
AddImageResElement(item, writer, 640, 480, playbackPercentage, unplayedCount, "jpg", "JPEG_SM");
AddImageResElement(item, writer, 4096, 4096, playbackPercentage, unplayedCount, "png", "PNG_LRG");
AddImageResElement(item, writer, 160, 160, playbackPercentage, unplayedCount, "png", "PNG_TN");
}
}
private void AddEmbeddedImageAsCover(string name, XmlElement element)
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
{
var result = element.OwnerDocument;
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg");
writer.WriteEndElement();
var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
profile.InnerText = _profile.AlbumArtPn;
icon.SetAttributeNode(profile);
icon.InnerText = _serverAddress + "/Dlna/icons/people480.jpg";
element.AppendChild(icon);
icon = result.CreateElement("upnp", "icon", NS_UPNP);
icon.InnerText = _serverAddress + "/Dlna/icons/people48.jpg";
element.AppendChild(icon);
writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg");
}
private void AddImageResElement(BaseItem item,
XmlElement element,
XmlWriter writer,
int maxWidth,
int maxHeight,
int playbackPercentage,
@ -960,13 +953,9 @@ namespace Emby.Dlna.Didl
return;
}
var result = element.OwnerDocument;
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, playbackPercentage, unplayedCount, format);
var res = result.CreateElement(string.Empty, "res", NS_DIDL);
res.InnerText = albumartUrlInfo.Url;
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var width = albumartUrlInfo.Width;
var height = albumartUrlInfo.Height;
@ -974,7 +963,7 @@ namespace Emby.Dlna.Didl
var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
res.SetAttribute("protocolInfo", String.Format(
writer.WriteAttributeString("protocolInfo", String.Format(
"http-get:*:{0}:{1}",
MimeTypes.GetMimeType("file." + format),
contentFeatures
@ -982,10 +971,12 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue)
{
res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width.Value, height.Value));
}
element.AppendChild(res);
writer.WriteString(albumartUrlInfo.Url);
writer.WriteEndElement();
}
private ImageDownloadInfo GetImageInfo(BaseItem item)

View file

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Emby.Dlna.Didl
{
public class StringWriterWithEncoding : StringWriter
{
private readonly Encoding _encoding;
public StringWriterWithEncoding()
{
}
public StringWriterWithEncoding(IFormatProvider formatProvider)
: base(formatProvider)
{
}
public StringWriterWithEncoding(StringBuilder sb)
: base(sb)
{
}
public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider)
: base(sb, formatProvider)
{
}
public StringWriterWithEncoding(Encoding encoding)
{
_encoding = encoding;
}
public StringWriterWithEncoding(IFormatProvider formatProvider, Encoding encoding)
: base(formatProvider)
{
_encoding = encoding;
}
public StringWriterWithEncoding(StringBuilder sb, Encoding encoding)
: base(sb)
{
_encoding = encoding;
}
public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider, Encoding encoding)
: base(sb, formatProvider)
{
_encoding = encoding;
}
public override Encoding Encoding
{
get { return (null == _encoding) ? base.Encoding : _encoding; }
}
}
}

View file

@ -16,12 +16,8 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
#if NETSTANDARD1_6
using System.Reflection;
#endif
using MediaBrowser.Model.Reflection;
namespace Emby.Dlna
{
@ -33,6 +29,7 @@ namespace Emby.Dlna
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
private readonly IAssemblyInfo _assemblyInfo;
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
@ -40,7 +37,7 @@ namespace Emby.Dlna
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILogger logger,
IJsonSerializer jsonSerializer, IServerApplicationHost appHost)
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
@ -48,6 +45,7 @@ namespace Emby.Dlna
_logger = logger;
_jsonSerializer = jsonSerializer;
_appHost = appHost;
_assemblyInfo = assemblyInfo;
}
public void InitProfiles()
@ -306,7 +304,7 @@ namespace Emby.Dlna
.Where(i => i != null)
.ToList();
}
catch (DirectoryNotFoundException)
catch (IOException)
{
return new List<DeviceProfile>();
}
@ -400,17 +398,11 @@ namespace Emby.Dlna
private void ExtractSystemProfiles()
{
#if NET46
var assembly = GetType().Assembly;
var namespaceName = GetType().Namespace + ".Profiles.Json.";
#elif NETSTANDARD1_6
var assembly = GetType().GetTypeInfo().Assembly;
var namespaceName = GetType().GetTypeInfo().Namespace + ".Profiles.Json.";
#endif
var systemProfilesPath = SystemProfilesPath;
foreach (var name in assembly.GetManifestResourceNames()
foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType())
.Where(i => i.StartsWith(namespaceName))
.ToList())
{
@ -418,9 +410,9 @@ namespace Emby.Dlna
var path = Path.Combine(systemProfilesPath, filename);
using (var stream = assembly.GetManifestResourceStream(name))
using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name))
{
var fileInfo = new FileInfo(path);
var fileInfo = _fileSystem.GetFileInfo(path);
if (!fileInfo.Exists || fileInfo.Length != stream.Length)
{
@ -512,7 +504,7 @@ namespace Emby.Dlna
try
{
File.Delete(Path.ChangeExtension(path, ".xml"));
_fileSystem.DeleteFile(Path.ChangeExtension(path, ".xml"));
}
catch
{
@ -562,20 +554,11 @@ namespace Emby.Dlna
var resource = GetType().Namespace + ".Images." + filename.ToLower();
#if NET46
return new ImageStream
{
Format = format,
Stream = GetType().Assembly.GetManifestResourceStream(resource)
Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource)
};
#elif NETSTANDARD1_6
return new ImageStream
{
Format = format,
Stream = GetType().GetTypeInfo().Assembly.GetManifestResourceStream(resource)
};
#endif
throw new NotImplementedException();
}
}

202
Emby.Dlna/Emby.Dlna.csproj Normal file
View file

@ -0,0 +1,202 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Emby.Dlna</RootNamespace>
<AssemblyName>Emby.Dlna</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<EmbeddedResource Include="Profiles\Json\BubbleUPnp.json" />
<EmbeddedResource Include="Profiles\Json\Default.json" />
<EmbeddedResource Include="Profiles\Json\Denon AVR.json" />
<EmbeddedResource Include="Profiles\Json\DirecTV HD-DVR.json" />
<EmbeddedResource Include="Profiles\Json\Dish Hopper-Joey.json" />
<EmbeddedResource Include="Profiles\Json\foobar2000.json" />
<EmbeddedResource Include="Profiles\Json\Kodi.json" />
<EmbeddedResource Include="Profiles\Json\LG Smart TV.json" />
<EmbeddedResource Include="Profiles\Json\Linksys DMA2100.json" />
<EmbeddedResource Include="Profiles\Json\MediaMonkey.json" />
<EmbeddedResource Include="Profiles\Json\Panasonic Viera.json" />
<EmbeddedResource Include="Profiles\Json\Popcorn Hour.json" />
<EmbeddedResource Include="Profiles\Json\Samsung Smart TV.json" />
<EmbeddedResource Include="Profiles\Json\Sony Blu-ray Player 2013.json" />
<EmbeddedResource Include="Profiles\Json\Sony Blu-ray Player 2014.json" />
<EmbeddedResource Include="Profiles\Json\Sony Blu-ray Player 2015.json" />
<EmbeddedResource Include="Profiles\Json\Sony Blu-ray Player 2016.json" />
<EmbeddedResource Include="Profiles\Json\Sony Blu-ray Player.json" />
<EmbeddedResource Include="Profiles\Json\Sony Bravia %282010%29.json" />
<EmbeddedResource Include="Profiles\Json\Sony Bravia %282011%29.json" />
<EmbeddedResource Include="Profiles\Json\Sony Bravia %282012%29.json" />
<EmbeddedResource Include="Profiles\Json\Sony Bravia %282013%29.json" />
<EmbeddedResource Include="Profiles\Json\Sony Bravia %282014%29.json" />
<EmbeddedResource Include="Profiles\Json\Sony PlayStation 3.json" />
<EmbeddedResource Include="Profiles\Json\Sony PlayStation 4.json" />
<EmbeddedResource Include="Profiles\Json\Vlc.json" />
<EmbeddedResource Include="Profiles\Json\WDTV Live.json" />
<EmbeddedResource Include="Profiles\Json\Xbox 360.json" />
<EmbeddedResource Include="Profiles\Json\Xbox One.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Common\Argument.cs" />
<Compile Include="Common\DeviceIcon.cs" />
<Compile Include="Common\DeviceService.cs" />
<Compile Include="Common\ServiceAction.cs" />
<Compile Include="Common\StateVariable.cs" />
<Compile Include="ConfigurationExtension.cs" />
<Compile Include="ConnectionManager\ConnectionManager.cs" />
<Compile Include="ConnectionManager\ConnectionManagerXmlBuilder.cs" />
<Compile Include="ConnectionManager\ControlHandler.cs" />
<Compile Include="ConnectionManager\ServiceActionListBuilder.cs" />
<Compile Include="ContentDirectory\ContentDirectory.cs" />
<Compile Include="ContentDirectory\ContentDirectoryBrowser.cs" />
<Compile Include="ContentDirectory\ContentDirectoryXmlBuilder.cs" />
<Compile Include="ContentDirectory\ControlHandler.cs" />
<Compile Include="ContentDirectory\ServiceActionListBuilder.cs" />
<Compile Include="Didl\DidlBuilder.cs" />
<Compile Include="Didl\Filter.cs" />
<Compile Include="Didl\StringWriterWithEncoding.cs" />
<Compile Include="DlnaManager.cs" />
<Compile Include="Eventing\EventManager.cs" />
<Compile Include="Eventing\EventSubscription.cs" />
<Compile Include="Main\DlnaEntryPoint.cs" />
<Compile Include="MediaReceiverRegistrar\ControlHandler.cs" />
<Compile Include="MediaReceiverRegistrar\MediaReceiverRegistrar.cs" />
<Compile Include="MediaReceiverRegistrar\MediaReceiverRegistrarXmlBuilder.cs" />
<Compile Include="MediaReceiverRegistrar\ServiceActionListBuilder.cs" />
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
<Compile Include="PlayTo\Device.cs" />
<Compile Include="PlayTo\DeviceInfo.cs" />
<Compile Include="PlayTo\PlaybackProgressEventArgs.cs" />
<Compile Include="PlayTo\PlaybackStartEventArgs.cs" />
<Compile Include="PlayTo\PlaybackStoppedEventArgs.cs" />
<Compile Include="PlayTo\PlaylistItem.cs" />
<Compile Include="PlayTo\PlaylistItemFactory.cs" />
<Compile Include="PlayTo\PlayToController.cs" />
<Compile Include="PlayTo\PlayToManager.cs" />
<Compile Include="PlayTo\SsdpHttpClient.cs" />
<Compile Include="PlayTo\TransportCommands.cs" />
<Compile Include="PlayTo\TRANSPORTSTATE.cs" />
<Compile Include="PlayTo\TransportStateEventArgs.cs" />
<Compile Include="PlayTo\uBaseObject.cs" />
<Compile Include="PlayTo\uParser.cs" />
<Compile Include="PlayTo\uParserObject.cs" />
<Compile Include="PlayTo\UpnpContainer.cs" />
<Compile Include="PlayTo\uPnpNamespaces.cs" />
<Compile Include="ProfileSerialization\CodecProfile.cs" />
<Compile Include="ProfileSerialization\ContainerProfile.cs" />
<Compile Include="ProfileSerialization\DeviceProfile.cs" />
<Compile Include="ProfileSerialization\DirectPlayProfile.cs" />
<Compile Include="ProfileSerialization\HttpHeaderInfo.cs" />
<Compile Include="ProfileSerialization\ProfileCondition.cs" />
<Compile Include="ProfileSerialization\ResponseProfile.cs" />
<Compile Include="ProfileSerialization\SubtitleProfile.cs" />
<Compile Include="ProfileSerialization\TranscodingProfile.cs" />
<Compile Include="ProfileSerialization\XmlAttribute.cs" />
<Compile Include="Profiles\BubbleUpnpProfile.cs" />
<Compile Include="Profiles\DefaultProfile.cs" />
<Compile Include="Profiles\DenonAvrProfile.cs" />
<Compile Include="Profiles\DirectTvProfile.cs" />
<Compile Include="Profiles\DishHopperJoeyProfile.cs" />
<Compile Include="Profiles\Foobar2000Profile.cs" />
<Compile Include="Profiles\KodiProfile.cs" />
<Compile Include="Profiles\LgTvProfile.cs" />
<Compile Include="Profiles\LinksysDMA2100Profile.cs" />
<Compile Include="Profiles\MediaMonkeyProfile.cs" />
<Compile Include="Profiles\PanasonicVieraProfile.cs" />
<Compile Include="Profiles\PopcornHourProfile.cs" />
<Compile Include="Profiles\SamsungSmartTvProfile.cs" />
<Compile Include="Profiles\SonyBlurayPlayer2013.cs" />
<Compile Include="Profiles\SonyBlurayPlayer2014.cs" />
<Compile Include="Profiles\SonyBlurayPlayer2015.cs" />
<Compile Include="Profiles\SonyBlurayPlayer2016.cs" />
<Compile Include="Profiles\SonyBlurayPlayerProfile.cs" />
<Compile Include="Profiles\SonyBravia2010Profile.cs" />
<Compile Include="Profiles\SonyBravia2011Profile.cs" />
<Compile Include="Profiles\SonyBravia2012Profile.cs" />
<Compile Include="Profiles\SonyBravia2013Profile.cs" />
<Compile Include="Profiles\SonyBravia2014Profile.cs" />
<Compile Include="Profiles\SonyPs3Profile.cs" />
<Compile Include="Profiles\SonyPs4Profile.cs" />
<Compile Include="Profiles\VlcProfile.cs" />
<Compile Include="Profiles\WdtvLiveProfile.cs" />
<Compile Include="Profiles\Xbox360Profile.cs" />
<Compile Include="Profiles\XboxOneProfile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Server\DescriptionXmlBuilder.cs" />
<Compile Include="Server\Headers.cs" />
<Compile Include="Server\UpnpDevice.cs" />
<Compile Include="Service\BaseControlHandler.cs" />
<Compile Include="Service\BaseService.cs" />
<Compile Include="Service\ControlErrorHandler.cs" />
<Compile Include="Service\ServiceXmlBuilder.cs" />
<Compile Include="Ssdp\DeviceDiscovery.cs" />
<Compile Include="Ssdp\Extensions.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Images\logo120.jpg" />
<EmbeddedResource Include="Images\logo120.png" />
<EmbeddedResource Include="Images\logo240.jpg" />
<EmbeddedResource Include="Images\logo240.png" />
<EmbeddedResource Include="Images\logo48.jpg" />
<EmbeddedResource Include="Images\logo48.png" />
<EmbeddedResource Include="Images\people48.jpg" />
<EmbeddedResource Include="Images\people48.png" />
<EmbeddedResource Include="Images\people480.jpg" />
<EmbeddedResource Include="Images\people480.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
<Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
<Name>MediaBrowser.Controller</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
<ProjectReference Include="..\RSSDP\RSSDP.csproj">
<Project>{21002819-c39a-4d3e-be83-2a276a77fb1f}</Project>
<Name>RSSDP</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>f40e364d-01d9-4bbf-b82c-5d6c55e0a1f5</ProjectGuid>
<RootNamespace>Emby.Dlna</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -19,6 +19,8 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Threading;
using Rssdp;
using Rssdp.Infrastructure;
@ -29,7 +31,6 @@ namespace Emby.Dlna.Main
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _network;
private PlayToManager _manager;
private readonly ISessionManager _sessionManager;
@ -49,10 +50,13 @@ namespace Emby.Dlna.Main
private bool _dlnaServerStarted;
private SsdpDevicePublisher _Publisher;
private readonly ITimerFactory _timerFactory;
private readonly ISocketFactory _socketFactory;
public DlnaEntryPoint(IServerConfigurationManager config,
ILogManager logManager,
IServerApplicationHost appHost,
INetworkManager network,
ISessionManager sessionManager,
IHttpClient httpClient,
ILibraryManager libraryManager,
@ -62,11 +66,10 @@ namespace Emby.Dlna.Main
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory)
{
_config = config;
_appHost = appHost;
_network = network;
_sessionManager = sessionManager;
_httpClient = httpClient;
_libraryManager = libraryManager;
@ -78,6 +81,8 @@ namespace Emby.Dlna.Main
_mediaSourceManager = mediaSourceManager;
_deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder;
_socketFactory = socketFactory;
_timerFactory = timerFactory;
_logger = logManager.GetLogger("Dlna");
}
@ -164,7 +169,7 @@ namespace Emby.Dlna.Main
private void StartPublishing()
{
SsdpDevicePublisherBase.LogFunction = LogMessage;
_Publisher = new SsdpDevicePublisher();
_Publisher = new SsdpDevicePublisher(_socketFactory, _timerFactory, "Windows", "10");
}
private void StartDeviceDiscovery()
@ -328,7 +333,8 @@ namespace Emby.Dlna.Main
_userDataManager,
_localization,
_mediaSourceManager,
_mediaEncoder);
_mediaEncoder,
_timerFactory);
_manager.Start();
}

View file

@ -5,15 +5,12 @@ using Emby.Dlna.Service;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Xml;
namespace Emby.Dlna.MediaReceiverRegistrar
{
public class ControlHandler : BaseControlHandler
{
public ControlHandler(IServerConfigurationManager config, ILogger logger) : base(config, logger)
{
}
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
{
if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
@ -39,5 +36,9 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{ "Result", "1" }
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
{
}
}
}

View file

@ -5,17 +5,20 @@ using Emby.Dlna.Service;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Xml;
namespace Emby.Dlna.MediaReceiverRegistrar
{
public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar, IDisposable
{
private readonly IServerConfigurationManager _config;
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
: base(logger, httpClient)
{
_config = config;
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
public string GetServiceXml(IDictionary<string, string> headers)
@ -27,7 +30,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{
return new ControlHandler(
_config,
Logger)
Logger, XmlReaderSettingsFactory)
.ProcessControlRequest(request);
}

View file

@ -14,6 +14,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Emby.Dlna.Server;
using MediaBrowser.Model.Threading;
namespace Emby.Dlna.PlayTo
{
@ -24,7 +25,7 @@ namespace Emby.Dlna.PlayTo
#region Fields & Properties
private Timer _timer;
private ITimer _timer;
public DeviceInfo Properties { get; set; }
@ -96,12 +97,15 @@ namespace Emby.Dlna.PlayTo
public DateTime DateLastActivity { get; private set; }
public Action OnDeviceUnavailable { get; set; }
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
private readonly ITimerFactory _timerFactory;
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config, ITimerFactory timerFactory)
{
Properties = deviceProperties;
_httpClient = httpClient;
_logger = logger;
_config = config;
_timerFactory = timerFactory;
}
private int GetPlaybackTimerIntervalMs()
@ -116,7 +120,7 @@ namespace Emby.Dlna.PlayTo
public void Start()
{
_timer = new Timer(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
_timer = _timerFactory.Create(TimerCallback, null, GetPlaybackTimerIntervalMs(), GetInactiveTimerIntervalMs());
_timerActive = false;
}
@ -830,7 +834,7 @@ namespace Emby.Dlna.PlayTo
set;
}
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger)
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, ITimerFactory timerFactory)
{
var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
@ -922,7 +926,7 @@ namespace Emby.Dlna.PlayTo
}
}
var device = new Device(deviceProperties, httpClient, logger, config);
var device = new Device(deviceProperties, httpClient, logger, config, timerFactory);
if (isRenderer)
{

View file

@ -11,12 +11,12 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Threading;
namespace Emby.Dlna.PlayTo
{
@ -38,11 +38,12 @@ namespace Emby.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly ITimerFactory _timerFactory;
private readonly List<string> _nonRendererUrls = new List<string>();
private DateTime _lastRendererClear;
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory)
{
_logger = logger;
_sessionManager = sessionManager;
@ -58,6 +59,7 @@ namespace Emby.Dlna.PlayTo
_localization = localization;
_mediaSourceManager = mediaSourceManager;
_mediaEncoder = mediaEncoder;
_timerFactory = timerFactory;
}
public void Start()
@ -107,7 +109,7 @@ namespace Emby.Dlna.PlayTo
var uri = info.Location;
_logger.Debug("Attempting to create PlayToController from location {0}", location);
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory).ConfigureAwait(false);
if (device.RendererCommands == null)
{

View file

@ -1,19 +1,30 @@
using System.Reflection;
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Emby.Dlna2")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Emby.Dlna")]
[assembly: AssemblyProduct("Emby.Dlna2")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f40e364d-01d9-4bbf-b82c-5d6c55e0a1f5")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -216,7 +216,17 @@ namespace Emby.Dlna.Server
return "Emby - " + _serverName;
}
var characters = _serverName.Where(c => (char.IsLetterOrDigit(c) || c == '-')).ToArray();
var characterList = new List<char>();
foreach (var c in _serverName)
{
if (char.IsLetterOrDigit(c) || c == '-')
{
characterList.Add(c);
}
}
var characters = characterList.ToArray();
var serverName = new string(characters);

View file

@ -11,7 +11,7 @@ namespace Emby.Dlna.Server
{
private readonly bool _asIs = false;
private readonly Dictionary<string, string> _dict = new Dictionary<string, string>();
private readonly static Regex Validator = new Regex(@"^[a-z\d][a-z\d_.-]+$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly static Regex Validator = new Regex(@"^[a-z\d][a-z\d_.-]+$", RegexOptions.IgnoreCase);
public Headers(bool asIs)
{
@ -42,13 +42,6 @@ namespace Emby.Dlna.Server
return hb.ToString();
}
}
public Stream HeaderStream
{
get
{
return new MemoryStream(Encoding.ASCII.GetBytes(HeaderBlock));
}
}
public bool IsReadOnly
{
get

View file

@ -1,5 +1,6 @@
using System;
using System.Net;
using MediaBrowser.Model.Net;
namespace Emby.Dlna.Server
{
@ -9,9 +10,9 @@ namespace Emby.Dlna.Server
public readonly string Type;
public readonly string USN;
public readonly string Uuid;
public readonly IPAddress Address;
public readonly IpAddressInfo Address;
public UpnpDevice(string aUuid, string aType, Uri aDescriptor, IPAddress address)
public UpnpDevice(string aUuid, string aType, Uri aDescriptor, IpAddressInfo address)
{
Uuid = aUuid;
Type = aType;

View file

@ -4,9 +4,12 @@ using Emby.Dlna.Server;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Emby.Dlna.Didl;
using MediaBrowser.Model.Xml;
namespace Emby.Dlna.Service
{
@ -16,11 +19,13 @@ namespace Emby.Dlna.Service
protected readonly IServerConfigurationManager Config;
protected readonly ILogger Logger;
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
{
Config = config;
Logger = logger;
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
}
public ControlResponse ProcessControlRequest(ControlRequest request)
@ -53,47 +58,57 @@ namespace Emby.Dlna.Service
private ControlResponse ProcessControlRequestInternal(ControlRequest request)
{
var soap = new XmlDocument();
soap.LoadXml(request.InputXml);
var sparams = new Headers();
var body = soap.GetElementsByTagName("Body", NS_SOAPENV).Item(0);
ControlRequestInfo requestInfo = null;
var method = body.FirstChild;
foreach (var p in method.ChildNodes)
using (var streamReader = new StreamReader(request.InputXml))
{
var e = p as XmlElement;
if (e == null)
var readerSettings = XmlReaderSettingsFactory.Create(false);
readerSettings.CheckCharacters = false;
readerSettings.IgnoreProcessingInstructions = true;
readerSettings.IgnoreComments = true;
using (var reader = XmlReader.Create(streamReader, readerSettings))
{
continue;
requestInfo = ParseRequest(reader);
}
sparams.Add(e.LocalName, e.InnerText.Trim());
}
Logger.Debug("Received control request {0}", method.LocalName);
Logger.Debug("Received control request {0}", requestInfo.LocalName);
var result = GetResult(method.LocalName, sparams);
var result = GetResult(requestInfo.LocalName, requestInfo.Headers);
var env = new XmlDocument();
env.AppendChild(env.CreateXmlDeclaration("1.0", "utf-8", string.Empty));
var envelope = env.CreateElement("SOAP-ENV", "Envelope", NS_SOAPENV);
env.AppendChild(envelope);
envelope.SetAttribute("encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
var rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV);
env.DocumentElement.AppendChild(rbody);
var response = env.CreateElement(String.Format("u:{0}Response", method.LocalName), method.NamespaceURI);
rbody.AppendChild(response);
foreach (var i in result)
var settings = new XmlWriterSettings
{
var ri = env.CreateElement(i.Key);
ri.InnerText = i.Value;
response.AppendChild(ri);
Encoding = Encoding.UTF8,
CloseOutput = false
};
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
using (XmlWriter writer = XmlWriter.Create(builder, settings))
{
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", "Body", NS_SOAPENV);
writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
foreach (var i in result)
{
writer.WriteStartElement(i.Key);
writer.WriteString(i.Value);
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
var xml = env.OuterXml.Replace("xmlns:m=", "xmlns:u=");
var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=");
var controlResponse = new ControlResponse
{
@ -108,6 +123,102 @@ namespace Emby.Dlna.Service
return controlResponse;
}
private ControlRequestInfo ParseRequest(XmlReader reader)
{
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.LocalName)
{
case "Body":
{
using (var subReader = reader.ReadSubtree())
{
return ParseBodyTag(subReader);
}
}
default:
{
reader.Skip();
break;
}
}
}
else
{
reader.Read();
}
}
return new ControlRequestInfo();
}
private ControlRequestInfo ParseBodyTag(XmlReader reader)
{
var result = new ControlRequestInfo();
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element)
{
result.LocalName = reader.LocalName;
result.NamespaceURI = reader.NamespaceURI;
using (var subReader = reader.ReadSubtree())
{
result.Headers = ParseFirstBodyChild(subReader);
return result;
}
}
else
{
reader.Read();
}
}
return result;
}
private Headers ParseFirstBodyChild(XmlReader reader)
{
var result = new Headers();
reader.MoveToContent();
reader.Read();
// Loop through each element
while (!reader.EOF)
{
if (reader.NodeType == XmlNodeType.Element)
{
result.Add(reader.LocalName, reader.ReadElementContentAsString());
}
else
{
reader.Read();
}
}
return result;
}
private class ControlRequestInfo
{
public string LocalName;
public string NamespaceURI;
public Headers Headers = new Headers();
}
protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams);
private void LogRequest(ControlRequest request)

View file

@ -1,6 +1,9 @@
using MediaBrowser.Controller.Dlna;
using System;
using System.IO;
using System.Text;
using System.Xml;
using Emby.Dlna.Didl;
namespace Emby.Dlna.Service
{
@ -10,30 +13,41 @@ namespace Emby.Dlna.Service
public ControlResponse GetResponse(Exception ex)
{
var env = new XmlDocument();
env.AppendChild(env.CreateXmlDeclaration("1.0", "utf-8", "yes"));
var envelope = env.CreateElement("SOAP-ENV", "Envelope", NS_SOAPENV);
env.AppendChild(envelope);
envelope.SetAttribute("encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
CloseOutput = false
};
var rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV);
env.DocumentElement.AppendChild(rbody);
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
var fault = env.CreateElement("SOAP-ENV", "Fault", NS_SOAPENV);
var faultCode = env.CreateElement("faultcode");
faultCode.InnerText = "500";
fault.AppendChild(faultCode);
var faultString = env.CreateElement("faultstring");
faultString.InnerText = ex.ToString();
fault.AppendChild(faultString);
var detail = env.CreateDocumentFragment();
detail.InnerXml = "<detail><UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\"><errorCode>401</errorCode><errorDescription>Invalid Action</errorDescription></UPnPError></detail>";
fault.AppendChild(detail);
rbody.AppendChild(fault);
using (XmlWriter writer = XmlWriter.Create(builder, settings))
{
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", "Body", NS_SOAPENV);
writer.WriteStartElement("SOAP-ENV", "Fault", NS_SOAPENV);
writer.WriteElementString("faultcode", "500");
writer.WriteElementString("faultstring", ex.Message);
writer.WriteStartElement("detail");
writer.WriteRaw("<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\"><errorCode>401</errorCode><errorDescription>Invalid Action</errorDescription></UPnPError>");
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
return new ControlResponse
{
Xml = env.OuterXml,
Xml = builder.ToString(),
IsSuccessful = false
};
}

View file

@ -7,12 +7,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Threading;
using Rssdp;
namespace Emby.Dlna.Ssdp
@ -30,18 +31,23 @@ namespace Emby.Dlna.Ssdp
private SsdpDeviceLocator _DeviceLocator;
public DeviceDiscovery(ILogger logger, IServerConfigurationManager config)
private readonly ITimerFactory _timerFactory;
private readonly ISocketFactory _socketFactory;
public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, ISocketFactory socketFactory, ITimerFactory timerFactory)
{
_tokenSource = new CancellationTokenSource();
_logger = logger;
_config = config;
_socketFactory = socketFactory;
_timerFactory = timerFactory;
}
// Call this method from somewhere in your code to start the search.
public void BeginSearch()
{
_DeviceLocator = new SsdpDeviceLocator();
_DeviceLocator = new SsdpDeviceLocator(_socketFactory, _timerFactory);
// (Optional) Set the filter so we only see notifications for devices we care about
// (can be any search target value i.e device type, uuid value etc - any value that appears in the

View file

@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Xml.Linq;

View file

@ -1,57 +0,0 @@
{
"version": "1.0.0-*",
"dependencies": {
},
"frameworks": {
"net46": {
"frameworkAssemblies": {
"System.Collections": "4.0.0.0",
"System.IO": "4.0.0.0",
"System.Xml": "4.0.0.0",
"System.Xml.Linq": "4.0.0.0",
"System.Xml.Serialization": "4.0.0.0"
},
"dependencies": {
"MediaBrowser.Controller": {
"target": "project"
},
"MediaBrowser.Common": {
"target": "project"
},
"MediaBrowser.Model": {
"target": "project"
},
"RSSDP": {
"target": "project"
}
}
},
"netstandard1.6": {
"imports": "dnxcore50",
"dependencies": {
"NETStandard.Library": "1.6.0",
"MediaBrowser.Controller": {
"target": "project"
},
"MediaBrowser.Common": {
"target": "project"
},
"MediaBrowser.Model": {
"target": "project"
},
"System.Xml.XmlSerializer": "4.0.11",
"System.Xml.XDocument": "4.0.11",
"System.Xml.XmlDocument": "4.0.1",
"System.Reflection": "4.1.0",
"System.Reflection.Primitives": "4.0.1",
"System.Runtime.Loader": "4.0.0",
"RSSDP": {
"target": "project"
}
}
}
}
}

View file

@ -98,7 +98,7 @@ namespace MediaBrowser.Api.Dlna
{
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
public string UuId { get; set; }
[ApiMember(Name = "Filename", Description = "The icon filename", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Filename { get; set; }
}
@ -152,41 +152,38 @@ namespace MediaBrowser.Api.Dlna
return ResultFactory.GetResult(xml, XMLContentType);
}
public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request)
public object Post(ProcessMediaReceiverRegistrarControlRequest request)
{
var response = await PostAsync(request.RequestStream, _mediaReceiverRegistrar).ConfigureAwait(false);
var response = PostAsync(request.RequestStream, _mediaReceiverRegistrar);
return ResultFactory.GetResult(response.Xml, XMLContentType);
}
public async Task<object> Post(ProcessContentDirectoryControlRequest request)
public object Post(ProcessContentDirectoryControlRequest request)
{
var response = await PostAsync(request.RequestStream, _contentDirectory).ConfigureAwait(false);
var response = PostAsync(request.RequestStream, _contentDirectory);
return ResultFactory.GetResult(response.Xml, XMLContentType);
}
public async Task<object> Post(ProcessConnectionManagerControlRequest request)
public object Post(ProcessConnectionManagerControlRequest request)
{
var response = await PostAsync(request.RequestStream, _connectionManager).ConfigureAwait(false);
var response = PostAsync(request.RequestStream, _connectionManager);
return ResultFactory.GetResult(response.Xml, XMLContentType);
}
private async Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
{
var id = GetPathValue(2);
using (var reader = new StreamReader(requestStream))
return service.ProcessControlRequest(new ControlRequest
{
return service.ProcessControlRequest(new ControlRequest
{
Headers = Request.Headers.ToDictionary(),
InputXml = await reader.ReadToEndAsync().ConfigureAwait(false),
TargetServerUuId = id,
RequestedUrl = Request.AbsoluteUri
});
}
Headers = Request.Headers.ToDictionary(),
InputXml = requestStream,
TargetServerUuId = id,
RequestedUrl = Request.AbsoluteUri
});
}
public object Get(GetIcon request)

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.IO;
namespace MediaBrowser.Controller.Dlna
{
@ -6,7 +7,7 @@ namespace MediaBrowser.Controller.Dlna
{
public IDictionary<string, string> Headers { get; set; }
public string InputXml { get; set; }
public Stream InputXml { get; set; }
public string TargetServerUuId { get; set; }

View file

@ -137,6 +137,10 @@
<Compile Include="Dto\MetadataEditorInfo.cs" />
<Compile Include="Dto\NameIdPair.cs" />
<Compile Include="Dto\NameValuePair.cs" />
<Compile Include="Net\IpEndPointInfo.cs" />
<Compile Include="Net\ISocketFactory.cs" />
<Compile Include="Net\IUdpSocket.cs" />
<Compile Include="Net\ReceivedUdpData.cs" />
<Compile Include="TextEncoding\IEncoding.cs" />
<Compile Include="Extensions\LinqExtensions.cs" />
<Compile Include="FileOrganization\SmartMatchInfo.cs" />

View file

@ -1,15 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rssdp.Infrastructure

namespace MediaBrowser.Model.Net
{
/// <summary>
/// Implemented by components that can create a platform specific UDP socket implementation, and wrap it in the cross platform <see cref="IUdpSocket"/> interface.
/// </summary>
public interface ISocketFactory
/// <summary>
/// Implemented by components that can create a platform specific UDP socket implementation, and wrap it in the cross platform <see cref="IUdpSocket"/> interface.
/// </summary>
public interface ISocketFactory
{
/// <summary>
@ -26,8 +21,6 @@ namespace Rssdp.Infrastructure
/// <param name="multicastTimeToLive">The multicast time to live value. Actually a maximum number of network hops for UDP packets.</param>
/// <param name="localPort">The local port to bind to.</param>
/// <returns>A <see cref="IUdpSocket"/> implementation.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip", Justification="IP is a well known and understood abbreviation and the full name is excessive.")]
IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort);
}
}

View file

@ -4,25 +4,24 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rssdp.Infrastructure
namespace MediaBrowser.Model.Net
{
/// <summary>
/// Provides a common interface across platforms for UDP sockets used by this SSDP implementation.
/// </summary>
public interface IUdpSocket : IDisposable
/// <summary>
/// Provides a common interface across platforms for UDP sockets used by this SSDP implementation.
/// </summary>
public interface IUdpSocket : IDisposable
{
/// <summary>
/// Waits for and returns the next UDP message sent to this socket (uni or multicast).
/// </summary>
/// <returns></returns>
System.Threading.Tasks.Task<ReceivedUdpData> ReceiveAsync();
Task<ReceivedUdpData> ReceiveAsync();
/// <summary>
/// Sends a UDP message to a particular end point (uni or multicast).
/// </summary>
/// <param name="messageData">The data to send.</param>
/// <param name="endPoint">The <see cref="UdpEndPoint"/> providing the address and port to send to.</param>
Task SendTo(byte[] messageData, UdpEndPoint endPoint);
/// <param name="endPoint">The <see cref="IpEndPointInfo"/> providing the address and port to send to.</param>
Task SendTo(byte[] messageData, IpEndPointInfo endPoint);
}
}

View file

@ -0,0 +1,18 @@
using System;
namespace MediaBrowser.Model.Net
{
public class IpEndPointInfo
{
public IpAddressInfo IpAddress { get; set; }
public int Port { get; set; }
public override string ToString()
{
var ipAddresString = IpAddress == null ? string.Empty : IpAddress.ToString();
return ipAddresString + ":" + this.Port.ToString();
}
}
}

View file

@ -0,0 +1,24 @@

namespace MediaBrowser.Model.Net
{
/// <summary>
/// Used by the sockets wrapper to hold raw data received from a UDP socket.
/// </summary>
public sealed class ReceivedUdpData
{
/// <summary>
/// The buffer to place received data into.
/// </summary>
public byte[] Buffer { get; set; }
/// <summary>
/// The number of bytes received.
/// </summary>
public int ReceivedBytes { get; set; }
/// <summary>
/// The <see cref="IpEndPointInfo"/> the data was received from.
/// </summary>
public IpEndPointInfo ReceivedFrom { get; set; }
}
}

View file

@ -6,5 +6,6 @@ namespace MediaBrowser.Model.Reflection
public interface IAssemblyInfo
{
Stream GetManifestResourceStream(Type type, string resource);
string[] GetManifestResourceNames(Type type);
}
}

View file

@ -558,7 +558,8 @@ namespace MediaBrowser.Server.Startup.Common
RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer(FileSystemManager, textEncoding));
RegisterSingleInstance<IXmlReaderSettingsFactory>(new XmlReaderSettingsFactory());
RegisterSingleInstance<IAssemblyInfo>(new AssemblyInfo());
IAssemblyInfo assemblyInfo = new AssemblyInfo();
RegisterSingleInstance<IAssemblyInfo>(assemblyInfo);
UserDataManager = new UserDataManager(LogManager, ServerConfigurationManager);
RegisterSingleInstance(UserDataManager);
@ -648,10 +649,10 @@ namespace MediaBrowser.Server.Startup.Common
SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager, TimerFactory);
RegisterSingleInstance(SessionManager);
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("Dlna"), JsonSerializer, this);
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("Dlna"), JsonSerializer, this, assemblyInfo);
RegisterSingleInstance<IDlnaManager>(dlnaManager);
var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient);
var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient, new XmlReaderSettingsFactory());
RegisterSingleInstance<IConnectionManager>(connectionManager);
CollectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager"), ProviderManager);
@ -666,10 +667,10 @@ namespace MediaBrowser.Server.Startup.Common
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
RegisterSingleInstance(UserViewManager);
var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, LocalizationManager, ChannelManager, MediaSourceManager, UserViewManager, () => MediaEncoder);
var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, LocalizationManager, ChannelManager, MediaSourceManager, UserViewManager, () => MediaEncoder, new XmlReaderSettingsFactory());
RegisterSingleInstance<IContentDirectory>(contentDirectory);
var mediaRegistrar = new MediaReceiverRegistrar(LogManager.GetLogger("MediaReceiverRegistrar"), HttpClient, ServerConfigurationManager);
var mediaRegistrar = new MediaReceiverRegistrar(LogManager.GetLogger("MediaReceiverRegistrar"), HttpClient, ServerConfigurationManager, new XmlReaderSettingsFactory());
RegisterSingleInstance<IMediaReceiverRegistrar>(mediaRegistrar);
NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
@ -678,7 +679,7 @@ namespace MediaBrowser.Server.Startup.Common
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
RegisterSingleInstance(SubtitleManager);
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager));
RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, SocketFactory, TimerFactory));
ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository);
RegisterSingleInstance(ChapterManager);

View file

@ -35,9 +35,6 @@
<Reference Include="Emby.Common.Implementations">
<HintPath>..\ThirdParty\emby\Emby.Common.Implementations.dll</HintPath>
</Reference>
<Reference Include="Emby.Dlna">
<HintPath>..\ThirdParty\emby\Emby.Dlna.dll</HintPath>
</Reference>
<Reference Include="MediaBrowser.Naming, Version=1.0.6151.30291, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.59\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private>
@ -50,9 +47,6 @@
<HintPath>..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="RSSDP">
<HintPath>..\ThirdParty\emby\RSSDP.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Interfaces, Version=4.0.0.0, Culture=neutral, PublicKeyToken=e06fbc6124f57c43, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
@ -99,6 +93,10 @@
<Compile Include="UnhandledExceptionWriter.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj">
<Project>{805844ab-e92f-45e6-9d99-4f6d48d129a5}</Project>
<Name>Emby.Dlna</Name>
</ProjectReference>
<ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj">
<Project>{08fff49b-f175-4807-a2b5-73b0ebd9f716}</Project>
<Name>Emby.Drawing</Name>
@ -155,6 +153,10 @@
<Project>{4a4402d4-e910-443b-b8fc-2c18286a2ca0}</Project>
<Name>OpenSubtitlesHandler</Name>
</ProjectReference>
<ProjectReference Include="..\RSSDP\RSSDP.csproj">
<Project>{21002819-c39a-4d3e-be83-2a276a77fb1f}</Project>
<Name>RSSDP</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />

View file

@ -64,14 +64,14 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Emby.Common.Implementations
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RSSDP", "RSSDP\RSSDP.xproj", "{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.xproj", "{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Mono.Nat", "Mono.Nat\Mono.Nat.xproj", "{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -566,36 +566,6 @@ Global
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x64.Build.0 = Release|Any CPU
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x86.ActiveCfg = Release|Any CPU
{88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|x86.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Win32.ActiveCfg = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|Win32.Build.0 = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|x64.ActiveCfg = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|x64.Build.0 = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|x86.ActiveCfg = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Debug|x86.Build.0 = Debug|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Any CPU.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Win32.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|Win32.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|x64.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|x64.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|x86.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release Mono|x86.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Any CPU.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Win32.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|Win32.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|x64.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|x64.Build.0 = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|x86.ActiveCfg = Release|Any CPU
{C227ADB7-E256-4E70-A8B9-22B9E0CF4F55}.Release|x86.Build.0 = Release|Any CPU
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -626,36 +596,6 @@ Global
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x64.Build.0 = Release|Any CPU
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x86.ActiveCfg = Release|Any CPU
{E383961B-9356-4D5D-8233-9A1079D03055}.Release|x86.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|Win32.Build.0 = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|x64.ActiveCfg = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|x64.Build.0 = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|x86.ActiveCfg = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Debug|x86.Build.0 = Debug|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Any CPU.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Win32.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|Win32.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|x64.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|x64.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|x86.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release Mono|x86.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Any CPU.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Win32.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|Win32.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|x64.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|x64.Build.0 = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|x86.ActiveCfg = Release|Any CPU
{F40E364D-01D9-4BBF-B82C-5D6C55E0A1F5}.Release|x86.Build.0 = Release|Any CPU
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -686,6 +626,66 @@ Global
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x64.Build.0 = Release|Any CPU
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x86.ActiveCfg = Release|Any CPU
{4ACAB6A2-AC9A-4B50-BAEC-1FE4A1F3B8BC}.Release|x86.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Win32.ActiveCfg = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Win32.Build.0 = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|x64.ActiveCfg = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|x64.Build.0 = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|x86.ActiveCfg = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|x86.Build.0 = Debug|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Any CPU.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Win32.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|Win32.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|x64.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|x64.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|x86.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release Mono|x86.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Win32.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Win32.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x64.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x64.Build.0 = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x86.ActiveCfg = Release|Any CPU
{21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|x86.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Win32.Build.0 = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|x64.Build.0 = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|x86.ActiveCfg = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|x86.Build.0 = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Any CPU.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Win32.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|Win32.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|x64.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|x64.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|x86.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release Mono|x86.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Win32.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Win32.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x64.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x64.Build.0 = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x86.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
namespace Rssdp.Infrastructure
{
@ -44,8 +45,8 @@ namespace Rssdp.Infrastructure
/// Sends a message to a particular address (uni or multicast) and port.
/// </summary>
/// <param name="messageData">A byte array containing the data to send.</param>
/// <param name="destination">A <see cref="UdpEndPoint"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
Task SendMessage(byte[] messageData, UdpEndPoint destination);
/// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
Task SendMessage(byte[] messageData, IpEndPointInfo destination);
/// <summary>
/// Sends a message to the SSDP multicast address and port.

View file

@ -1,19 +1,30 @@
using System.Reflection;
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RSSDP2")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RSSDP")]
[assembly: AssemblyProduct("RSSDP2")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c227adb7-e256-4e70-a8b9-22b9e0cf4f55")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -1,23 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{67F9D3A8-F71E-4428-913F-C37AE82CDB24}</ProjectGuid>
<ProjectGuid>{21002819-C39A-4D3E-BE83-2A276A77FB1F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Rssdp</RootNamespace>
<AssemblyName>Rssdp.Portable</AssemblyName>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile44</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<RootNamespace>RSSDP</RootNamespace>
<AssemblyName>RSSDP</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>1c5b2aa5</NuGetPackageImportStamp>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -27,79 +24,57 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<RunCodeAnalysis>true</RunCodeAnalysis>
<CodeAnalysisRuleSet>..\RssdpRuleset.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\Debug\Rssdp.Portable.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\lib\portable-net45+win+wpa81+wp80\</OutputPath>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>..\RssdpRuleset.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>..\lib\portable-net45+win+wpa81+wp80\Rssdp.Portable.XML</DocumentationFile>
<RunCodeAnalysis>true</RunCodeAnalysis>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\AssemblyInfoCommon.cs">
<Link>Properties\AssemblyInfoCommon.cs</Link>
</Compile>
<Compile Include="CustomHttpHeaders.cs" />
<Compile Include="DeviceAvailableEventArgs.cs" />
<Compile Include="DeviceEventArgs.cs" />
<Compile Include="DeviceUnavailableEventArgs.cs" />
<Compile Include="DiscoveredSsdpDevice.cs" />
<Compile Include="DisposableManagedObjectBase.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="HttpParserBase.cs" />
<Compile Include="HttpRequestParser.cs" />
<Compile Include="HttpResponseParser.cs" />
<Compile Include="IEnumerableExtensions.cs" />
<Compile Include="ISsdpCommunicationsServer.cs" />
<Compile Include="ISsdpDeviceLocator.cs" />
<Compile Include="ISsdpDevicePublisher.cs" />
<Compile Include="IUPnPDeviceValidator.cs" />
<Compile Include="ReadOnlyEnumerable.cs" />
<Compile Include="SsdpDeviceExtensions.cs" />
<Compile Include="SsdpDeviceLocatorBase.cs" />
<Compile Include="DiscoveredSsdpDevice.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="HttpRequestParser.cs" />
<Compile Include="HttpResponseParser.cs" />
<Compile Include="ISocketFactory.cs" />
<Compile Include="IUdpSocket.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReceivedUdpData.cs" />
<Compile Include="ReadOnlyEnumerable.cs" />
<Compile Include="RequestReceivedEventArgs.cs" />
<Compile Include="ResponseReceivedEventArgs.cs" />
<Compile Include="SsdpCommunicationsServer.cs" />
<Compile Include="SsdpConstants.cs" />
<Compile Include="SsdpDevice.cs" />
<Compile Include="SsdpDeviceExtensions.cs" />
<Compile Include="SsdpDeviceIcon.cs" />
<Compile Include="SsdpDeviceLocator.cs" />
<Compile Include="SsdpDeviceLocatorBase.cs" />
<Compile Include="SsdpDeviceProperties.cs" />
<Compile Include="SsdpDeviceProperty.cs" />
<Compile Include="SsdpDevicePublisher.cs" />
<Compile Include="SsdpDevicePublisherBase.cs" />
<Compile Include="SsdpEmbeddedDevice.cs" />
<Compile Include="SsdpRootDevice.cs" />
<Compile Include="UdpEndPoint.cs" />
<Compile Include="UPnP10DeviceValidator.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="..\Shared\CodeAnalysisDictionary.xml">
<Link>Properties\CodeAnalysisDictionary.xml</Link>
<SubType>Designer</SubType>
</CodeAnalysisDictionary>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" />
</Target>
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>c227adb7-e256-4e70-a8b9-22b9e0cf4f55</ProjectGuid>
<RootNamespace>RSSDP</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rssdp.Infrastructure
{
/// <summary>
/// Used by the sockets wrapper to hold raw data received from a UDP socket.
/// </summary>
public sealed class ReceivedUdpData
{
/// <summary>
/// The buffer to place received data into.
/// </summary>
public byte[] Buffer { get; set; }
/// <summary>
/// The number of bytes received.
/// </summary>
public int ReceivedBytes { get; set; }
/// <summary>
/// The <see cref="UdpEndPoint"/> the data was received from.
/// </summary>
public UdpEndPoint ReceivedFrom { get; set; }
}
}

View file

@ -5,6 +5,7 @@ using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
namespace Rssdp.Infrastructure
{
@ -17,7 +18,7 @@ namespace Rssdp.Infrastructure
#region Fields
private readonly HttpRequestMessage _Message;
private readonly UdpEndPoint _ReceivedFrom;
private readonly IpEndPointInfo _ReceivedFrom;
#endregion
@ -28,7 +29,7 @@ namespace Rssdp.Infrastructure
/// </summary>
/// <param name="message">The <see cref="HttpRequestMessage"/> that was received.</param>
/// <param name="receivedFrom">A <see cref="UdpEndPoint"/> representing the sender's address (sometimes used for replies).</param>
public RequestReceivedEventArgs(HttpRequestMessage message, UdpEndPoint receivedFrom)
public RequestReceivedEventArgs(HttpRequestMessage message, IpEndPointInfo receivedFrom)
{
_Message = message;
_ReceivedFrom = receivedFrom;
@ -49,7 +50,7 @@ namespace Rssdp.Infrastructure
/// <summary>
/// The <see cref="UdpEndPoint"/> the request came from.
/// </summary>
public UdpEndPoint ReceivedFrom
public IpEndPointInfo ReceivedFrom
{
get { return _ReceivedFrom; }
}

View file

@ -5,6 +5,7 @@ using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
namespace Rssdp.Infrastructure
{
@ -17,7 +18,7 @@ namespace Rssdp.Infrastructure
#region Fields
private readonly HttpResponseMessage _Message;
private readonly UdpEndPoint _ReceivedFrom;
private readonly IpEndPointInfo _ReceivedFrom;
#endregion
@ -26,9 +27,7 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Full constructor.
/// </summary>
/// <param name="message">The <see cref="HttpResponseMessage"/> that was received.</param>
/// <param name="receivedFrom">A <see cref="UdpEndPoint"/> representing the sender's address (sometimes used for replies).</param>
public ResponseReceivedEventArgs(HttpResponseMessage message, UdpEndPoint receivedFrom)
public ResponseReceivedEventArgs(HttpResponseMessage message, IpEndPointInfo receivedFrom)
{
_Message = message;
_ReceivedFrom = receivedFrom;
@ -49,7 +48,7 @@ namespace Rssdp.Infrastructure
/// <summary>
/// The <see cref="UdpEndPoint"/> the response came from.
/// </summary>
public UdpEndPoint ReceivedFrom
public IpEndPointInfo ReceivedFrom
{
get { return _ReceivedFrom; }
}

View file

@ -1,114 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security;
using System.Text;
using Rssdp.Infrastructure;
namespace Rssdp
{
// THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS
// Be careful to check any changes compile and work for all platform projects it is shared in.
// Not entirely happy with this. Would have liked to have done something more generic/reusable,
// but that wasn't really the point so kept to YAGNI principal for now, even if the
// interfaces are a bit ugly, specific and make assumptions.
/// <summary>
/// Used by RSSDP components to create implementations of the <see cref="IUdpSocket"/> interface, to perform platform agnostic socket communications.
/// </summary>
public sealed class SocketFactory : ISocketFactory
{
private IPAddress _LocalIP;
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="localIP">A string containing the IP address of the local network adapter to bind sockets to. Null or empty string will use <see cref="IPAddress.Any"/>.</param>
public SocketFactory(string localIP)
{
if (String.IsNullOrEmpty(localIP))
_LocalIP = IPAddress.Any;
else
_LocalIP = IPAddress.Parse(localIP);
}
#region ISocketFactory Members
/// <summary>
/// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
/// </summary>
/// <param name="localPort">An integer specifying the local port to bind the socket to.</param>
/// <returns>An implementation of the <see cref="IUdpSocket"/> interface used by RSSDP components to perform socket operations.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")]
public IUdpSocket CreateUdpSocket(int localPort)
{
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
try
{
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, SsdpConstants.SsdpDefaultMulticastTimeToLive);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(SsdpConstants.MulticastLocalAdminAddress), _LocalIP));
return new UdpSocket(retVal, localPort, _LocalIP.ToString());
}
catch
{
if (retVal != null)
retVal.Dispose();
throw;
}
}
/// <summary>
/// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port.
/// </summary>
/// <param name="ipAddress">The multicast IP address to make the socket a member of.</param>
/// <param name="multicastTimeToLive">The multicast time to live value for the socket.</param>
/// <param name="localPort">The number of the local port to bind to.</param>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")]
public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
{
if (ipAddress == null) throw new ArgumentNullException("ipAddress");
if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress");
if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive");
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
#if NETSTANDARD1_3
// The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket
// See https://github.com/dotnet/corefx/pull/11509 for more details
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
{
retVal.ExclusiveAddressUse = false;
}
#else
retVal.ExclusiveAddressUse = false;
#endif
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), _LocalIP));
retVal.MulticastLoopback = true;
return new UdpSocket(retVal, localPort, _LocalIP.ToString());
}
catch
{
if (retVal != null)
retVal.Dispose();
throw;
}
}
#endregion
}
}

View file

@ -5,6 +5,7 @@ using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
namespace Rssdp.Infrastructure
{
@ -157,10 +158,10 @@ namespace Rssdp.Infrastructure
/// Sends a message to a particular address (uni or multicast) and port.
/// </summary>
/// <param name="messageData">A byte array containing the data to send.</param>
/// <param name="destination">A <see cref="UdpEndPoint"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
/// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="messageData"/> argument is null.</exception>
/// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception>
public async Task SendMessage(byte[] messageData, UdpEndPoint destination)
public async Task SendMessage(byte[] messageData, IpEndPointInfo destination)
{
if (messageData == null) throw new ArgumentNullException("messageData");
@ -188,7 +189,7 @@ namespace Rssdp.Infrastructure
// SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP.
await Repeat(SsdpConstants.UdpResendCount, TimeSpan.FromMilliseconds(100),
() => SendMessageIfSocketNotDisposed(messageData, new UdpEndPoint() { IPAddress = SsdpConstants.MulticastLocalAdminAddress, Port = SsdpConstants.MulticastPort })).ConfigureAwait(false);
() => SendMessageIfSocketNotDisposed(messageData, new IpEndPointInfo() { IpAddress = new IpAddressInfo { Address = SsdpConstants.MulticastLocalAdminAddress }, Port = SsdpConstants.MulticastPort })).ConfigureAwait(false);
}
/// <summary>
@ -254,7 +255,7 @@ namespace Rssdp.Infrastructure
#region Private Methods
private async Task SendMessageIfSocketNotDisposed(byte[] messageData, UdpEndPoint destination)
private async Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination)
{
var socket = _SendSocket;
if (socket != null)
@ -343,7 +344,7 @@ namespace Rssdp.Infrastructure
}
}
private void ProcessMessage(string data, UdpEndPoint endPoint)
private void ProcessMessage(string data, IpEndPointInfo endPoint)
{
//Responses start with the HTTP version, prefixed with HTTP/ while
//requests start with a method which can vary and might be one we haven't
@ -375,7 +376,7 @@ namespace Rssdp.Infrastructure
}
}
private void OnRequestReceived(HttpRequestMessage data, UdpEndPoint endPoint)
private void OnRequestReceived(HttpRequestMessage data, IpEndPointInfo endPoint)
{
//SSDP specification says only * is currently used but other uri's might
//be implemented in the future and should be ignored unless understood.
@ -387,7 +388,7 @@ namespace Rssdp.Infrastructure
handlers(this, new RequestReceivedEventArgs(data, endPoint));
}
private void OnResponseReceived(HttpResponseMessage data, UdpEndPoint endPoint)
private void OnResponseReceived(HttpResponseMessage data, IpEndPointInfo endPoint)
{
var handlers = this.ResponseReceived;
if (handlers != null)

View file

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Threading;
using Rssdp.Infrastructure;
namespace Rssdp
@ -19,7 +21,7 @@ namespace Rssdp
/// Default constructor. Constructs a new instance using the default <see cref="ISsdpCommunicationsServer"/> and <see cref="ISocketFactory"/> implementations for this platform.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification="Can't expose along exception paths here (exceptions should be very rare anyway, and probably fatal too) and we shouldn't dipose the items we pass to base in any other case.")]
public SsdpDeviceLocator() : base(new SsdpCommunicationsServer(new SocketFactory(null)))
public SsdpDeviceLocator(ISocketFactory socketFactory, ITimerFactory timerFacatory) : base(new SsdpCommunicationsServer(socketFactory), timerFacatory)
{
// This is not the problem you are looking for;
// Yes, this is poor man's dependency injection which some call an anti-pattern.
@ -30,14 +32,5 @@ namespace Rssdp
// There is a constructor that takes a manually injected dependency anyway, so proper DI using
// a container or whatever can be done anyway.
}
/// <summary>
/// Full constructor. Constructs a new instance using the provided <see cref="ISsdpCommunicationsServer"/> implementation.
/// </summary>
public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer)
: base(communicationsServer)
{
}
}
}

View file

@ -7,6 +7,7 @@ using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Threading;
namespace Rssdp.Infrastructure
{
@ -24,7 +25,8 @@ namespace Rssdp.Infrastructure
private IList<DiscoveredSsdpDevice> _SearchResults;
private object _SearchResultsSynchroniser;
private System.Threading.Timer _ExpireCachedDevicesTimer;
private ITimer _ExpireCachedDevicesTimer;
private ITimerFactory _timerFactory;
private const string HttpURequestMessageFormat = @"{0} * HTTP/1.1
HOST: {1}:{2}
@ -44,12 +46,12 @@ ST: {4}
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="communicationsServer">The <see cref="ISsdpCommunicationsServer"/> implementation to use for network communications.</param>
protected SsdpDeviceLocatorBase(ISsdpCommunicationsServer communicationsServer)
protected SsdpDeviceLocatorBase(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFactory)
{
if (communicationsServer == null) throw new ArgumentNullException("communicationsServer");
_CommunicationsServer = communicationsServer;
_timerFactory = timerFactory;
_CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived;
_SearchResultsSynchroniser = new object();
@ -521,7 +523,7 @@ ST: {4}
if (IsDisposed) return;
if (_ExpireCachedDevicesTimer == null)
_ExpireCachedDevicesTimer = new Timer(this.ExpireCachedDevices, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
_ExpireCachedDevicesTimer = _timerFactory.Create(this.ExpireCachedDevices, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
_ExpireCachedDevicesTimer.Change(60000, System.Threading.Timeout.Infinite);
}

View file

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Threading;
using Rssdp.Infrastructure;
namespace Rssdp
@ -24,79 +26,13 @@ namespace Rssdp
/// <para>Uses the default <see cref="ISsdpCommunicationsServer"/> implementation and network settings for Windows and the SSDP specification.</para>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No way to do this here, and we don't want to dispose it except in the (rare) case of an exception anyway.")]
public SsdpDevicePublisher()
: this(new SsdpCommunicationsServer(new SocketFactory(null)))
public SsdpDevicePublisher(ISocketFactory socketFactory, ITimerFactory timerFactory, string osName, string osVersion)
: base(new SsdpCommunicationsServer(socketFactory), timerFactory, osName, osVersion)
{
}
/// <summary>
/// Full constructor.
/// </summary>
/// <remarks>
/// <para>Allows the caller to specify their own <see cref="ISsdpCommunicationsServer"/> implementation for full control over the networking, or for mocking/testing purposes..</para>
/// </remarks>
public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer)
: base(communicationsServer, GetOSName(), GetOSVersion())
{
}
/// <summary>
/// Partial constructor.
/// </summary>
/// <param name="localPort">The local port to use for socket communications, specify 0 to have the system choose it's own.</param>
/// <remarks>
/// <para>Uses the default <see cref="ISsdpCommunicationsServer"/> implementation and network settings for Windows and the SSDP specification, but specifies the local port to use for socket communications. Specify 0 to indicate the system should choose it's own port.</para>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No way to do this here, and we don't want to dispose it except in the (rare) case of an exception anyway.")]
public SsdpDevicePublisher(int localPort)
: this(new SsdpCommunicationsServer(new SocketFactory(null), localPort))
{
}
/// <summary>
/// Partial constructor.
/// </summary>
/// <param name="localPort">The local port to use for socket communications, specify 0 to have the system choose it's own.</param>
/// <param name="multicastTimeToLive">The number of hops a multicast packet can make before it expires. Must be 1 or greater.</param>
/// <remarks>
/// <para>Uses the default <see cref="ISsdpCommunicationsServer"/> implementation and network settings for Windows and the SSDP specification, but specifies the local port to use and multicast time to live setting for socket communications.</para>
/// <para>Specify 0 for the <paramref name="localPort"/> argument to indicate the system should choose it's own port.</para>
/// <para>The <paramref name="multicastTimeToLive"/> is actually a number of 'hops' on the network and not a time based argument.</para>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "No way to do this here, and we don't want to dispose it except in the (rare) case of an exception anyway.")]
public SsdpDevicePublisher(int localPort, int multicastTimeToLive)
: this(new SsdpCommunicationsServer(new SocketFactory(null), localPort, multicastTimeToLive))
{
}
#endregion
#region Private Methods
private static string GetOSName()
{
#if NET46
return Environment.OSVersion.Platform.ToString();
#elif NETSTANDARD1_6
return System.Runtime.InteropServices.RuntimeInformation.OSDescription;
#endif
return "Operating System";
}
private static string GetOSVersion()
{
#if NET46
return Environment.OSVersion.Version.ToString() + " " + Environment.OSVersion.ServicePack.ToString();
#elif NETSTANDARD1_6
return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
#endif
return "1.0";
}
#endregion
}
}

View file

@ -4,6 +4,8 @@ using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Threading;
namespace Rssdp.Infrastructure
{
@ -24,7 +26,8 @@ namespace Rssdp.Infrastructure
private IList<SsdpRootDevice> _Devices;
private ReadOnlyEnumerable<SsdpRootDevice> _ReadOnlyDevices;
private System.Threading.Timer _RebroadcastAliveNotificationsTimer;
private ITimer _RebroadcastAliveNotificationsTimer;
private ITimerFactory _timerFactory;
//private TimeSpan _RebroadcastAliveNotificationsTimeSpan;
private DateTime _LastNotificationTime;
@ -47,10 +50,7 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="communicationsServer">The <see cref="ISsdpCommunicationsServer"/> implementation, used to send and receive SSDP network messages.</param>
/// <param name="osName">Then name of the operating system running the server.</param>
/// <param name="osVersion">The version of the operating system running the server.</param>
protected SsdpDevicePublisherBase(ISsdpCommunicationsServer communicationsServer, string osName, string osVersion)
protected SsdpDevicePublisherBase(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFactory, string osName, string osVersion)
{
if (communicationsServer == null) throw new ArgumentNullException("communicationsServer");
if (osName == null) throw new ArgumentNullException("osName");
@ -59,6 +59,7 @@ namespace Rssdp.Infrastructure
if (osVersion.Length == 0) throw new ArgumentException("osVersion cannot be an empty string.", "osName");
_SupportPnpRootDevice = true;
_timerFactory = timerFactory;
_Devices = new List<SsdpRootDevice>();
_ReadOnlyDevices = new ReadOnlyEnumerable<SsdpRootDevice>(_Devices);
_RecentSearchRequests = new Dictionary<string, SearchRequest>(StringComparer.OrdinalIgnoreCase);
@ -234,7 +235,7 @@ namespace Rssdp.Infrastructure
#region Search Related Methods
private void ProcessSearchRequest(string mx, string searchTarget, UdpEndPoint endPoint)
private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo endPoint)
{
if (String.IsNullOrEmpty(searchTarget))
{
@ -305,7 +306,7 @@ namespace Rssdp.Infrastructure
return _Devices.Union(_Devices.SelectManyRecursive<SsdpDevice>((d) => d.Devices));
}
private void SendDeviceSearchResponses(SsdpDevice device, UdpEndPoint endPoint)
private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint)
{
bool isRootDevice = (device as SsdpRootDevice) != null;
if (isRootDevice)
@ -325,7 +326,7 @@ namespace Rssdp.Infrastructure
return String.Format("{0}::{1}", udn, fullDeviceType);
}
private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, UdpEndPoint endPoint)
private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint)
{
var rootDevice = device.ToRootDevice();
@ -357,7 +358,7 @@ namespace Rssdp.Infrastructure
WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device);
}
private bool IsDuplicateSearchRequest(string searchTarget, UdpEndPoint endPoint)
private bool IsDuplicateSearchRequest(string searchTarget, IpEndPointInfo endPoint)
{
var isDuplicateRequest = false;
@ -590,7 +591,7 @@ namespace Rssdp.Infrastructure
}
//_RebroadcastAliveNotificationsTimeSpan = rebroadCastInterval;
_RebroadcastAliveNotificationsTimer = new System.Threading.Timer(SendAllAliveNotifications, null, nextBroadcastInterval, rebroadCastInterval);
_RebroadcastAliveNotificationsTimer = _timerFactory.Create(SendAllAliveNotifications, null, nextBroadcastInterval, rebroadCastInterval);
WriteTrace(String.Format("Rebroadcast Interval = {0}, Next Broadcast At = {1}", rebroadCastInterval.ToString(), nextBroadcastInterval.ToString()));
}
@ -704,7 +705,7 @@ namespace Rssdp.Infrastructure
private class SearchRequest
{
public UdpEndPoint EndPoint { get; set; }
public IpEndPointInfo EndPoint { get; set; }
public DateTime Received { get; set; }
public string SearchTarget { get; set; }

View file

@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rssdp.Infrastructure
{
/// <summary>
/// Cross platform representation of a UDP end point, being an IP address (either IPv4 or IPv6) and a port.
/// </summary>
public sealed class UdpEndPoint
{
/// <summary>
/// The IP Address of the end point.
/// </summary>
/// <remarks>
/// <para>Can be either IPv4 or IPv6, up to the code using this instance to determine which was provided.</para>
/// </remarks>
public string IPAddress { get; set; }
/// <summary>
/// The port of the end point.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Returns the <see cref="IPAddress"/> and <see cref="Port"/> values separated by a colon.
/// </summary>
/// <returns>A string containing <see cref="IPAddress"/>:<see cref="Port"/>.</returns>
public override string ToString()
{
return (this.IPAddress ?? String.Empty) + ":" + this.Port.ToString();
}
}
}

View file

@ -1,48 +0,0 @@
{
"version": "1.0.0-*",
"dependencies": {
},
"frameworks": {
"net46": {
"frameworkAssemblies": {
"System.Collections": "4.0.0.0",
"System.Net": "4.0.0.0",
"System.Net.Http": "4.0.0.0",
"System.Runtime": "4.0.0.0",
"System.Threading": "4.0.0.0",
"System.Threading.Tasks": "4.0.0.0",
"System.Xml": "4.0.0.0"
},
"dependencies": {
}
},
"netstandard1.6": {
"imports": "dnxcore50",
"dependencies": {
"NETStandard.Library": "1.6.0",
"System.Collections": "4.0.11",
"System.Diagnostics.Debug": "4.0.11",
"System.Diagnostics.Tools": "4.0.1",
"System.IO": "4.1.0",
"System.Linq": "4.1.0",
"System.Net.Http": "4.1.0",
"System.Net.Primitives": "4.0.11",
"System.Net.Sockets": "4.1.0",
"System.Resources.ResourceManager": "4.0.1",
"System.Runtime": "4.1.0",
"System.Runtime.Extensions": "4.1.0",
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
"System.Text.Encoding": "4.0.11",
"System.Text.Encoding.Extensions": "4.0.11",
"System.Threading": "4.0.11",
"System.Threading.Tasks": "4.0.11",
"System.Threading.Timer": "4.0.1",
"System.Xml.ReaderWriter": "4.0.11"
}
}
}
}