improved device discovery

pull/702/head
Luke Pulverenti 10 years ago
parent 1544b7bf9c
commit eda8159b44

@ -93,7 +93,8 @@ namespace MediaBrowser.Api.Dlna
{
Headers = GetRequestHeaders(),
InputXml = await reader.ReadToEndAsync().ConfigureAwait(false),
TargetServerUuId = id
TargetServerUuId = id,
RequestedUrl = Request.AbsoluteUri
});
}
}

@ -1,16 +1,16 @@
using System.Globalization;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Api.Playback
{

@ -10,6 +10,8 @@ namespace MediaBrowser.Controller.Dlna
public string TargetServerUuId { get; set; }
public string RequestedUrl { get; set; }
public ControlRequest()
{
Headers = new Dictionary<string, string>();

@ -18,7 +18,7 @@ namespace MediaBrowser.Dlna.Didl
public class DidlBuilder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
@ -298,7 +298,7 @@ namespace MediaBrowser.Dlna.Didl
container.AppendChild(res);
}
public XmlElement GetFolderElement(XmlDocument doc, Folder folder, int childCount, Filter filter)
{
var container = doc.CreateElement(string.Empty, "container", NS_DIDL);
@ -450,9 +450,14 @@ namespace MediaBrowser.Dlna.Didl
private void AddPeople(BaseItem item, XmlElement element)
{
var types = new[] { PersonType.Director, PersonType.Writer, PersonType.Producer, PersonType.Composer, "Creator" };
foreach (var actor in item.People)
{
AddValue(element, "upnp", (actor.Type ?? PersonType.Actor).ToLower(), actor.Name, NS_UPNP);
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);
}
}

@ -25,10 +25,10 @@ namespace MediaBrowser.Dlna
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
public DlnaManager(IXmlSerializer xmlSerializer,
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILogger logger,
public DlnaManager(IXmlSerializer xmlSerializer,
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILogger logger,
IJsonSerializer jsonSerializer)
{
_xmlSerializer = xmlSerializer;
@ -230,6 +230,19 @@ namespace MediaBrowser.Dlna
{
_logger.Debug("Found matching device profile: {0}", profile.Name);
}
else
{
string userAgent = null;
headers.TryGetValue("User-Agent", out userAgent);
var msg = "No matching device profile found. The default will be used. ";
if (!string.IsNullOrEmpty(userAgent))
{
msg += "User-agent: " + userAgent + ". ";
}
_logger.Debug(msg);
}
return profile;
}

@ -3,81 +3,102 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Dlna.Ssdp;
using MediaBrowser.Model.Logging;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Net;
namespace MediaBrowser.Dlna.Server
namespace MediaBrowser.Dlna.Main
{
public class DlnaServerEntryPoint : IServerEntryPoint
public class DlnaEntryPoint : IServerEntryPoint
{
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private SsdpHandler _ssdpHandler;
private readonly IApplicationHost _appHost;
private readonly INetworkManager _network;
public static DlnaServerEntryPoint Instance;
private SsdpHandler _ssdpHandler;
public DlnaServerEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network)
{
Instance = this;
private readonly List<Guid> _registeredServerIds = new List<Guid>();
private bool _dlnaServerStarted;
public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network)
{
_config = config;
_appHost = appHost;
_network = network;
_logger = logManager.GetLogger("DlnaServer");
_logger = logManager.GetLogger("Dlna");
}
public void Run()
{
_config.ConfigurationUpdated += ConfigurationUpdated;
StartSsdpHandler();
ReloadComponents();
ReloadServer();
_config.ConfigurationUpdated += ConfigurationUpdated;
}
void ConfigurationUpdated(object sender, EventArgs e)
{
ReloadServer();
ReloadComponents();
}
private void ReloadServer()
private void ReloadComponents()
{
var isStarted = _ssdpHandler != null;
var isStarted = _dlnaServerStarted;
if (_config.Configuration.DlnaOptions.EnableServer && !isStarted)
{
StartServer();
StartDlnaServer();
}
else if (!_config.Configuration.DlnaOptions.EnableServer && isStarted)
{
DisposeServer();
DisposeDlnaServer();
}
}
private readonly object _syncLock = new object();
private void StartServer()
private void StartSsdpHandler()
{
var signature = GenerateServerSignature();
try
{
_ssdpHandler = new SsdpHandler(_logger, _config, GenerateServerSignature());
lock (_syncLock)
_ssdpHandler.Start();
}
catch (Exception ex)
{
try
{
_ssdpHandler = new SsdpHandler(_logger, _config, signature);
_logger.ErrorException("Error starting Dlna server", ex);
}
}
RegisterEndpoints();
}
catch (Exception ex)
{
_logger.ErrorException("Error starting Dlna server", ex);
}
private void DisposeSsdpHandler()
{
try
{
_ssdpHandler.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing ssdp handler", ex);
}
}
private void RegisterEndpoints()
public void StartDlnaServer()
{
try
{
RegisterServerEndpoints();
_dlnaServerStarted = true;
}
catch (Exception ex)
{
_logger.ErrorException("Error registering endpoint", ex);
}
}
private void RegisterServerEndpoints()
{
foreach (var address in _network.GetLocalIpAddresses())
{
@ -87,31 +108,17 @@ namespace MediaBrowser.Dlna.Server
var uri = new Uri(string.Format("http://{0}:{1}{2}", address, _config.Configuration.HttpServerPortNumber, descriptorURI));
_ssdpHandler.RegisterNotification(guid, uri, IPAddress.Parse(address));
}
}
var services = new List<string>
{
"upnp:rootdevice",
"urn:schemas-upnp-org:device:MediaServer:1",
"urn:schemas-upnp-org:service:ContentDirectory:1",
"uuid:" + guid.ToString("N")
};
public UpnpDevice GetServerUpnpDevice(string uuid)
{
return _ssdpHandler.Devices.FirstOrDefault(i => string.Equals(uuid, i.Uuid.ToString("N"), StringComparison.OrdinalIgnoreCase));
}
_ssdpHandler.RegisterNotification(guid, uri, IPAddress.Parse(address), services);
private void DisposeServer()
{
lock (_syncLock)
{
if (_ssdpHandler != null)
{
try
{
_ssdpHandler.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing Dlna server", ex);
}
_ssdpHandler = null;
}
_registeredServerIds.Add(guid);
}
}
@ -140,7 +147,27 @@ namespace MediaBrowser.Dlna.Server
public void Dispose()
{
DisposeServer();
DisposeDlnaServer();
DisposeSsdpHandler();
}
public void DisposeDlnaServer()
{
foreach (var id in _registeredServerIds)
{
try
{
_ssdpHandler.UnregisterNotification(id);
}
catch (Exception ex)
{
_logger.ErrorException("Error unregistering server", ex);
}
}
_registeredServerIds.Clear();
_dlnaServerStarted = false;
}
}
}

@ -54,6 +54,7 @@
<Compile Include="DlnaManager.cs" />
<Compile Include="Common\Argument.cs" />
<Compile Include="Eventing\EventManager.cs" />
<Compile Include="Main\DlnaEntryPoint.cs" />
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
<Compile Include="PlayTo\Device.cs">
<SubType>Code</SubType>
@ -79,7 +80,7 @@
<Compile Include="Server\ControlHandler.cs" />
<Compile Include="Server\ServiceActionListBuilder.cs" />
<Compile Include="Server\ContentDirectoryXmlBuilder.cs" />
<Compile Include="Server\Datagram.cs" />
<Compile Include="Ssdp\Datagram.cs" />
<Compile Include="Server\DescriptionXmlBuilder.cs" />
<Compile Include="Ssdp\SsdpHelper.cs" />
<Compile Include="PlayTo\SsdpHttpClient.cs" />
@ -108,10 +109,11 @@
<Compile Include="Profiles\Xbox360Profile.cs" />
<Compile Include="Profiles\XboxOneProfile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Server\DlnaServerEntryPoint.cs" />
<Compile Include="Server\Headers.cs" />
<Compile Include="Server\SsdpHandler.cs" />
<Compile Include="Server\UpnpDevice.cs" />
<Compile Include="Ssdp\SsdpMessageBuilder.cs" />
<Compile Include="Ssdp\SsdpMessageEventArgs.cs" />
<Compile Include="Ssdp\SsdpHandler.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">

@ -50,7 +50,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (_device == null || _device.UpdateTime == default(DateTime))
return false;
return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
return DateTime.UtcNow <= _device.UpdateTime.AddMinutes(10);
}
}

@ -1,4 +1,5 @@
using MediaBrowser.Common.Net;
using System.Text;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@ -68,7 +69,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
_logger.Debug("Found interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
if (!network.SupportsMulticast || !network.GetIPProperties().MulticastAddresses.Any())
if (!network.SupportsMulticast || OperationalStatus.Up != network.OperationalStatus || !network.GetIPProperties().MulticastAddresses.Any())
continue;
var ipV4 = network.GetIPProperties().GetIPv4Properties();
@ -84,7 +85,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
try
{
CreateListener(localIp);
CreateListener(localIp, ipV4.Index);
}
catch (Exception e)
{
@ -111,15 +112,15 @@ namespace MediaBrowser.Dlna.PlayTo
/// Creates a socket for the interface and listends for data.
/// </summary>
/// <param name="localIp">The local ip.</param>
private void CreateListener(IPAddress localIp)
private void CreateListener(IPAddress localIp, int networkInterfaceIndex)
{
Task.Factory.StartNew(async (o) =>
{
try
{
var socket = GetMulticastSocket();
var socket = GetMulticastSocket(networkInterfaceIndex);
socket.Bind(new IPEndPoint(localIp, 0));
socket.Bind(new IPEndPoint(localIp, 1900));
_logger.Info("Creating SSDP listener");
@ -183,7 +184,8 @@ namespace MediaBrowser.Dlna.PlayTo
{
try
{
var request = SsdpHelper.CreateRendererSSDP(3);
var msg = new SsdpMessageBuilder().BuildRendererDiscoveryMessage();
var request = Encoding.UTF8.GetBytes(msg);
while (true)
{
@ -210,12 +212,12 @@ namespace MediaBrowser.Dlna.PlayTo
/// Gets a socket configured for SDDP multicasting.
/// </summary>
/// <returns></returns>
private Socket GetMulticastSocket()
private Socket GetMulticastSocket(int networkInterfaceIndex)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250")));
//socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 3);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), networkInterfaceIndex));
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
return socket;
}

@ -1,6 +1,5 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dlna;
using System.Xml.Serialization;
namespace MediaBrowser.Dlna.Profiles
{

@ -14,7 +14,17 @@ namespace MediaBrowser.Dlna.Profiles
Identification = new DeviceIdentification
{
ModelUrl = "samsung.com"
ModelUrl = "samsung.com",
Headers = new[]
{
new HttpHeaderInfo
{
Name = "User-Agent",
Value = @"SEC_",
Match = HeaderMatchType.Substring
}
}
};
XmlRootAttributes = new[]

@ -1,6 +1,5 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dlna;
using System.Xml.Serialization;
namespace MediaBrowser.Dlna.Profiles
{

@ -1,6 +1,5 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dlna;
using System.Xml.Serialization;
namespace MediaBrowser.Dlna.Profiles
{

@ -3,7 +3,9 @@
<Name>Samsung Smart TV</Name>
<Identification>
<ModelUrl>samsung.com</ModelUrl>
<Headers />
<Headers>
<HttpHeaderInfo name="User-Agent" value="SEC_" match="Substring" />
</Headers>
</Identification>
<FriendlyName>Media Browser</FriendlyName>
<Manufacturer>Media Browser</Manufacturer>

@ -67,10 +67,8 @@ namespace MediaBrowser.Dlna.Server
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId);
var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
var user = GetUser(profile);
return new ControlHandler(

@ -4,24 +4,31 @@ using System.Net;
using System.Net.Sockets;
using System.Text;
namespace MediaBrowser.Dlna.Server
namespace MediaBrowser.Dlna.Ssdp
{
public class Datagram
{
public IPEndPoint EndPoint { get; private set; }
public IPAddress LocalAddress { get; private set; }
public string Message { get; private set; }
public bool Sticky { get; private set; }
/// <summary>
/// The number of times to send the message
/// </summary>
public int TotalSendCount { get; private set; }
/// <summary>
/// The number of times the message has been sent
/// </summary>
public int SendCount { get; private set; }
private readonly ILogger _logger;
public Datagram(IPEndPoint endPoint, IPAddress localAddress, ILogger logger, string message, bool sticky)
public Datagram(IPEndPoint endPoint, IPAddress localAddress, ILogger logger, string message, int totalSendCount)
{
Message = message;
_logger = logger;
Sticky = sticky;
TotalSendCount = totalSendCount;
LocalAddress = localAddress;
EndPoint = endPoint;
}
@ -31,9 +38,11 @@ namespace MediaBrowser.Dlna.Server
var msg = Encoding.ASCII.GetBytes(Message);
try
{
var client = new UdpClient();
client.Client.Bind(new IPEndPoint(LocalAddress, 0));
client.BeginSend(msg, msg.Length, EndPoint, result =>
var client = CreateSocket();
client.Bind(new IPEndPoint(LocalAddress, 0));
client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, EndPoint, result =>
{
try
{
@ -61,5 +70,13 @@ namespace MediaBrowser.Dlna.Server
}
++SendCount;
}
private Socket CreateSocket()
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
return socket;
}
}
}

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Dlna.Server;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
@ -10,72 +11,199 @@ using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace MediaBrowser.Dlna.Server
namespace MediaBrowser.Dlna.Ssdp
{
public class SsdpHandler : IDisposable
{
private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
private Socket _socket;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly string _serverSignature;
private bool _isDisposed;
const string SSDPAddr = "239.255.255.250";
const int SSDPPort = 1900;
private readonly string _serverSignature;
private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr);
private UdpClient _udpClient;
private readonly Dictionary<Guid, List<UpnpDevice>> _devices = new Dictionary<Guid, List<UpnpDevice>>();
private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
private Timer _queueTimer;
private Timer _notificationTimer;
private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
private bool _isDisposed;
private readonly ConcurrentDictionary<Guid, List<UpnpDevice>> _devices = new ConcurrentDictionary<Guid, List<UpnpDevice>>();
public SsdpHandler(ILogger logger, IServerConfigurationManager config, string serverSignature)
{
_logger = logger;
_config = config;
_serverSignature = serverSignature;
}
public event EventHandler<SsdpMessageEventArgs> MessageReceived;
Start();
private void OnMessageReceived(SsdpMessageEventArgs args)
{
if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase))
{
RespondToSearch(args.EndPoint, args.Headers["st"]);
}
}
public IEnumerable<UpnpDevice> Devices
public IEnumerable<UpnpDevice> RegisteredDevices
{
get
{
UpnpDevice[] devs;
lock (_devices)
{
devs = _devices.Values.SelectMany(i => i).ToArray();
}
return devs;
return _devices.Values.SelectMany(i => i).ToList();
}
}
private void Start()
public void Start()
{
_udpClient = new UdpClient();
_udpClient.Client.UseOnlyOverlappedIO = true;
_udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
_udpClient.ExclusiveAddressUse = false;
_udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, SSDPPort));
_udpClient.JoinMulticastGroup(_ssdpIp, 2);
_socket = CreateMulticastSocket();
_logger.Info("SSDP service started");
Receive();
StartNotificationTimer();
}
public void SendDatagram(string header,
Dictionary<string, string> values,
IPAddress localAddress,
int sendCount = 1)
{
SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
}
public void SendDatagram(string header,
Dictionary<string, string> values,
IPEndPoint endpoint,
IPAddress localAddress,
int sendCount = 1)
{
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount);
if (_messageQueue.Count == 0)
{
dgram.Send();
return;
}
_messageQueue.Enqueue(dgram);
StartQueueTimer();
}
public void SendDatagramFromDevices(string header,
Dictionary<string, string> values,
IPEndPoint endpoint,
string deviceType)
{
foreach (var d in RegisteredDevices)
{
if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
{
SendDatagram(header, values, endpoint, d.Address);
}
}
}
private void RespondToSearch(IPEndPoint endpoint, string deviceType)
{
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
_logger.Debug("RespondToSearch");
}
const string header = "HTTP/1.1 200 OK";
foreach (var d in RegisteredDevices)
{
if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
{
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
values["CACHE-CONTROL"] = "max-age = 600";
values["DATE"] = DateTime.Now.ToString("R");
values["EXT"] = "";
values["LOCATION"] = d.Descriptor.ToString();
values["SERVER"] = _serverSignature;
values["ST"] = d.Type;
values["USN"] = d.USN;
SendDatagram(header, values, endpoint, d.Address);
_logger.Info("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
}
}
}
private readonly object _queueTimerSyncLock = new object();
private void StartQueueTimer()
{
lock (_queueTimerSyncLock)
{
if (_queueTimer == null)
{
_queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
}
else
{
_queueTimer.Change(1000, Timeout.Infinite);
}
}
}
private void QueueTimerCallback(object state)
{
while (_messageQueue.Count != 0)
{
Datagram msg;
if (!_messageQueue.TryPeek(out msg))
{
continue;
}
if (msg != null && (!_isDisposed || msg.TotalSendCount > 1))
{
msg.Send();
if (msg.SendCount > msg.TotalSendCount)
{
_messageQueue.TryDequeue(out msg);
}
break;
}
_messageQueue.TryDequeue(out msg);
}
_datagramPosted.Set();
if (_messageQueue.Count > 0)
{
StartQueueTimer();
}
else
{
DisposeQueueTimer();
}
}
private void Receive()
{
try
{
_udpClient.BeginReceive(ReceiveCallback, null);
var buffer = new byte[1024];
EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
_socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer);
}
catch (ObjectDisposedException)
{
@ -84,10 +212,16 @@ namespace MediaBrowser.Dlna.Server
private void ReceiveCallback(IAsyncResult result)
{
if (_isDisposed)
{
return;
}
try
{
var endpoint = new IPEndPoint(IPAddress.None, SSDPPort);
var received = _udpClient.EndReceive(result, ref endpoint);
EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
var receivedCount = _socket.EndReceiveFrom(result, ref endpoint);
var received = (byte[])result.AsyncState;
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
@ -98,7 +232,7 @@ namespace MediaBrowser.Dlna.Server
{
var proto = (reader.ReadLine() ?? string.Empty).Trim();
var method = proto.Split(new[] { ' ' }, 2)[0];
var headers = new Headers();
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
line = line.Trim();
@ -119,10 +253,12 @@ namespace MediaBrowser.Dlna.Server
_logger.Debug("{0} - Datagram method: {1}", endpoint, method);
}
if (string.Equals(method, "M-SEARCH", StringComparison.OrdinalIgnoreCase))
OnMessageReceived(new SsdpMessageEventArgs
{
RespondToSearch(endpoint, headers["st"]);
}
Method = method,
Headers = headers,
EndPoint = (IPEndPoint)endpoint
});
}
}
catch (Exception ex)
@ -130,105 +266,60 @@ namespace MediaBrowser.Dlna.Server
_logger.ErrorException("Failed to read SSDP message", ex);
}
if (!_isDisposed)
if (_socket != null)
{
Receive();
}
}
private void RespondToSearch(IPEndPoint endpoint, string req)
public void Dispose()
{
if (string.Equals(req, "ssdp:all", StringComparison.OrdinalIgnoreCase))
{
req = null;
}
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
_logger.Debug("RespondToSearch");
}
foreach (var d in Devices)
_isDisposed = true;
while (_messageQueue.Count != 0)
{
if (!string.IsNullOrEmpty(req) && !string.Equals(req, d.Type, StringComparison.OrdinalIgnoreCase))
{
continue;
}
SendSearchResponse(endpoint, d);
_datagramPosted.WaitOne();
}
}
private void SendSearchResponse(IPEndPoint endpoint, UpnpDevice dev)
{
var builder = new StringBuilder();
const string argFormat = "{0}: {1}\r\n";
builder.Append("HTTP/1.1 200 OK\r\n");
builder.AppendFormat(argFormat, "CACHE-CONTROL", "max-age = 600");
builder.AppendFormat(argFormat, "DATE", DateTime.Now.ToString("R"));
builder.AppendFormat(argFormat, "EXT", "");
builder.AppendFormat(argFormat, "LOCATION", dev.Descriptor);
builder.AppendFormat(argFormat, "SERVER", _serverSignature);
builder.AppendFormat(argFormat, "ST", dev.Type);
builder.AppendFormat(argFormat, "USN", dev.USN);
builder.Append("\r\n");
SendDatagram(endpoint, dev.Address, builder.ToString(), false);
DisposeSocket();
DisposeQueueTimer();
DisposeNotificationTimer();
_logger.Info("{1} - Responded to a {0} request to {2}", dev.Type, endpoint, dev.Address.ToString());
_datagramPosted.Dispose();
}
private void SendDatagram(IPEndPoint endpoint, IPAddress localAddress, string msg, bool sticky)
private void DisposeSocket()
{
if (_isDisposed)
if (_socket != null)
{
return;
_socket.Close();
_socket.Dispose();
_socket = null;
}
var dgram = new Datagram(endpoint, localAddress, _logger, msg, sticky);
if (_messageQueue.Count == 0)
{
dgram.Send();
}
_messageQueue.Enqueue(dgram);
StartQueueTimer();
}
private void QueueTimerCallback(object state)
private void DisposeQueueTimer()
{
while (_messageQueue.Count != 0)
lock (_queueTimerSyncLock)
{
Datagram msg;
if (!_messageQueue.TryPeek(out msg))
{
continue;
}
if (msg != null && (!_isDisposed || msg.Sticky))
if (_queueTimer != null)
{
msg.Send();
if (msg.SendCount > 2)
{
_messageQueue.TryDequeue(out msg);
}
break;
_queueTimer.Dispose();
_queueTimer = null;
}
_messageQueue.TryDequeue(out msg);
}
}
_datagramPosted.Set();
private Socket CreateMulticastSocket()
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(_ssdpIp, 0));
if (_messageQueue.Count > 0)
{
StartQueueTimer();
}
else
{
DisposeQueueTimer();
}
socket.Bind(new IPEndPoint(IPAddress.Any, SSDPPort));
return socket;
}
private void NotifyAll()
@ -237,121 +328,64 @@ namespace MediaBrowser.Dlna.Server
{
_logger.Debug("Sending alive notifications");
}
foreach (var d in Devices)
foreach (var d in RegisteredDevices)
{
NotifyDevice(d, "alive", false);
NotifyDevice(d, "alive");
}
}
private void NotifyDevice(UpnpDevice dev, string type, bool sticky)
private void NotifyDevice(UpnpDevice dev, string type, int sendCount = 1)
{
var builder = new StringBuilder();
const string header = "NOTIFY * HTTP/1.1";
const string argFormat = "{0}: {1}\r\n";
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
builder.Append("NOTIFY * HTTP/1.1\r\n{0}\r\n");
builder.AppendFormat(argFormat, "HOST", "239.255.255.250:1900");
builder.AppendFormat(argFormat, "CACHE-CONTROL", "max-age = 600");
builder.AppendFormat(argFormat, "LOCATION", dev.Descriptor);
builder.AppendFormat(argFormat, "SERVER", _serverSignature);
builder.AppendFormat(argFormat, "NTS", "ssdp:" + type);
builder.AppendFormat(argFormat, "NT", dev.Type);
builder.AppendFormat(argFormat, "USN", dev.USN);
builder.Append("\r\n");
// If needed later for non-server devices, these headers will need to be dynamic
values["HOST"] = "239.255.255.250:1900";
values["CACHE-CONTROL"] = "max-age = 600";
values["LOCATION"] = dev.Descriptor.ToString();
values["SERVER"] = _serverSignature;
values["NTS"] = "ssdp:" + type;
values["NT"] = dev.Type;
values["USN"] = dev.USN;
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
_logger.Debug("{0} said {1}", dev.USN, type);
}
SendDatagram(_ssdpEndp, dev.Address, builder.ToString(), sticky);
SendDatagram(header, values, dev.Address, sendCount);
}
public void RegisterNotification(Guid uuid, Uri descriptor, IPAddress address)
public void RegisterNotification(Guid uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
{
List<UpnpDevice> list;
lock (_devices)
{
if (!_devices.TryGetValue(uuid, out list))
{
_devices.Add(uuid, list = new List<UpnpDevice>());
_devices.TryAdd(uuid, list = new List<UpnpDevice>());
}
}
foreach (var t in new[]
{
"upnp:rootdevice",
"urn:schemas-upnp-org:device:MediaServer:1",
"urn:schemas-upnp-org:service:ContentDirectory:1",
"uuid:" + uuid
})
{
list.Add(new UpnpDevice(uuid, t, descriptor, address));
}
list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
NotifyAll();
_logger.Debug("Registered mount {0} at {1}", uuid, descriptor);
_logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri);
}
private void UnregisterNotification(Guid uuid)
public void UnregisterNotification(Guid uuid)
{
List<UpnpDevice> dl;
lock (_devices)
if (_devices.TryRemove(uuid, out dl))
{
if (!_devices.TryGetValue(uuid, out dl))
{
return;
}
_devices.Remove(uuid);
}
foreach (var d in dl)
{
NotifyDevice(d, "byebye", true);
}
_logger.Debug("Unregistered mount {0}", uuid);
}
public void Dispose()
{
_isDisposed = true;
while (_messageQueue.Count != 0)
{
_datagramPosted.WaitOne();
}
_udpClient.DropMulticastGroup(_ssdpIp);
_udpClient.Close();
DisposeNotificationTimer();
DisposeQueueTimer();
_datagramPosted.Dispose();
}
private readonly object _queueTimerSyncLock = new object();
private void StartQueueTimer()
{
lock (_queueTimerSyncLock)
{
if (_queueTimer == null)
{
_queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
}
else
foreach (var d in dl.ToList())
{
_queueTimer.Change(1000, Timeout.Infinite);
NotifyDevice(d, "byebye", 2);
}
}
}
private void DisposeQueueTimer()
{
lock (_queueTimerSyncLock)
{
if (_queueTimer != null)
{
_queueTimer.Dispose();
_queueTimer = null;
}
_logger.Debug("Unregistered mount {0}", uuid);
}
}

@ -7,24 +7,6 @@ namespace MediaBrowser.Dlna.Ssdp
{
public class SsdpHelper
{
private const string SsdpRenderer = "M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"User-Agent: UPnP/1.0 DLNADOC/1.50 Platinum/0.6.9.1\r\n" +
"ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n" +
"MAN: \"ssdp:discover\"\r\n" +
"MX: {0}\r\n" +
"\r\n";
/// <summary>
/// Creates a SSDP MSearch packet for DlnaRenderers.
/// </summary>
/// <param name="mx">The mx. (Delaytime for device before responding)</param>
/// <returns></returns>
public static byte[] CreateRendererSSDP(int mx)
{
return Encoding.UTF8.GetBytes(string.Format(SsdpRenderer, mx));
}
/// <summary>
/// Parses the socket response into a location Uri for the DeviceDescription.xml.
/// </summary>

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MediaBrowser.Dlna.Ssdp
{
public class SsdpMessageBuilder
{
public string BuildMessage(string header, Dictionary<string, string> values)
{
var builder = new StringBuilder();
const string argFormat = "{0}: {1}\r\n";
builder.AppendFormat("{0}\r\n", header);
foreach (var pair in values)
{
builder.AppendFormat(argFormat, pair.Key, pair.Value);
}
builder.Append("\r\n");
return builder.ToString();
}
public string BuildDiscoveryMessage(string deviceSearchType, string mx)
{
const string header = "M-SEARCH * HTTP/1.1";
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
values["HOST"] = "239.255.255.250:1900";
values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/0.6.9.1";
values["ST"] = deviceSearchType;
values["MAN"] = "\"ssdp:discover\"";
values["MX"] = mx;
return BuildMessage(header, values);
}
public string BuildRendererDiscoveryMessage()
{
return BuildDiscoveryMessage("urn:schemas-upnp-org:device:MediaRenderer:1", "3");
}
}
}

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Net;
namespace MediaBrowser.Dlna.Ssdp
{
public class SsdpMessageEventArgs
{
public string Method { get; set; }
public IPEndPoint EndPoint { get; set; }
public Dictionary<string, string> Headers { get; set; }
public SsdpMessageEventArgs()
{
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
}

@ -298,7 +298,7 @@ namespace MediaBrowser.Model.Dlna
{
if (IsDirectStream)
{
return MediaSource.Bitrate;
return MediaSource.Size;
}
if (RunTimeTicks.HasValue)

Loading…
Cancel
Save