support sending upnp events
This commit is contained in:
parent
3b4be92038
commit
4331700747
|
@ -51,10 +51,14 @@ namespace MediaBrowser.Api.Dlna
|
|||
public class DlnaServerService : BaseApiService
|
||||
{
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
private readonly IContentDirectory _contentDirectory;
|
||||
private readonly IEventManager _eventManager;
|
||||
|
||||
public DlnaServerService(IDlnaManager dlnaManager)
|
||||
public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IEventManager eventManager)
|
||||
{
|
||||
_dlnaManager = dlnaManager;
|
||||
_contentDirectory = contentDirectory;
|
||||
_eventManager = eventManager;
|
||||
}
|
||||
|
||||
public object Get(GetDescriptionXml request)
|
||||
|
@ -66,7 +70,7 @@ namespace MediaBrowser.Api.Dlna
|
|||
|
||||
public object Get(GetContentDirectory request)
|
||||
{
|
||||
var xml = _dlnaManager.GetContentDirectoryXml(GetRequestHeaders());
|
||||
var xml = _contentDirectory.GetContentDirectoryXml(GetRequestHeaders());
|
||||
|
||||
return ResultFactory.GetResult(xml, "text/xml");
|
||||
}
|
||||
|
@ -85,7 +89,7 @@ namespace MediaBrowser.Api.Dlna
|
|||
|
||||
using (var reader = new StreamReader(request.RequestStream))
|
||||
{
|
||||
return _dlnaManager.ProcessControlRequest(new ControlRequest
|
||||
return _contentDirectory.ProcessControlRequest(new ControlRequest
|
||||
{
|
||||
Headers = GetRequestHeaders(),
|
||||
InputXml = await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||
|
@ -128,49 +132,24 @@ namespace MediaBrowser.Api.Dlna
|
|||
var callback = GetHeader("CALLBACK");
|
||||
var timeoutString = GetHeader("TIMEOUT");
|
||||
|
||||
var timeout = ParseTimeout(timeoutString) ?? 300;
|
||||
var timeout = ParseTimeout(timeoutString);
|
||||
|
||||
if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(notificationType))
|
||||
{
|
||||
RenewEvent(subscriptionId, timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubscribeToEvent(notificationType, timeout, callback);
|
||||
return GetSubscriptionResponse(_eventManager.RenewEventSubscription(subscriptionId, timeout));
|
||||
}
|
||||
|
||||
return GetSubscriptionResponse(request.UuId, timeout);
|
||||
return GetSubscriptionResponse(_eventManager.CreateEventSubscription(notificationType, timeout, callback));
|
||||
}
|
||||
|
||||
UnsubscribeFromEvent(subscriptionId);
|
||||
return ResultFactory.GetResult("", "text/plain");
|
||||
return GetSubscriptionResponse(_eventManager.CancelEventSubscription(subscriptionId));
|
||||
}
|
||||
|
||||
private void UnsubscribeFromEvent(string subscriptionId)
|
||||
private object GetSubscriptionResponse(EventSubscriptionResponse response)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SubscribeToEvent(string notificationType, int? timeout, string callback)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void RenewEvent(string subscriptionId, int? timeout)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private object GetSubscriptionResponse(string uuid, int timeout)
|
||||
{
|
||||
var headers = new Dictionary<string, string>();
|
||||
|
||||
headers["SID"] = "uuid:" + uuid;
|
||||
headers["TIMEOUT"] = "SECOND-" + timeout.ToString(_usCulture);
|
||||
|
||||
return ResultFactory.GetResult("\r\n", "text/plain", headers);
|
||||
return ResultFactory.GetResult(response.Content, response.ContentType, response.Headers);
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
|
|
@ -215,7 +215,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
/// <returns>Task{HttpResponseInfo}.</returns>
|
||||
/// <exception cref="HttpException">
|
||||
/// </exception>
|
||||
private async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
|
||||
public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
|
||||
{
|
||||
ValidateParams(options);
|
||||
|
||||
|
|
|
@ -43,6 +43,14 @@ namespace MediaBrowser.Common.Net
|
|||
/// <returns>Task{Stream}.</returns>
|
||||
Task<Stream> Get(HttpRequestOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="httpMethod">The HTTP method.</param>
|
||||
/// <returns>Task{HttpResponseInfo}.</returns>
|
||||
Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod);
|
||||
|
||||
/// <summary>
|
||||
/// Performs a POST request
|
||||
/// </summary>
|
||||
|
|
17
MediaBrowser.Controller/Dlna/EventSubscriptionResponse.cs
Normal file
17
MediaBrowser.Controller/Dlna/EventSubscriptionResponse.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
{
|
||||
public class EventSubscriptionResponse
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public Dictionary<string, string> Headers { get; set; }
|
||||
|
||||
public EventSubscriptionResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
21
MediaBrowser.Controller/Dlna/IContentDirectory.cs
Normal file
21
MediaBrowser.Controller/Dlna/IContentDirectory.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
{
|
||||
public interface IContentDirectory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the content directory XML.
|
||||
/// </summary>
|
||||
/// <param name="headers">The headers.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetContentDirectoryXml(IDictionary<string, string> headers);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the control request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>ControlResponse.</returns>
|
||||
ControlResponse ProcessControlRequest(ControlRequest request);
|
||||
}
|
||||
}
|
|
@ -64,20 +64,6 @@ namespace MediaBrowser.Controller.Dlna
|
|||
/// <returns>System.String.</returns>
|
||||
string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content directory XML.
|
||||
/// </summary>
|
||||
/// <param name="headers">The headers.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetContentDirectoryXml(IDictionary<string, string> headers);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the control request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>ControlResponse.</returns>
|
||||
ControlResponse ProcessControlRequest(ControlRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon.
|
||||
/// </summary>
|
||||
|
|
47
MediaBrowser.Controller/Dlna/IEventManager.cs
Normal file
47
MediaBrowser.Controller/Dlna/IEventManager.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using MediaBrowser.Model.Dlna;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
{
|
||||
public interface IEventManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Cancels the event subscription.
|
||||
/// </summary>
|
||||
/// <param name="subscriptionId">The subscription identifier.</param>
|
||||
EventSubscriptionResponse CancelEventSubscription(string subscriptionId);
|
||||
|
||||
/// <summary>
|
||||
/// Renews the event subscription.
|
||||
/// </summary>
|
||||
/// <param name="subscriptionId">The subscription identifier.</param>
|
||||
/// <param name="timeoutSeconds">The timeout seconds.</param>
|
||||
/// <returns>EventSubscriptionResponse.</returns>
|
||||
EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the event subscription.
|
||||
/// </summary>
|
||||
/// <param name="notificationType">Type of the notification.</param>
|
||||
/// <param name="timeoutSeconds">The timeout seconds.</param>
|
||||
/// <param name="callbackUrl">The callback URL.</param>
|
||||
/// <returns>EventSubscriptionResponse.</returns>
|
||||
EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subscription.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <returns>EventSubscription.</returns>
|
||||
EventSubscription GetSubscription(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the event.
|
||||
/// </summary>
|
||||
/// <param name="notificationType">Type of the notification.</param>
|
||||
/// <param name="stateVariables">The state variables.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task TriggerEvent(string notificationType, IDictionary<string,string> stateVariables);
|
||||
}
|
||||
}
|
|
@ -80,7 +80,10 @@
|
|||
<Compile Include="Collections\ICollectionManager.cs" />
|
||||
<Compile Include="Dlna\ControlRequest.cs" />
|
||||
<Compile Include="Dlna\DlnaIconResponse.cs" />
|
||||
<Compile Include="Dlna\EventSubscriptionResponse.cs" />
|
||||
<Compile Include="Dlna\IContentDirectory.cs" />
|
||||
<Compile Include="Dlna\IDlnaManager.cs" />
|
||||
<Compile Include="Dlna\IEventManager.cs" />
|
||||
<Compile Include="Drawing\IImageProcessor.cs" />
|
||||
<Compile Include="Drawing\ImageFormat.cs" />
|
||||
<Compile Include="Drawing\ImageProcessingOptions.cs" />
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Dlna.Profiles;
|
||||
using MediaBrowser.Dlna.Server;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
@ -28,28 +24,20 @@ namespace MediaBrowser.Dlna
|
|||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, IServerConfigurationManager config)
|
||||
public DlnaManager(IXmlSerializer xmlSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILogger logger,
|
||||
IJsonSerializer jsonSerializer)
|
||||
{
|
||||
_xmlSerializer = xmlSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
_appPaths = appPaths;
|
||||
_logger = logger;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_userManager = userManager;
|
||||
_libraryManager = libraryManager;
|
||||
_dtoService = dtoService;
|
||||
_imageProcessor = imageProcessor;
|
||||
_userDataManager = userDataManager;
|
||||
_config = config;
|
||||
|
||||
DumpProfiles();
|
||||
//DumpProfiles();
|
||||
}
|
||||
|
||||
public IEnumerable<DeviceProfile> GetProfiles()
|
||||
|
@ -499,37 +487,6 @@ namespace MediaBrowser.Dlna
|
|||
return new DescriptionXmlBuilder(profile, serverUuId).GetXml();
|
||||
}
|
||||
|
||||
public string GetContentDirectoryXml(IDictionary<string, string> headers)
|
||||
{
|
||||
var profile = GetProfile(headers) ??
|
||||
GetDefaultProfile();
|
||||
|
||||
return new ContentDirectoryXmlBuilder(profile).GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
{
|
||||
var profile = GetProfile(request.Headers)
|
||||
?? GetDefaultProfile();
|
||||
|
||||
var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId);
|
||||
|
||||
var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var user = GetUser(profile);
|
||||
|
||||
return new ControlHandler(
|
||||
_logger,
|
||||
_libraryManager,
|
||||
profile,
|
||||
serverAddress,
|
||||
_dtoService,
|
||||
_imageProcessor,
|
||||
_userDataManager,
|
||||
user)
|
||||
.ProcessControlRequest(request);
|
||||
}
|
||||
|
||||
public DlnaIconResponse GetIcon(string filename)
|
||||
{
|
||||
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
||||
|
@ -542,33 +499,5 @@ namespace MediaBrowser.Dlna
|
|||
Stream = GetType().Assembly.GetManifestResourceStream("MediaBrowser.Dlna.Images." + filename.ToLower())
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
private User GetUser(DeviceProfile profile)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(profile.UserId))
|
||||
{
|
||||
var user = _userManager.GetUserById(new Guid(profile.UserId));
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_config.Configuration.DlnaOptions.DefaultUserId))
|
||||
{
|
||||
var user = _userManager.GetUserById(new Guid(_config.Configuration.DlnaOptions.DefaultUserId));
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
// No configuration so it's going to be pretty arbitrary
|
||||
return _userManager.Users.First();
|
||||
}
|
||||
}
|
||||
}
|
171
MediaBrowser.Dlna/Eventing/EventManager.cs
Normal file
171
MediaBrowser.Dlna/Eventing/EventManager.cs
Normal file
|
@ -0,0 +1,171 @@
|
|||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Dlna.Eventing
|
||||
{
|
||||
public class EventManager : IEventManager
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
|
||||
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
public EventManager(ILogManager logManager, IHttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logManager.GetLogger("DlnaEventManager");
|
||||
}
|
||||
|
||||
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds)
|
||||
{
|
||||
var timeout = timeoutSeconds ?? 300;
|
||||
|
||||
var subscription = GetSubscription(subscriptionId, true);
|
||||
|
||||
_logger.Debug("Renewing event subscription for {0} with timeout of {1} to {2}",
|
||||
subscription.NotificationType,
|
||||
timeout,
|
||||
subscription.CallbackUrl);
|
||||
|
||||
subscription.TimeoutSeconds = timeout;
|
||||
subscription.SubscriptionTime = DateTime.UtcNow;
|
||||
|
||||
return GetEventSubscriptionResponse(subscriptionId, timeout);
|
||||
}
|
||||
|
||||
public EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl)
|
||||
{
|
||||
var timeout = timeoutSeconds ?? 300;
|
||||
var id = Guid.NewGuid().ToString("N");
|
||||
|
||||
_logger.Debug("Creating event subscription for {0} with timeout of {1} to {2}",
|
||||
notificationType,
|
||||
timeout,
|
||||
callbackUrl);
|
||||
|
||||
_subscriptions.TryAdd(id, new EventSubscription
|
||||
{
|
||||
Id = id,
|
||||
CallbackUrl = callbackUrl,
|
||||
SubscriptionTime = DateTime.UtcNow,
|
||||
TimeoutSeconds = timeout
|
||||
});
|
||||
|
||||
return GetEventSubscriptionResponse(id, timeout);
|
||||
}
|
||||
|
||||
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
||||
{
|
||||
_logger.Debug("Cancelling event subscription {0}", subscriptionId);
|
||||
|
||||
EventSubscription sub;
|
||||
_subscriptions.TryRemove(subscriptionId, out sub);
|
||||
|
||||
return new EventSubscriptionResponse
|
||||
{
|
||||
Content = "\r\n",
|
||||
ContentType = "text/plain"
|
||||
};
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, int timeoutSeconds)
|
||||
{
|
||||
var response = new EventSubscriptionResponse
|
||||
{
|
||||
Content = "\r\n",
|
||||
ContentType = "text/plain"
|
||||
};
|
||||
|
||||
response.Headers["SID"] = "uuid:" + subscriptionId;
|
||||
response.Headers["TIMEOUT"] = "SECOND-" + timeoutSeconds.ToString(_usCulture);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public EventSubscription GetSubscription(string id)
|
||||
{
|
||||
return GetSubscription(id, false);
|
||||
}
|
||||
|
||||
private EventSubscription GetSubscription(string id, bool throwOnMissing)
|
||||
{
|
||||
EventSubscription e;
|
||||
|
||||
if (!_subscriptions.TryGetValue(id, out e) && throwOnMissing)
|
||||
{
|
||||
throw new ResourceNotFoundException("Event with Id " + id + " not found.");
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public Task TriggerEvent(string notificationType, IDictionary<string, string> stateVariables)
|
||||
{
|
||||
var subs = _subscriptions.Values
|
||||
.Where(i => !i.IsExpired && string.Equals(notificationType, i.NotificationType, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
var tasks = subs.Select(i => TriggerEvent(i, stateVariables));
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
private async Task TriggerEvent(EventSubscription subscription, IDictionary<string, string> stateVariables)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append("<?xml version=\"1.0\"?>");
|
||||
builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
|
||||
foreach (var key in stateVariables.Keys)
|
||||
{
|
||||
builder.Append("<e:property>");
|
||||
builder.Append("<" + key + ">");
|
||||
builder.Append(stateVariables[key]);
|
||||
builder.Append("</" + key + ">");
|
||||
builder.Append("</e:property>");
|
||||
}
|
||||
builder.Append("</e:propertyset>");
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
RequestContent = builder.ToString(),
|
||||
RequestContentType = "text/xml",
|
||||
Url = subscription.CallbackUrl
|
||||
};
|
||||
|
||||
options.RequestHeaders.Add("NT", subscription.NotificationType);
|
||||
options.RequestHeaders.Add("NTS", "upnp:propchange");
|
||||
options.RequestHeaders.Add("SID", "uuid:" + subscription.Id);
|
||||
options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture));
|
||||
|
||||
try
|
||||
{
|
||||
await _httpClient.SendAsync(options, "NOTIFY").ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Already logged at lower levels
|
||||
}
|
||||
finally
|
||||
{
|
||||
subscription.IncrementTriggerCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,6 +53,7 @@
|
|||
</Compile>
|
||||
<Compile Include="DlnaManager.cs" />
|
||||
<Compile Include="Common\Argument.cs" />
|
||||
<Compile Include="Eventing\EventManager.cs" />
|
||||
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
|
||||
<Compile Include="PlayTo\Device.cs">
|
||||
<SubType>Code</SubType>
|
||||
|
@ -73,6 +74,7 @@
|
|||
<Compile Include="Profiles\Windows81Profile.cs" />
|
||||
<Compile Include="Profiles\WindowsMediaCenterProfile.cs" />
|
||||
<Compile Include="Profiles\WindowsPhoneProfile.cs" />
|
||||
<Compile Include="Server\ContentDirectory.cs" />
|
||||
<Compile Include="Server\ControlHandler.cs" />
|
||||
<Compile Include="Server\ServiceActionListBuilder.cs" />
|
||||
<Compile Include="Server\ContentDirectoryXmlBuilder.cs" />
|
||||
|
|
|
@ -24,6 +24,15 @@ namespace MediaBrowser.Dlna.Profiles
|
|||
Type = DlnaProfileType.Audio
|
||||
},
|
||||
new TranscodingProfile
|
||||
{
|
||||
Protocol = "hls",
|
||||
Container = "ts",
|
||||
VideoCodec = "h264",
|
||||
AudioCodec = "aac",
|
||||
Type = DlnaProfileType.Video,
|
||||
VideoProfile = "Baseline"
|
||||
},
|
||||
new TranscodingProfile
|
||||
{
|
||||
Container = "ts",
|
||||
VideoCodec = "h264",
|
||||
|
|
|
@ -19,6 +19,15 @@ namespace MediaBrowser.Dlna.Profiles
|
|||
Type = DlnaProfileType.Audio
|
||||
},
|
||||
new TranscodingProfile
|
||||
{
|
||||
Protocol = "hls",
|
||||
Container = "ts",
|
||||
VideoCodec = "h264",
|
||||
AudioCodec = "aac",
|
||||
Type = DlnaProfileType.Video,
|
||||
VideoProfile = "Baseline"
|
||||
},
|
||||
new TranscodingProfile
|
||||
{
|
||||
Container = "mp4",
|
||||
VideoCodec = "h264",
|
||||
|
|
151
MediaBrowser.Dlna/Server/ContentDirectory.cs
Normal file
151
MediaBrowser.Dlna/Server/ContentDirectory.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
public class ContentDirectory : IContentDirectory, IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IDlnaManager _dlna;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
private readonly IEventManager _eventManager;
|
||||
|
||||
private int _systemUpdateId;
|
||||
private Timer _systemUpdateTimer;
|
||||
|
||||
public ContentDirectory(IDlnaManager dlna,
|
||||
IUserDataManager userDataManager,
|
||||
IImageProcessor imageProcessor,
|
||||
IDtoService dtoService,
|
||||
ILibraryManager libraryManager,
|
||||
ILogManager logManager,
|
||||
IServerConfigurationManager config,
|
||||
IUserManager userManager,
|
||||
IEventManager eventManager)
|
||||
{
|
||||
_dlna = dlna;
|
||||
_userDataManager = userDataManager;
|
||||
_imageProcessor = imageProcessor;
|
||||
_dtoService = dtoService;
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_userManager = userManager;
|
||||
_eventManager = eventManager;
|
||||
_logger = logManager.GetLogger("DlnaContentDirectory");
|
||||
|
||||
_systemUpdateTimer = new Timer(SystemUdpateTimerCallback, null, Timeout.Infinite,
|
||||
Convert.ToInt64(TimeSpan.FromMinutes(60).TotalMilliseconds));
|
||||
}
|
||||
|
||||
public string GetContentDirectoryXml(IDictionary<string, string> headers)
|
||||
{
|
||||
var profile = _dlna.GetProfile(headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
return new ContentDirectoryXmlBuilder(profile).GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
{
|
||||
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 user = GetUser(profile);
|
||||
|
||||
return new ControlHandler(
|
||||
_logger,
|
||||
_libraryManager,
|
||||
profile,
|
||||
serverAddress,
|
||||
_dtoService,
|
||||
_imageProcessor,
|
||||
_userDataManager,
|
||||
user,
|
||||
_systemUpdateId)
|
||||
.ProcessControlRequest(request);
|
||||
}
|
||||
|
||||
private User GetUser(DeviceProfile profile)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(profile.UserId))
|
||||
{
|
||||
var user = _userManager.GetUserById(new Guid(profile.UserId));
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_config.Configuration.DlnaOptions.DefaultUserId))
|
||||
{
|
||||
var user = _userManager.GetUserById(new Guid(_config.Configuration.DlnaOptions.DefaultUserId));
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
// No configuration so it's going to be pretty arbitrary
|
||||
return _userManager.Users.First();
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private async void SystemUdpateTimerCallback(object state)
|
||||
{
|
||||
var values = new Dictionary<string, string>();
|
||||
|
||||
_systemUpdateId++;
|
||||
values["SystemUpdateID"] = _systemUpdateId.ToString(_usCulture);
|
||||
|
||||
try
|
||||
{
|
||||
await _eventManager.TriggerEvent("upnp:event", values).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error sending system update notification", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object _disposeLock = new object();
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_disposeLock)
|
||||
{
|
||||
DisposeUpdateTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeUpdateTimer()
|
||||
{
|
||||
if (_systemUpdateTimer != null)
|
||||
{
|
||||
_systemUpdateTimer.Dispose();
|
||||
_systemUpdateTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,10 +42,10 @@ namespace MediaBrowser.Dlna.Server
|
|||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||
|
||||
private int systemID = 0;
|
||||
private readonly int _systemUpdateId;
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user)
|
||||
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId)
|
||||
{
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
|
@ -55,6 +55,7 @@ namespace MediaBrowser.Dlna.Server
|
|||
_imageProcessor = imageProcessor;
|
||||
_userDataManager = userDataManager;
|
||||
_user = user;
|
||||
_systemUpdateId = systemUpdateId;
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
|
@ -205,7 +206,7 @@ namespace MediaBrowser.Dlna.Server
|
|||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID()
|
||||
{
|
||||
return new Headers { { "Id", systemID.ToString(_usCulture) } };
|
||||
return new Headers { { "Id", _systemUpdateId.ToString(_usCulture) } };
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList()
|
||||
|
@ -308,7 +309,7 @@ namespace MediaBrowser.Dlna.Server
|
|||
new KeyValuePair<string,string>("Result", resXML),
|
||||
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
|
||||
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
|
||||
new KeyValuePair<string,string>("UpdateID", systemID.ToString(_usCulture))
|
||||
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -382,7 +383,7 @@ namespace MediaBrowser.Dlna.Server
|
|||
new KeyValuePair<string,string>("Result", resXML),
|
||||
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
|
||||
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
|
||||
new KeyValuePair<string,string>("UpdateID", systemID.ToString(_usCulture))
|
||||
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace MediaBrowser.Dlna.Server
|
|||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append("<?xml version=\"1.0\"?>");
|
||||
builder.Append("<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\" xmlns:sec=\"http://www.sec.co.kr/dlna\">");
|
||||
builder.Append("<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">");
|
||||
|
||||
builder.Append("<specVersion>");
|
||||
builder.Append("<major>1</major>");
|
||||
|
@ -59,16 +59,8 @@ namespace MediaBrowser.Dlna.Server
|
|||
{
|
||||
builder.Append("<UDN>uuid:" + SecurityElement.Escape(_serverUdn) + "</UDN>");
|
||||
builder.Append("<dlna:X_DLNACAP>" + SecurityElement.Escape(_profile.XDlnaCap ?? string.Empty) + "</dlna:X_DLNACAP>");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_profile.XDlnaDoc))
|
||||
{
|
||||
builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" +
|
||||
SecurityElement.Escape(_profile.XDlnaDoc) + "</dlna:X_DLNADOC>");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">DMS-1.50</dlna:X_DLNADOC>");
|
||||
}
|
||||
|
||||
builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + SecurityElement.Escape(_profile.XDlnaDoc ?? string.Empty) + "</dlna:X_DLNADOC>");
|
||||
|
||||
builder.Append("<friendlyName>" + SecurityElement.Escape(_profile.FriendlyName ?? string.Empty) + "</friendlyName>");
|
||||
builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
|
||||
|
@ -80,9 +72,6 @@ namespace MediaBrowser.Dlna.Server
|
|||
builder.Append("<modelURL>" + SecurityElement.Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>");
|
||||
builder.Append("<serialNumber>" + SecurityElement.Escape(_profile.SerialNumber ?? string.Empty) + "</serialNumber>");
|
||||
|
||||
builder.Append("<sec:ProductCap>DCM10,getMediaInfo.sec</sec:ProductCap>");
|
||||
builder.Append("<sec:X_ProductCap>DCM10,getMediaInfo.sec</sec:X_ProductCap>");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_profile.SonyAggregationFlags))
|
||||
{
|
||||
builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">" + SecurityElement.Escape(_profile.SonyAggregationFlags) + "</av:aggregationFlags>");
|
||||
|
|
|
@ -117,7 +117,6 @@ namespace MediaBrowser.Dlna.Server
|
|||
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
|
||||
{
|
||||
_logger.Debug("{0} - Datagram method: {1}", endpoint, method);
|
||||
//_logger.Debug(headers);
|
||||
}
|
||||
|
||||
if (string.Equals(method, "M-SEARCH", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -234,7 +233,10 @@ namespace MediaBrowser.Dlna.Server
|
|||
|
||||
private void NotifyAll()
|
||||
{
|
||||
_logger.Debug("Sending alive notifications");
|
||||
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
|
||||
{
|
||||
_logger.Debug("Sending alive notifications");
|
||||
}
|
||||
foreach (var d in Devices)
|
||||
{
|
||||
NotifyDevice(d, "alive", false);
|
||||
|
@ -243,7 +245,6 @@ namespace MediaBrowser.Dlna.Server
|
|||
|
||||
private void NotifyDevice(UpnpDevice dev, string type, bool sticky)
|
||||
{
|
||||
_logger.Debug("NotifyDevice");
|
||||
var builder = new StringBuilder();
|
||||
|
||||
const string argFormat = "{0}: {1}\r\n";
|
||||
|
@ -258,7 +259,11 @@ namespace MediaBrowser.Dlna.Server
|
|||
builder.AppendFormat(argFormat, "USN", dev.USN);
|
||||
builder.Append("\r\n");
|
||||
|
||||
_logger.Debug("{0} said {1}", dev.USN, type);
|
||||
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
|
||||
{
|
||||
_logger.Debug("{0} said {1}", dev.USN, type);
|
||||
}
|
||||
|
||||
SendDatagram(_ssdpEndp, dev.Address, builder.ToString(), sticky);
|
||||
}
|
||||
|
||||
|
|
|
@ -122,6 +122,9 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs">
|
||||
<Link>Dlna\DlnaMaps.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\EventSubscription.cs">
|
||||
<Link>Dlna\EventSubscription.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\Filter.cs">
|
||||
<Link>Dlna\Filter.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -109,6 +109,9 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs">
|
||||
<Link>Dlna\DlnaMaps.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\EventSubscription.cs">
|
||||
<Link>Dlna\EventSubscription.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\Filter.cs">
|
||||
<Link>Dlna\Filter.cs</Link>
|
||||
</Compile>
|
||||
|
|
34
MediaBrowser.Model/Dlna/EventSubscription.cs
Normal file
34
MediaBrowser.Model/Dlna/EventSubscription.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class EventSubscription
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string CallbackUrl { get; set; }
|
||||
public string NotificationType { get; set; }
|
||||
|
||||
public DateTime SubscriptionTime { get; set; }
|
||||
public int TimeoutSeconds { get; set; }
|
||||
|
||||
public long TriggerCount { get; set; }
|
||||
|
||||
public void IncrementTriggerCount()
|
||||
{
|
||||
if (TriggerCount == long.MaxValue)
|
||||
{
|
||||
TriggerCount = 0;
|
||||
}
|
||||
|
||||
TriggerCount++;
|
||||
}
|
||||
|
||||
public bool IsExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
return SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,6 +73,7 @@
|
|||
<Compile Include="Dlna\DeviceProfileInfo.cs" />
|
||||
<Compile Include="Dlna\DirectPlayProfile.cs" />
|
||||
<Compile Include="Dlna\DlnaMaps.cs" />
|
||||
<Compile Include="Dlna\EventSubscription.cs" />
|
||||
<Compile Include="Dlna\Filter.cs" />
|
||||
<Compile Include="Dlna\MediaFormatProfile.cs" />
|
||||
<Compile Include="Dlna\MediaFormatProfileResolver.cs" />
|
||||
|
|
|
@ -32,7 +32,9 @@ using MediaBrowser.Controller.Session;
|
|||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Controller.Themes;
|
||||
using MediaBrowser.Dlna;
|
||||
using MediaBrowser.Dlna.Eventing;
|
||||
using MediaBrowser.Dlna.PlayTo;
|
||||
using MediaBrowser.Dlna.Server;
|
||||
using MediaBrowser.MediaEncoding.BdInfo;
|
||||
using MediaBrowser.MediaEncoding.Encoder;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
@ -506,9 +508,15 @@ namespace MediaBrowser.ServerApplication
|
|||
var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
|
||||
RegisterSingleInstance<IAppThemeManager>(appThemeManager);
|
||||
|
||||
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer, UserManager, LibraryManager, DtoService, ImageProcessor, UserDataManager, ServerConfigurationManager);
|
||||
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("Dlna"), JsonSerializer);
|
||||
RegisterSingleInstance<IDlnaManager>(dlnaManager);
|
||||
|
||||
var dlnaEventManager = new EventManager(LogManager, HttpClient);
|
||||
RegisterSingleInstance<IEventManager>(dlnaEventManager);
|
||||
|
||||
var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, DtoService, LibraryManager, LogManager, ServerConfigurationManager, UserManager, dlnaEventManager);
|
||||
RegisterSingleInstance<IContentDirectory>(contentDirectory);
|
||||
|
||||
var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);
|
||||
RegisterSingleInstance<ICollectionManager>(collectionManager);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user