dlna server fixes

This commit is contained in:
Luke Pulverenti 2014-04-20 01:21:08 -04:00
parent a5b3ab9fc5
commit 247400717e
42 changed files with 596 additions and 148 deletions

View File

@ -1,10 +1,12 @@
using System;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Dlna;
using ServiceStack;
using ServiceStack.Text.Controller;
using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Dlna
@ -17,21 +19,28 @@ namespace MediaBrowser.Api.Dlna
public string UuId { get; set; }
}
[Route("/Dlna/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")]
[Route("/Dlna/contentdirectory", "GET", Summary = "Gets dlna content directory xml")]
[Route("/Dlna/contentdirectory/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")]
[Route("/Dlna/contentdirectory/contentdirectory", "GET", Summary = "Gets dlna content directory xml")]
public class GetContentDirectory
{
}
[Route("/Dlna/{UuId}/control", "POST", Summary = "Processes a control request")]
[Route("/Dlna/contentdirectory/{UuId}/control", "POST", Summary = "Processes a control request")]
public class ProcessControlRequest : IRequiresRequestStream
{
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
public string UuId { get; set; }
public Stream RequestStream { get; set; }
}
[Route("/Dlna/contentdirectory/{UuId}/events", Summary = "Processes an event subscription request")]
public class ProcessEventRequest
{
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
public string UuId { get; set; }
}
[Route("/Dlna/icons/{Filename}", "GET", Summary = "Gets a server icon")]
public class GetIcon
{
@ -72,8 +81,8 @@ namespace MediaBrowser.Api.Dlna
private async Task<ControlResponse> PostAsync(ProcessControlRequest request)
{
var pathInfo = PathInfo.Parse(Request.PathInfo);
var id = pathInfo.GetArgumentValue<string>(1);
var id = pathInfo.GetArgumentValue<string>(2);
using (var reader = new StreamReader(request.RequestStream))
{
return _dlnaManager.ProcessControlRequest(new ControlRequest
@ -111,5 +120,76 @@ namespace MediaBrowser.Api.Dlna
}
}
}
public object Any(ProcessEventRequest request)
{
var subscriptionId = GetHeader("SID");
var notificationType = GetHeader("NT");
var callback = GetHeader("CALLBACK");
var timeoutString = GetHeader("TIMEOUT");
var timeout = ParseTimeout(timeoutString) ?? 300;
if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(notificationType))
{
RenewEvent(subscriptionId, timeout);
}
else
{
SubscribeToEvent(notificationType, timeout, callback);
}
return GetSubscriptionResponse(request.UuId, timeout);
}
UnsubscribeFromEvent(subscriptionId);
return ResultFactory.GetResult("", "text/plain");
}
private void UnsubscribeFromEvent(string subscriptionId)
{
}
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);
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private int? ParseTimeout(string header)
{
if (!string.IsNullOrEmpty(header))
{
// Starts with SECOND-
header = header.Split('-').Last();
int val;
if (int.TryParse(header, NumberStyles.Any, _usCulture, out val))
{
return val;
}
}
return null;
}
}
}

View File

@ -1,9 +1,11 @@
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;
@ -31,8 +33,9 @@ namespace MediaBrowser.Dlna
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)
public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, IServerConfigurationManager config)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
@ -44,6 +47,7 @@ namespace MediaBrowser.Dlna
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_userDataManager = userDataManager;
_config = config;
//DumpProfiles();
}
@ -451,15 +455,11 @@ namespace MediaBrowser.Dlna
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
if (current.Info.Type == DeviceProfileType.System)
{
throw new ArgumentException("System profiles are readonly");
}
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
var path = Path.Combine(UserProfilesPath, newFilename);
if (!string.Equals(path, current.Path, StringComparison.Ordinal))
if (!string.Equals(path, current.Path, StringComparison.Ordinal) &&
current.Info.Type != DeviceProfileType.System)
{
File.Delete(current.Path);
}
@ -516,15 +516,17 @@ namespace MediaBrowser.Dlna
var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
var user = GetUser(profile);
return new ControlHandler(
_logger,
_userManager,
_libraryManager,
profile,
serverAddress,
_dtoService,
_imageProcessor,
_userDataManager)
_userDataManager,
user)
.ProcessControlRequest(request);
}
@ -540,5 +542,33 @@ 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();
}
}
}

View File

@ -25,12 +25,12 @@ namespace MediaBrowser.Dlna.Server
public class ControlHandler
{
private readonly ILogger _logger;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly DeviceProfile _profile;
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
private readonly IUserDataManager _userDataManager;
private readonly User _user;
private readonly string _serverAddress;
@ -44,16 +44,16 @@ namespace MediaBrowser.Dlna.Server
private int systemID = 0;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public ControlHandler(ILogger logger, IUserManager userManager, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager)
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user)
{
_logger = logger;
_userManager = userManager;
_libraryManager = libraryManager;
_profile = profile;
_serverAddress = serverAddress;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_userDataManager = userDataManager;
_user = user;
}
public ControlResponse ProcessControlRequest(ControlRequest request)
@ -104,31 +104,24 @@ namespace MediaBrowser.Dlna.Server
_logger.Debug("Received control request {0}", method.Name);
var user = _userManager.Users.First();
var user = _user;
switch (method.LocalName)
{
case "GetSearchCapabilities":
result = HandleGetSearchCapabilities();
break;
case "GetSortCapabilities":
result = HandleGetSortCapabilities();
break;
case "GetSystemUpdateID":
result = HandleGetSystemUpdateID();
break;
case "Browse":
result = HandleBrowse(sparams, user, deviceId);
break;
case "X_GetFeatureList":
result = HandleXGetFeatureList();
break;
case "X_SetBookmark":
result = HandleXSetBookmark(sparams, user);
break;
default:
throw new ResourceNotFoundException();
}
if (string.Equals(method.LocalName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
result = HandleGetSearchCapabilities();
else if (string.Equals(method.LocalName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
result = HandleGetSortCapabilities();
else if (string.Equals(method.LocalName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
result = HandleGetSystemUpdateID();
else if (string.Equals(method.LocalName, "Browse", StringComparison.OrdinalIgnoreCase))
result = HandleBrowse(sparams, user, deviceId);
else if (string.Equals(method.LocalName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
result = HandleXGetFeatureList();
else if (string.Equals(method.LocalName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
result = HandleXSetBookmark(sparams, user);
else if (string.Equals(method.LocalName, "Search", StringComparison.OrdinalIgnoreCase))
result = HandleSearch(sparams, user, deviceId);
else
throw new ResourceNotFoundException("Unexpected control request name: " + method.LocalName);
var response = env.CreateElement(String.Format("u:{0}Response", method.LocalName), method.NamespaceURI);
rbody.AppendChild(response);
@ -241,10 +234,12 @@ namespace MediaBrowser.Dlna.Server
{
var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"];
var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", ""));
var provided = 0;
int requested = 0;
int start = 0;
var requested = 0;
var start = 0;
if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0)
{
@ -267,11 +262,13 @@ namespace MediaBrowser.Dlna.Server
var folder = (Folder)GetItemFromObjectId(id, user);
var children = GetChildrenSorted(folder, user).ToList();
var children = GetChildrenSorted(folder, user, sortCriteria).ToList();
var totalCount = children.Count;
if (string.Equals(flag, "BrowseMetadata"))
{
Browse_AddFolder(result, folder, children.Count);
Browse_AddFolder(result, folder, children.Count, filter);
provided++;
}
else
@ -292,13 +289,13 @@ namespace MediaBrowser.Dlna.Server
if (i.IsFolder)
{
var f = (Folder)i;
var childCount = GetChildrenSorted(f, user).Count();
var childCount = GetChildrenSorted(f, user, sortCriteria).Count();
Browse_AddFolder(result, f, childCount);
Browse_AddFolder(result, f, childCount, filter);
}
else
{
Browse_AddItem(result, i, user, deviceId);
Browse_AddItem(result, i, user, deviceId, filter);
}
}
}
@ -309,35 +306,175 @@ namespace MediaBrowser.Dlna.Server
{
new KeyValuePair<string,string>("Result", resXML),
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
new KeyValuePair<string,string>("TotalMatches", children.Count.ToString(_usCulture)),
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
new KeyValuePair<string,string>("UpdateID", systemID.ToString(_usCulture))
};
}
private IEnumerable<BaseItem> GetChildrenSorted(Folder folder, User user)
private IEnumerable<KeyValuePair<string, string>> HandleSearch(Headers sparams, User user, string deviceId)
{
var children = folder.GetChildren(user, true).Where(i => i.LocationType != LocationType.Virtual);
var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", ""));
var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", ""));
var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
// sort example: dc:title, dc:date
var provided = 0;
var requested = 0;
var start = 0;
if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0)
{
requested = 0;
}
if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0)
{
start = 0;
}
//var root = GetItem(id) as IMediaFolder;
var result = new XmlDocument();
var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
didl.SetAttribute("xmlns:dc", NS_DC);
didl.SetAttribute("xmlns:dlna", NS_DLNA);
didl.SetAttribute("xmlns:upnp", NS_UPNP);
didl.SetAttribute("xmlns:sec", NS_SEC);
result.AppendChild(didl);
var folder = (Folder)GetItemFromObjectId(sparams["ContainerID"], user);
var children = GetChildrenSorted(folder, user, searchCriteria, sortCriteria).ToList();
var totalCount = children.Count;
if (start > 0)
{
children = children.Skip(start).ToList();
}
if (requested > 0)
{
children = children.Take(requested).ToList();
}
provided = children.Count;
foreach (var i in children)
{
if (i.IsFolder)
{
var f = (Folder)i;
var childCount = GetChildrenSorted(f, user, searchCriteria, sortCriteria).Count();
Browse_AddFolder(result, f, childCount, filter);
}
else
{
Browse_AddItem(result, i, user, deviceId, filter);
}
}
var resXML = result.OuterXml;
return new List<KeyValuePair<string, string>>
{
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))
};
}
private IEnumerable<BaseItem> GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort)
{
if (search.SearchType == SearchType.Unknown)
{
return GetChildrenSorted(folder, user, sort);
}
var items = folder.GetRecursiveChildren(user);
items = FilterUnsupportedContent(items);
if (search.SearchType == SearchType.Audio)
{
items = items.OfType<Audio>();
}
else if (search.SearchType == SearchType.Video)
{
items = items.OfType<Video>();
}
else if (search.SearchType == SearchType.Image)
{
items = items.OfType<Photo>();
}
else if (search.SearchType == SearchType.Playlist)
{
}
return SortItems(items, user, sort);
}
private IEnumerable<BaseItem> GetChildrenSorted(Folder folder, User user, SortCriteria sort)
{
var items = folder.GetChildren(user, true);
items = FilterUnsupportedContent(items);
if (folder is Series || folder is Season || folder is BoxSet)
{
return children;
return items;
}
return _libraryManager.Sort(children, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
return SortItems(items, user, sort);
}
private IEnumerable<BaseItem> SortItems(IEnumerable<BaseItem> items, User user, SortCriteria sort)
{
return _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
}
private IEnumerable<BaseItem> FilterUnsupportedContent(IEnumerable<BaseItem> items)
{
return items.Where(i =>
{
// Unplayable
// TODO: Display and prevent playback with restricted flag?
if (i.LocationType == LocationType.Virtual)
{
return false;
}
// Unplayable
// TODO: Display and prevent playback with restricted flag?
var supportsPlaceHolder = i as ISupportsPlaceHolders;
if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder)
{
return false;
}
// Upnp renderers won't understand these
// TODO: Display and prevent playback with restricted flag?
if (i is Game || i is Book)
{
return false;
}
return true;
});
}
private BaseItem GetItemFromObjectId(string id, User user)
{
return string.IsNullOrWhiteSpace(id) || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
return string.IsNullOrWhiteSpace(id) || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
// Samsung sometimes uses 1 as root
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase)
// Samsung sometimes uses 1 as root
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase)
? user.RootFolder
: _libraryManager.GetItemById(new Guid(id));
? user.RootFolder
: _libraryManager.GetItemById(new Guid(id));
}
private void Browse_AddFolder(XmlDocument result, Folder f, int childCount)
private void Browse_AddFolder(XmlDocument result, Folder f, int childCount, Filter filter)
{
var container = result.CreateElement(string.Empty, "container", NS_DIDL);
container.SetAttribute("restricted", "0");
@ -355,7 +492,7 @@ namespace MediaBrowser.Dlna.Server
container.SetAttribute("parentID", parent.Id.ToString("N"));
}
AddCommonFields(f, container);
AddCommonFields(f, container, filter);
AddCover(f, container);
@ -377,7 +514,7 @@ namespace MediaBrowser.Dlna.Server
}
}
private void Browse_AddItem(XmlDocument result, BaseItem item, User user, string deviceId)
private void Browse_AddItem(XmlDocument result, BaseItem item, User user, string deviceId, Filter filter)
{
var element = result.CreateElement(string.Empty, "item", NS_DIDL);
element.SetAttribute("restricted", "1");
@ -392,7 +529,7 @@ namespace MediaBrowser.Dlna.Server
AddBookmarkInfo(item, user, element);
AddGeneralProperties(item, element);
AddGeneralProperties(item, element, filter);
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
@ -400,13 +537,13 @@ namespace MediaBrowser.Dlna.Server
var audio = item as Audio;
if (audio != null)
{
AddAudioResource(element, audio, deviceId);
AddAudioResource(element, audio, deviceId, filter);
}
var video = item as Video;
if (video != null)
{
AddVideoResource(element, video, deviceId);
AddVideoResource(element, video, deviceId, filter);
}
AddCover(item, element);
@ -414,7 +551,7 @@ namespace MediaBrowser.Dlna.Server
result.DocumentElement.AppendChild(element);
}
private void AddVideoResource(XmlElement container, Video video, string deviceId)
private void AddVideoResource(XmlElement container, Video video, string deviceId, Filter filter)
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
@ -441,13 +578,16 @@ namespace MediaBrowser.Dlna.Server
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
}
if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
if (filter.Contains("res@size"))
{
var size = streamInfo.TargetSize;
if (size.HasValue)
if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
var size = streamInfo.TargetSize;
if (size.HasValue)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
}
}
}
@ -473,11 +613,14 @@ namespace MediaBrowser.Dlna.Server
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
}
if (targetWidth.HasValue && targetHeight.HasValue)
if (filter.Contains("res@resolution"))
{
res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
if (targetWidth.HasValue && targetHeight.HasValue)
{
res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
}
}
if (targetSampleRate.HasValue)
{
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
@ -514,7 +657,7 @@ namespace MediaBrowser.Dlna.Server
container.AppendChild(res);
}
private void AddAudioResource(XmlElement container, Audio audio, string deviceId)
private void AddAudioResource(XmlElement container, Audio audio, string deviceId, Filter filter)
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
@ -538,13 +681,16 @@ namespace MediaBrowser.Dlna.Server
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
}
if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
if (filter.Contains("res@size"))
{
var size = streamInfo.TargetSize;
if (size.HasValue)
if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
var size = streamInfo.TargetSize;
if (size.HasValue)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
}
}
}
@ -659,13 +805,17 @@ namespace MediaBrowser.Dlna.Server
/// <summary>
/// Adds fields used by both items and folders
/// </summary>
/// <param name="item"></param>
/// <param name="element"></param>
private void AddCommonFields(BaseItem item, XmlElement element)
/// <param name="item">The item.</param>
/// <param name="element">The element.</param>
/// <param name="filter">The filter.</param>
private void AddCommonFields(BaseItem item, XmlElement element, Filter filter)
{
if (item.PremiereDate.HasValue)
if (filter.Contains("dc:date"))
{
AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
if (item.PremiereDate.HasValue)
{
AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
}
}
if (item.Genres.Count > 0)
@ -678,24 +828,44 @@ namespace MediaBrowser.Dlna.Server
AddValue(element, "upnp", "publisher", item.Studios[0], NS_UPNP);
}
AddValue(element, "dc", "title", item.Name, NS_DC);
if (!string.IsNullOrWhiteSpace(item.Overview))
if (filter.Contains("dc:title"))
{
AddValue(element, "dc", "description", item.Overview, NS_DC);
AddValue(element, "dc", "title", item.Name, NS_DC);
}
if (filter.Contains("dc:description"))
{
if (!string.IsNullOrWhiteSpace(item.Overview))
{
AddValue(element, "dc", "description", item.Overview, NS_DC);
}
}
if (filter.Contains("upnp:longDescription"))
{
if (!string.IsNullOrWhiteSpace(item.Overview))
{
AddValue(element, "upnp", "longDescription", item.Overview, NS_UPNP);
}
}
if (!string.IsNullOrEmpty(item.OfficialRating))
{
AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
if (filter.Contains("dc:rating"))
{
AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
}
if (filter.Contains("upnp:rating"))
{
AddValue(element, "upnp", "rating", item.OfficialRating, NS_UPNP);
}
}
AddPeople(item, element);
}
private void AddGeneralProperties(BaseItem item, XmlElement element)
private void AddGeneralProperties(BaseItem item, XmlElement element, Filter filter)
{
AddCommonFields(item, element);
AddCommonFields(item, element, filter);
var audio = item as Audio;
@ -861,7 +1031,7 @@ namespace MediaBrowser.Dlna.Server
{
}
return new ImageDownloadInfo
{
ItemId = item.Id.ToString("N"),

View File

@ -163,7 +163,7 @@ namespace MediaBrowser.Dlna.Server
Height = 48,
Url = "/mediabrowser/dlna/icons/logo48.jpg"
});
return list;
}
@ -175,8 +175,9 @@ namespace MediaBrowser.Dlna.Server
{
ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1",
ServiceId = "urn:upnp-org:serviceId:ContentDirectory",
ScpdUrl = "/mediabrowser/dlna/contentdirectory.xml",
ControlUrl = "/mediabrowser/dlna/" + _serverUdn + "/control"
ScpdUrl = "/mediabrowser/dlna/contentdirectory/contentdirectory.xml",
ControlUrl = "/mediabrowser/dlna/contentdirectory/" + _serverUdn + "/control",
EventSubUrl = "/mediabrowser/dlna/contentdirectory/" + _serverUdn + "/events"
});
return list;

View File

@ -160,5 +160,17 @@ namespace MediaBrowser.Dlna.Server
{
return _dict.TryGetValue(Normalize(key), out value);
}
public string GetValueOrDefault(string key, string defaultValue)
{
string val;
if (TryGetValue(key, out val))
{
return val;
}
return defaultValue;
}
}
}

View File

@ -107,7 +107,11 @@ namespace MediaBrowser.Dlna.Server
break;
}
var parts = line.Split(new[] { ':' }, 2);
headers[parts[0]] = parts[1].Trim();
if (parts.Length >= 2)
{
headers[parts[0]] = parts[1].Trim();
}
}
if (_config.Configuration.DlnaOptions.EnableDebugLogging)
@ -158,18 +162,21 @@ namespace MediaBrowser.Dlna.Server
private void SendSearchResponse(IPEndPoint endpoint, UpnpDevice dev)
{
var headers = new Headers(true);
headers.Add("CACHE-CONTROL", "max-age = 600");
headers.Add("DATE", DateTime.Now.ToString("R"));
headers.Add("EXT", "");
headers.Add("LOCATION", dev.Descriptor.ToString());
headers.Add("SERVER", _serverSignature);
headers.Add("ST", dev.Type);
headers.Add("USN", dev.USN);
var builder = new StringBuilder();
var msg = String.Format("HTTP/1.1 200 OK\r\n{0}\r\n", headers.HeaderBlock);
const string argFormat = "{0}: {1}\r\n";
SendDatagram(endpoint, dev.Address, msg, false);
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());
}
@ -237,19 +244,22 @@ namespace MediaBrowser.Dlna.Server
private void NotifyDevice(UpnpDevice dev, string type, bool sticky)
{
_logger.Debug("NotifyDevice");
var headers = new Headers(true);
headers.Add("HOST", "239.255.255.250:1900");
headers.Add("CACHE-CONTROL", "max-age = 600");
headers.Add("LOCATION", dev.Descriptor.ToString());
headers.Add("SERVER", _serverSignature);
headers.Add("NTS", "ssdp:" + type);
headers.Add("NT", dev.Type);
headers.Add("USN", dev.USN);
var builder = new StringBuilder();
var msg = String.Format("NOTIFY * HTTP/1.1\r\n{0}\r\n", headers.HeaderBlock);
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");
_logger.Debug("{0} said {1}", dev.USN, type);
SendDatagram(_ssdpEndp, dev.Address, msg, sticky);
SendDatagram(_ssdpEndp, dev.Address, builder.ToString(), sticky);
}
public void RegisterNotification(Guid uuid, Uri descriptor, IPAddress address)
@ -343,7 +353,12 @@ namespace MediaBrowser.Dlna.Server
private readonly object _notificationTimerSyncLock = new object();
private void StartNotificationTimer()
{
const int intervalMs = 60000;
if (!_config.Configuration.DlnaOptions.BlastAliveMessages)
{
return;
}
var intervalMs = _config.Configuration.DlnaOptions.BlastAliveMessageIntervalSeconds * 1000;
lock (_notificationTimerSyncLock)
{

View File

@ -19,13 +19,13 @@ namespace MediaBrowser.Dlna.Server
Address = address;
if (Type.StartsWith("uuid:"))
if (Type.StartsWith("uuid:", StringComparison.OrdinalIgnoreCase))
{
USN = Type;
}
else
{
USN = String.Format("uuid:{0}::{1}", Uuid.ToString(), Type);
USN = String.Format("uuid:{0}::{1}", Uuid.ToString("N"), Type);
}
}
}

View File

@ -27,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <returns>System.String.</returns>
private static string GetConcatInputArgument(List<string> inputFiles)
private static string GetConcatInputArgument(IReadOnlyList<string> inputFiles)
{
// Get all streams
// If there's more than one we'll need to use the concat command

View File

@ -122,6 +122,9 @@
<Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs">
<Link>Dlna\DlnaMaps.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\Filter.cs">
<Link>Dlna\Filter.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfile.cs">
<Link>Dlna\MediaFormatProfile.cs</Link>
</Compile>
@ -131,6 +134,12 @@
<Compile Include="..\MediaBrowser.Model\Dlna\ResponseProfile.cs">
<Link>Dlna\ResponseProfile.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\SearchCriteria.cs">
<Link>Dlna\SearchCriteria.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\SortCriteria.cs">
<Link>Dlna\SortCriteria.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\StreamBuilder.cs">
<Link>Dlna\StreamBuilder.cs</Link>
</Compile>

View File

@ -109,6 +109,9 @@
<Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs">
<Link>Dlna\DlnaMaps.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\Filter.cs">
<Link>Dlna\Filter.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfile.cs">
<Link>Dlna\MediaFormatProfile.cs</Link>
</Compile>
@ -118,6 +121,12 @@
<Compile Include="..\MediaBrowser.Model\Dlna\ResponseProfile.cs">
<Link>Dlna\ResponseProfile.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\SearchCriteria.cs">
<Link>Dlna\SearchCriteria.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\SortCriteria.cs">
<Link>Dlna\SortCriteria.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\StreamBuilder.cs">
<Link>Dlna\StreamBuilder.cs</Link>
</Compile>

View File

@ -6,13 +6,18 @@ namespace MediaBrowser.Model.Configuration
public bool EnablePlayTo { get; set; }
public bool EnableServer { get; set; }
public bool EnableDebugLogging { get; set; }
public bool BlastAliveMessages { get; set; }
public int ClientDiscoveryIntervalSeconds { get; set; }
public int BlastAliveMessageIntervalSeconds { get; set; }
public string DefaultUserId { get; set; }
public DlnaOptions()
{
EnablePlayTo = true;
EnableServer = true;
BlastAliveMessages = true;
ClientDiscoveryIntervalSeconds = 60;
BlastAliveMessageIntervalSeconds = 60;
}
}
}

View File

@ -40,6 +40,8 @@ namespace MediaBrowser.Model.Dlna
public string SupportedMediaTypes { get; set; }
public string UserId { get; set; }
/// <summary>
/// Controls the content of the X_DLNADOC element in the urn:schemas-dlna-org:device-1-0 namespace.
/// </summary>

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Model.Dlna
{
public class Filter
{
private readonly List<string> _fields;
private readonly bool _all;
public Filter(string filter)
{
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
_fields = (filter ?? string.Empty)
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
}
public bool Contains(string field)
{
return _all || _fields.Contains(field, StringComparer.OrdinalIgnoreCase);
}
}
}

View File

@ -0,0 +1,49 @@
using System;
namespace MediaBrowser.Model.Dlna
{
public class SearchCriteria
{
public SearchType SearchType { get; set; }
public SearchCriteria(string search)
{
if (string.IsNullOrEmpty(search))
{
throw new ArgumentNullException("search");
}
SearchType = SearchType.Unknown;
if (search.IndexOf("upnp:class", StringComparison.OrdinalIgnoreCase) != -1 &&
search.IndexOf("derivedfrom", StringComparison.OrdinalIgnoreCase) != -1)
{
if (search.IndexOf("object.item.audioItem", StringComparison.OrdinalIgnoreCase) != -1)
{
SearchType = SearchType.Audio;
}
else if (search.IndexOf("object.item.imageItem", StringComparison.OrdinalIgnoreCase) != -1)
{
SearchType = SearchType.Image;
}
else if (search.IndexOf("object.item.videoItem", StringComparison.OrdinalIgnoreCase) != -1)
{
SearchType = SearchType.Video;
}
else if (search.IndexOf("object.container.playlistContainer", StringComparison.OrdinalIgnoreCase) != -1)
{
SearchType = SearchType.Playlist;
}
}
}
}
public enum SearchType
{
Unknown = 0,
Audio = 1,
Image = 2,
Video = 3,
Playlist = 4
}
}

View File

@ -0,0 +1,11 @@

namespace MediaBrowser.Model.Dlna
{
public class SortCriteria
{
public SortCriteria(string value)
{
}
}
}

View File

@ -73,9 +73,12 @@
<Compile Include="Dlna\DeviceProfileInfo.cs" />
<Compile Include="Dlna\DirectPlayProfile.cs" />
<Compile Include="Dlna\DlnaMaps.cs" />
<Compile Include="Dlna\Filter.cs" />
<Compile Include="Dlna\MediaFormatProfile.cs" />
<Compile Include="Dlna\MediaFormatProfileResolver.cs" />
<Compile Include="Dlna\ResponseProfile.cs" />
<Compile Include="Dlna\SearchCriteria.cs" />
<Compile Include="Dlna\SortCriteria.cs" />
<Compile Include="Dlna\StreamBuilder.cs" />
<Compile Include="Dlna\StreamInfo.cs" />
<Compile Include="Dlna\TranscodingProfile.cs" />

View File

@ -44,7 +44,16 @@ namespace MediaBrowser.Providers.Movies
var file = new FileInfo(specificFile);
return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml"));
// In a mixed folder, only {moviename}.xml is supported
if (info.IsInMixedFolder)
{
return file;
}
// If in it's own folder, prefer movie.xml, but allow the specific file as well
var movieFile = new FileInfo(Path.Combine(directoryPath, "movie.xml"));
return movieFile.Exists ? movieFile : file;
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -414,7 +414,7 @@
"LabelEnableDlnaDebugLogging": "Enable DLNA debug logging",
"LabelEnableDlnaDebugLoggingHelp": "This will create large log files and should only be used as needed for troubleshooting purposes.",
"LabelEnableDlnaClientDiscoveryInterval": "Client discovery interval (seconds)",
"LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds of the interval between SSDP searches performed by Media Browser.",
"LabelEnableDlnaClientDiscoveryIntervalHelp": "Determines the duration in seconds between SSDP searches performed by Media Browser.",
"HeaderCustomDlnaProfiles": "Custom Profiles",
"HeaderSystemDlnaProfiles": "System Profiles",
"CustomDlnaProfilesHelp": "Create a custom profile to target a new device or override a system profile.",
@ -532,5 +532,15 @@
"LabelSupporterKey": "Supporter Key (paste from email)",
"LabelSupporterKeyHelp": "Enter your supporter key to start enjoying additional benefits the community has developed for Media Browser.",
"MessageInvalidKey": "MB3 Key Missing or Invalid",
"ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you."
"ErrorMessageInvalidKey": "In order for any premium content to be registered, you must also be an MB3 Supporter. Please donate and support the continued development of the core product. Thank you.",
"HeaderDisplaySettings": "Display Settings",
"TabPlayTo": "Play To",
"LabelEnableDlnaServer": "Enable Dlna server",
"LabelEnableDlnaServerHelp": "Allows UPnP devices on your network to browse and play Media Browser content.",
"LabelEnableBlastAliveMessages": "Blast alive messages",
"LabelEnableBlastAliveMessagesHelp": "Enable this if the server is not detected reliably by other UPnP devices on your network.",
"LabelBlastMessageInterval": "Alive message interval (seconds)",
"LabelBlastMessageIntervalHelp": "Determines the duration in seconds between server alive messages.",
"LabelDefaultUser": "Default user:",
"LabelDefaultUserHelp": "Determines which user library should be displayed on connected devices. This can be overridden using a device profile."
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -506,7 +506,7 @@ 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);
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer, UserManager, LibraryManager, DtoService, ImageProcessor, UserDataManager, ServerConfigurationManager);
RegisterSingleInstance<IDlnaManager>(dlnaManager);
var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);

View File

@ -543,6 +543,7 @@ namespace MediaBrowser.WebDashboard.Api
"dlnaprofile.js",
"dlnaprofiles.js",
"dlnasettings.js",
"dlnaserversettings.js",
"editcollectionitems.js",
"edititemmetadata.js",
"edititempeople.js",

View File

@ -241,6 +241,9 @@
<Content Include="dashboard-ui\dlnaprofiles.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\dlnaserversettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\dlnasettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -541,6 +544,9 @@
<Content Include="dashboard-ui\scripts\dlnaprofiles.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\dlnaserversettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\dlnasettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>