diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2016-10-07 11:08:13 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2016-10-07 11:08:13 -0400 |
| commit | 50e66869872579d2cbd8337c4b114cf68dff814a (patch) | |
| tree | e1ec36eb7bb83a70d5430348f7321140325d26ef | |
| parent | d22b7817a468be0dce6ab0891c8aaaeef2ea54ee (diff) | |
update live stream management
53 files changed, 484 insertions, 1075 deletions
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/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index a98637650..96d7874f0 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -197,6 +197,10 @@ <Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project> <Name>MediaBrowser.Model</Name> </ProjectReference> + <ProjectReference Include="..\MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj"> + <Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project> + <Name>MediaBrowser.Server.Implementations</Name> + </ProjectReference> </ItemGroup> <ItemGroup> <None Include="packages.config" /> diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index ebb3204a4..4c5abc996 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -116,6 +116,7 @@ namespace MediaBrowser.Api config.EnableCaseSensitiveItemIds = true; //config.EnableFolderView = true; config.SchemaVersion = 109; + config.EnableSimpleArtistDetection = true; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index be88c535e..90a22b217 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -116,6 +116,22 @@ namespace MediaBrowser.Controller.Entities public bool IsInMixedFolder { get; set; } [IgnoreDataMember] + protected virtual bool SupportsIsInMixedFolderDetection + { + get { return false; } + } + + public bool DetectIsInMixedFolder() + { + if (SupportsIsInMixedFolderDetection) + { + + } + + return IsInMixedFolder; + } + + [IgnoreDataMember] public virtual bool SupportsRemoteImageDownloading { get @@ -1116,7 +1132,7 @@ namespace MediaBrowser.Controller.Entities var hasThemeMedia = this as IHasThemeMedia; if (hasThemeMedia != null) { - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false); @@ -1266,7 +1282,15 @@ namespace MediaBrowser.Controller.Entities { var current = this; - return current.IsInMixedFolder == newItem.IsInMixedFolder; + if (!SupportsIsInMixedFolderDetection) + { + if (current.IsInMixedFolder != newItem.IsInMixedFolder) + { + return false; + } + } + + return true; } public void AfterMetadataRefresh() diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index 24910498f..a48b9f564 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -98,7 +98,7 @@ namespace MediaBrowser.Controller.Entities public override IEnumerable<string> GetDeletePaths() { - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { return new[] { System.IO.Path.GetDirectoryName(Path) }; } diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index a38b7394d..1ab0566e0 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -150,11 +150,7 @@ namespace MediaBrowser.Controller.Entities /// <value><c>true</c> if [supports local metadata]; otherwise, <c>false</c>.</value> bool SupportsLocalMetadata { get; } - /// <summary> - /// Gets a value indicating whether this instance is in mixed folder. - /// </summary> - /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value> - bool IsInMixedFolder { get; } + bool DetectIsInMixedFolder(); /// <summary> /// Gets a value indicating whether this instance is locked. diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index f0270497c..e1e336147 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.Entities.Movies // Must have a parent to have special features // In other words, it must be part of the Parent/Child tree - if (LocationType == LocationType.FileSystem && GetParent() != null && !IsInMixedFolder) + if (LocationType == LocationType.FileSystem && GetParent() != null && !DetectIsInMixedFolder()) { var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false); @@ -119,7 +119,7 @@ namespace MediaBrowser.Controller.Entities.Movies { var info = GetItemLookupInfo<MovieInfo>(); - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { info.Name = System.IO.Path.GetFileName(ContainingFolderPath); } @@ -145,7 +145,7 @@ namespace MediaBrowser.Controller.Entities.Movies else { // Try to get the year from the folder name - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath)); diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 8b749b7a5..9254802dd 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -37,6 +37,15 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + protected override bool SupportsIsInMixedFolderDetection + { + get + { + return true; + } + } + public override UnratedItem GetBlockUnratedType() { return UnratedItem.Music; @@ -65,7 +74,7 @@ namespace MediaBrowser.Controller.Entities else { // Try to get the year from the folder name - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath)); diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 7a987a68e..f68cd2c85 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities info.IsLocalTrailer = TrailerTypes.Contains(TrailerType.LocalTrailer); - if (!IsInMixedFolder && LocationType == LocationType.FileSystem) + if (!DetectIsInMixedFolder() && LocationType == LocationType.FileSystem) { info.Name = System.IO.Path.GetFileName(ContainingFolderPath); } @@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Entities else { // Try to get the year from the folder name - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath)); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 1406a05ce..c64cdf57d 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -480,7 +480,7 @@ namespace MediaBrowser.Controller.Entities public override IEnumerable<string> GetDeletePaths() { - if (!IsInMixedFolder) + if (!DetectIsInMixedFolder()) { return new[] { ContainingFolderPath }; } diff --git a/MediaBrowser.Controller/LiveTv/LiveStream.cs b/MediaBrowser.Controller/LiveTv/LiveStream.cs index 7d44fbd90..a5d432a54 100644 --- a/MediaBrowser.Controller/LiveTv/LiveStream.cs +++ b/MediaBrowser.Controller/LiveTv/LiveStream.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.LiveTv public ITunerHost TunerHost { get; set; } public string OriginalStreamId { get; set; } public bool EnableStreamSharing { get; set; } + public string UniqueId = Guid.NewGuid().ToString("N"); public LiveStream(MediaSourceInfo mediaSource) { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index e9d2054da..7c1114e22 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -287,9 +287,7 @@ <Compile Include="Providers\IHasItemChangeMonitor.cs" /> <Compile Include="Providers\IHasLookupInfo.cs" /> <Compile Include="Providers\IHasOrder.cs" /> - <Compile Include="Providers\IImageFileSaver.cs" /> <Compile Include="Providers\IImageProvider.cs" /> - <Compile Include="Providers\IImageSaver.cs" /> <Compile Include="Providers\ILocalImageFileProvider.cs" /> <Compile Include="Providers\ILocalMetadataProvider.cs" /> <Compile Include="Providers\ImageRefreshMode.cs" /> diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs index 8fdb1ce37..ca453840f 100644 --- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs @@ -12,14 +12,6 @@ namespace MediaBrowser.Controller.Net public interface IHttpResultFactory { /// <summary> - /// Throws the error. - /// </summary> - /// <param name="statusCode">The status code.</param> - /// <param name="errorMessage">The error message.</param> - /// <param name="responseHeaders">The response headers.</param> - void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null); - - /// <summary> /// Gets the result. /// </summary> /// <param name="content">The content.</param> diff --git a/MediaBrowser.Controller/Providers/IImageFileSaver.cs b/MediaBrowser.Controller/Providers/IImageFileSaver.cs deleted file mode 100644 index 3e11d8bf8..000000000 --- a/MediaBrowser.Controller/Providers/IImageFileSaver.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; - -namespace MediaBrowser.Controller.Providers -{ - public interface IImageFileSaver : IImageSaver - { - /// <summary> - /// Gets the save paths. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="type">The type.</param> - /// <param name="format">The format.</param> - /// <param name="index">The index.</param> - /// <returns>IEnumerable{System.String}.</returns> - IEnumerable<string> GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index); - } -}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/IImageSaver.cs b/MediaBrowser.Controller/Providers/IImageSaver.cs deleted file mode 100644 index 62017160f..000000000 --- a/MediaBrowser.Controller/Providers/IImageSaver.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MediaBrowser.Controller.Providers -{ - public interface IImageSaver - { - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - string Name { get; } - } -} diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 3eefa9647..d3e5685bb 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -95,15 +95,8 @@ namespace MediaBrowser.Controller.Providers /// <summary> /// Adds the metadata providers. /// </summary> - /// <param name="imageProviders">The image providers.</param> - /// <param name="metadataServices">The metadata services.</param> - /// <param name="metadataProviders">The metadata providers.</param> - /// <param name="savers">The savers.</param> - /// <param name="imageSavers">The image savers.</param> - /// <param name="externalIds">The external ids.</param> void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> savers, - IEnumerable<IImageSaver> imageSavers, IEnumerable<IExternalId> externalIds); /// <summary> diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs index 63cc48058..8de11b743 100644 --- a/MediaBrowser.Controller/Providers/ItemInfo.cs +++ b/MediaBrowser.Controller/Providers/ItemInfo.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Providers { Path = item.Path; ContainingFolderPath = item.ContainingFolderPath; - IsInMixedFolder = item.IsInMixedFolder; + IsInMixedFolder = item.DetectIsInMixedFolder(); var video = item as Video; if (video != null) diff --git a/MediaBrowser.Dlna/Eventing/EventManager.cs b/MediaBrowser.Dlna/Eventing/EventManager.cs index 68f012c3a..51c8d2d91 100644 --- a/MediaBrowser.Dlna/Eventing/EventManager.cs +++ b/MediaBrowser.Dlna/Eventing/EventManager.cs @@ -156,7 +156,6 @@ namespace MediaBrowser.Dlna.Eventing } catch (OperationCanceledException) { - throw; } catch { diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index fe61a7a46..ef9160b70 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -132,7 +132,7 @@ namespace MediaBrowser.LocalMetadata.Images } var imagePrefix = item.FileNameWithoutExtension + "-"; - var isInMixedFolder = item.IsInMixedFolder; + var isInMixedFolder = item.DetectIsInMixedFolder(); PopulatePrimaryImages(item, images, files, imagePrefix, isInMixedFolder); diff --git a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs index a90789a3e..5592c068c 100644 --- a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.LocalMetadata.Savers public static string GetGameSavePath(Game item) { - if (item.IsInMixedFolder) + if (item.DetectIsInMixedFolder()) { return Path.ChangeExtension(item.Path, ".xml"); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index 33e90743a..5d0f1f075 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public static string GetProbeSizeArgument(bool isDvd) { - return isDvd ? "-probesize 1G -analyzeduration 200M" : " -analyzeduration 2M"; + return isDvd ? "-probesize 1G -analyzeduration 200M" : ""; } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 25ad14fe8..5c3345008 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -426,8 +426,10 @@ namespace MediaBrowser.MediaEncoding.Encoder var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames); + var probeSizeArgument = GetProbeSizeArgument(inputFiles, request.Protocol); + return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters, - GetProbeSizeArgument(inputFiles, request.Protocol), request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken); + probeSizeArgument, request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken); } /// <summary> diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 1d2928f67..e7f8e6548 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -203,6 +203,7 @@ namespace MediaBrowser.Model.Configuration public string[] CodecsUsed { get; set; } public bool EnableChannelView { get; set; } public bool EnableExternalContentInSuggestions { get; set; } + public bool EnableSimpleArtistDetection { get; set; } public int ImageExtractionTimeoutMs { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs index f5e3624f0..eefd15808 100644 --- a/MediaBrowser.Model/Notifications/NotificationType.cs +++ b/MediaBrowser.Model/Notifications/NotificationType.cs @@ -16,7 +16,6 @@ namespace MediaBrowser.Model.Notifications PluginUpdateInstalled, PluginUninstalled, NewLibraryContent, - NewLibraryContentMultiple, ServerRestartRequired, TaskFailed, CameraImageUploaded, diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index c9b3f22c5..5203adc9d 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -371,7 +371,7 @@ namespace MediaBrowser.Providers.Manager return Path.Combine(seriesFolder, imageFilename); } - if (item.IsInMixedFolder) + if (item.DetectIsInMixedFolder()) { return GetSavePathForItemInMixedFolder(item, type, "landscape", extension); } @@ -447,7 +447,7 @@ namespace MediaBrowser.Providers.Manager path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension); } - else if (item.IsInMixedFolder) + else if (item.DetectIsInMixedFolder()) { path = GetSavePathForItemInMixedFolder(item, type, filename, extension); } @@ -514,7 +514,7 @@ namespace MediaBrowser.Providers.Manager if (imageIndex.Value == 0) { - if (item.IsInMixedFolder) + if (item.DetectIsInMixedFolder()) { return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) }; } @@ -540,7 +540,7 @@ namespace MediaBrowser.Providers.Manager var outputIndex = imageIndex.Value; - if (item.IsInMixedFolder) + if (item.DetectIsInMixedFolder()) { return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) }; } @@ -583,7 +583,7 @@ namespace MediaBrowser.Providers.Manager return new[] { Path.Combine(seasonFolder, imageFilename) }; } - if (item.IsInMixedFolder || item is MusicVideo) + if (item.DetectIsInMixedFolder() || item is MusicVideo) { return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) }; } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 7e28254b0..ae1d60eb9 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -58,7 +58,6 @@ namespace MediaBrowser.Providers.Manager private IMetadataService[] _metadataServices = { }; private IMetadataProvider[] _metadataProviders = { }; private IEnumerable<IMetadataSaver> _savers; - private IImageSaver[] _imageSavers; private readonly IServerApplicationPaths _appPaths; private readonly IJsonSerializer _json; @@ -91,21 +90,14 @@ namespace MediaBrowser.Providers.Manager /// <summary> /// Adds the metadata providers. /// </summary> - /// <param name="imageProviders">The image providers.</param> - /// <param name="metadataServices">The metadata services.</param> - /// <param name="metadataProviders">The metadata providers.</param> - /// <param name="metadataSavers">The metadata savers.</param> - /// <param name="imageSavers">The image savers.</param> - /// <param name="externalIds">The external ids.</param> public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers, - IEnumerable<IImageSaver> imageSavers, IEnumerable<IExternalId> externalIds) + IEnumerable<IExternalId> externalIds) { ImageProviders = imageProviders.ToArray(); _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); _metadataProviders = metadataProviders.ToArray(); - _imageSavers = imageSavers.ToArray(); _externalIds = externalIds.OrderBy(i => i.Name).ToArray(); _savers = metadataSavers.Where(i => diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index ca4f1b956..2572a4f58 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Providers.TV private readonly ILibraryManager _libraryManager; private readonly IMemoryStreamProvider _memoryStreamProvider; - public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager) + public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IMemoryStreamProvider memoryStreamProvider) { _zipClient = zipClient; _httpClient = httpClient; @@ -49,6 +49,7 @@ namespace MediaBrowser.Providers.TV _config = config; _logger = logger; _libraryManager = libraryManager; + _memoryStreamProvider = memoryStreamProvider; Current = this; } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs index e84b66c5a..f7fe707da 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs @@ -377,10 +377,10 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications DisposeLibraryUpdateTimer(); } - if (items.Count == 1) - { - var item = items.First(); + items = items.Take(10).ToList(); + foreach (var item in items) + { var notification = new NotificationRequest { NotificationType = NotificationType.NewLibraryContent.ToString() @@ -390,17 +390,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications await SendNotification(notification).ConfigureAwait(false); } - else - { - var notification = new NotificationRequest - { - NotificationType = NotificationType.NewLibraryContentMultiple.ToString() - }; - - notification.Variables["ItemCount"] = items.Count.ToString(CultureInfo.InvariantCulture); - - await SendNotification(notification).ConfigureAwait(false); - } } public static string GetItemName(BaseItem item) diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 7dc6fbb25..2ebeb0d44 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -94,12 +94,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer // The Markdown feature causes slow startup times (5 mins+) on cold boots for some users // Custom format allows images - HostConfig.Instance.EnableFeatures = Feature.Csv | Feature.Html | Feature.Json | Feature.Jsv | Feature.Metadata | Feature.Xml | Feature.CustomFormat; + HostConfig.Instance.EnableFeatures = Feature.Html | Feature.Json | Feature.CustomFormat; container.Adapter = _containerAdapter; Plugins.RemoveAll(x => x is NativeTypesFeature); - Plugins.Add(new SwaggerFeature()); + //Plugins.Add(new SwaggerFeature()); Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization")); //Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { @@ -546,8 +546,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } } - - throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo); + else + { + httpRes.Close(); + } } /// <summary> diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 04085d3c7..10d6f7493 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -683,29 +683,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } - /// <summary> - /// Gets the error result. - /// </summary> - /// <param name="statusCode">The status code.</param> - /// <param name="errorMessage">The error message.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <returns>System.Object.</returns> - public void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null) - { - var error = new HttpError - { - Status = statusCode, - ErrorCode = errorMessage - }; - - if (responseHeaders != null) - { - AddResponseHeaders(error, responseHeaders); - } - - throw error; - } - public object GetAsyncStreamWriter(IAsyncStreamSource streamSource) { return new AsyncStreamWriter(streamSource); diff --git a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs deleted file mode 100644 index cac2f8e09..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs +++ /dev/null @@ -1,240 +0,0 @@ -using MediaBrowser.Common.Events; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Logging; -using System; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType; -using WebSocketState = MediaBrowser.Model.Net.WebSocketState; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - /// <summary> - /// Class NativeWebSocket - /// </summary> - public class NativeWebSocket : IWebSocket - { - /// <summary> - /// The logger - /// </summary> - private readonly ILogger _logger; - - public event EventHandler<EventArgs> Closed; - - /// <summary> - /// Gets or sets the web socket. - /// </summary> - /// <value>The web socket.</value> - private System.Net.WebSockets.WebSocket WebSocket { get; set; } - - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - /// <summary> - /// Initializes a new instance of the <see cref="NativeWebSocket" /> class. - /// </summary> - /// <param name="socket">The socket.</param> - /// <param name="logger">The logger.</param> - /// <exception cref="System.ArgumentNullException">socket</exception> - public NativeWebSocket(WebSocket socket, ILogger logger) - { - if (socket == null) - { - throw new ArgumentNullException("socket"); - } - - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - - _logger = logger; - WebSocket = socket; - - Receive(); - } - - /// <summary> - /// Gets or sets the state. - /// </summary> - /// <value>The state.</value> - public WebSocketState State - { - get - { - WebSocketState commonState; - - if (!Enum.TryParse(WebSocket.State.ToString(), true, out commonState)) - { - _logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.State.ToString()); - } - - return commonState; - } - } - - /// <summary> - /// Receives this instance. - /// </summary> - private async void Receive() - { - while (true) - { - byte[] bytes; - - try - { - bytes = await ReceiveBytesAsync(_cancellationTokenSource.Token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - break; - } - catch (WebSocketException ex) - { - _logger.ErrorException("Error receiving web socket message", ex); - - break; - } - - if (bytes == null) - { - // Connection closed - EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger); - break; - } - - if (OnReceiveBytes != null) - { - OnReceiveBytes(bytes); - } - } - } - - /// <summary> - /// Receives the async. - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{WebSocketMessageInfo}.</returns> - /// <exception cref="System.Net.WebSockets.WebSocketException">Connection closed</exception> - private async Task<byte[]> ReceiveBytesAsync(CancellationToken cancellationToken) - { - var bytes = new byte[4096]; - var buffer = new ArraySegment<byte>(bytes); - - var result = await WebSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); - - if (result.CloseStatus.HasValue) - { - _logger.Info("Web socket connection closed by client. Reason: {0}", result.CloseStatus.Value); - return null; - } - - return buffer.Array; - } - - /// <summary> - /// Sends the async. - /// </summary> - /// <param name="bytes">The bytes.</param> - /// <param name="type">The type.</param> - /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken) - { - System.Net.WebSockets.WebSocketMessageType nativeType; - - if (!Enum.TryParse(type.ToString(), true, out nativeType)) - { - _logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString()); - } - - var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); - - return WebSocket.SendAsync(new ArraySegment<byte>(bytes), nativeType, true, linkedTokenSource.Token); - } - - public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) - { - var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); - - return WebSocket.SendAsync(new ArraySegment<byte>(bytes), System.Net.WebSockets.WebSocketMessageType.Binary, true, linkedTokenSource.Token); - } - - public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) - { - var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); - - var bytes = Encoding.UTF8.GetBytes(text); - - return WebSocket.SendAsync(new ArraySegment<byte>(bytes), System.Net.WebSockets.WebSocketMessageType.Text, true, linkedTokenSource.Token); - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - _cancellationTokenSource.Cancel(); - - WebSocket.Dispose(); - } - } - - /// <summary> - /// Gets or sets the receive action. - /// </summary> - /// <value>The receive action.</value> - public Action<byte[]> OnReceiveBytes { get; set; } - - /// <summary> - /// Gets or sets the on receive. - /// </summary> - /// <value>The on receive.</value> - public Action<string> OnReceive { get; set; } - - /// <summary> - /// The _supports native web socket - /// </summary> - private static bool? _supportsNativeWebSocket; - - /// <summary> - /// Gets a value indicating whether [supports web sockets]. - /// </summary> - /// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value> - public static bool IsSupported - { - get - { - if (!_supportsNativeWebSocket.HasValue) - { - try - { - new ClientWebSocket(); - - _supportsNativeWebSocket = true; - } - catch (PlatformNotSupportedException) - { - _supportsNativeWebSocket = false; - } - } - - return _supportsNativeWebSocket.Value; - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index 488c630fe..4b94095f5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -191,15 +191,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } } - catch (IOException) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error in range request writer", ex); - throw; - } finally { if (OnComplete != null) @@ -251,15 +242,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } } - catch (IOException ex) - { - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error in range request writer", ex); - throw; - } finally { if (OnComplete != null) diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index e08be8bd1..a58645ec5 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -81,20 +81,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp public void Write(string text) { - try - { - var bOutput = System.Text.Encoding.UTF8.GetBytes(text); - response.ContentLength64 = bOutput.Length; + var bOutput = System.Text.Encoding.UTF8.GetBytes(text); + response.ContentLength64 = bOutput.Length; - var outputStream = response.OutputStream; - outputStream.Write(bOutput, 0, bOutput.Length); - Close(); - } - catch (Exception ex) - { - _logger.ErrorException("Could not WriteTextToResponse: " + ex.Message, ex); - throw; - } + var outputStream = response.OutputStream; + outputStream.Write(bOutput, 0, bOutput.Length); + Close(); } public void Close() diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index b2bddc70d..f7661f55b 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2463,7 +2463,7 @@ namespace MediaBrowser.Server.Implementations.Library public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { - var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory) + var files = owner.DetectIsInMixedFolder() ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory) .Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => _fileSystem.GetFiles(i.FullName, false)) .ToList(); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index e819af06f..dadcff877 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -7,6 +7,7 @@ using System; using System.IO; using System.Linq; using CommonIO; +using MediaBrowser.Controller.Configuration; namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio { @@ -18,12 +19,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _config; - public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager) + public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config) { _logger = logger; _fileSystem = fileSystem; _libraryManager = libraryManager; + _config = config; } /// <summary> @@ -67,6 +70,19 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio return null; } + if (args.IsDirectory) + { + if (args.ContainsFileSystemEntryByName("artist.nfo")) + { + return new MusicArtist(); + } + } + + if (_config.Configuration.EnableSimpleArtistDetection) + { + return null; + } + var directoryService = args.DirectoryService; var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index ee9533d2a..c3d5f3441 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -48,12 +48,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies string collectionType, IDirectoryService directoryService) { - if (parent != null && parent.Path != null && parent.Path.IndexOf("disney", StringComparison.OrdinalIgnoreCase) != -1) - { - var b = true; - var a = b; - } - var result = ResolveMultipleInternal(parent, files, collectionType, directoryService); if (result != null) @@ -213,26 +207,22 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies // Find movies with their own folders if (args.IsDirectory) { - var files = args.FileSystemChildren - .Where(i => !LibraryManager.IgnoreFile(i, args.Parent)) - .ToList(); - if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args.Path, args.Parent, files, args.DirectoryService, collectionType); + return null; } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType); + return null; } if (string.IsNullOrEmpty(collectionType)) { - // Owned items should just use the plain video type + // Owned items will be caught by the plain video resolver if (args.Parent == null) { - return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType); + return null; } if (args.HasParent<Series>()) @@ -240,11 +230,21 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies return null; } - return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType); + { + var files = args.FileSystemChildren + .Where(i => !LibraryManager.IgnoreFile(i, args.Parent)) + .ToList(); + + return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType); + } } if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) { + var files = args.FileSystemChildren + .Where(i => !LibraryManager.IgnoreFile(i, args.Parent)) + .ToList(); + return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType); } diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs index 307cf4cd2..ec8ac1a42 100644 --- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs @@ -61,16 +61,7 @@ namespace MediaBrowser.Server.Implementations.Library foreach (var key in keys) { - try - { - await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error saving user data", ex); - - throw; - } + await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false); } var cacheKey = GetCacheKey(userId, item.Id); @@ -107,18 +98,7 @@ namespace MediaBrowser.Server.Implementations.Library cancellationToken.ThrowIfCancellationRequested(); - try - { - await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false); - - } - catch (Exception ex) - { - _logger.ErrorException("Error saving user data", ex); - - throw; - } - + await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 5b99849cd..23066149a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -841,21 +841,37 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return new Tuple<MediaSourceInfo, IDirectStreamProvider>(result.Item2, result.Item1 as IDirectStreamProvider); } - private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, int consumerId, bool enableStreamSharing) + private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing) { var json = _jsonSerializer.SerializeToString(mediaSource); mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json); mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id; - if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing) + //if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing) + //{ + // var ticks = (DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value).Ticks - TimeSpan.FromSeconds(10).Ticks; + // ticks = Math.Max(0, ticks); + // mediaSource.Path += "?t=" + ticks.ToString(CultureInfo.InvariantCulture) + "&s=" + mediaSource.DateLiveStreamOpened.Value.Ticks.ToString(CultureInfo.InvariantCulture); + //} + + return mediaSource; + } + + public async Task<LiveStream> GetLiveStream(string uniqueId) + { + await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false); + + try + { + return _liveStreams.Values + .FirstOrDefault(i => string.Equals(i.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase)); + } + finally { - var ticks = (DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value).Ticks - TimeSpan.FromSeconds(10).Ticks; - ticks = Math.Max(0, ticks); - mediaSource.Path += "?t=" + ticks.ToString(CultureInfo.InvariantCulture) + "&s=" + mediaSource.DateLiveStreamOpened.Value.Ticks.ToString(CultureInfo.InvariantCulture); + _liveStreamsSemaphore.Release(); } - return mediaSource; } private async Task<Tuple<LiveStream, MediaSourceInfo, ITunerHost>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) @@ -872,7 +888,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount); - var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.ConsumerCount - 1, result.EnableStreamSharing); + var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing); _liveStreamsSemaphore.Release(); return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, result.TunerHost); } @@ -885,7 +901,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); - var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, 0, result.EnableStreamSharing); + var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing); _liveStreams[openedMediaSource.Id] = result; @@ -1542,6 +1558,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (timer.IsKids) { AddGenre(timer.Genres, "Kids"); + AddGenre(timer.Genres, "Children"); } if (timer.IsNews) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 72287f32d..fd9e75b6f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -223,8 +223,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv return result.Items.FirstOrDefault(); } - private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken) { var info = await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false); @@ -284,80 +282,65 @@ namespace MediaBrowser.Server.Implementations.LiveTv private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken) { - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase)) { mediaSourceId = null; } - try + MediaSourceInfo info; + bool isVideo; + ILiveTvService service; + IDirectStreamProvider directStreamProvider = null; + + if (isChannel) { - MediaSourceInfo info; - bool isVideo; - ILiveTvService service; - IDirectStreamProvider directStreamProvider = null; + var channel = GetInternalChannel(id); + isVideo = channel.ChannelType == ChannelType.TV; + service = GetService(channel); + _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); - if (isChannel) + var supportsManagedStream = service as ISupportsDirectStreamProvider; + if (supportsManagedStream != null) { - var channel = GetInternalChannel(id); - isVideo = channel.ChannelType == ChannelType.TV; - service = GetService(channel); - _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); - - var supportsManagedStream = service as ISupportsDirectStreamProvider; - if (supportsManagedStream != null) - { - var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); - info = streamInfo.Item1; - directStreamProvider = streamInfo.Item2; - } - else - { - info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); - } - info.RequiresClosing = true; - - if (info.RequiresClosing) - { - var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; - - info.LiveStreamId = idPrefix + info.Id; - } + var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); + info = streamInfo.Item1; + directStreamProvider = streamInfo.Item2; } else { - var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false); - isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase); - service = GetService(recording); - - _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId); - info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false); - info.RequiresClosing = true; - - if (info.RequiresClosing) - { - var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; - - info.LiveStreamId = idPrefix + info.Id; - } + info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); } + info.RequiresClosing = true; - _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); - Normalize(info, service, isVideo); + if (info.RequiresClosing) + { + var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; - return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider); + info.LiveStreamId = idPrefix + info.Id; + } } - catch (Exception ex) + else { - _logger.ErrorException("Error getting channel stream", ex); + var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false); + isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase); + service = GetService(recording); - throw; - } - finally - { - _liveStreamSemaphore.Release(); + _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId); + info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false); + info.RequiresClosing = true; + + if (info.RequiresClosing) + { + var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; + + info.LiveStreamId = idPrefix + info.Id; + } } + + _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); + Normalize(info, service, isVideo); + + return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider); } private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo) @@ -2560,35 +2543,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task CloseLiveStream(string id) { - await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false); - - try - { - var parts = id.Split(new[] { '_' }, 2); - - var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase)); + var parts = id.Split(new[] { '_' }, 2); - if (service == null) - { - throw new ArgumentException("Service not found."); - } + var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase)); - id = parts[1]; + if (service == null) + { + throw new ArgumentException("Service not found."); + } - _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); + id = parts[1]; - await service.CloseLiveStream(id, CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error closing live stream", ex); + _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); - throw; - } - finally - { - _liveStreamSemaphore.Release(); - } + await service.CloseLiveStream(id, CancellationToken.None).ConfigureAwait(false); } public GuideInfo GetGuideInfo() diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index d77d0f33e..ff9b2a143 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -9,6 +9,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -139,7 +140,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv try { - await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false); + if (stream.MediaStreams.Any(i => i.Index != -1)) + { + await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false); + } + else + { + await AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false); + } } catch (Exception ex) { @@ -208,10 +216,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } - private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) + private async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) { var originalRuntime = mediaSource.RunTimeTicks; + var now = DateTime.UtcNow; + var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest { InputPath = mediaSource.Path, @@ -221,6 +231,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv }, cancellationToken).ConfigureAwait(false); + _logger.Info("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); + mediaSource.Bitrate = info.Bitrate; mediaSource.Container = info.Container; mediaSource.Formats = info.Formats; @@ -272,6 +284,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv videoStream.BitRate = 1000000; } } + + // This is coming up false and preventing stream copy + videoStream.IsAVC = null; } // Try to estimate this diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 84ba15e49..0fe74798f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -233,25 +233,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken); - private async Task AddMediaInfo(LiveStream stream, bool isAudio, CancellationToken cancellationToken) - { - //await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - //try - //{ - // await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false); - - // // Leave the resource locked. it will be released upstream - //} - //catch (Exception) - //{ - // // Release the resource if there's some kind of failure. - // resourcePool.Release(); - - // throw; - //} - } - protected abstract bool IsValidChannelId(string channelId); protected LiveTvOptions GetConfiguration() diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 101b9ba84..365f784a7 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -105,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun }); } - private Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>(); + private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>(); private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken) { lock (_modelCache) @@ -387,6 +387,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun } id += "_" + url.GetMD5().ToString("N"); + var enableLocalBuffer = EnableLocalBuffer(); + var mediaSource = new MediaSourceInfo { Path = url, @@ -420,8 +422,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun BufferMs = 0, Container = "ts", Id = id, - SupportsDirectPlay = false, - SupportsDirectStream = true, + SupportsDirectPlay = !enableLocalBuffer, + SupportsDirectStream = enableLocalBuffer, SupportsTranscoding = true, IsInfiniteStream = true }; @@ -488,6 +490,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); } + private bool EnableLocalBuffer() + { + return true; + } + protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) { var profile = streamId.Split('_')[0]; @@ -502,25 +509,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun var mediaSource = await GetMediaSource(info, hdhrId, profile).ConfigureAwait(false); - var liveStream = new HdHomerunLiveStream(mediaSource, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); - if (info.AllowHWTranscoding) + if (EnableLocalBuffer()) { - var model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false); - - if ((model ?? string.Empty).IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1) + var liveStream = new HdHomerunLiveStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + if (info.AllowHWTranscoding) { - liveStream.EnableStreamSharing = !info.AllowHWTranscoding; + var model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false); + + if ((model ?? string.Empty).IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1) + { + liveStream.EnableStreamSharing = !info.AllowHWTranscoding; + } + else + { + liveStream.EnableStreamSharing = true; + } } else { liveStream.EnableStreamSharing = true; } + return liveStream; } else { - liveStream.EnableStreamSharing = true; + var liveStream = new LiveStream(mediaSource); + liveStream.EnableStreamSharing = false; + return liveStream; } - return liveStream; } public async Task Validate(TunerHostInfo info) diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs index dd7726be0..60222415c 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -13,6 +13,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Server.Implementations.LiveTv.EmbyTV; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Common.Extensions; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -26,8 +27,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>(); + private readonly MulticastStream _multicastStream; - public HdHomerunLiveStream(MediaSourceInfo mediaSource, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost) + + public HdHomerunLiveStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost) : base(mediaSource) { _fileSystem = fileSystem; @@ -35,6 +38,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun _logger = logger; _appPaths = appPaths; _appHost = appHost; + OriginalStreamId = originalStreamId; + _multicastStream = new MulticastStream(_logger); } protected override async Task OpenInternal(CancellationToken openCancellationToken) @@ -44,22 +49,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun var mediaSource = OriginalMediaSource; var url = mediaSource.Path; - var tempFile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts"); - Directory.CreateDirectory(Path.GetDirectoryName(tempFile)); - - _logger.Info("Opening HDHR Live stream from {0} to {1}", url, tempFile); - var output = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read); + _logger.Info("Opening HDHR Live stream from {0}", url); var taskCompletionSource = new TaskCompletionSource<bool>(); - StartStreamingToTempFile(output, tempFile, url, taskCompletionSource, _liveStreamCancellationTokenSource.Token); + StartStreaming(url, taskCompletionSource, _liveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Path = tempFile; //OpenedMediaSource.ReadAtNativeFramerate = true; - OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + Path.GetFileNameWithoutExtension(tempFile) + "/stream.ts"; + OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; OpenedMediaSource.Protocol = MediaProtocol.Http; OpenedMediaSource.SupportsDirectPlay = false; OpenedMediaSource.SupportsDirectStream = true; @@ -78,178 +79,67 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun return _liveStreamTaskCompletionSource.Task; } - private async Task StartStreamingToTempFile(Stream outputStream, string tempFilePath, string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) + private async Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) { await Task.Run(async () => { - using (outputStream) - { - var isFirstAttempt = true; + var isFirstAttempt = true; - while (!cancellationToken.IsCancellationRequested) + while (!cancellationToken.IsCancellationRequested) + { + try { - try + using (var response = await _httpClient.SendAsync(new HttpRequestOptions { - using (var response = await _httpClient.SendAsync(new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - BufferContent = false + Url = url, + CancellationToken = cancellationToken, + BufferContent = false + + }, "GET").ConfigureAwait(false)) + { + _logger.Info("Opened HDHR stream from {0}", url); - }, "GET").ConfigureAwait(false)) + if (!cancellationToken.IsCancellationRequested) { - _logger.Info("Opened HDHR stream from {0}", url); + _logger.Info("Beginning multicastStream.CopyUntilCancelled"); - if (!cancellationToken.IsCancellationRequested) + Action onStarted = null; + if (isFirstAttempt) { - _logger.Info("Beginning DirectRecorder.CopyUntilCancelled"); - - Action onStarted = null; - if (isFirstAttempt) - { - onStarted = () => ResolveWhenExists(openTaskCompletionSource, tempFilePath, cancellationToken); - } - await CopyUntilCancelled(response.Content, outputStream, onStarted, cancellationToken).ConfigureAwait(false); + onStarted = () => openTaskCompletionSource.TrySetResult(true); } - } - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - if (isFirstAttempt) - { - _logger.ErrorException("Error opening live stream:", ex); - openTaskCompletionSource.TrySetException(ex); - break; - } - _logger.ErrorException("Error copying live stream, will reopen", ex); + await _multicastStream.CopyUntilCancelled(response.Content, onStarted, cancellationToken).ConfigureAwait(false); + } } - - isFirstAttempt = false; } - } - - _liveStreamTaskCompletionSource.TrySetResult(true); - - DeleteTempFile(tempFilePath); - - }).ConfigureAwait(false); - } - - private readonly List<Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>> _additionalStreams = new List<Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>>(); - - public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) - { - var taskCompletionSource = new TaskCompletionSource<bool>(); - _additionalStreams.Add(new Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>(stream, cancellationToken, taskCompletionSource)); - - return taskCompletionSource.Task; - } - - private void PopAdditionalStream(Tuple<Stream, CancellationToken, TaskCompletionSource<bool>> stream, Exception exception) - { - if (_additionalStreams.Remove(stream)) - { - stream.Item3.TrySetException(exception); - } - } - - private const int BufferSize = 81920; - private async Task CopyUntilCancelled(Stream source, Stream target, Action onStarted, CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, onStarted, cancellationToken).ConfigureAwait(false); - - onStarted = null; - - //var position = fs.Position; - //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) - { - await Task.Delay(100).ConfigureAwait(false); - } - } - } - - private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, Action onStarted, CancellationToken cancellationToken) - { - byte[] buffer = new byte[bufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) - { - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); - - var additionalStreams = _additionalStreams.ToList(); - foreach (var additionalStream in additionalStreams) - { - cancellationToken.ThrowIfCancellationRequested(); - - try + catch (OperationCanceledException) { - await additionalStream.Item1.WriteAsync(buffer, 0, bytesRead, additionalStream.Item2).ConfigureAwait(false); + break; } catch (Exception ex) { - _logger.ErrorException("Error writing HDHR data to stream", ex); + if (isFirstAttempt) + { + _logger.ErrorException("Error opening live stream:", ex); + openTaskCompletionSource.TrySetException(ex); + break; + } - PopAdditionalStream(additionalStream, ex); + _logger.ErrorException("Error copying live stream, will reopen", ex); } - } - totalBytesRead += bytesRead; - - if (onStarted != null) - { - onStarted(); + isFirstAttempt = false; } - onStarted = null; - } - return totalBytesRead; - } - - private async void ResolveWhenExists(TaskCompletionSource<bool> taskCompletionSource, string file, CancellationToken cancellationToken) - { - while (!File.Exists(file) && !cancellationToken.IsCancellationRequested) - { - await Task.Delay(50).ConfigureAwait(false); - } + _liveStreamTaskCompletionSource.TrySetResult(true); - taskCompletionSource.TrySetResult(true); + }).ConfigureAwait(false); } - private async void DeleteTempFile(string path) + public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) { - for (var i = 0; i < 10; i++) - { - try - { - File.Delete(path); - return; - } - catch (FileNotFoundException) - { - return; - } - catch (DirectoryNotFoundException) - { - return; - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting temp file {0}", ex, path); - } - - await Task.Delay(1000).ConfigureAwait(false); - } + return _multicastStream.CopyToAsync(stream); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs new file mode 100644 index 000000000..8ff3fd6c1 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts +{ + public class MulticastStream + { + private readonly List<QueueStream> _outputStreams = new List<QueueStream>(); + private const int BufferSize = 81920; + private CancellationToken _cancellationToken; + private readonly ILogger _logger; + + public MulticastStream(ILogger logger) + { + _logger = logger; + } + + public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + + while (!cancellationToken.IsCancellationRequested) + { + byte[] buffer = new byte[BufferSize]; + + var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + + if (bytesRead > 0) + { + byte[] copy = new byte[bytesRead]; + Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead); + + List<QueueStream> streams = null; + + lock (_outputStreams) + { + streams = _outputStreams.ToList(); + } + + foreach (var stream in streams) + { + stream.Queue(copy); + } + + if (onStarted != null) + { + var onStartedCopy = onStarted; + onStarted = null; + Task.Run(onStartedCopy); + } + } + + else + { + await Task.Delay(100).ConfigureAwait(false); + } + } + } + + public Task CopyToAsync(Stream stream) + { + var result = new QueueStream(stream, _logger) + { + OnFinished = OnFinished + }; + + lock (_outputStreams) + { + _outputStreams.Add(result); + } + + result.Start(_cancellationToken); + + return result.TaskCompletion.Task; + } + + public void RemoveOutputStream(QueueStream stream) + { + lock (_outputStreams) + { + _outputStreams.Remove(stream); + } + } + + private void OnFinished(QueueStream queueStream) + { + RemoveOutputStream(queueStream); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs new file mode 100644 index 000000000..7bc53fa24 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts +{ + public class QueueStream + { + private readonly Stream _outputStream; + private readonly ConcurrentQueue<byte[]> _queue = new ConcurrentQueue<byte[]>(); + private CancellationToken _cancellationToken; + public TaskCompletionSource<bool> TaskCompletion { get; private set; } + + public Action<QueueStream> OnFinished { get; set; } + private readonly ILogger _logger; + + public QueueStream(Stream outputStream, ILogger logger) + { + _outputStream = outputStream; + _logger = logger; + TaskCompletion = new TaskCompletionSource<bool>(); + } + + public void Queue(byte[] bytes) + { + _queue.Enqueue(bytes); + } + + public void Start(CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + Task.Run(StartInternal); + } + + private byte[] Dequeue() + { + byte[] bytes; + if (_queue.TryDequeue(out bytes)) + { + return bytes; + } + + return null; + } + + private async Task StartInternal() + { + var cancellationToken = _cancellationToken; + + try + { + while (!cancellationToken.IsCancellationRequested) + { + var bytes = Dequeue(); + if (bytes != null) + { + await _outputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + } + else + { + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + } + + TaskCompletion.TrySetResult(true); + _logger.Debug("QueueStream complete"); + } + catch (OperationCanceledException) + { + _logger.Debug("QueueStream cancelled"); + TaskCompletion.TrySetCanceled(); + } + catch (Exception ex) + { + _logger.ErrorException("Error in QueueStream", ex); + TaskCompletion.TrySetException(ex); + } + finally + { + if (OnFinished != null) + { + OnFinished(this); + } + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Localization/Core/en-US.json b/MediaBrowser.Server.Implementations/Localization/Core/en-US.json index 5e2f98c09..bc0dc236d 100644 --- a/MediaBrowser.Server.Implementations/Localization/Core/en-US.json +++ b/MediaBrowser.Server.Implementations/Localization/Core/en-US.json @@ -47,7 +47,6 @@ "NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)", "NotificationOptionCameraImageUploaded": "Camera image uploaded", "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionServerRestartRequired": "Server restart required", diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 12691a69b..78b04aaa5 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -161,7 +161,6 @@ <Compile Include="HttpServer\HttpListenerHost.cs" /> <Compile Include="HttpServer\HttpResultFactory.cs" /> <Compile Include="HttpServer\LoggerUtils.cs" /> - <Compile Include="HttpServer\NativeWebSocket.cs" /> <Compile Include="HttpServer\RangeRequestWriter.cs" /> <Compile Include="HttpServer\ResponseFilter.cs" /> <Compile Include="HttpServer\Security\AuthService.cs" /> @@ -247,6 +246,8 @@ <Compile Include="LiveTv\ProgramImageProvider.cs" /> <Compile Include="LiveTv\RecordingImageProvider.cs" /> <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" /> + <Compile Include="LiveTv\TunerHosts\MulticastStream.cs" /> + <Compile Include="LiveTv\TunerHosts\QueueStream.cs" /> <Compile Include="LiveTv\TunerHosts\SatIp\ChannelScan.cs" /> <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\ReportBlock.cs" /> <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpAppPacket.cs" /> diff --git a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs index 98d3672fa..8ca667739 100644 --- a/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs +++ b/MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs @@ -91,13 +91,6 @@ namespace MediaBrowser.Server.Implementations.Notifications new NotificationTypeInfo { - Type = NotificationType.NewLibraryContentMultiple.ToString(), - DefaultTitle = "{ItemCount} new items have been added to your media library.", - Variables = new List<string>{"ItemCount"} - }, - - new NotificationTypeInfo - { Type = NotificationType.AudioPlayback.ToString(), DefaultTitle = "{UserName} is playing {ItemName} on {DeviceName}.", Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"} diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index e8c1ce3fb..8a8d0b749 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -128,7 +128,7 @@ namespace MediaBrowser.Server.Implementations.Persistence var cacheSize = _config.Configuration.SqliteCacheSize; if (cacheSize <= 0) { - cacheSize = Math.Min(Environment.ProcessorCount * 50000, 200000); + cacheSize = Math.Min(Environment.ProcessorCount * 50000, 100000); } var connection = await DbConnector.Connect(DbFilePath, false, false, 0 - cacheSize).ConfigureAwait(false); @@ -2656,6 +2656,11 @@ namespace MediaBrowser.Server.Implementations.Persistence } } + if (query.SimilarTo != null) + { + whereClauses.Add("SimilarityScore > 0"); + } + if (query.IsFolder.HasValue) { whereClauses.Add("IsFolder=@IsFolder"); diff --git a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs index af0699834..60b04cf82 100644 --- a/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs +++ b/MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager public class WebSocketConnection : IWebSocketConnection { public event EventHandler<EventArgs> Closed; - + /// <summary> /// The _socket /// </summary> @@ -37,11 +37,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); /// <summary> - /// The _send semaphore - /// </summary> - private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1, 1); - - /// <summary> /// The logger /// </summary> private readonly ILogger _logger; @@ -210,7 +205,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager _logger.ErrorException("Error processing web socket message", ex); } } - + /// <summary> /// Sends a message asynchronously. /// </summary> @@ -237,7 +232,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager /// <param name="buffer">The buffer.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - public async Task SendAsync(byte[] buffer, CancellationToken cancellationToken) + public Task SendAsync(byte[] buffer, CancellationToken cancellationToken) { if (buffer == null) { @@ -246,33 +241,10 @@ namespace MediaBrowser.Server.Implementations.ServerManager cancellationToken.ThrowIfCancellationRequested(); - // Per msdn docs, attempting to send simultaneous messages will result in one failing. - // This should help us workaround that and ensure all messages get sent - await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - await _socket.SendAsync(buffer, true, cancellationToken); - } - catch (OperationCanceledException) - { - _logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint); - - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint); - - throw; - } - finally - { - _sendSemaphore.Release(); - } + return _socket.SendAsync(buffer, true, cancellationToken); } - public async Task SendAsync(string text, CancellationToken cancellationToken) + public Task SendAsync(string text, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(text)) { @@ -281,30 +253,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager cancellationToken.ThrowIfCancellationRequested(); - // Per msdn docs, attempting to send simultaneous messages will result in one failing. - // This should help us workaround that and ensure all messages get sent - await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - await _socket.SendAsync(text, true, cancellationToken); - } - catch (OperationCanceledException) - { - _logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint); - - throw; - } - catch (Exception ex) - { - _logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint); - - throw; - } - finally - { - _sendSemaphore.Release(); - } + return _socket.SendAsync(text, true, cancellationToken); } /// <summary> diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 18b340709..0ea50b39e 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -677,19 +677,11 @@ namespace MediaBrowser.Server.Startup.Common /// <returns>Task{IUserRepository}.</returns> private async Task<IUserRepository> GetUserRepository() { - try - { - var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer, NativeApp.GetDbConnector(), MemoryStreamProvider); + var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer, NativeApp.GetDbConnector(), MemoryStreamProvider); - await repo.Initialize().ConfigureAwait(false); + await repo.Initialize().ConfigureAwait(false); - return repo; - } - catch (Exception ex) - { - Logger.ErrorException("Error opening user db", ex); - throw; - } + return repo; } /// <summary> @@ -818,7 +810,6 @@ namespace MediaBrowser.Server.Startup.Common GetExports<IMetadataService>(), GetExports<IMetadataProvider>(), GetExports<IMetadataSaver>(), - GetExports<IImageSaver>(), GetExports<IExternalId>()); ImageProcessor.AddParts(GetExports<IImageEnhancer>()); diff --git a/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs b/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs deleted file mode 100644 index 01eeb5f3b..000000000 --- a/MediaBrowser.XbmcMetadata/Images/XbmcImageSaver.cs +++ /dev/null @@ -1,272 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; - -namespace MediaBrowser.XbmcMetadata.Images -{ - public class XbmcImageSaver : IImageFileSaver - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public IEnumerable<string> GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index) - { - var season = item as Season; - - if (!SupportsItem(item, type, season)) - { - return new string[] { }; - } - - var extension = "." + format.ToString().ToLower(); - - // Backdrop paths - if (type == ImageType.Backdrop) - { - if (index == 0) - { - if (item.IsInMixedFolder) - { - return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) }; - } - - if (season != null && season.IndexNumber.HasValue) - { - var seriesFolder = season.SeriesPath; - - var seasonMarker = season.IndexNumber.Value == 0 - ? "-specials" - : season.IndexNumber.Value.ToString("00", _usCulture); - - var imageFilename = "season" + seasonMarker + "-fanart" + extension; - - return new[] { Path.Combine(seriesFolder, imageFilename) }; - } - - return new[] - { - Path.Combine(item.ContainingFolderPath, "fanart" + extension) - }; - } - - if (item.IsInMixedFolder) - { - return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + index.ToString(_usCulture), extension) }; - } - - var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", index); - - return new[] - { - Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension), - Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + index.ToString(_usCulture) + extension) - }; - } - - if (type == ImageType.Primary) - { - if (season != null && season.IndexNumber.HasValue) - { - var seriesFolder = season.SeriesPath; - - var seasonMarker = season.IndexNumber.Value == 0 - ? "-specials" - : season.IndexNumber.Value.ToString("00", _usCulture); - - var imageFilename = "season" + seasonMarker + "-poster" + extension; - - return new[] { Path.Combine(seriesFolder, imageFilename) }; - } - - if (item is Episode) - { - var seasonFolder = Path.GetDirectoryName(item.Path); - - var imageFilename = Path.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension; - - return new[] { Path.Combine(seasonFolder, imageFilename) }; - } - - if (item.IsInMixedFolder || item is MusicVideo) - { - return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) }; - } - - if (item is MusicAlbum || item is MusicArtist) - { - return new[] { Path.Combine(item.ContainingFolderPath, "folder" + extension) }; - } - - return new[] { Path.Combine(item.ContainingFolderPath, "poster" + extension) }; - } - - if (type == ImageType.Banner) - { - if (season != null && season.IndexNumber.HasValue) - { - var seriesFolder = season.SeriesPath; - - var seasonMarker = season.IndexNumber.Value == 0 - ? "-specials" - : season.IndexNumber.Value.ToString("00", _usCulture); - - var imageFilename = "season" + seasonMarker + "-banner" + extension; - - return new[] { Path.Combine(seriesFolder, imageFilename) }; - } - } - - if (type == ImageType.Thumb) - { - if (season != null && season.IndexNumber.HasValue) - { - var seriesFolder = season.SeriesPath; - - var seasonMarker = season.IndexNumber.Value == 0 - ? "-specials" - : season.IndexNumber.Value.ToString("00", _usCulture); - - var imageFilename = "season" + seasonMarker + "-landscape" + extension; - - return new[] { Path.Combine(seriesFolder, imageFilename) }; - } - - if (item.IsInMixedFolder) - { - return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) }; - } - - return new[] { Path.Combine(item.ContainingFolderPath, "landscape" + extension) }; - } - - return GetStandardSavePaths(item, type, index, extension); - } - - private IEnumerable<string> GetStandardSavePaths(IHasImages item, ImageType type, int imageIndex, string extension) - { - string filename; - - switch (type) - { - case ImageType.Art: - filename = "clearart"; - break; - case ImageType.BoxRear: - filename = "back"; - break; - case ImageType.Disc: - filename = item is MusicAlbum ? "cdart" : "disc"; - break; - case ImageType.Screenshot: - filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex); - break; - default: - filename = type.ToString().ToLower(); - break; - } - - string path = null; - - if (item.IsInMixedFolder) - { - path = GetSavePathForItemInMixedFolder(item, type, filename, extension); - } - - if (string.IsNullOrEmpty(path)) - { - path = Path.Combine(item.ContainingFolderPath, filename + extension); - } - - if (string.IsNullOrEmpty(path)) - { - return new string[] { }; - } - - return new[] { path }; - } - - - private string GetSavePathForItemInMixedFolder(IHasImages item, ImageType type, string imageFilename, string extension) - { - if (type == ImageType.Primary) - { - imageFilename = "poster"; - } - var folder = Path.GetDirectoryName(item.Path); - - return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension); - } - - private bool SupportsItem(IHasImages item, ImageType type, Season season) - { - if (item.IsOwnedItem || item is Audio || item is User) - { - return false; - } - - if (type != ImageType.Primary && item is Episode) - { - return false; - } - - if (!item.SupportsLocalMetadata) - { - return false; - } - - var locationType = item.LocationType; - if (locationType == LocationType.Remote || locationType == LocationType.Virtual) - { - var allowSaving = false; - - // If season is virtual under a physical series, save locally if using compatible convention - if (season != null) - { - var series = season.Series; - - if (series != null && series.SupportsLocalMetadata) - { - allowSaving = true; - } - } - - if (!allowSaving) - { - return false; - } - } - - return true; - } - - private string GetBackdropSaveFilename(IEnumerable<ItemImageInfo> images, string zeroIndexFilename, string numberedIndexPrefix, int? index) - { - if (index.HasValue && index.Value == 0) - { - return zeroIndexFilename; - } - - var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList(); - - var current = 1; - while (filenames.Contains(numberedIndexPrefix + current.ToString(_usCulture), StringComparer.OrdinalIgnoreCase)) - { - current++; - } - - return numberedIndexPrefix + current.ToString(_usCulture); - } - - public string Name - { - get { return "Emby/Plex/Xbmc Images"; } - } - } -} diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index d95d8f12d..f3147a065 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -52,7 +52,6 @@ </Compile> <Compile Include="Configuration\NfoOptions.cs" /> <Compile Include="EntryPoint.cs" /> - <Compile Include="Images\XbmcImageSaver.cs" /> <Compile Include="Parsers\BaseNfoParser.cs" /> <Compile Include="Parsers\EpisodeNfoParser.cs" /> <Compile Include="Parsers\MovieNfoParser.cs" /> |
