diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 7b6e5ed19e..ce6ca015de 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -122,53 +122,12 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } - const string XbmcMetadata = "Xbmc Nfo"; - const string MediaBrowserMetadata = "Media Browser Xml"; - public void Post(AutoSetMetadataOptions request) { - var service = AutoDetectMetadataService(); - - Logger.Info("Setting preferred metadata format to " + service); - - var serviceToDisable = string.Equals(service, XbmcMetadata) ? - MediaBrowserMetadata : - XbmcMetadata; - - _configurationManager.DisableMetadataService(serviceToDisable); + _configurationManager.DisableMetadataService("Media Browser Xml"); _configurationManager.SaveConfiguration(); } - private string AutoDetectMetadataService() - { - try - { - var paths = _libraryManager.GetDefaultVirtualFolders() - .SelectMany(i => i.Locations) - .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(i => new DirectoryInfo(i)) - .ToList(); - - if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) - .Any()) - { - return XbmcMetadata; - } - - if (paths.SelectMany(i => i.EnumerateFiles("*.xml", SearchOption.AllDirectories)) - .Any(i => string.Equals(i.Name, "series.xml", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "movie.xml", StringComparison.OrdinalIgnoreCase))) - { - return MediaBrowserMetadata; - } - } - catch (Exception) - { - - } - - return XbmcMetadata; - } - /// /// Posts the specified configuraiton. /// diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs index bb8d8eda3f..b873c6a712 100644 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ b/MediaBrowser.Api/Devices/DeviceService.cs @@ -44,8 +44,8 @@ namespace MediaBrowser.Api.Devices [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] public string Name { get; set; } - [ApiMember(Name = "FullPath", Description = "FullPath", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string FullPath { get; set; } + [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Id { get; set; } public Stream RequestStream { get; set; } } @@ -132,7 +132,7 @@ namespace MediaBrowser.Api.Devices { var deviceId = Request.QueryString["DeviceId"]; var album = Request.QueryString["Album"]; - var fullPath = Request.QueryString["FullPath"]; + var id = Request.QueryString["Id"]; var name = Request.QueryString["Name"]; var task = _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo @@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Devices MimeType = Request.ContentType, Album = album, Name = name, - FullPath = fullPath + Id = id }); Task.WaitAll(task); diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 1fa1a5509d..65c51befff 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -237,7 +237,7 @@ namespace MediaBrowser.Api if (musicVideo != null) { - musicVideo.Artist = request.Artists[0]; + musicVideo.Artists = request.Artists.ToList(); musicVideo.Album = request.Album; } diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index b0ee6b6d57..a6024d4610 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -1,10 +1,20 @@ -using MediaBrowser.Controller.Dto; +using MediaBrowser.Api.UserLibrary; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Api.Movies { @@ -16,6 +26,17 @@ namespace MediaBrowser.Api.Movies { } + [Route("/Trailers", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] + public class Getrailers : BaseItemsRequest, IReturn + { + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public Guid? UserId { get; set; } + } + /// /// Class TrailersService /// @@ -38,6 +59,7 @@ namespace MediaBrowser.Api.Movies private readonly IItemRepository _itemRepo; private readonly IDtoService _dtoService; + private readonly IChannelManager _channelManager; /// /// Initializes a new instance of the class. @@ -45,13 +67,14 @@ namespace MediaBrowser.Api.Movies /// The user manager. /// The user data repository. /// The library manager. - public TrailersService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) + public TrailersService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, IChannelManager channelManager) { _userManager = userManager; _userDataRepository = userDataRepository; _libraryManager = libraryManager; _itemRepo = itemRepo; _dtoService = dtoService; + _channelManager = channelManager; } /// @@ -75,5 +98,71 @@ namespace MediaBrowser.Api.Movies return ToOptimizedSerializedResultUsingCache(result); } + + public async Task Get(Getrailers request) + { + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + var result = await GetAllTrailers(user).ConfigureAwait(false); + + IEnumerable items = result.Items; + + // Apply filters + // Run them starting with the ones that are likely to reduce the list the most + foreach (var filter in request.GetFilters().OrderByDescending(f => (int)f)) + { + items = ItemsService.ApplyFilter(items, filter, user, _userDataRepository); + } + + items = _libraryManager.Sort(items, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending); + + var itemsArray = items.ToList(); + + var pagedItems = ApplyPaging(request, itemsArray); + + var fields = request.GetItemFields().ToList(); + + var returnItems = pagedItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray(); + + return new ItemsResult + { + TotalRecordCount = itemsArray.Count, + Items = returnItems + }; + } + + private IEnumerable ApplyPaging(Getrailers request, IEnumerable items) + { + // Start at + if (request.StartIndex.HasValue) + { + items = items.Skip(request.StartIndex.Value); + } + + // Return limit + if (request.Limit.HasValue) + { + items = items.Take(request.Limit.Value); + } + + return items; + } + + private async Task> GetAllTrailers(User user) + { + var trailerResult = await _channelManager.GetAllMediaInternal(new AllChannelMediaQuery + { + ContentTypes = new[] { ChannelMediaContentType.MovieExtra }, + ExtraTypes = new[] { ExtraType.Trailer }, + UserId = user.Id.ToString("N") + + }, CancellationToken.None).ConfigureAwait(false); + + + return new QueryResult + { + Items = trailerResult.Items, + TotalRecordCount = trailerResult.TotalRecordCount + }; + } } } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index ec6ca7737b..ed777cafe0 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -714,8 +714,10 @@ namespace MediaBrowser.Api.Playback /// true if the specified stream is H264; otherwise, false. protected bool IsH264(MediaStream stream) { - return stream.Codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || - stream.Codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; + var codec = stream.Codec ?? string.Empty; + + return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || + codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; } /// @@ -1976,6 +1978,7 @@ namespace MediaBrowser.Api.Playback state.TargetPacketLength, state.TargetTimestamp, state.IsTargetAnamorphic, + state.IsTargetCabac, state.TargetRefFrames); if (mediaProfile != null) @@ -2065,6 +2068,7 @@ namespace MediaBrowser.Api.Playback state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, + state.IsTargetCabac, state.TargetRefFrames ).FirstOrDefault() ?? string.Empty; diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index bea4ba37ed..97565cdc3d 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -367,16 +367,6 @@ namespace MediaBrowser.Api.Playback.Hls { var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); - if (string.Equals(request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Audio codec copy is not allowed here."); - } - - if (string.Equals(request.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Video codec copy is not allowed here."); - } - if (string.IsNullOrEmpty(request.MediaSourceId)) { throw new ArgumentException("MediaSourceId is required"); @@ -511,7 +501,7 @@ namespace MediaBrowser.Api.Playback.Hls private void AppendPlaylist(StringBuilder builder, string url, int bitrate, string subtitleGroup) { - var header = "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture); + var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(UsCulture); if (!string.IsNullOrWhiteSpace(subtitleGroup)) { @@ -638,7 +628,7 @@ namespace MediaBrowser.Api.Playback.Hls // See if we can save come cpu cycles by avoiding encoding if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { - return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy"; + return state.VideoStream != null && IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy"; } var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 02acf7e718..06fa4065c2 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -139,7 +139,7 @@ namespace MediaBrowser.Api.Playback.Hls // See if we can save come cpu cycles by avoiding encoding if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) { - return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy"; + return state.VideoStream != null && IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy"; } var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 6e77e5eabd..35f714cb18 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -1,5 +1,4 @@ -using System.Threading; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Model.Logging; using ServiceStack.Web; using System; @@ -49,9 +48,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// The response stream. public void WriteTo(Stream responseStream) { - var task = WriteToAsync(responseStream); - - Task.WaitAll(task); + WriteToInternal(responseStream); } /// @@ -59,12 +56,12 @@ namespace MediaBrowser.Api.Playback.Progressive /// /// The response stream. /// Task. - public async Task WriteToAsync(Stream responseStream) + private void WriteToInternal(Stream responseStream) { try { - await new ProgressiveFileCopier(_fileSystem, _job) - .StreamFile(Path, responseStream).ConfigureAwait(false); + new ProgressiveFileCopier(_fileSystem, _job) + .StreamFile(Path, responseStream); } catch (Exception ex) { @@ -95,16 +92,16 @@ namespace MediaBrowser.Api.Playback.Progressive _job = job; } - public async Task StreamFile(string path, Stream outputStream) + public void StreamFile(string path, Stream outputStream) { var eofCount = 0; long position = 0; - using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, false)) { while (eofCount < 15) { - await CopyToAsyncInternal(fs, outputStream, 81920, CancellationToken.None).ConfigureAwait(false); + CopyToInternal(fs, outputStream, 81920); var fsPosition = fs.Position; @@ -118,7 +115,8 @@ namespace MediaBrowser.Api.Playback.Progressive { eofCount++; } - await Task.Delay(100).ConfigureAwait(false); + var task = Task.Delay(100); + Task.WaitAll(task); } else { @@ -130,13 +128,13 @@ namespace MediaBrowser.Api.Playback.Progressive } } - private async Task CopyToAsyncInternal(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken) + private void CopyToInternal(Stream source, Stream destination, int bufferSize) { byte[] array = new byte[bufferSize]; int count; - while ((count = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + while ((count = source.Read(array, 0, array.Length)) != 0) { - await destination.WriteAsync(array, 0, count, cancellationToken).ConfigureAwait(false); + destination.Write(array, 0, count); _bytesWritten += count; diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs index 8f9fa11db1..ada2a98a15 100644 --- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs +++ b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs @@ -2,7 +2,6 @@ using ServiceStack.Web; using System.Collections.Generic; using System.IO; -using System.Threading.Tasks; namespace MediaBrowser.Api.Playback { @@ -41,22 +40,7 @@ namespace MediaBrowser.Api.Playback /// The response stream. public void WriteTo(Stream responseStream) { - var task = WriteToAsync(responseStream); - - Task.WaitAll(task); - } - - /// - /// Writes to async. - /// - /// The response stream. - /// Task. - public async Task WriteToAsync(Stream responseStream) - { - using (_response) - { - await _response.Content.CopyToAsync(responseStream, 819200).ConfigureAwait(false); - } + _response.Content.CopyTo(responseStream, 819200); } } } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 24b2bebd3a..832eb3ca87 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -97,7 +97,12 @@ namespace MediaBrowser.Api.Playback public bool ReadInputAtNativeFramerate { - get { return InputProtocol == MediaProtocol.Rtmp || string.Equals(InputContainer, "wtv", StringComparison.OrdinalIgnoreCase); } + get { + + return InputProtocol == MediaProtocol.Rtmp || + string.Equals(InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) || + !string.IsNullOrEmpty(LiveTvStreamId); + } } public TransportStreamTimestamp InputTimestamp { get; set; } @@ -413,5 +418,18 @@ namespace MediaBrowser.Api.Playback return false; } } + + public bool? IsTargetCabac + { + get + { + if (Request.Static) + { + return VideoStream == null ? null : VideoStream.IsCabac; + } + + return true; + } + } } } diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 897a68a65c..9cad64bfa4 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Collections; +using System.Threading; +using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -8,6 +9,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Channels; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using ServiceStack; diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index 5a5d48ac27..a5b8082262 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -296,6 +296,16 @@ namespace MediaBrowser.Common.Implementations logger.Info("64-Bit Process: {0}", Environment.Is64BitProcess); logger.Info("Program data path: {0}", appPaths.ProgramDataPath); + Type type = Type.GetType("Mono.Runtime"); + if (type != null) + { + MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); + if (displayName != null) + { + logger.Info("Mono: " + displayName.Invoke(null, null)); + } + } + logger.Info("Application Path: {0}", appPaths.ApplicationPath); logger.Info("*** When reporting issues please include the entire log file. ***".ToUpper()); diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index 4a8597b7fc..43db03b428 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager } request.Method = method; - request.Timeout = 20000; + request.Timeout = options.TimeoutMs; if (!string.IsNullOrEmpty(options.Host)) { @@ -390,7 +390,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager if (!options.BufferContent) { - var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false); + var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false); var httpResponse = (HttpWebResponse)response; @@ -401,7 +401,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager return GetResponseInfo(httpResponse, httpResponse.GetResponseStream(), GetContentLength(httpResponse), httpResponse); } - using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) + using (var response = await GetResponseAsync(httpWebRequest, TimeSpan.FromMilliseconds(options.TimeoutMs)).ConfigureAwait(false)) { var httpResponse = (HttpWebResponse)response; @@ -434,21 +434,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager throw exception; } - catch (HttpRequestException ex) - { - _logger.ErrorException("Error getting response from " + options.Url, ex); - - throw new HttpException(ex.Message, ex); - } - catch (WebException ex) - { - throw GetException(ex, options); - } catch (Exception ex) { - _logger.ErrorException("Error getting response from " + options.Url, ex); - - throw; + throw GetException(ex, options); } finally { @@ -636,21 +624,10 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager return GetResponseInfo(httpResponse, tempFile, contentLength); } } - catch (OperationCanceledException ex) - { - throw GetTempFileException(ex, options, tempFile); - } - catch (HttpRequestException ex) - { - throw GetTempFileException(ex, options, tempFile); - } - catch (WebException ex) - { - throw GetTempFileException(ex, options, tempFile); - } catch (Exception ex) { - throw GetTempFileException(ex, options, tempFile); + DeleteTempFile(tempFile); + throw GetException(ex, options); } finally { @@ -675,44 +652,25 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// - /// Handles the temp file exception. - /// - /// The ex. - /// The options. - /// The temp file. - /// Task. - /// - private Exception GetTempFileException(Exception ex, HttpRequestOptions options, string tempFile) + private Exception GetException(Exception ex, HttpRequestOptions options) { - var operationCanceledException = ex as OperationCanceledException; + var webException = ex as WebException + ?? ex.InnerException as WebException; - if (operationCanceledException != null) + if (webException != null) { - // Cleanup - DeleteTempFile(tempFile); - - return GetCancellationException(options.Url, options.CancellationToken, operationCanceledException); + return GetException(webException, options); } - _logger.ErrorException("Error getting response from " + options.Url, ex); - - // Cleanup - DeleteTempFile(tempFile); - - var httpRequestException = ex as HttpRequestException; + var operationCanceledException = ex as OperationCanceledException + ?? ex.InnerException as OperationCanceledException; - if (httpRequestException != null) + if (operationCanceledException != null) { - return new HttpException(ex.Message, ex); + return GetCancellationException(options.Url, options.CancellationToken, operationCanceledException); } - var webException = ex as WebException; - - if (webException != null) - { - throw GetException(webException, options); - } + _logger.ErrorException("Error getting response from " + options.Url, ex); return ex; } @@ -843,5 +801,47 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager { return Post(url, postData, null, cancellationToken); } + + private Task GetResponseAsync(WebRequest request, TimeSpan timeout) + { + var taskCompletion = new TaskCompletionSource(); + + Task asyncTask = Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null); + + ThreadPool.RegisterWaitForSingleObject((asyncTask as IAsyncResult).AsyncWaitHandle, TimeoutCallback, request, timeout, true); + asyncTask.ContinueWith(task => + { + taskCompletion.TrySetResult(task.Result); + + }, TaskContinuationOptions.NotOnFaulted); + + // Handle errors + asyncTask.ContinueWith(task => + { + if (task.Exception != null) + { + taskCompletion.TrySetException(task.Exception); + } + else + { + taskCompletion.TrySetException(new List()); + } + + }, TaskContinuationOptions.OnlyOnFaulted); + + return taskCompletion.Task; + } + + private static void TimeoutCallback(object state, bool timedOut) + { + if (timedOut) + { + WebRequest request = (WebRequest)state; + if (state != null) + { + request.Abort(); + } + } + } } } diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index 09bf6e2328..81f1d70d39 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -94,6 +94,8 @@ namespace MediaBrowser.Common.Net public CacheMode CacheMode { get; set; } public TimeSpan CacheLength { get; set; } + public int TimeoutMs { get; set; } + private string GetHeaderValue(string name) { string value; @@ -115,6 +117,8 @@ namespace MediaBrowser.Common.Net LogRequest = true; CacheMode = CacheMode.None; + + TimeoutMs = 20000; } public void SetPostData(IDictionary values) diff --git a/MediaBrowser.Controller/Connect/UserLinkResult.cs b/MediaBrowser.Controller/Connect/UserLinkResult.cs index 4ed57cfc2d..16ebfc70a3 100644 --- a/MediaBrowser.Controller/Connect/UserLinkResult.cs +++ b/MediaBrowser.Controller/Connect/UserLinkResult.cs @@ -4,5 +4,7 @@ namespace MediaBrowser.Controller.Connect public class UserLinkResult { public bool IsPending { get; set; } + public bool IsNewUserInvitation { get; set; } + public string GuestDisplayName { get; set; } } } diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index b3917d70b9..61b2caec0d 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -1,8 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using System; using System.Collections.Generic; namespace MediaBrowser.Controller.Dto @@ -12,14 +10,6 @@ namespace MediaBrowser.Controller.Dto /// public interface IDtoService { - /// - /// Gets the user dto. - /// - /// The user. - /// UserDto. - [Obsolete] - UserDto GetUserDto(User user); - /// /// Gets the dto id. /// diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 070572b9b2..2d9e052b1a 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -226,7 +226,9 @@ namespace MediaBrowser.Controller.Entities.Audio public IEnumerable GetTaggedItems(IEnumerable inputItems) { - return inputItems.OfType().Where(i => i.HasArtist(Name)).Cast(); + return inputItems.OfType() + .Where(i => i.HasArtist(Name)) + .Cast(); } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 3137166953..3193ad091d 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -429,6 +429,12 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public virtual BaseItem DisplayParent + { + get { return Parent; } + } + /// /// When the item first debuted. For movies this could be premiere date, episodes would be first aired /// @@ -548,7 +554,7 @@ namespace MediaBrowser.Controller.Entities return CustomRating; } - var parent = Parent; + var parent = DisplayParent; if (parent != null) { return parent.CustomRatingForComparison; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 4b90741c09..a10742f01e 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -74,7 +74,8 @@ namespace MediaBrowser.Controller.Entities { FileInfo = new DirectoryInfo(path), Path = path, - Parent = Parent + Parent = Parent, + CollectionType = CollectionType }; // Gather child folder and files diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 4614f2f8a3..4abdde8ddd 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -736,7 +736,9 @@ namespace MediaBrowser.Controller.Entities /// IEnumerable{BaseItem}. protected virtual IEnumerable GetNonCachedChildren(IDirectoryService directoryService) { - return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this); + var collectionType = LibraryManager.FindCollectionType(this); + + return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, collectionType); } /// @@ -745,7 +747,16 @@ namespace MediaBrowser.Controller.Entities /// IEnumerable{BaseItem}. protected IEnumerable GetCachedChildren() { - return ItemRepository.GetChildren(Id).Select(RetrieveChild).Where(i => i != null); + var childrenItems = ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null); + + //var children = ItemRepository.GetChildren(Id).Select(RetrieveChild).Where(i => i != null).ToList(); + + //if (children.Count != childrenItems.Count) + //{ + // var b = this; + //} + + return childrenItems; } /// @@ -770,6 +781,29 @@ namespace MediaBrowser.Controller.Entities return item; } + private BaseItem RetrieveChild(BaseItem child) + { + var item = LibraryManager.GetMemoryItemById(child.Id); + + if (item != null) + { + if (item is IByReferenceItem) + { + return LibraryManager.GetOrAddByReferenceItem(item); + } + + item.Parent = this; + } + else + { + child.Parent = this; + LibraryManager.RegisterItem(child); + item = child; + } + + return item; + } + public virtual Task> GetItems(InternalItemsQuery query) { var user = query.User; diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index d36bfd7c41..015e4b4aee 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -11,12 +11,6 @@ namespace MediaBrowser.Controller.Entities { public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasProductionLocations, IHasBudget, IHasLookupInfo { - /// - /// Gets or sets the artist. - /// - /// The artist. - public string Artist { get; set; } - /// /// Gets or sets the album. /// @@ -35,43 +29,35 @@ namespace MediaBrowser.Controller.Entities /// The revenue. public double? Revenue { get; set; } public List ProductionLocations { get; set; } + public List Artists { get; set; } public MusicVideo() { ProductionLocations = new List(); + Artists = new List(); } [IgnoreDataMember] - public List Artists + public List AllArtists { get { - var list = new List(); - - if (!string.IsNullOrEmpty(Artist)) - { - list.Add(Artist); - } - - return list; - + return Artists; } } - [IgnoreDataMember] - public List AllArtists + /// + /// TODO: Remove + /// + public string Artist { - get + get { return Artists.FirstOrDefault(); } + set { - var list = new List(); - - if (!string.IsNullOrEmpty(Artist)) + if (!string.IsNullOrEmpty(value) && !Artists.Contains(value, StringComparer.OrdinalIgnoreCase)) { - list.Add(Artist); + Artists.Add(value); } - - return list; - } } @@ -82,7 +68,7 @@ namespace MediaBrowser.Controller.Entities /// true if the specified name has artist; otherwise, false. public bool HasArtist(string name) { - return string.Equals(Artist, name, StringComparison.OrdinalIgnoreCase); + return AllArtists.Contains(name, StringComparer.OrdinalIgnoreCase); } /// diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 87074ca022..be7867e06c 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -104,6 +104,15 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override BaseItem DisplayParent + { + get + { + return Season ?? Parent; + } + } + /// /// Gets the user data key. /// @@ -153,7 +162,7 @@ namespace MediaBrowser.Controller.Entities.TV // Episodes directly in series folder if (season == null) { - var series = FindParent(); + var series = Series; if (ParentIndexNumber.HasValue) { diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 6804b29b73..5426281963 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -44,6 +44,12 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override BaseItem DisplayParent + { + get { return Series ?? Parent; } + } + /// /// We want to group into our Series /// diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 951513962c..f935648822 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -17,30 +17,14 @@ namespace MediaBrowser.Controller.Library /// public interface ILibraryManager { - /// - /// Resolves the item. - /// - /// The args. - /// BaseItem. - BaseItem ResolveItem(ItemResolveArgs args); - - /// - /// Resolves a path into a BaseItem - /// - /// The file info. - /// The directory service. - /// The parent. - /// BaseItem. - /// - BaseItem ResolvePath(FileSystemInfo fileInfo, IDirectoryService directoryService, Folder parent = null); - /// /// Resolves the path. /// /// The file information. /// The parent. + /// Type of the collection. /// BaseItem. - BaseItem ResolvePath(FileSystemInfo fileInfo, Folder parent = null); + BaseItem ResolvePath(FileSystemInfo fileInfo, Folder parent = null, string collectionType = null); /// /// Resolves a set of files into a list of BaseItem @@ -49,8 +33,9 @@ namespace MediaBrowser.Controller.Library /// The files. /// The directory service. /// The parent. + /// Type of the collection. /// List{``0}. - List ResolvePaths(IEnumerable files, IDirectoryService directoryService, Folder parent) + List ResolvePaths(IEnumerable files, IDirectoryService directoryService, Folder parent, string collectionType = null) where T : BaseItem; /// @@ -151,6 +136,13 @@ namespace MediaBrowser.Controller.Library /// BaseItem. BaseItem GetItemById(Guid id); + /// + /// Gets the memory item by identifier. + /// + /// The identifier. + /// BaseItem. + BaseItem GetMemoryItemById(Guid id); + /// /// Gets the intros. /// diff --git a/MediaBrowser.Controller/Library/IMetadataSaver.cs b/MediaBrowser.Controller/Library/IMetadataSaver.cs index ce8feb4c6c..f13cf45879 100644 --- a/MediaBrowser.Controller/Library/IMetadataSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataSaver.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; using System.Threading; namespace MediaBrowser.Controller.Library diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index c1fcdc9feb..d1692aabf0 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -230,29 +230,18 @@ namespace MediaBrowser.Controller.Library } /// - /// Gets the name of the meta file by. + /// Determines whether [contains meta file by name] [the specified name]. /// /// The name. - /// FileSystemInfo. - /// - public FileSystemInfo GetMetaFileByName(string name) + /// true if [contains meta file by name] [the specified name]; otherwise, false. + public bool ContainsMetaFileByName(string name) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(); } - return GetFileSystemEntryByName(name); - } - - /// - /// Determines whether [contains meta file by name] [the specified name]. - /// - /// The name. - /// true if [contains meta file by name] [the specified name]; otherwise, false. - public bool ContainsMetaFileByName(string name) - { - return GetMetaFileByName(name) != null; + return GetFileSystemEntryByName(name) != null; } /// @@ -265,20 +254,13 @@ namespace MediaBrowser.Controller.Library return GetFileSystemEntryByName(name) != null; } - private bool _collectionTypeDiscovered; - private string _collectionType; - public string GetCollectionType() { - if (!_collectionTypeDiscovered) - { - _collectionType = Parent == null ? null : _libraryManager.FindCollectionType(Parent); - _collectionTypeDiscovered = true; - } - - return _collectionType; + return CollectionType; } + public string CollectionType { get; set; } + #region Equality Overrides /// diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 34486182b7..c85a8f3354 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -284,7 +284,7 @@ namespace MediaBrowser.Controller.Library { if (IsSeasonFolder(child.FullName, directoryService, fileSystem)) { - logger.Debug("{0} is a series because of season folder {1}.", path, child.FullName); + //logger.Debug("{0} is a series because of season folder {1}.", path, child.FullName); return true; } diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 02bca739f4..edaa15c9df 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Persistence /// The cancellation token. /// Task. Task DeleteItem(Guid id, CancellationToken cancellationToken); - + /// /// Gets the critic reviews. /// @@ -41,6 +41,13 @@ namespace MediaBrowser.Controller.Persistence /// Task{IEnumerable{ItemReview}}. IEnumerable GetCriticReviews(Guid itemId); + /// + /// Gets the children items. + /// + /// The parent identifier. + /// IEnumerable<BaseItem>. + IEnumerable GetChildrenItems(Guid parentId); + /// /// Saves the critic reviews. /// @@ -101,7 +108,7 @@ namespace MediaBrowser.Controller.Persistence /// The type. /// IEnumerable{Guid}. IEnumerable GetItemsOfType(Type type); - + /// /// Saves the children. /// diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index dd07979d34..e7dcd03b5b 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -14,10 +14,6 @@ namespace MediaBrowser.Controller.Providers public MetadataRefreshMode MetadataRefreshMode { get; set; } - /// - /// TODO: deprecate. Keeping this for now, for api compatibility - /// - [Obsolete] public bool ForceSave { get; set; } public MetadataRefreshOptions() diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs index 7d9c9d8769..5b40073226 100644 --- a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs +++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs @@ -246,7 +246,7 @@ namespace MediaBrowser.Controller.Resolvers if (config.UseFileCreationTimeForDateAdded) { - item.DateModified = fileSystem.GetCreationTimeUtc(info); + item.DateCreated = fileSystem.GetCreationTimeUtc(info); } else { diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 53a8d5a7cb..d8a2464d64 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -80,6 +80,12 @@ namespace MediaBrowser.Controller.Session /// The last activity date. public DateTime LastActivityDate { get; set; } + /// + /// Gets or sets the last playback check in. + /// + /// The last playback check in. + public DateTime LastPlaybackCheckIn { get; set; } + /// /// Gets or sets the name of the device. /// diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs index 3f9ce66c5c..b92a4174ef 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -1,7 +1,9 @@ -using MediaBrowser.Common.Extensions; +using System.Linq; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Dlna.Didl; @@ -85,7 +87,9 @@ namespace MediaBrowser.Dlna.ContentDirectory { var id = sparams["ObjectID"]; - var item = GetItemFromObjectId(id, user); + var serverItem = GetItemFromObjectId(id, user); + + var item = serverItem.Item; var newbookmark = int.Parse(sparams["PosSecond"], _usCulture); @@ -173,49 +177,48 @@ namespace MediaBrowser.Dlna.ContentDirectory //didl.SetAttribute("xmlns:sec", NS_SEC); result.AppendChild(didl); - var item = GetItemFromObjectId(id, user); + var serverItem = GetItemFromObjectId(id, user); + var item = serverItem.Item; var totalCount = 0; if (string.Equals(flag, "BrowseMetadata")) { - var folder = item as Folder; - - if (folder == null) + if (item.IsFolder || serverItem.StubType.HasValue) { - result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, item, null, deviceId, filter)); + var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requested).ConfigureAwait(false)); + totalCount = childrenResult.TotalRecordCount; + + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, item, serverItem.StubType, null, totalCount, filter, id)); } else { - var childrenResult = (await GetUserItems(folder, user, sortCriteria, start, requested).ConfigureAwait(false)); - totalCount = childrenResult.TotalRecordCount; - - result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, totalCount, filter, id)); + result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, item, null, null, deviceId, filter)); } + provided++; } else { - var folder = (Folder)item; - - var childrenResult = (await GetUserItems(folder, user, sortCriteria, start, requested).ConfigureAwait(false)); + var childrenResult = (await GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requested).ConfigureAwait(false)); totalCount = childrenResult.TotalRecordCount; provided = childrenResult.Items.Length; foreach (var i in childrenResult.Items) { - if (i.IsFolder) + var displayStubType = GetDisplayStubType(i, serverItem.Item); + + if (i.IsFolder || displayStubType.HasValue) { - var f = (Folder)i; - var childCount = (await GetUserItems(f, user, sortCriteria, null, 0).ConfigureAwait(false)) + var childCount = (await GetUserItems(i, displayStubType, user, sortCriteria, null, 0).ConfigureAwait(false)) .TotalRecordCount; - result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, i, displayStubType, item, childCount, filter)); } else { - result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, i, folder, deviceId, filter)); + result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, i, item, serverItem.StubType, deviceId, filter)); } } } @@ -231,6 +234,24 @@ namespace MediaBrowser.Dlna.ContentDirectory }; } + private StubType? GetDisplayStubType(BaseItem item, BaseItem context) + { + if (context == null || context.IsFolder) + { + var movie = item as Movie; + if (movie != null) + { + if (movie.LocalTrailerIds.Count > 0 || + movie.SpecialFeatureIds.Count > 0) + { + return StubType.Folder; + } + } + } + + return null; + } + private async Task>> HandleSearch(Headers sparams, User user, string deviceId) { var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", "")); @@ -269,9 +290,11 @@ namespace MediaBrowser.Dlna.ContentDirectory result.AppendChild(didl); - var folder = (Folder)GetItemFromObjectId(sparams["ContainerID"], user); + var serverItem = GetItemFromObjectId(sparams["ContainerID"], user); + + var item = serverItem.Item; - var childrenResult = (await GetChildrenSorted(folder, user, searchCriteria, sortCriteria, start, requested).ConfigureAwait(false)); + var childrenResult = (await GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requested).ConfigureAwait(false)); var totalCount = childrenResult.TotalRecordCount; @@ -281,15 +304,14 @@ namespace MediaBrowser.Dlna.ContentDirectory { if (i.IsFolder) { - var f = (Folder)i; - var childCount = (await GetChildrenSorted(f, user, searchCriteria, sortCriteria, null, 0).ConfigureAwait(false)) + var childCount = (await GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0).ConfigureAwait(false)) .TotalRecordCount; - result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, i, null, item, childCount, filter)); } else { - result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, i, folder, deviceId, filter)); + result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, i, item, serverItem.StubType, deviceId, filter)); } } @@ -304,8 +326,10 @@ namespace MediaBrowser.Dlna.ContentDirectory }; } - private async Task> GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) + private async Task> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) { + var folder = (Folder)item; + var sortOrders = new List(); if (!folder.IsPreSorted) { @@ -340,7 +364,7 @@ namespace MediaBrowser.Dlna.ContentDirectory //items = items.OfType(); isFolder = true; } - + return await folder.GetItems(new InternalItemsQuery { Limit = limit, @@ -356,8 +380,20 @@ namespace MediaBrowser.Dlna.ContentDirectory }).ConfigureAwait(false); } - private async Task> GetUserItems(Folder folder, User user, SortCriteria sort, int? startIndex, int? limit) + private async Task> GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit) { + if (stubType.HasValue) + { + var movie = item as Movie; + + if (movie != null) + { + return await GetMovieItems(movie).ConfigureAwait(false); + } + } + + var folder = (Folder)item; + var sortOrders = new List(); if (!folder.IsPreSorted) { @@ -376,6 +412,23 @@ namespace MediaBrowser.Dlna.ContentDirectory }).ConfigureAwait(false); } + private Task> GetMovieItems(Movie item) + { + var list = new List(); + + list.Add(item); + + list.AddRange(item.LocalTrailerIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null)); + list.AddRange(item.SpecialFeatureIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null)); + list.AddRange(item.ThemeVideoIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null)); + + return Task.FromResult(new QueryResult + { + Items = list.ToArray(), + TotalRecordCount = list.Count + }); + } + private bool FilterUnsupportedContent(BaseItem i, User user) { // Unplayable @@ -399,26 +452,50 @@ namespace MediaBrowser.Dlna.ContentDirectory return true; } - private BaseItem GetItemFromObjectId(string id, User user) + private ServerItem GetItemFromObjectId(string id, User user) { return DidlBuilder.IsIdRoot(id) - ? user.RootFolder + ? new ServerItem { Item = user.RootFolder } : ParseItemId(id, user); } - private BaseItem ParseItemId(string id, User user) + private ServerItem ParseItemId(string id, User user) { Guid itemId; + StubType? stubType = null; + + if (id.StartsWith("folder_", StringComparison.OrdinalIgnoreCase)) + { + stubType = StubType.Folder; + id = id.Split(new[] { '_' }, 2)[1]; + } if (Guid.TryParse(id, out itemId)) { - return _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetItemById(itemId); + + return new ServerItem + { + Item = item, + StubType = stubType + }; } Logger.Error("Error parsing item Id: {0}. Returning user root folder.", id); - return user.RootFolder; + return new ServerItem { Item = user.RootFolder }; } } + + internal class ServerItem + { + public BaseItem Item { get; set; } + public StubType? StubType { get; set; } + } + + public enum StubType + { + Folder = 0 + } } diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 28aebae79e..62f1febe7d 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Playlists; +using MediaBrowser.Dlna.ContentDirectory; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; @@ -63,25 +64,35 @@ namespace MediaBrowser.Dlna.Didl result.AppendChild(didl); - result.DocumentElement.AppendChild(GetItemElement(result, item, context, deviceId, filter, streamInfo)); + result.DocumentElement.AppendChild(GetItemElement(result, item, context, null, deviceId, filter, streamInfo)); return result.DocumentElement.OuterXml; } - public XmlElement GetItemElement(XmlDocument doc, BaseItem item, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo = null) + public XmlElement GetItemElement(XmlDocument doc, BaseItem item, BaseItem context, StubType? contextStubType, string deviceId, Filter filter, StreamInfo streamInfo = null) { + var clientId = GetClientId(item, null); + var element = doc.CreateElement(string.Empty, "item", NS_DIDL); element.SetAttribute("restricted", "1"); - element.SetAttribute("id", item.Id.ToString("N")); + element.SetAttribute("id", clientId); - if (item.Parent != null) + if (context != null) + { + element.SetAttribute("parentID", GetClientId(context, contextStubType)); + } + else { - element.SetAttribute("parentID", item.Parent.Id.ToString("N")); + var parent = item.DisplayParent; + if (parent != null) + { + element.SetAttribute("parentID", GetClientId(parent, null)); + } } //AddBookmarkInfo(item, user, element); - AddGeneralProperties(item, context, element, filter); + AddGeneralProperties(item, null, context, element, filter); // refID? // storeAttribute(itemNode, object, ClassProperties.REF_ID, false); @@ -111,14 +122,14 @@ namespace MediaBrowser.Dlna.Didl { var sources = _user == null ? video.GetMediaSources(true).ToList() : video.GetMediaSources(true, _user).ToList(); - streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions - { - ItemId = video.Id.ToString("N"), - MediaSources = sources, - Profile = _profile, - DeviceId = deviceId, - MaxBitrate = _profile.MaxStreamingBitrate - }); + streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions + { + ItemId = GetClientId(video), + MediaSources = sources, + Profile = _profile, + DeviceId = deviceId, + MaxBitrate = _profile.MaxStreamingBitrate + }); } var targetWidth = streamInfo.TargetWidth; @@ -142,6 +153,7 @@ namespace MediaBrowser.Dlna.Didl streamInfo.TargetPacketLength, streamInfo.TranscodeSeekInfo, streamInfo.IsTargetAnamorphic, + streamInfo.IsTargetCabac, streamInfo.TargetRefFrames); foreach (var contentFeature in contentFeatureList) @@ -263,6 +275,7 @@ namespace MediaBrowser.Dlna.Didl streamInfo.TargetPacketLength, streamInfo.TargetTimestamp, streamInfo.IsTargetAnamorphic, + streamInfo.IsTargetCabac, streamInfo.TargetRefFrames); var filename = url.Substring(0, url.IndexOf('?')); @@ -311,7 +324,7 @@ namespace MediaBrowser.Dlna.Didl return item.Name; } - + private void AddAudioResource(XmlElement container, IHasMediaSources audio, string deviceId, Filter filter, StreamInfo streamInfo = null) { var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); @@ -322,7 +335,7 @@ namespace MediaBrowser.Dlna.Didl streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions { - ItemId = audio.Id.ToString("N"), + ItemId = GetClientId(audio), MediaSources = sources, Profile = _profile, DeviceId = deviceId @@ -403,8 +416,8 @@ namespace MediaBrowser.Dlna.Didl public static bool IsIdRoot(string id) { - if (string.IsNullOrWhiteSpace(id) || - + if (string.IsNullOrWhiteSpace(id) || + string.Equals(id, "0", StringComparison.OrdinalIgnoreCase) // Samsung sometimes uses 1 as root @@ -416,13 +429,15 @@ namespace MediaBrowser.Dlna.Didl return false; } - public XmlElement GetFolderElement(XmlDocument doc, BaseItem folder, int childCount, Filter filter, string requestedId = null) + public XmlElement GetFolderElement(XmlDocument doc, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null) { var container = doc.CreateElement(string.Empty, "container", NS_DIDL); container.SetAttribute("restricted", "0"); container.SetAttribute("searchable", "1"); container.SetAttribute("childCount", childCount.ToString(_usCulture)); + var clientId = GetClientId(folder, stubType); + if (string.Equals(requestedId, "0")) { container.SetAttribute("id", "0"); @@ -430,20 +445,20 @@ namespace MediaBrowser.Dlna.Didl } else { - container.SetAttribute("id", folder.Id.ToString("N")); + container.SetAttribute("id", clientId); - var parent = folder.Parent; + var parent = context ?? folder.DisplayParent; if (parent == null) { container.SetAttribute("parentID", "0"); } else { - container.SetAttribute("parentID", parent.Id.ToString("N")); + container.SetAttribute("parentID", GetClientId(parent, null)); } } - AddCommonFields(folder, null, container, filter); + AddCommonFields(folder, stubType, null, container, filter); AddCover(folder, container); @@ -466,10 +481,11 @@ namespace MediaBrowser.Dlna.Didl /// Adds fields used by both items and folders /// /// The item. + /// Type of the item stub. /// The context. /// The element. /// The filter. - private void AddCommonFields(BaseItem item, BaseItem context, XmlElement element, Filter filter) + private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlElement element, Filter filter) { // Don't filter on dc:title because not all devices will include it in the filter // MediaMonkey for example won't display content without a title @@ -478,7 +494,7 @@ namespace MediaBrowser.Dlna.Didl AddValue(element, "dc", "title", GetDisplayName(item, context), NS_DC); } - element.AppendChild(CreateObjectClass(element.OwnerDocument, item)); + element.AppendChild(CreateObjectClass(element.OwnerDocument, item, itemStubType)); if (filter.Contains("dc:date")) { @@ -539,14 +555,14 @@ namespace MediaBrowser.Dlna.Didl AddPeople(item, element); } - private XmlElement CreateObjectClass(XmlDocument result, BaseItem item) + private XmlElement CreateObjectClass(XmlDocument result, BaseItem item, StubType? stubType) { // More types here // http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs var objectClass = result.CreateElement("upnp", "class", NS_UPNP); - if (item.IsFolder) + if (item.IsFolder || stubType.HasValue) { string classType = null; @@ -560,7 +576,7 @@ namespace MediaBrowser.Dlna.Didl { classType = "object.container.person.musicArtist"; } - else if (item is Series || item is Season || item is BoxSet) + else if (item is Series || item is Season || item is BoxSet || item is Video) { classType = "object.container.album.videoAlbum"; } @@ -628,9 +644,9 @@ namespace MediaBrowser.Dlna.Didl } } - private void AddGeneralProperties(BaseItem item, BaseItem context, XmlElement element, Filter filter) + private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlElement element, Filter filter) { - AddCommonFields(item, context, element, filter); + AddCommonFields(item, itemStubType, context, element, filter); var audio = item as Audio; @@ -671,10 +687,10 @@ namespace MediaBrowser.Dlna.Didl if (musicVideo != null) { - if (!string.IsNullOrEmpty(musicVideo.Artist)) + foreach (var artist in musicVideo.Artists) { - AddValue(element, "upnp", "artist", musicVideo.Artist, NS_UPNP); - AddAlbumArtist(element, musicVideo.Artist); + AddValue(element, "upnp", "artist", artist, NS_UPNP); + AddAlbumArtist(element, artist); } if (!string.IsNullOrEmpty(musicVideo.Album)) @@ -773,20 +789,26 @@ namespace MediaBrowser.Dlna.Didl } } - AddImageResElement(item, element, 4096, 4096, playbackPercentage, "jpg", "JPEG_LRG"); - AddImageResElement(item, element, 4096, 4096, playbackPercentage, "png", "PNG_LRG"); - AddImageResElement(item, element, 1024, 768, playbackPercentage, "jpg", "JPEG_MED"); - AddImageResElement(item, element, 640, 480, playbackPercentage, "jpg", "JPEG_SM"); + var imageLimit = _profile.DidlAlbumArtLimit ?? 100; + AddImageResElement(item, element, 160, 160, playbackPercentage, "jpg", "JPEG_TN"); - AddImageResElement(item, element, 160, 160, playbackPercentage, "png", "PNG_TN"); + + if (imageLimit > 1) + { + AddImageResElement(item, element, 4096, 4096, playbackPercentage, "jpg", "JPEG_LRG"); + AddImageResElement(item, element, 1024, 768, playbackPercentage, "jpg", "JPEG_MED"); + AddImageResElement(item, element, 640, 480, playbackPercentage, "jpg", "JPEG_SM"); + AddImageResElement(item, element, 4096, 4096, playbackPercentage, "png", "PNG_LRG"); + AddImageResElement(item, element, 160, 160, playbackPercentage, "png", "PNG_TN"); + } } - private void AddImageResElement(BaseItem item, - XmlElement element, - int maxWidth, - int maxHeight, + private void AddImageResElement(BaseItem item, + XmlElement element, + int maxWidth, + int maxHeight, int playbackPercentage, - string format, + string format, string org_Pn) { var imageInfo = GetImageInfo(item); @@ -920,6 +942,25 @@ namespace MediaBrowser.Dlna.Didl internal int? Height; } + public static string GetClientId(BaseItem item, StubType? stubType) + { + var id = item.Id.ToString("N"); + + if (stubType.HasValue) + { + id = stubType.Value.ToString().ToLower() + "_" + id; + } + + return id; + } + + public static string GetClientId(IHasMediaSources item) + { + var id = item.Id.ToString("N"); + + return id; + } + private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, int playbackPercentage, string format) { var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/{7}", diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 1a5269b9db..f4578eca7c 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Dlna.Profiles; using MediaBrowser.Dlna.Server; using MediaBrowser.Model.Dlna; @@ -37,8 +38,6 @@ namespace MediaBrowser.Dlna _appPaths = appPaths; _logger = logger; _jsonSerializer = jsonSerializer; - - //DumpProfiles(); } public IEnumerable GetProfiles() @@ -55,44 +54,6 @@ namespace MediaBrowser.Dlna return list; } - private void DumpProfiles() - { - var list = new List - { - new SamsungSmartTvProfile(), - new Xbox360Profile(), - new XboxOneProfile(), - new SonyPs3Profile(), - new SonyBravia2010Profile(), - new SonyBravia2011Profile(), - new SonyBravia2012Profile(), - new SonyBravia2013Profile(), - new SonyBlurayPlayer2013Profile(), - new SonyBlurayPlayerProfile(), - new PanasonicVieraProfile(), - new WdtvLiveProfile(), - new DenonAvrProfile(), - new LinksysDMA2100Profile(), - new LgTvProfile(), - new Foobar2000Profile(), - new MediaMonkeyProfile(), - new Windows81Profile(), - //new WindowsMediaCenterProfile(), - new WindowsPhoneProfile(), - new AndroidProfile(true, true, new[]{"baseline", "constrained baseline"}), - new DirectTvProfile(), - new DishHopperJoeyProfile(), - new DefaultProfile() - }; - - foreach (var item in list) - { - var path = Path.Combine(_appPaths.ProgramDataPath, _fileSystem.GetValidFilename(item.Name) + ".xml"); - - _xmlSerializer.SerializeToFile(item, path); - } - } - private bool _extracted; private readonly object _syncLock = new object(); private void ExtractProfilesIfNeeded() @@ -521,4 +482,66 @@ namespace MediaBrowser.Dlna }; } } + + class DlnaProfileEntryPoint : IServerEntryPoint + { + private readonly IApplicationPaths _appPaths; + private readonly IXmlSerializer _xmlSerializer; + private readonly IFileSystem _fileSystem; + + public DlnaProfileEntryPoint(IApplicationPaths appPaths, IXmlSerializer xmlSerializer, IFileSystem fileSystem) + { + _appPaths = appPaths; + _xmlSerializer = xmlSerializer; + _fileSystem = fileSystem; + } + + public void Run() + { + //DumpProfiles(); + } + + private void DumpProfiles() + { + var list = new List + { + new SamsungSmartTvProfile(), + new Xbox360Profile(), + new XboxOneProfile(), + new SonyPs3Profile(), + new SonyBravia2010Profile(), + new SonyBravia2011Profile(), + new SonyBravia2012Profile(), + new SonyBravia2013Profile(), + new SonyBlurayPlayer2013Profile(), + new SonyBlurayPlayerProfile(), + new PanasonicVieraProfile(), + new WdtvLiveProfile(), + new DenonAvrProfile(), + new LinksysDMA2100Profile(), + new LgTvProfile(), + new Foobar2000Profile(), + new MediaMonkeyProfile(), + new Windows81Profile(), + //new WindowsMediaCenterProfile(), + new WindowsPhoneProfile(), + new AndroidProfile(), + new DirectTvProfile(), + new DishHopperJoeyProfile(), + new DefaultProfile(), + new PopcornHourProfile() + }; + + foreach (var item in list) + { + var path = Path.Combine(_appPaths.ProgramDataPath, _fileSystem.GetValidFilename(item.Name) + ".xml"); + + _xmlSerializer.SerializeToFile(item, path); + } + } + + public void Dispose() + { + } + } } \ No newline at end of file diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index 761ce52c88..bc6e4f32a6 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -75,6 +75,7 @@ + @@ -194,6 +195,9 @@ + + +