You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
8.6 KiB
276 lines
8.6 KiB
using MediaBrowser.Common.Events;
|
|
using MediaBrowser.Controller;
|
|
using MediaBrowser.Controller.Configuration;
|
|
using MediaBrowser.Controller.Dlna;
|
|
using MediaBrowser.Model.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using MoreLinq;
|
|
|
|
namespace MediaBrowser.Dlna.Ssdp
|
|
{
|
|
public class DeviceDiscovery : IDeviceDiscovery, IDisposable
|
|
{
|
|
private bool _disposed;
|
|
|
|
private readonly ILogger _logger;
|
|
private readonly IServerConfigurationManager _config;
|
|
private SsdpHandler _ssdpHandler;
|
|
private readonly CancellationTokenSource _tokenSource;
|
|
private readonly IServerApplicationHost _appHost;
|
|
|
|
public event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
|
|
public event EventHandler<SsdpMessageEventArgs> DeviceLeft;
|
|
|
|
public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IServerApplicationHost appHost)
|
|
{
|
|
_tokenSource = new CancellationTokenSource();
|
|
|
|
_logger = logger;
|
|
_config = config;
|
|
_appHost = appHost;
|
|
}
|
|
|
|
private List<IPAddress> GetLocalIpAddresses()
|
|
{
|
|
NetworkInterface[] interfaces;
|
|
|
|
try
|
|
{
|
|
interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.ErrorException("Error in GetAllNetworkInterfaces", ex);
|
|
return new List<IPAddress>();
|
|
}
|
|
|
|
return interfaces.SelectMany(network => {
|
|
|
|
try
|
|
{
|
|
_logger.Debug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
|
|
|
|
var properties = network.GetIPProperties();
|
|
|
|
return properties.UnicastAddresses
|
|
.Select(i => i.Address)
|
|
.Where(i => i.AddressFamily == AddressFamily.InterNetwork)
|
|
.ToList();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.ErrorException("Error querying network interface", ex);
|
|
return new List<IPAddress>();
|
|
}
|
|
|
|
})
|
|
.DistinctBy(i => i.ToString())
|
|
.ToList();
|
|
}
|
|
|
|
public void Start(SsdpHandler ssdpHandler)
|
|
{
|
|
_ssdpHandler = ssdpHandler;
|
|
_ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived;
|
|
|
|
foreach (var localIp in GetLocalIpAddresses())
|
|
{
|
|
try
|
|
{
|
|
CreateListener(localIp);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.ErrorException("Failed to Initilize Socket", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
|
|
{
|
|
string nts;
|
|
e.Headers.TryGetValue("NTS", out nts);
|
|
|
|
if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) &&
|
|
String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) &&
|
|
!_disposed)
|
|
{
|
|
EventHelper.FireEventIfNotNull(DeviceLeft, this, e, _logger);
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (e.LocalEndPoint == null)
|
|
{
|
|
var ip = _appHost.LocalIpAddresses.FirstOrDefault(i => !IPAddress.IsLoopback(i));
|
|
if (ip != null)
|
|
{
|
|
e.LocalEndPoint = new IPEndPoint(ip, 0);
|
|
}
|
|
}
|
|
|
|
if (e.LocalEndPoint != null)
|
|
{
|
|
TryCreateDevice(e);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.ErrorException("Error creating play to controller", ex);
|
|
}
|
|
}
|
|
|
|
private void CreateListener(IPAddress localIp)
|
|
{
|
|
Task.Factory.StartNew(async (o) =>
|
|
{
|
|
try
|
|
{
|
|
_logger.Info("Creating SSDP listener on {0}", localIp);
|
|
|
|
var endPoint = new IPEndPoint(localIp, 1900);
|
|
|
|
var socket = GetMulticastSocket(localIp, endPoint);
|
|
|
|
var receiveBuffer = new byte[64000];
|
|
|
|
CreateNotifier(localIp);
|
|
|
|
while (!_tokenSource.IsCancellationRequested)
|
|
{
|
|
var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
|
|
|
|
if (receivedBytes > 0)
|
|
{
|
|
var args = SsdpHelper.ParseSsdpResponse(receiveBuffer);
|
|
args.EndPoint = endPoint;
|
|
args.LocalEndPoint = new IPEndPoint(localIp, 0);
|
|
|
|
if (!_ssdpHandler.IsSelfNotification(args))
|
|
{
|
|
TryCreateDevice(args);
|
|
}
|
|
}
|
|
}
|
|
|
|
_logger.Info("SSDP listener - Task completed");
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.ErrorException("Error in listener", e);
|
|
}
|
|
|
|
}, _tokenSource.Token, TaskCreationOptions.LongRunning);
|
|
}
|
|
|
|
private void CreateNotifier(IPAddress localIp)
|
|
{
|
|
Task.Factory.StartNew(async (o) =>
|
|
{
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
_ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900));
|
|
|
|
var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
|
|
|
|
await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.ErrorException("Error in notifier", ex);
|
|
}
|
|
|
|
}, _tokenSource.Token, TaskCreationOptions.LongRunning);
|
|
}
|
|
|
|
private Socket GetMulticastSocket(IPAddress localIpAddress, EndPoint localEndpoint)
|
|
{
|
|
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"), localIpAddress));
|
|
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
|
|
|
|
socket.Bind(localEndpoint);
|
|
|
|
return socket;
|
|
}
|
|
|
|
private void TryCreateDevice(SsdpMessageEventArgs args)
|
|
{
|
|
string nts;
|
|
args.Headers.TryGetValue("NTS", out nts);
|
|
|
|
if (String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (String.Equals(args.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
string usn;
|
|
if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
|
|
|
|
string nt;
|
|
if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
|
|
|
|
// Need to be able to download device description
|
|
string location;
|
|
if (!args.Headers.TryGetValue("Location", out location) ||
|
|
string.IsNullOrEmpty(location))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_config.GetDlnaConfiguration().EnableDebugLogging)
|
|
{
|
|
var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
|
|
var headerText = string.Join(",", headerTexts.ToArray());
|
|
|
|
_logger.Debug("{0} Device message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText);
|
|
}
|
|
|
|
EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_ssdpHandler != null)
|
|
{
|
|
_ssdpHandler.MessageReceived -= _ssdpHandler_MessageReceived;
|
|
}
|
|
|
|
if (!_disposed)
|
|
{
|
|
_disposed = true;
|
|
_tokenSource.Cancel();
|
|
}
|
|
}
|
|
}
|
|
}
|