Pārlūkot izejas kodu

improved device discovery

Luke Pulverenti 11 gadi atpakaļ
vecāks
revīzija
eda8159b44

+ 2 - 1
MediaBrowser.Api/Dlna/DlnaServerService.cs

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

+ 3 - 3
MediaBrowser.Api/Playback/StreamState.cs

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

+ 2 - 0
MediaBrowser.Controller/Dlna/ControlRequest.cs

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

+ 8 - 3
MediaBrowser.Dlna/Didl/DidlBuilder.cs

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

+ 17 - 4
MediaBrowser.Dlna/DlnaManager.cs

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

+ 82 - 55
MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs → MediaBrowser.Dlna/Main/DlnaEntryPoint.cs

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

+ 5 - 3
MediaBrowser.Dlna/MediaBrowser.Dlna.csproj

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

+ 1 - 1
MediaBrowser.Dlna/PlayTo/DlnaController.cs

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

+ 12 - 10
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

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

+ 2 - 3
MediaBrowser.Dlna/Profiles/LgTvProfile.cs

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

+ 11 - 1
MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs

@@ -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[]

+ 2 - 3
MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013Profile.cs

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

+ 2 - 3
MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs

@@ -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 - 1
MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml

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

+ 2 - 4
MediaBrowser.Dlna/Server/ContentDirectory.cs

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

+ 24 - 7
MediaBrowser.Dlna/Server/Datagram.cs → MediaBrowser.Dlna/Ssdp/Datagram.cs

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

+ 222 - 188
MediaBrowser.Dlna/Server/SsdpHandler.cs → MediaBrowser.Dlna/Ssdp/SsdpHandler.cs

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

+ 0 - 18
MediaBrowser.Dlna/Ssdp/SsdpHelper.cs

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

+ 47 - 0
MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs

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

+ 20 - 0
MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs

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

+ 1 - 1
MediaBrowser.Model/Dlna/StreamInfo.cs

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