From eda8159b4408c4b33cbdd56e3e6ed2f82757bb0c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 25 Apr 2014 13:30:41 -0400 Subject: improved device discovery --- MediaBrowser.Api/Dlna/DlnaServerService.cs | 3 +- MediaBrowser.Api/Playback/StreamState.cs | 6 +- MediaBrowser.Controller/Dlna/ControlRequest.cs | 2 + MediaBrowser.Dlna/Didl/DidlBuilder.cs | 11 +- MediaBrowser.Dlna/DlnaManager.cs | 21 +- MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 173 +++++++++ MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 8 +- MediaBrowser.Dlna/PlayTo/DlnaController.cs | 2 +- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 22 +- MediaBrowser.Dlna/Profiles/LgTvProfile.cs | 5 +- .../Profiles/SamsungSmartTvProfile.cs | 12 +- .../Profiles/SonyBlurayPlayer2013Profile.cs | 5 +- .../Profiles/SonyBravia2010Profile.cs | 5 +- .../Profiles/Xml/Samsung Smart TV.xml | 4 +- MediaBrowser.Dlna/Server/ContentDirectory.cs | 6 +- MediaBrowser.Dlna/Server/Datagram.cs | 65 ---- MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs | 146 ------- MediaBrowser.Dlna/Server/SsdpHandler.cs | 393 ------------------- MediaBrowser.Dlna/Ssdp/Datagram.cs | 82 ++++ MediaBrowser.Dlna/Ssdp/SsdpHandler.cs | 427 +++++++++++++++++++++ MediaBrowser.Dlna/Ssdp/SsdpHelper.cs | 18 - MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs | 47 +++ MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs | 20 + MediaBrowser.Model/Dlna/StreamInfo.cs | 2 +- 24 files changed, 822 insertions(+), 663 deletions(-) create mode 100644 MediaBrowser.Dlna/Main/DlnaEntryPoint.cs delete mode 100644 MediaBrowser.Dlna/Server/Datagram.cs delete mode 100644 MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs delete mode 100644 MediaBrowser.Dlna/Server/SsdpHandler.cs create mode 100644 MediaBrowser.Dlna/Ssdp/Datagram.cs create mode 100644 MediaBrowser.Dlna/Ssdp/SsdpHandler.cs create mode 100644 MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs create mode 100644 MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs index b357409c37..05d41c18bc 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/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 }); } } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index c7a62a332b..38d734ff99 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/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 { diff --git a/MediaBrowser.Controller/Dlna/ControlRequest.cs b/MediaBrowser.Controller/Dlna/ControlRequest.cs index a2b9f7a926..1bb5ddf8a9 100644 --- a/MediaBrowser.Controller/Dlna/ControlRequest.cs +++ b/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(); diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 32b49ef9cc..e5eff90457 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/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); } } diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 963c68d9a3..1f2d5b9c9d 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/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; } diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs new file mode 100644 index 0000000000..3746630be3 --- /dev/null +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -0,0 +1,173 @@ +using MediaBrowser.Common; +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.Collections.Generic; +using System.Net; + +namespace MediaBrowser.Dlna.Main +{ + public class DlnaEntryPoint : IServerEntryPoint + { + private readonly IServerConfigurationManager _config; + private readonly ILogger _logger; + private readonly IApplicationHost _appHost; + private readonly INetworkManager _network; + + private SsdpHandler _ssdpHandler; + + private readonly List _registeredServerIds = new List(); + private bool _dlnaServerStarted; + + public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network) + { + _config = config; + _appHost = appHost; + _network = network; + _logger = logManager.GetLogger("Dlna"); + } + + public void Run() + { + StartSsdpHandler(); + ReloadComponents(); + + _config.ConfigurationUpdated += ConfigurationUpdated; + } + + void ConfigurationUpdated(object sender, EventArgs e) + { + ReloadComponents(); + } + + private void ReloadComponents() + { + var isStarted = _dlnaServerStarted; + + if (_config.Configuration.DlnaOptions.EnableServer && !isStarted) + { + StartDlnaServer(); + } + else if (!_config.Configuration.DlnaOptions.EnableServer && isStarted) + { + DisposeDlnaServer(); + } + } + + private void StartSsdpHandler() + { + try + { + _ssdpHandler = new SsdpHandler(_logger, _config, GenerateServerSignature()); + + _ssdpHandler.Start(); + } + 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); + } + } + + 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()) + { + var guid = address.GetMD5(); + + var descriptorURI = "/mediabrowser/dlna/" + guid.ToString("N") + "/description.xml"; + + var uri = new Uri(string.Format("http://{0}:{1}{2}", address, _config.Configuration.HttpServerPortNumber, descriptorURI)); + + var services = new List + { + "upnp:rootdevice", + "urn:schemas-upnp-org:device:MediaServer:1", + "urn:schemas-upnp-org:service:ContentDirectory:1", + "uuid:" + guid.ToString("N") + }; + + _ssdpHandler.RegisterNotification(guid, uri, IPAddress.Parse(address), services); + + _registeredServerIds.Add(guid); + } + } + + private string GenerateServerSignature() + { + var os = Environment.OSVersion; + var pstring = os.Platform.ToString(); + switch (os.Platform) + { + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + pstring = "WIN"; + break; + } + + return String.Format( + "{0}{1}/{2}.{3} UPnP/1.0 DLNADOC/1.5 MediaBrowser/{4}", + pstring, + IntPtr.Size * 8, + os.Version.Major, + os.Version.Minor, + _appHost.ApplicationVersion + ); + } + + public void Dispose() + { + 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; + } + } +} diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index a96838a4dd..97da7b697e 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -54,6 +54,7 @@ + Code @@ -79,7 +80,7 @@ - + @@ -108,10 +109,11 @@ - - + + + diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 6b6152d85a..11514fb853 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/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); } } diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index 10e82a2276..020bd21169 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/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. /// /// The local ip. - 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. /// /// - 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; } diff --git a/MediaBrowser.Dlna/Profiles/LgTvProfile.cs b/MediaBrowser.Dlna/Profiles/LgTvProfile.cs index cce33ae100..4ecfd3c5b9 100644 --- a/MediaBrowser.Dlna/Profiles/LgTvProfile.cs +++ b/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 { diff --git a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs index 30f62e81cd..d9581ff2ce 100644 --- a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs +++ b/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[] diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013Profile.cs index 01ee7fd68d..baaccba5a7 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013Profile.cs +++ b/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 { diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs index 6167f553c8..3897724959 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs +++ b/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 { diff --git a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml index b99ecf8728..7b281abd1e 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml @@ -3,7 +3,9 @@ Samsung Smart TV samsung.com - + + + Media Browser Media Browser diff --git a/MediaBrowser.Dlna/Server/ContentDirectory.cs b/MediaBrowser.Dlna/Server/ContentDirectory.cs index c5b3360902..e657a2ff65 100644 --- a/MediaBrowser.Dlna/Server/ContentDirectory.cs +++ b/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( diff --git a/MediaBrowser.Dlna/Server/Datagram.cs b/MediaBrowser.Dlna/Server/Datagram.cs deleted file mode 100644 index 2432cbd062..0000000000 --- a/MediaBrowser.Dlna/Server/Datagram.cs +++ /dev/null @@ -1,65 +0,0 @@ -using MediaBrowser.Model.Logging; -using System; -using System.Net; -using System.Net.Sockets; -using System.Text; - -namespace MediaBrowser.Dlna.Server -{ - 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; } - - public int SendCount { get; private set; } - - private readonly ILogger _logger; - - public Datagram(IPEndPoint endPoint, IPAddress localAddress, ILogger logger, string message, bool sticky) - { - Message = message; - _logger = logger; - Sticky = sticky; - LocalAddress = localAddress; - EndPoint = endPoint; - } - - public void Send() - { - 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 => - { - try - { - client.EndSend(result); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending Datagram", ex); - } - finally - { - try - { - client.Close(); - } - catch (Exception) - { - } - } - }, null); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending Datagram", ex); - } - ++SendCount; - } - } -} diff --git a/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs b/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs deleted file mode 100644 index d7a34c699c..0000000000 --- a/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs +++ /dev/null @@ -1,146 +0,0 @@ -using MediaBrowser.Common; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.Logging; -using System; -using System.Linq; -using System.Net; - -namespace MediaBrowser.Dlna.Server -{ - public class DlnaServerEntryPoint : IServerEntryPoint - { - private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; - - private SsdpHandler _ssdpHandler; - private readonly IApplicationHost _appHost; - private readonly INetworkManager _network; - - public static DlnaServerEntryPoint Instance; - - public DlnaServerEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network) - { - Instance = this; - - _config = config; - _appHost = appHost; - _network = network; - _logger = logManager.GetLogger("DlnaServer"); - } - - public void Run() - { - _config.ConfigurationUpdated += ConfigurationUpdated; - - ReloadServer(); - } - - void ConfigurationUpdated(object sender, EventArgs e) - { - ReloadServer(); - } - - private void ReloadServer() - { - var isStarted = _ssdpHandler != null; - - if (_config.Configuration.DlnaOptions.EnableServer && !isStarted) - { - StartServer(); - } - else if (!_config.Configuration.DlnaOptions.EnableServer && isStarted) - { - DisposeServer(); - } - } - - private readonly object _syncLock = new object(); - private void StartServer() - { - var signature = GenerateServerSignature(); - - lock (_syncLock) - { - try - { - _ssdpHandler = new SsdpHandler(_logger, _config, signature); - - RegisterEndpoints(); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting Dlna server", ex); - } - } - } - - private void RegisterEndpoints() - { - foreach (var address in _network.GetLocalIpAddresses()) - { - var guid = address.GetMD5(); - - var descriptorURI = "/mediabrowser/dlna/" + guid.ToString("N") + "/description.xml"; - - var uri = new Uri(string.Format("http://{0}:{1}{2}", address, _config.Configuration.HttpServerPortNumber, descriptorURI)); - - _ssdpHandler.RegisterNotification(guid, uri, IPAddress.Parse(address)); - } - } - - public UpnpDevice GetServerUpnpDevice(string uuid) - { - return _ssdpHandler.Devices.FirstOrDefault(i => string.Equals(uuid, i.Uuid.ToString("N"), StringComparison.OrdinalIgnoreCase)); - } - - private void DisposeServer() - { - lock (_syncLock) - { - if (_ssdpHandler != null) - { - try - { - _ssdpHandler.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing Dlna server", ex); - } - _ssdpHandler = null; - } - } - } - - private string GenerateServerSignature() - { - var os = Environment.OSVersion; - var pstring = os.Platform.ToString(); - switch (os.Platform) - { - case PlatformID.Win32NT: - case PlatformID.Win32S: - case PlatformID.Win32Windows: - pstring = "WIN"; - break; - } - - return String.Format( - "{0}{1}/{2}.{3} UPnP/1.0 DLNADOC/1.5 MediaBrowser/{4}", - pstring, - IntPtr.Size * 8, - os.Version.Major, - os.Version.Minor, - _appHost.ApplicationVersion - ); - } - - public void Dispose() - { - DisposeServer(); - } - } -} diff --git a/MediaBrowser.Dlna/Server/SsdpHandler.cs b/MediaBrowser.Dlna/Server/SsdpHandler.cs deleted file mode 100644 index 0430f6a02c..0000000000 --- a/MediaBrowser.Dlna/Server/SsdpHandler.cs +++ /dev/null @@ -1,393 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; - -namespace MediaBrowser.Dlna.Server -{ - public class SsdpHandler : IDisposable - { - private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false); - private readonly ConcurrentQueue _messageQueue = new ConcurrentQueue(); - - 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 IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort); - private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr); - - private UdpClient _udpClient; - - private readonly Dictionary> _devices = new Dictionary>(); - - private Timer _queueTimer; - private Timer _notificationTimer; - - public SsdpHandler(ILogger logger, IServerConfigurationManager config, string serverSignature) - { - _logger = logger; - _config = config; - _serverSignature = serverSignature; - - Start(); - } - - public IEnumerable Devices - { - get - { - UpnpDevice[] devs; - lock (_devices) - { - devs = _devices.Values.SelectMany(i => i).ToArray(); - } - return devs; - } - } - - private 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); - _logger.Info("SSDP service started"); - Receive(); - - StartNotificationTimer(); - } - - private void Receive() - { - try - { - _udpClient.BeginReceive(ReceiveCallback, null); - } - catch (ObjectDisposedException) - { - } - } - - private void ReceiveCallback(IAsyncResult result) - { - try - { - var endpoint = new IPEndPoint(IPAddress.None, SSDPPort); - var received = _udpClient.EndReceive(result, ref endpoint); - - if (_config.Configuration.DlnaOptions.EnableDebugLogging) - { - _logger.Debug("{0} - SSDP Received a datagram", endpoint); - } - - using (var reader = new StreamReader(new MemoryStream(received), Encoding.ASCII)) - { - var proto = (reader.ReadLine() ?? string.Empty).Trim(); - var method = proto.Split(new[] { ' ' }, 2)[0]; - var headers = new Headers(); - for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) - { - line = line.Trim(); - if (string.IsNullOrEmpty(line)) - { - break; - } - var parts = line.Split(new[] { ':' }, 2); - - if (parts.Length >= 2) - { - headers[parts[0]] = parts[1].Trim(); - } - } - - if (_config.Configuration.DlnaOptions.EnableDebugLogging) - { - _logger.Debug("{0} - Datagram method: {1}", endpoint, method); - } - - if (string.Equals(method, "M-SEARCH", StringComparison.OrdinalIgnoreCase)) - { - RespondToSearch(endpoint, headers["st"]); - } - } - } - catch (Exception ex) - { - _logger.ErrorException("Failed to read SSDP message", ex); - } - - if (!_isDisposed) - { - Receive(); - } - } - - private void RespondToSearch(IPEndPoint endpoint, string req) - { - if (string.Equals(req, "ssdp:all", StringComparison.OrdinalIgnoreCase)) - { - req = null; - } - - if (_config.Configuration.DlnaOptions.EnableDebugLogging) - { - _logger.Debug("RespondToSearch"); - } - - foreach (var d in Devices) - { - if (!string.IsNullOrEmpty(req) && !string.Equals(req, d.Type, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - SendSearchResponse(endpoint, d); - } - } - - 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); - - _logger.Info("{1} - Responded to a {0} request to {2}", dev.Type, endpoint, dev.Address.ToString()); - } - - private void SendDatagram(IPEndPoint endpoint, IPAddress localAddress, string msg, bool sticky) - { - if (_isDisposed) - { - return; - } - - var dgram = new Datagram(endpoint, localAddress, _logger, msg, sticky); - if (_messageQueue.Count == 0) - { - dgram.Send(); - } - _messageQueue.Enqueue(dgram); - StartQueueTimer(); - } - - private void QueueTimerCallback(object state) - { - while (_messageQueue.Count != 0) - { - Datagram msg; - if (!_messageQueue.TryPeek(out msg)) - { - continue; - } - - if (msg != null && (!_isDisposed || msg.Sticky)) - { - msg.Send(); - if (msg.SendCount > 2) - { - _messageQueue.TryDequeue(out msg); - } - break; - } - - _messageQueue.TryDequeue(out msg); - } - - _datagramPosted.Set(); - - if (_messageQueue.Count > 0) - { - StartQueueTimer(); - } - else - { - DisposeQueueTimer(); - } - } - - private void NotifyAll() - { - if (_config.Configuration.DlnaOptions.EnableDebugLogging) - { - _logger.Debug("Sending alive notifications"); - } - foreach (var d in Devices) - { - NotifyDevice(d, "alive", false); - } - } - - private void NotifyDevice(UpnpDevice dev, string type, bool sticky) - { - var builder = new StringBuilder(); - - const string argFormat = "{0}: {1}\r\n"; - - 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 (_config.Configuration.DlnaOptions.EnableDebugLogging) - { - _logger.Debug("{0} said {1}", dev.USN, type); - } - - SendDatagram(_ssdpEndp, dev.Address, builder.ToString(), sticky); - } - - public void RegisterNotification(Guid uuid, Uri descriptor, IPAddress address) - { - List list; - lock (_devices) - { - if (!_devices.TryGetValue(uuid, out list)) - { - _devices.Add(uuid, list = new List()); - } - } - - 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)); - } - - NotifyAll(); - _logger.Debug("Registered mount {0} at {1}", uuid, descriptor); - } - - private void UnregisterNotification(Guid uuid) - { - List dl; - lock (_devices) - { - 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 - { - _queueTimer.Change(1000, Timeout.Infinite); - } - } - } - - private void DisposeQueueTimer() - { - lock (_queueTimerSyncLock) - { - if (_queueTimer != null) - { - _queueTimer.Dispose(); - _queueTimer = null; - } - } - } - - private readonly object _notificationTimerSyncLock = new object(); - private void StartNotificationTimer() - { - if (!_config.Configuration.DlnaOptions.BlastAliveMessages) - { - return; - } - - var intervalMs = _config.Configuration.DlnaOptions.BlastAliveMessageIntervalSeconds * 1000; - - lock (_notificationTimerSyncLock) - { - if (_notificationTimer == null) - { - _notificationTimer = new Timer(state => NotifyAll(), null, intervalMs, intervalMs); - } - else - { - _notificationTimer.Change(intervalMs, intervalMs); - } - } - } - - private void DisposeNotificationTimer() - { - lock (_notificationTimerSyncLock) - { - if (_notificationTimer != null) - { - _notificationTimer.Dispose(); - _notificationTimer = null; - } - } - } - } -} diff --git a/MediaBrowser.Dlna/Ssdp/Datagram.cs b/MediaBrowser.Dlna/Ssdp/Datagram.cs new file mode 100644 index 0000000000..0caf5c78fa --- /dev/null +++ b/MediaBrowser.Dlna/Ssdp/Datagram.cs @@ -0,0 +1,82 @@ +using MediaBrowser.Model.Logging; +using System; +using System.Net; +using System.Net.Sockets; +using System.Text; + +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; } + + /// + /// The number of times to send the message + /// + public int TotalSendCount { get; private set; } + + /// + /// The number of times the message has been sent + /// + public int SendCount { get; private set; } + + private readonly ILogger _logger; + + public Datagram(IPEndPoint endPoint, IPAddress localAddress, ILogger logger, string message, int totalSendCount) + { + Message = message; + _logger = logger; + TotalSendCount = totalSendCount; + LocalAddress = localAddress; + EndPoint = endPoint; + } + + public void Send() + { + var msg = Encoding.ASCII.GetBytes(Message); + try + { + var client = CreateSocket(); + + client.Bind(new IPEndPoint(LocalAddress, 0)); + + client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, EndPoint, result => + { + try + { + client.EndSend(result); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending Datagram", ex); + } + finally + { + try + { + client.Close(); + } + catch (Exception) + { + } + } + }, null); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending Datagram", ex); + } + ++SendCount; + } + + private Socket CreateSocket() + { + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + return socket; + } + } +} diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs new file mode 100644 index 0000000000..01393c6ce4 --- /dev/null +++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs @@ -0,0 +1,427 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Dlna.Server; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace MediaBrowser.Dlna.Ssdp +{ + public class SsdpHandler : IDisposable + { + private Socket _socket; + + private readonly ILogger _logger; + private readonly IServerConfigurationManager _config; + + const string SSDPAddr = "239.255.255.250"; + const int SSDPPort = 1900; + private readonly string _serverSignature; + + private readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr); + 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 _messageQueue = new ConcurrentQueue(); + + private bool _isDisposed; + private readonly ConcurrentDictionary> _devices = new ConcurrentDictionary>(); + + public SsdpHandler(ILogger logger, IServerConfigurationManager config, string serverSignature) + { + _logger = logger; + _config = config; + _serverSignature = serverSignature; + } + + public event EventHandler MessageReceived; + + private void OnMessageReceived(SsdpMessageEventArgs args) + { + if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase)) + { + RespondToSearch(args.EndPoint, args.Headers["st"]); + } + } + + public IEnumerable RegisteredDevices + { + get + { + return _devices.Values.SelectMany(i => i).ToList(); + } + } + + public void Start() + { + _socket = CreateMulticastSocket(); + + _logger.Info("SSDP service started"); + Receive(); + + StartNotificationTimer(); + } + + public void SendDatagram(string header, + Dictionary values, + IPAddress localAddress, + int sendCount = 1) + { + SendDatagram(header, values, _ssdpEndp, localAddress, sendCount); + } + + public void SendDatagram(string header, + Dictionary 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 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(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 + { + 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) + { + } + } + + private void ReceiveCallback(IAsyncResult result) + { + if (_isDisposed) + { + return; + } + + try + { + EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort); + var receivedCount = _socket.EndReceiveFrom(result, ref endpoint); + var received = (byte[])result.AsyncState; + + if (_config.Configuration.DlnaOptions.EnableDebugLogging) + { + _logger.Debug("{0} - SSDP Received a datagram", endpoint); + } + + using (var reader = new StreamReader(new MemoryStream(received), Encoding.ASCII)) + { + var proto = (reader.ReadLine() ?? string.Empty).Trim(); + var method = proto.Split(new[] { ' ' }, 2)[0]; + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var line = reader.ReadLine(); line != null; line = reader.ReadLine()) + { + line = line.Trim(); + if (string.IsNullOrEmpty(line)) + { + break; + } + var parts = line.Split(new[] { ':' }, 2); + + if (parts.Length >= 2) + { + headers[parts[0]] = parts[1].Trim(); + } + } + + if (_config.Configuration.DlnaOptions.EnableDebugLogging) + { + _logger.Debug("{0} - Datagram method: {1}", endpoint, method); + } + + OnMessageReceived(new SsdpMessageEventArgs + { + Method = method, + Headers = headers, + EndPoint = (IPEndPoint)endpoint + }); + } + } + catch (Exception ex) + { + _logger.ErrorException("Failed to read SSDP message", ex); + } + + if (_socket != null) + { + Receive(); + } + } + + public void Dispose() + { + _isDisposed = true; + while (_messageQueue.Count != 0) + { + _datagramPosted.WaitOne(); + } + + DisposeSocket(); + DisposeQueueTimer(); + DisposeNotificationTimer(); + + _datagramPosted.Dispose(); + } + + private void DisposeSocket() + { + if (_socket != null) + { + _socket.Close(); + _socket.Dispose(); + _socket = null; + } + } + + private void DisposeQueueTimer() + { + lock (_queueTimerSyncLock) + { + if (_queueTimer != null) + { + _queueTimer.Dispose(); + _queueTimer = null; + } + } + } + + 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)); + + socket.Bind(new IPEndPoint(IPAddress.Any, SSDPPort)); + + return socket; + } + + private void NotifyAll() + { + if (_config.Configuration.DlnaOptions.EnableDebugLogging) + { + _logger.Debug("Sending alive notifications"); + } + foreach (var d in RegisteredDevices) + { + NotifyDevice(d, "alive"); + } + } + + private void NotifyDevice(UpnpDevice dev, string type, int sendCount = 1) + { + const string header = "NOTIFY * HTTP/1.1"; + + var values = new Dictionary(StringComparer.OrdinalIgnoreCase); + + // 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(header, values, dev.Address, sendCount); + } + + public void RegisterNotification(Guid uuid, Uri descriptionUri, IPAddress address, IEnumerable services) + { + List list; + lock (_devices) + { + if (!_devices.TryGetValue(uuid, out list)) + { + _devices.TryAdd(uuid, list = new List()); + } + } + + list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address))); + + NotifyAll(); + _logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri); + } + + public void UnregisterNotification(Guid uuid) + { + List dl; + if (_devices.TryRemove(uuid, out dl)) + { + + foreach (var d in dl.ToList()) + { + NotifyDevice(d, "byebye", 2); + } + + _logger.Debug("Unregistered mount {0}", uuid); + } + } + + private readonly object _notificationTimerSyncLock = new object(); + private void StartNotificationTimer() + { + if (!_config.Configuration.DlnaOptions.BlastAliveMessages) + { + return; + } + + var intervalMs = _config.Configuration.DlnaOptions.BlastAliveMessageIntervalSeconds * 1000; + + lock (_notificationTimerSyncLock) + { + if (_notificationTimer == null) + { + _notificationTimer = new Timer(state => NotifyAll(), null, intervalMs, intervalMs); + } + else + { + _notificationTimer.Change(intervalMs, intervalMs); + } + } + } + + private void DisposeNotificationTimer() + { + lock (_notificationTimerSyncLock) + { + if (_notificationTimer != null) + { + _notificationTimer.Dispose(); + _notificationTimer = null; + } + } + } + } +} diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs b/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs index b22db781ad..2b5f386226 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHelper.cs +++ b/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"; - - /// - /// Creates a SSDP MSearch packet for DlnaRenderers. - /// - /// The mx. (Delaytime for device before responding) - /// - public static byte[] CreateRendererSSDP(int mx) - { - return Encoding.UTF8.GetBytes(string.Format(SsdpRenderer, mx)); - } - /// /// Parses the socket response into a location Uri for the DeviceDescription.xml. /// diff --git a/MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs b/MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs new file mode 100644 index 0000000000..4e60cfe2e3 --- /dev/null +++ b/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 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(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"); + } + } +} diff --git a/MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs b/MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs new file mode 100644 index 0000000000..d6368191b4 --- /dev/null +++ b/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 Headers { get; set; } + + public SsdpMessageEventArgs() + { + Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 1e8ca6f20c..b6bf9b183d 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -298,7 +298,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return MediaSource.Bitrate; + return MediaSource.Size; } if (RunTimeTicks.HasValue) -- cgit v1.2.3