diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 80ebbb719..e9f8f81f3 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -829,18 +829,7 @@ namespace Emby.Drawing // Run the enhancers sequentially in order of priority foreach (var enhancer in imageEnhancers) { - var typeName = enhancer.GetType().Name; - - try - { - await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("{0} failed enhancing {1}", ex, typeName, item.Name); - - throw; - } + await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false); // Feed the output into the next enhancer as input inputPath = outputPath; diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 7c5f7cde4..c6e45c61a 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Session; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -15,6 +16,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Model.Dto; namespace MediaBrowser.Api { @@ -43,7 +45,13 @@ namespace MediaBrowser.Api private readonly IFileSystem _fileSystem; private readonly IMediaSourceManager _mediaSourceManager; - public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1); + /// + /// The active transcoding jobs + /// + private readonly List _activeTranscodingJobs = new List(); + + private readonly Dictionary _transcodingLocks = + new Dictionary(); /// /// Initializes a new instance of the class. @@ -66,6 +74,21 @@ namespace MediaBrowser.Api _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; } + public SemaphoreSlim GetTranscodingLock(string outputPath) + { + lock (_transcodingLocks) + { + SemaphoreSlim result; + if (!_transcodingLocks.TryGetValue(outputPath, out result)) + { + result = new SemaphoreSlim(1, 1); + _transcodingLocks[outputPath] = result; + } + + return result; + } + } + private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) { if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) @@ -147,11 +170,6 @@ namespace MediaBrowser.Api } } - /// - /// The active transcoding jobs - /// - private readonly List _activeTranscodingJobs = new List(); - /// /// Called when [transcode beginning]. /// @@ -187,7 +205,8 @@ namespace MediaBrowser.Api CancellationTokenSource = cancellationTokenSource, Id = transcodingJobId, PlaySessionId = playSessionId, - LiveStreamId = liveStreamId + LiveStreamId = liveStreamId, + MediaSource = state.MediaSource }; _activeTranscodingJobs.Add(job); @@ -256,6 +275,11 @@ namespace MediaBrowser.Api } } + lock (_transcodingLocks) + { + _transcodingLocks.Remove(path); + } + if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) { _sessionManager.ClearTranscodingInfo(state.Request.DeviceId); @@ -281,6 +305,14 @@ namespace MediaBrowser.Api } } + public TranscodingJob GetTranscodingJob(string playSessionId) + { + lock (_activeTranscodingJobs) + { + return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase)); + } + } + /// /// Called when [transcode begin request]. /// @@ -487,6 +519,11 @@ namespace MediaBrowser.Api } } + lock (_transcodingLocks) + { + _transcodingLocks.Remove(job.Path); + } + lock (job.ProcessLock) { if (job.TranscodingThrottler != null) @@ -530,7 +567,7 @@ namespace MediaBrowser.Api { try { - await _mediaSourceManager.CloseLiveStream(job.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false); } catch (Exception ex) { @@ -656,6 +693,7 @@ namespace MediaBrowser.Api /// Gets or sets the path. /// /// The path. + public MediaSourceInfo MediaSource { get; set; } public string Path { get; set; } /// /// Gets or sets the type. @@ -751,12 +789,12 @@ namespace MediaBrowser.Api { if (KillTimer == null) { - Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + //Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); KillTimer = new Timer(callback, this, intervalMs, Timeout.Infinite); } else { - Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); KillTimer.Change(intervalMs, Timeout.Infinite); } } @@ -775,7 +813,7 @@ namespace MediaBrowser.Api { var intervalMs = PingTimeout; - Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); KillTimer.Change(intervalMs, Timeout.Infinite); } } diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs index 4e7b1a7d5..b2c7e5532 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs @@ -7,6 +7,8 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.IO; namespace MediaBrowser.Api.Dlna { @@ -109,13 +111,15 @@ namespace MediaBrowser.Api.Dlna private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar; private const string XMLContentType = "text/xml; charset=UTF-8"; + private readonly IMemoryStreamProvider _memoryStreamProvider; - public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar) + public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar, IMemoryStreamProvider memoryStreamProvider) { _dlnaManager = dlnaManager; _contentDirectory = contentDirectory; _connectionManager = connectionManager; _mediaReceiverRegistrar = mediaReceiverRegistrar; + _memoryStreamProvider = memoryStreamProvider; } public object Get(GetDescriptionXml request) @@ -201,7 +205,7 @@ namespace MediaBrowser.Api.Dlna { using (var response = _dlnaManager.GetIcon(request.Filename)) { - using (var ms = new MemoryStream()) + using (var ms = _memoryStreamProvider.CreateNew()) { response.Stream.CopyTo(ms); diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index b3b75359a..57aa5575f 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Querying; using ServiceStack; using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -104,7 +105,13 @@ namespace MediaBrowser.Api MediaTypes = request.GetMediaTypes(), IncludeItemTypes = request.GetIncludeItemTypes(), Recursive = true, - EnableTotalRecordCount = false + EnableTotalRecordCount = false, + DtoOptions = new Controller.Dto.DtoOptions + { + Fields = new List { ItemFields.Genres, ItemFields.Tags }, + EnableImages = false, + EnableUserData = false + } }; return query; diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index 0953b95e6..efa69d333 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -200,6 +200,8 @@ namespace MediaBrowser.Api (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); + var dtoOptions = GetDtoOptions(request); + var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { Limit = request.Limit, @@ -207,12 +209,11 @@ namespace MediaBrowser.Api { typeof(Game).Name }, - SimilarTo = item + SimilarTo = item, + DtoOptions = dtoOptions }).ToList(); - var dtoOptions = GetDtoOptions(request); - var result = new QueryResult { Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(), diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index b21e54495..25d7639c6 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -273,7 +273,8 @@ namespace MediaBrowser.Api.Images { var result = await _httpClient.GetResponse(new HttpRequestOptions { - Url = url + Url = url, + BufferContent = false }).ConfigureAwait(false); diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index cda7ce0c6..687a21a46 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -243,11 +243,7 @@ namespace MediaBrowser.Api hasBudget.Revenue = request.Revenue; } - var hasOriginalTitle = item as IHasOriginalTitle; - if (hasOriginalTitle != null) - { - hasOriginalTitle.OriginalTitle = hasOriginalTitle.OriginalTitle; - } + item.OriginalTitle = string.IsNullOrWhiteSpace(request.OriginalTitle) ? null : request.OriginalTitle; var hasCriticRating = item as IHasCriticRating; if (hasCriticRating != null) @@ -278,10 +274,9 @@ namespace MediaBrowser.Api item.Tags = request.Tags; - var hasTaglines = item as IHasTaglines; - if (hasTaglines != null) + if (request.Taglines != null) { - hasTaglines.Taglines = request.Taglines; + item.Tagline = request.Taglines.FirstOrDefault(); } var hasShortOverview = item as IHasShortOverview; @@ -308,8 +303,6 @@ namespace MediaBrowser.Api item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating; item.CustomRating = request.CustomRating; - SetProductionLocations(item, request); - item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode; item.PreferredMetadataLanguage = request.PreferredMetadataLanguage; @@ -417,23 +410,5 @@ namespace MediaBrowser.Api series.AirTime = request.AirTime; } } - - private void SetProductionLocations(BaseItem item, BaseItemDto request) - { - var hasProductionLocations = item as IHasProductionLocations; - - if (hasProductionLocations != null) - { - hasProductionLocations.ProductionLocations = request.ProductionLocations; - } - - var person = item as Person; - if (person != null) - { - person.PlaceOfBirth = request.ProductionLocations == null - ? null - : request.ProductionLocations.FirstOrDefault(); - } - } } } diff --git a/MediaBrowser.Api/Library/ChapterService.cs b/MediaBrowser.Api/Library/ChapterService.cs deleted file mode 100644 index 6b8dd18f1..000000000 --- a/MediaBrowser.Api/Library/ChapterService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MediaBrowser.Controller.Chapters; -using MediaBrowser.Controller.Net; -using ServiceStack; -using System.Linq; - -namespace MediaBrowser.Api.Library -{ - [Route("/Providers/Chapters", "GET")] - public class GetChapterProviders : IReturnVoid - { - } - - [Authenticated] - public class ChapterService : BaseApiService - { - private readonly IChapterManager _chapterManager; - - public ChapterService(IChapterManager chapterManager) - { - _chapterManager = chapterManager; - } - - public object Get(GetChapterProviders request) - { - var result = _chapterManager.GetProviders().ToList(); - - return ToOptimizedResult(result); - } - } -} diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index c6b637f01..e0dfcb9ad 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -835,14 +835,14 @@ namespace MediaBrowser.Api.Library : (Folder)_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null) + while (item.ThemeSongIds.Count == 0 && request.InheritFromParent && item.GetParent() != null) { item = item.GetParent(); } var dtoOptions = GetDtoOptions(request); - var dtos = GetThemeSongIds(item).Select(_libraryManager.GetItemById) + var dtos = item.ThemeSongIds.Select(_libraryManager.GetItemById) .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); @@ -879,14 +879,14 @@ namespace MediaBrowser.Api.Library : (Folder)_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null) + while (item.ThemeVideoIds.Count == 0 && request.InheritFromParent && item.GetParent() != null) { item = item.GetParent(); } var dtoOptions = GetDtoOptions(request); - var dtos = GetThemeVideoIds(item).Select(_libraryManager.GetItemById) + var dtos = item.ThemeVideoIds.Select(_libraryManager.GetItemById) .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); @@ -901,30 +901,6 @@ namespace MediaBrowser.Api.Library }; } - private List GetThemeVideoIds(BaseItem item) - { - var i = item as IHasThemeMedia; - - if (i != null) - { - return i.ThemeVideoIds; - } - - return new List(); - } - - private List GetThemeSongIds(BaseItem item) - { - var i = item as IHasThemeMedia; - - if (i != null) - { - return i.ThemeSongIds; - } - - return new List(); - } - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); public object Get(GetYearIndex request) diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 72966a7cd..e872061a7 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -112,6 +112,8 @@ namespace MediaBrowser.Api.Library /// The name. public string Path { get; set; } + public MediaPathInfo PathInfo { get; set; } + /// /// Gets or sets a value indicating whether [refresh library]. /// @@ -119,6 +121,18 @@ namespace MediaBrowser.Api.Library public bool RefreshLibrary { get; set; } } + [Route("/Library/VirtualFolders/Paths/Update", "POST")] + public class UpdateMediaPath : IReturnVoid + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + public MediaPathInfo PathInfo { get; set; } + } + [Route("/Library/VirtualFolders/Paths", "DELETE")] public class RemoveMediaPath : IReturnVoid { @@ -212,7 +226,12 @@ namespace MediaBrowser.Api.Library { var libraryOptions = request.LibraryOptions ?? new LibraryOptions(); - _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, libraryOptions, request.RefreshLibrary); + if (request.Paths != null && request.Paths.Length > 0) + { + libraryOptions.PathInfos = request.Paths.Select(i => new MediaPathInfo { Path = i }).ToArray(); + } + + _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, libraryOptions, request.RefreshLibrary); } /// @@ -308,7 +327,16 @@ namespace MediaBrowser.Api.Library try { - _libraryManager.AddMediaPath(request.Name, request.Path); + var mediaPath = request.PathInfo; + + if (mediaPath == null) + { + mediaPath = new MediaPathInfo + { + Path = request.Path + }; + } + _libraryManager.AddMediaPath(request.Name, mediaPath); } finally { @@ -332,6 +360,20 @@ namespace MediaBrowser.Api.Library } } + /// + /// Posts the specified request. + /// + /// The request. + public void Post(UpdateMediaPath request) + { + if (string.IsNullOrWhiteSpace(request.Name)) + { + throw new ArgumentNullException("request"); + } + + _libraryManager.UpdateMediaPath(request.Name, request.PathInfo); + } + /// /// Deletes the specified request. /// diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 3ad0ec1ba..4217cd6ab 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -12,9 +12,14 @@ using ServiceStack; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using CommonIO; +using MediaBrowser.Api.Playback.Progressive; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Server.Implementations.LiveTv.EmbyTV; namespace MediaBrowser.Api.LiveTv { @@ -44,6 +49,21 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? StartIndex { get; set; } + [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsMovie { get; set; } + + [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSeries { get; set; } + + [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsNews { get; set; } + + [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsKids { get; set; } + + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSports { get; set; } + /// /// The maximum number of items to return /// @@ -85,6 +105,26 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableUserData { get; set; } + public string SortBy { get; set; } + + public SortOrder? SortOrder { get; set; } + + /// + /// Gets the order by. + /// + /// IEnumerable{ItemSortBy}. + public string[] GetOrderBy() + { + var val = SortBy; + + if (string.IsNullOrEmpty(val)) + { + return new string[] { }; + } + + return val.Split(','); + } + public GetChannels() { AddCurrentProgram = true; @@ -159,6 +199,7 @@ namespace MediaBrowser.Api.LiveTv public bool? IsSeries { get; set; } public bool? IsKids { get; set; } public bool? IsSports { get; set; } + public bool? IsNews { get; set; } public GetRecordings() { @@ -275,6 +316,8 @@ namespace MediaBrowser.Api.LiveTv public string SeriesTimerId { get; set; } public bool? IsActive { get; set; } + + public bool? IsScheduled { get; set; } } [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")] @@ -305,6 +348,12 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsMovie { get; set; } + [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSeries { get; set; } + + [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsNews { get; set; } + [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsKids { get; set; } @@ -340,6 +389,8 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableUserData { get; set; } + public string SeriesTimerId { get; set; } + /// /// Fields to return within the items, in addition to basic information /// @@ -376,15 +427,21 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? HasAired { get; set; } - [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSports { get; set; } + [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSeries { get; set; } - [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsMovie { get; set; } - [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsNews { get; set; } + + [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsKids { get; set; } + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSports { get; set; } + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableImages { get; set; } @@ -613,16 +670,30 @@ namespace MediaBrowser.Api.LiveTv } + [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")] + public class GetLiveStreamFile + { + public string Id { get; set; } + public string Container { get; set; } + } + + [Route("/LiveTv/LiveRecordings/{Id}/stream", "GET", Summary = "Gets a live tv channel")] + public class GetLiveRecordingFile + { + public string Id { get; set; } + } + public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; private readonly IUserManager _userManager; - private readonly IConfigurationManager _config; + private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; + private readonly IFileSystem _fileSystem; - public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService) + public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -630,6 +701,41 @@ namespace MediaBrowser.Api.LiveTv _httpClient = httpClient; _libraryManager = libraryManager; _dtoService = dtoService; + _fileSystem = fileSystem; + } + + public async Task Get(GetLiveRecordingFile request) + { + var path = EmbyTV.Current.GetActiveRecordingPath(request.Id); + + if (path == null) + { + throw new FileNotFoundException(); + } + + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); + + var streamSource = new ProgressiveFileCopier(_fileSystem, path, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); + } + + public async Task Get(GetLiveStreamFile request) + { + var directStreamProvider = (await EmbyTV.Current.GetLiveStream(request.Id).ConfigureAwait(false)) as IDirectStreamProvider; + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); + + var streamSource = new ProgressiveFileCopier(directStreamProvider, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); } public object Get(GetDefaultListingProvider request) @@ -702,7 +808,8 @@ namespace MediaBrowser.Api.LiveTv var response = await _httpClient.Get(new HttpRequestOptions { - Url = "https://json.schedulesdirect.org/20141201/available/countries" + Url = "https://json.schedulesdirect.org/20141201/available/countries", + BufferContent = false }).ConfigureAwait(false); @@ -786,6 +893,13 @@ namespace MediaBrowser.Api.LiveTv IsLiked = request.IsLiked, IsDisliked = request.IsDisliked, EnableFavoriteSorting = request.EnableFavoriteSorting, + IsMovie = request.IsMovie, + IsSeries = request.IsSeries, + IsNews = request.IsNews, + IsKids = request.IsKids, + IsSports = request.IsSports, + SortBy = request.GetOrderBy(), + SortOrder = request.SortOrder ?? SortOrder.Ascending, AddCurrentProgram = request.AddCurrentProgram }, CancellationToken.None).ConfigureAwait(false); @@ -868,9 +982,12 @@ namespace MediaBrowser.Api.LiveTv query.Limit = request.Limit; query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); query.SortOrder = request.SortOrder; + query.IsNews = request.IsNews; query.IsMovie = request.IsMovie; + query.IsSeries = request.IsSeries; query.IsKids = request.IsKids; query.IsSports = request.IsSports; + query.SeriesTimerId = request.SeriesTimerId; query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var result = await _liveTvManager.GetPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false); @@ -886,8 +1003,10 @@ namespace MediaBrowser.Api.LiveTv IsAiring = request.IsAiring, Limit = request.Limit, HasAired = request.HasAired, + IsSeries = request.IsSeries, IsMovie = request.IsMovie, IsKids = request.IsKids, + IsNews = request.IsNews, IsSports = request.IsSports, EnableTotalRecordCount = request.EnableTotalRecordCount }; @@ -919,6 +1038,7 @@ namespace MediaBrowser.Api.LiveTv IsInProgress = request.IsInProgress, EnableTotalRecordCount = request.EnableTotalRecordCount, IsMovie = request.IsMovie, + IsNews = request.IsNews, IsSeries = request.IsSeries, IsKids = request.IsKids, IsSports = request.IsSports @@ -975,7 +1095,8 @@ namespace MediaBrowser.Api.LiveTv { ChannelId = request.ChannelId, SeriesTimerId = request.SeriesTimerId, - IsActive = request.IsActive + IsActive = request.IsActive, + IsScheduled = request.IsScheduled }, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 77949d179..d7091df56 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -1,5 +1,5 @@  - + Debug @@ -11,9 +11,10 @@ MediaBrowser.Api 512 ..\ - v4.5 + v4.5.1 + true @@ -79,7 +80,6 @@ - @@ -198,6 +198,10 @@ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model + + {2e781478-814d-4a48-9d80-bff206441a65} + MediaBrowser.Server.Implementations + diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index f153a0475..559bca755 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -156,18 +156,19 @@ namespace MediaBrowser.Api.Movies itemTypes.Add(typeof(LiveTvProgram).Name); } + var dtoOptions = GetDtoOptions(request); + var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { Limit = request.Limit, IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, SimilarTo = item, - EnableGroupByMetadataKey = true + EnableGroupByMetadataKey = true, + DtoOptions = dtoOptions }).ToList(); - var dtoOptions = GetDtoOptions(request); - var result = new QueryResult { Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(), @@ -198,7 +199,8 @@ namespace MediaBrowser.Api.Movies Limit = 7, ParentId = parentIdGuid, Recursive = true, - IsPlayed = true + IsPlayed = true, + DtoOptions = dtoOptions }; var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList(); @@ -221,7 +223,8 @@ namespace MediaBrowser.Api.Movies ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(), EnableGroupByMetadataKey = true, ParentId = parentIdGuid, - Recursive = true + Recursive = true, + DtoOptions = dtoOptions }).ToList(); @@ -302,7 +305,8 @@ namespace MediaBrowser.Api.Movies PersonTypes = new[] { PersonType.Director }, IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, - EnableGroupByMetadataKey = true + EnableGroupByMetadataKey = true, + DtoOptions = dtoOptions }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) .Take(itemLimit) @@ -339,7 +343,8 @@ namespace MediaBrowser.Api.Movies Limit = itemLimit + 2, IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, - EnableGroupByMetadataKey = true + EnableGroupByMetadataKey = true, + DtoOptions = dtoOptions }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) .Take(itemLimit) @@ -375,7 +380,8 @@ namespace MediaBrowser.Api.Movies IncludeItemTypes = itemTypes.ToArray(), IsMovie = true, SimilarTo = item, - EnableGroupByMetadataKey = true + EnableGroupByMetadataKey = true, + DtoOptions = dtoOptions }).ToList(); diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 6d8378aae..dc7c4c6c1 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -46,7 +46,7 @@ namespace MediaBrowser.Api /// /// The name. [ApiMember(Name = "PackageType", Description = "Optional package type filter (System/UserInstalled)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public PackageType? PackageType { get; set; } + public string PackageType { get; set; } [ApiMember(Name = "TargetSystems", Description = "Optional. Filter by target system type. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET", AllowMultiple = true)] public string TargetSystems { get; set; } @@ -72,7 +72,7 @@ namespace MediaBrowser.Api /// /// The name. [ApiMember(Name = "PackageType", Description = "Package type filter (System/UserInstalled)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public PackageType PackageType { get; set; } + public string PackageType { get; set; } } /// @@ -149,12 +149,12 @@ namespace MediaBrowser.Api { var result = new List(); - if (request.PackageType == PackageType.UserInstalled || request.PackageType == PackageType.All) + if (string.Equals(request.PackageType, "UserInstalled", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase)) { result.AddRange(_installationManager.GetAvailablePluginUpdates(_appHost.ApplicationVersion, false, CancellationToken.None).Result.ToList()); } - else if (request.PackageType == PackageType.System || request.PackageType == PackageType.All) + else if (string.Equals(request.PackageType, "System", StringComparison.OrdinalIgnoreCase) || string.Equals(request.PackageType, "All", StringComparison.OrdinalIgnoreCase)) { var updateCheckResult = _appHost.CheckForApplicationUpdate(CancellationToken.None, new Progress()).Result; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 0b0b23e12..0e91c0b9e 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -367,6 +367,8 @@ namespace MediaBrowser.Api.Playback { param += " -crf 23"; } + + param += " -tune zerolatency"; } else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) @@ -461,13 +463,15 @@ namespace MediaBrowser.Api.Playback var level = NormalizeTranscodingLevel(state.OutputVideoCodec, state.VideoRequest.Level); // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format + // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { switch (level) { case "30": - param += " -level 3"; + param += " -level 3.0"; break; case "31": param += " -level 3.1"; @@ -476,7 +480,7 @@ namespace MediaBrowser.Api.Playback param += " -level 3.2"; break; case "40": - param += " -level 4"; + param += " -level 4.0"; break; case "41": param += " -level 4.1"; @@ -485,7 +489,7 @@ namespace MediaBrowser.Api.Playback param += " -level 4.2"; break; case "50": - param += " -level 5"; + param += " -level 5.0"; break; case "51": param += " -level 5.1"; @@ -767,7 +771,20 @@ namespace MediaBrowser.Api.Playback if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) { outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"'); - outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase)); + + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase)); + } + else + { + outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase)); + } + } + + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0) + { + outputSizeParam = ",format=nv12|vaapi,hwupload"; } var videoSizeParam = string.Empty; @@ -802,10 +819,10 @@ namespace MediaBrowser.Api.Playback { if (state.PlayableStreamFileNames.Count > 0) { - return MediaEncoder.GetProbeSizeArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol); + return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol); } - return MediaEncoder.GetProbeSizeArgument(new[] { state.MediaPath }, state.InputProtocol); + return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(new[] { state.MediaPath }, state.InputProtocol); } /// @@ -1022,7 +1039,15 @@ namespace MediaBrowser.Api.Playback var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) { - arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg; + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; + var hwOutputFormat = "vaapi"; + + if (hasGraphicalSubs) + { + hwOutputFormat = "yuv420p"; + } + + arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg; } } @@ -1219,7 +1244,7 @@ namespace MediaBrowser.Api.Playback private void StartThrottler(StreamState state, TranscodingJob transcodingJob) { - if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EnableThrottling(state)) { transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager); state.TranscodingThrottler.Start(); @@ -1832,26 +1857,37 @@ namespace MediaBrowser.Api.Playback state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - var archivable = item as IArchivable; - state.IsInputArchive = archivable != null && archivable.IsArchive; - - MediaSourceInfo mediaSource; + MediaSourceInfo mediaSource = null; if (string.IsNullOrWhiteSpace(request.LiveStreamId)) { - var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList(); + TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? + ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) + : null; - mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); - - if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + if (currentJob != null) { - mediaSource = mediaSources.First(); + mediaSource = currentJob.MediaSource; + } + + if (mediaSource == null) + { + var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList(); + + mediaSource = string.IsNullOrEmpty(request.MediaSourceId) + ? mediaSources.First() + : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); + + if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + { + mediaSource = mediaSources.First(); + } } } else { - mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false); + var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false); + mediaSource = liveStreamInfo.Item1; + state.DirectStreamProvider = liveStreamInfo.Item2; } var videoRequest = request as VideoStreamRequest; @@ -2294,7 +2330,8 @@ namespace MediaBrowser.Api.Playback state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, - state.TargetVideoCodecTag); + state.TargetVideoCodecTag, + state.IsTargetAVC); if (mediaProfile != null) { @@ -2429,7 +2466,8 @@ namespace MediaBrowser.Api.Playback Url = "https://mb3admin.com/admin/service/transcoding/report", CancellationToken = CancellationToken.None, LogRequest = false, - LogErrors = false + LogErrors = false, + BufferContent = false }; options.RequestContent = JsonSerializer.SerializeToString(dict); options.RequestContentType = "application/json"; @@ -2512,7 +2550,8 @@ namespace MediaBrowser.Api.Playback state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, - state.TargetVideoCodecTag + state.TargetVideoCodecTag, + state.IsTargetAVC ).FirstOrDefault() ?? string.Empty; } @@ -2567,6 +2606,7 @@ namespace MediaBrowser.Api.Playback inputModifier += " " + GetFastSeekCommandLineParameter(state.Request); inputModifier = inputModifier.Trim(); + //inputModifier += " -fflags +genpts+ignidx+igndts"; if (state.VideoRequest != null && genPts) { inputModifier += " -fflags +genpts"; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index a0ac96b9d..319e4bbb6 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -87,7 +87,8 @@ namespace MediaBrowser.Api.Playback.Hls if (!FileSystem.FileExists(playlist)) { - await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlist); + await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (!FileSystem.FileExists(playlist)) @@ -104,13 +105,13 @@ namespace MediaBrowser.Api.Playback.Hls throw; } - var waitForSegments = state.SegmentLength >= 10 ? 2 : 3; + var waitForSegments = state.SegmentLength >= 10 ? 2 : (state.SegmentLength > 3 || !isLive ? 3 : 3); await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false); } } finally { - ApiEntryPoint.Instance.TranscodingStartLock.Release(); + transcodingLock.Release(); } } @@ -128,10 +129,9 @@ namespace MediaBrowser.Api.Playback.Hls var audioBitrate = state.OutputAudioBitrate ?? 0; var videoBitrate = state.OutputVideoBitrate ?? 0; - var appendBaselineStream = false; var baselineStreamBitrate = 64000; - var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); + var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); @@ -151,9 +151,10 @@ namespace MediaBrowser.Api.Playback.Hls { var text = reader.ReadToEnd(); + text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT"); + var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture); - // ffmpeg pads the reported length by a full second text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase); return text; @@ -161,7 +162,7 @@ namespace MediaBrowser.Api.Playback.Hls } } - private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate) + private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate) { var builder = new StringBuilder(); @@ -175,14 +176,6 @@ namespace MediaBrowser.Api.Playback.Hls var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8"); builder.AppendLine(playlistUrl); - // Low bitrate stream - if (includeBaselineStream) - { - builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + baselineStreamBitrate.ToString(UsCulture)); - playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "-low/stream.m3u8"); - builder.AppendLine(playlistUrl); - } - return builder.ToString(); } @@ -190,32 +183,41 @@ namespace MediaBrowser.Api.Playback.Hls { Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist); - while (true) + while (!cancellationToken.IsCancellationRequested) { - // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written - using (var fileStream = GetPlaylistFileStream(playlist)) + try { - 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 = GetPlaylistFileStream(playlist)) { - var count = 0; - - while (!reader.EndOfStream) + using (var reader = new StreamReader(fileStream)) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); + var count = 0; - if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) + while (!reader.EndOfStream) { - count++; - if (count >= segmentCount) + var line = await reader.ReadLineAsync().ConfigureAwait(false); + + if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) { - Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist); - return; + count++; + if (count >= segmentCount) + { + Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist); + return; + } } } + await Task.Delay(100, cancellationToken).ConfigureAwait(false); } - await Task.Delay(100, cancellationToken).ConfigureAwait(false); } } + catch (IOException) + { + // May get an error if the file is locked + } + + await Task.Delay(50, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 9cd55528d..51455c141 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -171,14 +171,15 @@ namespace MediaBrowser.Api.Playback.Hls return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } - await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlistPath); + await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); var released = false; try { if (FileSystem.FileExists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - ApiEntryPoint.Instance.TranscodingStartLock.Release(); + transcodingLock.Release(); released = true; return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } @@ -242,7 +243,7 @@ namespace MediaBrowser.Api.Playback.Hls { if (!released) { - ApiEntryPoint.Instance.TranscodingStartLock.Release(); + transcodingLock.Release(); } } @@ -906,6 +907,7 @@ namespace MediaBrowser.Api.Playback.Hls ).Trim(); } + // TODO: check libavformat version for 57 50.100 and use -hls_flags split_by_time return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"", inputModifier, GetInputArgument(state), diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 27deaf25e..976fed3f0 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -68,8 +68,6 @@ namespace MediaBrowser.Api.Playback.Hls [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] public class GetHlsVideoSegmentLegacy : VideoStreamRequest { - // TODO: Deprecate with new iOS app - public string PlaylistId { get; set; } /// @@ -113,7 +111,7 @@ namespace MediaBrowser.Api.Playback.Hls var file = request.SegmentId + Path.GetExtension(Request.PathInfo); file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file); - var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty); + var normalizedPlaylistId = request.PlaylistId; var playlistPath = Directory.EnumerateFiles(_config.ApplicationPaths.TranscodingTempPath, "*") .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 8a14948d2..c7258d72f 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -113,6 +113,8 @@ namespace MediaBrowser.Api.Playback.Hls args += GetGraphicalSubtitleParam(state, codec); } + args += " -flags -global_header"; + return args; } diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 656b2ee08..7fe7d5a21 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -107,7 +107,7 @@ namespace MediaBrowser.Api.Playback { var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); - var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false); + var result = await _mediaSourceManager.OpenLiveStream(request, true, CancellationToken.None).ConfigureAwait(false); var profile = request.DeviceProfile; if (profile == null) @@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback public void Post(CloseMediaSource request) { - var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None); + var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId); Task.WaitAll(task); } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index b8cb6b14f..809eabef8 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -17,6 +17,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using CommonIO; +using ServiceStack; namespace MediaBrowser.Api.Playback.Progressive { @@ -26,12 +27,10 @@ namespace MediaBrowser.Api.Playback.Progressive public abstract class BaseProgressiveStreamingService : BaseStreamingService { protected readonly IImageProcessor ImageProcessor; - protected readonly IHttpClient HttpClient; protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer) { ImageProcessor = imageProcessor; - HttpClient = httpClient; } /// @@ -122,6 +121,25 @@ namespace MediaBrowser.Api.Playback.Progressive var responseHeaders = new Dictionary(); + if (request.Static && state.DirectStreamProvider != null) + { + AddDlnaHeaders(state, responseHeaders, true); + + using (state) + { + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + // TODO: Don't hardcode this + outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts"); + + var streamSource = new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); + } + } + // Static remote stream if (request.Static && state.InputProtocol == MediaProtocol.Http) { @@ -129,8 +147,7 @@ namespace MediaBrowser.Api.Playback.Progressive using (state) { - return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource) - .ConfigureAwait(false); + return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); } } @@ -154,6 +171,19 @@ namespace MediaBrowser.Api.Playback.Progressive using (state) { + if (state.MediaSource.IsInfiniteStream) + { + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + outputHeaders["Content-Type"] = contentType; + + var streamSource = new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); + } + TimeSpan? cacheDuration = null; if (!string.IsNullOrEmpty(request.Tag)) @@ -345,7 +375,8 @@ namespace MediaBrowser.Api.Playback.Progressive return streamResult; } - await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); + await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { TranscodingJob job; @@ -376,7 +407,7 @@ namespace MediaBrowser.Api.Playback.Progressive } finally { - ApiEntryPoint.Instance.TranscodingStartLock.Release(); + transcodingLock.Release(); } } diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 0a9a44641..3477ad57b 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -7,6 +7,7 @@ using CommonIO; using MediaBrowser.Controller.Net; using System.Collections.Generic; using ServiceStack.Web; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Api.Playback.Progressive { @@ -23,6 +24,10 @@ namespace MediaBrowser.Api.Playback.Progressive private const int BufferSize = 81920; private long _bytesWritten = 0; + public long StartPosition { get; set; } + public bool AllowEndOfFile = true; + + private IDirectStreamProvider _directStreamProvider; public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { @@ -34,6 +39,15 @@ namespace MediaBrowser.Api.Playback.Progressive _cancellationToken = cancellationToken; } + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) + { + _directStreamProvider = directStreamProvider; + _outputHeaders = outputHeaders; + _job = job; + _logger = logger; + _cancellationToken = cancellationToken; + } + public IDictionary Options { get @@ -42,17 +56,33 @@ namespace MediaBrowser.Api.Playback.Progressive } } + private Stream GetInputStream() + { + return _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); + } + public async Task WriteToAsync(Stream outputStream) { try { + if (_directStreamProvider != null) + { + await _directStreamProvider.CopyToAsync(outputStream, _cancellationToken).ConfigureAwait(false); + return; + } + var eofCount = 0; - using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var inputStream = GetInputStream()) { - while (eofCount < 15) + if (StartPosition > 0) { - var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); + inputStream.Position = StartPosition; + } + + while (eofCount < 15 || !AllowEndOfFile) + { + var bytesRead = await CopyToAsyncInternal(inputStream, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 2e92c4a49..003599390 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -37,6 +37,7 @@ namespace MediaBrowser.Api.Playback /// /// The log file stream. public Stream LogFileStream { get; set; } + public IDirectStreamProvider DirectStreamProvider { get; set; } public string InputContainer { get; set; } @@ -62,7 +63,6 @@ namespace MediaBrowser.Api.Playback get { return Request is VideoStreamRequest; } } public bool IsInputVideo { get; set; } - public bool IsInputArchive { get; set; } public VideoType VideoType { get; set; } public IsoType? IsoType { get; set; } @@ -88,9 +88,17 @@ namespace MediaBrowser.Api.Playback return 10; } + if (!RunTimeTicks.HasValue) + { + return 3; + } return 6; } + if (!RunTimeTicks.HasValue) + { + return 3; + } return 3; } } @@ -217,7 +225,7 @@ namespace MediaBrowser.Api.Playback { try { - await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false); } catch (Exception ex) { @@ -508,5 +516,18 @@ namespace MediaBrowser.Api.Playback return false; } } + + public bool? IsTargetAVC + { + get + { + if (Request.Static) + { + return VideoStream == null ? null : VideoStream.IsAVC; + } + + return true; + } + } } } diff --git a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs index e4a560383..c9c63847c 100644 --- a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs +++ b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs @@ -517,10 +517,6 @@ namespace MediaBrowser.Api.Reports internalHeader = HeaderMetadata.Album; break; - case HeaderMetadata.Countries: - option.Column = (i, r) => this.GetListAsString(this.GetObject>(i, (x) => x.ProductionLocations)); - break; - case HeaderMetadata.Disc: option.Column = (i, r) => i.ParentIndexNumber; break; diff --git a/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs b/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs index 0da4857ac..52b095dee 100644 --- a/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs +++ b/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs @@ -36,7 +36,6 @@ namespace MediaBrowser.Api.Reports result = this.GetResultStudios(result, items, topItem); result = this.GetResultPersons(result, items, topItem); result = this.GetResultProductionYears(result, items, topItem); - result = this.GetResulProductionLocations(result, items, topItem); result = this.GetResultCommunityRatings(result, items, topItem); result = this.GetResultParentalRatings(result, items, topItem); @@ -100,30 +99,6 @@ namespace MediaBrowser.Api.Reports } } - /// Gets resul production locations. - /// The result. - /// The items. - /// The top item. - /// The resul production locations. - private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Countries), topItem, - items.OfType() - .Where(x => x.ProductionLocations != null) - .SelectMany(x => x.ProductionLocations) - .GroupBy(x => x) - .OrderByDescending(x => x.Count()) - .Take(topItem) - .Select(x => new ReportStatItem - { - Name = x.Key.ToString(), - Value = x.Count().ToString() - }) - ); - - return result; - } - /// Gets result community ratings. /// The result. /// The items. diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 1621c8056..ddcb6b7bf 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -81,7 +81,8 @@ namespace MediaBrowser.Api var query = new InternalItemsQuery(user) { IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(), - Recursive = true + Recursive = true, + DtoOptions = dtoOptions }; // ExcludeArtistIds diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 176b497d7..d5158677c 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -88,8 +88,6 @@ namespace MediaBrowser.Api var result = new StartupConfiguration { UICulture = _config.Configuration.UICulture, - EnableInternetProviders = _config.Configuration.EnableInternetProviders, - SaveLocalMeta = _config.Configuration.SaveLocalMeta, MetadataCountryCode = _config.Configuration.MetadataCountryCode, PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage }; @@ -116,15 +114,16 @@ namespace MediaBrowser.Api config.EnableLocalizedGuids = true; config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; - //config.EnableFolderView = true; + config.EnableFolderView = true; config.SchemaVersion = 109; + config.EnableSimpleArtistDetection = true; + config.SkipDeserializationForBasicTypes = true; + config.SkipDeserializationForPrograms = true; } public void Post(UpdateStartupConfiguration request) { _config.Configuration.UICulture = request.UICulture; - _config.Configuration.EnableInternetProviders = request.EnableInternetProviders; - _config.Configuration.SaveLocalMeta = request.SaveLocalMeta; _config.Configuration.MetadataCountryCode = request.MetadataCountryCode; _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage; _config.SaveConfiguration(); @@ -215,8 +214,6 @@ namespace MediaBrowser.Api public class StartupConfiguration { public string UICulture { get; set; } - public bool EnableInternetProviders { get; set; } - public bool SaveLocalMeta { get; set; } public string MetadataCountryCode { get; set; } public string PreferredMetadataLanguage { get; set; } public string LiveTvTunerType { get; set; } diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index fe13e8b21..b07a31a87 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Api.Subtitles { var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); - var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, false).ConfigureAwait(false); + var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false); var builder = new StringBuilder(); diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index a0d69317c..ef93d2fc9 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -302,6 +302,8 @@ namespace MediaBrowser.Api (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); + var dtoOptions = GetDtoOptions(request); + var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { Limit = request.Limit, @@ -309,12 +311,11 @@ namespace MediaBrowser.Api { typeof(Series).Name }, - SimilarTo = item + SimilarTo = item, + DtoOptions = dtoOptions }).ToList(); - var dtoOptions = GetDtoOptions(request); - var result = new QueryResult { Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(), @@ -333,6 +334,8 @@ namespace MediaBrowser.Api var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId); + var options = GetDtoOptions(request); + var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { IncludeItemTypes = new[] { typeof(Episode).Name }, @@ -342,12 +345,11 @@ namespace MediaBrowser.Api StartIndex = request.StartIndex, Limit = request.Limit, ParentId = parentIdGuid, - Recursive = true + Recursive = true, + DtoOptions = options }).ToList(); - var options = GetDtoOptions(request); - var returnItems = (await _dtoService.GetBaseItemDtos(itemsResult, options, user).ConfigureAwait(false)).ToArray(); var result = new ItemsResult diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 5381f9004..182a92fc8 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -205,6 +205,7 @@ namespace MediaBrowser.Api.UserLibrary private void SetItemCounts(BaseItemDto dto, ItemCounts counts) { dto.ChildCount = counts.ItemCount; + dto.ProgramCount = counts.ProgramCount; dto.SeriesCount = counts.SeriesCount; dto.EpisodeCount = counts.EpisodeCount; dto.MovieCount = counts.MovieCount; diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 681624ba2..04395da3c 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -99,8 +99,10 @@ namespace MediaBrowser.Api.UserLibrary private async Task GetItems(GetItems request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; - - var result = await GetQueryResult(request, user).ConfigureAwait(false); + + var dtoOptions = GetDtoOptions(request); + + var result = await GetQueryResult(request, dtoOptions, user).ConfigureAwait(false); if (result == null) { @@ -112,8 +114,6 @@ namespace MediaBrowser.Api.UserLibrary throw new InvalidOperationException("GetItemsToSerialize result.Items returned null"); } - var dtoOptions = GetDtoOptions(request); - var dtoList = await _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ConfigureAwait(false); if (dtoList == null) @@ -131,24 +131,31 @@ namespace MediaBrowser.Api.UserLibrary /// /// Gets the items to serialize. /// - /// The request. - /// The user. - /// IEnumerable{BaseItem}. - private async Task> GetQueryResult(GetItems request, User user) + private async Task> GetQueryResult(GetItems request, DtoOptions dtoOptions, User user) { var item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : user.RootFolder : + null : _libraryManager.GetItemById(request.ParentId); if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase)) { - //item = user == null ? _libraryManager.RootFolder : user.RootFolder; + if (item == null || user != null) + { + item = _libraryManager.RootFolder.Children.OfType().FirstOrDefault(i => string.Equals(i.GetType().Name, "PlaylistsFolder", StringComparison.OrdinalIgnoreCase)); + } } else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) { item = user == null ? _libraryManager.RootFolder : user.RootFolder; } + if (item == null) + { + item = string.IsNullOrEmpty(request.ParentId) ? + user == null ? _libraryManager.RootFolder : user.RootFolder : + _libraryManager.GetItemById(request.ParentId); + } + // Default list type = children var folder = item as Folder; @@ -159,14 +166,14 @@ namespace MediaBrowser.Api.UserLibrary if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null) { - return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); + return await folder.GetItems(GetItemsQuery(request, dtoOptions, user)).ConfigureAwait(false); } var userRoot = item as UserRootFolder; if (userRoot == null) { - return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); + return await folder.GetItems(GetItemsQuery(request, dtoOptions, user)).ConfigureAwait(false); } IEnumerable items = folder.GetChildren(user, true); @@ -180,7 +187,7 @@ namespace MediaBrowser.Api.UserLibrary }; } - private InternalItemsQuery GetItemsQuery(GetItems request, User user) + private InternalItemsQuery GetItemsQuery(GetItems request, DtoOptions dtoOptions, User user) { var query = new InternalItemsQuery(user) { @@ -241,7 +248,8 @@ namespace MediaBrowser.Api.UserLibrary AiredDuringSeason = request.AiredDuringSeason, AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater, EnableTotalRecordCount = request.EnableTotalRecordCount, - ExcludeItemIds = request.GetExcludeItemIds() + ExcludeItemIds = request.GetExcludeItemIds(), + DtoOptions = dtoOptions }; if (!string.IsNullOrWhiteSpace(request.Ids)) diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index baf5afc1b..4099c9c56 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -30,6 +30,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Common.IO; namespace MediaBrowser.Common.Implementations { @@ -192,6 +193,8 @@ namespace MediaBrowser.Common.Implementations get { return Environment.OSVersion.VersionString; } } + public IMemoryStreamProvider MemoryStreamProvider { get; set; } + /// /// Initializes a new instance of the class. /// @@ -231,6 +234,8 @@ namespace MediaBrowser.Common.Implementations JsonSerializer = CreateJsonSerializer(); + MemoryStreamProvider = new MemoryStreamProvider(); + OnLoggerLoaded(true); LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false); @@ -456,6 +461,7 @@ namespace MediaBrowser.Common.Implementations RegisterSingleInstance(JsonSerializer); RegisterSingleInstance(XmlSerializer); + RegisterSingleInstance(MemoryStreamProvider); RegisterSingleInstance(LogManager); RegisterSingleInstance(Logger); @@ -464,7 +470,7 @@ namespace MediaBrowser.Common.Implementations RegisterSingleInstance(FileSystemManager); - HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager); + HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamProvider); RegisterSingleInstance(HttpClient); NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager")); diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index 371757f6c..eec18e985 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -42,6 +42,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; + private readonly IMemoryStreamProvider _memoryStreamProvider; /// /// Initializes a new instance of the class. @@ -52,7 +53,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// appPaths /// or /// logger - public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) + public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider) { if (appPaths == null) { @@ -65,6 +66,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager _logger = logger; _fileSystem = fileSystem; + _memoryStreamProvider = memoryStreamProvider; _appPaths = appPaths; // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c @@ -269,6 +271,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager Url = url, ResourcePool = resourcePool, CancellationToken = cancellationToken, + BufferContent = resourcePool != null }); } @@ -293,12 +296,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// public async Task SendAsync(HttpRequestOptions options, string httpMethod) { - HttpResponseInfo response; - if (options.CacheMode == CacheMode.None) { - response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false); - return response; + return await SendAsyncInternal(options, httpMethod).ConfigureAwait(false); } var url = options.Url; @@ -306,7 +306,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash); - response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false); + var response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false); if (response != null) { return response; @@ -332,7 +332,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager { using (var stream = _fileSystem.GetFileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { - var memoryStream = new MemoryStream(); + var memoryStream = _memoryStreamProvider.CreateNew(); await stream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0; @@ -366,7 +366,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager using (var responseStream = response.Content) { - var memoryStream = new MemoryStream(); + var memoryStream = _memoryStreamProvider.CreateNew(); await responseStream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0; @@ -458,7 +458,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager using (var stream = httpResponse.GetResponseStream()) { - var memoryStream = new MemoryStream(); + var memoryStream = _memoryStreamProvider.CreateNew(); await stream.CopyToAsync(memoryStream).ConfigureAwait(false); @@ -553,7 +553,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager { Url = url, ResourcePool = resourcePool, - CancellationToken = cancellationToken + CancellationToken = cancellationToken, + BufferContent = resourcePool != null }, postData); } @@ -563,7 +564,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// /// The options. /// Task{System.String}. - /// progress public async Task GetTempFile(HttpRequestOptions options) { var response = await GetTempFileResponse(options).ConfigureAwait(false); diff --git a/MediaBrowser.Common.Implementations/IO/MemoryStreamProvider.cs b/MediaBrowser.Common.Implementations/IO/MemoryStreamProvider.cs new file mode 100644 index 000000000..364055283 --- /dev/null +++ b/MediaBrowser.Common.Implementations/IO/MemoryStreamProvider.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using Microsoft.IO; + +namespace MediaBrowser.Common.Implementations.IO +{ + public class MemoryStreamProvider : IMemoryStreamProvider + { + readonly RecyclableMemoryStreamManager _manager = new RecyclableMemoryStreamManager(); + + public MemoryStream CreateNew() + { + return _manager.GetStream(); + } + + public MemoryStream CreateNew(int capacity) + { + return _manager.GetStream("RecyclableMemoryStream", capacity); + } + + public MemoryStream CreateNew(byte[] buffer) + { + return _manager.GetStream("RecyclableMemoryStream", buffer, 0, buffer.Length); + } + } +} diff --git a/MediaBrowser.Common.Implementations/Logging/NLogger.cs b/MediaBrowser.Common.Implementations/Logging/NLogger.cs index 29b618890..97bc437a0 100644 --- a/MediaBrowser.Common.Implementations/Logging/NLogger.cs +++ b/MediaBrowser.Common.Implementations/Logging/NLogger.cs @@ -72,6 +72,11 @@ namespace MediaBrowser.Common.Implementations.Logging /// The param list. public void Debug(string message, params object[] paramList) { + if (_logManager.LogSeverity == LogSeverity.Info) + { + return; + } + _logger.Debug(message, paramList); } @@ -137,6 +142,11 @@ namespace MediaBrowser.Common.Implementations.Logging /// Content of the additional. public void LogMultiline(string message, LogSeverity severity, StringBuilder additionalContent) { + if (severity == LogSeverity.Debug && _logManager.LogSeverity == LogSeverity.Info) + { + return; + } + additionalContent.Insert(0, message + Environment.NewLine); const char tabChar = '\t'; diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index ced2dd5a3..f3444f01b 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -1,5 +1,5 @@  - + Debug @@ -14,6 +14,7 @@ 10.0.0 2.0 v4.5 + true @@ -23,7 +24,7 @@ DEBUG;TRACE prompt 4 - v4.5 + v4.5.1 none @@ -51,11 +52,15 @@ False ..\packages\CommonIO.1.0.0.9\lib\net45\CommonIO.dll + + ..\packages\Microsoft.IO.RecyclableMemoryStream.1.1.0.0\lib\net45\Microsoft.IO.RecyclableMemoryStream.dll + True + ..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll - ..\packages\NLog.4.3.6\lib\net45\NLog.dll + ..\packages\NLog.4.3.8\lib\net45\NLog.dll True @@ -65,8 +70,8 @@ False ..\ThirdParty\SharpCompress\SharpCompress.dll - - ..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll + + ..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll True @@ -75,6 +80,9 @@ + + ..\ThirdParty\fastjsonparser\System.Text.Json.dll + ..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll @@ -93,6 +101,7 @@ + diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index e6b6bc2e3..ced85780f 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -390,13 +390,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks try { - var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress); - if (options != null && options.MaxRuntimeMs.HasValue) { CurrentCancellationTokenSource.CancelAfter(options.MaxRuntimeMs.Value); } + var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress); + await localTask.ConfigureAwait(false); status = TaskCompletionStatus.Completed; diff --git a/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs b/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs index 10c0f8fc9..5d440609e 100644 --- a/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs +++ b/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs @@ -169,7 +169,8 @@ namespace MediaBrowser.Common.Implementations.Security var options = new HttpRequestOptions() { Url = AppstoreRegUrl, - CancellationToken = CancellationToken.None + CancellationToken = CancellationToken.None, + BufferContent = false }; options.RequestHeaders.Add("X-Emby-Token", _appHost.SystemId); options.RequestContent = parameters; @@ -269,7 +270,8 @@ namespace MediaBrowser.Common.Implementations.Security Url = MBValidateUrl, // Seeing block length errors - EnableHttpCompression = false + EnableHttpCompression = false, + BufferContent = false }; options.SetPostData(data); diff --git a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs index 84c08439e..371c2ea11 100644 --- a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs +++ b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs @@ -30,7 +30,8 @@ namespace MediaBrowser.Common.Implementations.Updates Url = url, EnableKeepAlive = false, CancellationToken = cancellationToken, - UserAgent = "Emby/3.0" + UserAgent = "Emby/3.0", + BufferContent = false }; if (cacheLength.Ticks > 0) @@ -105,7 +106,8 @@ namespace MediaBrowser.Common.Implementations.Updates Url = url, EnableKeepAlive = false, CancellationToken = cancellationToken, - UserAgent = "Emby/3.0" + UserAgent = "Emby/3.0", + BufferContent = false }; using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) diff --git a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs index 8c7646209..9674199fe 100644 --- a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs +++ b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs @@ -148,14 +148,10 @@ namespace MediaBrowser.Common.Implementations.Updates /// /// Gets all available packages. /// - /// The cancellation token. - /// if set to true [with registration]. - /// Type of the package. - /// The application version. /// Task{List{PackageInfo}}. public async Task> GetAvailablePackages(CancellationToken cancellationToken, bool withRegistration = true, - PackageType? packageType = null, + string packageType = null, Version applicationVersion = null) { var data = new Dictionary @@ -293,7 +289,7 @@ namespace MediaBrowser.Common.Implementations.Updates return packages; } - protected IEnumerable FilterPackages(List packages, PackageType? packageType, Version applicationVersion) + protected IEnumerable FilterPackages(List packages, string packageType, Version applicationVersion) { foreach (var package in packages) { @@ -301,9 +297,9 @@ namespace MediaBrowser.Common.Implementations.Updates .OrderByDescending(GetPackageVersion).ToList(); } - if (packageType.HasValue) + if (!string.IsNullOrWhiteSpace(packageType)) { - packages = packages.Where(p => p.type == packageType.Value).ToList(); + packages = packages.Where(p => string.Equals(p.type, packageType, StringComparison.OrdinalIgnoreCase)).ToList(); } // If an app version was supplied, filter the versions for each package to only include supported versions diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config index 594b4c7c5..40c727a06 100644 --- a/MediaBrowser.Common.Implementations/packages.config +++ b/MediaBrowser.Common.Implementations/packages.config @@ -1,8 +1,9 @@  + - + - + \ No newline at end of file diff --git a/MediaBrowser.Common/IO/IMemoryStreamProvider.cs b/MediaBrowser.Common/IO/IMemoryStreamProvider.cs new file mode 100644 index 000000000..1c0e98018 --- /dev/null +++ b/MediaBrowser.Common/IO/IMemoryStreamProvider.cs @@ -0,0 +1,11 @@ +using System.IO; + +namespace MediaBrowser.Common.IO +{ + public interface IMemoryStreamProvider + { + MemoryStream CreateNew(); + MemoryStream CreateNew(int capacity); + MemoryStream CreateNew(byte[] buffer); + } +} diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 17f211d84..a46aaf9f7 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -60,6 +60,7 @@ + diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 68853f05e..f7a202f97 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -51,7 +51,7 @@ namespace MediaBrowser.Common.Updates /// Task{List{PackageInfo}}. Task> GetAvailablePackages(CancellationToken cancellationToken, bool withRegistration = true, - PackageType? packageType = null, + string packageType = null, Version applicationVersion = null); /// diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 3c46247a7..9177e2d81 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -134,15 +134,5 @@ namespace MediaBrowser.Controller.Channels /// The cancellation token. /// BaseItemDto. Task GetChannelFolder(string userId, CancellationToken cancellationToken); - - /// - /// Downloads the channel item. - /// - /// The item. - /// The destination path. - /// The progress. - /// The cancellation token. - /// Task. - Task DownloadChannelItem(BaseItem item, string destinationPath, IProgress progress, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Chapters/ChapterResponse.cs b/MediaBrowser.Controller/Chapters/ChapterResponse.cs deleted file mode 100644 index 3c1b8ed07..000000000 --- a/MediaBrowser.Controller/Chapters/ChapterResponse.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Chapters -{ - public class ChapterResponse - { - /// - /// Gets or sets the chapters. - /// - /// The chapters. - public List Chapters { get; set; } - - public ChapterResponse() - { - Chapters = new List(); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs b/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs deleted file mode 100644 index 982dc35bb..000000000 --- a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Chapters -{ - public class ChapterSearchRequest : IHasProviderIds - { - public string Language { get; set; } - - public VideoContentType ContentType { get; set; } - - public string MediaPath { get; set; } - public string SeriesName { get; set; } - public string Name { get; set; } - public int? IndexNumber { get; set; } - public int? IndexNumberEnd { get; set; } - public int? ParentIndexNumber { get; set; } - public int? ProductionYear { get; set; } - public long? RuntimeTicks { get; set; } - public Dictionary ProviderIds { get; set; } - - public bool SearchAllProviders { get; set; } - - public ChapterSearchRequest() - { - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs index 27e06fb8d..4b39e66cc 100644 --- a/MediaBrowser.Controller/Chapters/IChapterManager.cs +++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs @@ -1,6 +1,4 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Configuration; @@ -13,12 +11,6 @@ namespace MediaBrowser.Controller.Chapters /// public interface IChapterManager { - /// - /// Adds the parts. - /// - /// The chapter providers. - void AddParts(IEnumerable chapterProviders); - /// /// Gets the chapters. /// @@ -35,43 +27,6 @@ namespace MediaBrowser.Controller.Chapters /// Task. Task SaveChapters(string itemId, List chapters, CancellationToken cancellationToken); - /// - /// Searches the specified video. - /// - /// The video. - /// The cancellation token. - /// Task{IEnumerable{RemoteChapterResult}}. - Task> Search(Video video, CancellationToken cancellationToken); - - /// - /// Searches the specified request. - /// - /// The request. - /// The cancellation token. - /// Task{IEnumerable{RemoteChapterResult}}. - Task> Search(ChapterSearchRequest request, CancellationToken cancellationToken); - - /// - /// Gets the chapters. - /// - /// The identifier. - /// The cancellation token. - /// Task{ChapterResponse}. - Task GetChapters(string id, CancellationToken cancellationToken); - - /// - /// Gets the providers. - /// - /// The item identifier. - /// IEnumerable{ChapterProviderInfo}. - IEnumerable GetProviders(string itemId); - - /// - /// Gets the providers. - /// - /// IEnumerable{ChapterProviderInfo}. - IEnumerable GetProviders(); - /// /// Gets the configuration. /// diff --git a/MediaBrowser.Controller/Chapters/IChapterProvider.cs b/MediaBrowser.Controller/Chapters/IChapterProvider.cs deleted file mode 100644 index a7505347b..000000000 --- a/MediaBrowser.Controller/Chapters/IChapterProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Chapters -{ - public interface IChapterProvider - { - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - - /// - /// Gets the supported media types. - /// - /// The supported media types. - IEnumerable SupportedMediaTypes { get; } - - /// - /// Searches the specified request. - /// - /// The request. - /// The cancellation token. - /// Task{IEnumerable{RemoteChapterResult}}. - Task> Search(ChapterSearchRequest request, CancellationToken cancellationToken); - - /// - /// Gets the chapters. - /// - /// The identifier. - /// The cancellation token. - /// Task{ChapterResponse}. - Task GetChapters(string id, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs index e8083b363..d2c5b9e4e 100644 --- a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs +++ b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs @@ -1,10 +1,20 @@ using System; +using System.Collections.Generic; +using System.Net; +using MediaBrowser.Model.Events; namespace MediaBrowser.Controller.Dlna { public interface IDeviceDiscovery { - event EventHandler DeviceDiscovered; - event EventHandler DeviceLeft; + event EventHandler> DeviceDiscovered; + event EventHandler> DeviceLeft; + } + + public class UpnpDeviceInfo + { + public Uri Location { get; set; } + public Dictionary Headers { get; set; } + public IPEndPoint LocalEndPoint { get; set; } } } diff --git a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs index e4126ddcf..ec3a00aad 100644 --- a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs +++ b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs @@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna { public interface ISsdpHandler { - event EventHandler MessageReceived; } } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index efc450248..7b769deb3 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -34,11 +34,26 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public override bool IsPhysicalRoot + { + get { return true; } + } + public override bool CanDelete() { return false; } + [IgnoreDataMember] + public override bool SupportsPlayedStatus + { + get + { + return false; + } + } + /// /// The _virtual children /// diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 1af55a389..56c3248f3 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -23,8 +23,7 @@ namespace MediaBrowser.Controller.Entities.Audio IHasMusicGenres, IHasLookupInfo, IHasMediaSources, - IThemeMedia, - IArchivable + IThemeMedia { public List ChannelMediaSources { get; set; } @@ -60,10 +59,19 @@ namespace MediaBrowser.Controller.Entities.Audio AlbumArtists = new List(); } + [IgnoreDataMember] + public override bool SupportsPlayedStatus + { + get + { + return true; + } + } + [IgnoreDataMember] public override bool SupportsAddingToPlaylist { - get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; } + get { return true; } } [IgnoreDataMember] @@ -84,21 +92,6 @@ namespace MediaBrowser.Controller.Entities.Audio } } - [IgnoreDataMember] - public bool IsArchive - { - get - { - if (string.IsNullOrWhiteSpace(Path)) - { - return false; - } - var ext = System.IO.Path.GetExtension(Path) ?? string.Empty; - - return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase); - } - } - public override bool CanDownload() { var locationType = LocationType; @@ -262,7 +255,7 @@ namespace MediaBrowser.Controller.Entities.Audio Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File, MediaStreams = MediaSourceManager.GetMediaStreams(i.Id).ToList(), Name = i.Name, - Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path, + Path = enablePathSubstituion ? GetMappedPath(i, i.Path, locationType) : i.Path, RunTimeTicks = i.RunTimeTicks, Container = i.Container, Size = i.Size diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 1f3b0c92a..3f457d89a 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -17,6 +17,9 @@ namespace MediaBrowser.Controller.Entities.Audio /// public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo, IMetadataContainer { + public List AlbumArtists { get; set; } + public List Artists { get; set; } + public MusicAlbum() { Artists = new List(); @@ -48,6 +51,15 @@ namespace MediaBrowser.Controller.Entities.Audio } } + [IgnoreDataMember] + public override bool SupportsPlayedStatus + { + get + { + return false; + } + } + [IgnoreDataMember] public override bool SupportsCumulativeRunTimeTicks { @@ -83,8 +95,6 @@ namespace MediaBrowser.Controller.Entities.Audio get { return false; } } - public List AlbumArtists { get; set; } - /// /// Gets the tracks. /// @@ -103,8 +113,6 @@ namespace MediaBrowser.Controller.Entities.Audio return Tracks; } - public List Artists { get; set; } - public override List GetUserDataKeys() { var list = base.GetUserDataKeys(); diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 81d1deaa2..a31f04fc4 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// Class MusicArtist /// - public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasProductionLocations, IHasLookupInfo + public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo { [IgnoreDataMember] public bool IsAccessedByName @@ -24,8 +24,6 @@ namespace MediaBrowser.Controller.Entities.Audio get { return ParentId == Guid.Empty; } } - public List ProductionLocations { get; set; } - [IgnoreDataMember] public override bool IsFolder { @@ -50,6 +48,15 @@ namespace MediaBrowser.Controller.Entities.Audio get { return true; } } + [IgnoreDataMember] + public override bool SupportsPlayedStatus + { + get + { + return false; + } + } + public override bool CanDelete() { return !IsAccessedByName; @@ -111,11 +118,6 @@ namespace MediaBrowser.Controller.Entities.Audio return base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService); } - public MusicArtist() - { - ProductionLocations = new List(); - } - public override List GetUserDataKeys() { var list = base.GetUserDataKeys(); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 55aaf04ff..7eb84fc80 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -37,6 +37,8 @@ namespace MediaBrowser.Controller.Entities { protected BaseItem() { + ThemeSongIds = new List(); + ThemeVideoIds = new List(); Keywords = new List(); Tags = new List(); Genres = new List(); @@ -44,6 +46,8 @@ namespace MediaBrowser.Controller.Entities ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); LockedFields = new List(); ImageInfos = new List(); + InheritedTags = new List(); + ProductionLocations = new List(); } public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; @@ -64,6 +68,9 @@ namespace MediaBrowser.Controller.Entities public static string ThemeSongFilename = "theme"; public static string ThemeVideosFolderName = "backdrops"; + public List ThemeSongIds { get; set; } + public List ThemeVideoIds { get; set; } + [IgnoreDataMember] public string PreferredMetadataCountryCode { get; set; } [IgnoreDataMember] @@ -72,6 +79,8 @@ namespace MediaBrowser.Controller.Entities public long? Size { get; set; } public string Container { get; set; } public string ShortOverview { get; set; } + [IgnoreDataMember] + public string Tagline { get; set; } public List ImageInfos { get; set; } @@ -114,6 +123,31 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public bool IsInMixedFolder { get; set; } + [IgnoreDataMember] + protected virtual bool SupportsIsInMixedFolderDetection + { + get { return false; } + } + + [IgnoreDataMember] + public virtual bool SupportsPlayedStatus + { + get + { + return false; + } + } + + public bool DetectIsInMixedFolder() + { + if (SupportsIsInMixedFolderDetection) + { + + } + + return IsInMixedFolder; + } + [IgnoreDataMember] public virtual bool SupportsRemoteImageDownloading { @@ -254,6 +288,19 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public string ExternalSeriesId { get; set; } + + [IgnoreDataMember] + public string ExternalSeriesIdLegacy + { + get { return this.GetProviderId("ProviderExternalSeriesId"); } + set + { + this.SetProviderId("ProviderExternalSeriesId", value); + } + } + /// /// Gets or sets the etag. /// @@ -408,7 +455,7 @@ namespace MediaBrowser.Controller.Entities public virtual bool IsInternetMetadataEnabled() { - return ConfigurationManager.Configuration.EnableInternetProviders; + return LibraryManager.GetLibraryOptions(this).EnableInternetProviders; } public virtual bool CanDelete() @@ -784,6 +831,9 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public int InheritedParentalRatingValue { get; set; } + [IgnoreDataMember] + public List InheritedTags { get; set; } + /// /// Gets or sets the critic rating. /// @@ -841,6 +891,7 @@ namespace MediaBrowser.Controller.Entities public List Tags { get; set; } public List Keywords { get; set; } + public List ProductionLocations { get; set; } /// /// Gets or sets the home page URL. @@ -956,7 +1007,7 @@ namespace MediaBrowser.Controller.Entities /// Loads the theme songs. /// /// List{Audio.Audio}. - private IEnumerable LoadThemeSongs(List fileSystemChildren, IDirectoryService directoryService) + private static IEnumerable LoadThemeSongs(List fileSystemChildren, IDirectoryService directoryService) { var files = fileSystemChildren.Where(i => i.IsDirectory) .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) @@ -992,7 +1043,7 @@ namespace MediaBrowser.Controller.Entities /// Loads the video backdrops. /// /// List{Video}. - private IEnumerable