mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-07 15:44:38 -04:00
improved device discovery
This commit is contained in:
parent
1544b7bf9c
commit
eda8159b44
@ -93,7 +93,8 @@ namespace MediaBrowser.Api.Dlna
|
|||||||
{
|
{
|
||||||
Headers = GetRequestHeaders(),
|
Headers = GetRequestHeaders(),
|
||||||
InputXml = await reader.ReadToEndAsync().ConfigureAwait(false),
|
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.Controller.LiveTv;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback
|
namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,8 @@ namespace MediaBrowser.Controller.Dlna
|
|||||||
|
|
||||||
public string TargetServerUuId { get; set; }
|
public string TargetServerUuId { get; set; }
|
||||||
|
|
||||||
|
public string RequestedUrl { get; set; }
|
||||||
|
|
||||||
public ControlRequest()
|
public ControlRequest()
|
||||||
{
|
{
|
||||||
Headers = new Dictionary<string, string>();
|
Headers = new Dictionary<string, string>();
|
||||||
|
@ -450,9 +450,14 @@ namespace MediaBrowser.Dlna.Didl
|
|||||||
|
|
||||||
private void AddPeople(BaseItem item, XmlElement element)
|
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)
|
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);
|
_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;
|
return profile;
|
||||||
}
|
}
|
||||||
|
@ -3,81 +3,102 @@ using MediaBrowser.Common.Extensions;
|
|||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
|
using MediaBrowser.Dlna.Ssdp;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
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 IServerConfigurationManager _config;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private SsdpHandler _ssdpHandler;
|
|
||||||
private readonly IApplicationHost _appHost;
|
private readonly IApplicationHost _appHost;
|
||||||
private readonly INetworkManager _network;
|
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;
|
_config = config;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_network = network;
|
_network = network;
|
||||||
_logger = logManager.GetLogger("DlnaServer");
|
_logger = logManager.GetLogger("Dlna");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
_config.ConfigurationUpdated += ConfigurationUpdated;
|
StartSsdpHandler();
|
||||||
|
ReloadComponents();
|
||||||
|
|
||||||
ReloadServer();
|
_config.ConfigurationUpdated += ConfigurationUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigurationUpdated(object sender, EventArgs e)
|
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)
|
if (_config.Configuration.DlnaOptions.EnableServer && !isStarted)
|
||||||
{
|
{
|
||||||
StartServer();
|
StartDlnaServer();
|
||||||
}
|
}
|
||||||
else if (!_config.Configuration.DlnaOptions.EnableServer && isStarted)
|
else if (!_config.Configuration.DlnaOptions.EnableServer && isStarted)
|
||||||
{
|
{
|
||||||
DisposeServer();
|
DisposeDlnaServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object _syncLock = new object();
|
private void StartSsdpHandler()
|
||||||
private void StartServer()
|
|
||||||
{
|
|
||||||
var signature = GenerateServerSignature();
|
|
||||||
|
|
||||||
lock (_syncLock)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ssdpHandler = new SsdpHandler(_logger, _config, signature);
|
_ssdpHandler = new SsdpHandler(_logger, _config, GenerateServerSignature());
|
||||||
|
|
||||||
RegisterEndpoints();
|
_ssdpHandler.Start();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Error starting Dlna server", 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())
|
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));
|
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)
|
_ssdpHandler.RegisterNotification(guid, uri, IPAddress.Parse(address), services);
|
||||||
{
|
|
||||||
return _ssdpHandler.Devices.FirstOrDefault(i => string.Equals(uuid, i.Uuid.ToString("N"), StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeServer()
|
_registeredServerIds.Add(guid);
|
||||||
{
|
|
||||||
lock (_syncLock)
|
|
||||||
{
|
|
||||||
if (_ssdpHandler != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ssdpHandler.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error disposing Dlna server", ex);
|
|
||||||
}
|
|
||||||
_ssdpHandler = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +147,27 @@ namespace MediaBrowser.Dlna.Server
|
|||||||
|
|
||||||
public void Dispose()
|
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="DlnaManager.cs" />
|
||||||
<Compile Include="Common\Argument.cs" />
|
<Compile Include="Common\Argument.cs" />
|
||||||
<Compile Include="Eventing\EventManager.cs" />
|
<Compile Include="Eventing\EventManager.cs" />
|
||||||
|
<Compile Include="Main\DlnaEntryPoint.cs" />
|
||||||
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
|
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
|
||||||
<Compile Include="PlayTo\Device.cs">
|
<Compile Include="PlayTo\Device.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
@ -79,7 +80,7 @@
|
|||||||
<Compile Include="Server\ControlHandler.cs" />
|
<Compile Include="Server\ControlHandler.cs" />
|
||||||
<Compile Include="Server\ServiceActionListBuilder.cs" />
|
<Compile Include="Server\ServiceActionListBuilder.cs" />
|
||||||
<Compile Include="Server\ContentDirectoryXmlBuilder.cs" />
|
<Compile Include="Server\ContentDirectoryXmlBuilder.cs" />
|
||||||
<Compile Include="Server\Datagram.cs" />
|
<Compile Include="Ssdp\Datagram.cs" />
|
||||||
<Compile Include="Server\DescriptionXmlBuilder.cs" />
|
<Compile Include="Server\DescriptionXmlBuilder.cs" />
|
||||||
<Compile Include="Ssdp\SsdpHelper.cs" />
|
<Compile Include="Ssdp\SsdpHelper.cs" />
|
||||||
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
||||||
@ -108,10 +109,11 @@
|
|||||||
<Compile Include="Profiles\Xbox360Profile.cs" />
|
<Compile Include="Profiles\Xbox360Profile.cs" />
|
||||||
<Compile Include="Profiles\XboxOneProfile.cs" />
|
<Compile Include="Profiles\XboxOneProfile.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Server\DlnaServerEntryPoint.cs" />
|
|
||||||
<Compile Include="Server\Headers.cs" />
|
<Compile Include="Server\Headers.cs" />
|
||||||
<Compile Include="Server\SsdpHandler.cs" />
|
|
||||||
<Compile Include="Server\UpnpDevice.cs" />
|
<Compile Include="Server\UpnpDevice.cs" />
|
||||||
|
<Compile Include="Ssdp\SsdpMessageBuilder.cs" />
|
||||||
|
<Compile Include="Ssdp\SsdpMessageEventArgs.cs" />
|
||||||
|
<Compile Include="Ssdp\SsdpHandler.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||||
|
@ -50,7 +50,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||||||
if (_device == null || _device.UpdateTime == default(DateTime))
|
if (_device == null || _device.UpdateTime == default(DateTime))
|
||||||
return false;
|
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;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
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);
|
_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;
|
continue;
|
||||||
|
|
||||||
var ipV4 = network.GetIPProperties().GetIPv4Properties();
|
var ipV4 = network.GetIPProperties().GetIPv4Properties();
|
||||||
@ -84,7 +85,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CreateListener(localIp);
|
CreateListener(localIp, ipV4.Index);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -111,15 +112,15 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||||||
/// Creates a socket for the interface and listends for data.
|
/// Creates a socket for the interface and listends for data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="localIp">The local ip.</param>
|
/// <param name="localIp">The local ip.</param>
|
||||||
private void CreateListener(IPAddress localIp)
|
private void CreateListener(IPAddress localIp, int networkInterfaceIndex)
|
||||||
{
|
{
|
||||||
Task.Factory.StartNew(async (o) =>
|
Task.Factory.StartNew(async (o) =>
|
||||||
{
|
{
|
||||||
try
|
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");
|
_logger.Info("Creating SSDP listener");
|
||||||
|
|
||||||
@ -183,7 +184,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = SsdpHelper.CreateRendererSSDP(3);
|
var msg = new SsdpMessageBuilder().BuildRendererDiscoveryMessage();
|
||||||
|
var request = Encoding.UTF8.GetBytes(msg);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@ -210,12 +212,12 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||||||
/// Gets a socket configured for SDDP multicasting.
|
/// Gets a socket configured for SDDP multicasting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private Socket GetMulticastSocket()
|
private Socket GetMulticastSocket(int networkInterfaceIndex)
|
||||||
{
|
{
|
||||||
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
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.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), networkInterfaceIndex));
|
||||||
//socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 3);
|
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.Xml.Serialization;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Profiles
|
namespace MediaBrowser.Dlna.Profiles
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,17 @@ namespace MediaBrowser.Dlna.Profiles
|
|||||||
|
|
||||||
Identification = new DeviceIdentification
|
Identification = new DeviceIdentification
|
||||||
{
|
{
|
||||||
ModelUrl = "samsung.com"
|
ModelUrl = "samsung.com",
|
||||||
|
|
||||||
|
Headers = new[]
|
||||||
|
{
|
||||||
|
new HttpHeaderInfo
|
||||||
|
{
|
||||||
|
Name = "User-Agent",
|
||||||
|
Value = @"SEC_",
|
||||||
|
Match = HeaderMatchType.Substring
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
XmlRootAttributes = new[]
|
XmlRootAttributes = new[]
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.Xml.Serialization;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Profiles
|
namespace MediaBrowser.Dlna.Profiles
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.Xml.Serialization;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Profiles
|
namespace MediaBrowser.Dlna.Profiles
|
||||||
{
|
{
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
<Name>Samsung Smart TV</Name>
|
<Name>Samsung Smart TV</Name>
|
||||||
<Identification>
|
<Identification>
|
||||||
<ModelUrl>samsung.com</ModelUrl>
|
<ModelUrl>samsung.com</ModelUrl>
|
||||||
<Headers />
|
<Headers>
|
||||||
|
<HttpHeaderInfo name="User-Agent" value="SEC_" match="Substring" />
|
||||||
|
</Headers>
|
||||||
</Identification>
|
</Identification>
|
||||||
<FriendlyName>Media Browser</FriendlyName>
|
<FriendlyName>Media Browser</FriendlyName>
|
||||||
<Manufacturer>Media Browser</Manufacturer>
|
<Manufacturer>Media Browser</Manufacturer>
|
||||||
|
@ -67,9 +67,7 @@ namespace MediaBrowser.Dlna.Server
|
|||||||
var profile = _dlna.GetProfile(request.Headers) ??
|
var profile = _dlna.GetProfile(request.Headers) ??
|
||||||
_dlna.GetDefaultProfile();
|
_dlna.GetDefaultProfile();
|
||||||
|
|
||||||
var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId);
|
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
var user = GetUser(profile);
|
var user = GetUser(profile);
|
||||||
|
|
||||||
|
@ -4,24 +4,31 @@ using System.Net;
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Server
|
namespace MediaBrowser.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
public class Datagram
|
public class Datagram
|
||||||
{
|
{
|
||||||
public IPEndPoint EndPoint { get; private set; }
|
public IPEndPoint EndPoint { get; private set; }
|
||||||
public IPAddress LocalAddress { get; private set; }
|
public IPAddress LocalAddress { get; private set; }
|
||||||
public string Message { 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; }
|
public int SendCount { get; private set; }
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
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;
|
Message = message;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
Sticky = sticky;
|
TotalSendCount = totalSendCount;
|
||||||
LocalAddress = localAddress;
|
LocalAddress = localAddress;
|
||||||
EndPoint = endPoint;
|
EndPoint = endPoint;
|
||||||
}
|
}
|
||||||
@ -31,9 +38,11 @@ namespace MediaBrowser.Dlna.Server
|
|||||||
var msg = Encoding.ASCII.GetBytes(Message);
|
var msg = Encoding.ASCII.GetBytes(Message);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var client = new UdpClient();
|
var client = CreateSocket();
|
||||||
client.Client.Bind(new IPEndPoint(LocalAddress, 0));
|
|
||||||
client.BeginSend(msg, msg.Length, EndPoint, result =>
|
client.Bind(new IPEndPoint(LocalAddress, 0));
|
||||||
|
|
||||||
|
client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, EndPoint, result =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -61,5 +70,13 @@ namespace MediaBrowser.Dlna.Server
|
|||||||
}
|
}
|
||||||
++SendCount;
|
++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.Controller.Configuration;
|
||||||
|
using MediaBrowser.Dlna.Server;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@ -10,190 +11,153 @@ using System.Net.Sockets;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Server
|
namespace MediaBrowser.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
public class SsdpHandler : IDisposable
|
public class SsdpHandler : IDisposable
|
||||||
{
|
{
|
||||||
private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
|
private Socket _socket;
|
||||||
private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly string _serverSignature;
|
|
||||||
private bool _isDisposed;
|
|
||||||
|
|
||||||
const string SSDPAddr = "239.255.255.250";
|
const string SSDPAddr = "239.255.255.250";
|
||||||
const int SSDPPort = 1900;
|
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 readonly IPAddress _ssdpIp = IPAddress.Parse(SSDPAddr);
|
||||||
|
private readonly IPEndPoint _ssdpEndp = new IPEndPoint(IPAddress.Parse(SSDPAddr), SSDPPort);
|
||||||
private UdpClient _udpClient;
|
|
||||||
|
|
||||||
private readonly Dictionary<Guid, List<UpnpDevice>> _devices = new Dictionary<Guid, List<UpnpDevice>>();
|
|
||||||
|
|
||||||
private Timer _queueTimer;
|
private Timer _queueTimer;
|
||||||
private Timer _notificationTimer;
|
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)
|
public SsdpHandler(ILogger logger, IServerConfigurationManager config, string serverSignature)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_serverSignature = serverSignature;
|
_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
|
get
|
||||||
{
|
{
|
||||||
UpnpDevice[] devs;
|
return _devices.Values.SelectMany(i => i).ToList();
|
||||||
lock (_devices)
|
|
||||||
{
|
|
||||||
devs = _devices.Values.SelectMany(i => i).ToArray();
|
|
||||||
}
|
|
||||||
return devs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
_udpClient = new UdpClient();
|
_socket = CreateMulticastSocket();
|
||||||
_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");
|
_logger.Info("SSDP service started");
|
||||||
Receive();
|
Receive();
|
||||||
|
|
||||||
StartNotificationTimer();
|
StartNotificationTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Receive()
|
public void SendDatagram(string header,
|
||||||
|
Dictionary<string, string> values,
|
||||||
|
IPAddress localAddress,
|
||||||
|
int sendCount = 1)
|
||||||
{
|
{
|
||||||
try
|
SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
|
||||||
{
|
|
||||||
_udpClient.BeginReceive(ReceiveCallback, null);
|
|
||||||
}
|
}
|
||||||
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)
|
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
|
||||||
{
|
{
|
||||||
_logger.Debug("RespondToSearch");
|
_logger.Debug("RespondToSearch");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var d in Devices)
|
const string header = "HTTP/1.1 200 OK";
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(req) && !string.Equals(req, d.Type, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
lock (_queueTimerSyncLock)
|
||||||
|
{
|
||||||
const string argFormat = "{0}: {1}\r\n";
|
if (_queueTimer == null)
|
||||||
|
{
|
||||||
builder.Append("HTTP/1.1 200 OK\r\n");
|
_queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void SendDatagram(IPEndPoint endpoint, IPAddress localAddress, string msg, bool sticky)
|
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
_queueTimer.Change(1000, Timeout.Infinite);
|
||||||
{
|
|
||||||
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)
|
private void QueueTimerCallback(object state)
|
||||||
@ -206,10 +170,10 @@ namespace MediaBrowser.Dlna.Server
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg != null && (!_isDisposed || msg.Sticky))
|
if (msg != null && (!_isDisposed || msg.TotalSendCount > 1))
|
||||||
{
|
{
|
||||||
msg.Send();
|
msg.Send();
|
||||||
if (msg.SendCount > 2)
|
if (msg.SendCount > msg.TotalSendCount)
|
||||||
{
|
{
|
||||||
_messageQueue.TryDequeue(out msg);
|
_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();
|
if (_isDisposed)
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_devices.Remove(uuid);
|
|
||||||
}
|
try
|
||||||
foreach (var d in dl)
|
|
||||||
{
|
{
|
||||||
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()
|
public void Dispose()
|
||||||
@ -319,27 +280,20 @@ namespace MediaBrowser.Dlna.Server
|
|||||||
_datagramPosted.WaitOne();
|
_datagramPosted.WaitOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
_udpClient.DropMulticastGroup(_ssdpIp);
|
DisposeSocket();
|
||||||
_udpClient.Close();
|
|
||||||
|
|
||||||
DisposeNotificationTimer();
|
|
||||||
DisposeQueueTimer();
|
DisposeQueueTimer();
|
||||||
|
DisposeNotificationTimer();
|
||||||
|
|
||||||
_datagramPosted.Dispose();
|
_datagramPosted.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object _queueTimerSyncLock = new object();
|
private void DisposeSocket()
|
||||||
private void StartQueueTimer()
|
|
||||||
{
|
{
|
||||||
lock (_queueTimerSyncLock)
|
if (_socket != null)
|
||||||
{
|
{
|
||||||
if (_queueTimer == null)
|
_socket.Close();
|
||||||
{
|
_socket.Dispose();
|
||||||
_queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
|
_socket = null;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_queueTimer.Change(1000, Timeout.Infinite);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 readonly object _notificationTimerSyncLock = new object();
|
||||||
private void StartNotificationTimer()
|
private void StartNotificationTimer()
|
||||||
{
|
{
|
@ -7,24 +7,6 @@ namespace MediaBrowser.Dlna.Ssdp
|
|||||||
{
|
{
|
||||||
public class SsdpHelper
|
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>
|
/// <summary>
|
||||||
/// Parses the socket response into a location Uri for the DeviceDescription.xml.
|
/// Parses the socket response into a location Uri for the DeviceDescription.xml.
|
||||||
/// </summary>
|
/// </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)
|
if (IsDirectStream)
|
||||||
{
|
{
|
||||||
return MediaSource.Bitrate;
|
return MediaSource.Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RunTimeTicks.HasValue)
|
if (RunTimeTicks.HasValue)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user