jellyfin-server/MediaBrowser.Dlna/PlayTo/PlayToManager.cs

337 lines
12 KiB
C#
Raw Normal View History

2014-04-27 03:42:05 +00:00
using MediaBrowser.Common.Net;
2014-03-17 04:25:11 +00:00
using MediaBrowser.Controller;
2014-03-13 14:54:11 +00:00
using MediaBrowser.Controller.Configuration;
2014-03-13 19:08:02 +00:00
using MediaBrowser.Controller.Dlna;
2014-04-23 16:29:21 +00:00
using MediaBrowser.Controller.Drawing;
2014-02-27 02:44:00 +00:00
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Session;
2014-03-25 05:25:03 +00:00
using MediaBrowser.Dlna.Ssdp;
2014-02-27 02:44:00 +00:00
using MediaBrowser.Model.Logging;
2014-03-23 06:07:43 +00:00
using MediaBrowser.Model.Session;
2014-02-27 02:44:00 +00:00
using System;
2014-03-25 05:25:03 +00:00
using System.Collections.Generic;
2014-02-27 02:44:00 +00:00
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
2014-04-27 03:42:05 +00:00
using System.Text;
2014-02-27 02:44:00 +00:00
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Dlna.PlayTo
{
class PlayToManager : IDisposable
{
2014-04-22 17:25:54 +00:00
private bool _disposed;
2014-02-27 02:44:00 +00:00
private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient;
private readonly CancellationTokenSource _tokenSource;
private readonly IItemRepository _itemRepository;
private readonly ILibraryManager _libraryManager;
2014-03-13 14:54:11 +00:00
private readonly IUserManager _userManager;
2014-03-13 19:08:02 +00:00
private readonly IDlnaManager _dlnaManager;
2014-03-14 17:09:22 +00:00
private readonly IServerConfigurationManager _config;
2014-03-17 04:25:11 +00:00
private readonly IServerApplicationHost _appHost;
2014-04-23 16:29:21 +00:00
private readonly IImageProcessor _imageProcessor;
2014-02-27 18:00:49 +00:00
2014-04-27 03:42:05 +00:00
private readonly SsdpHandler _ssdpHandler;
public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, SsdpHandler ssdpHandler)
2014-02-27 02:44:00 +00:00
{
_tokenSource = new CancellationTokenSource();
_logger = logger;
_sessionManager = sessionManager;
_httpClient = httpClient;
_itemRepository = itemRepository;
_libraryManager = libraryManager;
2014-02-27 18:00:49 +00:00
_userManager = userManager;
2014-03-13 19:08:02 +00:00
_dlnaManager = dlnaManager;
2014-03-17 04:25:11 +00:00
_appHost = appHost;
2014-04-23 16:29:21 +00:00
_imageProcessor = imageProcessor;
2014-04-27 03:42:05 +00:00
_ssdpHandler = ssdpHandler;
2014-03-14 17:09:22 +00:00
_config = config;
2014-02-27 02:44:00 +00:00
}
2014-03-23 06:07:43 +00:00
public void Start()
2014-02-27 02:44:00 +00:00
{
2014-04-08 04:17:18 +00:00
foreach (var network in GetNetworkInterfaces())
2014-02-27 02:44:00 +00:00
{
_logger.Debug("Found interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
2014-04-25 17:30:41 +00:00
if (!network.SupportsMulticast || OperationalStatus.Up != network.OperationalStatus || !network.GetIPProperties().MulticastAddresses.Any())
2014-02-27 02:44:00 +00:00
continue;
var ipV4 = network.GetIPProperties().GetIPv4Properties();
if (null == ipV4)
continue;
2014-04-22 17:25:54 +00:00
var localIp = network.GetIPProperties().UnicastAddresses
.Where(i => i.Address.AddressFamily == AddressFamily.InterNetwork)
.Select(i => i.Address)
.FirstOrDefault();
2014-02-27 02:44:00 +00:00
2014-04-22 17:25:54 +00:00
if (localIp != null)
2014-02-27 02:44:00 +00:00
{
2014-04-22 17:25:54 +00:00
try
2014-02-27 02:44:00 +00:00
{
2014-04-25 17:30:41 +00:00
CreateListener(localIp, ipV4.Index);
2014-04-22 17:25:54 +00:00
}
catch (Exception e)
{
_logger.ErrorException("Failed to Initilize Socket", e);
2014-02-27 02:44:00 +00:00
}
}
}
}
2014-04-08 04:17:18 +00:00
private IEnumerable<NetworkInterface> GetNetworkInterfaces()
{
try
{
return NetworkInterface.GetAllNetworkInterfaces();
}
catch (Exception ex)
{
_logger.ErrorException("Error in GetAllNetworkInterfaces", ex);
return new List<NetworkInterface>();
}
}
2014-02-27 02:44:00 +00:00
/// <summary>
/// Creates a socket for the interface and listends for data.
/// </summary>
/// <param name="localIp">The local ip.</param>
2014-04-26 02:55:07 +00:00
/// <param name="networkInterfaceIndex">Index of the network interface.</param>
2014-04-25 17:30:41 +00:00
private void CreateListener(IPAddress localIp, int networkInterfaceIndex)
2014-02-27 02:44:00 +00:00
{
Task.Factory.StartNew(async (o) =>
{
try
{
2014-04-25 17:30:41 +00:00
var socket = GetMulticastSocket(networkInterfaceIndex);
2014-02-27 02:44:00 +00:00
2014-04-27 03:42:05 +00:00
var endPoint = new IPEndPoint(localIp, 1900);
socket.Bind(endPoint);
2014-02-27 02:44:00 +00:00
_logger.Info("Creating SSDP listener");
var receiveBuffer = new byte[64000];
CreateNotifier(socket);
while (!_tokenSource.IsCancellationRequested)
{
var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
if (receivedBytes > 0)
{
2014-04-27 03:42:05 +00:00
var args = SsdpHelper.ParseSsdpResponse(receiveBuffer, endPoint);
2014-02-27 02:44:00 +00:00
2014-04-30 15:07:02 +00:00
TryCreateController(args, localIp);
2014-02-27 02:44:00 +00:00
}
}
_logger.Info("SSDP listener - Task completed");
}
2014-03-23 20:07:02 +00:00
catch (OperationCanceledException)
2014-02-27 02:44:00 +00:00
{
}
catch (Exception e)
{
_logger.ErrorException("Error in listener", e);
}
}, _tokenSource.Token, TaskCreationOptions.LongRunning);
}
2014-04-30 15:07:02 +00:00
private void TryCreateController(SsdpMessageEventArgs args, IPAddress localIp)
2014-02-27 02:44:00 +00:00
{
2014-04-27 03:42:05 +00:00
string nts;
args.Headers.TryGetValue("NTS", out nts);
string usn;
if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
// Don't create a new controller when a device is indicating it's shutting down
if (string.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
{
return;
}
// It has to report that it's a media renderer
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
{
return;
}
// Need to be able to download device description
2014-03-25 05:25:03 +00:00
string location;
2014-04-27 03:42:05 +00:00
if (!args.Headers.TryGetValue("Location", out location) ||
string.IsNullOrEmpty(location))
{
return;
}
2014-03-25 05:25:03 +00:00
2014-04-27 03:42:05 +00:00
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
{
var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
var headerText = string.Join(",", headerTexts.ToArray());
_logger.Debug("{0} PlayTo message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText);
}
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
2014-03-25 05:25:03 +00:00
{
return;
}
2014-02-27 02:44:00 +00:00
Task.Run(async () =>
{
try
{
2014-04-30 15:07:02 +00:00
await CreateController(new Uri(location), localIp).ConfigureAwait(false);
2014-02-27 02:44:00 +00:00
}
2014-03-23 20:07:02 +00:00
catch (OperationCanceledException)
2014-02-27 02:44:00 +00:00
{
}
catch (Exception ex)
{
_logger.ErrorException("Error creating play to controller", ex);
}
});
}
private void CreateNotifier(Socket socket)
{
Task.Factory.StartNew(async (o) =>
{
try
{
2014-04-25 17:30:41 +00:00
var msg = new SsdpMessageBuilder().BuildRendererDiscoveryMessage();
var request = Encoding.UTF8.GetBytes(msg);
2014-02-27 02:44:00 +00:00
while (true)
{
socket.SendTo(request, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900));
2014-03-23 21:46:13 +00:00
var delay = _config.Configuration.DlnaOptions.ClientDiscoveryIntervalSeconds * 1000;
2014-03-23 20:49:05 +00:00
await Task.Delay(delay).ConfigureAwait(false);
2014-02-27 02:44:00 +00:00
}
}
2014-03-23 20:07:02 +00:00
catch (OperationCanceledException)
2014-02-27 02:44:00 +00:00
{
}
catch (Exception ex)
{
_logger.ErrorException("Error in notifier", ex);
}
}, _tokenSource.Token, TaskCreationOptions.LongRunning);
}
/// <summary>
/// Gets a socket configured for SDDP multicasting.
/// </summary>
/// <returns></returns>
2014-04-25 17:30:41 +00:00
private Socket GetMulticastSocket(int networkInterfaceIndex)
2014-02-27 02:44:00 +00:00
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
2014-04-25 17:30:41 +00:00
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), networkInterfaceIndex));
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
2014-02-27 02:44:00 +00:00
return socket;
}
/// <summary>
/// Creates a new DlnaSessionController.
/// and logs the session in SessionManager
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns></returns>
2014-04-30 15:07:02 +00:00
private async Task CreateController(Uri uri, IPAddress localIp)
2014-02-27 02:44:00 +00:00
{
2014-03-14 17:09:22 +00:00
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
2014-02-27 02:44:00 +00:00
2014-04-27 03:42:05 +00:00
if (device != null && device.RendererCommands != null)
2014-02-27 02:44:00 +00:00
{
2014-03-25 05:25:03 +00:00
var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, _appHost.ApplicationVersion.ToString(), device.Properties.UUID, device.Properties.Name, uri.OriginalString, null)
2014-02-27 02:44:00 +00:00
.ConfigureAwait(false);
var controller = sessionInfo.SessionController as PlayToController;
if (controller == null)
{
2014-04-30 15:07:02 +00:00
var serverAddress = GetServerAddress(localIp);
sessionInfo.SessionController = controller = new PlayToController(sessionInfo,
_sessionManager,
_itemRepository,
_libraryManager,
_logger,
_dlnaManager,
_userManager,
_imageProcessor,
_ssdpHandler,
serverAddress);
2014-03-14 17:09:22 +00:00
2014-03-25 05:25:03 +00:00
controller.Init(device);
2014-02-27 02:44:00 +00:00
2014-03-26 16:10:46 +00:00
var profile = _dlnaManager.GetProfile(device.Properties.ToDeviceIdentification()) ??
_dlnaManager.GetDefaultProfile();
2014-03-25 05:25:03 +00:00
_sessionManager.ReportCapabilities(sessionInfo.Id, new SessionCapabilities
{
2014-04-02 21:55:19 +00:00
PlayableMediaTypes = profile.GetSupportedMediaTypes(),
2014-03-25 21:13:55 +00:00
2014-04-02 21:55:19 +00:00
SupportedCommands = new List<string>
{
GeneralCommandType.VolumeDown.ToString(),
GeneralCommandType.VolumeUp.ToString(),
GeneralCommandType.Mute.ToString(),
GeneralCommandType.Unmute.ToString(),
2014-04-15 03:54:52 +00:00
GeneralCommandType.ToggleMute.ToString(),
GeneralCommandType.SetVolume.ToString(),
GeneralCommandType.SetAudioStreamIndex.ToString(),
GeneralCommandType.SetSubtitleStreamIndex.ToString()
2014-05-17 18:37:40 +00:00
},
SupportsMediaControl = true
2014-03-25 05:25:03 +00:00
});
2014-02-27 18:00:49 +00:00
2014-03-25 05:25:03 +00:00
_logger.Info("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName);
}
2014-03-13 21:29:25 +00:00
}
2014-02-27 18:00:49 +00:00
}
2014-04-30 15:07:02 +00:00
private string GetServerAddress(IPAddress localIp)
{
return string.Format("{0}://{1}:{2}/mediabrowser",
"http",
localIp,
_appHost.HttpServerPort
);
}
2014-02-27 02:44:00 +00:00
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_tokenSource.Cancel();
}
}
}
}