mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-03 19:17:24 -05:00 
			
		
		
		
	improved device discovery
This commit is contained in:
		
							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>();
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
        private readonly List<Guid> _registeredServerIds = new List<Guid>();
 | 
			
		||||
        private bool _dlnaServerStarted;
 | 
			
		||||
 | 
			
		||||
        public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network)
 | 
			
		||||
        {
 | 
			
		||||
            Instance = this;
 | 
			
		||||
 | 
			
		||||
            _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()
 | 
			
		||||
        {
 | 
			
		||||
            var signature = GenerateServerSignature();
 | 
			
		||||
 | 
			
		||||
            lock (_syncLock)
 | 
			
		||||
        private void StartSsdpHandler()
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                    _ssdpHandler = new SsdpHandler(_logger, _config, signature);
 | 
			
		||||
                _ssdpHandler = new SsdpHandler(_logger, _config, GenerateServerSignature());
 | 
			
		||||
 | 
			
		||||
                    RegisterEndpoints();
 | 
			
		||||
                _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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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,9 +67,7 @@ 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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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,190 +11,153 @@ 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;
 | 
			
		||||
 | 
			
		||||
            Start();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<UpnpDevice> Devices
 | 
			
		||||
        public event EventHandler<SsdpMessageEventArgs> MessageReceived;
 | 
			
		||||
 | 
			
		||||
        private void OnMessageReceived(SsdpMessageEventArgs args)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
            {
 | 
			
		||||
                RespondToSearch(args.EndPoint, args.Headers["st"]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Receive()
 | 
			
		||||
        public void SendDatagram(string header,
 | 
			
		||||
            Dictionary<string, string> values,
 | 
			
		||||
            IPAddress localAddress,
 | 
			
		||||
            int sendCount = 1)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                _udpClient.BeginReceive(ReceiveCallback, null);
 | 
			
		||||
            SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
 | 
			
		||||
        }
 | 
			
		||||
            catch (ObjectDisposedException)
 | 
			
		||||
 | 
			
		||||
        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 ReceiveCallback(IAsyncResult result)
 | 
			
		||||
        private void RespondToSearch(IPEndPoint endpoint, string deviceType)
 | 
			
		||||
        {
 | 
			
		||||
            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;
 | 
			
		||||
                }
 | 
			
		||||
            const string header = "HTTP/1.1 200 OK";
 | 
			
		||||
 | 
			
		||||
                SendSearchResponse(endpoint, d);
 | 
			
		||||
            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 void SendSearchResponse(IPEndPoint endpoint, UpnpDevice dev)
 | 
			
		||||
        private readonly object _queueTimerSyncLock = new object();
 | 
			
		||||
        private void StartQueueTimer()
 | 
			
		||||
        {
 | 
			
		||||
            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());
 | 
			
		||||
            lock (_queueTimerSyncLock)
 | 
			
		||||
            {
 | 
			
		||||
                if (_queueTimer == null)
 | 
			
		||||
                {
 | 
			
		||||
                    _queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        private void SendDatagram(IPEndPoint endpoint, IPAddress localAddress, string msg, bool sticky)
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
            if (_isDisposed)
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
                    _queueTimer.Change(1000, Timeout.Infinite);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            var dgram = new Datagram(endpoint, localAddress, _logger, msg, sticky);
 | 
			
		||||
            if (_messageQueue.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                dgram.Send();
 | 
			
		||||
            }
 | 
			
		||||
            _messageQueue.Enqueue(dgram);
 | 
			
		||||
            StartQueueTimer();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void QueueTimerCallback(object state)
 | 
			
		||||
@ -206,10 +170,10 @@ namespace MediaBrowser.Dlna.Server
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (msg != null && (!_isDisposed || msg.Sticky))
 | 
			
		||||
                if (msg != null && (!_isDisposed || msg.TotalSendCount > 1))
 | 
			
		||||
                {
 | 
			
		||||
                    msg.Send();
 | 
			
		||||
                    if (msg.SendCount > 2)
 | 
			
		||||
                    if (msg.SendCount > msg.TotalSendCount)
 | 
			
		||||
                    {
 | 
			
		||||
                        _messageQueue.TryDequeue(out msg);
 | 
			
		||||
                    }
 | 
			
		||||
@ -231,84 +195,81 @@ namespace MediaBrowser.Dlna.Server
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void NotifyAll()
 | 
			
		||||
        private void Receive()
 | 
			
		||||
        {
 | 
			
		||||
            if (_config.Configuration.DlnaOptions.EnableDebugLogging)
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                _logger.Debug("Sending alive notifications");
 | 
			
		||||
                var buffer = new byte[1024];
 | 
			
		||||
 | 
			
		||||
                EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
 | 
			
		||||
 | 
			
		||||
                _socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var d in Devices)
 | 
			
		||||
            catch (ObjectDisposedException)
 | 
			
		||||
            {
 | 
			
		||||
                NotifyDevice(d, "alive", false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void NotifyDevice(UpnpDevice dev, string type, bool sticky)
 | 
			
		||||
        private void ReceiveCallback(IAsyncResult result)
 | 
			
		||||
        {
 | 
			
		||||
            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<UpnpDevice> list;
 | 
			
		||||
            lock (_devices)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_devices.TryGetValue(uuid, out list))
 | 
			
		||||
                {
 | 
			
		||||
                    _devices.Add(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));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            NotifyAll();
 | 
			
		||||
            _logger.Debug("Registered mount {0} at {1}", uuid, descriptor);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void UnregisterNotification(Guid uuid)
 | 
			
		||||
        {
 | 
			
		||||
            List<UpnpDevice> dl;
 | 
			
		||||
            lock (_devices)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_devices.TryGetValue(uuid, out dl))
 | 
			
		||||
            if (_isDisposed)
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
                _devices.Remove(uuid);
 | 
			
		||||
            }
 | 
			
		||||
            foreach (var d in dl)
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                NotifyDevice(d, "byebye", true);
 | 
			
		||||
                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<string, string>(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();
 | 
			
		||||
            }
 | 
			
		||||
            _logger.Debug("Unregistered mount {0}", uuid);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
@ -319,27 +280,20 @@ namespace MediaBrowser.Dlna.Server
 | 
			
		||||
                _datagramPosted.WaitOne();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _udpClient.DropMulticastGroup(_ssdpIp);
 | 
			
		||||
            _udpClient.Close();
 | 
			
		||||
 | 
			
		||||
            DisposeNotificationTimer();
 | 
			
		||||
            DisposeSocket();
 | 
			
		||||
            DisposeQueueTimer();
 | 
			
		||||
            DisposeNotificationTimer();
 | 
			
		||||
 | 
			
		||||
            _datagramPosted.Dispose();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly object _queueTimerSyncLock = new object();
 | 
			
		||||
        private void StartQueueTimer()
 | 
			
		||||
        private void DisposeSocket()
 | 
			
		||||
        {
 | 
			
		||||
            lock (_queueTimerSyncLock)
 | 
			
		||||
            if (_socket != null)
 | 
			
		||||
            {
 | 
			
		||||
                if (_queueTimer == null)
 | 
			
		||||
                {
 | 
			
		||||
                    _queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    _queueTimer.Change(1000, Timeout.Infinite);
 | 
			
		||||
                }
 | 
			
		||||
                _socket.Close();
 | 
			
		||||
                _socket.Dispose();
 | 
			
		||||
                _socket = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -355,6 +309,86 @@ namespace MediaBrowser.Dlna.Server
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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<string, string>(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<string> services)
 | 
			
		||||
        {
 | 
			
		||||
            List<UpnpDevice> list;
 | 
			
		||||
            lock (_devices)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_devices.TryGetValue(uuid, out list))
 | 
			
		||||
                {
 | 
			
		||||
                    _devices.TryAdd(uuid, list = new List<UpnpDevice>());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            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<UpnpDevice> 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()
 | 
			
		||||
        {
 | 
			
		||||
@ -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
									
								
								MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								MediaBrowser.Dlna/Ssdp/SsdpMessageBuilder.cs
									
									
									
									
									
										Normal file
									
								
							@ -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
									
								
								MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								MediaBrowser.Dlna/Ssdp/SsdpMessageEventArgs.cs
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user