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>();
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace MediaBrowser.Dlna.Didl
|
|||
public class DidlBuilder
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
|
||||
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
||||
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
|
||||
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||
|
@ -298,7 +298,7 @@ namespace MediaBrowser.Dlna.Didl
|
|||
|
||||
container.AppendChild(res);
|
||||
}
|
||||
|
||||
|
||||
public XmlElement GetFolderElement(XmlDocument doc, Folder folder, int childCount, Filter filter)
|
||||
{
|
||||
var container = doc.CreateElement(string.Empty, "container", NS_DIDL);
|
||||
|
@ -450,9 +450,14 @@ namespace MediaBrowser.Dlna.Didl
|
|||
|
||||
private void AddPeople(BaseItem item, XmlElement element)
|
||||
{
|
||||
var types = new[] { PersonType.Director, PersonType.Writer, PersonType.Producer, PersonType.Composer, "Creator" };
|
||||
|
||||
foreach (var actor in item.People)
|
||||
{
|
||||
AddValue(element, "upnp", (actor.Type ?? PersonType.Actor).ToLower(), actor.Name, NS_UPNP);
|
||||
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
||||
?? PersonType.Actor;
|
||||
|
||||
AddValue(element, "upnp", type.ToLower(), actor.Name, NS_UPNP);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,10 +25,10 @@ namespace MediaBrowser.Dlna
|
|||
private readonly ILogger _logger;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
|
||||
public DlnaManager(IXmlSerializer xmlSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILogger logger,
|
||||
public DlnaManager(IXmlSerializer xmlSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILogger logger,
|
||||
IJsonSerializer jsonSerializer)
|
||||
{
|
||||
_xmlSerializer = xmlSerializer;
|
||||
|
@ -230,6 +230,19 @@ namespace MediaBrowser.Dlna
|
|||
{
|
||||
_logger.Debug("Found matching device profile: {0}", profile.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
string userAgent = null;
|
||||
headers.TryGetValue("User-Agent", out userAgent);
|
||||
|
||||
var msg = "No matching device profile found. The default will be used. ";
|
||||
if (!string.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
msg += "User-agent: " + userAgent + ". ";
|
||||
}
|
||||
|
||||
_logger.Debug(msg);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
|
|
@ -3,81 +3,102 @@ using MediaBrowser.Common.Extensions;
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Dlna.Ssdp;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
namespace MediaBrowser.Dlna.Main
|
||||
{
|
||||
public class DlnaServerEntryPoint : IServerEntryPoint
|
||||
public class DlnaEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private SsdpHandler _ssdpHandler;
|
||||
private readonly IApplicationHost _appHost;
|
||||
private readonly INetworkManager _network;
|
||||
|
||||
public static DlnaServerEntryPoint Instance;
|
||||
private SsdpHandler _ssdpHandler;
|
||||
|
||||
public DlnaServerEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network)
|
||||
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()
|
||||
private void StartSsdpHandler()
|
||||
{
|
||||
var signature = GenerateServerSignature();
|
||||
|
||||
lock (_syncLock)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
_ssdpHandler = new SsdpHandler(_logger, _config, signature);
|
||||
_ssdpHandler = new SsdpHandler(_logger, _config, GenerateServerSignature());
|
||||
|
||||
RegisterEndpoints();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error starting Dlna server", ex);
|
||||
}
|
||||
_ssdpHandler.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error starting Dlna server", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterEndpoints()
|
||||
private void DisposeSsdpHandler()
|
||||
{
|
||||
try
|
||||
{
|
||||
_ssdpHandler.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error disposing ssdp handler", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void StartDlnaServer()
|
||||
{
|
||||
try
|
||||
{
|
||||
RegisterServerEndpoints();
|
||||
|
||||
_dlnaServerStarted = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error registering endpoint", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterServerEndpoints()
|
||||
{
|
||||
foreach (var address in _network.GetLocalIpAddresses())
|
||||
{
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
public UpnpDevice GetServerUpnpDevice(string uuid)
|
||||
{
|
||||
return _ssdpHandler.Devices.FirstOrDefault(i => string.Equals(uuid, i.Uuid.ToString("N"), StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private void DisposeServer()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_ssdpHandler != null)
|
||||
var services = new List<string>
|
||||
{
|
||||
try
|
||||
{
|
||||
_ssdpHandler.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error disposing Dlna server", ex);
|
||||
}
|
||||
_ssdpHandler = null;
|
||||
}
|
||||
"upnp:rootdevice",
|
||||
"urn:schemas-upnp-org:device:MediaServer:1",
|
||||
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||
"uuid:" + guid.ToString("N")
|
||||
};
|
||||
|
||||
_ssdpHandler.RegisterNotification(guid, uri, IPAddress.Parse(address), services);
|
||||
|
||||
_registeredServerIds.Add(guid);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,7 +147,27 @@ namespace MediaBrowser.Dlna.Server
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeServer();
|
||||
DisposeDlnaServer();
|
||||
DisposeSsdpHandler();
|
||||
}
|
||||
|
||||
public void DisposeDlnaServer()
|
||||
{
|
||||
foreach (var id in _registeredServerIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ssdpHandler.UnregisterNotification(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error unregistering server", ex);
|
||||
}
|
||||
}
|
||||
|
||||
_registeredServerIds.Clear();
|
||||
|
||||
_dlnaServerStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,6 +54,7 @@
|
|||
<Compile Include="DlnaManager.cs" />
|
||||
<Compile Include="Common\Argument.cs" />
|
||||
<Compile Include="Eventing\EventManager.cs" />
|
||||
<Compile Include="Main\DlnaEntryPoint.cs" />
|
||||
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
|
||||
<Compile Include="PlayTo\Device.cs">
|
||||
<SubType>Code</SubType>
|
||||
|
@ -79,7 +80,7 @@
|
|||
<Compile Include="Server\ControlHandler.cs" />
|
||||
<Compile Include="Server\ServiceActionListBuilder.cs" />
|
||||
<Compile Include="Server\ContentDirectoryXmlBuilder.cs" />
|
||||
<Compile Include="Server\Datagram.cs" />
|
||||
<Compile Include="Ssdp\Datagram.cs" />
|
||||
<Compile Include="Server\DescriptionXmlBuilder.cs" />
|
||||
<Compile Include="Ssdp\SsdpHelper.cs" />
|
||||
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
||||
|
@ -108,10 +109,11 @@
|
|||
<Compile Include="Profiles\Xbox360Profile.cs" />
|
||||
<Compile Include="Profiles\XboxOneProfile.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Server\DlnaServerEntryPoint.cs" />
|
||||
<Compile Include="Server\Headers.cs" />
|
||||
<Compile Include="Server\SsdpHandler.cs" />
|
||||
<Compile Include="Server\UpnpDevice.cs" />
|
||||
<Compile Include="Ssdp\SsdpMessageBuilder.cs" />
|
||||
<Compile Include="Ssdp\SsdpMessageEventArgs.cs" />
|
||||
<Compile Include="Ssdp\SsdpHandler.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
if (_device == null || _device.UpdateTime == default(DateTime))
|
||||
return false;
|
||||
|
||||
return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
|
||||
return DateTime.UtcNow <= _device.UpdateTime.AddMinutes(10);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using System.Text;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
|
@ -68,7 +69,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
{
|
||||
_logger.Debug("Found interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
|
||||
|
||||
if (!network.SupportsMulticast || !network.GetIPProperties().MulticastAddresses.Any())
|
||||
if (!network.SupportsMulticast || OperationalStatus.Up != network.OperationalStatus || !network.GetIPProperties().MulticastAddresses.Any())
|
||||
continue;
|
||||
|
||||
var ipV4 = network.GetIPProperties().GetIPv4Properties();
|
||||
|
@ -84,7 +85,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
{
|
||||
try
|
||||
{
|
||||
CreateListener(localIp);
|
||||
CreateListener(localIp, ipV4.Index);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -111,15 +112,15 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
/// Creates a socket for the interface and listends for data.
|
||||
/// </summary>
|
||||
/// <param name="localIp">The local ip.</param>
|
||||
private void CreateListener(IPAddress localIp)
|
||||
private void CreateListener(IPAddress localIp, int networkInterfaceIndex)
|
||||
{
|
||||
Task.Factory.StartNew(async (o) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var socket = GetMulticastSocket();
|
||||
var socket = GetMulticastSocket(networkInterfaceIndex);
|
||||
|
||||
socket.Bind(new IPEndPoint(localIp, 0));
|
||||
socket.Bind(new IPEndPoint(localIp, 1900));
|
||||
|
||||
_logger.Info("Creating SSDP listener");
|
||||
|
||||
|
@ -183,7 +184,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
{
|
||||
try
|
||||
{
|
||||
var request = SsdpHelper.CreateRendererSSDP(3);
|
||||
var msg = new SsdpMessageBuilder().BuildRendererDiscoveryMessage();
|
||||
var request = Encoding.UTF8.GetBytes(msg);
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
@ -210,12 +212,12 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
/// Gets a socket configured for SDDP multicasting.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Socket GetMulticastSocket()
|
||||
private Socket GetMulticastSocket(int networkInterfaceIndex)
|
||||
{
|
||||
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250")));
|
||||
//socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 3);
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), networkInterfaceIndex));
|
||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
|
||||
return socket;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
|
|
@ -14,7 +14,17 @@ namespace MediaBrowser.Dlna.Profiles
|
|||
|
||||
Identification = new DeviceIdentification
|
||||
{
|
||||
ModelUrl = "samsung.com"
|
||||
ModelUrl = "samsung.com",
|
||||
|
||||
Headers = new[]
|
||||
{
|
||||
new HttpHeaderInfo
|
||||
{
|
||||
Name = "User-Agent",
|
||||
Value = @"SEC_",
|
||||
Match = HeaderMatchType.Substring
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XmlRootAttributes = new[]
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<Name>Samsung Smart TV</Name>
|
||||
<Identification>
|
||||
<ModelUrl>samsung.com</ModelUrl>
|
||||
<Headers />
|
||||
<Headers>
|
||||
<HttpHeaderInfo name="User-Agent" value="SEC_" match="Substring" />
|
||||
</Headers>
|
||||
</Identification>
|
||||
<FriendlyName>Media Browser</FriendlyName>
|
||||
<Manufacturer>Media Browser</Manufacturer>
|
||||
|
|
|
@ -67,10 +67,8 @@ namespace MediaBrowser.Dlna.Server
|
|||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId);
|
||||
|
||||
var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var user = GetUser(profile);
|
||||
|
||||
return new ControlHandler(
|
||||
|
|
|
@ -4,24 +4,31 @@ using System.Net;
|
|||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
namespace MediaBrowser.Dlna.Ssdp
|
||||
{
|
||||
public class Datagram
|
||||
{
|
||||
public IPEndPoint EndPoint { get; private set; }
|
||||
public IPAddress LocalAddress { get; private set; }
|
||||
public string Message { get; private set; }
|
||||
public bool Sticky { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of times to send the message
|
||||
/// </summary>
|
||||
public int TotalSendCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of times the message has been sent
|
||||
/// </summary>
|
||||
public int SendCount { get; private set; }
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public Datagram(IPEndPoint endPoint, IPAddress localAddress, ILogger logger, string message, bool sticky)
|
||||
public Datagram(IPEndPoint endPoint, IPAddress localAddress, ILogger logger, string message, int totalSendCount)
|
||||
{
|
||||
Message = message;
|
||||
_logger = logger;
|
||||
Sticky = sticky;
|
||||
TotalSendCount = totalSendCount;
|
||||
LocalAddress = localAddress;
|
||||
EndPoint = endPoint;
|
||||
}
|
||||
|
@ -31,9 +38,11 @@ namespace MediaBrowser.Dlna.Server
|
|||
var msg = Encoding.ASCII.GetBytes(Message);
|
||||
try
|
||||
{
|
||||
var client = new UdpClient();
|
||||
client.Client.Bind(new IPEndPoint(LocalAddress, 0));
|
||||
client.BeginSend(msg, msg.Length, EndPoint, result =>
|
||||
var client = CreateSocket();
|
||||
|
||||
client.Bind(new IPEndPoint(LocalAddress, 0));
|
||||
|
||||
client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, EndPoint, result =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -61,5 +70,13 @@ namespace MediaBrowser.Dlna.Server
|
|||
}
|
||||
++SendCount;
|
||||
}
|
||||
|
||||
private Socket CreateSocket()
|
||||
{
|
||||
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
|
||||
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Dlna.Server;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
@ -10,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)
|
||||
{
|
||||
get
|
||||
if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
UpnpDevice[] devs;
|
||||
lock (_devices)
|
||||
{
|
||||
devs = _devices.Values.SelectMany(i => i).ToArray();
|
||||
}
|
||||
return devs;
|
||||
RespondToSearch(args.EndPoint, args.Headers["st"]);
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
public IEnumerable<UpnpDevice> RegisteredDevices
|
||||
{
|
||||
_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);
|
||||
get
|
||||
{
|
||||
return _devices.Values.SelectMany(i => i).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_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
|
||||
SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
|
||||
}
|
||||
|
||||
public void SendDatagram(string header,
|
||||
Dictionary<string, string> values,
|
||||
IPEndPoint endpoint,
|
||||
IPAddress localAddress,
|
||||
int sendCount = 1)
|
||||
{
|
||||
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
|
||||
|
||||
var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount);
|
||||
if (_messageQueue.Count == 0)
|
||||
{
|
||||
_udpClient.BeginReceive(ReceiveCallback, null);
|
||||
dgram.Send();
|
||||
return;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
|
||||
_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)
|
||||
const string header = "HTTP/1.1 200 OK";
|
||||
|
||||
foreach (var d in RegisteredDevices)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(req) && !string.Equals(req, d.Type, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
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());
|
||||
}
|
||||
|
||||
SendSearchResponse(endpoint, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
private void SendDatagram(IPEndPoint endpoint, IPAddress localAddress, string msg, bool sticky)
|
||||
{
|
||||
if (_isDisposed)
|
||||
lock (_queueTimerSyncLock)
|
||||
{
|
||||
return;
|
||||
if (_queueTimer == null)
|
||||
{
|
||||
_queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
_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)
|
||||
if (_isDisposed)
|
||||
{
|
||||
_logger.Debug("{0} said {1}", dev.USN, type);
|
||||
return;
|
||||
}
|
||||
|
||||
SendDatagram(_ssdpEndp, dev.Address, builder.ToString(), sticky);
|
||||
}
|
||||
|
||||
public void RegisterNotification(Guid uuid, Uri descriptor, IPAddress address)
|
||||
{
|
||||
List<UpnpDevice> list;
|
||||
lock (_devices)
|
||||
try
|
||||
{
|
||||
if (!_devices.TryGetValue(uuid, out list))
|
||||
EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
|
||||
var receivedCount = _socket.EndReceiveFrom(result, ref endpoint);
|
||||
var received = (byte[])result.AsyncState;
|
||||
|
||||
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
|
||||
{
|
||||
_devices.Add(uuid, list = new List<UpnpDevice>());
|
||||
_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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var t in new[]
|
||||
catch (Exception ex)
|
||||
{
|
||||
"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));
|
||||
_logger.ErrorException("Failed to read SSDP message", ex);
|
||||
}
|
||||
|
||||
NotifyAll();
|
||||
_logger.Debug("Registered mount {0} at {1}", uuid, descriptor);
|
||||
}
|
||||
|
||||
private void UnregisterNotification(Guid uuid)
|
||||
{
|
||||
List<UpnpDevice> dl;
|
||||
lock (_devices)
|
||||
if (_socket != null)
|
||||
{
|
||||
if (!_devices.TryGetValue(uuid, out dl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_devices.Remove(uuid);
|
||||
Receive();
|
||||
}
|
||||
foreach (var d in dl)
|
||||
{
|
||||
NotifyDevice(d, "byebye", true);
|
||||
}
|
||||
_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…
Reference in New Issue
Block a user