#514 - Support HLS seeking

This commit is contained in:
Luke Pulverenti 2014-07-01 17:13:32 -04:00
parent 970504abdf
commit 3bef6ead9c
16 changed files with 110 additions and 101 deletions

View File

@ -159,7 +159,7 @@ namespace MediaBrowser.Api
return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager)); return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
} }
protected IList<BaseItem> GetAllLibraryItems(Guid? userId, IUserManager userManager, ILibraryManager libraryManager, string parentId = null) protected IEnumerable<BaseItem> GetAllLibraryItems(Guid? userId, IUserManager userManager, ILibraryManager libraryManager, string parentId = null)
{ {
if (!string.IsNullOrEmpty(parentId)) if (!string.IsNullOrEmpty(parentId))
{ {
@ -169,7 +169,7 @@ namespace MediaBrowser.Api
{ {
var user = userManager.GetUserById(userId.Value); var user = userManager.GetUserById(userId.Value);
return folder.GetRecursiveChildren(user).ToList(); return folder.GetRecursiveChildren(user);
} }
return folder.GetRecursiveChildren(); return folder.GetRecursiveChildren();
@ -178,7 +178,7 @@ namespace MediaBrowser.Api
{ {
var user = userManager.GetUserById(userId.Value); var user = userManager.GetUserById(userId.Value);
return userManager.GetUserById(userId.Value).RootFolder.GetRecursiveChildren(user, null); return userManager.GetUserById(userId.Value).RootFolder.GetRecursiveChildren(user);
} }
return libraryManager.RootFolder.GetRecursiveChildren(); return libraryManager.RootFolder.GetRecursiveChildren();
@ -239,7 +239,8 @@ namespace MediaBrowser.Api
return name; return name;
} }
return libraryManager.RootFolder.GetRecursiveChildren(i => i is Game) return libraryManager.RootFolder.GetRecursiveChildren()
.OfType<Game>()
.SelectMany(i => i.Genres) .SelectMany(i => i.Genres)
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.FirstOrDefault(i => .FirstOrDefault(i =>

View File

@ -381,7 +381,7 @@ namespace MediaBrowser.Api.DefaultTheme
var currentUser1 = user; var currentUser1 = user;
var ownedEpisodes = series var ownedEpisodes = series
.SelectMany(i => i.GetRecursiveChildren(currentUser1, j => j.LocationType != LocationType.Virtual)) .SelectMany(i => i.GetRecursiveChildren(currentUser1).Where(j => j.LocationType != LocationType.Virtual))
.OfType<Episode>() .OfType<Episode>()
.ToList(); .ToList();

View File

@ -59,10 +59,11 @@ namespace MediaBrowser.Api.Playback.Hls
/// Processes the request. /// Processes the request.
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <param name="isLive">if set to <c>true</c> [is live].</param>
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
protected object ProcessRequest(StreamRequest request) protected object ProcessRequest(StreamRequest request, bool isLive)
{ {
return ProcessRequestAsync(request).Result; return ProcessRequestAsync(request, isLive).Result;
} }
private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1);
@ -70,13 +71,12 @@ namespace MediaBrowser.Api.Playback.Hls
/// Processes the request async. /// Processes the request async.
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <param name="isLive">if set to <c>true</c> [is live].</param>
/// <returns>Task{System.Object}.</returns> /// <returns>Task{System.Object}.</returns>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">A video bitrate is required
/// A video bitrate is required
/// or /// or
/// An audio bitrate is required /// An audio bitrate is required</exception>
/// </exception> private async Task<object> ProcessRequestAsync(StreamRequest request, bool isLive)
private async Task<object> ProcessRequestAsync(StreamRequest request)
{ {
var cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
@ -110,7 +110,8 @@ namespace MediaBrowser.Api.Playback.Hls
throw; throw;
} }
await WaitForMinimumSegmentCount(playlist, GetSegmentWait(), cancellationTokenSource.Token).ConfigureAwait(false); var waitCount = isLive ? 1 : GetSegmentWait();
await WaitForMinimumSegmentCount(playlist, waitCount, cancellationTokenSource.Token).ConfigureAwait(false);
} }
} }
finally finally
@ -119,6 +120,22 @@ namespace MediaBrowser.Api.Playback.Hls
} }
} }
if (isLive)
{
//var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
//file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
try
{
return ResultFactory.GetStaticFileResult(Request, playlist, FileShare.ReadWrite);
}
finally
{
ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
}
}
var audioBitrate = state.OutputAudioBitrate ?? 0; var audioBitrate = state.OutputAudioBitrate ?? 0;
var videoBitrate = state.OutputVideoBitrate ?? 0; var videoBitrate = state.OutputVideoBitrate ?? 0;
@ -188,16 +205,18 @@ namespace MediaBrowser.Api.Playback.Hls
protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken) protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
{ {
var count = 0; Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist);
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written while (true)
using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{ {
using (var reader = new StreamReader(fileStream)) // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{ {
while (true) using (var reader = new StreamReader(fileStream))
{ {
if (!reader.EndOfStream) var count = 0;
while (!reader.EndOfStream)
{ {
var line = await reader.ReadLineAsync().ConfigureAwait(false); var line = await reader.ReadLineAsync().ConfigureAwait(false);
@ -206,11 +225,12 @@ namespace MediaBrowser.Api.Playback.Hls
count++; count++;
if (count >= segmentCount) if (count >= segmentCount)
{ {
Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
return; return;
} }
} }
} }
await Task.Delay(25, cancellationToken).ConfigureAwait(false); await Task.Delay(100, cancellationToken).ConfigureAwait(false);
} }
} }
} }
@ -229,7 +249,7 @@ namespace MediaBrowser.Api.Playback.Hls
var itsOffsetMs = hlsVideoRequest == null var itsOffsetMs = hlsVideoRequest == null
? 0 ? 0
: ((GetHlsVideoStream)state.VideoRequest).TimeStampOffsetMs; : hlsVideoRequest.TimeStampOffsetMs;
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture)); var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
@ -240,7 +260,15 @@ namespace MediaBrowser.Api.Playback.Hls
// If isEncoding is true we're actually starting ffmpeg // If isEncoding is true we're actually starting ffmpeg
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0"; var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"", var baseUrlParam = string.Empty;
if (state.Request is GetLiveHlsStream)
{
baseUrlParam = string.Format(" -hls_base_url \"{0}/\"",
"hls/" + Path.GetFileNameWithoutExtension(outputPath));
}
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
itsOffset, itsOffset,
inputModifier, inputModifier,
GetInputArgument(state), GetInputArgument(state),
@ -251,6 +279,7 @@ namespace MediaBrowser.Api.Playback.Hls
state.SegmentLength.ToString(UsCulture), state.SegmentLength.ToString(UsCulture),
startNumberParam, startNumberParam,
state.HlsListSize.ToString(UsCulture), state.HlsListSize.ToString(UsCulture),
baseUrlParam,
outputPath outputPath
).Trim(); ).Trim();

View File

@ -22,11 +22,6 @@ namespace MediaBrowser.Api.Playback.Hls
[Api(Description = "Gets a video stream using HTTP live streaming.")] [Api(Description = "Gets a video stream using HTTP live streaming.")]
public class GetMasterHlsVideoStream : VideoStreamRequest public class GetMasterHlsVideoStream : VideoStreamRequest
{ {
[ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? BaselineStreamAudioBitRate { get; set; }
[ApiMember(Name = "AppendBaselineStream", Description = "Optional. Whether or not to include a baseline audio-only stream in the master playlist.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool AppendBaselineStream { get; set; }
} }
[Route("/Videos/{Id}/main.m3u8", "GET")] [Route("/Videos/{Id}/main.m3u8", "GET")]
@ -35,12 +30,6 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
} }
[Route("/Videos/{Id}/baseline.m3u8", "GET")]
[Api(Description = "Gets a video stream using HTTP live streaming.")]
public class GetBaselineHlsVideoStream : VideoStreamRequest
{
}
/// <summary> /// <summary>
/// Class GetHlsVideoSegment /// Class GetHlsVideoSegment
/// </summary> /// </summary>
@ -73,16 +62,11 @@ namespace MediaBrowser.Api.Playback.Hls
public object Get(GetDynamicHlsVideoSegment request) public object Get(GetDynamicHlsVideoSegment request)
{ {
if (string.Equals("baseline", request.PlaylistId, StringComparison.OrdinalIgnoreCase)) return GetDynamicSegment(request).Result;
{
return GetDynamicSegment(request, false).Result;
}
return GetDynamicSegment(request, true).Result;
} }
private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1); private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1);
private async Task<object> GetDynamicSegment(GetDynamicHlsVideoSegment request, bool isMain) private async Task<object> GetDynamicSegment(GetDynamicHlsVideoSegment request)
{ {
if ((request.StartTimeTicks ?? 0) > 0) if ((request.StartTimeTicks ?? 0) > 0)
{ {
@ -322,7 +306,9 @@ namespace MediaBrowser.Api.Playback.Hls
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
// Main stream // Main stream
var playlistUrl = "main.m3u8" + queryString; var playlistUrl = (state.RunTimeTicks ?? 0) > 0 ? "main.m3u8" : "live.m3u8";
playlistUrl += queryString;
AppendPlaylist(builder, playlistUrl, totalBitrate); AppendPlaylist(builder, playlistUrl, totalBitrate);
if (state.VideoRequest.VideoBitRate.HasValue) if (state.VideoRequest.VideoBitRate.HasValue)
@ -385,13 +371,6 @@ namespace MediaBrowser.Api.Playback.Hls
return result; return result;
} }
public object Get(GetBaselineHlsVideoStream request)
{
var result = GetPlaylistAsync(request, "baseline").Result;
return result;
}
private async Task<object> GetPlaylistAsync(VideoStreamRequest request, string name) private async Task<object> GetPlaylistAsync(VideoStreamRequest request, string name)
{ {
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
@ -506,14 +485,6 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{ {
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
var itsOffsetMs = hlsVideoRequest == null
? 0
: ((GetHlsVideoStream)state.VideoRequest).TimeStampOffsetMs;
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
var threads = GetNumberOfThreads(state, false); var threads = GetNumberOfThreads(state, false);
var inputModifier = GetInputModifier(state); var inputModifier = GetInputModifier(state);
@ -521,8 +492,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If isEncoding is true we're actually starting ffmpeg // If isEncoding is true we're actually starting ffmpeg
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0"; var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -flags -global_header {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"", var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
itsOffset,
inputModifier, inputModifier,
GetInputArgument(state), GetInputArgument(state),
threads, threads,

View File

@ -32,6 +32,12 @@ namespace MediaBrowser.Api.Playback.Hls
public int TimeStampOffsetMs { get; set; } public int TimeStampOffsetMs { get; set; }
} }
[Route("/Videos/{Id}/live.m3u8", "GET")]
[Api(Description = "Gets a video stream using HTTP live streaming.")]
public class GetLiveHlsStream : VideoStreamRequest
{
}
/// <summary> /// <summary>
/// Class GetHlsVideoSegment /// Class GetHlsVideoSegment
/// </summary> /// </summary>
@ -105,7 +111,12 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
public object Get(GetHlsVideoStream request) public object Get(GetHlsVideoStream request)
{ {
return ProcessRequest(request); return ProcessRequest(request, false);
}
public object Get(GetLiveHlsStream request)
{
return ProcessRequest(request, true);
} }
/// <summary> /// <summary>

View File

@ -192,14 +192,14 @@ namespace MediaBrowser.Api
{ {
result.Series = season.Series.Name; result.Series = season.Series.Name;
result.EpisodeCount = season.GetRecursiveChildren(i => i is Episode).Count; result.EpisodeCount = season.GetRecursiveChildren().Count(i => i is Episode);
} }
var series = item as Series; var series = item as Series;
if (series != null) if (series != null)
{ {
result.EpisodeCount = series.GetRecursiveChildren(i => i is Episode).Count; result.EpisodeCount = series.GetRecursiveChildren().Count(i => i is Episode);
} }
var album = item as MusicAlbum; var album = item as MusicAlbum;

View File

@ -78,8 +78,8 @@ namespace MediaBrowser.Api
var fields = request.GetItemFields().ToList(); var fields = request.GetItemFields().ToList();
var inputItems = user == null var inputItems = user == null
? libraryManager.RootFolder.GetRecursiveChildren(i => i.Id != item.Id) ? libraryManager.RootFolder.GetRecursiveChildren().Where(i => i.Id != item.Id)
: user.RootFolder.GetRecursiveChildren(user, i => i.Id != item.Id); : user.RootFolder.GetRecursiveChildren(user).Where(i => i.Id != item.Id);
var items = GetSimilaritems(item, inputItems.Where(includeInSearch), getSimilarityScore) var items = GetSimilaritems(item, inputItems.Where(includeInSearch), getSimilarityScore)
.ToList(); .ToList();

View File

@ -541,7 +541,8 @@ namespace MediaBrowser.Api.UserLibrary
if (series != null) if (series != null)
{ {
var dtos = series var dtos = series
.GetRecursiveChildren(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0) .GetRecursiveChildren()
.Where(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0)
.OrderBy(i => .OrderBy(i =>
{ {
if (i.PremiereDate.HasValue) if (i.PremiereDate.HasValue)

View File

@ -856,19 +856,6 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IEnumerable{BaseItem}.</returns> /// <returns>IEnumerable{BaseItem}.</returns>
/// <exception cref="System.ArgumentNullException"></exception> /// <exception cref="System.ArgumentNullException"></exception>
public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true) public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
{
return GetRecursiveChildren(user, null, includeLinkedChildren);
}
/// <summary>
/// Gets the recursive children.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="filter">The filter.</param>
/// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
/// <returns>IList{BaseItem}.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
public IList<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter, bool includeLinkedChildren = true)
{ {
if (user == null) if (user == null)
{ {
@ -877,7 +864,7 @@ namespace MediaBrowser.Controller.Entities
var list = new List<BaseItem>(); var list = new List<BaseItem>();
var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, filter); var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, null);
return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list;
} }
@ -887,20 +874,10 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
/// <returns>IList{BaseItem}.</returns> /// <returns>IList{BaseItem}.</returns>
public IList<BaseItem> GetRecursiveChildren() public IList<BaseItem> GetRecursiveChildren()
{
return GetRecursiveChildren(i => true);
}
/// <summary>
/// Gets the recursive children.
/// </summary>
/// <param name="filter">The filter.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter)
{ {
var list = new List<BaseItem>(); var list = new List<BaseItem>();
AddChildrenToList(list, true, filter); AddChildrenToList(list, true, null);
return list; return list;
} }

View File

@ -18,11 +18,17 @@ namespace MediaBrowser.Controller.Entities
switch (ViewType) switch (ViewType)
{ {
case CollectionType.Games: case CollectionType.Games:
return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)).OfType<GameSystem>(); return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
.OfType<GameSystem>();
case CollectionType.BoxSets: case CollectionType.BoxSets:
return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)).OfType<BoxSet>(); return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
.OfType<BoxSet>();
case CollectionType.TvShows: case CollectionType.TvShows:
return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)).OfType<Series>(); return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
.OfType<Series>();
case CollectionType.Trailers:
return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren))
.OfType<Trailer>();
default: default:
return mediaFolders.SelectMany(i => i.GetChildren(user, includeLinkedChildren)); return mediaFolders.SelectMany(i => i.GetChildren(user, includeLinkedChildren));
} }

View File

@ -217,6 +217,7 @@ namespace MediaBrowser.Model.ApiClient
/// Gets the additional parts. /// Gets the additional parts.
/// </summary> /// </summary>
/// <param name="itemId">The item identifier.</param> /// <param name="itemId">The item identifier.</param>
/// <param name="userId">The user identifier.</param>
/// <returns>Task{BaseItemDto[]}.</returns> /// <returns>Task{BaseItemDto[]}.</returns>
Task<ItemsResult> GetAdditionalParts(string itemId, string userId); Task<ItemsResult> GetAdditionalParts(string itemId, string userId);
@ -241,6 +242,12 @@ namespace MediaBrowser.Model.ApiClient
/// <returns>Task{SessionInfoDto[]}.</returns> /// <returns>Task{SessionInfoDto[]}.</returns>
Task<SessionInfoDto[]> GetClientSessionsAsync(SessionQuery query); Task<SessionInfoDto[]> GetClientSessionsAsync(SessionQuery query);
/// <summary>
/// Gets the client session asynchronous.
/// </summary>
/// <returns>Task{SessionInfoDto}.</returns>
Task<SessionInfoDto> GetCurrentSessionAsync();
/// <summary> /// <summary>
/// Gets the item counts async. /// Gets the item counts async.
/// </summary> /// </summary>

View File

@ -93,8 +93,6 @@ namespace MediaBrowser.Model.Configuration
BlockUnratedItems = new UnratedItem[] { }; BlockUnratedItems = new UnratedItem[] { };
ExcludeFoldersFromGrouping = new string[] { }; ExcludeFoldersFromGrouping = new string[] { };
DisplayCollectionsView = true;
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.Serialization; using System.Runtime.Serialization;
@ -17,6 +16,12 @@ namespace MediaBrowser.Model.Dto
/// <value>The name.</value> /// <value>The name.</value>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the primary image tag. /// Gets or sets the primary image tag.
/// </summary> /// </summary>

View File

@ -328,7 +328,8 @@ namespace MediaBrowser.Server.Implementations.Dto
if (!string.IsNullOrEmpty(item.Album)) if (!string.IsNullOrEmpty(item.Album))
{ {
var parentAlbum = _libraryManager.RootFolder var parentAlbum = _libraryManager.RootFolder
.GetRecursiveChildren(i => i is MusicAlbum) .GetRecursiveChildren()
.Where(i => i is MusicAlbum)
.FirstOrDefault(i => string.Equals(i.Name, item.Album, StringComparison.OrdinalIgnoreCase)); .FirstOrDefault(i => string.Equals(i.Name, item.Album, StringComparison.OrdinalIgnoreCase));
if (parentAlbum != null) if (parentAlbum != null)
@ -539,6 +540,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (dictionary.TryGetValue(studio, out entity)) if (dictionary.TryGetValue(studio, out entity))
{ {
studioDto.Id = entity.Id.ToString("N");
studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
} }
@ -1248,7 +1250,8 @@ namespace MediaBrowser.Server.Implementations.Dto
} }
else else
{ {
children = folder.GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual); children = folder.GetRecursiveChildren(user)
.Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual);
} }
// Loop through each recursive child // Loop through each recursive child

View File

@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Library
{ {
var user = _userManager.GetUserById(new Guid(query.UserId)); var user = _userManager.GetUserById(new Guid(query.UserId));
inputItems = user.RootFolder.GetRecursiveChildren(user, null); inputItems = user.RootFolder.GetRecursiveChildren(user, true);
} }

View File

@ -15,6 +15,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Library namespace MediaBrowser.Server.Implementations.Library
{ {
@ -84,7 +85,7 @@ namespace MediaBrowser.Server.Implementations.Library
if (user.Configuration.DisplayCollectionsView || if (user.Configuration.DisplayCollectionsView ||
recursiveChildren.OfType<BoxSet>().Any()) recursiveChildren.OfType<BoxSet>().Any())
{ {
list.Add(await GetUserView(CollectionType.BoxSets, user, "zzz_" + CollectionType.BoxSets, cancellationToken).ConfigureAwait(false)); list.Add(await GetUserView(CollectionType.BoxSets, user, CollectionType.BoxSets, cancellationToken).ConfigureAwait(false));
} }
if (query.IncludeExternalContent) if (query.IncludeExternalContent)
@ -114,7 +115,7 @@ namespace MediaBrowser.Server.Implementations.Library
} }
} }
return list.OrderBy(i => i.SortName); return _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).Cast<Folder>();
} }
private Task<UserView> GetUserView(string type, User user, string sortName, CancellationToken cancellationToken) private Task<UserView> GetUserView(string type, User user, string sortName, CancellationToken cancellationToken)