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.
jellyfin/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs

648 lines
24 KiB

using MediaBrowser.Common.Extensions;
10 years ago
using MediaBrowser.Controller.Channels;
11 years ago
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
10 years ago
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Dlna.Didl;
using MediaBrowser.Dlna.Server;
using MediaBrowser.Dlna.Service;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Controller.MediaEncoding;
namespace MediaBrowser.Dlna.ContentDirectory
{
public class ControlHandler : BaseControlHandler
{
private readonly ILibraryManager _libraryManager;
10 years ago
private readonly IChannelManager _channelManager;
private readonly IUserDataManager _userDataManager;
private readonly IServerConfigurationManager _config;
11 years ago
private readonly User _user;
private readonly IUserViewManager _userViewManager;
private readonly IMediaEncoder _mediaEncoder;
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private readonly int _systemUpdateId;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly DidlBuilder _didlBuilder;
private readonly DeviceProfile _profile;
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder)
: base(config, logger)
{
_libraryManager = libraryManager;
_userDataManager = userDataManager;
11 years ago
_user = user;
_systemUpdateId = systemUpdateId;
10 years ago
_channelManager = channelManager;
_userViewManager = userViewManager;
_mediaEncoder = mediaEncoder;
_profile = profile;
_config = config;
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, _mediaEncoder);
}
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
{
var deviceId = "test";
11 years ago
var user = _user;
11 years ago
if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
return HandleGetSearchCapabilities();
11 years ago
if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
return HandleGetSortCapabilities();
10 years ago
if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase))
return HandleGetSortExtensionCapabilities();
if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
return HandleGetSystemUpdateID();
if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase))
return HandleBrowse(methodParams, user, deviceId).Result;
if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
return HandleXGetFeatureList();
10 years ago
if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase))
return HandleGetFeatureList();
if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
return HandleXSetBookmark(methodParams, user);
11 years ago
if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
return HandleSearch(methodParams, user, deviceId).Result;
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
private IEnumerable<KeyValuePair<string, string>> HandleXSetBookmark(IDictionary<string, string> sparams, User user)
{
var id = sparams["ObjectID"];
var serverItem = GetItemFromObjectId(id, user);
var item = serverItem.Item;
var newbookmark = int.Parse(sparams["PosSecond"], _usCulture);
var userdata = _userDataManager.GetUserData(user, item);
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
_userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed,
CancellationToken.None);
return new Headers();
}
private IEnumerable<KeyValuePair<string, string>> HandleGetSearchCapabilities()
{
return new Headers(true) { { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } };
}
private IEnumerable<KeyValuePair<string, string>> HandleGetSortCapabilities()
{
10 years ago
return new Headers(true)
{
{ "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
};
}
private IEnumerable<KeyValuePair<string, string>> HandleGetSortExtensionCapabilities()
{
return new Headers(true)
{
{ "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
};
}
private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID()
{
10 years ago
var headers = new Headers(true);
headers.Add("Id", _systemUpdateId.ToString(_usCulture));
return headers;
}
10 years ago
private IEnumerable<KeyValuePair<string, string>> HandleGetFeatureList()
{
return new Headers(true)
{
{ "FeatureList", GetFeatureListXml() }
};
}
private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList()
{
10 years ago
return new Headers(true)
{
{ "FeatureList", GetFeatureListXml() }
};
}
private string GetFeatureListXml()
{
var builder = new StringBuilder();
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
builder.Append("<Features xmlns=\"urn:schemas-upnp-org:av:avs\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">");
builder.Append("<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">");
builder.Append("<container id=\"I\" type=\"object.item.imageItem\"/>");
builder.Append("<container id=\"A\" type=\"object.item.audioItem\"/>");
builder.Append("<container id=\"V\" type=\"object.item.videoItem\"/>");
builder.Append("</Feature>");
builder.Append("</Features>");
return builder.ToString();
}
private async Task<IEnumerable<KeyValuePair<string, string>>> HandleBrowse(Headers sparams, User user, string deviceId)
{
var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"];
11 years ago
var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", ""));
var provided = 0;
// Default to null instead of 0
// Upnp inspector sends 0 as requestedCount when it wants everything
int? requestedCount = null;
int? start = 0;
int requestedVal;
if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requestedVal) && requestedVal > 0)
{
requestedCount = requestedVal;
}
int startVal;
if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out startVal) && startVal > 0)
{
start = startVal;
}
//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 serverItem = GetItemFromObjectId(id, user);
var item = serverItem.Item;
int totalCount;
if (string.Equals(flag, "BrowseMetadata"))
{
totalCount = 1;
if (item.IsFolder || serverItem.StubType.HasValue)
{
var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false));
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id));
}
else
{
result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, item, null, null, deviceId, filter));
}
provided++;
}
else
{
var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount).ConfigureAwait(false));
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Length;
foreach (var i in childrenResult.Items)
{
var childItem = i.Item;
var displayStubType = i.StubType;
if (childItem.IsFolder || displayStubType.HasValue)
{
var childCount = (await GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0).ConfigureAwait(false))
.TotalRecordCount;
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, childItem, displayStubType, item, childCount, filter));
}
else
{
result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, childItem, item, serverItem.StubType, 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", _systemUpdateId.ToString(_usCulture))
};
}
private async Task<IEnumerable<KeyValuePair<string, string>>> HandleSearch(Headers sparams, User user, string deviceId)
{
11 years ago
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
// Default to null instead of 0
// Upnp inspector sends 0 as requestedCount when it wants everything
int? requestedCount = null;
int? start = 0;
11 years ago
int requestedVal;
if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requestedVal) && requestedVal > 0)
11 years ago
{
requestedCount = requestedVal;
11 years ago
}
int startVal;
if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out startVal) && startVal > 0)
11 years ago
{
start = startVal;
11 years ago
}
//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);
foreach (var att in _profile.XmlRootAttributes)
{
didl.SetAttribute(att.Name, att.Value);
}
11 years ago
result.AppendChild(didl);
var serverItem = GetItemFromObjectId(sparams["ContainerID"], user);
var item = serverItem.Item;
11 years ago
var childrenResult = (await GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount).ConfigureAwait(false));
11 years ago
var totalCount = childrenResult.TotalRecordCount;
11 years ago
var provided = childrenResult.Items.Length;
11 years ago
foreach (var i in childrenResult.Items)
11 years ago
{
if (i.IsFolder)
{
var childCount = (await GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0).ConfigureAwait(false))
.TotalRecordCount;
11 years ago
result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, i, null, item, childCount, filter));
11 years ago
}
else
{
result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(_config.GetDlnaConfiguration(), result, i, item, serverItem.StubType, deviceId, filter));
11 years ago
}
}
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", _systemUpdateId.ToString(_usCulture))
11 years ago
};
}
private Task<QueryResult<BaseItem>> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
11 years ago
{
var folder = (Folder)item;
var sortOrders = new List<string>();
if (!folder.IsPreSorted)
{
sortOrders.Add(ItemSortBy.SortName);
}
var mediaTypes = new List<string>();
bool? isFolder = null;
11 years ago
if (search.SearchType == SearchType.Audio)
{
mediaTypes.Add(MediaType.Audio);
isFolder = false;
11 years ago
}
else if (search.SearchType == SearchType.Video)
{
mediaTypes.Add(MediaType.Video);
isFolder = false;
11 years ago
}
else if (search.SearchType == SearchType.Image)
{
mediaTypes.Add(MediaType.Photo);
isFolder = false;
11 years ago
}
else if (search.SearchType == SearchType.Playlist)
{
//items = items.OfType<Playlist>();
isFolder = true;
11 years ago
}
else if (search.SearchType == SearchType.MusicAlbum)
{
//items = items.OfType<MusicAlbum>();
isFolder = true;
}
return folder.GetItems(new InternalItemsQuery
{
Limit = limit,
StartIndex = startIndex,
SortBy = sortOrders.ToArray(),
SortOrder = sort.SortOrder,
User = user,
Recursive = true,
IsMissing = false,
ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
IsFolder = isFolder,
MediaTypes = mediaTypes.ToArray()
});
}
private async Task<QueryResult<ServerItem>> GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit)
{
if (stubType.HasValue)
{
if (stubType.Value == StubType.People)
{
9 years ago
var items = _libraryManager.GetPeopleItems(new InternalPeopleQuery
{
ItemId = item.Id
}).ToArray();
var result = new QueryResult<ServerItem>
{
10 years ago
Items = items.Select(i => new ServerItem { Item = i, StubType = StubType.Folder }).ToArray(),
TotalRecordCount = items.Length
};
return ApplyPaging(result, startIndex, limit);
}
if (stubType.Value == StubType.Folder)
{
var movie = item as Movie;
if (movie != null)
{
return ApplyPaging(await GetMovieItems(movie).ConfigureAwait(false), startIndex, limit);
}
}
10 years ago
var person = item as Person;
if (person != null)
{
9 years ago
return GetItemsFromPerson(person, user, startIndex, limit);
10 years ago
}
return ApplyPaging(new QueryResult<ServerItem>(), startIndex, limit);
}
var folder = (Folder)item;
var sortOrders = new List<string>();
if (!folder.IsPreSorted)
{
sortOrders.Add(ItemSortBy.SortName);
}
9 years ago
var queryResult = await folder.GetItems(new InternalItemsQuery
{
9 years ago
Limit = limit,
StartIndex = startIndex,
SortBy = sortOrders.ToArray(),
SortOrder = sort.SortOrder,
User = user,
IsMissing = false,
PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music },
ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
IsPlaceHolder = false
9 years ago
}).ConfigureAwait(false);
var options = _config.GetDlnaConfiguration();
var serverItems = queryResult
.Items
.Select(i => new ServerItem
{
Item = i,
StubType = GetDisplayStubType(i, item, options)
})
.ToArray();
return new QueryResult<ServerItem>
{
TotalRecordCount = queryResult.TotalRecordCount,
Items = serverItems
};
}
9 years ago
private QueryResult<ServerItem> GetItemsFromPerson(Person person, User user, int? startIndex, int? limit)
10 years ago
{
var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
10 years ago
{
Person = person.Name,
9 years ago
IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name, typeof(Trailer).Name },
SortBy = new[] { ItemSortBy.SortName },
Limit = limit,
StartIndex = startIndex
});
10 years ago
var serverItems = itemsResult.Items.Select(i => new ServerItem
10 years ago
{
Item = i,
StubType = null
})
.ToArray();
return new QueryResult<ServerItem>
{
TotalRecordCount = itemsResult.TotalRecordCount,
10 years ago
Items = serverItems
};
}
private QueryResult<ServerItem> ApplyPaging(QueryResult<ServerItem> result, int? startIndex, int? limit)
{
result.Items = result.Items.Skip(startIndex ?? 0).Take(limit ?? int.MaxValue).ToArray();
return result;
}
private StubType? GetDisplayStubType(BaseItem item, BaseItem context, DlnaOptions options)
{
if (context == null || context.IsFolder)
{
var movie = item as Movie;
if (movie != null && options.EnableMovieFolders)
{
if (movie.GetTrailerIds().Count > 0 ||
movie.SpecialFeatureIds.Count > 0)
{
return StubType.Folder;
}
if (EnablePeopleDisplay(item))
{
return StubType.Folder;
}
}
}
return null;
}
10 years ago
private bool EnablePeopleDisplay(BaseItem item)
{
9 years ago
if (_libraryManager.GetPeopleNames(new InternalPeopleQuery
{
ItemId = item.Id
}).Count > 0)
10 years ago
{
return item is Movie;
}
return false;
}
private Task<QueryResult<ServerItem>> GetMovieItems(Movie item)
{
var list = new List<BaseItem>();
list.Add(item);
list.AddRange(item.GetTrailerIds().Select(i => _libraryManager.GetItemById(i)).Where(i => i != null));
list.AddRange(item.SpecialFeatureIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null));
var serverItems = list.Select(i => new ServerItem { Item = i, StubType = null })
.ToList();
serverItems.Add(new ServerItem
{
Item = item,
StubType = StubType.People
});
return Task.FromResult(new QueryResult<ServerItem>
{
Items = serverItems.ToArray(),
TotalRecordCount = serverItems.Count
});
}
private ServerItem GetItemFromObjectId(string id, User user)
11 years ago
{
return DidlBuilder.IsIdRoot(id)
11 years ago
? new ServerItem { Item = user.RootFolder }
10 years ago
: ParseItemId(id, user);
}
private ServerItem ParseItemId(string id, User user)
10 years ago
{
Guid itemId;
StubType? stubType = null;
// After using PlayTo, MediaMonkey sends a request to the server trying to get item info
const string paramsSrch = "Params=";
var paramsIndex = id.IndexOf(paramsSrch, StringComparison.OrdinalIgnoreCase);
if (paramsIndex != -1)
{
id = id.Substring(paramsIndex + paramsSrch.Length);
var parts = id.Split(';');
id = parts[24];
}
if (id.StartsWith("folder_", StringComparison.OrdinalIgnoreCase))
{
stubType = StubType.Folder;
id = id.Split(new[] { '_' }, 2)[1];
}
else if (id.StartsWith("people_", StringComparison.OrdinalIgnoreCase))
{
stubType = StubType.People;
id = id.Split(new[] { '_' }, 2)[1];
}
10 years ago
if (Guid.TryParse(id, out itemId))
{
var item = _libraryManager.GetItemById(itemId);
return new ServerItem
{
Item = item,
StubType = stubType
};
10 years ago
}
Logger.Error("Error parsing item Id: {0}. Returning user root folder.", id);
return new ServerItem { Item = user.RootFolder };
11 years ago
}
}
internal class ServerItem
{
public BaseItem Item { get; set; }
public StubType? StubType { get; set; }
}
public enum StubType
{
Folder = 0,
People = 1
}
}