From 6aa45830814219801934a6aed18039484fe7a032 Mon Sep 17 00:00:00 2001 From: Kay Diefenthal Date: Sun, 28 Aug 2016 11:19:50 +0200 Subject: Update SatIpDiscovery.cs Expand and correcting the Reading of the DeviceDescription Capability Field now can you look for how many Tuners and wicht type ( DVBS DVBT DVBC ) it is --- .../LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs | 79 +++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs index 9d5dba282..1e4487757 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs @@ -26,7 +26,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly IHttpClient _httpClient; private readonly IJsonSerializer _json; - + private int _tunerCountDVBS=0; + private int _tunerCountDVBC=0; + private int _tunerCountDVBT=0; + private bool _supportsDVBS=false; + private bool _supportsDVBC=false; + private bool _supportsDVBT=false; public static SatIpDiscovery Current; public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json) @@ -167,7 +172,57 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp public void Dispose() { } + private void ReadCapability(string capability) + { + + string[] cap = capability.Split('-'); + switch (cap[0].ToLower()) + { + case "dvbs": + case "dvbs2": + { + // Optional that you know what an device Supports can you add an flag + _supportsDVBS = true; + for (int i = 0; i < int.Parse(cap[1]); i++) + { + //ToDo Create Digital Recorder / Tuner Capture Instance here for each with index FE param in Sat>Ip Spec for direct communication with this instance + } + _tunerCountDVBS = int.Parse(cap[1]); + break; + } + case "dvbc": + case "dvbc2": + { + // Optional that you know what an device Supports can you add an flag + _supportsDVBC = true; + + for (int i = 0; i < int.Parse(cap[1]); i++) + { + //ToDo Create Digital Recorder / Tuner Capture Instance here for each with index FE param in Sat>Ip Spec for direct communication with this instance + + } + _tunerCountDVBC = int.Parse(cap[1]); + break; + } + case "dvbt": + case "dvbt2": + { + // Optional that you know what an device Supports can you add an flag + _supportsDVBT = true; + + + for (int i = 0; i < int.Parse(cap[1]); i++) + { + //ToDo Create Digital Recorder / Tuner Capture Instance here for each with index FE param in Sat>Ip Spec for direct communication with this instance + + } + _tunerCountDVBT = int.Parse(cap[1]); + break; + } + } + + } public async Task GetInfo(string url, CancellationToken cancellationToken) { Uri locationUri = new Uri(url); @@ -227,7 +282,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp var presentationUrlElement = deviceElement.Element(n0 + "presentationURL"); if (presentationUrlElement != null) presentationurl = presentationUrlElement.Value; var capabilitiesElement = deviceElement.Element(n1 + "X_SATIPCAP"); - if (capabilitiesElement != null) capabilities = capabilitiesElement.Value; + if (capabilitiesElement != null) + { + _capabilities = capabilitiesElement.Value; + if (capabilitiesElement.Value.Contains(',')) + { + string[] capabilities = capabilitiesElement.Value.Split(','); + foreach (var capability in capabilities) + { + ReadCapability(capability); + } + } + else + { + ReadCapability(capabilitiesElement.Value); + } + } + else + { + _supportsDVBS = true; + _tunerCountDVBS =1; + } var m3uElement = deviceElement.Element(n1 + "X_SATIPM3U"); if (m3uElement != null) m3u = m3uElement.Value; } -- cgit v1.2.3 From 806c7950692d194d7b3a4e5be35eede60f44187b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 28 Aug 2016 12:46:28 -0400 Subject: update episode nfo saving --- .../MediaEncoder/EncodingManager.cs | 9 ++++++++- MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs | 18 +++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index 46ba7d2e7..67ddcc5cc 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -123,10 +123,17 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder { if (extractImages) { - if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd) + if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso) { continue; } + if (video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd) + { + if (video.PlayableStreamFileNames.Count != 1) + { + continue; + } + } // Add some time for the first chapter to make sure we don't end up with a black image var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs index bf6df2347..d57d22d5c 100644 --- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs @@ -72,19 +72,23 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture)); } - if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) - { - writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture)); - } if (episode.AirsBeforeSeasonNumber.HasValue && episode.AirsBeforeSeasonNumber.Value != -1) { writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(UsCulture)); } - var season = episode.AiredSeasonNumber; - if (season.HasValue && season.Value != -1) + if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0) { - writer.WriteElementString("displayseason", season.Value.ToString(UsCulture)); + if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) + { + writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture)); + } + + var specialSeason = episode.AiredSeasonNumber; + if (specialSeason.HasValue && specialSeason.Value != -1) + { + writer.WriteElementString("displayseason", specialSeason.Value.ToString(UsCulture)); + } } if (episode.DvdEpisodeNumber.HasValue) -- cgit v1.2.3 From dc9742a2ed4e36108721f7dd7776da939c97563d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 28 Aug 2016 12:57:59 -0400 Subject: comment out code not compiling --- .../LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs index 1e4487757..ba5c604ed 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs @@ -284,19 +284,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp var capabilitiesElement = deviceElement.Element(n1 + "X_SATIPCAP"); if (capabilitiesElement != null) { - _capabilities = capabilitiesElement.Value; - if (capabilitiesElement.Value.Contains(',')) - { - string[] capabilities = capabilitiesElement.Value.Split(','); - foreach (var capability in capabilities) - { - ReadCapability(capability); - } - } - else - { - ReadCapability(capabilitiesElement.Value); - } + //_capabilities = capabilitiesElement.Value; + //if (capabilitiesElement.Value.Contains(',')) + //{ + // string[] capabilities = capabilitiesElement.Value.Split(','); + // foreach (var capability in capabilities) + // { + // ReadCapability(capability); + // } + //} + //else + //{ + // ReadCapability(capabilitiesElement.Value); + //} } else { -- cgit v1.2.3 From f0864b1dae70f31f8e911e2994a07421753361c4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 29 Aug 2016 03:12:34 -0400 Subject: update intros --- .../Intros/DefaultIntroProvider.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs index 7c7a535cd..4a43befed 100644 --- a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs +++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs @@ -216,7 +216,8 @@ namespace MediaBrowser.Server.Implementations.Intros } return allIntros - .Where(i => IsMatch(i.Path, codec)); + .Where(i => IsMatch(i.Path, codec)) + .OrderBy(i => Guid.NewGuid()); } private IEnumerable GetMediaInfoIntrosByAudioStream(List allIntros, MediaStream stream) @@ -229,13 +230,15 @@ namespace MediaBrowser.Server.Implementations.Intros } return allIntros - .Where(i => IsAudioMatch(i.Path, stream)); + .Where(i => IsAudioMatch(i.Path, stream)) + .OrderBy(i => Guid.NewGuid()); } private IEnumerable GetMediaInfoIntrosByTags(List allIntros, List tags) { return allIntros - .Where(i => tags.Any(t => IsMatch(i.Path, t))); + .Where(i => tags.Any(t => IsMatch(i.Path, t))) + .OrderBy(i => Guid.NewGuid()); } private bool IsMatch(string file, string attribute) -- cgit v1.2.3 From 06ebf9d3c262d64d3d1aa967c7893ae32a4fb191 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 29 Aug 2016 14:42:53 -0400 Subject: update recording file name --- .../ScheduledTasks/ScheduledTaskWorker.cs | 4 --- .../Configuration/ServerConfiguration.cs | 2 -- MediaBrowser.Providers/Manager/MetadataService.cs | 15 -------- .../Library/LibraryManager.cs | 4 --- .../Library/Validators/PeopleValidator.cs | 4 --- .../LiveTv/EmbyTV/RecordingHelper.cs | 4 +++ .../ApplicationHost.cs | 3 +- .../MediaBrowser.Server.Startup.Common.csproj | 1 - .../Migrations/CollectionsViewMigration.cs | 40 ---------------------- 9 files changed, 5 insertions(+), 72 deletions(-) delete mode 100644 MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index ab2aa761b..798e18ef5 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -423,10 +423,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks CurrentProgress = null; OnTaskCompleted(startTime, endTime, status, failureException); - - // Bad practice, i know. But we keep a lot in memory, unfortunately. - GC.Collect(2, GCCollectionMode.Forced, true); - GC.Collect(2, GCCollectionMode.Forced, true); } /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index a891a422a..abe572cc8 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -176,7 +176,6 @@ namespace MediaBrowser.Model.Configuration public string UICulture { get; set; } public PeopleMetadataOptions PeopleMetadataOptions { get; set; } - public bool FindInternetTrailers { get; set; } public bool SaveMetadataHidden { get; set; } @@ -243,7 +242,6 @@ namespace MediaBrowser.Model.Configuration LibraryMonitorDelay = 60; EnableInternetProviders = true; - FindInternetTrailers = true; PathSubstitutions = new PathSubstitution[] { }; ContentTypes = new NameValuePair[] { }; diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index c535f33ba..a610df427 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -647,8 +647,6 @@ namespace MediaBrowser.Providers.Manager if (result.HasMetadata) { - NormalizeRemoteResult(result.Item); - MergeData(result, temp, new List(), false, false); refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload; @@ -673,19 +671,6 @@ namespace MediaBrowser.Providers.Manager return refreshResult; } - private void NormalizeRemoteResult(TItemType item) - { - if (!ServerConfigurationManager.Configuration.FindInternetTrailers) - { - var hasTrailers = item as IHasTrailers; - - if (hasTrailers != null) - { - hasTrailers.RemoteTrailers.Clear(); - } - } - } - private void MergeNewData(TItemType source, TIdType lookupInfo) { // Copy new provider id's that may have been obtained diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index cc3a7e41f..5d556e3a6 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1088,10 +1088,6 @@ namespace MediaBrowser.Server.Implementations.Library await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false); progress.Report(100); - - // Bad practice, i know. But we keep a lot in memory, unfortunately. - GC.Collect(2, GCCollectionMode.Forced, true); - GC.Collect(2, GCCollectionMode.Forced, true); } /// diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs index d90b9615b..93b9c0da1 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -165,10 +165,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); _logger.Info("People validation complete"); - - // Bad practice, i know. But we keep a lot in memory, unfortunately. - GC.Collect(2, GCCollectionMode.Forced, true); - GC.Collect(2, GCCollectionMode.Forced, true); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 37e10d925..9485e0325 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -55,6 +55,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { name += " " + info.OriginalAirDate.Value.ToString("yyyy-MM-dd"); } + else + { + name += " " + DateTime.Now.ToString("yyyy-MM-dd"); + } if (!string.IsNullOrWhiteSpace(info.EpisodeTitle)) { diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 9c5015b0e..ac2422b08 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -385,8 +385,7 @@ namespace MediaBrowser.Server.Startup.Common new OmdbEpisodeProviderMigration(ServerConfigurationManager), new MovieDbEpisodeProviderMigration(ServerConfigurationManager), new DbMigration(ServerConfigurationManager, TaskManager), - new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename), - new CollectionsViewMigration(ServerConfigurationManager, UserManager) + new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename) }; foreach (var task in migrations) diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index 5ee7d49e8..c415dec8a 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -70,7 +70,6 @@ - diff --git a/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs deleted file mode 100644 index 3f68ec48b..000000000 --- a/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; - -namespace MediaBrowser.Server.Startup.Common.Migrations -{ - public class CollectionsViewMigration : IVersionMigration - { - private readonly IServerConfigurationManager _config; - private readonly IUserManager _userManager; - - public CollectionsViewMigration(IServerConfigurationManager config, IUserManager userManager) - { - _config = config; - _userManager = userManager; - } - - public void Run() - { - var migrationKey = this.GetType().Name; - var migrationKeyList = _config.Configuration.Migrations.ToList(); - - if (!migrationKeyList.Contains(migrationKey)) - { - if (_config.Configuration.IsStartupWizardCompleted) - { - if (_userManager.Users.Any(i => i.Configuration.DisplayCollectionsView)) - { - _config.Configuration.DisplayCollectionsView = true; - } - } - - migrationKeyList.Add(migrationKey); - _config.Configuration.Migrations = migrationKeyList.ToArray(); - _config.SaveConfiguration(); - } - - } - } -} -- cgit v1.2.3 From 2bdaba633cab470b25470a5bb05a34c4dfe8aec1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 29 Aug 2016 17:06:24 -0400 Subject: make chapter images a per-library setting --- .../Entities/CollectionFolder.cs | 2 +- MediaBrowser.Model/Configuration/LibraryOptions.cs | 2 ++ .../MediaInfo/FFProbeVideoInfo.cs | 9 +++++- .../MediaEncoder/EncodingManager.cs | 37 +++++++++++++++------- 4 files changed, 36 insertions(+), 14 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 597ecf973..30ea26eb6 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -106,7 +106,7 @@ namespace MediaBrowser.Controller.Entities { LibraryOptions[path] = options; - options.SchemaVersion = 1; + options.SchemaVersion = 2; XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path)); } } diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 3fe694553..551363223 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -6,6 +6,8 @@ public bool EnablePhotos { get; set; } public bool EnableRealtimeMonitor { get; set; } public int SchemaVersion { get; set; } + public bool EnableChapterImageExtraction { get; set; } + public bool ExtractChapterImagesDuringLibraryScan { get; set; } public LibraryOptions() { diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index c20823535..0f8cf93fb 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -261,11 +261,18 @@ namespace MediaBrowser.Providers.MediaInfo NormalizeChapterNames(chapters); + var libraryOptions = _libraryManager.GetLibraryOptions(video); + var extractDuringScan = chapterOptions.ExtractDuringLibraryScan; + if (libraryOptions != null && libraryOptions.SchemaVersion >= 2) + { + extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan; + } + await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions { Chapters = chapters, Video = video, - ExtractImages = chapterOptions.ExtractDuringLibraryScan, + ExtractImages = extractDuringScan, SaveChapters = false }, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index 67ddcc5cc..0ba3ba344 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Server.Implementations.MediaEncoder { @@ -24,6 +25,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder private readonly ILogger _logger; private readonly IMediaEncoder _encoder; private readonly IChapterManager _chapterManager; + private readonly ILibraryManager _libraryManager; public EncodingManager(IFileSystem fileSystem, ILogger logger, @@ -57,27 +59,38 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder return false; } - var options = _chapterManager.GetConfiguration(); - - if (video is Movie) + var libraryOptions = _libraryManager.GetLibraryOptions(video); + if (libraryOptions != null && libraryOptions.SchemaVersion >= 2) { - if (!options.EnableMovieChapterImageExtraction) + if (!libraryOptions.EnableChapterImageExtraction) { return false; } } - else if (video is Episode) + else { - if (!options.EnableEpisodeChapterImageExtraction) + var options = _chapterManager.GetConfiguration(); + + if (video is Movie) { - return false; + if (!options.EnableMovieChapterImageExtraction) + { + return false; + } } - } - else - { - if (!options.EnableOtherVideoChapterImageExtraction) + else if (video is Episode) { - return false; + if (!options.EnableEpisodeChapterImageExtraction) + { + return false; + } + } + else + { + if (!options.EnableOtherVideoChapterImageExtraction) + { + return false; + } } } -- cgit v1.2.3 From 6b3e1951e2ad03d5331864e227db09fff1caacb4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 30 Aug 2016 02:06:24 -0400 Subject: switch to shared image editor --- MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs | 3 ++- MediaBrowser.Server.Startup.Common/ApplicationHost.cs | 2 +- MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj | 6 ------ 3 files changed, 3 insertions(+), 8 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index 0ba3ba344..11338df6d 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -30,12 +30,13 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder public EncodingManager(IFileSystem fileSystem, ILogger logger, IMediaEncoder encoder, - IChapterManager chapterManager) + IChapterManager chapterManager, ILibraryManager libraryManager) { _fileSystem = fileSystem; _logger = logger; _encoder = encoder; _chapterManager = chapterManager; + _libraryManager = libraryManager; } /// diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index ac2422b08..36bfa5661 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -547,7 +547,7 @@ namespace MediaBrowser.Server.Startup.Common await RegisterMediaEncoder(innerProgress).ConfigureAwait(false); progress.Report(90); - EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager); + EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager, LibraryManager); RegisterSingleInstance(EncodingManager); RegisterSingleInstance(NativeApp.GetPowerManagement()); diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 5b66c27f4..1ba456ce5 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -281,9 +281,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -1086,9 +1083,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest -- cgit v1.2.3 From d394076fb6608b1cf3c3068050a18c0be6d59939 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 30 Aug 2016 13:39:15 -0400 Subject: update image editor --- MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs | 2 +- MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs | 6 +++--- MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs | 2 +- MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs | 2 +- MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs | 2 +- MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs | 2 +- MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs | 10 +++++----- MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml | 6 +++--- MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml | 2 +- MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml | 10 +++++----- MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs | 2 +- 15 files changed, 27 insertions(+), 27 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs b/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs index 7b0f18fa6..5edf3afbf 100644 --- a/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs +++ b/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Dlna.Profiles { Container = "mkv", VideoCodec = "h264,mpeg2video", - AudioCodec = "aac,ac3,dca,mp3,mp2,pcm", + AudioCodec = "aac,ac3,dca,mp3,mp2,pcm,dts", Type = DlnaProfileType.Video }, diff --git a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs index 63fcf60ec..aae520d6f 100644 --- a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs +++ b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs @@ -65,14 +65,14 @@ namespace MediaBrowser.Dlna.Profiles { Container = "avi", VideoCodec = "h264,mpeg4,mjpeg", - AudioCodec = "mp3,ac3,dca", + AudioCodec = "mp3,ac3,dca,dts", Type = DlnaProfileType.Video }, new DirectPlayProfile { Container = "mkv", VideoCodec = "h264,mpeg4,mjpeg4", - AudioCodec = "mp3,ac3,dca,aac", + AudioCodec = "mp3,ac3,dca,aac,dts", Type = DlnaProfileType.Video }, new DirectPlayProfile @@ -300,7 +300,7 @@ namespace MediaBrowser.Dlna.Profiles new CodecProfile { Type = CodecType.VideoAudio, - Codec = "ac3,wmav2,dca,aac,mp3", + Codec = "ac3,wmav2,dca,aac,mp3,dts", Conditions = new[] { diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs index bbdf370b8..fefb96117 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs @@ -115,7 +115,7 @@ namespace MediaBrowser.Dlna.Profiles { Container = "mkv", VideoCodec = "mpeg4,h264", - AudioCodec = "ac3,dca,aac,mp3,pcm", + AudioCodec = "ac3,dca,aac,mp3,pcm,dts", Type = DlnaProfileType.Video }, new DirectPlayProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs index 1eed398ed..4f2ff3ad1 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs @@ -115,7 +115,7 @@ namespace MediaBrowser.Dlna.Profiles { Container = "mkv", VideoCodec = "mpeg4,h264", - AudioCodec = "ac3,dca,aac,mp3,pcm", + AudioCodec = "ac3,dca,aac,mp3,pcm,dts", Type = DlnaProfileType.Video }, new DirectPlayProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs index 563c2db7b..57cd5dad6 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Dlna.Profiles { Container = "mkv", VideoCodec = "mpeg4,h264", - AudioCodec = "ac3,dca,aac,mp3,pcm", + AudioCodec = "ac3,dca,aac,mp3,pcm,dts", Type = DlnaProfileType.Video }, new DirectPlayProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs index 21e0c092c..f504820d1 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Dlna.Profiles { Container = "mkv", VideoCodec = "mpeg4,h264", - AudioCodec = "ac3,dca,aac,mp3,pcm", + AudioCodec = "ac3,dca,aac,mp3,pcm,dts", Type = DlnaProfileType.Video }, new DirectPlayProfile diff --git a/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs b/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs index 1c0c7d297..5f9e30318 100644 --- a/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs +++ b/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Dlna.Profiles Container = "avi", Type = DlnaProfileType.Video, VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1", - AudioCodec = "ac3,dca,mp2,mp3,pcm,dca" + AudioCodec = "ac3,dca,mp2,mp3,pcm,dts" }, new DirectPlayProfile @@ -66,7 +66,7 @@ namespace MediaBrowser.Dlna.Profiles Container = "mpeg", Type = DlnaProfileType.Video, VideoCodec = "mpeg1video,mpeg2video", - AudioCodec = "ac3,dca,mp2,mp3,pcm,dca" + AudioCodec = "ac3,dca,mp2,mp3,pcm,dts" }, new DirectPlayProfile @@ -74,7 +74,7 @@ namespace MediaBrowser.Dlna.Profiles Container = "mkv", Type = DlnaProfileType.Video, VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1", - AudioCodec = "ac3,dca,aac,mp2,mp3,pcm,dca" + AudioCodec = "ac3,dca,aac,mp2,mp3,pcm,dts" }, new DirectPlayProfile @@ -82,7 +82,7 @@ namespace MediaBrowser.Dlna.Profiles Container = "ts,m2ts", Type = DlnaProfileType.Video, VideoCodec = "mpeg1video,mpeg2video,h264,vc1", - AudioCodec = "ac3,dca,mp2,mp3,aac,dca" + AudioCodec = "ac3,dca,mp2,mp3,aac,dts" }, new DirectPlayProfile @@ -90,7 +90,7 @@ namespace MediaBrowser.Dlna.Profiles Container = "mp4,mov", Type = DlnaProfileType.Video, VideoCodec = "h264,mpeg4", - AudioCodec = "ac3,aac,mp2,mp3,dca" + AudioCodec = "ac3,aac,mp2,mp3,dca,dts" }, new DirectPlayProfile diff --git a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml index 865449cb6..d26346ff6 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml @@ -39,7 +39,7 @@ - + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml index 77fa928cc..1918c0297 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml @@ -38,8 +38,8 @@ - - + + @@ -100,7 +100,7 @@ - + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml index 1c626d3d5..f8b583b50 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml @@ -45,7 +45,7 @@ - + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml index bb55a1a80..eaa37c620 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml @@ -45,7 +45,7 @@ - + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml index 804770a59..368e892ff 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml @@ -43,7 +43,7 @@ - + diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml index 920b6ccfa..9ec096b7f 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml @@ -43,7 +43,7 @@ - + diff --git a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml index ef8fa5757..ebd4eb9b5 100644 --- a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml +++ b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml @@ -36,11 +36,11 @@ true - - - - - + + + + + diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs index 175dbbc01..f40b64498 100644 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.Sync if (supportsDca) { - mkvAudio += ",dca"; + mkvAudio += ",dca,dts"; } var videoProfile = "high|main|baseline|constrained baseline"; -- cgit v1.2.3 From 0375990d8e6b0ebfb58c318fdb4255326d72d75d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 30 Aug 2016 14:17:37 -0400 Subject: Support grabbing channel number from url --- .../LiveTv/TunerHosts/M3uParser.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index ffe95c862..8095a6989 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -70,7 +71,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith("#", StringComparison.OrdinalIgnoreCase)) { - var channel = GetChannelnfo(extInf, tunerHostId); + var channel = GetChannelnfo(extInf, tunerHostId, line); channel.Id = channelIdPrefix + urlHash + line.GetMD5().ToString("N"); channel.Path = line; channels.Add(channel); @@ -79,7 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } return channels; } - private M3UChannel GetChannelnfo(string extInf, string tunerHostId) + private M3UChannel GetChannelnfo(string extInf, string tunerHostId, string mediaUrl) { var titleIndex = extInf.LastIndexOf(','); var channel = new M3UChannel(); @@ -87,8 +88,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts channel.Number = extInf.Trim().Split(' ')[0] ?? "0"; channel.Name = extInf.Substring(titleIndex + 1); - - if(channel.Number == "-1") { channel.Number = "0"; } //Check for channel number with the format from SatIp int number; @@ -101,6 +100,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts channel.Name = channel.Name.Substring(numberIndex + 1); } } + + if (string.Equals(channel.Number, "-1", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(mediaUrl)) + { + channel.Number = Path.GetFileNameWithoutExtension(mediaUrl.Split('/').Last()); + } + + if (string.Equals(channel.Number, "-1", StringComparison.OrdinalIgnoreCase)) + { + channel.Number = "0"; + } + channel.ImageUrl = FindProperty("tvg-logo", extInf, null); channel.Number = FindProperty("tvg-id", extInf, channel.Number); channel.Number = FindProperty("channel-id", extInf, channel.Number); -- cgit v1.2.3 From eefd697b85fcaa76fe8f7165ea48793b8477c91d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 31 Aug 2016 15:17:11 -0400 Subject: update app footer --- .../HttpServer/HttpListenerHost.cs | 7 +++- .../LiveTv/EmbyTV/EmbyTV.cs | 46 +++++++++++----------- .../ApplicationHost.cs | 3 +- .../Browser/BrowserLauncher.cs | 5 +++ .../MediaBrowser.Server.Startup.Common.csproj | 1 - .../Migrations/OmdbEpisodeProviderMigration.cs | 43 -------------------- MediaBrowser.ServerApplication/ServerNotifyIcon.cs | 15 +++++++ 7 files changed, 49 insertions(+), 71 deletions(-) delete mode 100644 MediaBrowser.Server.Startup.Common/Migrations/OmdbEpisodeProviderMigration.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 51a53fe21..28b7824c8 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -71,14 +71,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer HostConfig.Instance.MapExceptionToStatusCode = new Dictionary { - {typeof (InvalidOperationException), 422}, + {typeof (InvalidOperationException), 500}, + {typeof (NotImplementedException), 500}, {typeof (ResourceNotFoundException), 404}, {typeof (FileNotFoundException), 404}, {typeof (DirectoryNotFoundException), 404}, {typeof (SecurityException), 401}, {typeof (PaymentRequiredException), 402}, {typeof (UnauthorizedAccessException), 500}, - {typeof (ApplicationException), 500} + {typeof (ApplicationException), 500}, + {typeof (PlatformNotSupportedException), 500}, + {typeof (NotSupportedException), 500} }; HostConfig.Instance.GlobalResponseHeaders = new Dictionary(); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 6acb0783e..649024d16 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -851,29 +851,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var recordPath = RecordingPath; var config = GetConfiguration(); - if (info.IsMovie) - { - var customRecordingPath = config.MovieRecordingPath; - var allowSubfolder = true; - if (!string.IsNullOrWhiteSpace(customRecordingPath)) - { - allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase); - recordPath = customRecordingPath; - } - - if (allowSubfolder && config.EnableRecordingSubfolders) - { - recordPath = Path.Combine(recordPath, "Movies"); - } - - var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); - if (info.ProductionYear.HasValue) - { - folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; - } - recordPath = Path.Combine(recordPath, folderName); - } - else if (info.IsSeries) + if (info.IsSeries) { var customRecordingPath = config.SeriesRecordingPath; var allowSubfolder = true; @@ -910,6 +888,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = Path.Combine(recordPath, folderName); } } + else if (info.IsMovie) + { + var customRecordingPath = config.MovieRecordingPath; + var allowSubfolder = true; + if (!string.IsNullOrWhiteSpace(customRecordingPath)) + { + allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase); + recordPath = customRecordingPath; + } + + if (allowSubfolder && config.EnableRecordingSubfolders) + { + recordPath = Path.Combine(recordPath, "Movies"); + } + + var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); + if (info.ProductionYear.HasValue) + { + folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; + } + recordPath = Path.Combine(recordPath, folderName); + } else if (info.IsKids) { if (config.EnableRecordingSubfolders) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 36bfa5661..170719b0f 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -382,7 +382,6 @@ namespace MediaBrowser.Server.Startup.Common { var migrations = new List { - new OmdbEpisodeProviderMigration(ServerConfigurationManager), new MovieDbEpisodeProviderMigration(ServerConfigurationManager), new DbMigration(ServerConfigurationManager, TaskManager), new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename) @@ -947,7 +946,7 @@ namespace MediaBrowser.Server.Startup.Common { if (!CanSelfRestart) { - throw new InvalidOperationException("The server is unable to self-restart. Please restart manually."); + throw new PlatformNotSupportedException("The server is unable to self-restart. Please restart manually."); } try diff --git a/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs b/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs index 6b3602a73..1a0e2d973 100644 --- a/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs +++ b/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs @@ -28,6 +28,11 @@ namespace MediaBrowser.Server.Startup.Common.Browser OpenUrl(appHost, "http://emby.media/community"); } + public static void OpenEmbyPremiere(IServerApplicationHost appHost) + { + OpenDashboardPage("supporterkey.html", appHost); + } + /// /// Opens the web client. /// diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index c415dec8a..778002e50 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -73,7 +73,6 @@ - diff --git a/MediaBrowser.Server.Startup.Common/Migrations/OmdbEpisodeProviderMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/OmdbEpisodeProviderMigration.cs deleted file mode 100644 index ebc0e67de..000000000 --- a/MediaBrowser.Server.Startup.Common/Migrations/OmdbEpisodeProviderMigration.cs +++ /dev/null @@ -1,43 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using System.Linq; - -namespace MediaBrowser.Server.Startup.Common.Migrations -{ - class OmdbEpisodeProviderMigration : IVersionMigration - { - private readonly IServerConfigurationManager _config; - private const string _providerName = "The Open Movie Database"; - - public OmdbEpisodeProviderMigration(IServerConfigurationManager config) - { - _config = config; - } - - public void Run() - { - var migrationKey = this.GetType().FullName; - var migrationKeyList = _config.Configuration.Migrations.ToList(); - - if (!migrationKeyList.Contains(migrationKey)) - { - foreach (var metaDataOption in _config.Configuration.MetadataOptions) - { - if (metaDataOption.ItemType == "Episode") - { - var disabledFetchers = metaDataOption.DisabledMetadataFetchers.ToList(); - if (!disabledFetchers.Contains(_providerName)) - { - disabledFetchers.Add(_providerName); - metaDataOption.DisabledMetadataFetchers = disabledFetchers.ToArray(); - } - } - } - - migrationKeyList.Add(migrationKey); - _config.Configuration.Migrations = migrationKeyList.ToArray(); - _config.SaveConfiguration(); - } - - } - } -} diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs index d04128a18..7805c0d68 100644 --- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs +++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.ServerApplication private ToolStripMenuItem cmdRestart; private ToolStripSeparator toolStripSeparator1; private ToolStripMenuItem cmdCommunity; + private ToolStripMenuItem cmdPremiere; private Container components; private readonly ILogger _logger; @@ -50,6 +51,7 @@ namespace MediaBrowser.ServerApplication cmdExit = new ToolStripMenuItem(); cmdCommunity = new ToolStripMenuItem(); + cmdPremiere = new ToolStripMenuItem(); toolStripSeparator1 = new ToolStripSeparator(); cmdRestart = new ToolStripMenuItem(); toolStripSeparator2 = new ToolStripSeparator(); @@ -69,6 +71,7 @@ namespace MediaBrowser.ServerApplication contextMenuStrip1.Items.AddRange(new ToolStripItem[] { cmdBrowse, cmdConfigure, + cmdPremiere, toolStripSeparator2, cmdRestart, toolStripSeparator1, @@ -89,6 +92,11 @@ namespace MediaBrowser.ServerApplication cmdCommunity.Name = "cmdCommunity"; cmdCommunity.Size = new System.Drawing.Size(208, 22); // + // cmdPremiere + // + cmdPremiere.Name = "cmdPremiere"; + cmdPremiere.Size = new System.Drawing.Size(208, 22); + // // toolStripSeparator1 // toolStripSeparator1.Name = "toolStripSeparator1"; @@ -118,6 +126,7 @@ namespace MediaBrowser.ServerApplication cmdRestart.Click += cmdRestart_Click; cmdConfigure.Click += cmdConfigure_Click; cmdCommunity.Click += cmdCommunity_Click; + cmdPremiere.Click += cmdPremiere_Click; cmdBrowse.Click += cmdBrowse_Click; _configurationManager.ConfigurationUpdated += Instance_ConfigurationUpdated; @@ -138,6 +147,7 @@ namespace MediaBrowser.ServerApplication cmdExit.Text = _localization.GetLocalizedString("LabelExit"); cmdCommunity.Text = _localization.GetLocalizedString("LabelVisitCommunity"); + cmdPremiere.Text = _localization.GetLocalizedString("Emby Premiere"); cmdBrowse.Text = _localization.GetLocalizedString("LabelBrowseLibrary"); cmdConfigure.Text = _localization.GetLocalizedString("LabelConfigureServer"); cmdRestart.Text = _localization.GetLocalizedString("LabelRestartServer"); @@ -163,6 +173,11 @@ namespace MediaBrowser.ServerApplication BrowserLauncher.OpenWebClient(_appHost); } + void cmdPremiere_Click(object sender, EventArgs e) + { + BrowserLauncher.OpenEmbyPremiere(_appHost); + } + void cmdCommunity_Click(object sender, EventArgs e) { BrowserLauncher.OpenCommunity(_appHost); -- cgit v1.2.3 From 3862207a7352feddfae6416ba2dff4e78620089a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 31 Aug 2016 16:46:09 -0400 Subject: move channel view setting to global --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 1 + MediaBrowser.Model/Configuration/UserConfiguration.cs | 1 - MediaBrowser.Server.Implementations/Library/UserViewManager.cs | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index abe572cc8..9022f64a1 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -204,6 +204,7 @@ namespace MediaBrowser.Model.Configuration public bool DisplayCollectionsView { get; set; } public string[] LocalNetworkAddresses { get; set; } public string[] CodecsUsed { get; set; } + public bool EnableChannelView { get; set; } /// /// Initializes a new instance of the class. diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 313c5243c..a6785a06a 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -41,7 +41,6 @@ namespace MediaBrowser.Model.Configuration public string[] PlainFolderViews { get; set; } public bool HidePlayedInLatest { get; set; } - public bool EnableChannelView { get; set; } public bool RememberAudioSelections { get; set; } public bool RememberSubtitleSelections { get; set; } diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs index 5fffa3d1f..2cbee7c97 100644 --- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs @@ -120,8 +120,8 @@ namespace MediaBrowser.Server.Implementations.Library }, cancellationToken).ConfigureAwait(false); var channels = channelResult.Items; - - if (user.Configuration.EnableChannelView && channels.Length > 0) + + if (_config.Configuration.EnableChannelView && channels.Length > 0) { list.Add(await _channelManager.GetInternalChannelFolder(cancellationToken).ConfigureAwait(false)); } -- cgit v1.2.3 From 37dfbbe4776ef8cb005907cbc2ae96e64d641295 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 1 Sep 2016 12:36:11 -0400 Subject: update tabs --- .../HttpServer/HttpListenerHost.cs | 27 +++++++++++++++------- MediaBrowser.WebDashboard/Api/PackageCreator.cs | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 28b7824c8..8e46f8f03 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -102,14 +102,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer // new SessionAuthProvider(_containerAdapter.Resolve()), //})); - PreRequestFilters.Add((httpReq, httpRes) => - { - //Handles Request and closes Responses after emitting global HTTP Headers - if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) - { - httpRes.EndRequest(); //add a 'using ServiceStack;' - } - }); + //PreRequestFilters.Add((httpReq, httpRes) => + //{ + // //Handles Request and closes Responses after emitting global HTTP Headers + // if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) + // { + // httpRes.EndRequest(); //add a 'using ServiceStack;' + // } + //}); HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse); } @@ -403,6 +403,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer return; } + if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) + { + httpRes.StatusCode = 200; + httpRes.AddHeader("Access-Control-Allow-Origin", "*"); + httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); + httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); + httpRes.ContentType = "text/html"; + + httpRes.Close(); + } + var operationName = httpReq.OperationName; var localPath = url.LocalPath; diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 65e7fa5d3..adf36e4bf 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -346,7 +346,7 @@ namespace MediaBrowser.WebDashboard.Api if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { - sb.Append(""); + sb.Append(""); } sb.Append(""); -- cgit v1.2.3 From fb251448c90ac8905a9f9ad6ec9a1b676aa51922 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 1 Sep 2016 21:54:16 -0400 Subject: Rtp Rtcp fix Discovery #2116 --- .../LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs | 79 ++++++++ .../LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs | 68 +++++++ .../LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs | 59 ++++++ .../LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs | 203 +++++++++++++++++++++ .../LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs | 37 ++++ .../SatIp/Rtcp/RtcpReceiverReportPacket.cs | 68 +++++++ .../SatIp/Rtcp/RtcpSenderReportPacket.cs | 105 +++++++++++ .../SatIp/Rtcp/RtcpSourceDescriptionPacket.cs | 57 ++++++ .../SatIp/Rtcp/SourceDescriptionBlock.cs | 65 +++++++ .../TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs | 60 ++++++ .../LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs | 160 ++++++++++++++++ .../LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs | 116 ++++++++++++ .../LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs | 32 ++-- .../LiveTv/TunerHosts/SatIp/TransmissionMode.cs | 25 +++ .../LiveTv/TunerHosts/SatIp/Utils.cs | 90 +++++++++ .../MediaBrowser.Server.Implementations.csproj | 14 ++ 16 files changed, 1222 insertions(+), 16 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs new file mode 100644 index 000000000..dddd77179 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs @@ -0,0 +1,79 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class ReportBlock + { + /// + /// Get the length of the block. + /// + public int BlockLength { get { return (24); } } + /// + /// Get the synchronization source. + /// + public string SynchronizationSource { get; private set; } + /// + /// Get the fraction lost. + /// + public int FractionLost { get; private set; } + /// + /// Get the cumulative packets lost. + /// + public int CumulativePacketsLost { get; private set; } + /// + /// Get the highest number received. + /// + public int HighestNumberReceived { get; private set; } + /// + /// Get the inter arrival jitter. + /// + public int InterArrivalJitter { get; private set; } + /// + /// Get the timestamp of the last report. + /// + public int LastReportTimeStamp { get; private set; } + /// + /// Get the delay since the last report. + /// + public int DelaySinceLastReport { get; private set; } + + /// + /// Initialize a new instance of the ReportBlock class. + /// + public ReportBlock() { } + + /// + /// Unpack the data in a packet. + /// + /// The buffer containing the packet. + /// The offset to the first byte of the packet within the buffer. + /// An ErrorSpec instance if an error occurs; null otherwise. + public void Process(byte[] buffer, int offset) + { + SynchronizationSource = Utils.ConvertBytesToString(buffer, offset, 4); + FractionLost = buffer[offset + 4]; + CumulativePacketsLost = Utils.Convert3BytesToInt(buffer, offset + 5); + HighestNumberReceived = Utils.Convert4BytesToInt(buffer, offset + 8); + InterArrivalJitter = Utils.Convert4BytesToInt(buffer, offset + 12); + LastReportTimeStamp = Utils.Convert4BytesToInt(buffer, offset + 16); + DelaySinceLastReport = Utils.Convert4BytesToInt(buffer, offset + 20); + + + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs new file mode 100644 index 000000000..990b6dd94 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs @@ -0,0 +1,68 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + class RtcpAppPacket : RtcpPacket + { + /// + /// Get the synchronization source. + /// + public int SynchronizationSource { get; private set; } + /// + /// Get the name. + /// + public string Name { get; private set; } + /// + /// Get the identity. + /// + public int Identity { get; private set; } + /// + /// Get the variable data portion. + /// + public string Data { get; private set; } + + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + SynchronizationSource = Utils.Convert4BytesToInt(buffer, offset + 4); + Name = Utils.ConvertBytesToString(buffer, offset + 8, 4); + Identity = Utils.Convert2BytesToInt(buffer, offset + 12); + + int dataLength = Utils.Convert2BytesToInt(buffer, offset + 14); + if (dataLength != 0) + Data = Utils.ConvertBytesToString(buffer, offset + 16, dataLength); + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Application Specific.\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource); + sb.AppendFormat("Name : {0} .\n", Name); + sb.AppendFormat("Identity : {0} .\n", Identity); + sb.AppendFormat("Data : {0} .\n", Data); + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs new file mode 100644 index 000000000..c79ea31a8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs @@ -0,0 +1,59 @@ +using System.Collections.ObjectModel; +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class RtcpByePacket :RtcpPacket + { + public Collection SynchronizationSources { get; private set; } + public string ReasonForLeaving { get; private set; } + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + SynchronizationSources = new Collection(); + int index = 4; + + while (SynchronizationSources.Count < ReportCount) + { + SynchronizationSources.Add(Utils.ConvertBytesToString(buffer, offset + index, 4)); + index += 4; + } + + if (index < Length) + { + int reasonLength = buffer[offset + index]; + ReasonForLeaving = Utils.ConvertBytesToString(buffer, offset + index + 1, reasonLength); + } + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("ByeBye .\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("SynchronizationSources : {0} .\n", SynchronizationSources); + sb.AppendFormat("ReasonForLeaving : {0} .\n", ReasonForLeaving); + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs new file mode 100644 index 000000000..2c54f0665 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs @@ -0,0 +1,203 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class RtcpListener + { + private readonly ILogger _logger; + private Thread _rtcpListenerThread; + private AutoResetEvent _rtcpListenerThreadStopEvent = null; + private UdpClient _udpClient; + private IPEndPoint _multicastEndPoint; + private IPEndPoint _serverEndPoint; + private TransmissionMode _transmissionMode; + + public RtcpListener(String address, int port, TransmissionMode mode,ILogger logger) + { + _logger = logger; + _transmissionMode = mode; + switch (mode) + { + case TransmissionMode.Unicast: + _udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(address), port)); + _serverEndPoint = new IPEndPoint(IPAddress.Any, 0); + break; + case TransmissionMode.Multicast: + _multicastEndPoint = new IPEndPoint(IPAddress.Parse(address), port); + _serverEndPoint = new IPEndPoint(IPAddress.Any, 0); + _udpClient = new UdpClient(); + _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); + _udpClient.ExclusiveAddressUse = false; + _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port)); + _udpClient.JoinMulticastGroup(_multicastEndPoint.Address); + break; + } + //StartRtcpListenerThread(); + } + + public void StartRtcpListenerThread() + { + // Kill the existing thread if it is in "zombie" state. + if (_rtcpListenerThread != null && !_rtcpListenerThread.IsAlive) + { + StopRtcpListenerThread(); + } + + if (_rtcpListenerThread == null) + { + _logger.Info("SAT>IP : starting new RTCP listener thread"); + _rtcpListenerThreadStopEvent = new AutoResetEvent(false); + _rtcpListenerThread = new Thread(new ThreadStart(RtcpListenerThread)); + _rtcpListenerThread.Name = string.Format("SAT>IP tuner RTCP listener"); + _rtcpListenerThread.IsBackground = true; + _rtcpListenerThread.Priority = ThreadPriority.Lowest; + _rtcpListenerThread.Start(); + } + } + + public void StopRtcpListenerThread() + { + if (_rtcpListenerThread != null) + { + if (!_rtcpListenerThread.IsAlive) + { + _logger.Info("SAT>IP : aborting old RTCP listener thread"); + _rtcpListenerThread.Abort(); + } + else + { + _rtcpListenerThreadStopEvent.Set(); + if (!_rtcpListenerThread.Join(400 * 2)) + { + _logger.Info("SAT>IP : failed to join RTCP listener thread, aborting thread"); + _rtcpListenerThread.Abort(); + } + } + _rtcpListenerThread = null; + if (_rtcpListenerThreadStopEvent != null) + { + _rtcpListenerThreadStopEvent.Close(); + _rtcpListenerThreadStopEvent = null; + } + } + } + + private void RtcpListenerThread() + { + try + { + bool receivedGoodBye = false; + try + { + _udpClient.Client.ReceiveTimeout = 400; + IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, 0); + while (!receivedGoodBye && !_rtcpListenerThreadStopEvent.WaitOne(1)) + { + byte[] packets = _udpClient.Receive(ref serverEndPoint); + if (packets == null) + { + continue; + } + + int offset = 0; + while (offset < packets.Length) + { + switch (packets[offset + 1]) + { + case 200: //sr + var sr = new RtcpSenderReportPacket(); + sr.Parse(packets, offset); + offset += sr.Length; + break; + case 201: //rr + var rr = new RtcpReceiverReportPacket(); + rr.Parse(packets, offset); + offset += rr.Length; + break; + case 202: //sd + var sd = new RtcpSourceDescriptionPacket(); + sd.Parse(packets, offset); + offset += sd.Length; + break; + case 203: // bye + var bye = new RtcpByePacket(); + bye.Parse(packets, offset); + receivedGoodBye = true; + OnPacketReceived(new RtcpPacketReceivedArgs(bye)); + offset += bye.Length; + break; + case 204: // app + var app = new RtcpAppPacket(); + app.Parse(packets, offset); + OnPacketReceived(new RtcpPacketReceivedArgs(app)); + offset += app.Length; + break; + } + } + + } + } + finally + { + switch (_transmissionMode) + { + case TransmissionMode.Multicast: + _udpClient.DropMulticastGroup(_multicastEndPoint.Address); + _udpClient.Close(); + break; + case TransmissionMode.Unicast: + _udpClient.Close(); + break; + } + } + } + catch (ThreadAbortException) + { + } + catch (Exception ex) + { + _logger.Info(string.Format("SAT>IP : RTCP listener thread exception"), ex); + return; + } + _logger.Info("SAT>IP : RTCP listener thread stopping"); + } + public delegate void PacketReceivedHandler(object sender, RtcpPacketReceivedArgs e); + public event PacketReceivedHandler PacketReceived; + public class RtcpPacketReceivedArgs : EventArgs + { + public Object Packet { get; private set; } + + public RtcpPacketReceivedArgs(Object packet) + { + Packet = packet; + } + } + protected void OnPacketReceived(RtcpPacketReceivedArgs args) + { + if (PacketReceived != null) + { + PacketReceived(this, args); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs new file mode 100644 index 000000000..0a949eb7e --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs @@ -0,0 +1,37 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public abstract class RtcpPacket + { + public int Version { get; private set; } + public bool Padding { get; private set; } + public int ReportCount { get; private set; } + public int Type { get; private set; } + public int Length { get; private set; } + + public virtual void Parse(byte[] buffer, int offset) + { + Version = buffer[offset] >> 6; + Padding = (buffer[offset] & 0x20) != 0; + ReportCount = buffer[offset] & 0x1f; + Type = buffer[offset + 1]; + Length = (Utils.Convert2BytesToInt(buffer, offset + 2) * 4) + 4; + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs new file mode 100644 index 000000000..abb863652 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs @@ -0,0 +1,68 @@ +using System.Collections.ObjectModel; +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class RtcpReceiverReportPacket :RtcpPacket + { + public string SynchronizationSource { get; private set; } + public Collection ReportBlocks { get; private set; } + public byte[] ProfileExtension { get; private set; } + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + SynchronizationSource = Utils.ConvertBytesToString(buffer, offset + 4, 4); + + ReportBlocks = new Collection(); + int index = 8; + + while (ReportBlocks.Count < ReportCount) + { + ReportBlock reportBlock = new ReportBlock(); + reportBlock.Process(buffer, offset + index); + ReportBlocks.Add(reportBlock); + index += reportBlock.BlockLength; + } + + if (index < Length) + { + ProfileExtension = new byte[Length - index]; + + for (int extensionIndex = 0; index < Length; index++) + { + ProfileExtension[extensionIndex] = buffer[offset + index]; + extensionIndex++; + } + } + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Receiver Report.\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource); + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs new file mode 100644 index 000000000..dda5d6a03 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs @@ -0,0 +1,105 @@ +using System.Collections.ObjectModel; +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class RtcpSenderReportPacket : RtcpPacket + { + #region Properties + /// + /// Get the synchronization source. + /// + public int SynchronizationSource { get; private set; } + /// + /// Get the NPT timestamp. + /// + public long NPTTimeStamp { get; private set; } + /// + /// Get the RTP timestamp. + /// + public int RTPTimeStamp { get; private set; } + /// + /// Get the packet count. + /// + public int SenderPacketCount { get; private set; } + /// + /// Get the octet count. + /// + public int SenderOctetCount { get; private set; } + /// + /// Get the list of report blocks. + /// + public Collection ReportBlocks { get; private set; } + /// + /// Get the profile extension data. + /// + public byte[] ProfileExtension { get; private set; } + #endregion + + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + SynchronizationSource = Utils.Convert4BytesToInt(buffer, offset + 4); + NPTTimeStamp = Utils.Convert8BytesToLong(buffer, offset + 8); + RTPTimeStamp = Utils.Convert4BytesToInt(buffer, offset + 16); + SenderPacketCount = Utils.Convert4BytesToInt(buffer, offset + 20); + SenderOctetCount = Utils.Convert4BytesToInt(buffer, offset + 24); + + ReportBlocks = new Collection(); + int index = 28; + + while (ReportBlocks.Count < ReportCount) + { + ReportBlock reportBlock = new ReportBlock(); + reportBlock.Process(buffer, offset + index); + ReportBlocks.Add(reportBlock); + index += reportBlock.BlockLength; + } + + if (index < Length) + { + ProfileExtension = new byte[Length - index]; + + for (int extensionIndex = 0; index < Length; index++) + { + ProfileExtension[extensionIndex] = buffer[offset + index]; + extensionIndex++; + } + } + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Sender Report.\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource); + sb.AppendFormat("NTP Timestamp : {0} .\n", Utils.NptTimestampToDateTime(NPTTimeStamp)); + sb.AppendFormat("RTP Timestamp : {0} .\n", RTPTimeStamp); + sb.AppendFormat("Sender PacketCount : {0} .\n", SenderPacketCount); + sb.AppendFormat("Sender Octet Count : {0} .\n", SenderOctetCount); + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs new file mode 100644 index 000000000..0a95a4413 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs @@ -0,0 +1,57 @@ +using System.Collections.ObjectModel; +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + class RtcpSourceDescriptionPacket :RtcpPacket + { /// + /// Get the list of source descriptions. + /// + public Collection Descriptions; + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + Descriptions = new Collection(); + + int index = 4; + + while (Descriptions.Count < ReportCount) + { + SourceDescriptionBlock descriptionBlock = new SourceDescriptionBlock(); + descriptionBlock.Process(buffer, offset + index); + Descriptions.Add(descriptionBlock); + index += descriptionBlock.BlockLength; + } + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Source Description.\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("Descriptions : {0} .\n", Descriptions); + + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs new file mode 100644 index 000000000..bf56087cd --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs @@ -0,0 +1,65 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System.Collections.ObjectModel; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + class SourceDescriptionBlock + { + /// + /// Get the length of the block. + /// + public int BlockLength { get { return (blockLength + (blockLength % 4)); } } + + /// + /// Get the synchronization source. + /// + public string SynchronizationSource { get; private set; } + /// + /// Get the list of source descriptioni items. + /// + public Collection Items; + + private int blockLength; + + public void Process(byte[] buffer, int offset) + { + SynchronizationSource = Utils.ConvertBytesToString(buffer, offset, 4); + Items = new Collection(); + int index = 4; + bool done = false; + do + { + SourceDescriptionItem item = new SourceDescriptionItem(); + item.Process(buffer, offset + index); + + if (item.Type != 0) + { + Items.Add(item); + index += item.ItemLength; + blockLength += item.ItemLength; + } + else + { + blockLength++; + done = true; + } + } + while (!done); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs new file mode 100644 index 000000000..5dd033642 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs @@ -0,0 +1,60 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + /// + /// The class that describes a source description item. + /// + public class SourceDescriptionItem + { + /// + /// Get the type. + /// + public int Type { get; private set; } + /// + /// Get the text. + /// + public string Text { get; private set; } + + /// + /// Get the length of the item. + /// + public int ItemLength { get { return (Text.Length + 2); } } + + /// + /// Initialize a new instance of the SourceDescriptionItem class. + /// + public SourceDescriptionItem() { } + + /// + /// Unpack the data in a packet. + /// + /// The buffer containing the packet. + /// The offset to the first byte of the packet within the buffer. + /// An ErrorSpec instance if an error occurs; null otherwise. + public void Process(byte[] buffer, int offset) + { + Type = buffer[offset]; + if (Type != 0) + { + int length = buffer[offset + 1]; + Text = Utils.ConvertBytesToString(buffer, offset + 2, length); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs new file mode 100644 index 000000000..ea6a9ba6a --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs @@ -0,0 +1,160 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtp +{ + public class RtpListener + { + private readonly ILogger _logger; + private AutoResetEvent _rtpListenerThreadStopEvent; + private Thread _rtpListenerThread; + private UdpClient _udpClient; + private IPEndPoint _multicastEndPoint; + private IPEndPoint _serverEndPoint; + private TransmissionMode _transmissionMode; + public RtpListener(String address, int port,TransmissionMode mode,ILogger logger) + { + _logger = logger; + _transmissionMode = mode; + switch (mode) + { + case TransmissionMode.Unicast: + _udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(address), port)); + _serverEndPoint = new IPEndPoint(IPAddress.Any, 0); + break; + case TransmissionMode.Multicast: + _multicastEndPoint = new IPEndPoint(IPAddress.Parse(address), port); + _serverEndPoint = null; + _udpClient = new UdpClient(); + _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); + _udpClient.ExclusiveAddressUse = false; + _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, _multicastEndPoint.Port)); + _udpClient.JoinMulticastGroup(_multicastEndPoint.Address); + break; + } + //StartRtpListenerThread(); + } + public void StartRtpListenerThread() + { + // Kill the existing thread if it is in "zombie" state. + if (_rtpListenerThread != null && !_rtpListenerThread.IsAlive) + { + StopRtpListenerThread(); + } + + if (_rtpListenerThread == null) + { + _logger.Info("SAT>IP : starting new RTP listener thread"); + _rtpListenerThreadStopEvent = new AutoResetEvent(false); + _rtpListenerThread = new Thread(new ThreadStart(RtpListenerThread)); + _rtpListenerThread.Name = string.Format("SAT>IP tuner RTP listener"); + _rtpListenerThread.IsBackground = true; + _rtpListenerThread.Priority = ThreadPriority.Lowest; + _rtpListenerThread.Start(); + } + } + + public void StopRtpListenerThread() + { + if (_rtpListenerThread != null) + { + if (!_rtpListenerThread.IsAlive) + { + _logger.Info("SAT>IP : aborting old RTP listener thread"); + _rtpListenerThread.Abort(); + } + else + { + _rtpListenerThreadStopEvent.Set(); + if (!_rtpListenerThread.Join(400 * 2)) + { + _logger.Info("SAT>IP : failed to join RTP listener thread, aborting thread"); + _rtpListenerThread.Abort(); + } + } + _rtpListenerThread = null; + if (_rtpListenerThreadStopEvent != null) + { + _rtpListenerThreadStopEvent.Close(); + _rtpListenerThreadStopEvent = null; + } + } + } + + private void RtpListenerThread() + { + try + { + try + { + + while (!_rtpListenerThreadStopEvent.WaitOne(1)) + { + byte[] receivedbytes = _udpClient.Receive(ref _serverEndPoint); + RtpPacket packet = RtpPacket.Decode(receivedbytes); + OnPacketReceived(new RtpPacketReceivedArgs(packet)); + } + } + finally + { + switch (_transmissionMode) + { + case TransmissionMode.Multicast: + _udpClient.DropMulticastGroup(_multicastEndPoint.Address); + _udpClient.Close(); + break; + case TransmissionMode.Unicast: + _udpClient.Close(); + break; + } + } + } + catch (ThreadAbortException) + { + } + catch (Exception ex) + { + _logger.Info(string.Format("SAT>IP : RTP listener thread exception"), ex); + return; + } + _logger.Info("SAT>IP : RTP listener thread stopping"); + } + public delegate void PacketReceivedHandler(object sender, RtpPacketReceivedArgs e); + public event PacketReceivedHandler PacketReceived; + public class RtpPacketReceivedArgs : EventArgs + { + public RtpPacket Packet { get; private set; } + + public RtpPacketReceivedArgs(RtpPacket packet) + { + Packet = packet; + } + } + protected void OnPacketReceived(RtpPacketReceivedArgs args) + { + if (PacketReceived != null) + { + PacketReceived(this, args); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs new file mode 100644 index 000000000..489d7f087 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs @@ -0,0 +1,116 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ + +using System; +using System.Collections.ObjectModel; +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtp +{ + public class RtpPacket + { + private static int MinHeaderLength = 12; + public int HeaderSize = MinHeaderLength; + public int Version { get; set; } + public Boolean Padding { get; set; } + public Boolean Extension { get; set; } + public int ContributingSourceCount { get; set; } + public Boolean Marker { get; set; } + public int PayloadType { get; set; } + public int SequenceNumber { get; set; } + public long TimeStamp { get; set; } + public long SynchronizationSource { get; set; } + public Collection ContributingSources { get; private set; } + public int ExtensionHeaderId = 0; + public int ExtensionHeaderLength = 0; + public bool HasPayload { get; set; } + public byte[] Payload { get; set; } + public RtpPacket() + { + + } + public static RtpPacket Decode(byte[] buffer) + { + var packet = new RtpPacket(); + packet.Version = buffer[0] >> 6; + packet.Padding = (buffer[0] & 0x20) != 0; + packet.Extension = (buffer[0] & 0x10) != 0; + packet.ContributingSourceCount = buffer[0] & 0x0f; + + packet.Marker = (buffer[1] & 0x80) != 0; + packet.PayloadType = buffer[1] & 0x7f; + + packet.SequenceNumber = Utils.Convert2BytesToInt(buffer, 2); + packet.TimeStamp = Utils.Convert4BytesToLong(buffer, 4); + packet.SynchronizationSource = Utils.Convert4BytesToLong(buffer, 8); + + int index = 12; + + if (packet.ContributingSourceCount != 0) + { + packet.ContributingSources = new Collection(); + + while (packet.ContributingSources.Count < packet.ContributingSourceCount) + { + packet.ContributingSources.Add(Utils.ConvertBytesToString(buffer, index, 4)); + index += 4; + } + } + var dataoffset = 0; + if (!packet.Extension) + dataoffset = index; + else + { + packet.ExtensionHeaderId = Utils.Convert2BytesToInt(buffer, index); + packet.ExtensionHeaderLength = Utils.Convert2BytesToInt(buffer, index + 2); + dataoffset = index + packet.ExtensionHeaderLength + 4; + } + + var dataLength = buffer.Length - dataoffset; + if (dataLength > dataoffset) + { + packet.HasPayload = true; + packet.Payload = new byte[dataLength]; + Array.Copy(buffer, dataoffset, packet.Payload, 0, dataLength); + } + else + { + packet.HasPayload = false; + } + return packet; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("RTP Packet"); + sb.AppendFormat("Version: {0} \n", Version); + sb.AppendFormat("Padding: {0} \n", Padding); + sb.AppendFormat("Extension: {0} \n", Extension); + sb.AppendFormat("Contributing Source Identifiers Count: {0} \n", ContributingSourceCount); + sb.AppendFormat("Marker: {0} \n", Marker); + sb.AppendFormat("Payload Type: {0} \n", PayloadType); + sb.AppendFormat("Sequence Number: {0} \n", SequenceNumber); + sb.AppendFormat("Timestamp: {0} .\n", TimeStamp); + sb.AppendFormat("Synchronization Source Identifier: {0} \n", SynchronizationSource); + sb.AppendFormat("\n"); + return sb.ToString(); + } + + } + +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs index ba5c604ed..cb0e573da 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs @@ -237,7 +237,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp string modelurl = ""; string serialnumber = ""; string presentationurl = ""; - string capabilities = ""; + //string capabilities = ""; string m3u = ""; var document = XDocument.Load(locationUri.AbsoluteUri); var xnm = new XmlNamespaceManager(new NameTable()); @@ -284,20 +284,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp var capabilitiesElement = deviceElement.Element(n1 + "X_SATIPCAP"); if (capabilitiesElement != null) { - //_capabilities = capabilitiesElement.Value; - //if (capabilitiesElement.Value.Contains(',')) - //{ - // string[] capabilities = capabilitiesElement.Value.Split(','); - // foreach (var capability in capabilities) - // { - // ReadCapability(capability); - // } - //} - //else - //{ - // ReadCapability(capabilitiesElement.Value); - //} + //_capabilities = capabilitiesElement.Value; + if (capabilitiesElement.Value.Contains(',')) + { + string[] capabilities = capabilitiesElement.Value.Split(','); + foreach (var capability in capabilities) + { + ReadCapability(capability); + } } + else + { + ReadCapability(capabilitiesElement.Value); + } + } else { _supportsDVBS = true; @@ -314,8 +314,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp Id = uniquedevicename, IsEnabled = true, Type = SatIpHost.DeviceType, - Tuners = 1, - TunersAvailable = 1, + Tuners = _tunerCountDVBS, + TunersAvailable = _tunerCountDVBS, M3UUrl = m3u }; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs new file mode 100644 index 000000000..71d7656d9 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs @@ -0,0 +1,25 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp +{ + public enum TransmissionMode + { + Unicast, + Multicast + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs new file mode 100644 index 000000000..3595e4b0a --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs @@ -0,0 +1,90 @@ +/* + Copyright (C) <2007-2016> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see . +*/ +using System; +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp +{ + public class Utils + { + public static int Convert2BytesToInt(byte[] buffer, int offset) + { + int temp = (int)buffer[offset]; + temp = (temp * 256) + buffer[offset + 1]; + + return (temp); + } + public static int Convert3BytesToInt(byte[] buffer, int offset) + { + int temp = (int)buffer[offset]; + temp = (temp * 256) + buffer[offset + 1]; + temp = (temp * 256) + buffer[offset + 2]; + + return (temp); + } + public static int Convert4BytesToInt(byte[] buffer, int offset) + { + int temp =(int)buffer[offset]; + temp = (temp * 256) + buffer[offset + 1]; + temp = (temp * 256) + buffer[offset + 2]; + temp = (temp * 256) + buffer[offset + 3]; + + return (temp); + } + public static long Convert4BytesToLong(byte[] buffer, int offset) + { + long temp = 0; + + for (int index = 0; index < 4; index++) + temp = (temp * 256) + buffer[offset + index]; + + return (temp); + } + public static long Convert8BytesToLong(byte[] buffer, int offset) + { + long temp = 0; + + for (int index = 0; index < 8; index++) + temp = (temp * 256) + buffer[offset + index]; + + return (temp); + } + public static string ConvertBytesToString(byte[] buffer, int offset, int length) + { + StringBuilder reply = new StringBuilder(4); + for (int index = 0; index < length; index++) + reply.Append((char)buffer[offset + index]); + return (reply.ToString()); + } + public static DateTime NptTimestampToDateTime(long nptTimestamp) { return NptTimestampToDateTime((uint)((nptTimestamp >> 32) & 0xFFFFFFFF), (uint)(nptTimestamp & 0xFFFFFFFF),null); } + + public static DateTime NptTimestampToDateTime(uint seconds, uint fractions, DateTime? epoch ) + { + ulong ticks =(ulong)((seconds * TimeSpan.TicksPerSecond) + ((fractions * TimeSpan.TicksPerSecond) / 0x100000000L)); + if (epoch.HasValue) return epoch.Value + TimeSpan.FromTicks((Int64)ticks); + return (seconds & 0x80000000L) == 0 ? UtcEpoch2036 + TimeSpan.FromTicks((Int64)ticks) : UtcEpoch1900 + TimeSpan.FromTicks((Int64)ticks); + } + + //When the First Epoch will wrap (The real Y2k) + public static DateTime UtcEpoch2036 = new DateTime(2036, 2, 7, 6, 28, 16, DateTimeKind.Utc); + + public static DateTime UtcEpoch1900 = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static DateTime UtcEpoch1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 6879c3f40..9a92cf896 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -251,6 +251,18 @@ + + + + + + + + + + + + @@ -258,6 +270,8 @@ + + -- cgit v1.2.3 From 116b234ea658be22a4e010f46f2dd675d151b14b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 2 Sep 2016 16:42:07 -0400 Subject: automate loopback exception for windows store app --- .../Udp/UdpServer.cs | 26 -- MediaBrowser.ServerApplication/MainStartup.cs | 8 + .../MediaBrowser.ServerApplication.csproj | 1 + .../Native/LoopbackUtil.cs | 358 +++++++++++++++++++++ MediaBrowser.sln | 2 +- 5 files changed, 368 insertions(+), 27 deletions(-) create mode 100644 MediaBrowser.ServerApplication/Native/LoopbackUtil.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs index 32992b9b2..9d60dcd40 100644 --- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs +++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs @@ -51,7 +51,6 @@ namespace MediaBrowser.Server.Implementations.Udp AddMessageResponder("who is EmbyServer?", RespondToV2Message); AddMessageResponder("who is MediaBrowserServer_v2?", RespondToV2Message); - AddMessageResponder("who is MediaBrowserServer?", RespondToV1Message); } private void AddMessageResponder(string message, Action responder) @@ -94,31 +93,6 @@ namespace MediaBrowser.Server.Implementations.Udp } } - private async void RespondToV1Message(string endpoint, Encoding encoding) - { - var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false); - - if (!string.IsNullOrEmpty(localUrl)) - { - // This is how we did the old v1 search, so need to strip off the protocol - var index = localUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); - - if (index != -1) - { - localUrl = localUrl.Substring(index + 3); - } - - // Send a response back with our ip address and port - var response = String.Format("MediaBrowserServer|{0}", localUrl); - - await SendAsync(Encoding.UTF8.GetBytes(response), endpoint); - } - else - { - _logger.Warn("Unable to respond to udp request because the local ip address could not be determined."); - } - } - private async void RespondToV2Message(string endpoint, Encoding encoding) { var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false); diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 8f153f33f..67b2a3b47 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -292,6 +292,14 @@ namespace MediaBrowser.ServerApplication ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); } + try + { + LoopUtil.Run(); + } + catch (Exception ex) + { + logManager.GetLogger("LoopUtil").ErrorException("Error in LoopUtil", ex); + } var task = _appHost.Init(initProgress); Task.WaitAll(task); diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 9c5470358..a32312493 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -119,6 +119,7 @@ + diff --git a/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs b/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs new file mode 100644 index 000000000..dea77d375 --- /dev/null +++ b/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs @@ -0,0 +1,358 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.ServerApplication.Native +{ + /// + /// http://blogs.msdn.com/b/fiddler/archive/2011/12/10/fiddler-windows-8-apps-enable-LoopUtil-network-isolation-exemption.aspx + /// + public class LoopUtil + { + //http://msdn.microsoft.com/en-us/library/windows/desktop/aa379595(v=vs.85).aspx + [StructLayout(LayoutKind.Sequential)] + internal struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + public uint Attributes; + } + + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct INET_FIREWALL_AC_CAPABILITIES + { + public uint count; + public IntPtr capabilities; //SID_AND_ATTRIBUTES + } + + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct INET_FIREWALL_AC_BINARIES + { + public uint count; + public IntPtr binaries; + } + + [StructLayoutAttribute(LayoutKind.Sequential)] + internal struct INET_FIREWALL_APP_CONTAINER + { + internal IntPtr appContainerSid; + internal IntPtr userSid; + [MarshalAs(UnmanagedType.LPWStr)] + public string appContainerName; + [MarshalAs(UnmanagedType.LPWStr)] + public string displayName; + [MarshalAs(UnmanagedType.LPWStr)] + public string description; + internal INET_FIREWALL_AC_CAPABILITIES capabilities; + internal INET_FIREWALL_AC_BINARIES binaries; + [MarshalAs(UnmanagedType.LPWStr)] + public string workingDirectory; + [MarshalAs(UnmanagedType.LPWStr)] + public string packageFullName; + } + + + // Call this API to free the memory returned by the Enumeration API + [DllImport("FirewallAPI.dll")] + internal static extern void NetworkIsolationFreeAppContainers(IntPtr pACs); + + // Call this API to load the current list of LoopUtil-enabled AppContainers + [DllImport("FirewallAPI.dll")] + internal static extern uint NetworkIsolationGetAppContainerConfig(out uint pdwCntACs, out IntPtr appContainerSids); + + // Call this API to set the LoopUtil-exemption list + [DllImport("FirewallAPI.dll")] + private static extern uint NetworkIsolationSetAppContainerConfig(uint pdwCntACs, SID_AND_ATTRIBUTES[] appContainerSids); + + + // Use this API to convert a string SID into an actual SID + [DllImport("advapi32.dll", SetLastError = true)] + internal static extern bool ConvertStringSidToSid(string strSid, out IntPtr pSid); + + [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)] + static extern bool ConvertSidToStringSid( + [MarshalAs(UnmanagedType.LPArray)] byte[] pSID, + out IntPtr ptrSid); + + [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)] + static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid); + + // Use this API to convert a string reference (e.g. "@{blah.pri?ms-resource://whatever}") into a plain string + [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf); + + // Call this API to enumerate all of the AppContainers on the system + [DllImport("FirewallAPI.dll")] + internal static extern uint NetworkIsolationEnumAppContainers(uint Flags, out uint pdwCntPublicACs, out IntPtr ppACs); + // DWORD NetworkIsolationEnumAppContainers( + // _In_ DWORD Flags, + // _Out_ DWORD *pdwNumPublicAppCs, + // _Out_ PINET_FIREWALL_APP_CONTAINER *ppPublicAppCs + //); + + //http://msdn.microsoft.com/en-gb/library/windows/desktop/hh968116.aspx + enum NETISO_FLAG + { + NETISO_FLAG_FORCE_COMPUTE_BINARIES = 0x1, + NETISO_FLAG_MAX = 0x2 + } + + + public class AppContainer + { + public String appContainerName { get; set; } + public String displayName { get; set; } + public String workingDirectory { get; set; } + public String StringSid { get; set; } + public List capabilities { get; set; } + public bool LoopUtil { get; set; } + + public AppContainer(String _appContainerName, String _displayName, String _workingDirectory, IntPtr _sid) + { + this.appContainerName = _appContainerName; + this.displayName = _displayName; + this.workingDirectory = _workingDirectory; + String tempSid; + ConvertSidToStringSid(_sid, out tempSid); + this.StringSid = tempSid; + } + } + + internal List _AppList; + internal List _AppListConfig; + public List Apps = new List(); + internal IntPtr _pACs; + + public LoopUtil() + { + LoadApps(); + } + + public void LoadApps() + { + Apps.Clear(); + _pACs = IntPtr.Zero; + //Full List of Apps + _AppList = PI_NetworkIsolationEnumAppContainers(); + //List of Apps that have LoopUtil enabled. + _AppListConfig = PI_NetworkIsolationGetAppContainerConfig(); + foreach (var PI_app in _AppList) + { + AppContainer app = new AppContainer(PI_app.appContainerName, PI_app.displayName, PI_app.workingDirectory, PI_app.appContainerSid); + + var app_capabilities = LoopUtil.getCapabilites(PI_app.capabilities); + if (app_capabilities.Count > 0) + { + //var sid = new SecurityIdentifier(app_capabilities[0], 0); + + IntPtr arrayValue = IntPtr.Zero; + //var b = LoopUtil.ConvertStringSidToSid(app_capabilities[0].Sid, out arrayValue); + //string mysid; + //var b = LoopUtil.ConvertSidToStringSid(app_capabilities[0].Sid, out mysid); + } + app.LoopUtil = CheckLoopback(PI_app.appContainerSid); + Apps.Add(app); + } + } + private bool CheckLoopback(IntPtr intPtr) + { + foreach (SID_AND_ATTRIBUTES item in _AppListConfig) + { + string left, right; + ConvertSidToStringSid(item.Sid, out left); + ConvertSidToStringSid(intPtr, out right); + + if (left == right) + { + return true; + } + } + return false; + } + + private bool CreateExcemptions(string appName) + { + var hasChanges = false; + + foreach (var app in Apps) + { + if ((app.appContainerName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1 || + (app.displayName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1) + { + if (!app.LoopUtil) + { + app.LoopUtil = true; + hasChanges = true; + } + } + } + + return hasChanges; + } + + public static void Run() + { + var util = new LoopUtil(); + util.LoadApps(); + + var hasChanges = util.CreateExcemptions("Emby"); + + if (hasChanges) + { + util.SaveLoopbackState(); + } + util.SaveLoopbackState(); + } + + private static List getCapabilites(INET_FIREWALL_AC_CAPABILITIES cap) + { + List mycap = new List(); + + IntPtr arrayValue = cap.capabilities; + + var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES)); + for (var i = 0; i < cap.count; i++) + { + var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES)); + mycap.Add(cur); + arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize)); + } + + return mycap; + + } + + private static List getContainerSID(INET_FIREWALL_AC_CAPABILITIES cap) + { + List mycap = new List(); + + IntPtr arrayValue = cap.capabilities; + + var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES)); + for (var i = 0; i < cap.count; i++) + { + var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES)); + mycap.Add(cur); + arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize)); + } + + return mycap; + + } + + private static List PI_NetworkIsolationGetAppContainerConfig() + { + + IntPtr arrayValue = IntPtr.Zero; + uint size = 0; + var list = new List(); + + // Pin down variables + GCHandle handle_pdwCntPublicACs = GCHandle.Alloc(size, GCHandleType.Pinned); + GCHandle handle_ppACs = GCHandle.Alloc(arrayValue, GCHandleType.Pinned); + + uint retval = NetworkIsolationGetAppContainerConfig(out size, out arrayValue); + + var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES)); + for (var i = 0; i < size; i++) + { + var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES)); + list.Add(cur); + arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize)); + } + + //release pinned variables. + handle_pdwCntPublicACs.Free(); + handle_ppACs.Free(); + + return list; + + + } + + private List PI_NetworkIsolationEnumAppContainers() + { + + IntPtr arrayValue = IntPtr.Zero; + uint size = 0; + var list = new List(); + + // Pin down variables + GCHandle handle_pdwCntPublicACs = GCHandle.Alloc(size, GCHandleType.Pinned); + GCHandle handle_ppACs = GCHandle.Alloc(arrayValue, GCHandleType.Pinned); + + //uint retval2 = NetworkIsolationGetAppContainerConfig( out size, out arrayValue); + + uint retval = NetworkIsolationEnumAppContainers((Int32)NETISO_FLAG.NETISO_FLAG_MAX, out size, out arrayValue); + _pACs = arrayValue; //store the pointer so it can be freed when we close the form + + var structSize = Marshal.SizeOf(typeof(INET_FIREWALL_APP_CONTAINER)); + for (var i = 0; i < size; i++) + { + var cur = (INET_FIREWALL_APP_CONTAINER)Marshal.PtrToStructure(arrayValue, typeof(INET_FIREWALL_APP_CONTAINER)); + list.Add(cur); + arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize)); + } + + //release pinned variables. + handle_pdwCntPublicACs.Free(); + handle_ppACs.Free(); + + return list; + + + } + + public bool SaveLoopbackState() + { + var countEnabled = CountEnabledLoopUtil(); + SID_AND_ATTRIBUTES[] arr = new SID_AND_ATTRIBUTES[countEnabled]; + int count = 0; + + for (int i = 0; i < Apps.Count; i++) + { + if (Apps[i].LoopUtil) + { + arr[count].Attributes = 0; + //TO DO: + IntPtr ptr; + ConvertStringSidToSid(Apps[i].StringSid, out ptr); + arr[count].Sid = ptr; + count++; + } + + } + + + if (NetworkIsolationSetAppContainerConfig((uint)countEnabled, arr) == 0) + { + return true; + } + else + { return false; } + + } + + private int CountEnabledLoopUtil() + { + var count = 0; + for (int i = 0; i < Apps.Count; i++) + { + if (Apps[i].LoopUtil) + { + count++; + } + + } + return count; + } + + public void FreeResources() + { + NetworkIsolationFreeAppContainers(_pACs); + } + + } +} diff --git a/MediaBrowser.sln b/MediaBrowser.sln index c6068f536..0b9dd90cd 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F0E0E64C-2A6F-4E35-9533-D53AC07C2CD1}" EndProject -- cgit v1.2.3 From 2d7c82d0dd8b51af58a2e187ef30639fdbfd918a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 3 Sep 2016 13:16:36 -0400 Subject: add loopback handler --- MediaBrowser.Api/ItemUpdateService.cs | 5 +- MediaBrowser.Controller/IServerApplicationHost.cs | 2 + .../MediaBrowser.LocalMetadata.csproj | 2 - .../Parsers/SeasonXmlParser.cs | 48 ------------------ .../Providers/SeasonXmlProvider.cs | 46 ----------------- .../Subtitles/SubtitleEncoder.cs | 5 +- .../Library/Resolvers/TV/SeriesResolver.cs | 15 ++---- .../Udp/UdpServer.cs | 57 ++++++++++++++-------- MediaBrowser.Server.Mono/Native/BaseMonoApp.cs | 5 ++ .../ApplicationHost.cs | 5 ++ MediaBrowser.Server.Startup.Common/INativeApp.cs | 2 + MediaBrowser.ServerApplication/MainStartup.cs | 9 ---- .../Native/LoopbackUtil.cs | 4 +- .../Native/WindowsApp.cs | 5 ++ MediaBrowser.sln | 3 ++ Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 18 files changed, 79 insertions(+), 144 deletions(-) delete mode 100644 MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs delete mode 100644 MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 2778cfe29..cda7ce0c6 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -70,12 +70,13 @@ namespace MediaBrowser.Api Cultures = _localizationManager.GetCultures().ToList() }; - if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName)) + if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName) && + item.SourceType == SourceType.Library) { var inheritedContentType = _libraryManager.GetInheritedContentType(item); var configuredContentType = _libraryManager.GetConfiguredContentType(item); - if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(configuredContentType)) + if (string.IsNullOrWhiteSpace(inheritedContentType) || !string.IsNullOrWhiteSpace(configuredContentType)) { info.ContentTypeOptions = GetContentTypeOptions(true); info.ContentType = configuredContentType; diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index f4c0e7658..d6744e804 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -89,5 +89,7 @@ namespace MediaBrowser.Controller string GetLocalApiUrl(IPAddress ipAddress); void LaunchUrl(string url); + + void EnableLoopback(string appName); } } diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 134385765..ac127458e 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -63,7 +63,6 @@ - @@ -75,7 +74,6 @@ - diff --git a/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs deleted file mode 100644 index 9769ffc72..000000000 --- a/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Xml; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.LocalMetadata.Parsers -{ - public class SeasonXmlParser : BaseItemXmlParser - { - public SeasonXmlParser(ILogger logger, IProviderManager providerManager) - : base(logger, providerManager) - { - } - - /// - /// Fetches the data from XML node. - /// - /// The reader. - /// The result. - protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult result) - { - var item = result.Item; - - switch (reader.Name) - { - case "SeasonNumber": - { - var number = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(number)) - { - int num; - - if (int.TryParse(number, out num)) - { - item.IndexNumber = num; - } - } - break; - } - - default: - base.FetchDataFromXmlNode(reader, result); - break; - } - } - } -} diff --git a/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs deleted file mode 100644 index 75af906cb..000000000 --- a/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.IO; -using System.Threading; -using CommonIO; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Providers; -using MediaBrowser.LocalMetadata.Parsers; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.LocalMetadata.Providers -{ - /// - /// Class SeriesProviderFromXml - /// - public class SeasonXmlProvider : BaseXmlProvider, IHasOrder - { - private readonly ILogger _logger; - private readonly IProviderManager _providerManager; - - public SeasonXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) - : base(fileSystem) - { - _logger = logger; - _providerManager = providerManager; - } - - protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) - { - new SeasonXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken); - } - - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) - { - return directoryService.GetFile(Path.Combine(info.Path, "season.xml")); - } - - public int Order - { - get - { - // After Xbmc - return 1; - } - } - } -} - diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index a63aca11b..dc5e69283 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -818,14 +818,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles public string GetSubtitleFileCharacterSetFromLanguage(string language) { + // https://developer.xamarin.com/api/type/System.Text.Encoding/ + switch (language.ToLower()) { + case "hun": + return "windows-1252"; case "pol": case "cze": case "ces": case "slo": case "slk": - case "hun": case "slv": case "srp": case "hrv": diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index aefb29f1a..4c6254330 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -54,14 +54,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { if (args.IsDirectory) { + if (args.HasParent()) + { + return null; + } + var collectionType = args.GetCollectionType(); if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - if (args.HasParent()) - { - return null; - } - var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path); if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { @@ -76,11 +76,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { if (string.IsNullOrWhiteSpace(collectionType)) { - if (args.HasParent()) - { - return null; - } - if (args.Parent.IsRoot) { return null; diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs index 9d60dcd40..241ceaf65 100644 --- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs +++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Server.Implementations.Udp private bool _isDisposed; - private readonly List>> _responders = new List>>(); + private readonly List>> _responders = new List>>(); private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _json; @@ -49,42 +49,35 @@ namespace MediaBrowser.Server.Implementations.Udp _appHost = appHost; _json = json; - AddMessageResponder("who is EmbyServer?", RespondToV2Message); - AddMessageResponder("who is MediaBrowserServer_v2?", RespondToV2Message); + AddMessageResponder("who is EmbyServer?", true, RespondToV2Message); + AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message); } - private void AddMessageResponder(string message, Action responder) + private void AddMessageResponder(string message, bool isSubstring, Func responder) { - var expectedMessageBytes = Encoding.UTF8.GetBytes(message); - - _responders.Add(new Tuple>(message, expectedMessageBytes, responder)); + _responders.Add(new Tuple>(message, isSubstring, responder)); } /// /// Raises the event. /// /// The instance containing the event data. - private void OnMessageReceived(UdpMessageReceivedEventArgs e) + private async void OnMessageReceived(UdpMessageReceivedEventArgs e) { - var responder = _responders.FirstOrDefault(i => i.Item2.SequenceEqual(e.Bytes)); var encoding = Encoding.UTF8; + var responder = GetResponder(e.Bytes, encoding); if (responder == null) { - var text = Encoding.Unicode.GetString(e.Bytes); - responder = _responders.FirstOrDefault(i => string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase)); - - if (responder != null) - { - encoding = Encoding.Unicode; - } + encoding = Encoding.Unicode; + responder = GetResponder(e.Bytes, encoding); } if (responder != null) { try { - responder.Item3(e.RemoteEndPoint, encoding); + await responder.Item2.Item3(responder.Item1, e.RemoteEndPoint, encoding).ConfigureAwait(false); } catch (Exception ex) { @@ -93,8 +86,34 @@ namespace MediaBrowser.Server.Implementations.Udp } } - private async void RespondToV2Message(string endpoint, Encoding encoding) + private Tuple>> GetResponder(byte[] bytes, Encoding encoding) + { + var text = encoding.GetString(bytes); + var responder = _responders.FirstOrDefault(i => + { + if (i.Item2) + { + return text.IndexOf(i.Item1, StringComparison.OrdinalIgnoreCase) != -1; + } + return string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase); + }); + + if (responder == null) + { + return null; + } + return new Tuple>>(text, responder); + } + + private async Task RespondToV2Message(string messageText, string endpoint, Encoding encoding) { + var parts = messageText.Split('|'); + + if (parts.Length > 1) + { + _appHost.EnableLoopback(parts[1]); + } + var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false); if (!string.IsNullOrEmpty(localUrl)) @@ -106,7 +125,7 @@ namespace MediaBrowser.Server.Implementations.Udp Name = _appHost.FriendlyName }; - await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint); + await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint).ConfigureAwait(false); } else { diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index faf3ba37e..2185f4804 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -278,6 +278,11 @@ namespace MediaBrowser.Server.Mono.Native return info; } + + public void EnableLoopback(string appName) + { + + } } public class NullPowerManagement : IPowerManagement diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 170719b0f..a25bd3812 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -1392,5 +1392,10 @@ namespace MediaBrowser.Server.Startup.Common { NativeApp.LaunchUrl(url); } + + public void EnableLoopback(string appName) + { + NativeApp.EnableLoopback(appName); + } } } diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs index c13d3624e..d2e278a3b 100644 --- a/MediaBrowser.Server.Startup.Common/INativeApp.cs +++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs @@ -107,5 +107,7 @@ namespace MediaBrowser.Server.Startup.Common void LaunchUrl(string url); IDbConnector GetDbConnector(); + + void EnableLoopback(string appName); } } diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 67b2a3b47..8fb0e8f46 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -292,15 +292,6 @@ namespace MediaBrowser.ServerApplication ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); } - try - { - LoopUtil.Run(); - } - catch (Exception ex) - { - logManager.GetLogger("LoopUtil").ErrorException("Error in LoopUtil", ex); - } - var task = _appHost.Init(initProgress); Task.WaitAll(task); diff --git a/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs b/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs index dea77d375..5b260685b 100644 --- a/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs +++ b/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs @@ -192,12 +192,12 @@ namespace MediaBrowser.ServerApplication.Native return hasChanges; } - public static void Run() + public static void Run(string appName) { var util = new LoopUtil(); util.LoadApps(); - var hasChanges = util.CreateExcemptions("Emby"); + var hasChanges = util.CreateExcemptions(appName); if (hasChanges) { diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 3c9c04acb..139471f11 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -203,5 +203,10 @@ namespace MediaBrowser.ServerApplication.Native { ((Process)sender).Dispose(); } + + public void EnableLoopback(string appName) + { + LoopUtil.Run(appName); + } } } \ No newline at end of file diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 90b318492..0b9dd90cd 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -65,6 +65,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" EndProject Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|Mixed Platforms = Debug|Mixed Platforms diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index cfdfc7ef0..06159dadb 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.656 + 3.0.657 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 1ebf4d052..de1af036d 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.656 + 3.0.657 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 630ff8fd2..d21fefc86 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.656 + 3.0.657 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + -- cgit v1.2.3 From 2741f24dffc5ab66e8d19003082ee0bea88c6d72 Mon Sep 17 00:00:00 2001 From: Fredrik Zetterlund Date: Sat, 3 Sep 2016 22:25:21 +0200 Subject: Send response first Respond to message should be highest priority. --- MediaBrowser.Server.Implementations/Udp/UdpServer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs index 241ceaf65..d75ea1e00 100644 --- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs +++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs @@ -109,11 +109,6 @@ namespace MediaBrowser.Server.Implementations.Udp { var parts = messageText.Split('|'); - if (parts.Length > 1) - { - _appHost.EnableLoopback(parts[1]); - } - var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false); if (!string.IsNullOrEmpty(localUrl)) @@ -126,6 +121,11 @@ namespace MediaBrowser.Server.Implementations.Udp }; await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint).ConfigureAwait(false); + + if (parts.Length > 1) + { + _appHost.EnableLoopback(parts[1]); + } } else { -- cgit v1.2.3 From daca521e63e54f94ba778aa522e7292c46557d6c Mon Sep 17 00:00:00 2001 From: Fredrik Zetterlund Date: Sat, 3 Sep 2016 22:28:07 +0200 Subject: Update UdpServer.cs --- MediaBrowser.Server.Implementations/Udp/UdpServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs index d75ea1e00..6dd5de548 100644 --- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs +++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs @@ -122,7 +122,7 @@ namespace MediaBrowser.Server.Implementations.Udp await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint).ConfigureAwait(false); - if (parts.Length > 1) + if (parts.Length > 1) { _appHost.EnableLoopback(parts[1]); } -- cgit v1.2.3 From 9c7eef891b808c60181704046a48dd9cdbe96bae Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 4 Sep 2016 11:01:31 -0400 Subject: add configurable encoding params --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 20 ++++++++++++++++++-- MediaBrowser.Controller/Entities/UserView.cs | 3 ++- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 7 ++++++- MediaBrowser.Model/Configuration/EncodingOptions.cs | 4 ++++ .../Configuration/ServerConfiguration.cs | 2 ++ .../UserViews/DynamicImageProvider.cs | 16 ++++++++++++++-- 6 files changed, 46 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 4af564a5a..89ad02170 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -346,11 +346,27 @@ namespace MediaBrowser.Api.Playback var isVc1 = state.VideoStream != null && string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); + var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); + if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { - param = "-preset superfast"; + if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset)) + { + param = "-preset " + encodingOptions.H264Preset; + } + else + { + param = "-preset superfast"; + } - param += " -crf 23"; + if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51) + { + param = " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture); + } + else + { + param += " -crf 23"; + } } else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 35375e7e6..194ba0ee4 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -113,7 +113,8 @@ namespace MediaBrowser.Controller.Entities { var standaloneTypes = new List { - CollectionType.Playlists + CollectionType.Playlists, + CollectionType.BoxSets }; var collectionFolder = folder as ICollectionFolder; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index ad84ffee8..75d85cd32 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -928,7 +928,12 @@ namespace MediaBrowser.MediaEncoding.Encoder { StartProcess(processWrapper); - ranToCompletion = process.WaitForExit(10000); + var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; + if (timeoutMs <= 0) + { + timeoutMs = 10000; + } + ranToCompletion = process.WaitForExit(timeoutMs); if (!ranToCompletion) { diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 3c03dc12a..d49816803 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration public string HardwareAccelerationType { get; set; } public string EncoderAppPath { get; set; } public string VaapiDevice { get; set; } + public int H264Crf { get; set; } + public string H264Preset { get; set; } public EncodingOptions() { @@ -19,6 +21,8 @@ namespace MediaBrowser.Model.Configuration ThrottleDelaySeconds = 180; EncodingThreadCount = -1; VaapiDevice = "/dev/dri/card0"; + H264Crf = 23; + H264Preset = "superfast"; } } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index b0595b558..44a2ae954 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -207,6 +207,7 @@ namespace MediaBrowser.Model.Configuration public bool EnableChannelView { get; set; } public bool EnableExternalContentInSuggestions { get; set; } + public int ImageExtractionTimeoutMs { get; set; } /// /// Initializes a new instance of the class. /// @@ -216,6 +217,7 @@ namespace MediaBrowser.Model.Configuration Migrations = new string[] { }; CodecsUsed = new string[] { }; SqliteCacheSize = 0; + ImageExtractionTimeoutMs = 10000; EnableLocalizedGuids = true; DisplaySpecialsWithinSeasons = true; diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index 3c75c8a48..f40072897 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -14,17 +14,20 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.LiveTv; namespace MediaBrowser.Server.Implementations.UserViews { public class DynamicImageProvider : BaseDynamicImageProvider { private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; - public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager) + public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) { _userManager = userManager; + _libraryManager = libraryManager; } public override IEnumerable GetSupportedImages(IHasImages item) @@ -50,7 +53,15 @@ namespace MediaBrowser.Server.Implementations.UserViews if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) { - return new List(); + var programs = _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, + ImageTypes = new[] { ImageType.Primary }, + Limit = 30, + IsMovie = true + }).ToList(); + + return GetFinalItems(programs).ToList(); } if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) || @@ -147,6 +158,7 @@ namespace MediaBrowser.Server.Implementations.UserViews CollectionType.MusicVideos, CollectionType.HomeVideos, CollectionType.BoxSets, + CollectionType.LiveTv, CollectionType.Playlists, CollectionType.Photos, string.Empty -- cgit v1.2.3 From 08fb2707be127b7940ab3ea095011e2180b6dabf Mon Sep 17 00:00:00 2001 From: Jose Alacan Date: Wed, 24 Aug 2016 20:08:03 -0400 Subject: Only usermanager should have access to the userrepository --- MediaBrowser.Server.Implementations/Session/SessionManager.cs | 11 ++--------- MediaBrowser.Server.Startup.Common/ApplicationHost.cs | 5 ++--- MediaBrowser.sln | 3 --- 3 files changed, 4 insertions(+), 15 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index b21fcddd4..9243a951e 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -43,11 +43,6 @@ namespace MediaBrowser.Server.Implementations.Session /// private readonly IUserDataManager _userDataRepository; - /// - /// The _user repository - /// - private readonly IUserRepository _userRepository; - /// /// The _logger /// @@ -99,11 +94,10 @@ namespace MediaBrowser.Server.Implementations.Session private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); - public SessionManager(IUserDataManager userDataRepository, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) + public SessionManager(IUserDataManager userDataRepository, ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) { _userDataRepository = userDataRepository; _logger = logger; - _userRepository = userRepository; _libraryManager = libraryManager; _userManager = userManager; _musicManager = musicManager; @@ -253,8 +247,7 @@ namespace MediaBrowser.Server.Implementations.Session { try { - // Save this directly. No need to fire off all the events for this. - await _userRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false); + await _userManager.UpdateUser(user).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index a25bd3812..005b38a7d 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -423,8 +423,7 @@ namespace MediaBrowser.Server.Startup.Common RegisterSingleInstance(UserDataManager); UserRepository = await GetUserRepository().ConfigureAwait(false); - RegisterSingleInstance(UserRepository); - + var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths, NativeApp.GetDbConnector()); DisplayPreferencesRepository = displayPreferencesRepo; RegisterSingleInstance(DisplayPreferencesRepository); @@ -505,7 +504,7 @@ namespace MediaBrowser.Server.Startup.Common MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager, UserDataManager); RegisterSingleInstance(MediaSourceManager); - SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); + SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); RegisterSingleInstance(SessionManager); var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("Dlna"), JsonSerializer, this); diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 0b9dd90cd..90b318492 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -65,9 +65,6 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" EndProject Global - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|Mixed Platforms = Debug|Mixed Platforms -- cgit v1.2.3 From 038cfabca5e057da73df4719a7c0ad5e37400eb5 Mon Sep 17 00:00:00 2001 From: Jose Alacan Date: Wed, 24 Aug 2016 20:12:15 -0400 Subject: Session manager works with the userdata manager not the repository --- .../Session/SessionManager.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 9243a951e..9f52f799b 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Session /// /// The _user data repository /// - private readonly IUserDataManager _userDataRepository; + private readonly IUserDataManager _userDataManager; /// /// The _logger @@ -94,9 +94,9 @@ namespace MediaBrowser.Server.Implementations.Session private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); - public SessionManager(IUserDataManager userDataRepository, ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) + public SessionManager(IUserDataManager userDataManager, ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) { - _userDataRepository = userDataRepository; + _userDataManager = userDataManager; _logger = logger; _libraryManager = libraryManager; _userManager = userManager; @@ -631,7 +631,7 @@ namespace MediaBrowser.Server.Implementations.Session /// Task. private async Task OnPlaybackStart(Guid userId, IHasUserData item) { - var data = _userDataRepository.GetUserData(userId, item); + var data = _userDataManager.GetUserData(userId, item); data.PlayCount++; data.LastPlayedDate = DateTime.UtcNow; @@ -641,7 +641,7 @@ namespace MediaBrowser.Server.Implementations.Session data.Played = true; } - await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false); + await _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None).ConfigureAwait(false); } /// @@ -708,17 +708,17 @@ namespace MediaBrowser.Server.Implementations.Session private async Task OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info) { - var data = _userDataRepository.GetUserData(user.Id, item); + var data = _userDataManager.GetUserData(user.Id, item); var positionTicks = info.PositionTicks; if (positionTicks.HasValue) { - _userDataRepository.UpdatePlayState(item, data, positionTicks.Value); + _userDataManager.UpdatePlayState(item, data, positionTicks.Value); UpdatePlaybackSettings(user, info, data); - await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false); + await _userDataManager.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false); } } @@ -844,11 +844,11 @@ namespace MediaBrowser.Server.Implementations.Session if (!playbackFailed) { - var data = _userDataRepository.GetUserData(userId, item); + var data = _userDataManager.GetUserData(userId, item); if (positionTicks.HasValue) { - playedToCompletion = _userDataRepository.UpdatePlayState(item, data, positionTicks.Value); + playedToCompletion = _userDataManager.UpdatePlayState(item, data, positionTicks.Value); } else { @@ -859,7 +859,7 @@ namespace MediaBrowser.Server.Implementations.Session playedToCompletion = true; } - await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false); + await _userDataManager.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackFinished, CancellationToken.None).ConfigureAwait(false); } return playedToCompletion; -- cgit v1.2.3 From cc7b150b90e238286121d58e618d4f8f7c2b6c49 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 5 Sep 2016 01:39:14 -0400 Subject: update recording layout --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 7 +++- MediaBrowser.Api/Playback/BaseStreamingService.cs | 12 +++++- MediaBrowser.Api/Playback/StreamState.cs | 2 + MediaBrowser.Model/LiveTv/RecordingQuery.cs | 2 + MediaBrowser.Model/System/SystemInfo.cs | 2 + .../LiveTv/LiveTvManager.cs | 48 ++++++++++++++++++++-- .../ApplicationHost.cs | 3 +- .../Migrations/UpdateLevelMigration.cs | 44 +++++++++++++++----- 8 files changed, 103 insertions(+), 17 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 545ac162f..f6817e0e7 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -155,6 +155,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableUserData { get; set; } + public bool? IsMovie { get; set; } + public bool? IsSeries { get; set; } + public GetRecordings() { EnableTotalRecordCount = true; @@ -863,7 +866,9 @@ namespace MediaBrowser.Api.LiveTv Status = request.Status, SeriesTimerId = request.SeriesTimerId, IsInProgress = request.IsInProgress, - EnableTotalRecordCount = request.EnableTotalRecordCount + EnableTotalRecordCount = request.EnableTotalRecordCount, + IsMovie = request.IsMovie, + IsSeries = request.IsSeries }, options, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index e626d49f4..b419250f7 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1786,6 +1786,16 @@ namespace MediaBrowser.Api.Playback // state.SegmentLength = 6; //} + if (state.VideoRequest != null) + { + if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec)) + { + state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i)) + ?? state.SupportedVideoCodecs.FirstOrDefault(); + } + } + if (!string.IsNullOrWhiteSpace(request.AudioCodec)) { state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); @@ -2028,7 +2038,7 @@ namespace MediaBrowser.Api.Playback } // Source and target codecs must match - if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase)) { return false; } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index d7d94c69b..2e92c4a49 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -112,6 +112,7 @@ namespace MediaBrowser.Api.Playback public string OutputVideoSync = "-1"; public List SupportedAudioCodecs { get; set; } + public List SupportedVideoCodecs { get; set; } public string UserAgent { get; set; } public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger) @@ -119,6 +120,7 @@ namespace MediaBrowser.Api.Playback _mediaSourceManager = mediaSourceManager; _logger = logger; SupportedAudioCodecs = new List(); + SupportedVideoCodecs = new List(); PlayableStreamFileNames = new List(); RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index 923d303f8..cedc0b852 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -68,6 +68,8 @@ namespace MediaBrowser.Model.LiveTv /// The fields. public ItemFields[] Fields { get; set; } public bool? EnableImages { get; set; } + public bool? IsMovie { get; set; } + public bool? IsSeries { get; set; } public int? ImageTypeLimit { get; set; } public ImageType[] EnableImageTypes { get; set; } diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 3d1de5b37..c4d056a61 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -8,6 +8,8 @@ namespace MediaBrowser.Model.System /// public class SystemInfo : PublicSystemInfo { + public PackageVersionClass SystemUpdateLevel { get; set; } + /// /// Gets or sets the display name of the operating system. /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 88017aa59..3d9a0f80e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -31,6 +31,8 @@ using CommonIO; using IniParser; using IniParser.Model; using MediaBrowser.Common.Events; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Events; namespace MediaBrowser.Server.Implementations.LiveTv @@ -1423,6 +1425,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv return new QueryResult(); } + var includeItemTypes = new List(); + var excludeItemTypes = new List(); + + if (query.IsMovie.HasValue) + { + if (query.IsMovie.Value) + { + includeItemTypes.Add(typeof (Movie).Name); + } + else + { + excludeItemTypes.Add(typeof(Movie).Name); + } + } + if (query.IsSeries.HasValue) + { + if (query.IsSeries.Value) + { + includeItemTypes.Add(typeof(Episode).Name); + } + else + { + excludeItemTypes.Add(typeof(Episode).Name); + } + } + return _libraryManager.GetItemsResult(new InternalItemsQuery(user) { MediaTypes = new[] { MediaType.Video }, @@ -1433,7 +1461,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv Limit = Math.Min(200, query.Limit ?? int.MaxValue), SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, - EnableTotalRecordCount = query.EnableTotalRecordCount + EnableTotalRecordCount = query.EnableTotalRecordCount, + IncludeItemTypes = includeItemTypes.ToArray(), + ExcludeItemTypes = excludeItemTypes.ToArray() }); } @@ -1492,6 +1522,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv recordings = recordings.Where(i => i.Status == val); } + if (query.IsMovie.HasValue) + { + var val = query.IsMovie.Value; + recordings = recordings.Where(i => i.IsMovie == val); + } + + if (query.IsSeries.HasValue) + { + var val = query.IsSeries.Value; + recordings = recordings.Where(i => i.IsSeries == val); + } + if (!string.IsNullOrEmpty(query.SeriesTimerId)) { var guid = new Guid(query.SeriesTimerId); @@ -1950,16 +1992,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.Number = channel.Number; dto.ChannelNumber = channel.Number; dto.ChannelType = channel.ChannelType; - dto.ServiceName = GetService(channel).Name; + dto.ServiceName = channel.ServiceName; if (options.Fields.Contains(ItemFields.MediaSources)) { dto.MediaSources = channel.GetMediaSources(true).ToList(); } - var channelIdString = channel.Id.ToString("N"); if (options.AddCurrentProgram) { + var channelIdString = channel.Id.ToString("N"); var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString)); if (currentProgram != null) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index a25bd3812..86c5c06bc 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -1096,7 +1096,8 @@ namespace MediaBrowser.Server.Startup.Common LocalAddress = localAddress, SupportsLibraryMonitor = SupportsLibraryMonitor, EncoderLocationType = MediaEncoder.EncoderLocationType, - SystemArchitecture = NativeApp.Environment.SystemArchitecture + SystemArchitecture = NativeApp.Environment.SystemArchitecture, + SystemUpdateLevel = ConfigurationManager.CommonConfiguration.SystemUpdateLevel }; } diff --git a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs index 1c90a7438..de898e66c 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Implementations.Updates; @@ -55,21 +56,39 @@ namespace MediaBrowser.Server.Startup.Common.Migrations } } - private async Task CheckVersion(Version currentVersion, PackageVersionClass updateLevel, CancellationToken cancellationToken) + private async Task CheckVersion(Version currentVersion, PackageVersionClass currentUpdateLevel, CancellationToken cancellationToken) { var releases = await new GithubUpdater(_httpClient, _jsonSerializer, TimeSpan.FromMinutes(3)) .GetLatestReleases("MediaBrowser", "Emby", _releaseAssetFilename, cancellationToken).ConfigureAwait(false); - var newUpdateLevel = updateLevel; + var newUpdateLevel = GetNewUpdateLevel(currentVersion, currentUpdateLevel, releases); + + if (newUpdateLevel != currentUpdateLevel) + { + _config.Configuration.SystemUpdateLevel = newUpdateLevel; + _config.SaveConfiguration(); + } + } + + private PackageVersionClass GetNewUpdateLevel(Version currentVersion, PackageVersionClass currentUpdateLevel, List releases) + { + var newUpdateLevel = currentUpdateLevel; // If the current version is later than current stable, set the update level to beta if (releases.Count >= 1) { var release = releases[0]; var version = ParseVersion(release.tag_name); - if (version != null && currentVersion > version) + if (version != null) { - newUpdateLevel = PackageVersionClass.Beta; + if (currentVersion > version) + { + newUpdateLevel = PackageVersionClass.Beta; + } + else + { + return PackageVersionClass.Release; + } } } @@ -78,17 +97,20 @@ namespace MediaBrowser.Server.Startup.Common.Migrations { var release = releases[1]; var version = ParseVersion(release.tag_name); - if (version != null && currentVersion > version) + if (version != null) { - newUpdateLevel = PackageVersionClass.Dev; + if (currentVersion > version) + { + newUpdateLevel = PackageVersionClass.Dev; + } + else + { + return PackageVersionClass.Beta; + } } } - if (newUpdateLevel != updateLevel) - { - _config.Configuration.SystemUpdateLevel = newUpdateLevel; - _config.SaveConfiguration(); - } + return newUpdateLevel; } private Version ParseVersion(string versionString) -- cgit v1.2.3 From d4324b7e893725c1fc42eb482d54184420b9a5d9 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 5 Sep 2016 16:07:36 -0400 Subject: add chapter image error handling --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 3 +- .../Collections/ManualCollectionsFolder.cs | 36 +++++++++++++ MediaBrowser.Controller/Entities/UserView.cs | 3 +- .../MediaBrowser.Controller.csproj | 1 + .../Providers/BaseItemXmlParser.cs | 27 ++++++---- .../Folders/CollectionFolderMetadataService.cs | 14 +++++ .../Collections/CollectionsDynamicFolder.cs | 1 + .../Collections/ManualCollectionsFolder.cs | 36 ------------- .../LiveTv/EmbyTV/EncodedRecorder.cs | 22 +++++++- .../MediaBrowser.Server.Implementations.csproj | 1 - .../MediaEncoder/EncodingManager.cs | 16 +++--- .../UserViews/CollectionFolderImageProvider.cs | 62 ++++++++++++++++++++++ .../MediaBrowser.WebDashboard.csproj | 6 --- 13 files changed, 160 insertions(+), 68 deletions(-) create mode 100644 MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs delete mode 100644 MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index b419250f7..a979848e2 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1791,8 +1791,7 @@ namespace MediaBrowser.Api.Playback if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec)) { state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i)) - ?? state.SupportedVideoCodecs.FirstOrDefault(); + state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); } } diff --git a/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs new file mode 100644 index 000000000..d2d28e504 --- /dev/null +++ b/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs @@ -0,0 +1,36 @@ +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Collections +{ + public class ManualCollectionsFolder : BasePluginFolder, IHiddenFromDisplay + { + public ManualCollectionsFolder() + { + Name = "Collections"; + DisplayMediaType = "CollectionFolder"; + } + + public override bool IsHidden + { + get + { + return true; + } + } + + public bool IsHiddenFromUser(User user) + { + return !ConfigurationManager.Configuration.DisplayCollectionsView; + } + + public override string CollectionType + { + get { return Model.Entities.CollectionType.BoxSets; } + } + + public override string GetClientTypeName() + { + return typeof(CollectionFolder).Name; + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 194ba0ee4..35375e7e6 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -113,8 +113,7 @@ namespace MediaBrowser.Controller.Entities { var standaloneTypes = new List { - CollectionType.Playlists, - CollectionType.BoxSets + CollectionType.Playlists }; var collectionFolder = folder as ICollectionFolder; diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index e7eaa1dc0..5e74a3999 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -98,6 +98,7 @@ + diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 4484adb1d..fccbd9211 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -790,20 +790,25 @@ namespace MediaBrowser.Controller.Providers } default: - if (_validProviderIds.ContainsKey(reader.Name)) - { - var id = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(id)) + { + string readerName = reader.Name; + string providerIdValue; + if (_validProviderIds.TryGetValue(readerName, out providerIdValue)) { - item.SetProviderId(_validProviderIds[reader.Name], id); + var id = reader.ReadElementContentAsString(); + if (!string.IsNullOrWhiteSpace(id)) + { + item.SetProviderId(providerIdValue, id); + } + } + else + { + reader.Skip(); } - } - else - { - reader.Skip(); - } - break; + break; + + } } } diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs index cdaa38366..2f534c12e 100644 --- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using CommonIO; +using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -21,4 +22,17 @@ namespace MediaBrowser.Providers.Folders { } } + + public class ManualCollectionsFolderMetadataService : MetadataService + { + protected override void MergeData(MetadataResult source, MetadataResult target, List lockedFields, bool replaceData, bool mergeMetadataSettings) + { + ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + } + + public ManualCollectionsFolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) + { + } + } + } diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs index 6cd9e9620..50bb6c559 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using System.IO; using CommonIO; +using MediaBrowser.Controller.Collections; namespace MediaBrowser.Server.Implementations.Collections { diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs deleted file mode 100644 index 3e33066ae..000000000 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MediaBrowser.Controller.Entities; - -namespace MediaBrowser.Server.Implementations.Collections -{ - public class ManualCollectionsFolder : BasePluginFolder, IHiddenFromDisplay - { - public ManualCollectionsFolder() - { - Name = "Collections"; - DisplayMediaType = "CollectionFolder"; - } - - public override bool IsHidden - { - get - { - return true; - } - } - - public bool IsHiddenFromUser(User user) - { - return !ConfigurationManager.Configuration.DisplayCollectionsView; - } - - public override string CollectionType - { - get { return Model.Entities.CollectionType.BoxSets; } - } - - public override string GetClientTypeName() - { - return typeof(CollectionFolder).Name; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index fc3a507d1..5e7e3a94f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -53,11 +53,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { + if (mediaSource.Path.IndexOf("m3u8", StringComparison.OrdinalIgnoreCase) != -1) + { + await RecordWithoutTempFile(mediaSource, targetFile, duration, onStarted, cancellationToken) + .ConfigureAwait(false); + + return; + } + var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts"); try { - await RecordInternal(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) + await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) .ConfigureAwait(false); } finally @@ -73,7 +81,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - public async Task RecordInternal(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + private async Task RecordWithoutTempFile(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + { + var durationToken = new CancellationTokenSource(duration); + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + + await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false); + + _logger.Info("Recording completed to file {0}", targetFile); + } + + private async Task RecordWithTempFile(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { var httpRequestOptions = new HttpRequestOptions() { diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 9a92cf896..8850f3d35 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -124,7 +124,6 @@ - diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index 11338df6d..7d0841fa6 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -149,16 +149,16 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder } } - // Add some time for the first chapter to make sure we don't end up with a black image - var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); + try + { + // Add some time for the first chapter to make sure we don't end up with a black image + var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); - var protocol = MediaProtocol.File; + var protocol = MediaProtocol.File; - var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, video.PlayableStreamFileNames); + var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, video.PlayableStreamFileNames); - try - { - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); var tempFile = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false); File.Copy(tempFile, path, true); @@ -178,7 +178,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder } catch (Exception ex) { - _logger.ErrorException("Error extracting chapter images for {0}", ex, string.Join(",", inputPath)); + _logger.ErrorException("Error extracting chapter images for {0}", ex, string.Join(",", video.Path)); success = false; break; } diff --git a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index 29716d33e..2cff4a14f 100644 --- a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -13,6 +13,10 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Querying; namespace MediaBrowser.Server.Implementations.UserViews { @@ -109,4 +113,62 @@ namespace MediaBrowser.Server.Implementations.UserViews return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false); } } + + public class ManualCollectionFolderImageProvider : BaseDynamicImageProvider + { + private readonly ILibraryManager _libraryManager; + + public ManualCollectionFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + _libraryManager = libraryManager; + } + + public override IEnumerable GetSupportedImages(IHasImages item) + { + return new List + { + ImageType.Primary + }; + } + + protected override async Task> GetItemsWithImages(IHasImages item) + { + var view = (ManualCollectionsFolder)item; + + var recursive = !new[] { CollectionType.Playlists, CollectionType.Channels }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + + var items = _libraryManager.GetItemList(new InternalItemsQuery + { + Recursive = recursive, + IncludeItemTypes = new[] { typeof(BoxSet).Name }, + Limit = 20, + SortBy = new[] { ItemSortBy.Random } + }); + + return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8); + } + + protected override bool Supports(IHasImages item) + { + return item is ManualCollectionsFolder; + } + + protected override async Task CreateImage(IHasImages item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + { + var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png"); + + if (imageType == ImageType.Primary) + { + if (itemsWithImages.Count == 0) + { + return null; + } + + return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false); + } + + return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false); + } + } + } diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index d1308a501..2b828b8fc 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -755,9 +755,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -944,9 +941,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest -- cgit v1.2.3 From 67505e24bdccae33387e59358c01471dd9536d42 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 6 Sep 2016 01:02:05 -0400 Subject: fix artist editor --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 8 +--- MediaBrowser.Controller/Library/NameExtensions.cs | 4 +- .../LiveTv/IHasRegistrationInfo.cs | 15 ------ MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 4 +- .../MediaBrowser.Controller.csproj | 1 - .../Configuration/ServerConfiguration.cs | 2 +- .../Music/AudioMetadataService.cs | 3 +- .../Dto/DtoService.cs | 20 ++++++++ .../LiveTv/EmbyTV/EmbyTV.cs | 26 ++-------- .../LiveTv/LiveTvManager.cs | 40 +++++++--------- MediaBrowser.Server.Mono/Native/BaseMonoApp.cs | 5 ++ .../ApplicationHost.cs | 16 ++++++- MediaBrowser.Server.Startup.Common/INativeApp.cs | 2 + .../Native/RegisterServer.bat | 2 + .../Native/WindowsApp.cs | 55 ++++++++++++++++++++++ 15 files changed, 126 insertions(+), 77 deletions(-) delete mode 100644 MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index f6817e0e7..8868daaca 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -538,12 +538,6 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class GetLiveTvRegistrationInfo : IReturn { - [ApiMember(Name = "ChannelId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ChannelId { get; set; } - - [ApiMember(Name = "ProgramId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ProgramId { get; set; } - [ApiMember(Name = "Feature", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string Feature { get; set; } } @@ -595,7 +589,7 @@ namespace MediaBrowser.Api.LiveTv public async Task Get(GetLiveTvRegistrationInfo request) { - var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false); + var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs index 6973dce64..72f036b0a 100644 --- a/MediaBrowser.Controller/Library/NameExtensions.cs +++ b/MediaBrowser.Controller/Library/NameExtensions.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Controller.Library } } - class TextComparer : IComparer, IEqualityComparer + public class DistinctNameComparer : IComparer, IEqualityComparer { public int Compare(string x, string y) { @@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.Library return 0; } - return string.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace); + return string.Compare(x.RemoveDiacritics(), y.RemoveDiacritics(), StringComparison.OrdinalIgnoreCase); } public bool Equals(string x, string y) diff --git a/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs b/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs deleted file mode 100644 index 3626c18e5..000000000 --- a/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using MediaBrowser.Model.Entities; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.LiveTv -{ - public interface IHasRegistrationInfo - { - /// - /// Gets the registration information. - /// - /// The feature. - /// Task<MBRegistrationRecord>. - Task GetRegistrationInfo(string feature); - } -} diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index ed64127c3..d30231eb9 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -364,11 +364,9 @@ namespace MediaBrowser.Controller.LiveTv /// /// Gets the registration information. /// - /// The channel identifier. - /// The program identifier. /// The feature. /// Task<MBRegistrationRecord>. - Task GetRegistrationInfo(string channelId, string programId, string feature); + Task GetRegistrationInfo(string feature); /// /// Adds the channel information. diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5e74a3999..7cfd56c1e 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -199,7 +199,6 @@ - diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 44a2ae954..26bf3107d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.Model.Configuration Migrations = new string[] { }; CodecsUsed = new string[] { }; SqliteCacheSize = 0; - ImageExtractionTimeoutMs = 10000; + ImageExtractionTimeoutMs = 14000; EnableLocalizedGuids = true; DisplaySpecialsWithinSeasons = true; diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs index 532128186..67ddd8981 100644 --- a/MediaBrowser.Providers/Music/AudioMetadataService.cs +++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; +using System.Linq; using CommonIO; namespace MediaBrowser.Providers.Music @@ -21,7 +22,7 @@ namespace MediaBrowser.Providers.Music if (replaceData || targetItem.Artists.Count == 0) { - targetItem.Artists = sourceItem.Artists; + targetItem.Artists = sourceItem.Artists.ToList(); } if (replaceData || string.IsNullOrEmpty(targetItem.Album)) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index be68162ca..9284f4fc7 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1168,6 +1168,26 @@ namespace MediaBrowser.Server.Implementations.Dto }; }) .ToList(); + + // Include artists that are not in the database yet, e.g., just added via metadata editor + var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); + dto.ArtistItems.AddRange(hasArtist.Artists + .Except(foundArtists, new DistinctNameComparer()) + .Select(i => + { + var artist = _libraryManager.GetArtist(i); + if (artist != null) + { + return new NameIdPair + { + Name = artist.Name, + Id = artist.Id.ToString("N") + }; + } + + return null; + + }).Where(i => i != null)); } var hasAlbumArtist = item as IHasAlbumArtist; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 649024d16..8f5b42df0 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -33,7 +33,7 @@ using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { - public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IHasRegistrationInfo, IDisposable + public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IDisposable { private readonly IApplicationHost _appHpst; private readonly ILogger _logger; @@ -46,7 +46,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private readonly LiveTvManager _liveTvManager; private readonly IFileSystem _fileSystem; - private readonly ISecurityManager _security; private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; @@ -62,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private readonly ConcurrentDictionary _activeRecordings = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement) + public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement) { Current = this; @@ -71,7 +70,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _httpClient = httpClient; _config = config; _fileSystem = fileSystem; - _security = security; _libraryManager = libraryManager; _libraryMonitor = libraryMonitor; _providerManager = providerManager; @@ -1114,7 +1112,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (config.EnableRecordingEncoding) { - var regInfo = await _security.GetRegistrationStatus("embytvrecordingconversion").ConfigureAwait(false); + var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false); if (regInfo.IsValid) { @@ -1171,8 +1169,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private async Task UpdateTimersForSeriesTimer(List epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers) { var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList(); - - var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); + + var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); if (registration.IsValid) { @@ -1349,20 +1347,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - public Task GetRegistrationInfo(string feature) - { - if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase)) - { - return _security.GetRegistrationStatus("embytvseriesrecordings"); - } - - return Task.FromResult(new MBRegistrationRecord - { - IsValid = true, - IsRegistered = true - }); - } - public List GetRecordingFolders() { var list = new List(); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 3d9a0f80e..72d1dc120 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -31,6 +31,7 @@ using CommonIO; using IniParser; using IniParser.Model; using MediaBrowser.Common.Events; +using MediaBrowser.Common.Security; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Events; @@ -51,6 +52,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly ITaskManager _taskManager; private readonly IJsonSerializer _jsonSerializer; private readonly IProviderManager _providerManager; + private readonly ISecurityManager _security; private readonly IDtoService _dtoService; private readonly ILocalizationManager _localization; @@ -73,7 +75,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public event EventHandler> TimerCreated; public event EventHandler> SeriesTimerCreated; - public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem) + public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem, ISecurityManager security) { _config = config; _logger = logger; @@ -85,6 +87,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _jsonSerializer = jsonSerializer; _providerManager = providerManager; _fileSystem = fileSystem; + _security = security; _dtoService = dtoService; _userDataManager = userDataManager; @@ -2133,6 +2136,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken) { + var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); + + if (!registration.IsValid) + { + _logger.Info("Creating series recordings requires an active Emby Premiere subscription."); + return; + } + var service = GetService(timer.ServiceName); var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false); @@ -2695,33 +2706,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } - public Task GetRegistrationInfo(string channelId, string programId, string feature) + public Task GetRegistrationInfo(string feature) { - ILiveTvService service; - - if (string.IsNullOrWhiteSpace(programId)) + if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase)) { - var channel = GetInternalChannel(channelId); - service = GetService(channel); + feature = "embytvseriesrecordings"; } - else - { - var program = GetInternalProgram(programId); - service = GetService(program); - } - - var hasRegistration = service as IHasRegistrationInfo; - if (hasRegistration != null) - { - return hasRegistration.GetRegistrationInfo(feature); - } - - return Task.FromResult(new MBRegistrationRecord - { - IsValid = true, - IsRegistered = true - }); + return _security.GetRegistrationStatus(feature); } public List GetSatIniMappings() diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index 2185f4804..48f6a2a48 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -283,6 +283,11 @@ namespace MediaBrowser.Server.Mono.Native { } + + public bool PortsRequireAuthorization(string applicationPath) + { + return false; + } } public class NullPowerManagement : IPowerManagement diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 86c5c06bc..2417c5b11 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -520,7 +520,7 @@ namespace MediaBrowser.Server.Startup.Common PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager, ProviderManager); RegisterSingleInstance(PlaylistManager); - LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager); + LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, SecurityManager); RegisterSingleInstance(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); @@ -773,7 +773,19 @@ namespace MediaBrowser.Server.Startup.Common /// protected override void FindParts() { - if (!ServerConfigurationManager.Configuration.IsPortAuthorized) + var isAuthorized = ServerConfigurationManager.Configuration.IsPortAuthorized; + if (isAuthorized) + { + try + { + isAuthorized = !NativeApp.PortsRequireAuthorization(ConfigurationManager.CommonApplicationPaths.ApplicationPath); + } + catch + { + + } + } + if (!isAuthorized) { RegisterServerWithAdministratorAccess(); ServerConfigurationManager.Configuration.IsPortAuthorized = true; diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs index d2e278a3b..9297a6d37 100644 --- a/MediaBrowser.Server.Startup.Common/INativeApp.cs +++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs @@ -25,6 +25,8 @@ namespace MediaBrowser.Server.Startup.Common /// The temporary directory. void AuthorizeServer(int udpPort, int httpServerPort, int httpsServerPort, string applicationPath, string tempDirectory); + bool PortsRequireAuthorization(string applicationPath); + /// /// Gets the environment. /// diff --git a/MediaBrowser.ServerApplication/Native/RegisterServer.bat b/MediaBrowser.ServerApplication/Native/RegisterServer.bat index 27f863d58..85baa0d03 100644 --- a/MediaBrowser.ServerApplication/Native/RegisterServer.bat +++ b/MediaBrowser.ServerApplication/Native/RegisterServer.bat @@ -20,7 +20,9 @@ netsh advfirewall firewall add rule name="Port %3" dir=in action=allow protocol= if [%4]==[] GOTO DONE +netsh advfirewall firewall delete rule name="mediabrowser.serverapplication.exe" netsh advfirewall firewall delete rule name="Emby Server" + netsh advfirewall firewall add rule name="Emby Server" dir=in action=allow protocol=TCP program=%4 enable=yes netsh advfirewall firewall add rule name="Emby Server" dir=in action=allow protocol=UDP program=%4 enable=yes diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 139471f11..f7b19162d 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -208,5 +208,60 @@ namespace MediaBrowser.ServerApplication.Native { LoopUtil.Run(appName); } + + public bool PortsRequireAuthorization(string applicationPath) + { + var appNameSrch = Path.GetFileName(applicationPath); + + var startInfo = new ProcessStartInfo + { + FileName = "netsh", + + Arguments = "advfirewall firewall show rule \"" + appNameSrch + "\"", + + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false, + RedirectStandardOutput = true + }; + + using (var process = Process.Start(startInfo)) + { + process.Start(); + + try + { + var data = process.StandardOutput.ReadToEnd() ?? string.Empty; + + //_logger.Debug("Found windows firewall rule: " + data); + if (data.IndexOf("Block", StringComparison.OrdinalIgnoreCase) != -1) + { + return true; + } + + //var parts = data.Split('\n'); + + //return parts.Length > 4; + return false; + } + catch (Exception ex) + { + _logger.ErrorException("Error querying windows firewall", ex); + + // Hate having to do this + try + { + process.Kill(); + } + catch (Exception ex1) + { + _logger.ErrorException("Error killing process", ex1); + } + + throw; + } + } + } } } \ No newline at end of file -- cgit v1.2.3 From c3f947f4da122343b87ec3c36364885d6f934d01 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 6 Sep 2016 13:59:10 -0400 Subject: add sports and kids recording categories --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 83 +++++++++++++++++++- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 1 + MediaBrowser.Model/LiveTv/RecordingQuery.cs | 2 + .../EntryPoints/UsageEntryPoint.cs | 6 +- .../LiveTv/LiveTvManager.cs | 91 +++++++++++++++++++++- .../Native/WindowsApp.cs | 2 +- 6 files changed, 177 insertions(+), 8 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 8868daaca..3ad0ec1ba 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -157,6 +157,8 @@ namespace MediaBrowser.Api.LiveTv public bool? IsMovie { get; set; } public bool? IsSeries { get; set; } + public bool? IsKids { get; set; } + public bool? IsSports { get; set; } public GetRecordings() { @@ -164,6 +166,61 @@ namespace MediaBrowser.Api.LiveTv } } + [Route("/LiveTv/Recordings/Series", "GET", Summary = "Gets live tv recordings")] + [Authenticated] + public class GetRecordingSeries : IReturn>, IHasDtoOptions + { + [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ChannelId { get; set; } + + [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string UserId { get; set; } + + [ApiMember(Name = "GroupId", Description = "Optional filter by recording group.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string GroupId { get; set; } + + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + + [ApiMember(Name = "Status", Description = "Optional filter by recording status.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public RecordingStatus? Status { get; set; } + + [ApiMember(Name = "Status", Description = "Optional filter by recordings that are in progress, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsInProgress { get; set; } + + [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by recordings belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string SeriesTimerId { get; set; } + + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableImages { get; set; } + + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? ImageTypeLimit { get; set; } + + [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string EnableImageTypes { get; set; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } + + public bool EnableTotalRecordCount { get; set; } + + [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableUserData { get; set; } + + public GetRecordingSeries() + { + EnableTotalRecordCount = true; + } + } + [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")] [Authenticated] public class GetRecordingGroups : IReturn> @@ -862,7 +919,31 @@ namespace MediaBrowser.Api.LiveTv IsInProgress = request.IsInProgress, EnableTotalRecordCount = request.EnableTotalRecordCount, IsMovie = request.IsMovie, - IsSeries = request.IsSeries + IsSeries = request.IsSeries, + IsKids = request.IsKids, + IsSports = request.IsSports + + }, options, CancellationToken.None).ConfigureAwait(false); + + return ToOptimizedResult(result); + } + + public async Task Get(GetRecordingSeries request) + { + var options = GetDtoOptions(request); + options.DeviceId = AuthorizationContext.GetAuthorizationInfo(Request).DeviceId; + + var result = await _liveTvManager.GetRecordingSeries(new RecordingQuery + { + ChannelId = request.ChannelId, + UserId = request.UserId, + GroupId = request.GroupId, + StartIndex = request.StartIndex, + Limit = request.Limit, + Status = request.Status, + SeriesTimerId = request.SeriesTimerId, + IsInProgress = request.IsInProgress, + EnableTotalRecordCount = request.EnableTotalRecordCount }, options, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index d30231eb9..a8e42749b 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -108,6 +108,7 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// QueryResult{RecordingInfoDto}. Task> GetRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken); + Task> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken); /// /// Gets the timers. diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index cedc0b852..0ba5f1779 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -70,6 +70,8 @@ namespace MediaBrowser.Model.LiveTv public bool? EnableImages { get; set; } public bool? IsMovie { get; set; } public bool? IsSeries { get; set; } + public bool? IsKids { get; set; } + public bool? IsSports { get; set; } public int? ImageTypeLimit { get; set; } public ImageType[] EnableImageTypes { get; set; } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs index f82bb01bb..d14bd4368 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs @@ -92,11 +92,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints DeviceId = session.DeviceId }; - // Report usage to remote server, except for web client, since we already have data on that - if (!string.Equals(info.AppName, "Dashboard", StringComparison.OrdinalIgnoreCase)) - { - ReportNewSession(info); - } + ReportNewSession(info); return info; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 72d1dc120..e76f95ab4 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1430,6 +1430,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var includeItemTypes = new List(); var excludeItemTypes = new List(); + var genres = new List(); if (query.IsMovie.HasValue) { @@ -1453,6 +1454,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv excludeItemTypes.Add(typeof(Episode).Name); } } + if (query.IsSports.HasValue) + { + if (query.IsSports.Value) + { + genres.Add("Sports"); + } + } + if (query.IsKids.HasValue) + { + if (query.IsKids.Value) + { + genres.Add("Kids"); + genres.Add("Children"); + genres.Add("Family"); + } + } return _libraryManager.GetItemsResult(new InternalItemsQuery(user) { @@ -1461,13 +1478,73 @@ namespace MediaBrowser.Server.Implementations.LiveTv AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(), IsFolder = false, ExcludeLocationTypes = new[] { LocationType.Virtual }, - Limit = Math.Min(200, query.Limit ?? int.MaxValue), + Limit = query.Limit, + SortBy = new[] { ItemSortBy.DateCreated }, + SortOrder = SortOrder.Descending, + EnableTotalRecordCount = query.EnableTotalRecordCount, + IncludeItemTypes = includeItemTypes.ToArray(), + ExcludeItemTypes = excludeItemTypes.ToArray(), + Genres = genres.ToArray() + }); + } + + public async Task> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken) + { + var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); + if (user != null && !IsLiveTvEnabled(user)) + { + return new QueryResult(); + } + + if (_services.Count > 1) + { + return new QueryResult(); + } + + if (user == null || (query.IsInProgress ?? false)) + { + return new QueryResult(); + } + + var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders() + .SelectMany(i => i.Locations) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(i => _libraryManager.FindByPath(i, true)) + .Where(i => i != null) + .Where(i => i.IsVisibleStandalone(user)) + .ToList(); + + if (folders.Count == 0) + { + return new QueryResult(); + } + + var includeItemTypes = new List(); + var excludeItemTypes = new List(); + + includeItemTypes.Add(typeof(Series).Name); + + var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) + { + Recursive = true, + AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(), + Limit = query.Limit, SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, EnableTotalRecordCount = query.EnableTotalRecordCount, IncludeItemTypes = includeItemTypes.ToArray(), ExcludeItemTypes = excludeItemTypes.ToArray() }); + + RemoveFields(options); + + var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); + + return new QueryResult + { + Items = returnArray, + TotalRecordCount = internalResult.TotalRecordCount + }; } public async Task> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken) @@ -1537,6 +1614,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv recordings = recordings.Where(i => i.IsSeries == val); } + if (query.IsKids.HasValue) + { + var val = query.IsKids.Value; + recordings = recordings.Where(i => i.IsKids == val); + } + + if (query.IsSports.HasValue) + { + var val = query.IsSports.Value; + recordings = recordings.Where(i => i.IsSports == val); + } + if (!string.IsNullOrEmpty(query.SeriesTimerId)) { var guid = new Guid(query.SeriesTimerId); diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 2af6400e2..3cd6c95d2 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -240,9 +240,9 @@ namespace MediaBrowser.ServerApplication.Native { var data = process.StandardOutput.ReadToEnd() ?? string.Empty; - //_logger.Debug("Found windows firewall rule: " + data); if (data.IndexOf("Block", StringComparison.OrdinalIgnoreCase) != -1) { + _logger.Info("Found windows firewall rule: " + data); return Confirm(); } -- cgit v1.2.3 From 2a6a6d6911527a0ff5d0ce94d363d6faff5ed98d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 6 Sep 2016 23:38:01 -0400 Subject: add additional ignores from librarymonitor --- MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 7ed4dc71e..c87d10ef4 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -46,6 +46,14 @@ namespace MediaBrowser.Server.Implementations.IO "TempSBE" }; + private readonly IReadOnlyList _alwaysIgnoreSubstrings = new List + { + // Synology + "@eaDir", + ".wd_tv", + ".actors" + }; + private readonly IReadOnlyList _alwaysIgnoreExtensions = new List { // thumbs.db @@ -421,10 +429,11 @@ namespace MediaBrowser.Server.Implementations.IO } var filename = Path.GetFileName(path); - + var monitorPath = !string.IsNullOrEmpty(filename) && !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) && - !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase); + !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase) && + _alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1); // Ignore certain files var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); -- cgit v1.2.3 From 4e0adb17e74a252f3168070721854a03ecc9d07b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 7 Sep 2016 01:48:14 -0400 Subject: update dialogs --- MediaBrowser.Controller/Library/ILibraryManager.cs | 3 +++ .../Library/LibraryManager.cs | 31 +++++++++++++++++++++- .../LiveTv/EmbyTV/EmbyTV.cs | 2 ++ .../Migrations/UpdateLevelMigration.cs | 6 ----- 4 files changed, 35 insertions(+), 7 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 0862e3eaf..04268bcea 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -566,5 +566,8 @@ namespace MediaBrowser.Controller.Library QueryResult> GetArtists(InternalItemsQuery query); QueryResult> GetAlbumArtists(InternalItemsQuery query); QueryResult> GetAllArtists(InternalItemsQuery query); + + void RegisterIgnoredPath(string path); + void UnRegisterIgnoredPath(string path); } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 5d556e3a6..7758d690a 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -621,9 +621,38 @@ namespace MediaBrowser.Server.Implementations.Library return ResolveItem(args, resolvers); } + private readonly List _ignoredPaths = new List(); + + public void RegisterIgnoredPath(string path) + { + lock (_ignoredPaths) + { + _ignoredPaths.Add(path); + } + } + public void UnRegisterIgnoredPath(string path) + { + lock (_ignoredPaths) + { + _ignoredPaths.Remove(path); + } + } + public bool IgnoreFile(FileSystemMetadata file, BaseItem parent) { - return EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)); + if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent))) + { + return true; + } + + //lock (_ignoredPaths) + { + if (_ignoredPaths.Contains(file.FullName, StringComparer.OrdinalIgnoreCase)) + { + return true; + } + } + return false; } public IEnumerable NormalizeRootPathList(IEnumerable paths) diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 8f5b42df0..8fa34109d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -993,6 +993,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = EnsureFileUnique(recordPath, timer.Id); + _libraryManager.RegisterIgnoredPath(recordPath); _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); activeRecordingInfo.Path = recordPath; @@ -1044,6 +1045,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV semaphore.Release(); } + _libraryManager.UnRegisterIgnoredPath(recordPath); _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); ActiveRecordingInfo removed; diff --git a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs index de898e66c..d5fe9707c 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs @@ -42,12 +42,6 @@ namespace MediaBrowser.Server.Startup.Common.Migrations { var updateLevel = _config.Configuration.SystemUpdateLevel; - if (updateLevel == PackageVersionClass.Dev) - { - // It's already dev, there's nothing to check - return; - } - await CheckVersion(currentVersion, updateLevel, CancellationToken.None).ConfigureAwait(false); } catch -- cgit v1.2.3 From d68a8268331ab2c3f75b1f3edd8b786e48b0ba57 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 7 Sep 2016 13:17:26 -0400 Subject: update dialogs --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 10 +++++++--- MediaBrowser.Controller/Entities/Folder.cs | 12 +++++++++++- MediaBrowser.Dlna/PlayTo/Device.cs | 2 ++ .../Library/LibraryManager.cs | 2 +- MediaBrowser.Server.Startup.Common/ApplicationHost.cs | 8 +++++--- 5 files changed, 26 insertions(+), 8 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index a979848e2..5d7f01ad3 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1176,17 +1176,21 @@ namespace MediaBrowser.Api.Playback await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); } - if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive) + if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited) { await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); - if (state.ReadInputAtNativeFramerate) + if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited) { await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); } } - StartThrottler(state, transcodingJob); + if (!transcodingJob.HasExited) + { + StartThrottler(state, transcodingJob); + } + ReportUsage(state); return transcodingJob; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index bf47ada0d..f1d8def4b 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1057,10 +1057,20 @@ namespace MediaBrowser.Controller.Entities /// IList{BaseItem}. public IList GetRecursiveChildren() { - return GetRecursiveChildren(i => true); + return GetRecursiveChildren(true); + } + + public IList GetRecursiveChildren(bool includeLinkedChildren) + { + return GetRecursiveChildren(i => true, includeLinkedChildren); } public IList GetRecursiveChildren(Func filter) + { + return GetRecursiveChildren(filter, true); + } + + public IList GetRecursiveChildren(Func filter, bool includeLinkedChildren) { var result = new Dictionary(); diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 174ca871a..d1802b3ad 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -483,7 +483,9 @@ namespace MediaBrowser.Dlna.PlayTo { if (OnDeviceUnavailable != null) { + _logger.Debug("Disposing device due to loss of connection"); OnDeviceUnavailable(); + return; } } if (_successiveStopCount >= maxSuccessiveStopReturns) diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 7758d690a..442e2ebe5 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -401,7 +401,7 @@ namespace MediaBrowser.Server.Implementations.Library var locationType = item.LocationType; var children = item.IsFolder - ? ((Folder)item).GetRecursiveChildren().ToList() + ? ((Folder)item).GetRecursiveChildren(false).ToList() : new List(); foreach (var metadataPath in GetMetadataPaths(item, children)) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 2417c5b11..07659fdfc 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -363,7 +363,10 @@ namespace MediaBrowser.Server.Startup.Common private void PerformPreInitMigrations() { - var migrations = new List(); + var migrations = new List + { + new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename) + }; foreach (var task in migrations) { @@ -383,8 +386,7 @@ namespace MediaBrowser.Server.Startup.Common var migrations = new List { new MovieDbEpisodeProviderMigration(ServerConfigurationManager), - new DbMigration(ServerConfigurationManager, TaskManager), - new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename) + new DbMigration(ServerConfigurationManager, TaskManager) }; foreach (var task in migrations) -- cgit v1.2.3 From 13004d2541becedd8bfa089cd56125042b906e06 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 7 Sep 2016 16:11:34 -0400 Subject: enable sync for channels that allow downloading --- .../Channels/IChannelManager.cs | 2 ++ .../Channels/ChannelManager.cs | 31 ++++++++++++++++++++++ .../Sync/SyncManager.cs | 5 ++++ 3 files changed, 38 insertions(+) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index e3d2d0440..3c46247a7 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -31,6 +31,8 @@ namespace MediaBrowser.Controller.Channels /// ChannelFeatures. ChannelFeatures GetChannelFeatures(string id); + bool SupportsSync(string channelId); + /// /// Gets all channel features. /// diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index bb7e142b6..b76cf46b0 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -530,6 +530,19 @@ namespace MediaBrowser.Server.Implementations.Channels return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures()); } + public bool SupportsSync(string channelId) + { + if (string.IsNullOrWhiteSpace(channelId)) + { + throw new ArgumentNullException("channelId"); + } + + //var channel = GetChannel(channelId); + var channelProvider = GetChannelProvider(channelId); + + return channelProvider.GetChannelFeatures().SupportsContentDownloading; + } + public ChannelFeatures GetChannelFeaturesDto(Channel channel, IChannel provider, InternalChannelFeatures features) @@ -1450,6 +1463,24 @@ namespace MediaBrowser.Server.Implementations.Channels return result; } + internal IChannel GetChannelProvider(string internalChannelId) + { + if (internalChannelId == null) + { + throw new ArgumentNullException("internalChannelId"); + } + + var result = GetAllChannels() + .FirstOrDefault(i => string.Equals(GetInternalChannelId(i.Name).ToString("N"), internalChannelId, StringComparison.OrdinalIgnoreCase)); + + if (result == null) + { + throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId); + } + + return result; + } + private IEnumerable ApplyFilters(IEnumerable items, IEnumerable filters, User user) { foreach (var filter in filters.OrderByDescending(f => (int)f)) diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index ffba60af8..1278a40a4 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -541,6 +541,11 @@ namespace MediaBrowser.Server.Implementations.Sync return true; } + if (item.SourceType == SourceType.Channel) + { + return BaseItem.ChannelManager.SupportsSync(item.ChannelId); + } + return item.LocationType == LocationType.FileSystem || item is Season; } -- cgit v1.2.3 From efebc78cf53c779611d9d7b3e7ed3c0dfcef2b1d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 8 Sep 2016 02:15:44 -0400 Subject: update prompt dialog --- .../LiveTv/Listings/SchedulesDirect.cs | 3 ++- .../LiveTv/LiveTvManager.cs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index e37109c14..206e90e40 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -785,9 +785,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings get { return "Schedules Direct"; } } + public static string TypeName = "SchedulesDirect"; public string Type { - get { return "SchedulesDirect"; } + get { return TypeName; } } private async Task HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index e76f95ab4..a135a188e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -35,6 +35,7 @@ using MediaBrowser.Common.Security; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Events; +using MediaBrowser.Server.Implementations.LiveTv.Listings; namespace MediaBrowser.Server.Implementations.LiveTv { @@ -1436,7 +1437,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { if (query.IsMovie.Value) { - includeItemTypes.Add(typeof (Movie).Name); + includeItemTypes.Add(typeof(Movie).Name); } else { @@ -2802,6 +2803,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv feature = "embytvseriesrecordings"; } + var config = GetConfiguration(); + if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && + config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) + { + return Task.FromResult(new MBRegistrationRecord + { + IsRegistered = true, + IsValid = true + }); + } + return _security.GetRegistrationStatus(feature); } -- cgit v1.2.3 From 323d4104843dd50323c0d1378b4df5b65571bb8c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 8 Sep 2016 02:41:49 -0400 Subject: add option to save recordings as mkv --- MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 2 ++ .../LiveTv/EmbyTV/EncodedRecorder.cs | 24 +++++++++++++++------- MediaBrowser.ServerApplication/MainStartup.cs | 2 +- .../Native/WindowsApp.cs | 9 ++++++-- 4 files changed, 27 insertions(+), 10 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 242a2d24e..ee7dd8b98 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -13,6 +13,7 @@ namespace MediaBrowser.Model.LiveTv public string SeriesRecordingPath { get; set; } public bool EnableAutoOrganize { get; set; } public bool EnableRecordingEncoding { get; set; } + public string RecordingEncodingFormat { get; set; } public bool EnableRecordingSubfolders { get; set; } public bool EnableOriginalAudioWithEncodedRecordings { get; set; } @@ -31,6 +32,7 @@ namespace MediaBrowser.Model.LiveTv TunerHosts = new List(); ListingProviders = new List(); MediaLocationsCreated = new string[] { }; + RecordingEncodingFormat = "mp4"; } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 5e7e3a94f..75ad3de59 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -46,9 +46,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _httpClient = httpClient; } + private string OutputFormat + { + get + { + var format = _liveTvOptions.RecordingEncodingFormat; + + if (string.Equals(format, "mkv", StringComparison.OrdinalIgnoreCase)) + { + return "mkv"; + } + + return "mp4"; + } + } + public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile) { - return Path.ChangeExtension(targetFile, ".mp4"); + return Path.ChangeExtension(targetFile, "." + OutputFormat); } public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) @@ -233,15 +248,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private string GetAudioArgs(MediaSourceInfo mediaSource) { - // do not copy aac because many players have difficulty with aac_latm - var copyAudio = new[] { "mp3" }; var mediaStreams = mediaSource.MediaStreams ?? new List(); var inputAudioCodec = mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Select(i => i.Codec).FirstOrDefault() ?? string.Empty; - if (copyAudio.Contains(inputAudioCodec, StringComparer.OrdinalIgnoreCase)) - { - return "-codec:a:0 copy"; - } + // do not copy aac because many players have difficulty with aac_latm if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings && !string.Equals(inputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase)) { return "-codec:a:0 copy"; diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 8fb0e8f46..cdacdc81f 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -331,7 +331,7 @@ namespace MediaBrowser.ServerApplication Application.Run(); } - private static SplashForm _splash; + internal static SplashForm _splash; private static Thread _splashThread; private static void ShowSplashScreen(Version appVersion, Progress progress, ILogger logger) { diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 3cd6c95d2..200e2d3ae 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -212,7 +212,12 @@ namespace MediaBrowser.ServerApplication.Native private bool Confirm() { - return MessageBox.Show("Emby has detected that Windows Firewall has been configured in a way that may prevent your other devices from accessing Emby Server. Click OK to remove this rule, or cancel to proceed anyway.", "Windows Firewall", MessageBoxButtons.OKCancel) == DialogResult.OK; + if (MainStartup._splash == null) + { + return false; + } + + return MessageBox.Show(MainStartup._splash, "Emby has detected that Windows Firewall has been configured in a way that may prevent your other devices from accessing Emby Server. Click OK to remove this rule, or cancel to proceed anyway.", "Windows Firewall", MessageBoxButtons.OKCancel) == DialogResult.OK; } public bool PortsRequireAuthorization(string applicationPath) @@ -242,7 +247,7 @@ namespace MediaBrowser.ServerApplication.Native if (data.IndexOf("Block", StringComparison.OrdinalIgnoreCase) != -1) { - _logger.Info("Found windows firewall rule: " + data); + _logger.Info("Found potential windows firewall rule blocking Emby Server: " + data); return Confirm(); } -- cgit v1.2.3 From 232b5758f08e8e748427dcef7d7bdc7c0d7b94ae Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 8 Sep 2016 16:32:30 -0400 Subject: update dialogs --- MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 7 +++++++ MediaBrowser.Model/Configuration/ServerConfiguration.cs | 1 + MediaBrowser.Model/LiveTv/ProgramAudio.cs | 3 ++- .../LiveTv/Listings/SchedulesDirect.cs | 7 ++++++- .../LiveTv/LiveTvManager.cs | 17 ++++++++++------- 5 files changed, 26 insertions(+), 9 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index a6a3e6108..ea5e6dbc6 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -1,6 +1,7 @@ using MediaBrowser.Model.LiveTv; using System; using System.Collections.Generic; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.LiveTv { @@ -66,6 +67,8 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is hd; otherwise, false. public bool? IsHD { get; set; } + public bool? Is3D { get; set; } + /// /// Gets or sets the audio. /// @@ -84,6 +87,8 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is repeat; otherwise, false. public bool IsRepeat { get; set; } + public bool IsSubjectToBlackout { get; set; } + /// /// Gets or sets the episode title. /// @@ -144,6 +149,8 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is kids; otherwise, false. public bool IsKids { get; set; } + public bool IsEducational { get; set; } + /// /// Gets or sets a value indicating whether this instance is premiere. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 26bf3107d..a444afb07 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -234,6 +234,7 @@ namespace MediaBrowser.Model.Configuration EnableAnonymousUsageReporting = true; EnableAutomaticRestart = true; + EnableFolderView = true; EnableUPnP = true; SharingExpirationDays = 30; diff --git a/MediaBrowser.Model/LiveTv/ProgramAudio.cs b/MediaBrowser.Model/LiveTv/ProgramAudio.cs index 902079b9a..9a272492c 100644 --- a/MediaBrowser.Model/LiveTv/ProgramAudio.cs +++ b/MediaBrowser.Model/LiveTv/ProgramAudio.cs @@ -6,6 +6,7 @@ Stereo, Dolby, DolbyDigital, - Thx + Thx, + Atmos } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 206e90e40..7cac87590 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -348,7 +348,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings if (programInfo.audioProperties != null) { - if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) + if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase))) + { + audioType = ProgramAudio.Atmos; + } + else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) { audioType = ProgramAudio.DolbyDigital; } @@ -405,6 +409,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings if (programInfo.videoProperties != null) { info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); + info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); } if (details.contentRating != null && details.contentRating.Count > 0) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index a135a188e..b3ced55a5 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2803,15 +2803,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv feature = "embytvseriesrecordings"; } - var config = GetConfiguration(); - if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && - config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) + if (string.Equals(feature, "dvr", StringComparison.OrdinalIgnoreCase)) { - return Task.FromResult(new MBRegistrationRecord + var config = GetConfiguration(); + if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && + config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) { - IsRegistered = true, - IsValid = true - }); + return Task.FromResult(new MBRegistrationRecord + { + IsRegistered = true, + IsValid = true + }); + } } return _security.GetRegistrationStatus(feature); -- cgit v1.2.3 From d2fa3d10afd3aa3f6395c8ceec428908583cbe95 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 8 Sep 2016 17:30:19 -0400 Subject: update dialogs --- MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs index 28a62c012..2b2373a47 100644 --- a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs +++ b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.Connect { LoadCachedAddress(); - _timer = new PeriodicTimer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3)); + _timer = new PeriodicTimer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(1)); ((ConnectManager)_connectManager).Start(); } -- cgit v1.2.3 From 906546ca5e5ae1a4d6ef38b3be8744a3111ac6c6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 9 Sep 2016 02:59:23 -0400 Subject: update dialogs --- .../Updates/GithubUpdater.cs | 16 +++------ .../LiveTv/Listings/SchedulesDirect.cs | 18 +++++++--- .../ApplicationHost.cs | 4 +-- .../Migrations/UpdateLevelMigration.cs | 2 +- .../Native/WindowsApp.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 38 ---------------------- 6 files changed, 21 insertions(+), 59 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs index 6281ab3ed..84c08439e 100644 --- a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs +++ b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs @@ -14,16 +14,14 @@ namespace MediaBrowser.Common.Implementations.Updates { private readonly IHttpClient _httpClient; private readonly IJsonSerializer _jsonSerializer; - private TimeSpan _cacheLength; - public GithubUpdater(IHttpClient httpClient, IJsonSerializer jsonSerializer, TimeSpan cacheLength) + public GithubUpdater(IHttpClient httpClient, IJsonSerializer jsonSerializer) { _httpClient = httpClient; _jsonSerializer = jsonSerializer; - _cacheLength = cacheLength; } - public async Task CheckForUpdateResult(string organzation, string repository, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename, CancellationToken cancellationToken) + public async Task CheckForUpdateResult(string organzation, string repository, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename, TimeSpan cacheLength, CancellationToken cancellationToken) { var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository); @@ -35,10 +33,10 @@ namespace MediaBrowser.Common.Implementations.Updates UserAgent = "Emby/3.0" }; - if (_cacheLength.Ticks > 0) + if (cacheLength.Ticks > 0) { options.CacheMode = CacheMode.Unconditional; - options.CacheLength = _cacheLength; + options.CacheLength = cacheLength; } using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) @@ -110,12 +108,6 @@ namespace MediaBrowser.Common.Implementations.Updates UserAgent = "Emby/3.0" }; - if (_cacheLength.Ticks > 0) - { - options.CacheMode = CacheMode.Unconditional; - options.CacheLength = _cacheLength; - } - using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) { var obj = _jsonSerializer.DeserializeFromStream(stream); diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 7cac87590..1a5ebedc2 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -194,14 +194,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return station; } - if (string.IsNullOrWhiteSpace(channelName)) + if (!string.IsNullOrWhiteSpace(channelName)) { - return null; - } + channelName = NormalizeName(channelName); - channelName = NormalizeName(channelName); + var result = channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase)); - return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase)); + if (result != null) + { + return result; + } + } + + if (!string.IsNullOrWhiteSpace(channelNumber)) + { + return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase)); + } } return null; diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 07659fdfc..703d8242f 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -1345,8 +1345,8 @@ namespace MediaBrowser.Server.Startup.Common cacheLength = TimeSpan.FromMinutes(5); } - var result = await new GithubUpdater(HttpClient, JsonSerializer, cacheLength).CheckForUpdateResult("MediaBrowser", "Emby", ApplicationVersion, updateLevel, _releaseAssetFilename, - "MBServer", "Mbserver.zip", cancellationToken).ConfigureAwait(false); + var result = await new GithubUpdater(HttpClient, JsonSerializer).CheckForUpdateResult("MediaBrowser", "Emby", ApplicationVersion, updateLevel, _releaseAssetFilename, + "MBServer", "Mbserver.zip", cacheLength, cancellationToken).ConfigureAwait(false); HasUpdateAvailable = result.IsUpdateAvailable; diff --git a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs index d5fe9707c..4afd5bd34 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Server.Startup.Common.Migrations private async Task CheckVersion(Version currentVersion, PackageVersionClass currentUpdateLevel, CancellationToken cancellationToken) { - var releases = await new GithubUpdater(_httpClient, _jsonSerializer, TimeSpan.FromMinutes(3)) + var releases = await new GithubUpdater(_httpClient, _jsonSerializer) .GetLatestReleases("MediaBrowser", "Emby", _releaseAssetFilename, cancellationToken).ConfigureAwait(false); var newUpdateLevel = GetNewUpdateLevel(currentVersion, currentUpdateLevel, releases); diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index 200e2d3ae..b08b82de5 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.ServerApplication.Native return false; } - return MessageBox.Show(MainStartup._splash, "Emby has detected that Windows Firewall has been configured in a way that may prevent your other devices from accessing Emby Server. Click OK to remove this rule, or cancel to proceed anyway.", "Windows Firewall", MessageBoxButtons.OKCancel) == DialogResult.OK; + return MessageBox.Show(MainStartup._splash, "Emby has detected that a rule has been added to Windows Firewall that may prevent your other devices from accessing Emby Server. Click OK to remove this rule, or cancel to proceed anyway.", "Windows Firewall", MessageBoxButtons.OKCancel) == DialogResult.OK; } public bool PortsRequireAuthorization(string applicationPath) diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index aec4632ae..2f8b34879 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -58,11 +58,6 @@ namespace MediaBrowser.WebDashboard.Api { } - [Route("/web/staticfiles", "GET")] - public class GetCacheFiles - { - } - /// /// Class GetDashboardResource /// @@ -145,37 +140,6 @@ namespace MediaBrowser.WebDashboard.Api return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator().ModifyHtml("dummy.html", page.GetHtmlStream(), null, _appHost.ApplicationVersion.ToString(), null, false)); } - public object Get(GetCacheFiles request) - { - var allFiles = GetCacheFileList(); - - return ResultFactory.GetOptimizedResult(Request, _jsonSerializer.SerializeToString(allFiles)); - } - - private List GetCacheFileList() - { - var creator = GetPackageCreator(); - var directory = creator.DashboardUIPath; - - var skipExtensions = GetDeployIgnoreExtensions(); - var skipNames = GetDeployIgnoreFilenames(); - - return - Directory.GetFiles(directory, "*", SearchOption.AllDirectories) - .Where(i => !skipExtensions.Contains(Path.GetExtension(i) ?? string.Empty, StringComparer.OrdinalIgnoreCase)) - .Where(i => !skipNames.Any(s => - { - if (s.Item2) - { - return string.Equals(s.Item1, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase); - } - - return (Path.GetFileName(i) ?? string.Empty).IndexOf(s.Item1, StringComparison.OrdinalIgnoreCase) != -1; - })) - .Select(i => i.Replace(directory, string.Empty, StringComparison.OrdinalIgnoreCase).Replace("\\", "/").TrimStart('/') + "?v=" + _appHost.ApplicationVersion.ToString()) - .ToList(); - } - /// /// Gets the specified request. /// @@ -369,8 +333,6 @@ namespace MediaBrowser.WebDashboard.Api var appVersion = _appHost.ApplicationVersion.ToString(); - File.WriteAllText(Path.Combine(path, "staticfiles"), _jsonSerializer.SerializeToString(GetCacheFileList())); - var mode = request.Mode; // Try to trim the output size a bit -- cgit v1.2.3 From 62d9eb1ec7da1b7017818e5620c2334ad336ac2f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 11 Sep 2016 03:33:53 -0400 Subject: rework upnp discovery --- MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs | 14 +- MediaBrowser.Controller/Dlna/ISsdpHandler.cs | 1 - MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 2 + MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 86 ++- MediaBrowser.Dlna/MediaBrowser.Dlna.csproj | 12 +- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 13 +- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 26 +- MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs | 213 ++----- MediaBrowser.Dlna/Ssdp/SsdpHandler.cs | 383 ------------ .../MediaBrowser.Model.Portable.csproj | 3 - .../MediaBrowser.Model.net35.csproj | 5 +- MediaBrowser.Model/Configuration/AutoOnOff.cs | 10 - .../Configuration/ServerConfiguration.cs | 3 - MediaBrowser.Model/MediaBrowser.Model.csproj | 1 - .../EntryPoints/ExternalPortForwarding.cs | 66 ++- .../IO/LibraryMonitor.cs | 20 +- .../LiveTv/Listings/SchedulesDirect.cs | 86 ++- .../TunerHosts/HdHomerun/HdHomerunDiscovery.cs | 9 +- .../LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs | 11 +- .../MediaBrowser.Server.Implementations.csproj | 7 +- .../packages.config | 1 - .../MediaBrowser.Server.Mono.csproj | 5 +- MediaBrowser.Server.Mono/app.config | 14 +- .../ApplicationHost.cs | 4 +- .../MediaBrowser.Server.Startup.Common.csproj | 3 +- MediaBrowser.sln | 32 + Mono.Nat/AbstractNatDevice.cs | 97 +++ Mono.Nat/AsyncResults/AsyncResult.cs | 71 +++ Mono.Nat/Enums/MapState.cs | 36 ++ Mono.Nat/Enums/ProtocolType.cs | 36 ++ Mono.Nat/EventArgs/DeviceEventArgs.cs | 45 ++ Mono.Nat/Exceptions/MappingException.cs | 87 +++ Mono.Nat/IMapper.cs | 50 ++ Mono.Nat/INatDevice.cs | 62 ++ Mono.Nat/ISearcher.cs | 51 ++ Mono.Nat/Mapping.cs | 123 ++++ Mono.Nat/Mono.Nat.csproj | 104 ++++ Mono.Nat/NatProtocol.cs | 9 + Mono.Nat/NatUtility.cs | 264 +++++++++ Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs | 52 ++ Mono.Nat/Pmp/Mappers/PmpMapper.cs | 83 +++ Mono.Nat/Pmp/Pmp.cs | 118 ++++ Mono.Nat/Pmp/PmpConstants.cs | 56 ++ Mono.Nat/Pmp/PmpNatDevice.cs | 347 +++++++++++ Mono.Nat/Pmp/Searchers/PmpSearcher.cs | 149 +++++ Mono.Nat/Properties/AssemblyInfo.cs | 31 + .../Upnp/AsyncResults/GetAllMappingsAsyncResult.cs | 56 ++ Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs | 75 +++ Mono.Nat/Upnp/Mappers/UpnpMapper.cs | 110 ++++ Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs | 60 ++ Mono.Nat/Upnp/Messages/ErrorMessage.cs | 63 ++ Mono.Nat/Upnp/Messages/GetServicesMessage.cs | 62 ++ .../Messages/Requests/CreatePortMappingMessage.cs | 75 +++ .../Messages/Requests/DeletePortMappingMessage.cs | 57 ++ .../Requests/GetExternalIPAddressMessage.cs | 51 ++ .../Requests/GetGenericPortMappingEntry.cs | 55 ++ .../Requests/GetSpecificPortMappingEntryMessage.cs | 60 ++ .../Responses/CreatePortMappingResponseMessage.cs | 46 ++ .../Responses/DeletePortMappingResponseMessage.cs | 44 ++ .../GetExternalIPAddressResponseMessage.cs | 53 ++ .../GetGenericPortMappingEntryResponseMessage.cs | 108 ++++ Mono.Nat/Upnp/Messages/UpnpMessage.cs | 132 +++++ Mono.Nat/Upnp/Searchers/UpnpSearcher.cs | 287 +++++++++ Mono.Nat/Upnp/Upnp.cs | 83 +++ Mono.Nat/Upnp/UpnpNatDevice.cs | 651 +++++++++++++++++++++ 65 files changed, 4329 insertions(+), 700 deletions(-) delete mode 100644 MediaBrowser.Model/Configuration/AutoOnOff.cs create mode 100644 Mono.Nat/AbstractNatDevice.cs create mode 100644 Mono.Nat/AsyncResults/AsyncResult.cs create mode 100644 Mono.Nat/Enums/MapState.cs create mode 100644 Mono.Nat/Enums/ProtocolType.cs create mode 100644 Mono.Nat/EventArgs/DeviceEventArgs.cs create mode 100644 Mono.Nat/Exceptions/MappingException.cs create mode 100644 Mono.Nat/IMapper.cs create mode 100644 Mono.Nat/INatDevice.cs create mode 100644 Mono.Nat/ISearcher.cs create mode 100644 Mono.Nat/Mapping.cs create mode 100644 Mono.Nat/Mono.Nat.csproj create mode 100644 Mono.Nat/NatProtocol.cs create mode 100644 Mono.Nat/NatUtility.cs create mode 100644 Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs create mode 100644 Mono.Nat/Pmp/Mappers/PmpMapper.cs create mode 100644 Mono.Nat/Pmp/Pmp.cs create mode 100644 Mono.Nat/Pmp/PmpConstants.cs create mode 100644 Mono.Nat/Pmp/PmpNatDevice.cs create mode 100644 Mono.Nat/Pmp/Searchers/PmpSearcher.cs create mode 100644 Mono.Nat/Properties/AssemblyInfo.cs create mode 100644 Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs create mode 100644 Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs create mode 100644 Mono.Nat/Upnp/Mappers/UpnpMapper.cs create mode 100644 Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/ErrorMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/GetServicesMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs create mode 100644 Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs create mode 100644 Mono.Nat/Upnp/Messages/UpnpMessage.cs create mode 100644 Mono.Nat/Upnp/Searchers/UpnpSearcher.cs create mode 100644 Mono.Nat/Upnp/Upnp.cs create mode 100644 Mono.Nat/Upnp/UpnpNatDevice.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs index e8083b363..d2c5b9e4e 100644 --- a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs +++ b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs @@ -1,10 +1,20 @@ using System; +using System.Collections.Generic; +using System.Net; +using MediaBrowser.Model.Events; namespace MediaBrowser.Controller.Dlna { public interface IDeviceDiscovery { - event EventHandler DeviceDiscovered; - event EventHandler DeviceLeft; + event EventHandler> DeviceDiscovered; + event EventHandler> DeviceLeft; + } + + public class UpnpDeviceInfo + { + public Uri Location { get; set; } + public Dictionary Headers { get; set; } + public IPEndPoint LocalEndPoint { get; set; } } } diff --git a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs index e4126ddcf..ec3a00aad 100644 --- a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs +++ b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs @@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna { public interface ISsdpHandler { - event EventHandler MessageReceived; } } diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index ea5e6dbc6..d0377fbfd 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -107,6 +107,8 @@ namespace MediaBrowser.Controller.LiveTv /// The image URL. public string ImageUrl { get; set; } + public string LogoImageUrl { get; set; } + /// /// Gets or sets a value indicating whether this instance has image. /// diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index 9f2726b31..af03f325f 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -14,8 +14,10 @@ using MediaBrowser.Dlna.Ssdp; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; +using Rssdp; namespace MediaBrowser.Dlna.Main { @@ -38,12 +40,11 @@ namespace MediaBrowser.Dlna.Main private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; - private readonly SsdpHandler _ssdpHandler; private readonly IDeviceDiscovery _deviceDiscovery; - private readonly List _registeredServerIds = new List(); private bool _ssdpHandlerStarted; private bool _dlnaServerStarted; + private SsdpDevicePublisher _Publisher; public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, @@ -58,7 +59,7 @@ namespace MediaBrowser.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder) + IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder) { _config = config; _appHost = appHost; @@ -74,7 +75,6 @@ namespace MediaBrowser.Dlna.Main _mediaSourceManager = mediaSourceManager; _deviceDiscovery = deviceDiscovery; _mediaEncoder = mediaEncoder; - _ssdpHandler = (SsdpHandler)ssdpHandler; _logger = logManager.GetLogger("Dlna"); } @@ -154,7 +154,7 @@ namespace MediaBrowser.Dlna.Main { try { - _ssdpHandler.Start(); + StartPublishing(); _ssdpHandlerStarted = true; StartDeviceDiscovery(); @@ -165,13 +165,16 @@ namespace MediaBrowser.Dlna.Main } } + private void StartPublishing() + { + _Publisher = new SsdpDevicePublisher(); + } + private void StartDeviceDiscovery() { try { - ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler); - - //DlnaChannel.Current.Start(() => _registeredServerIds.ToList()); + ((DeviceDiscovery)_deviceDiscovery).Start(); } catch (Exception ex) { @@ -199,8 +202,6 @@ namespace MediaBrowser.Dlna.Main { ((DeviceDiscovery)_deviceDiscovery).Dispose(); - _ssdpHandler.Dispose(); - _ssdpHandlerStarted = false; } catch (Exception ex) @@ -225,6 +226,14 @@ namespace MediaBrowser.Dlna.Main private async Task RegisterServerEndpoints() { + if (!_config.GetDlnaConfiguration().BlastAliveMessages) + { + return; + } + + var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds*2; + _Publisher.SupportPnpRootDevice = true; + foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false)) { //if (IPAddress.IsLoopback(address)) @@ -234,25 +243,41 @@ namespace MediaBrowser.Dlna.Main //} var addressString = address.ToString(); - var udn = addressString.GetMD5().ToString("N"); - - var descriptorURI = "/dlna/" + udn + "/description.xml"; - - var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI); var services = new List { - "upnp:rootdevice", "urn:schemas-upnp-org:device:MediaServer:1", "urn:schemas-upnp-org:service:ContentDirectory:1", "urn:schemas-upnp-org:service:ConnectionManager:1", - "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1", - "uuid:" + udn + "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" }; - _ssdpHandler.RegisterNotification(udn, uri, address, services); + var udn = (addressString).GetMD5().ToString("N"); + + foreach (var fullService in services) + { + var descriptorURI = "/dlna/" + udn + "/description.xml"; + var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI); + + var service = fullService.Replace("urn:", string.Empty).Replace(":1", string.Empty); - _registeredServerIds.Add(udn); + var serviceParts = service.Split(':'); + + var deviceTypeNamespace = serviceParts[0].Replace('.', '-'); + + _Publisher.AddDevice(new SsdpRootDevice + { + CacheLifetime = TimeSpan.FromSeconds(cacheLength), //How long SSDP clients can cache this info. + Location = uri, // Must point to the URL that serves your devices UPnP description document. + DeviceTypeNamespace = deviceTypeNamespace, + DeviceClass = serviceParts[1], + DeviceType = serviceParts[2], + FriendlyName = "Emby Server", + Manufacturer = "Emby", + ModelName = "Emby Server", + Uuid = udn // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. + }); + } } } @@ -315,20 +340,23 @@ namespace MediaBrowser.Dlna.Main public void DisposeDlnaServer() { - foreach (var id in _registeredServerIds) + if (_Publisher != null) { - try - { - _ssdpHandler.UnregisterNotification(id); - } - catch (Exception ex) + var devices = _Publisher.Devices.ToList(); + foreach (var device in devices) { - _logger.ErrorException("Error unregistering server", ex); + try + { + _Publisher.RemoveDevice(device); + } + catch (Exception ex) + { + _logger.ErrorException("Error sending bye bye", ex); + } } + _Publisher.Dispose(); } - _registeredServerIds.Clear(); - _dlnaServerStarted = false; } } diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index d10a5f7b5..b25376d1b 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -1,5 +1,5 @@  - + Debug @@ -14,6 +14,7 @@ 2.0 v4.5 ..\ + true @@ -23,7 +24,7 @@ DEBUG;TRACE prompt 4 - v4.5 + v4.5.1 none @@ -50,8 +51,15 @@ ..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll + + ..\ThirdParty\rssdp\Rssdp.NetFx40.dll + + + ..\ThirdParty\rssdp\Rssdp.Portable.dll + + diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 5622885fc..d958d0e37 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -19,6 +19,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Events; namespace MediaBrowser.Dlna.PlayTo { @@ -122,16 +123,18 @@ namespace MediaBrowser.Dlna.PlayTo } } - void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e) + void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs e) { + var info = e.Argument; + string nts; - e.Headers.TryGetValue("NTS", out nts); + info.Headers.TryGetValue("NTS", out nts); string usn; - if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty; + if (!info.Headers.TryGetValue("USN", out usn)) usn = String.Empty; string nt; - if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty; + if (!info.Headers.TryGetValue("NT", out nt)) nt = String.Empty; if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 && !_disposed) @@ -653,7 +656,7 @@ namespace MediaBrowser.Dlna.PlayTo _device.PlaybackProgress -= _device_PlaybackProgress; _device.PlaybackStopped -= _device_PlaybackStopped; _device.MediaChanged -= _device_MediaChanged; - _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; + //_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; _device.OnDeviceUnavailable = null; _device.Dispose(); diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index cd9a7b1f0..6d6986f01 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -12,7 +12,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Events; namespace MediaBrowser.Dlna.PlayTo { @@ -61,16 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; } - async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) + async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) { + var info = e.Argument; + string usn; - if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty; string nt; - if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty; - string location; - if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty; + string location = info.Location.ToString(); // It has to report that it's a media renderer if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && @@ -100,7 +103,7 @@ namespace MediaBrowser.Dlna.PlayTo } } - var uri = new Uri(location); + var uri = info.Location; _logger.Debug("Attempting to create PlayToController from location {0}", location); var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false); @@ -121,7 +124,7 @@ namespace MediaBrowser.Dlna.PlayTo if (controller == null) { - var serverAddress = GetServerAddress(e.LocalEndPoint.Address); + var serverAddress = await GetServerAddress(info.LocalEndPoint == null ? null : info.LocalEndPoint.Address).ConfigureAwait(false); string accessToken = null; sessionInfo.SessionController = controller = new PlayToController(sessionInfo, @@ -173,9 +176,14 @@ namespace MediaBrowser.Dlna.PlayTo } } - private string GetServerAddress(IPAddress localIp) + private Task GetServerAddress(IPAddress localIp) { - return _appHost.GetLocalApiUrl(localIp); + if (localIp == null) + { + return _appHost.GetLocalApiUrl(); + } + + return Task.FromResult(_appHost.GetLocalApiUrl(localIp)); } public void Dispose() diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs index 68768745e..91dbeb96e 100644 --- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs +++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs @@ -11,6 +11,8 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; +using MediaBrowser.Model.Events; +using Rssdp; namespace MediaBrowser.Dlna.Ssdp { @@ -20,132 +22,43 @@ namespace MediaBrowser.Dlna.Ssdp private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - private SsdpHandler _ssdpHandler; private readonly CancellationTokenSource _tokenSource; - private readonly IServerApplicationHost _appHost; - public event EventHandler DeviceDiscovered; - public event EventHandler DeviceLeft; - private readonly INetworkManager _networkManager; + public event EventHandler> DeviceDiscovered; + public event EventHandler> DeviceLeft; - public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IServerApplicationHost appHost, INetworkManager networkManager) + private SsdpDeviceLocator _DeviceLocator; + + public DeviceDiscovery(ILogger logger, IServerConfigurationManager config) { _tokenSource = new CancellationTokenSource(); _logger = logger; _config = config; - _appHost = appHost; - _networkManager = networkManager; - } - - private List GetLocalIpAddresses() - { - return _networkManager.GetLocalIpAddresses().ToList(); - } - - public void Start(SsdpHandler ssdpHandler) - { - _ssdpHandler = ssdpHandler; - _ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived; - - foreach (var localIp in GetLocalIpAddresses()) - { - try - { - CreateListener(localIp); - } - catch (Exception e) - { - _logger.ErrorException("Failed to Initilize Socket", e); - } - } } - async void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e) + // Call this method from somewhere in your code to start the search. + public void BeginSearch() { - string nts; - e.Headers.TryGetValue("NTS", out nts); - - if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) && - String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) && - !_disposed) - { - EventHelper.FireEventIfNotNull(DeviceLeft, this, e, _logger); - return; - } - - try - { - if (e.LocalEndPoint == null) - { - var ip = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i)); - if (ip != null) - { - e.LocalEndPoint = new IPEndPoint(ip, 0); - } - } - - if (e.LocalEndPoint != null) - { - TryCreateDevice(e); - } - } - catch (OperationCanceledException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error creating play to controller", ex); - } - } - - private void CreateListener(IPAddress localIp) - { - Task.Factory.StartNew(async (o) => - { - try - { - _logger.Info("Creating SSDP listener on {0}", localIp); - - var endPoint = new IPEndPoint(localIp, 1900); - - using (var socket = GetMulticastSocket(localIp, endPoint)) - { - var receiveBuffer = new byte[64000]; - - CreateNotifier(localIp); - - while (!_tokenSource.IsCancellationRequested) - { - var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000); - - if (receivedBytes > 0) - { - var args = SsdpHelper.ParseSsdpResponse(receiveBuffer); - args.EndPoint = endPoint; - args.LocalEndPoint = new IPEndPoint(localIp, 0); - - _ssdpHandler.LogMessageReceived(args, true); - - TryCreateDevice(args); - } - } - } - - _logger.Info("SSDP listener - Task completed"); - } - catch (OperationCanceledException) - { - } - catch (Exception e) - { - _logger.ErrorException("Error in listener", e); - } - - }, _tokenSource.Token, TaskCreationOptions.LongRunning); + _DeviceLocator = new SsdpDeviceLocator(); + + // (Optional) Set the filter so we only see notifications for devices we care about + // (can be any search target value i.e device type, uuid value etc - any value that appears in the + // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method). + //_DeviceLocator.NotificationFilter = "upnp:rootdevice"; + + // Connect our event handler so we process devices as they are found + _DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; + _DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; + // Enable listening for notifications (optional) + _DeviceLocator.StartListeningForNotifications(); + + // Perform a search so we don't have to wait for devices to broadcast notifications + // again to get any results right away (notifications are broadcast periodically). + StartAsyncSearch(); } - private void CreateNotifier(IPAddress localIp) + private void StartAsyncSearch() { Task.Factory.StartNew(async (o) => { @@ -153,7 +66,7 @@ namespace MediaBrowser.Dlna.Ssdp { while (true) { - _ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900)); + await _DeviceLocator.SearchAsync().ConfigureAwait(false); var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000; @@ -165,66 +78,60 @@ namespace MediaBrowser.Dlna.Ssdp } catch (Exception ex) { - _logger.ErrorException("Error in notifier", ex); + _logger.ErrorException("Error searching for devices", ex); } - }, _tokenSource.Token, TaskCreationOptions.LongRunning); + }, CancellationToken.None, TaskCreationOptions.LongRunning); } - private Socket GetMulticastSocket(IPAddress localIpAddress, EndPoint localEndpoint) + // Process each found device in the event handler + void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e) { - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress)); - socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); + var originalHeaders = e.DiscoveredDevice.ResponseHeaders; - socket.Bind(localEndpoint); + var headerDict = originalHeaders == null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); - return socket; - } - - private void TryCreateDevice(SsdpMessageEventArgs args) - { - string nts; - args.Headers.TryGetValue("NTS", out nts); + var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - if (String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase)) + var args = new GenericEventArgs { - if (String.Equals(args.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase)) + Argument = new UpnpDeviceInfo { - if (!_disposed) - { - EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger); - } + Location = e.DiscoveredDevice.DescriptionLocation, + Headers = headers } + }; - return; - } + EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger); + } - string usn; - if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e) + { + var originalHeaders = e.DiscoveredDevice.ResponseHeaders; + + var headerDict = originalHeaders == null ? new Dictionary>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase); - string nt; - if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - // Need to be able to download device description - string location; - if (!args.Headers.TryGetValue("Location", out location) || - string.IsNullOrEmpty(location)) + var args = new GenericEventArgs { - return; - } + Argument = new UpnpDeviceInfo + { + Location = e.DiscoveredDevice.DescriptionLocation, + Headers = headers + } + }; - EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger); + EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger); } - public void Dispose() + public void Start() { - if (_ssdpHandler != null) - { - _ssdpHandler.MessageReceived -= _ssdpHandler_MessageReceived; - } + BeginSearch(); + } + public void Dispose() + { if (!_disposed) { _disposed = true; diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs index 720ea71a0..0d0ca98a2 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs @@ -83,90 +83,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - public event EventHandler MessageReceived; - - private async void OnMessageReceived(SsdpMessageEventArgs args, bool isMulticast) - { - if (IgnoreMessage(args, isMulticast)) - { - return; - } - - LogMessageReceived(args, isMulticast); - - var headers = args.Headers; - string st; - - if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase) && headers.TryGetValue("st", out st)) - { - TimeSpan delay = GetSearchDelay(headers); - - if (_config.GetDlnaConfiguration().EnableDebugLog) - { - _logger.Debug("Delaying search response by {0} seconds", delay.TotalSeconds); - } - - await Task.Delay(delay).ConfigureAwait(false); - - RespondToSearch(args.EndPoint, st); - } - - EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger); - } - - internal void LogMessageReceived(SsdpMessageEventArgs args, bool isMulticast) - { - var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog; - - if (enableDebugLogging) - { - var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)); - var headerText = string.Join(",", headerTexts.ToArray()); - - var protocol = isMulticast ? "Multicast" : "Unicast"; - var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString(); - _logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol); - } - } - - internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast) - { - string usn; - if (args.Headers.TryGetValue("USN", out usn)) - { - // USN=uuid:b67df29b5c379445fde78c3774ab518d::urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1 - if (RegisteredDevices.Any(i => string.Equals(i.USN, usn, StringComparison.OrdinalIgnoreCase))) - { - //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)); - //var headerText = string.Join(",", headerTexts.ToArray()); - - //var protocol = isMulticast ? "Multicast" : "Unicast"; - //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString(); - //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol); - - return true; - } - } - - string serverId; - if (args.Headers.TryGetValue("X-EMBY-SERVERID", out serverId)) - { - if (string.Equals(serverId, _appHost.SystemId, StringComparison.OrdinalIgnoreCase)) - { - //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)); - //var headerText = string.Join(",", headerTexts.ToArray()); - - //var protocol = isMulticast ? "Multicast" : "Unicast"; - //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString(); - //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol); - - return true; - } - } - - return false; - } - public IEnumerable RegisteredDevices { get @@ -188,8 +104,6 @@ namespace MediaBrowser.Dlna.Ssdp RestartSocketListener(); ReloadAliveNotifier(); - CreateUnicastClient(); - SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged; SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; } @@ -202,32 +116,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - public void SendSearchMessage(EndPoint localIp) - { - var values = new Dictionary(StringComparer.OrdinalIgnoreCase); - - values["HOST"] = "239.255.255.250:1900"; - values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2"; - values["X-EMBY-SERVERID"] = _appHost.SystemId; - - values["MAN"] = "\"ssdp:discover\""; - - // Search target - values["ST"] = "ssdp:all"; - - // Seconds to delay response - values["MX"] = "3"; - - var header = "M-SEARCH * HTTP/1.1"; - - var msg = new SsdpMessageBuilder().BuildMessage(header, values); - - // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2) - SendDatagram(msg, _ssdpEndp, localIp, true); - - SendUnicastRequest(msg); - } - public async void SendDatagram(string msg, EndPoint endpoint, EndPoint localAddress, @@ -248,75 +136,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - /// - /// According to the spec: http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf - /// Device responses should be delayed a random duration between 0 and this many seconds to balance - /// load for the control point when it processes responses. In my testing kodi times out after mx - /// so we will generate from mx - 1 - /// - /// The mx headers - /// A timepsan for the amount to delay before returning search result. - private TimeSpan GetSearchDelay(Dictionary headers) - { - string mx; - headers.TryGetValue("mx", out mx); - int delaySeconds = 0; - if (!string.IsNullOrWhiteSpace(mx) - && int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds) - && delaySeconds > 1) - { - delaySeconds = new Random().Next(delaySeconds - 1); - } - - return TimeSpan.FromSeconds(delaySeconds); - } - - private void RespondToSearch(EndPoint endpoint, string deviceType) - { - var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog; - - var isLogged = false; - - const string header = "HTTP/1.1 200 OK"; - - foreach (var d in RegisteredDevices) - { - if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) || - string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase)) - { - if (!isLogged) - { - if (enableDebugLogging) - { - _logger.Debug("Responding to search from {0} for {1}", endpoint, deviceType); - } - isLogged = true; - } - - var values = new Dictionary(StringComparer.OrdinalIgnoreCase); - - values["CACHE-CONTROL"] = "max-age = 600"; - values["DATE"] = DateTime.Now.ToString("R"); - values["EXT"] = ""; - values["LOCATION"] = d.Descriptor.ToString(); - values["SERVER"] = _serverSignature; - values["ST"] = d.Type; - values["USN"] = d.USN; - - var msg = new SsdpMessageBuilder().BuildMessage(header, values); - - SendDatagram(msg, endpoint, null, false, 2); - SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 2); - //SendDatagram(header, values, endpoint, null, true); - - if (enableDebugLogging) - { - _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString()); - } - } - } - } - private void RestartSocketListener() { if (_isDisposed) @@ -329,8 +148,6 @@ namespace MediaBrowser.Dlna.Ssdp _multicastSocket = CreateMulticastSocket(); _logger.Info("MultiCast socket created"); - - Receive(); } catch (Exception ex) { @@ -339,74 +156,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - private void Receive() - { - try - { - var buffer = new byte[1024]; - - EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort); - - _multicastSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer); - } - catch (ObjectDisposedException) - { - if (!_isDisposed) - { - //StartSocketRetryTimer(); - } - } - catch (Exception ex) - { - _logger.Debug("Error in BeginReceiveFrom", ex); - } - } - - private void ReceiveCallback(IAsyncResult result) - { - if (_isDisposed) - { - return; - } - - try - { - EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort); - - var length = _multicastSocket.EndReceiveFrom(result, ref endpoint); - - var received = (byte[])result.AsyncState; - - var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog; - - if (enableDebugLogging) - { - _logger.Debug(Encoding.ASCII.GetString(received)); - } - - var args = SsdpHelper.ParseSsdpResponse(received); - args.EndPoint = endpoint; - - OnMessageReceived(args, true); - } - catch (ObjectDisposedException) - { - if (!_isDisposed) - { - //StartSocketRetryTimer(); - } - } - catch (Exception ex) - { - _logger.ErrorException("Failed to read SSDP message", ex); - } - - if (_multicastSocket != null) - { - Receive(); - } - } - public void Dispose() { _config.NamedConfigurationUpdated -= _config_ConfigurationUpdated; @@ -414,7 +163,6 @@ namespace MediaBrowser.Dlna.Ssdp _isDisposed = true; - DisposeUnicastClient(); DisposeSocket(); StopAliveNotifier(); } @@ -523,137 +271,6 @@ namespace MediaBrowser.Dlna.Ssdp } } - private void CreateUnicastClient() - { - if (_unicastClient == null) - { - try - { - _unicastClient = new UdpClient(_unicastPort); - } - catch (Exception ex) - { - _logger.ErrorException("Error creating unicast client", ex); - } - - UnicastSetBeginReceive(); - } - } - - private void DisposeUnicastClient() - { - if (_unicastClient != null) - { - try - { - _unicastClient.Close(); - } - catch (Exception ex) - { - _logger.ErrorException("Error closing unicast client", ex); - } - - _unicastClient = null; - } - } - - /// - /// Listen for Unicast SSDP Responses - /// - private void UnicastSetBeginReceive() - { - try - { - var ipRxEnd = new IPEndPoint(IPAddress.Any, _unicastPort); - var udpListener = new UdpState { EndPoint = ipRxEnd }; - - udpListener.UdpClient = _unicastClient; - _unicastClient.BeginReceive(UnicastReceiveCallback, udpListener); - } - catch (Exception ex) - { - _logger.ErrorException("Error in UnicastSetBeginReceive", ex); - } - } - - /// - /// The UnicastReceiveCallback receives Http Responses - /// and Fired the SatIpDeviceFound Event for adding the SatIpDevice - /// - /// - private void UnicastReceiveCallback(IAsyncResult ar) - { - var udpClient = ((UdpState)(ar.AsyncState)).UdpClient; - var endpoint = ((UdpState)(ar.AsyncState)).EndPoint; - if (udpClient.Client != null) - { - try - { - var responseBytes = udpClient.EndReceive(ar, ref endpoint); - var args = SsdpHelper.ParseSsdpResponse(responseBytes); - - args.EndPoint = endpoint; - - OnMessageReceived(args, false); - - UnicastSetBeginReceive(); - } - catch (ObjectDisposedException) - { - - } - catch (SocketException) - { - - } - catch (Exception) - { - // If called while shutting down, seeing a NullReferenceException inside EndReceive - } - } - } - - private void SendUnicastRequest(string request, int sendCount = 3) - { - if (_unicastClient == null) - { - return; - } - - var ipSsdp = IPAddress.Parse(SSDPAddr); - var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort); - - SendUnicastRequest(request, ipTxEnd, sendCount); - } - - private async void SendUnicastRequest(string request, IPEndPoint toEndPoint, int sendCount = 3) - { - if (_unicastClient == null) - { - return; - } - - //_logger.Debug("Sending unicast request"); - - byte[] req = Encoding.ASCII.GetBytes(request); - - try - { - for (var i = 0; i < sendCount; i++) - { - if (i > 0) - { - await Task.Delay(50).ConfigureAwait(false); - } - _unicastClient.Send(req, req.Length, toEndPoint); - } - } - catch (Exception ex) - { - _logger.ErrorException("Error in SendUnicastRequest", ex); - } - } - private readonly object _notificationTimerSyncLock = new object(); private int _aliveNotifierIntervalMs; private void ReloadAliveNotifier() diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 351740e6e..ad7dea0a5 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -175,9 +175,6 @@ Configuration\AccessSchedule.cs - - Configuration\AutoOnOff.cs - Configuration\BaseApplicationConfiguration.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index ad3811646..61f2f3f13 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -147,9 +147,6 @@ Configuration\AccessSchedule.cs - - Configuration\AutoOnOff.cs - Configuration\BaseApplicationConfiguration.cs @@ -1193,4 +1190,4 @@ --> - + \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/AutoOnOff.cs b/MediaBrowser.Model/Configuration/AutoOnOff.cs deleted file mode 100644 index e911a0ff1..000000000 --- a/MediaBrowser.Model/Configuration/AutoOnOff.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace MediaBrowser.Model.Configuration -{ - public enum AutoOnOff - { - Auto, - Enabled, - Disabled - } -} diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 40ac4be8a..5cf266674 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -183,8 +183,6 @@ namespace MediaBrowser.Model.Configuration public int RemoteClientBitrateLimit { get; set; } - public AutoOnOff EnableLibraryMonitor { get; set; } - public int SharingExpirationDays { get; set; } public string[] Migrations { get; set; } @@ -244,7 +242,6 @@ namespace MediaBrowser.Model.Configuration // 5 minutes MinResumeDurationSeconds = 300; - EnableLibraryMonitor = AutoOnOff.Auto; LibraryMonitorDelay = 60; EnableInternetProviders = true; diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index db70b8606..c1a01680d 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -89,7 +89,6 @@ - diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 280bec65b..1021d8823 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Globalization; using System.Net; using MediaBrowser.Common.Threading; +using MediaBrowser.Model.Events; namespace MediaBrowser.Server.Implementations.EntryPoints { @@ -17,17 +18,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly IServerApplicationHost _appHost; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - private readonly ISsdpHandler _ssdp; + private readonly IDeviceDiscovery _deviceDiscovery; private PeriodicTimer _timer; private bool _isStarted; - public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp) + public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery) { _logger = logmanager.GetLogger("PortMapper"); _appHost = appHost; _config = config; - _ssdp = ssdp; + _deviceDiscovery = deviceDiscovery; } private string _lastConfigIdentifier; @@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints public void Run() { - //NatUtility.Logger = new LogWriter(_logger); + NatUtility.Logger = _logger; if (_config.Configuration.EnableUPnP) { @@ -93,33 +94,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); - _ssdp.MessageReceived += _ssdp_MessageReceived; + _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; _lastConfigIdentifier = GetConfigIdentifier(); _isStarted = true; } - private void ClearCreatedRules(object state) - { - _createdRules = new List(); - _usnsHandled = new List(); - } - - void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e) + private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) { - var endpoint = e.EndPoint as IPEndPoint; - - if (endpoint == null || e.LocalEndPoint == null) - { - return; - } + var info = e.Argument; string usn; - if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty; + if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty; string nt; - if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty; + if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty; // Filter device type if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 && @@ -132,15 +122,45 @@ namespace MediaBrowser.Server.Implementations.EntryPoints var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn; - if (!_usnsHandled.Contains(identifier)) + if (info.Location != null && !_usnsHandled.Contains(identifier)) { _usnsHandled.Add(identifier); _logger.Debug("Calling Nat.Handle on " + identifier); - NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp); + + IPAddress address; + if (IPAddress.TryParse(info.Location.Host, out address)) + { + // The Handle method doesn't need the port + var endpoint = new IPEndPoint(address, info.Location.Port); + + IPAddress localAddress = null; + + try + { + var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false); + + if (!IPAddress.TryParse(localAddressString, out localAddress)) + { + return; + } + } + catch + { + return; + } + + NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp); + } } } + private void ClearCreatedRules(object state) + { + _createdRules = new List(); + _usnsHandled = new List(); + } + void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e) { var ex = e.ExceptionObject as Exception; @@ -228,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _timer = null; } - _ssdp.MessageReceived -= _ssdp_MessageReceived; + _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; try { diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index c87d10ef4..80364bb55 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -164,32 +164,16 @@ namespace MediaBrowser.Server.Implementations.IO Start(); } - private bool EnableLibraryMonitor - { - get - { - switch (ConfigurationManager.Configuration.EnableLibraryMonitor) - { - case AutoOnOff.Auto: - return Environment.OSVersion.Platform == PlatformID.Win32NT; - case AutoOnOff.Enabled: - return true; - default: - return false; - } - } - } - private bool IsLibraryMonitorEnabaled(BaseItem item) { var options = LibraryManager.GetLibraryOptions(item); - if (options != null && options.SchemaVersion >= 1) + if (options != null) { return options.EnableRealtimeMonitor; } - return EnableLibraryMonitor; + return false; } public void Start() diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1a5ebedc2..6d2f79fa0 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -166,7 +166,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10)); if (imageIndex > -1) { - programDict[schedule.programID].images = GetProgramLogo(ApiUrl, images[imageIndex]); + var programEntry = programDict[schedule.programID]; + + var data = images[imageIndex].data ?? new List(); + data = data.OrderByDescending(GetSizeOrder).ToList(); + + programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true); + //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false); + //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? + // GetProgramImage(ApiUrl, data, "Banner-LOT", false); + + if (!string.IsNullOrWhiteSpace(programEntry.thumbImage)) + { + var b = true; + } + + if (!string.IsNullOrWhiteSpace(programEntry.bannerImage)) + { + var b = true; + } } } @@ -179,6 +199,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return programsInfo; } + private int GetSizeOrder(ScheduleDirect.ImageData image) + { + if (!string.IsNullOrWhiteSpace(image.size)) + { + int value; + if (int.TryParse(image.size, out value)) + { + return value; + } + } + + return 0; + } + private readonly object _channelCacheLock = new object(); private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName) { @@ -384,13 +418,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings episodeTitle = details.episodeTitle150; } - string imageUrl = null; - - if (details.hasImageArtwork) - { - imageUrl = details.images; - } - var showType = details.showType ?? string.Empty; var info = new ProgramInfo @@ -406,7 +433,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings Audio = audioType, IsRepeat = repeat, IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1, - ImageUrl = imageUrl, + ImageUrl = details.primaryImage, IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase), IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1, IsMovie = showType.IndexOf("movie", StringComparison.OrdinalIgnoreCase) != -1 || showType.IndexOf("film", StringComparison.OrdinalIgnoreCase) != -1, @@ -485,36 +512,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return date; } - private string GetProgramLogo(string apiUrl, ScheduleDirect.ShowImages images) + private string GetProgramImage(string apiUrl, List images, string category, bool returnDefaultImage) { string url = null; - if (images.data != null) + + var logoIndex = images.FindIndex(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase)); + if (logoIndex == -1) { - var smallImages = images.data.Where(i => i.size == "Sm").ToList(); - if (smallImages.Any()) + if (!returnDefaultImage) { - images.data = smallImages; + return null; } - var logoIndex = images.data.FindIndex(i => i.category == "Logo"); - if (logoIndex == -1) + logoIndex = 0; + } + var uri = images[logoIndex].uri; + + if (!string.IsNullOrWhiteSpace(uri)) + { + if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1) { - logoIndex = 0; + url = uri; } - var uri = images.data[logoIndex].uri; - - if (!string.IsNullOrWhiteSpace(uri)) + else { - if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1) - { - url = uri; - } - else - { - url = apiUrl + "/image/" + uri; - } + url = apiUrl + "/image/" + uri; } - //_logger.Debug("URL for image is : " + url); } + //_logger.Debug("URL for image is : " + url); return url; } @@ -1204,7 +1228,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings public List crew { get; set; } public string showType { get; set; } public bool hasImageArtwork { get; set; } - public string images { get; set; } + public string primaryImage { get; set; } + public string thumbImage { get; set; } + public string bannerImage { get; set; } public string imageID { get; set; } public string md5 { get; set; } public List contentAdvisory { get; set; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs index 9ba1c60cc..ef37e3b35 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs @@ -10,6 +10,7 @@ using System; using System.Linq; using System.Threading; using MediaBrowser.Common.Net; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun @@ -39,13 +40,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; } - void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) + void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) { string server = null; - if (e.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1) + var info = e.Argument; + + if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1) { string location; - if (e.Headers.TryGetValue("Location", out location)) + if (info.Headers.TryGetValue("Location", out location)) { //_logger.Debug("HdHomerun found at {0}", location); diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs index cb0e573da..a0b8ef5f7 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs @@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Extensions; using System.Xml.Linq; +using MediaBrowser.Model.Events; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp { @@ -50,18 +51,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; } - void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) + void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) { + var info = e.Argument; + string st = null; string nt = null; - e.Headers.TryGetValue("ST", out st); - e.Headers.TryGetValue("NT", out nt); + info.Headers.TryGetValue("ST", out st); + info.Headers.TryGetValue("NT", out nt); if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) || string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase)) { string location; - if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location)) + if (info.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location)) { _logger.Debug("SAT IP found at {0}", location); diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 8850f3d35..e182ad6a5 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -105,9 +105,6 @@ ..\ThirdParty\UniversalDetector\UniversalDetector.dll - - ..\packages\Mono.Nat.1.2.24.0\lib\net40\Mono.Nat.dll - @@ -390,6 +387,10 @@ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model + + {d7453b88-2266-4805-b39b-2b5a2a33e1ba} + Mono.Nat + diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 746dc7f62..94522cd50 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -5,7 +5,6 @@ - diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index bcbb10174..e7acb3f50 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -1,5 +1,5 @@  - + Debug x86 @@ -10,8 +10,9 @@ MediaBrowser.Server.Mono MediaBrowser.Server.Mono MediaBrowser.Server.Mono.MainClass - v4.5 + v4.5.1 ..\ + true diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config index b0e8558fd..e14b908ad 100644 --- a/MediaBrowser.Server.Mono/app.config +++ b/MediaBrowser.Server.Mono/app.config @@ -1,21 +1,21 @@ - + -
+
- - + + - - + + - + diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 433855ea0..e6d9b482e 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -549,7 +549,7 @@ namespace MediaBrowser.Server.Startup.Common SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager); RegisterSingleInstance(SubtitleManager); - RegisterSingleInstance(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, this, NetworkManager)); + RegisterSingleInstance(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager)); ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository); RegisterSingleInstance(ChapterManager); @@ -566,8 +566,6 @@ namespace MediaBrowser.Server.Startup.Common await sharingRepo.Initialize().ConfigureAwait(false); RegisterSingleInstance(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this)); - RegisterSingleInstance(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this)); - var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false); RegisterSingleInstance(activityLogRepo); RegisterSingleInstance(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager)); diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index 778002e50..7eba89650 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -9,9 +9,10 @@ Properties MediaBrowser.Server.Startup.Common MediaBrowser.Server.Startup.Common - v4.5 + v4.5.1 512 ..\ + true diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 0b9dd90cd..7e0d834fa 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -64,6 +64,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}" +EndProject Global GlobalSection(Performance) = preSolution HasPerformanceSessions = true @@ -522,6 +524,36 @@ Global {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x64.ActiveCfg = Release|Any CPU {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.Build.0 = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs new file mode 100644 index 000000000..046cfc10f --- /dev/null +++ b/Mono.Nat/AbstractNatDevice.cs @@ -0,0 +1,97 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace Mono.Nat +{ + public abstract class AbstractNatDevice : INatDevice + { + private DateTime lastSeen; + + protected AbstractNatDevice () + { + + } + + public abstract IPAddress LocalAddress { get; } + + public DateTime LastSeen + { + get { return lastSeen; } + set { lastSeen = value; } + } + + public virtual void CreatePortMap (Mapping mapping) + { + IAsyncResult result = BeginCreatePortMap (mapping, null, null); + EndCreatePortMap(result); + } + + public virtual void DeletePortMap (Mapping mapping) + { + IAsyncResult result = BeginDeletePortMap (mapping, null, mapping); + EndDeletePortMap(result); + } + + public virtual Mapping[] GetAllMappings () + { + IAsyncResult result = BeginGetAllMappings (null, null); + return EndGetAllMappings (result); + } + + public virtual IPAddress GetExternalIP () + { + IAsyncResult result = BeginGetExternalIP(null, null); + return EndGetExternalIP(result); + } + + public virtual Mapping GetSpecificMapping (Protocol protocol, int port) + { + IAsyncResult result = this.BeginGetSpecificMapping (protocol, port, null, null); + return this.EndGetSpecificMapping(result); + } + + public abstract IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState); + public abstract IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState); + + public abstract IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState); + public abstract IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState); + public abstract IAsyncResult BeginGetSpecificMapping(Protocol protocol, int externalPort, AsyncCallback callback, object asyncState); + + public abstract void EndCreatePortMap (IAsyncResult result); + public abstract void EndDeletePortMap (IAsyncResult result); + + public abstract Mapping[] EndGetAllMappings (IAsyncResult result); + public abstract IPAddress EndGetExternalIP (IAsyncResult result); + public abstract Mapping EndGetSpecificMapping (IAsyncResult result); + } +} diff --git a/Mono.Nat/AsyncResults/AsyncResult.cs b/Mono.Nat/AsyncResults/AsyncResult.cs new file mode 100644 index 000000000..e98e7d7ca --- /dev/null +++ b/Mono.Nat/AsyncResults/AsyncResult.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Mono.Nat +{ + internal class AsyncResult : IAsyncResult + { + private object asyncState; + private AsyncCallback callback; + private bool completedSynchronously; + private bool isCompleted; + private Exception storedException; + private ManualResetEvent waitHandle; + + public AsyncResult(AsyncCallback callback, object asyncState) + { + this.callback = callback; + this.asyncState = asyncState; + waitHandle = new ManualResetEvent(false); + } + + public object AsyncState + { + get { return asyncState; } + } + + public ManualResetEvent AsyncWaitHandle + { + get { return waitHandle; } + } + + WaitHandle IAsyncResult.AsyncWaitHandle + { + get { return waitHandle; } + } + + public bool CompletedSynchronously + { + get { return completedSynchronously; } + protected internal set { completedSynchronously = value; } + } + + public bool IsCompleted + { + get { return isCompleted; } + protected internal set { isCompleted = value; } + } + + public Exception StoredException + { + get { return storedException; } + } + + public void Complete() + { + Complete(storedException); + } + + public void Complete(Exception ex) + { + storedException = ex; + isCompleted = true; + waitHandle.Set(); + + if (callback != null) + callback(this); + } + } +} diff --git a/Mono.Nat/Enums/MapState.cs b/Mono.Nat/Enums/MapState.cs new file mode 100644 index 000000000..5ed2abd8f --- /dev/null +++ b/Mono.Nat/Enums/MapState.cs @@ -0,0 +1,36 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat +{ + public enum MapState + { + AlreadyMapped, + Available + } +} \ No newline at end of file diff --git a/Mono.Nat/Enums/ProtocolType.cs b/Mono.Nat/Enums/ProtocolType.cs new file mode 100644 index 000000000..a1f5cbb0e --- /dev/null +++ b/Mono.Nat/Enums/ProtocolType.cs @@ -0,0 +1,36 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat +{ + public enum Protocol + { + Tcp, + Udp + } +} \ No newline at end of file diff --git a/Mono.Nat/EventArgs/DeviceEventArgs.cs b/Mono.Nat/EventArgs/DeviceEventArgs.cs new file mode 100644 index 000000000..fbbbf63e3 --- /dev/null +++ b/Mono.Nat/EventArgs/DeviceEventArgs.cs @@ -0,0 +1,45 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat +{ + public class DeviceEventArgs : EventArgs + { + private INatDevice device; + + public DeviceEventArgs(INatDevice device) + { + this.device = device; + } + + public INatDevice Device + { + get { return this.device; } + } + } +} \ No newline at end of file diff --git a/Mono.Nat/Exceptions/MappingException.cs b/Mono.Nat/Exceptions/MappingException.cs new file mode 100644 index 000000000..bb2e6a69d --- /dev/null +++ b/Mono.Nat/Exceptions/MappingException.cs @@ -0,0 +1,87 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Security.Permissions; + +namespace Mono.Nat +{ + [Serializable] + public class MappingException : Exception + { + private int errorCode; + private string errorText; + + public int ErrorCode + { + get { return this.errorCode; } + } + + public string ErrorText + { + get { return this.errorText; } + } + + #region Constructors + public MappingException() + : base() + { + } + + public MappingException(string message) + : base(message) + { + } + + public MappingException(int errorCode, string errorText) + : base (string.Format ("Error {0}: {1}", errorCode, errorText)) + { + this.errorCode = errorCode; + this.errorText = errorText; + } + + public MappingException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected MappingException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } + #endregion + + [SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)] + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + { + if(info==null) throw new ArgumentNullException("info"); + + this.errorCode = info.GetInt32("errorCode"); + this.errorText = info.GetString("errorText"); + base.GetObjectData(info, context); + } + } +} diff --git a/Mono.Nat/IMapper.cs b/Mono.Nat/IMapper.cs new file mode 100644 index 000000000..b18e6cff2 --- /dev/null +++ b/Mono.Nat/IMapper.cs @@ -0,0 +1,50 @@ +// +// Authors: +// Nicholas Terry +// +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Mono.Nat +{ + public enum MapperType + { + Pmp, + Upnp + } + + internal interface IMapper + { + event EventHandler DeviceFound; + + void Map(IPAddress gatewayAddress); + + void Handle(IPAddress localAddres, byte[] response); + } +} diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs new file mode 100644 index 000000000..c9f27055b --- /dev/null +++ b/Mono.Nat/INatDevice.cs @@ -0,0 +1,62 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace Mono.Nat +{ + public interface INatDevice + { + void CreatePortMap (Mapping mapping); + void DeletePortMap (Mapping mapping); + + IPAddress LocalAddress { get; } + Mapping[] GetAllMappings (); + IPAddress GetExternalIP (); + Mapping GetSpecificMapping (Protocol protocol, int port); + + IAsyncResult BeginCreatePortMap (Mapping mapping, AsyncCallback callback, object asyncState); + IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState); + + IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState); + IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState); + IAsyncResult BeginGetSpecificMapping (Protocol protocol, int externalPort, AsyncCallback callback, object asyncState); + + void EndCreatePortMap (IAsyncResult result); + void EndDeletePortMap (IAsyncResult result); + + Mapping[] EndGetAllMappings (IAsyncResult result); + IPAddress EndGetExternalIP (IAsyncResult result); + Mapping EndGetSpecificMapping (IAsyncResult result); + + DateTime LastSeen { get; set; } + } +} diff --git a/Mono.Nat/ISearcher.cs b/Mono.Nat/ISearcher.cs new file mode 100644 index 000000000..56e438105 --- /dev/null +++ b/Mono.Nat/ISearcher.cs @@ -0,0 +1,51 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; + +namespace Mono.Nat +{ + public delegate void NatDeviceCallback(INatDevice device); + + internal interface ISearcher + { + event EventHandler DeviceFound; + event EventHandler DeviceLost; + + void Search(); + void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint); + DateTime NextSearch { get; } + NatProtocol Protocol { get; } + } +} diff --git a/Mono.Nat/Mapping.cs b/Mono.Nat/Mapping.cs new file mode 100644 index 000000000..dd49404c6 --- /dev/null +++ b/Mono.Nat/Mapping.cs @@ -0,0 +1,123 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat +{ + public class Mapping + { + private string description; + private DateTime expiration; + private int lifetime; + private int privatePort; + private Protocol protocol; + private int publicPort; + + + + public Mapping (Protocol protocol, int privatePort, int publicPort) + : this (protocol, privatePort, publicPort, 0) + { + } + + public Mapping (Protocol protocol, int privatePort, int publicPort, int lifetime) + { + this.protocol = protocol; + this.privatePort = privatePort; + this.publicPort = publicPort; + this.lifetime = lifetime; + + if (lifetime == int.MaxValue) + this.expiration = DateTime.MaxValue; + else if (lifetime == 0) + this.expiration = DateTime.Now; + else + this.expiration = DateTime.Now.AddSeconds (lifetime); + } + + public string Description + { + get { return description; } + set { description = value; } + } + + public Protocol Protocol + { + get { return protocol; } + internal set { protocol = value; } + } + + public int PrivatePort + { + get { return privatePort; } + internal set { privatePort = value; } + } + + public int PublicPort + { + get { return publicPort; } + internal set { publicPort = value; } + } + + public int Lifetime + { + get { return lifetime; } + internal set { lifetime = value; } + } + + public DateTime Expiration + { + get { return expiration; } + internal set { expiration = value; } + } + + public bool IsExpired () + { + return expiration < DateTime.Now; + } + + public override bool Equals (object obj) + { + Mapping other = obj as Mapping; + return other == null ? false : this.protocol == other.protocol && + this.privatePort == other.privatePort && this.publicPort == other.publicPort; + } + + public override int GetHashCode() + { + return this.protocol.GetHashCode() ^ this.privatePort.GetHashCode() ^ this.publicPort.GetHashCode(); + } + + public override string ToString( ) + { + return String.Format( "Protocol: {0}, Public Port: {1}, Private Port: {2}, Description: {3}, Expiration: {4}, Lifetime: {5}", + this.protocol, this.publicPort, this.privatePort, this.description, this.expiration, this.lifetime ); + } + } +} diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj new file mode 100644 index 000000000..9c2781433 --- /dev/null +++ b/Mono.Nat/Mono.Nat.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {D7453B88-2266-4805-B39B-2B5A2A33E1BA} + Library + Properties + Mono.Nat + Mono.Nat + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + Properties\SharedVersion.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + MediaBrowser.Controller + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + + + + + \ No newline at end of file diff --git a/Mono.Nat/NatProtocol.cs b/Mono.Nat/NatProtocol.cs new file mode 100644 index 000000000..ade8d921c --- /dev/null +++ b/Mono.Nat/NatProtocol.cs @@ -0,0 +1,9 @@ + +namespace Mono.Nat +{ + public enum NatProtocol + { + Upnp = 0, + Pmp = 1 + } +} diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs new file mode 100644 index 000000000..6d91d2513 --- /dev/null +++ b/Mono.Nat/NatUtility.cs @@ -0,0 +1,264 @@ +// +// Authors: +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Linq; +using System.Collections.Generic; +using System.IO; +using System.Net.NetworkInformation; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Model.Logging; +using Mono.Nat.Pmp.Mappers; +using Mono.Nat.Upnp.Mappers; + +namespace Mono.Nat +{ + public static class NatUtility + { + private static ManualResetEvent searching; + public static event EventHandler DeviceFound; + public static event EventHandler DeviceLost; + + public static event EventHandler UnhandledException; + + private static List controllers; + private static bool verbose; + + public static List EnabledProtocols { get; set; } + + public static ILogger Logger { get; set; } + + public static bool Verbose + { + get { return verbose; } + set { verbose = value; } + } + + static NatUtility() + { + EnabledProtocols = new List + { + NatProtocol.Upnp, + NatProtocol.Pmp + }; + + searching = new ManualResetEvent(false); + + controllers = new List(); + controllers.Add(UpnpSearcher.Instance); + controllers.Add(PmpSearcher.Instance); + + controllers.ForEach(searcher => + { + searcher.DeviceFound += (sender, args) => + { + if (DeviceFound != null) + DeviceFound(sender, args); + }; + searcher.DeviceLost += (sender, args) => + { + if (DeviceLost != null) + DeviceLost(sender, args); + }; + }); + Thread t = new Thread(SearchAndListen); + t.IsBackground = true; + t.Start(); + } + + internal static void Log(string format, params object[] args) + { + var logger = Logger; + if (logger != null) + logger.Debug(format, args); + } + + private static void SearchAndListen() + { + while (true) + { + searching.WaitOne(); + + try + { + var enabledProtocols = EnabledProtocols.ToList(); + + if (enabledProtocols.Contains(UpnpSearcher.Instance.Protocol)) + { + Receive(UpnpSearcher.Instance, UpnpSearcher.sockets); + } + if (enabledProtocols.Contains(PmpSearcher.Instance.Protocol)) + { + Receive(PmpSearcher.Instance, PmpSearcher.sockets); + } + + foreach (ISearcher s in controllers) + if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol)) + { + Log("Searching for: {0}", s.GetType().Name); + s.Search(); + } + } + catch (Exception e) + { + if (UnhandledException != null) + UnhandledException(typeof(NatUtility), new UnhandledExceptionEventArgs(e, false)); + } + Thread.Sleep(10); + } + } + + static void Receive (ISearcher searcher, List clients) + { + IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); + foreach (UdpClient client in clients) + { + if (client.Available > 0) + { + IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address; + byte[] data = client.Receive(ref received); + searcher.Handle(localAddress, data, received); + } + } + } + + static void Receive(IMapper mapper, List clients) + { + IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); + foreach (UdpClient client in clients) + { + if (client.Available > 0) + { + IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address; + byte[] data = client.Receive(ref received); + mapper.Handle(localAddress, data); + } + } + } + + public static void StartDiscovery () + { + searching.Set(); + } + + public static void StopDiscovery () + { + searching.Reset(); + } + + //This is for when you know the Gateway IP and want to skip the costly search... + public static void DirectMap(IPAddress gatewayAddress, MapperType type) + { + IMapper mapper; + switch (type) + { + case MapperType.Pmp: + mapper = new PmpMapper(); + break; + case MapperType.Upnp: + mapper = new UpnpMapper(); + mapper.DeviceFound += (sender, args) => + { + if (DeviceFound != null) + DeviceFound(sender, args); + }; + mapper.Map(gatewayAddress); + break; + default: + throw new InvalidOperationException("Unsuported type given"); + + } + searching.Reset(); + + } + + //So then why is it here? -Nick + [Obsolete ("This method serves no purpose and shouldn't be used")] + public static IPAddress[] GetLocalAddresses (bool includeIPv6) + { + List addresses = new List (); + + IPHostEntry hostInfo = Dns.GetHostEntry (Dns.GetHostName ()); + foreach (IPAddress address in hostInfo.AddressList) { + if (address.AddressFamily == AddressFamily.InterNetwork || + (includeIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6)) { + addresses.Add (address); + } + } + + return addresses.ToArray (); + } + + //checks if an IP address is a private address space as defined by RFC 1918 + public static bool IsPrivateAddressSpace (IPAddress address) + { + byte[] ba = address.GetAddressBytes (); + + switch ((int)ba[0]) { + case 10: + return true; //10.x.x.x + case 172: + return ((int)ba[1] & 16) != 0; //172.16-31.x.x + case 192: + return (int)ba[1] == 168; //192.168.x.x + default: + return false; + } + } + + public static void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint, NatProtocol protocol) + { + switch (protocol) + { + case NatProtocol.Upnp: + UpnpSearcher.Instance.Handle(localAddress, response, endpoint); + break; + case NatProtocol.Pmp: + PmpSearcher.Instance.Handle(localAddress, response, endpoint); + break; + default: + throw new ArgumentException("Unexpected protocol: " + protocol); + } + } + + public static void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol) + { + switch (protocol) + { + case NatProtocol.Upnp: + UpnpSearcher.Instance.Handle(localAddress, deviceInfo, endpoint); + break; + default: + throw new ArgumentException("Unexpected protocol: " + protocol); + } + } + } +} diff --git a/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs new file mode 100644 index 000000000..c8ccf5435 --- /dev/null +++ b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs @@ -0,0 +1,52 @@ +// +// Authors: +// Ben Motmans +// +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat.Pmp +{ + internal class PortMapAsyncResult : AsyncResult + { + private Mapping mapping; + + internal PortMapAsyncResult (Mapping mapping, AsyncCallback callback, object asyncState) + : base (callback, asyncState) + { + this.mapping = mapping; + } + + internal PortMapAsyncResult (Protocol protocol, int port, int lifetime, AsyncCallback callback, object asyncState) + : base (callback, asyncState) + { + this.mapping = new Mapping (protocol, port, port, lifetime); + } + + internal Mapping Mapping + { + get { return mapping; } + } + } +} diff --git a/Mono.Nat/Pmp/Mappers/PmpMapper.cs b/Mono.Nat/Pmp/Mappers/PmpMapper.cs new file mode 100644 index 000000000..f33ca44c3 --- /dev/null +++ b/Mono.Nat/Pmp/Mappers/PmpMapper.cs @@ -0,0 +1,83 @@ +// +// Authors: +// Nicholas Terry +// +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using Mono.Nat.Pmp; + +namespace Mono.Nat.Pmp.Mappers +{ + internal class PmpMapper : Pmp, IMapper + { + public event EventHandler DeviceFound; + + static PmpMapper() + { + CreateSocketsAndAddGateways(); + } + + public void Map(IPAddress gatewayAddress) + { + sockets.ForEach(x => Map(x, gatewayAddress)); + } + + void Map(UdpClient client, IPAddress gatewayAddress) + { + // The nat-pmp search message. Must be sent to GatewayIP:53531 + byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode }; + + client.Send(buffer, buffer.Length, new IPEndPoint(gatewayAddress, PmpConstants.ServerPort)); + } + + public void Handle(IPAddress localAddres, byte[] response) + { + //if (!IsSearchAddress(endpoint.Address)) + // return; + if (response.Length != 12) + return; + if (response[0] != PmpConstants.Version) + return; + if (response[1] != PmpConstants.ServerNoop) + return; + int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); + if (errorcode != 0) + NatUtility.Log("Non zero error: {0}", errorcode); + + IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] }); + OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(localAddres, publicIp))); + } + + private void OnDeviceFound(DeviceEventArgs args) + { + if (DeviceFound != null) + DeviceFound(this, args); + } + } +} diff --git a/Mono.Nat/Pmp/Pmp.cs b/Mono.Nat/Pmp/Pmp.cs new file mode 100644 index 000000000..6795561b1 --- /dev/null +++ b/Mono.Nat/Pmp/Pmp.cs @@ -0,0 +1,118 @@ +// +// Authors: +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; + +namespace Mono.Nat.Pmp +{ + internal abstract class Pmp + { + public static List sockets; + protected static Dictionary> gatewayLists; + + internal static void CreateSocketsAndAddGateways() + { + sockets = new List(); + gatewayLists = new Dictionary>(); + + try + { + foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) + { + if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown) + continue; + IPInterfaceProperties properties = n.GetIPProperties(); + List gatewayList = new List(); + + foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses) + { + if (gateway.Address.AddressFamily == AddressFamily.InterNetwork) + { + gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort)); + } + } + if (gatewayList.Count == 0) + { + /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */ + foreach (var gw2 in properties.DnsAddresses) + { + if (gw2.AddressFamily == AddressFamily.InterNetwork) + { + gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort)); + } + } + foreach (var unicast in properties.UnicastAddresses) + { + if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred + && unicast.AddressPreferredLifetime != UInt32.MaxValue + && */unicast.Address.AddressFamily == AddressFamily.InterNetwork) + { + var bytes = unicast.Address.GetAddressBytes(); + bytes[3] = 1; + gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort)); + } + } + } + + if (gatewayList.Count > 0) + { + foreach (UnicastIPAddressInformation address in properties.UnicastAddresses) + { + if (address.Address.AddressFamily == AddressFamily.InterNetwork) + { + UdpClient client; + + try + { + client = new UdpClient(new IPEndPoint(address.Address, 0)); + } + catch (SocketException) + { + continue; // Move on to the next address. + } + + gatewayLists.Add(client, gatewayList); + sockets.Add(client); + } + } + } + } + } + catch (Exception) + { + // NAT-PMP does not use multicast, so there isn't really a good fallback. + } + } + } +} diff --git a/Mono.Nat/Pmp/PmpConstants.cs b/Mono.Nat/Pmp/PmpConstants.cs new file mode 100644 index 000000000..ff3eb6230 --- /dev/null +++ b/Mono.Nat/Pmp/PmpConstants.cs @@ -0,0 +1,56 @@ +// +// Authors: +// Ben Motmans +// +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat.Pmp +{ + internal static class PmpConstants + { + public const byte Version = (byte)0; + + public const byte OperationCode = (byte)0; + public const byte OperationCodeUdp = (byte)1; + public const byte OperationCodeTcp = (byte)2; + public const byte ServerNoop = (byte)128; + + public const int ClientPort = 5350; + public const int ServerPort = 5351; + + public const int RetryDelay = 250; + public const int RetryAttempts = 9; + + public const int RecommendedLeaseTime = 60 * 60; + public const int DefaultLeaseTime = RecommendedLeaseTime; + + public const short ResultCodeSuccess = 0; + public const short ResultCodeUnsupportedVersion = 1; + public const short ResultCodeNotAuthorized = 2; + public const short ResultCodeNetworkFailure = 3; + public const short ResultCodeOutOfResources = 4; + public const short ResultCodeUnsupportedOperationCode = 5; + } +} \ No newline at end of file diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs new file mode 100644 index 000000000..9a2962c4d --- /dev/null +++ b/Mono.Nat/Pmp/PmpNatDevice.cs @@ -0,0 +1,347 @@ +// +// Authors: +// Ben Motmans +// +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Collections.Generic; + +namespace Mono.Nat.Pmp +{ + internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable + { + private AsyncResult externalIpResult; + private bool pendingOp; + private IPAddress localAddress; + private IPAddress publicAddress; + + internal PmpNatDevice (IPAddress localAddress, IPAddress publicAddress) + { + this.localAddress = localAddress; + this.publicAddress = publicAddress; + } + + public override IPAddress LocalAddress + { + get { return localAddress; } + } + + public override IPAddress GetExternalIP () + { + return publicAddress; + } + + public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState) + { + PortMapAsyncResult pmar = new PortMapAsyncResult (mapping.Protocol, mapping.PublicPort, PmpConstants.DefaultLeaseTime, callback, asyncState); + ThreadPool.QueueUserWorkItem (delegate + { + try + { + CreatePortMap(pmar.Mapping, true); + pmar.Complete(); + } + catch (Exception e) + { + pmar.Complete(e); + } + }); + return pmar; + } + + public override IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState) + { + PortMapAsyncResult pmar = new PortMapAsyncResult (mapping, callback, asyncState); + ThreadPool.QueueUserWorkItem (delegate { + try { + CreatePortMap(pmar.Mapping, false); + pmar.Complete(); + } catch (Exception e) { + pmar.Complete(e); + } + }); + return pmar; + } + + public override void EndCreatePortMap (IAsyncResult result) + { + PortMapAsyncResult pmar = result as PortMapAsyncResult; + pmar.AsyncWaitHandle.WaitOne (); + } + + public override void EndDeletePortMap (IAsyncResult result) + { + PortMapAsyncResult pmar = result as PortMapAsyncResult; + pmar.AsyncWaitHandle.WaitOne (); + } + + public override IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState) + { + //NAT-PMP does not specify a way to get all port mappings + throw new NotSupportedException (); + } + + public override IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState) + { + StartOp(ref externalIpResult, callback, asyncState); + AsyncResult result = externalIpResult; + result.Complete(); + return result; + } + + public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState) + { + //NAT-PMP does not specify a way to get a specific port map + throw new NotSupportedException (); + } + + public override Mapping[] EndGetAllMappings (IAsyncResult result) + { + //NAT-PMP does not specify a way to get all port mappings + throw new NotSupportedException (); + } + + public override IPAddress EndGetExternalIP (IAsyncResult result) + { + EndOp(result, ref externalIpResult); + return publicAddress; + } + + private void StartOp(ref AsyncResult result, AsyncCallback callback, object asyncState) + { + if (pendingOp == true) + throw new InvalidOperationException("Can only have one simultaenous async operation"); + + pendingOp = true; + result = new AsyncResult(callback, asyncState); + } + + private void EndOp(IAsyncResult supplied, ref AsyncResult actual) + { + if (supplied == null) + throw new ArgumentNullException("result"); + + if (supplied != actual) + throw new ArgumentException("Supplied IAsyncResult does not match the stored result"); + + if (!supplied.IsCompleted) + supplied.AsyncWaitHandle.WaitOne(); + + if (actual.StoredException != null) + throw actual.StoredException; + + pendingOp = false; + actual = null; + } + + public override Mapping EndGetSpecificMapping (IAsyncResult result) + { + //NAT-PMP does not specify a way to get a specific port map + throw new NotSupportedException (); + } + + public override bool Equals(object obj) + { + PmpNatDevice device = obj as PmpNatDevice; + return (device == null) ? false : this.Equals(device); + } + + public override int GetHashCode () + { + return this.publicAddress.GetHashCode(); + } + + public bool Equals (PmpNatDevice other) + { + return (other == null) ? false : this.publicAddress.Equals(other.publicAddress); + } + + private Mapping CreatePortMap (Mapping mapping, bool create) + { + List package = new List (); + + package.Add (PmpConstants.Version); + package.Add (mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp); + package.Add ((byte)0); //reserved + package.Add ((byte)0); //reserved + package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder((short)mapping.PrivatePort))); + package.AddRange (BitConverter.GetBytes (create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0)); + package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder(mapping.Lifetime))); + + CreatePortMapAsyncState state = new CreatePortMapAsyncState (); + state.Buffer = package.ToArray (); + state.Mapping = mapping; + + ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapAsync), state); + WaitHandle.WaitAll (new WaitHandle[] {state.ResetEvent}); + + if (!state.Success) { + string type = create ? "create" : "delete"; + throw new MappingException (String.Format ("Failed to {0} portmap (protocol={1}, private port={2}", type, mapping.Protocol, mapping.PrivatePort)); + } + + return state.Mapping; + } + + private void CreatePortMapAsync (object obj) + { + CreatePortMapAsyncState state = obj as CreatePortMapAsyncState; + + UdpClient udpClient = new UdpClient (); + CreatePortMapListenState listenState = new CreatePortMapListenState (state, udpClient); + + int attempt = 0; + int delay = PmpConstants.RetryDelay; + + ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapListen), listenState); + + while (attempt < PmpConstants.RetryAttempts && !listenState.Success) { + udpClient.Send (state.Buffer, state.Buffer.Length, new IPEndPoint (localAddress, PmpConstants.ServerPort)); + listenState.UdpClientReady.Set(); + + attempt++; + delay *= 2; + Thread.Sleep (delay); + } + + state.Success = listenState.Success; + + udpClient.Close (); + state.ResetEvent.Set (); + } + + private void CreatePortMapListen (object obj) + { + CreatePortMapListenState state = obj as CreatePortMapListenState; + + UdpClient udpClient = state.UdpClient; + state.UdpClientReady.WaitOne(); // Evidently UdpClient has some lazy-init Send/Receive race? + IPEndPoint endPoint = new IPEndPoint (localAddress, PmpConstants.ServerPort); + + while (!state.Success) + { + byte[] data; + try + { + data = udpClient.Receive(ref endPoint); + } + catch (SocketException) + { + state.Success = false; + return; + } + + catch (ObjectDisposedException) + { + state.Success = false; + return; + } + + if (data.Length < 16) + continue; + + if (data[0] != PmpConstants.Version) + continue; + + byte opCode = (byte)(data[1] & (byte)127); + + Protocol protocol = Protocol.Tcp; + if (opCode == PmpConstants.OperationCodeUdp) + protocol = Protocol.Udp; + + short resultCode = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 2)); + uint epoch = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 4)); + + int privatePort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 8)); + int publicPort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 10)); + + uint lifetime = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 12)); + + if (publicPort < 0 || privatePort < 0 || resultCode != PmpConstants.ResultCodeSuccess) + { + state.Success = false; + return; + } + + if (lifetime == 0) + { + //mapping was deleted + state.Success = true; + state.Mapping = null; + return; + } + else + { + //mapping was created + //TODO: verify that the private port+protocol are a match + Mapping mapping = state.Mapping; + mapping.PublicPort = publicPort; + mapping.Protocol = protocol; + mapping.Expiration = DateTime.Now.AddSeconds (lifetime); + + state.Success = true; + } + } + } + + + /// + /// Overridden. + /// + /// + public override string ToString( ) + { + return String.Format( "PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}", + this.localAddress, this.publicAddress, this.LastSeen ); + } + + + private class CreatePortMapAsyncState + { + internal byte[] Buffer; + internal ManualResetEvent ResetEvent = new ManualResetEvent (false); + internal Mapping Mapping; + + internal bool Success; + } + + private class CreatePortMapListenState + { + internal volatile bool Success; + internal Mapping Mapping; + internal UdpClient UdpClient; + internal ManualResetEvent UdpClientReady; + + internal CreatePortMapListenState (CreatePortMapAsyncState state, UdpClient client) + { + Mapping = state.Mapping; + UdpClient = client; UdpClientReady = new ManualResetEvent(false); + } + } + } +} \ No newline at end of file diff --git a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs new file mode 100644 index 000000000..df0273ccb --- /dev/null +++ b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs @@ -0,0 +1,149 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using Mono.Nat.Pmp; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Linq; + +namespace Mono.Nat +{ + internal class PmpSearcher : Pmp.Pmp, ISearcher + { + static PmpSearcher instance = new PmpSearcher(); + + + public static PmpSearcher Instance + { + get { return instance; } + } + + private int timeout; + private DateTime nextSearch; + public event EventHandler DeviceFound; + public event EventHandler DeviceLost; + + static PmpSearcher() + { + CreateSocketsAndAddGateways(); + } + + PmpSearcher() + { + timeout = 250; + } + + public void Search() + { + foreach (UdpClient s in sockets) + { + try + { + Search(s); + } + catch + { + // Ignore any search errors + } + } + } + + void Search (UdpClient client) + { + // Sort out the time for the next search first. The spec says the + // timeout should double after each attempt. Once it reaches 64 seconds + // (and that attempt fails), assume no devices available + nextSearch = DateTime.Now.AddMilliseconds(timeout); + timeout *= 2; + + // We've tried 9 times as per spec, try searching again in 5 minutes + if (timeout == 128 * 1000) + { + timeout = 250; + nextSearch = DateTime.Now.AddMinutes(10); + return; + } + + // The nat-pmp search message. Must be sent to GatewayIP:53531 + byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode }; + foreach (IPEndPoint gatewayEndpoint in gatewayLists[client]) + client.Send(buffer, buffer.Length, gatewayEndpoint); + } + + bool IsSearchAddress(IPAddress address) + { + foreach (List gatewayList in gatewayLists.Values) + foreach (IPEndPoint gatewayEndpoint in gatewayList) + if (gatewayEndpoint.Address.Equals(address)) + return true; + return false; + } + + public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + if (!IsSearchAddress(endpoint.Address)) + return; + if (response.Length != 12) + return; + if (response[0] != PmpConstants.Version) + return; + if (response[1] != PmpConstants.ServerNoop) + return; + int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2)); + if (errorcode != 0) + NatUtility.Log("Non zero error: {0}", errorcode); + + IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] }); + nextSearch = DateTime.Now.AddMinutes(5); + timeout = 250; + OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(endpoint.Address, publicIp))); + } + + public DateTime NextSearch + { + get { return nextSearch; } + } + private void OnDeviceFound(DeviceEventArgs args) + { + if (DeviceFound != null) + DeviceFound(this, args); + } + + public NatProtocol Protocol + { + get { return NatProtocol.Pmp; } + } + } +} diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c3c3101de --- /dev/null +++ b/Mono.Nat/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Mono.Nat")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Mono.Nat")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d7453b88-2266-4805-b39b-2b5a2a33e1ba")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// \ No newline at end of file diff --git a/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs new file mode 100644 index 000000000..51ecfbaf0 --- /dev/null +++ b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs @@ -0,0 +1,56 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace Mono.Nat.Upnp +{ + internal class GetAllMappingsAsyncResult : PortMapAsyncResult + { + private List mappings; + private Mapping specificMapping; + + public GetAllMappingsAsyncResult(WebRequest request, AsyncCallback callback, object asyncState) + : base(request, callback, asyncState) + { + mappings = new List(); + } + + public List Mappings + { + get { return this.mappings; } + } + + public Mapping SpecificMapping + { + get { return this.specificMapping; } + set { this.specificMapping = value; } + } + } +} diff --git a/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs new file mode 100644 index 000000000..d8ac3fe61 --- /dev/null +++ b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs @@ -0,0 +1,75 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + + +using System; +using System.Net; +using System.Threading; + +namespace Mono.Nat.Upnp +{ + internal class PortMapAsyncResult : AsyncResult + { + private WebRequest request; + private MessageBase savedMessage; + + protected PortMapAsyncResult(WebRequest request, AsyncCallback callback, object asyncState) + : base (callback, asyncState) + { + this.request = request; + } + + internal WebRequest Request + { + get { return this.request; } + set { this.request = value; } + } + + internal MessageBase SavedMessage + { + get { return this.savedMessage; } + set { this.savedMessage = value; } + } + + internal static PortMapAsyncResult Create (MessageBase message, WebRequest request, AsyncCallback storedCallback, object asyncState) + { + if (message is GetGenericPortMappingEntry) + return new GetAllMappingsAsyncResult(request, storedCallback, asyncState); + + if (message is GetSpecificPortMappingEntryMessage) + { + GetSpecificPortMappingEntryMessage mapMessage = (GetSpecificPortMappingEntryMessage)message; + GetAllMappingsAsyncResult result = new GetAllMappingsAsyncResult(request, storedCallback, asyncState); + + result.SpecificMapping = new Mapping(mapMessage.protocol, 0, mapMessage.externalPort, 0); + return result; + } + + return new PortMapAsyncResult(request, storedCallback, asyncState); + } + } +} diff --git a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs new file mode 100644 index 000000000..6f2716805 --- /dev/null +++ b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs @@ -0,0 +1,110 @@ +// +// Authors: +// Nicholas Terry +// +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace Mono.Nat.Upnp.Mappers +{ + internal class UpnpMapper : Upnp, IMapper + { + + public event EventHandler DeviceFound; + + public UdpClient Client { get; set; } + + public UpnpMapper() + { + //Bind to local port 1900 for ssdp responses + Client = new UdpClient(1900); + } + + public void Map(IPAddress gatewayAddress) + { + //Get the httpu request payload + byte[] data = DiscoverDeviceMessage.EncodeUnicast(gatewayAddress); + + Client.Send(data, data.Length, new IPEndPoint(gatewayAddress, 1900)); + + new Thread(Receive).Start(); + } + + public void Receive() + { + while (true) + { + IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351); + if (Client.Available > 0) + { + IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address; + byte[] data = Client.Receive(ref received); + Handle(localAddress, data, received); + } + } + } + + public void Handle(IPAddress localAddres, byte[] response) + { + Handle(localAddres, response, null); + } + + public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + // No matter what, this method should never throw an exception. If something goes wrong + // we should still be in a position to handle the next reply correctly. + try + { + UpnpNatDevice d = base.Handle(localAddress, response, endpoint); + d.GetServicesList(DeviceSetupComplete); + } + catch (Exception ex) + { + Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: "); + Trace.WriteLine("ErrorMessage:"); + Trace.WriteLine(ex.Message); + Trace.WriteLine("Data string:"); + Trace.WriteLine(Encoding.UTF8.GetString(response)); + } + } + + private void DeviceSetupComplete(INatDevice device) + { + OnDeviceFound(new DeviceEventArgs(device)); + } + + private void OnDeviceFound(DeviceEventArgs args) + { + if (DeviceFound != null) + DeviceFound(this, args); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs new file mode 100644 index 000000000..87f5835a6 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs @@ -0,0 +1,60 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Net; +using System.Text; + +namespace Mono.Nat.Upnp +{ + internal static class DiscoverDeviceMessage + { + /// + /// The message sent to discover all uPnP devices on the network + /// + /// + public static byte[] EncodeSSDP() + { + string s = "M-SEARCH * HTTP/1.1\r\n" + + "HOST: 239.255.255.250:1900\r\n" + + "MAN: \"ssdp:discover\"\r\n" + + "MX: 3\r\n" + + "ST: ssdp:all\r\n\r\n"; + return UTF8Encoding.ASCII.GetBytes(s); + } + + public static byte[] EncodeUnicast(IPAddress gatewayAddress) + { + //Format obtained from http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf pg 31 + //This method only works with upnp 1.1 routers... unfortunately + string s = "M-SEARCH * HTTP/1.1\r\n" + + "HOST: " + gatewayAddress + ":1900\r\n" + + "MAN: \"ssdp:discover\"\r\n" + + "ST: ssdp:all\r\n\r\n"; + //+ "USER-AGENT: unix/5.1 UPnP/1.1 MyProduct/1.0\r\n\r\n"; + return UTF8Encoding.ASCII.GetBytes(s); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/ErrorMessage.cs b/Mono.Nat/Upnp/Messages/ErrorMessage.cs new file mode 100644 index 000000000..ce5270e9b --- /dev/null +++ b/Mono.Nat/Upnp/Messages/ErrorMessage.cs @@ -0,0 +1,63 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; + +namespace Mono.Nat.Upnp +{ + internal class ErrorMessage : MessageBase + { + #region Member Variables + public string Description + { + get { return this.description; } + } + private string description; + + public int ErrorCode + { + get { return this.errorCode; } + } + private int errorCode; + #endregion + + + #region Constructors + public ErrorMessage(int errorCode, string description) + :base(null) + { + this.description = description; + this.errorCode = errorCode; + } + #endregion + + + public override System.Net.WebRequest Encode(out byte[] body) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs new file mode 100644 index 000000000..c5d7bce70 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs @@ -0,0 +1,62 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Diagnostics; +using System.Net; + +namespace Mono.Nat.Upnp +{ + internal class GetServicesMessage : MessageBase + { + private string servicesDescriptionUrl; + private EndPoint hostAddress; + + public GetServicesMessage(string description, EndPoint hostAddress) + :base(null) + { + if (string.IsNullOrEmpty(description)) + Trace.WriteLine("Description is null"); + + if (hostAddress == null) + Trace.WriteLine("hostaddress is null"); + + this.servicesDescriptionUrl = description; + this.hostAddress = hostAddress; + } + + + public override WebRequest Encode(out byte[] body) + { + HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl); + req.Headers.Add("ACCEPT-LANGUAGE", "en"); + req.Method = "GET"; + + body = new byte[0]; + return req; + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs new file mode 100644 index 000000000..da650fb41 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs @@ -0,0 +1,75 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Net; +using System.IO; +using System.Globalization; +using System.Text; +using System.Xml; + +namespace Mono.Nat.Upnp +{ + internal class CreatePortMappingMessage : MessageBase + { + #region Private Fields + + private IPAddress localIpAddress; + private Mapping mapping; + + #endregion + + + #region Constructors + public CreatePortMappingMessage(Mapping mapping, IPAddress localIpAddress, UpnpNatDevice device) + : base(device) + { + this.mapping = mapping; + this.localIpAddress = localIpAddress; + } + #endregion + + + public override WebRequest Encode(out byte[] body) + { + CultureInfo culture = CultureInfo.InvariantCulture; + + StringBuilder builder = new StringBuilder(256); + XmlWriter writer = CreateWriter(builder); + + WriteFullElement(writer, "NewRemoteHost", string.Empty); + WriteFullElement(writer, "NewExternalPort", this.mapping.PublicPort.ToString(culture)); + WriteFullElement(writer, "NewProtocol", this.mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP"); + WriteFullElement(writer, "NewInternalPort", this.mapping.PrivatePort.ToString(culture)); + WriteFullElement(writer, "NewInternalClient", this.localIpAddress.ToString()); + WriteFullElement(writer, "NewEnabled", "1"); + WriteFullElement(writer, "NewPortMappingDescription", string.IsNullOrEmpty(mapping.Description) ? "Mono.Nat" : mapping.Description); + WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString()); + + writer.Flush(); + return CreateRequest("AddPortMapping", builder.ToString(), out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs new file mode 100644 index 000000000..d9be89a69 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs @@ -0,0 +1,57 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System.Net; +using System.IO; +using System.Text; +using System.Xml; + +namespace Mono.Nat.Upnp +{ + internal class DeletePortMappingMessage : MessageBase + { + private Mapping mapping; + + public DeletePortMappingMessage(Mapping mapping, UpnpNatDevice device) + : base(device) + { + this.mapping = mapping; + } + + public override WebRequest Encode(out byte[] body) + { + StringBuilder builder = new StringBuilder(256); + XmlWriter writer = CreateWriter(builder); + + WriteFullElement(writer, "NewRemoteHost", string.Empty); + WriteFullElement(writer, "NewExternalPort", mapping.PublicPort.ToString(MessageBase.Culture)); + WriteFullElement(writer, "NewProtocol", mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP"); + + writer.Flush(); + return CreateRequest("DeletePortMapping", builder.ToString(), out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs new file mode 100644 index 000000000..8f97002ea --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs @@ -0,0 +1,51 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.IO; + +namespace Mono.Nat.Upnp +{ + internal class GetExternalIPAddressMessage : MessageBase + { + + #region Constructors + public GetExternalIPAddressMessage(UpnpNatDevice device) + :base(device) + { + } + #endregion + + + public override WebRequest Encode(out byte[] body) + { + return CreateRequest("GetExternalIPAddress", string.Empty, out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs new file mode 100644 index 000000000..c0c555881 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs @@ -0,0 +1,55 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; + +namespace Mono.Nat.Upnp +{ + internal class GetGenericPortMappingEntry : MessageBase + { + private int index; + + public GetGenericPortMappingEntry(int index, UpnpNatDevice device) + :base(device) + { + this.index = index; + } + + public override System.Net.WebRequest Encode(out byte[] body) + { + StringBuilder sb = new StringBuilder(128); + XmlWriter writer = CreateWriter(sb); + + WriteFullElement(writer, "NewPortMappingIndex", index.ToString()); + + writer.Flush(); + return CreateRequest("GetGenericPortMappingEntry", sb.ToString(), out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs new file mode 100644 index 000000000..314468ece --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs @@ -0,0 +1,60 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; +using System.Net; + +namespace Mono.Nat.Upnp +{ + internal class GetSpecificPortMappingEntryMessage : MessageBase + { + internal Protocol protocol; + internal int externalPort; + + public GetSpecificPortMappingEntryMessage(Protocol protocol, int externalPort, UpnpNatDevice device) + : base(device) + { + this.protocol = protocol; + this.externalPort = externalPort; + } + + public override WebRequest Encode(out byte[] body) + { + StringBuilder sb = new StringBuilder(64); + XmlWriter writer = CreateWriter(sb); + + WriteFullElement(writer, "NewRemoteHost", string.Empty); + WriteFullElement(writer, "NewExternalPort", externalPort.ToString()); + WriteFullElement(writer, "NewProtocol", protocol == Protocol.Tcp ? "TCP" : "UDP"); + writer.Flush(); + + return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs new file mode 100644 index 000000000..e75926b09 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs @@ -0,0 +1,46 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + + +using System; +namespace Mono.Nat.Upnp +{ + internal class CreatePortMappingResponseMessage : MessageBase + { + #region Constructors + public CreatePortMappingResponseMessage() + :base(null) + { + } + #endregion + + public override System.Net.WebRequest Encode(out byte[] body) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs new file mode 100644 index 000000000..1fce4eb04 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs @@ -0,0 +1,44 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + + + +using System; +namespace Mono.Nat.Upnp +{ + internal class DeletePortMapResponseMessage : MessageBase + { + public DeletePortMapResponseMessage() + :base(null) + { + } + + public override System.Net.WebRequest Encode(out byte[] body) + { + throw new NotSupportedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs new file mode 100644 index 000000000..ee4b18cd1 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs @@ -0,0 +1,53 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace Mono.Nat.Upnp +{ + internal class GetExternalIPAddressResponseMessage : MessageBase + { + public IPAddress ExternalIPAddress + { + get { return this.externalIPAddress; } + } + private IPAddress externalIPAddress; + + public GetExternalIPAddressResponseMessage(string ip) + :base(null) + { + this.externalIPAddress = IPAddress.Parse(ip); + } + + public override WebRequest Encode(out byte[] body) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs new file mode 100644 index 000000000..b11bfa027 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs @@ -0,0 +1,108 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml; + +namespace Mono.Nat.Upnp +{ + internal class GetGenericPortMappingEntryResponseMessage : MessageBase + { + private string remoteHost; + private int externalPort; + private Protocol protocol; + private int internalPort; + private string internalClient; + private bool enabled; + private string portMappingDescription; + private int leaseDuration; + + public string RemoteHost + { + get { return this.remoteHost; } + } + + public int ExternalPort + { + get { return this.externalPort; } + } + + public Protocol Protocol + { + get { return this.protocol; } + } + + public int InternalPort + { + get { return this.internalPort; } + } + + public string InternalClient + { + get { return this.internalClient; } + } + + public bool Enabled + { + get { return this.enabled; } + } + + public string PortMappingDescription + { + get { return this.portMappingDescription; } + } + + public int LeaseDuration + { + get { return this.leaseDuration; } + } + + + public GetGenericPortMappingEntryResponseMessage(XmlNode data, bool genericMapping) + : base(null) + { + remoteHost = (genericMapping) ? data["NewRemoteHost"].InnerText : string.Empty; + externalPort = (genericMapping) ? Convert.ToInt32(data["NewExternalPort"].InnerText) : -1; + if (genericMapping) + protocol = data["NewProtocol"].InnerText.Equals("TCP", StringComparison.InvariantCultureIgnoreCase) ? Protocol.Tcp : Protocol.Udp; + else + protocol = Protocol.Udp; + + internalPort = Convert.ToInt32(data["NewInternalPort"].InnerText); + internalClient = data["NewInternalClient"].InnerText; + enabled = data["NewEnabled"].InnerText == "1" ? true : false; + portMappingDescription = data["NewPortMappingDescription"].InnerText; + leaseDuration = Convert.ToInt32(data["NewLeaseDuration"].InnerText); + } + + public override System.Net.WebRequest Encode(out byte[] body) + { + throw new NotImplementedException(); + } + } +} diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs new file mode 100644 index 000000000..44c16eec6 --- /dev/null +++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs @@ -0,0 +1,132 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// +// Copyright (C) 2006 Alan McGovern +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Diagnostics; +using System.Xml; +using System.Net; +using System.IO; +using System.Text; +using System.Globalization; + +namespace Mono.Nat.Upnp +{ + internal abstract class MessageBase + { + internal static readonly CultureInfo Culture = CultureInfo.InvariantCulture; + protected UpnpNatDevice device; + + protected MessageBase(UpnpNatDevice device) + { + this.device = device; + } + + protected WebRequest CreateRequest(string upnpMethod, string methodParameters, out byte[] body) + { + string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl; + NatUtility.Log("Initiating request to: {0}", ss); + Uri location = new Uri(ss); + + HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(location); + req.KeepAlive = false; + req.Method = "POST"; + req.ContentType = "text/xml; charset=\"utf-8\""; + req.Headers.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\""); + + string bodyString = "" + + "" + + "" + + methodParameters + + "" + + "" + + "\r\n\r\n"; + + body = System.Text.Encoding.UTF8.GetBytes(bodyString); + return req; + } + + public static MessageBase Decode(UpnpNatDevice device, string message) + { + XmlNode node; + XmlDocument doc = new XmlDocument(); + doc.LoadXml(message); + + XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable); + + // Error messages should be found under this namespace + nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0"); + nsm.AddNamespace("responseNs", device.ServiceType); + + // Check to see if we have a fault code message. + if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) { + string errorCode = node["errorCode"] != null ? node["errorCode"].InnerText : ""; + string errorDescription = node["errorDescription"] != null ? node["errorDescription"].InnerText : ""; + + return new ErrorMessage(Convert.ToInt32(errorCode, CultureInfo.InvariantCulture), errorDescription); + } + + if ((doc.SelectSingleNode("//responseNs:AddPortMappingResponse", nsm)) != null) + return new CreatePortMappingResponseMessage(); + + if ((doc.SelectSingleNode("//responseNs:DeletePortMappingResponse", nsm)) != null) + return new DeletePortMapResponseMessage(); + + if ((node = doc.SelectSingleNode("//responseNs:GetExternalIPAddressResponse", nsm)) != null) { + string newExternalIPAddress = node["NewExternalIPAddress"] != null ? node["NewExternalIPAddress"].InnerText : ""; + return new GetExternalIPAddressResponseMessage(newExternalIPAddress); + } + + if ((node = doc.SelectSingleNode("//responseNs:GetGenericPortMappingEntryResponse", nsm)) != null) + return new GetGenericPortMappingEntryResponseMessage(node, true); + + if ((node = doc.SelectSingleNode("//responseNs:GetSpecificPortMappingEntryResponse", nsm)) != null) + return new GetGenericPortMappingEntryResponseMessage(node, false); + + NatUtility.Log("Unknown message returned. Please send me back the following XML:"); + NatUtility.Log(message); + return null; + } + + public abstract WebRequest Encode(out byte[] body); + + internal static void WriteFullElement(XmlWriter writer, string element, string value) + { + writer.WriteStartElement(element); + writer.WriteString(value); + writer.WriteEndElement(); + } + + internal static XmlWriter CreateWriter(StringBuilder sb) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.ConformanceLevel = ConformanceLevel.Fragment; + return XmlWriter.Create(sb, settings); + } + } +} diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs new file mode 100644 index 000000000..edc5a5d76 --- /dev/null +++ b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs @@ -0,0 +1,287 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using Mono.Nat.Upnp; +using System.Diagnostics; +using System.Net.Sockets; +using System.Net.NetworkInformation; +using MediaBrowser.Controller.Dlna; + +namespace Mono.Nat +{ + internal class UpnpSearcher : ISearcher + { + private const int SearchPeriod = 5 * 60; // The time in seconds between each search + static UpnpSearcher instance = new UpnpSearcher(); + public static List sockets = CreateSockets(); + + public static UpnpSearcher Instance + { + get { return instance; } + } + + public event EventHandler DeviceFound; + public event EventHandler DeviceLost; + + private List devices; + private Dictionary lastFetched; + private DateTime nextSearch; + private IPEndPoint searchEndpoint; + + UpnpSearcher() + { + devices = new List(); + lastFetched = new Dictionary(); + //searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900); + searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900); + } + + static List CreateSockets() + { + List clients = new List(); + try + { + foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces()) + { + foreach (UnicastIPAddressInformation address in n.GetIPProperties().UnicastAddresses) + { + if (address.Address.AddressFamily == AddressFamily.InterNetwork) + { + try + { + clients.Add(new UdpClient(new IPEndPoint(address.Address, 0))); + } + catch + { + continue; // Move on to the next address. + } + } + } + } + } + catch (Exception) + { + clients.Add(new UdpClient(0)); + } + return clients; + } + + public void Search() + { + foreach (UdpClient s in sockets) + { + try + { + Search(s); + } + catch + { + // Ignore any search errors + } + } + } + + void Search(UdpClient client) + { + nextSearch = DateTime.Now.AddSeconds(SearchPeriod); + byte[] data = DiscoverDeviceMessage.EncodeSSDP(); + + // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2) + for (int i = 0; i < 3; i++) + client.Send(data, data.Length, searchEndpoint); + } + + public IPEndPoint SearchEndpoint + { + get { return searchEndpoint; } + } + + public void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint) + { + // No matter what, this method should never throw an exception. If something goes wrong + // we should still be in a position to handle the next reply correctly. + try + { + /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. + Any other device type is no good to us for this purpose. See the IGP overview paper + page 5 for an overview of device types and their hierarchy. + http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ + + /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which + version it is and apply the correct URN. */ + + /* Some routers don't correctly implement the version ID on the URN, so we only search for the type + prefix. */ + + // We have an internet gateway device now + UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty); + + if (devices.Contains(d)) + { + // We already have found this device, so we just refresh it to let people know it's + // Still alive. If a device doesn't respond to a search, we dump it. + devices[devices.IndexOf(d)].LastSeen = DateTime.Now; + } + else + { + + // If we send 3 requests at a time, ensure we only fetch the services list once + // even if three responses are received + if (lastFetched.ContainsKey(endpoint.Address)) + { + DateTime last = lastFetched[endpoint.Address]; + if ((DateTime.Now - last) < TimeSpan.FromSeconds(20)) + return; + } + lastFetched[endpoint.Address] = DateTime.Now; + + // Once we've parsed the information we need, we tell the device to retrieve it's service list + // Once we successfully receive the service list, the callback provided will be invoked. + NatUtility.Log("Fetching service list: {0}", d.HostEndPoint); + d.GetServicesList(DeviceSetupComplete); + } + } + catch (Exception ex) + { + NatUtility.Log("Unhandled exception when trying to decode a device's response Send me the following data: "); + NatUtility.Log("ErrorMessage:"); + NatUtility.Log(ex.Message); + } + } + + public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + // Convert it to a string for easy parsing + string dataString = null; + + // No matter what, this method should never throw an exception. If something goes wrong + // we should still be in a position to handle the next reply correctly. + try { + string urn; + dataString = Encoding.UTF8.GetString(response); + + if (NatUtility.Verbose) + NatUtility.Log("UPnP Response: {0}", dataString); + + /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. + Any other device type is no good to us for this purpose. See the IGP overview paper + page 5 for an overview of device types and their hierarchy. + http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ + + /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which + version it is and apply the correct URN. */ + + /* Some routers don't correctly implement the version ID on the URN, so we only search for the type + prefix. */ + + string log = "UPnP Response: Router advertised a '{0}' service"; + StringComparison c = StringComparison.OrdinalIgnoreCase; + if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) { + urn = "urn:schemas-upnp-org:service:WANIPConnection:1"; + NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1"); + } else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) { + urn = "urn:schemas-upnp-org:service:WANPPPConnection:1"; + NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:"); + } else + return; + + // We have an internet gateway device now + UpnpNatDevice d = new UpnpNatDevice(localAddress, dataString, urn); + + if (devices.Contains(d)) + { + // We already have found this device, so we just refresh it to let people know it's + // Still alive. If a device doesn't respond to a search, we dump it. + devices[devices.IndexOf(d)].LastSeen = DateTime.Now; + } + else + { + + // If we send 3 requests at a time, ensure we only fetch the services list once + // even if three responses are received + if (lastFetched.ContainsKey(endpoint.Address)) + { + DateTime last = lastFetched[endpoint.Address]; + if ((DateTime.Now - last) < TimeSpan.FromSeconds(20)) + return; + } + lastFetched[endpoint.Address] = DateTime.Now; + + // Once we've parsed the information we need, we tell the device to retrieve it's service list + // Once we successfully receive the service list, the callback provided will be invoked. + NatUtility.Log("Fetching service list: {0}", d.HostEndPoint); + d.GetServicesList(DeviceSetupComplete); + } + } + catch (Exception ex) + { + Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: "); + Trace.WriteLine("ErrorMessage:"); + Trace.WriteLine(ex.Message); + Trace.WriteLine("Data string:"); + Trace.WriteLine(dataString); + } + } + + public DateTime NextSearch + { + get { return nextSearch; } + } + + private void DeviceSetupComplete(INatDevice device) + { + lock (this.devices) + { + // We don't want the same device in there twice + if (devices.Contains(device)) + return; + + devices.Add(device); + } + + OnDeviceFound(new DeviceEventArgs(device)); + } + + private void OnDeviceFound(DeviceEventArgs args) + { + if (DeviceFound != null) + DeviceFound(this, args); + } + + public NatProtocol Protocol + { + get { return NatProtocol.Upnp; } + } + } +} diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs new file mode 100644 index 000000000..e44a51c24 --- /dev/null +++ b/Mono.Nat/Upnp/Upnp.cs @@ -0,0 +1,83 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// Nicholas Terry +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// Copyright (C) 2014 Nicholas Terry +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; + +namespace Mono.Nat.Upnp +{ + internal class Upnp + { + public UpnpNatDevice Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint) + { + // Convert it to a string for easy parsing + string dataString = null; + + + string urn; + dataString = Encoding.UTF8.GetString(response); + + if (NatUtility.Verbose) + NatUtility.Log("UPnP Response: {0}", dataString); + + /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection. + Any other device type is no good to us for this purpose. See the IGP overview paper + page 5 for an overview of device types and their hierarchy. + http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */ + + /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which + version it is and apply the correct URN. */ + + /* Some routers don't correctly implement the version ID on the URN, so we only search for the type + prefix. */ + + string log = "UPnP Response: Router advertised a '{0}' service"; + StringComparison c = StringComparison.OrdinalIgnoreCase; + if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) + { + urn = "urn:schemas-upnp-org:service:WANIPConnection:1"; + NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1"); + } + else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) + { + urn = "urn:schemas-upnp-org:service:WANPPPConnection:1"; + NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:"); + } + else + throw new NotSupportedException("Received non-supported device type"); + + // We have an internet gateway device now + return new UpnpNatDevice(localAddress, dataString, urn); + } + } +} diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs new file mode 100644 index 000000000..1160d3ac2 --- /dev/null +++ b/Mono.Nat/Upnp/UpnpNatDevice.cs @@ -0,0 +1,651 @@ +// +// Authors: +// Alan McGovern alan.mcgovern@gmail.com +// Ben Motmans +// +// Copyright (C) 2006 Alan McGovern +// Copyright (C) 2007 Ben Motmans +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.IO; +using System.Net; +using System.Xml; +using System.Text; +using System.Diagnostics; +using MediaBrowser.Controller.Dlna; + +namespace Mono.Nat.Upnp +{ + public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable + { + private EndPoint hostEndPoint; + private IPAddress localAddress; + private string serviceDescriptionUrl; + private string controlUrl; + private string serviceType; + + public override IPAddress LocalAddress + { + get { return localAddress; } + } + + /// + /// The callback to invoke when we are finished setting up the device + /// + private NatDeviceCallback callback; + + internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType) + { + this.LastSeen = DateTime.Now; + this.localAddress = localAddress; + + // Split the string at the "location" section so i can extract the ipaddress and service description url + string locationDetails = deviceInfo.Location.ToString(); + this.serviceType = serviceType; + + // Make sure we have no excess whitespace + locationDetails = locationDetails.Trim(); + + // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address + // Are we going to get addresses with the "http://" attached? + if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) + { + NatUtility.Log("Found device at: {0}", locationDetails); + // This bit strings out the "http://" from the string + locationDetails = locationDetails.Substring(7); + + this.hostEndPoint = hostEndPoint; + + NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString()); + + // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip + // and port information + this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/')); + } + else + { + NatUtility.Log("Couldn't decode address. Please send following string to the developer: "); + } + } + + internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType) + { + this.LastSeen = DateTime.Now; + this.localAddress = localAddress; + + // Split the string at the "location" section so i can extract the ipaddress and service description url + string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.InvariantCultureIgnoreCase) + 9).Split('\r')[0]; + this.serviceType = serviceType; + + // Make sure we have no excess whitespace + locationDetails = locationDetails.Trim(); + + // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address + // Are we going to get addresses with the "http://" attached? + if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase)) + { + NatUtility.Log("Found device at: {0}", locationDetails); + // This bit strings out the "http://" from the string + locationDetails = locationDetails.Substring(7); + + // We then split off the end of the string to get something like: 192.168.0.3:241 in our string + string hostAddressAndPort = locationDetails.Remove(locationDetails.IndexOf('/')); + + // From this we parse out the IP address and Port + if (hostAddressAndPort.IndexOf(':') > 0) + { + this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))), + Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture)); + } + else + { + // there is no port specified, use default port (80) + this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort), 80); + } + + NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString()); + + // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip + // and port information + this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/')); + } + else + { + Trace.WriteLine("Couldn't decode address. Please send following string to the developer: "); + Trace.WriteLine(deviceDetails); + } + } + + /// + /// The EndPoint that the device is at + /// + internal EndPoint HostEndPoint + { + get { return this.hostEndPoint; } + } + + /// + /// The relative url of the xml file that describes the list of services is at + /// + internal string ServiceDescriptionUrl + { + get { return this.serviceDescriptionUrl; } + } + + /// + /// The relative url that we can use to control the port forwarding + /// + internal string ControlUrl + { + get { return this.controlUrl; } + } + + /// + /// The service type we're using on the device + /// + public string ServiceType + { + get { return serviceType; } + } + + /// + /// Begins an async call to get the external ip address of the router + /// + public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState) + { + // Create the port map message + GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this); + return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal); + } + + /// + /// Maps the specified port to this computer + /// + public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState) + { + CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this); + return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal); + } + + /// + /// Removes a port mapping from this computer + /// + public override IAsyncResult BeginDeletePortMap(Mapping mapping, AsyncCallback callback, object asyncState) + { + DeletePortMappingMessage message = new DeletePortMappingMessage(mapping, this); + return BeginMessageInternal(message, callback, asyncState, EndDeletePortMapInternal); + } + + + public override IAsyncResult BeginGetAllMappings(AsyncCallback callback, object asyncState) + { + GetGenericPortMappingEntry message = new GetGenericPortMappingEntry(0, this); + return BeginMessageInternal(message, callback, asyncState, EndGetAllMappingsInternal); + } + + + public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState) + { + GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this); + return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal)); + } + + /// + /// + /// + /// + public override void EndCreatePortMap(IAsyncResult result) + { + if (result == null) throw new ArgumentNullException("result"); + + PortMapAsyncResult mappingResult = result as PortMapAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + // Check if we need to wait for the operation to finish + if (!result.IsCompleted) + result.AsyncWaitHandle.WaitOne(); + + // If we have a saved exception, it means something went wrong during the mapping + // so we just rethrow the exception and let the user figure out what they should do. + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; + throw new MappingException(msg.ErrorCode, msg.Description); + } + + //return result.AsyncState as Mapping; + } + + + /// + /// + /// + /// + public override void EndDeletePortMap(IAsyncResult result) + { + if (result == null) + throw new ArgumentNullException("result"); + + PortMapAsyncResult mappingResult = result as PortMapAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + // Check if we need to wait for the operation to finish + if (!mappingResult.IsCompleted) + mappingResult.AsyncWaitHandle.WaitOne(); + + // If we have a saved exception, it means something went wrong during the mapping + // so we just rethrow the exception and let the user figure out what they should do. + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; + throw new MappingException(msg.ErrorCode, msg.Description); + } + + // If all goes well, we just return + //return true; + } + + + public override Mapping[] EndGetAllMappings(IAsyncResult result) + { + if (result == null) + throw new ArgumentNullException("result"); + + GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + if (!mappingResult.IsCompleted) + mappingResult.AsyncWaitHandle.WaitOne(); + + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; + if (msg.ErrorCode != 713) + throw new MappingException(msg.ErrorCode, msg.Description); + } + + return mappingResult.Mappings.ToArray(); + } + + + /// + /// Ends an async request to get the external ip address of the router + /// + public override IPAddress EndGetExternalIP(IAsyncResult result) + { + if (result == null) throw new ArgumentNullException("result"); + + PortMapAsyncResult mappingResult = result as PortMapAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + if (!result.IsCompleted) + result.AsyncWaitHandle.WaitOne(); + + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage; + throw new MappingException(msg.ErrorCode, msg.Description); + } + + if (mappingResult.SavedMessage == null) + return null; + else + return ((GetExternalIPAddressResponseMessage)mappingResult.SavedMessage).ExternalIPAddress; + } + + + public override Mapping EndGetSpecificMapping(IAsyncResult result) + { + if (result == null) + throw new ArgumentNullException("result"); + + GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult; + if (mappingResult == null) + throw new ArgumentException("Invalid AsyncResult", "result"); + + if (!mappingResult.IsCompleted) + mappingResult.AsyncWaitHandle.WaitOne(); + + if (mappingResult.SavedMessage is ErrorMessage) + { + ErrorMessage message = mappingResult.SavedMessage as ErrorMessage; + if (message.ErrorCode != 0x2ca) + { + throw new MappingException(message.ErrorCode, message.Description); + } + } + if (mappingResult.Mappings.Count == 0) + return new Mapping (Protocol.Tcp, -1, -1); + + return mappingResult.Mappings[0]; + } + + + public override bool Equals(object obj) + { + UpnpNatDevice device = obj as UpnpNatDevice; + return (device == null) ? false : this.Equals((device)); + } + + + public bool Equals(UpnpNatDevice other) + { + return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint) + //&& this.controlUrl == other.controlUrl + && this.serviceDescriptionUrl == other.serviceDescriptionUrl); + } + + public override int GetHashCode() + { + return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode()); + } + + private IAsyncResult BeginMessageInternal(MessageBase message, AsyncCallback storedCallback, object asyncState, AsyncCallback callback) + { + byte[] body; + WebRequest request = message.Encode(out body); + PortMapAsyncResult mappingResult = PortMapAsyncResult.Create(message, request, storedCallback, asyncState); + + if (body.Length > 0) + { + request.ContentLength = body.Length; + request.BeginGetRequestStream(delegate(IAsyncResult result) { + try + { + Stream s = request.EndGetRequestStream(result); + s.Write(body, 0, body.Length); + request.BeginGetResponse(callback, mappingResult); + } + catch (Exception ex) + { + mappingResult.Complete(ex); + } + }, null); + } + else + { + request.BeginGetResponse(callback, mappingResult); + } + return mappingResult; + } + + private void CompleteMessage(IAsyncResult result) + { + PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult; + mappingResult.CompletedSynchronously = result.CompletedSynchronously; + mappingResult.Complete(); + } + + private MessageBase DecodeMessageFromResponse(Stream s, long length) + { + StringBuilder data = new StringBuilder(); + int bytesRead = 0; + int totalBytesRead = 0; + byte[] buffer = new byte[10240]; + + // Read out the content of the message, hopefully picking everything up in the case where we have no contentlength + if (length != -1) + { + while (totalBytesRead < length) + { + bytesRead = s.Read(buffer, 0, buffer.Length); + data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); + totalBytesRead += bytesRead; + } + } + else + { + while ((bytesRead = s.Read(buffer, 0, buffer.Length)) != 0) + data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); + } + + // Once we have our content, we need to see what kind of message it is. It'll either a an error + // or a response based on the action we performed. + return MessageBase.Decode(this, data.ToString()); + } + + private void EndCreatePortMapInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } + + private void EndMessageInternal(IAsyncResult result) + { + HttpWebResponse response = null; + PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult; + + try + { + try + { + response = (HttpWebResponse)mappingResult.Request.EndGetResponse(result); + } + catch (WebException ex) + { + // Even if the request "failed" i want to continue on to read out the response from the router + response = ex.Response as HttpWebResponse; + if (response == null) + mappingResult.SavedMessage = new ErrorMessage((int)ex.Status, ex.Message); + } + if (response != null) + mappingResult.SavedMessage = DecodeMessageFromResponse(response.GetResponseStream(), response.ContentLength); + } + + finally + { + if (response != null) + response.Close(); + } + } + + private void EndDeletePortMapInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } + + private void EndGetAllMappingsInternal(IAsyncResult result) + { + EndMessageInternal(result); + + GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult; + GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage; + if (message != null) + { + Mapping mapping = new Mapping (message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration); + mapping.Description = message.PortMappingDescription; + mappingResult.Mappings.Add(mapping); + GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this); + + // It's ok to do this synchronously because we should already be on anther thread + // and this won't block the user. + byte[] body; + WebRequest request = next.Encode(out body); + if (body.Length > 0) + { + request.ContentLength = body.Length; + request.GetRequestStream().Write(body, 0, body.Length); + } + mappingResult.Request = request; + request.BeginGetResponse(EndGetAllMappingsInternal, mappingResult); + return; + } + + CompleteMessage(result); + } + + private void EndGetExternalIPInternal(IAsyncResult result) + { + EndMessageInternal(result); + CompleteMessage(result); + } + + private void EndGetSpecificMappingInternal(IAsyncResult result) + { + EndMessageInternal(result); + + GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult; + GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage; + if (message != null) { + Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration); + mapping.Description = mappingResult.SpecificMapping.Description; + mappingResult.Mappings.Add(mapping); + } + + CompleteMessage(result); + } + + internal void GetServicesList(NatDeviceCallback callback) + { + // Save the callback so i can use it again later when i've finished parsing the services available + this.callback = callback; + + // Create a HTTPWebRequest to download the list of services the device offers + byte[] body; + WebRequest request = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint).Encode(out body); + if (body.Length > 0) + NatUtility.Log("Error: Services Message contained a body"); + request.BeginGetResponse(this.ServicesReceived, request); + } + + private void ServicesReceived(IAsyncResult result) + { + HttpWebResponse response = null; + try + { + int abortCount = 0; + int bytesRead = 0; + byte[] buffer = new byte[10240]; + StringBuilder servicesXml = new StringBuilder(); + XmlDocument xmldoc = new XmlDocument(); + HttpWebRequest request = result.AsyncState as HttpWebRequest; + response = request.EndGetResponse(result) as HttpWebResponse; + Stream s = response.GetResponseStream(); + + if (response.StatusCode != HttpStatusCode.OK) { + NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode); + return; // FIXME: This the best thing to do?? + } + + while (true) + { + bytesRead = s.Read(buffer, 0, buffer.Length); + servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); + try + { + xmldoc.LoadXml(servicesXml.ToString()); + response.Close(); + break; + } + catch (XmlException) + { + // If we can't receive the entire XML within 500ms, then drop the connection + // Unfortunately not all routers supply a valid ContentLength (mine doesn't) + // so this hack is needed to keep testing our recieved data until it gets successfully + // parsed by the xmldoc. Without this, the code will never pick up my router. + if (abortCount++ > 50) + { + response.Close(); + return; + } + NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint); + System.Threading.Thread.Sleep(10); + } + } + + NatUtility.Log("{0}: Parsed services list", HostEndPoint); + XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable); + ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0"); + XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns); + + foreach (XmlNode node in nodes) + { + //Go through each service there + foreach (XmlNode service in node.ChildNodes) + { + //If the service is a WANIPConnection, then we have what we want + string type = service["serviceType"].InnerText; + NatUtility.Log("{0}: Found service: {1}", HostEndPoint, type); + StringComparison c = StringComparison.OrdinalIgnoreCase; + // TODO: Add support for version 2 of UPnP. + if (type.Equals("urn:schemas-upnp-org:service:WANPPPConnection:1", c) || + type.Equals("urn:schemas-upnp-org:service:WANIPConnection:1", c)) + { + this.controlUrl = service["controlURL"].InnerText; + NatUtility.Log("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl); + try + { + Uri u = new Uri(controlUrl); + if (u.IsAbsoluteUri) + { + EndPoint old = hostEndPoint; + this.hostEndPoint = new IPEndPoint(IPAddress.Parse(u.Host), u.Port); + NatUtility.Log("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint); + this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length); + NatUtility.Log("{0}: New control url: {1}", HostEndPoint, controlUrl); + } + } + catch + { + NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl); + } + NatUtility.Log("{0}: Handshake Complete", HostEndPoint); + this.callback(this); + return; + } + } + } + + //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding + //So we don't invoke the callback, so this device is never added to our lists + } + catch (WebException ex) + { + // Just drop the connection, FIXME: Should i retry? + NatUtility.Log("{0}: Device denied the connection attempt: {1}", HostEndPoint, ex); + } + finally + { + if (response != null) + response.Close(); + } + } + + /// + /// Overridden. + /// + /// + public override string ToString( ) + { + //GetExternalIP is blocking and can throw exceptions, can't use it here. + return String.Format( + "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}", + this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen); + } + } +} \ No newline at end of file -- cgit v1.2.3 From 151d88f20db2d6fbb8ea901d84d25d26ccbb136c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 12 Sep 2016 14:10:09 -0400 Subject: encapsulate path substitution --- MediaBrowser.Controller/Entities/BaseItem.cs | 5 +--- MediaBrowser.Controller/Library/ILibraryManager.cs | 2 ++ MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 16 +++++++++++-- .../Dto/DtoService.cs | 5 +--- .../Library/LibraryManager.cs | 10 ++++++++ MediaBrowser.ServerApplication/MainStartup.cs | 27 ++++++++++++++++++++-- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 7 +----- 7 files changed, 54 insertions(+), 18 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 55aaf04ff..2a49168ed 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2111,10 +2111,7 @@ namespace MediaBrowser.Controller.Entities { if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) { - foreach (var map in ConfigurationManager.Configuration.PathSubstitutions) - { - path = LibraryManager.SubstitutePath(path, map.From, map.To); - } + return LibraryManager.GetPathAfterNetworkSubstitution(path); } return path; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 04268bcea..d5c2fcd20 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -506,6 +506,8 @@ namespace MediaBrowser.Controller.Library /// QueryResult<BaseItem>. QueryResult QueryItems(InternalItemsQuery query); + string GetPathAfterNetworkSubstitution(string path); + /// /// Substitutes the path. /// diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index af03f325f..0ab41020e 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -343,7 +343,8 @@ namespace MediaBrowser.Dlna.Main if (_Publisher != null) { var devices = _Publisher.Devices.ToList(); - foreach (var device in devices) + + Parallel.ForEach(devices, device => { try { @@ -353,7 +354,18 @@ namespace MediaBrowser.Dlna.Main { _logger.ErrorException("Error sending bye bye", ex); } - } + }); + //foreach (var device in devices) + //{ + // try + // { + // _Publisher.RemoveDevice(device); + // } + // catch (Exception ex) + // { + // _logger.ErrorException("Error sending bye bye", ex); + // } + //} _Publisher.Dispose(); } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 9284f4fc7..3236c38d5 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1516,10 +1516,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) { - foreach (var map in _config.Configuration.PathSubstitutions) - { - path = _libraryManager.SubstitutePath(path, map.From, map.To); - } + path = _libraryManager.GetPathAfterNetworkSubstitution(path); } return path; diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 442e2ebe5..bd408c9d3 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2527,6 +2527,16 @@ namespace MediaBrowser.Server.Implementations.Library }).OrderBy(i => i.Path).ToList(); } + public string GetPathAfterNetworkSubstitution(string path) + { + foreach (var map in ConfigurationManager.Configuration.PathSubstitutions) + { + path = SubstitutePath(path, map.From, map.To); + } + + return path; + } + public string SubstitutePath(string path, string from, string to) { if (string.IsNullOrWhiteSpace(path)) diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index cdacdc81f..5d4fba32d 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Management; using System.Runtime.InteropServices; using System.ServiceProcess; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -37,9 +38,31 @@ namespace MediaBrowser.ServerApplication [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetDllDirectory(string lpPathName); + public static bool TryGetLocalFromUncDirectory(string local, out string unc) + { + if ((local == null) || (local == "")) + { + unc = ""; + throw new ArgumentNullException("local"); + } + + ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_share WHERE path ='" + local.Replace("\\", "\\\\") + "'"); + ManagementObjectCollection coll = searcher.Get(); + if (coll.Count == 1) + { + foreach (ManagementObject share in searcher.Get()) + { + unc = share["Name"] as String; + unc = "\\\\" + SystemInformation.ComputerName + "\\" + unc; + return true; + } + } + unc = ""; + return false; + } /// - /// Defines the entry point of the application. - /// + /// Defines the entry point of the application. + ///
public static void Main() { var options = new StartupOptions(); diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 290ea588e..dc208d495 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -1040,12 +1040,7 @@ namespace MediaBrowser.XbmcMetadata.Savers private static string GetPathToSave(string path, ILibraryManager libraryManager, IServerConfigurationManager config) { - foreach (var map in config.Configuration.PathSubstitutions) - { - path = libraryManager.SubstitutePath(path, map.From, map.To); - } - - return path; + return libraryManager.GetPathAfterNetworkSubstitution(path); } private static bool IsPersonType(PersonInfo person, string type) -- cgit v1.2.3 From 2d36b262fe47642f3a370e61f3c8f05cf644962b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 12 Sep 2016 14:49:34 -0400 Subject: update german ratings --- MediaBrowser.Server.Implementations/Localization/Ratings/de.txt | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt index 723807509..ad1f18619 100644 --- a/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt +++ b/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt @@ -1,5 +1,10 @@ DE-0,1 +FSK-0,1 DE-6,5 +FSK-6,5 DE-12,7 +FSK-12,7 DE-16,8 +FSK-16,8 DE-18,9 +FSK-18,9 \ No newline at end of file -- cgit v1.2.3 From 42d67db1b84341998e81521154d8255ae85818e6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 12 Sep 2016 17:24:05 -0400 Subject: removed dead code --- .../MediaBrowser.Controller.csproj | 1 - MediaBrowser.Controller/Power/IPowerManagement.cs | 13 --- .../LiveTv/EmbyTV/EmbyTV.cs | 5 +- .../LiveTv/EmbyTV/TimerManager.cs | 26 +----- MediaBrowser.Server.Mono/Native/BaseMonoApp.cs | 14 ---- .../ApplicationHost.cs | 2 - MediaBrowser.Server.Startup.Common/INativeApp.cs | 7 -- .../MediaBrowser.ServerApplication.csproj | 1 - .../Native/WindowsApp.cs | 6 -- .../Native/WindowsPowerManagement.cs | 94 ---------------------- 10 files changed, 3 insertions(+), 166 deletions(-) delete mode 100644 MediaBrowser.Controller/Power/IPowerManagement.cs delete mode 100644 MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 7cfd56c1e..8fae46906 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -266,7 +266,6 @@ - diff --git a/MediaBrowser.Controller/Power/IPowerManagement.cs b/MediaBrowser.Controller/Power/IPowerManagement.cs deleted file mode 100644 index faa289695..000000000 --- a/MediaBrowser.Controller/Power/IPowerManagement.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace MediaBrowser.Controller.Power -{ - public interface IPowerManagement - { - /// - /// Schedules the wake. - /// - /// The UTC time. - void ScheduleWake(DateTime utcTime); - } -} diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 8fa34109d..96e1e8569 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -27,7 +27,6 @@ using System.Threading.Tasks; using CommonIO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Power; using MediaBrowser.Model.Configuration; using Microsoft.Win32; @@ -61,7 +60,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private readonly ConcurrentDictionary _activeRecordings = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement) + public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder) { Current = this; @@ -79,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _jsonSerializer = jsonSerializer; _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); - _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger); + _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger); _timerProvider.TimerFired += _timerProvider_TimerFired; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 423358906..a7e34a373 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -9,7 +9,6 @@ using System.Globalization; using System.Linq; using System.Threading; using CommonIO; -using MediaBrowser.Controller.Power; using MediaBrowser.Model.LiveTv; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV @@ -17,15 +16,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public class TimerManager : ItemDataProvider { private readonly ConcurrentDictionary _timers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private readonly IPowerManagement _powerManagement; private readonly ILogger _logger; public event EventHandler> TimerFired; - public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, IPowerManagement powerManagement, ILogger logger1) + public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1) : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) { - _powerManagement = powerManagement; _logger = logger1; } @@ -64,7 +61,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow; timer.Change(timespan, TimeSpan.Zero); - ScheduleWake(item); } else { @@ -101,7 +97,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV base.Add(item); AddTimer(item); - ScheduleWake(item); } private void AddTimer(TimerInfo item) @@ -124,25 +119,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV StartTimer(item, timerLength); } - private void ScheduleWake(TimerInfo info) - { - var startDate = RecordingHelper.GetStartTime(info).AddMinutes(-5); - - try - { - _powerManagement.ScheduleWake(startDate); - _logger.Info("Scheduled system wake timer at {0} (UTC)", startDate); - } - catch (NotImplementedException) - { - - } - catch (Exception ex) - { - _logger.ErrorException("Error scheduling wake timer", ex); - } - } - public void StartTimer(TimerInfo item, TimeSpan dueTime) { StopTimer(item); diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs index 48f6a2a48..d2a544477 100644 --- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs +++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; -using MediaBrowser.Controller.Power; using MediaBrowser.Model.System; using MediaBrowser.Server.Implementations.Persistence; using MediaBrowser.Server.Startup.Common.FFMpeg; @@ -232,11 +231,6 @@ namespace MediaBrowser.Server.Mono.Native public string machine = string.Empty; } - public IPowerManagement GetPowerManagement() - { - return new NullPowerManagement(); - } - public FFMpegInstallInfo GetFfmpegInstallInfo() { return GetInfo(Environment); @@ -289,12 +283,4 @@ namespace MediaBrowser.Server.Mono.Native return false; } } - - public class NullPowerManagement : IPowerManagement - { - public void ScheduleWake(DateTime utcTime) - { - throw new NotImplementedException(); - } - } } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 3de2f5ddf..f5419e5cf 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -561,8 +561,6 @@ namespace MediaBrowser.Server.Startup.Common EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager, LibraryManager); RegisterSingleInstance(EncodingManager); - RegisterSingleInstance(NativeApp.GetPowerManagement()); - var sharingRepo = new SharingRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector()); await sharingRepo.Initialize().ConfigureAwait(false); RegisterSingleInstance(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this)); diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs index 9297a6d37..bf8314d13 100644 --- a/MediaBrowser.Server.Startup.Common/INativeApp.cs +++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs @@ -2,7 +2,6 @@ using MediaBrowser.Model.Logging; using System.Collections.Generic; using System.Reflection; -using MediaBrowser.Controller.Power; using MediaBrowser.Server.Implementations.Persistence; using MediaBrowser.Server.Startup.Common.FFMpeg; @@ -98,12 +97,6 @@ namespace MediaBrowser.Server.Startup.Common void AllowSystemStandby(); - /// - /// Gets the power management. - /// - /// IPowerManagement. - IPowerManagement GetPowerManagement(); - FFMpegInstallInfo GetFfmpegInstallInfo(); void LaunchUrl(string url); diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index a32312493..65b91e6f7 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -123,7 +123,6 @@ - diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs index b08b82de5..1e50ac85e 100644 --- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs +++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs @@ -9,7 +9,6 @@ using System.IO; using System.Reflection; using System.Windows.Forms; using CommonIO; -using MediaBrowser.Controller.Power; using MediaBrowser.Model.System; using MediaBrowser.Server.Implementations.Persistence; using MediaBrowser.Server.Startup.Common.FFMpeg; @@ -148,11 +147,6 @@ namespace MediaBrowser.ServerApplication.Native MainStartup.Invoke(Standby.AllowSleep); } - public IPowerManagement GetPowerManagement() - { - return new WindowsPowerManagement(_logger); - } - public FFMpegInstallInfo GetFfmpegInstallInfo() { var info = new FFMpegInstallInfo(); diff --git a/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs b/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs deleted file mode 100644 index 866272639..000000000 --- a/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.ComponentModel; -using System.Runtime.InteropServices; -using System.Threading; -using MediaBrowser.Controller.Power; -using MediaBrowser.Model.Logging; -using Microsoft.Win32.SafeHandles; - -namespace MediaBrowser.ServerApplication.Native -{ - public class WindowsPowerManagement : IPowerManagement - { - [DllImport("kernel32.dll")] - public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, - bool bManualReset, - string lpTimerName); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetWaitableTimer(SafeWaitHandle hTimer, - [In] ref long pDueTime, - int lPeriod, - IntPtr pfnCompletionRoutine, - IntPtr lpArgToCompletionRoutine, - bool fResume); - - private BackgroundWorker _bgWorker; - private readonly ILogger _logger; - private readonly object _initLock = new object(); - - public WindowsPowerManagement(ILogger logger) - { - _logger = logger; - } - - public void ScheduleWake(DateTime utcTime) - { - //Initialize(); - //_bgWorker.RunWorkerAsync(utcTime.ToFileTime()); - throw new NotImplementedException(); - } - - private void Initialize() - { - lock (_initLock) - { - if (_bgWorker == null) - { - _bgWorker = new BackgroundWorker(); - - _bgWorker.DoWork += bgWorker_DoWork; - _bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted; - } - } - } - - void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - //if (Woken != null) - //{ - // Woken(this, new EventArgs()); - //} - } - - private void bgWorker_DoWork(object sender, DoWorkEventArgs e) - { - try - { - long waketime = (long)e.Argument; - - using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, GetType().Assembly.GetName().Name + "Timer")) - { - if (SetWaitableTimer(handle, ref waketime, 0, IntPtr.Zero, IntPtr.Zero, true)) - { - using (EventWaitHandle wh = new EventWaitHandle(false, - EventResetMode.AutoReset)) - { - wh.SafeWaitHandle = handle; - wh.WaitOne(); - } - } - else - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - } - } - catch (Exception ex) - { - _logger.ErrorException("Error scheduling wake timer", ex); - } - } - } -} -- cgit v1.2.3 From 020ceb97d2335f57ad4d0c796e2d6bf800907337 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 13 Sep 2016 13:49:13 -0400 Subject: update device discovery --- MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs | 26 ++++++++++------------ .../Connect/ConnectManager.cs | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs index 91dbeb96e..c9bba526a 100644 --- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs +++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs @@ -50,8 +50,6 @@ namespace MediaBrowser.Dlna.Ssdp // Connect our event handler so we process devices as they are found _DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable; _DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable; - // Enable listening for notifications (optional) - _DeviceLocator.StartListeningForNotifications(); // Perform a search so we don't have to wait for devices to broadcast notifications // again to get any results right away (notifications are broadcast periodically). @@ -62,23 +60,23 @@ namespace MediaBrowser.Dlna.Ssdp { Task.Factory.StartNew(async (o) => { - try + while (!_tokenSource.IsCancellationRequested) { - while (true) + try { + // Enable listening for notifications (optional) + _DeviceLocator.StartListeningForNotifications(); + await _DeviceLocator.SearchAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error searching for devices", ex); + } - var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000; + var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000; - await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false); - } - } - catch (OperationCanceledException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error searching for devices", ex); + await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false); } }, CancellationToken.None, TaskCreationOptions.LongRunning); diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs index 45cb2f57d..e7e52a887 100644 --- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs +++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs @@ -450,7 +450,7 @@ namespace MediaBrowser.Server.Implementations.Connect if (!string.IsNullOrWhiteSpace(user.ConnectUserId)) { - await RemoveConnect(user, connectUser.Id).ConfigureAwait(false); + await RemoveConnect(user, user.ConnectUserId).ConfigureAwait(false); } var url = GetConnectUrl("ServerAuthorizations"); -- cgit v1.2.3 From 0c952972696e7e9aa74bfd646469521b42722398 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 14 Sep 2016 12:21:33 -0400 Subject: improve resiliency of recording process --- .../UserLibrary/BaseItemsByNameService.cs | 1 + MediaBrowser.Model/Dto/BaseItemDto.cs | 1 + MediaBrowser.Model/Dto/ItemCounts.cs | 1 + .../Movies/FanartMovieImageProvider.cs | 30 +++--------- .../Dto/DtoService.cs | 1 + .../LiveTv/EmbyTV/DirectRecorder.cs | 35 +++++++++++++- .../LiveTv/EmbyTV/EmbyTV.cs | 10 +++- .../LiveTv/EmbyTV/EncodedRecorder.cs | 53 ++++++++++++++++------ .../LiveTv/TunerHosts/BaseTunerHost.cs | 10 +++- 9 files changed, 99 insertions(+), 43 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 5381f9004..182a92fc8 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -205,6 +205,7 @@ namespace MediaBrowser.Api.UserLibrary private void SetItemCounts(BaseItemDto dto, ItemCounts counts) { dto.ChildCount = counts.ItemCount; + dto.ProgramCount = counts.ProgramCount; dto.SeriesCount = counts.SeriesCount; dto.EpisodeCount = counts.EpisodeCount; dto.MovieCount = counts.MovieCount; diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 348a781ae..9434ab03a 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -826,6 +826,7 @@ namespace MediaBrowser.Model.Dto /// /// The series count. public int? SeriesCount { get; set; } + public int? ProgramCount { get; set; } /// /// Gets or sets the episode count. /// diff --git a/MediaBrowser.Model/Dto/ItemCounts.cs b/MediaBrowser.Model/Dto/ItemCounts.cs index 66c3dfebc..8ceb3a86b 100644 --- a/MediaBrowser.Model/Dto/ItemCounts.cs +++ b/MediaBrowser.Model/Dto/ItemCounts.cs @@ -26,6 +26,7 @@ /// The game count. public int GameCount { get; set; } public int ArtistCount { get; set; } + public int ProgramCount { get; set; } /// /// Gets or sets the game system count. /// diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index 18f177932..2a40e4d85 100644 --- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -20,6 +20,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Providers.TV; namespace MediaBrowser.Providers.Movies @@ -59,30 +60,13 @@ namespace MediaBrowser.Providers.Movies public bool Supports(IHasImages item) { - //var channelItem = item as IChannelMediaItem; - - //if (channelItem != null) - //{ - // if (channelItem.ContentType == ChannelMediaContentType.Movie) - // { - // return true; - // } - // if (channelItem.ContentType == ChannelMediaContentType.MovieExtra) - // { - // if (channelItem.ExtraType == ExtraType.Trailer) - // { - // return true; - // } - // } - //} - // Supports images for tv movies - //var tvProgram = item as LiveTvProgram; - //if (tvProgram != null && tvProgram.IsMovie) - //{ - // return true; - //} - + var tvProgram = item as LiveTvProgram; + if (tvProgram != null && tvProgram.IsMovie) + { + return true; + } + return item is Movie || item is BoxSet || item is MusicVideo; } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 3236c38d5..ae676626d 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -437,6 +437,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.TrailerCount = taggedItems.Count(i => i is Trailer); dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); dto.SeriesCount = taggedItems.Count(i => i is Series); + dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram); dto.SongCount = taggedItems.Count(i => i is Audio); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index b21aa904b..2e3edf3e9 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -61,11 +61,44 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; } - await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); + await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false); } } _logger.Info("Recording completed to file {0}", targetFile); } + + private const int BufferSize = 81920; + public static async Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, cancellationToken).ConfigureAwait(false); + + //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 static async Task CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, 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); + + totalBytesRead += bytesRead; + } + + return totalBytesRead; + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 96e1e8569..46351ce95 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -580,7 +580,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV PrePaddingSeconds = Math.Max(config.PrePaddingSeconds, 0), RecordAnyChannel = true, RecordAnyTime = true, - RecordNewOnly = false, + RecordNewOnly = true, Days = new List { @@ -730,6 +730,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return result.Item1; } + catch (FileNotFoundException) + { + } catch (Exception e) { _logger.ErrorException("Error getting channel stream", e); @@ -751,6 +754,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return new Tuple(result.Item1, hostInstance, result.Item2); } + catch (FileNotFoundException) + { + } catch (Exception e) { _logger.ErrorException("Error getting channel stream", e); @@ -1213,7 +1219,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } // Exclude programs that have already ended - allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow); + allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow); allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 75ad3de59..560b0d5b4 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -78,21 +78,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts"); - try - { - await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) - .ConfigureAwait(false); - } - finally + await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) + .ConfigureAwait(false); + } + + private async void DeleteTempFile(string path) + { + for (var i = 0; i < 10; i++) { try { - File.Delete(tempfile); + File.Delete(path); + return; + } + catch (FileNotFoundException) + { + return; + } + catch (DirectoryNotFoundException) + { + return; } catch (Exception ex) { _logger.ErrorException("Error deleting recording temp file", ex); } + + await Task.Delay(1000).ConfigureAwait(false); } } @@ -101,7 +113,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var durationToken = new CancellationTokenSource(duration); cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false); + await RecordFromFile(mediaSource, mediaSource.Path, targetFile, false, duration, onStarted, cancellationToken).ConfigureAwait(false); _logger.Info("Recording completed to file {0}", targetFile); } @@ -143,14 +155,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; } - var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken); + var tempFileTask = DirectRecorder.CopyUntilCancelled(response.Content, output, cancellationToken); // Give the temp file a little time to build up await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false); - var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, duration, onStarted, cancellationToken), cancellationToken); + var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, true, duration, onStarted, cancellationToken), CancellationToken.None); - await tempFileTask.ConfigureAwait(false); + try + { + await tempFileTask.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + + } await recordTask.ConfigureAwait(false); } @@ -159,7 +178,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Info("Recording completed to file {0}", targetFile); } - private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, bool deleteInputFileAfterCompletion, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { _targetPath = targetFile; _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile)); @@ -200,7 +219,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length); - process.Exited += (sender, args) => OnFfMpegProcessExited(process); + process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile, deleteInputFileAfterCompletion); process.Start(); @@ -309,8 +328,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV /// /// Processes the exited. /// - /// The process. - private void OnFfMpegProcessExited(Process process) + private void OnFfMpegProcessExited(Process process, string inputFile, bool deleteInputFileAfterCompletion) { _hasExited = true; @@ -336,6 +354,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Error("FFMpeg recording exited with an error for {0}.", _targetPath); _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed", _targetPath))); } + + if (deleteInputFileAfterCompletion) + { + DeleteTempFile(inputFile); + } } private void DisposeLogStream() diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 9bb5b4fd7..7aa9eb1cf 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -195,7 +196,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase)) { - hostsWithChannel = new List { host }; + hostsWithChannel = new List {host}; streamId = streamId.Substring(host.Id.Length); break; } @@ -222,7 +223,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } } - var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); + var stream = + await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); if (EnableMediaProbing) { @@ -239,6 +241,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } } } + else + { + throw new FileNotFoundException(); + } throw new LiveTvConflictException(); } -- cgit v1.2.3 From d95c0e8324284b7391fb1d6a275c8af18508141b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 14 Sep 2016 17:34:19 -0400 Subject: fix web socket session creation --- MediaBrowser.Api/ConnectService.cs | 34 ++++++++++++++++++---- .../Session/AuthenticationRequest.cs | 1 + MediaBrowser.Providers/Manager/MetadataService.cs | 2 -- .../Library/LibraryManager.cs | 12 ++++---- .../Session/SessionManager.cs | 15 ++++++++-- 5 files changed, 49 insertions(+), 15 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/ConnectService.cs b/MediaBrowser.Api/ConnectService.cs index 4bcd33d9e..494a6e756 100644 --- a/MediaBrowser.Api/ConnectService.cs +++ b/MediaBrowser.Api/ConnectService.cs @@ -7,6 +7,7 @@ using ServiceStack; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Controller.Session; namespace MediaBrowser.Api { @@ -76,12 +77,12 @@ namespace MediaBrowser.Api public class ConnectService : BaseApiService { private readonly IConnectManager _connectManager; - private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; - public ConnectService(IConnectManager connectManager, IUserManager userManager) + public ConnectService(IConnectManager connectManager, ISessionManager sessionManager) { _connectManager = connectManager; - _userManager = userManager; + _sessionManager = sessionManager; } public object Post(CreateConnectLink request) @@ -141,10 +142,33 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException(); } + var auth = AuthorizationContext.GetAuthorizationInfo(Request); + + if (string.IsNullOrWhiteSpace(auth.Client)) + { + return ToOptimizedResult(new ConnectAuthenticationExchangeResult + { + AccessToken = user.ConnectAccessKey, + LocalUserId = user.Id.ToString("N") + }); + } + + var session = await _sessionManager.CreateNewSession(new AuthenticationRequest + { + App = auth.Client, + AppVersion = auth.Version, + DeviceId = auth.DeviceId, + DeviceName = auth.Device, + RemoteEndPoint = Request.RemoteIp, + Username = user.Name, + UserId = user.Id.ToString("N") + + }).ConfigureAwait(false); + return ToOptimizedResult(new ConnectAuthenticationExchangeResult { - AccessToken = user.ConnectAccessKey, - LocalUserId = user.Id.ToString("N") + AccessToken = session.AccessToken, + LocalUserId = session.User.Id }); } } diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs index bfd7f928b..362f5b2b9 100644 --- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -4,6 +4,7 @@ namespace MediaBrowser.Controller.Session public class AuthenticationRequest { public string Username { get; set; } + public string UserId { get; set; } public string PasswordSha1 { get; set; } public string PasswordMd5 { get; set; } public string App { get; set; } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 85e25d2b6..e16e76bfc 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -547,8 +547,6 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - refreshResult.Failures++; - Logger.ErrorException("Error in {0}", ex, provider.Name); // If a local provider fails, consider that a failure diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index bd408c9d3..1f8c77953 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -695,7 +695,7 @@ namespace MediaBrowser.Server.Implementations.Library public IEnumerable ResolvePaths(IEnumerable files, IDirectoryService directoryService, - Folder parent, + Folder parent, LibraryOptions libraryOptions, string collectionType, IItemResolver[] resolvers) @@ -1490,10 +1490,10 @@ namespace MediaBrowser.Server.Implementations.Library private void AddUserToQuery(InternalItemsQuery query, User user) { - if (query.AncestorIds.Length == 0 && - !query.ParentId.HasValue && - query.ChannelIds.Length == 0 && - query.TopParentIds.Length == 0 && + if (query.AncestorIds.Length == 0 && + !query.ParentId.HasValue && + query.ChannelIds.Length == 0 && + query.TopParentIds.Length == 0 && string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey) && query.ItemIds.Length == 0) { @@ -2552,7 +2552,7 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("to"); } - var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase); + var newPath = path.Replace(from.Trim(), to.Trim(), StringComparison.OrdinalIgnoreCase); if (!string.Equals(newPath, path)) { diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index b21fcddd4..afcdf9d90 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1341,8 +1341,19 @@ namespace MediaBrowser.Server.Implementations.Session private async Task AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) { - var user = _userManager.Users - .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); + User user = null; + if (!string.IsNullOrWhiteSpace(request.UserId)) + { + var idGuid = new Guid(request.UserId); + user = _userManager.Users + .FirstOrDefault(i => i.Id == idGuid); + } + + if (user == null) + { + user = _userManager.Users + .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); + } if (user != null && !string.IsNullOrWhiteSpace(request.DeviceId)) { -- cgit v1.2.3 From 38fec0a74a8b46e5dde32232c91d92fc7911dfb5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 15 Sep 2016 02:23:39 -0400 Subject: reduce recording failures --- MediaBrowser.Controller/LiveTv/TimerInfo.cs | 16 ++++ MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 2 + .../LiveTv/EmbyTV/EmbyTV.cs | 89 +++++++++++++--------- .../LiveTv/EmbyTV/RecordingHelper.cs | 22 ++++-- 4 files changed, 85 insertions(+), 44 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 5d92a212f..94bc24b9e 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -82,5 +82,21 @@ namespace MediaBrowser.Controller.LiveTv /// /// The priority. public int Priority { get; set; } + + + // Program properties + public int? SeasonNumber { get; set; } + /// + /// Gets or sets the episode number. + /// + /// The episode number. + public int? EpisodeNumber { get; set; } + public bool IsMovie { get; set; } + public bool IsKids { get; set; } + public bool IsSports { get; set; } + public int? ProductionYear { get; set; } + public string EpisodeTitle { get; set; } + public DateTime? OriginalAirDate { get; set; } + public bool IsProgramSeries { get; set; } } } diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index 0ab41020e..faf293f6b 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -256,6 +256,8 @@ namespace MediaBrowser.Dlna.Main foreach (var fullService in services) { + _logger.Info("Registering publisher for {0} on {1}", fullService, addressString); + var descriptorURI = "/dlna/" + udn + "/description.xml"; var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 46351ce95..3dc0eedd6 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -461,11 +461,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return CreateSeriesTimer(info, cancellationToken); } - public Task CreateTimer(TimerInfo info, CancellationToken cancellationToken) + public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken) { - info.Id = Guid.NewGuid().ToString("N"); - _timerProvider.Add(info); - return Task.FromResult(info.Id); + timer.Id = Guid.NewGuid().ToString("N"); + + ProgramInfo programInfo = null; + + if (!string.IsNullOrWhiteSpace(timer.ProgramId)) + { + programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); + } + if (programInfo == null) + { + _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); + programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); + } + + if (programInfo != null) + { + RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); + } + + _timerProvider.Add(timer); + return Task.FromResult(timer.Id); } public async Task CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken) @@ -849,12 +867,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private string GetRecordingPath(TimerInfo timer, ProgramInfo info) + private string GetRecordingPath(TimerInfo timer) { var recordPath = RecordingPath; var config = GetConfiguration(); - if (info.IsSeries) + if (timer.IsProgramSeries) { var customRecordingPath = config.SeriesRecordingPath; var allowSubfolder = true; @@ -869,11 +887,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = Path.Combine(recordPath, "Series"); } - var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); + var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); var folderNameWithYear = folderName; - if (info.ProductionYear.HasValue) + if (timer.ProductionYear.HasValue) { - folderNameWithYear += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; + folderNameWithYear += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; } if (Directory.Exists(Path.Combine(recordPath, folderName))) @@ -885,13 +903,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = Path.Combine(recordPath, folderNameWithYear); } - if (info.SeasonNumber.HasValue) + if (timer.SeasonNumber.HasValue) { - folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); + folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); recordPath = Path.Combine(recordPath, folderName); } } - else if (info.IsMovie) + else if (timer.IsMovie) { var customRecordingPath = config.MovieRecordingPath; var allowSubfolder = true; @@ -906,34 +924,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = Path.Combine(recordPath, "Movies"); } - var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); - if (info.ProductionYear.HasValue) + var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); + if (timer.ProductionYear.HasValue) { - folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; + folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; } recordPath = Path.Combine(recordPath, folderName); } - else if (info.IsKids) + else if (timer.IsKids) { if (config.EnableRecordingSubfolders) { recordPath = Path.Combine(recordPath, "Kids"); } - var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); - if (info.ProductionYear.HasValue) + var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); + if (timer.ProductionYear.HasValue) { - folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; + folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; } recordPath = Path.Combine(recordPath, folderName); } - else if (info.IsSports) + else if (timer.IsSports) { if (config.EnableRecordingSubfolders) { recordPath = Path.Combine(recordPath, "Sports"); } - recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim()); + recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim()); } else { @@ -941,10 +959,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { recordPath = Path.Combine(recordPath, "Other"); } - recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim()); + recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim()); } - var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts"; + var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer)).Trim() + ".ts"; return Path.Combine(recordPath, recordingFileName); } @@ -956,29 +974,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException("timer"); } - ProgramInfo info = null; + ProgramInfo programInfo = null; - if (string.IsNullOrWhiteSpace(timer.ProgramId)) - { - _logger.Info("Timer {0} has null programId", timer.Id); - } - else + if (!string.IsNullOrWhiteSpace(timer.ProgramId)) { - info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); + programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); } - - if (info == null) + if (programInfo == null) { _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); - info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); + programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); } - if (info == null) + if (programInfo != null) { - throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId)); + RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); } - var recordPath = GetRecordingPath(timer, info); + var recordPath = GetRecordingPath(timer); var recordingStatus = RecordingStatus.New; var isResourceOpen = false; SemaphoreSlim semaphore = null; @@ -1062,7 +1075,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Status = RecordingStatus.Completed; _timerProvider.Delete(timer); - OnSuccessfulRecording(info.IsSeries, recordPath); + OnSuccessfulRecording(timer.IsProgramSeries, recordPath); } else if (DateTime.UtcNow < timer.EndDate) { @@ -1176,7 +1189,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private async Task UpdateTimersForSeriesTimer(List epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers) { var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList(); - + var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); if (registration.IsValid) diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 9485e0325..236439bc5 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -30,19 +30,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Overview = parent.Overview; timer.SeriesTimerId = series.Id; + CopyProgramInfoToTimerInfo(parent, timer); + return timer; } - public static string GetRecordingName(TimerInfo timer, ProgramInfo info) + public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo) { - if (info == null) - { - return timer.ProgramId; - } + timerInfo.SeasonNumber = programInfo.SeasonNumber; + timerInfo.EpisodeNumber = programInfo.EpisodeNumber; + timerInfo.IsMovie = programInfo.IsMovie; + timerInfo.IsKids = programInfo.IsKids; + timerInfo.IsSports = programInfo.IsSports; + timerInfo.ProductionYear = programInfo.ProductionYear; + timerInfo.EpisodeTitle = programInfo.EpisodeTitle; + timerInfo.OriginalAirDate = programInfo.OriginalAirDate; + timerInfo.IsProgramSeries = programInfo.IsSeries; + } + public static string GetRecordingName(TimerInfo info) + { var name = info.Name; - if (info.IsSeries) + if (info.IsProgramSeries) { var addHyphen = true; -- cgit v1.2.3 From c3355218594576457b3094dccea8e68896ef9cee Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 15 Sep 2016 02:38:09 -0400 Subject: don't use year in series folder name --- .../LiveTv/EmbyTV/EmbyTV.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3dc0eedd6..3eb032ba8 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -888,20 +888,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); - var folderNameWithYear = folderName; - if (timer.ProductionYear.HasValue) - { - folderNameWithYear += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; - } - if (Directory.Exists(Path.Combine(recordPath, folderName))) - { - recordPath = Path.Combine(recordPath, folderName); - } - else - { - recordPath = Path.Combine(recordPath, folderNameWithYear); - } + // Can't use the year here in the folder name because it is the year of the episode, not the series. + recordPath = Path.Combine(recordPath, folderName); if (timer.SeasonNumber.HasValue) { -- cgit v1.2.3 From ab79129d0c9ea8026d95c8421b8e27f8661cc4c1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 15 Sep 2016 16:30:46 -0400 Subject: add library monitor error handling --- MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 80364bb55..8bb40a00e 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -106,7 +106,14 @@ namespace MediaBrowser.Server.Implementations.IO if (refreshPath) { - ReportFileSystemChanged(path); + try + { + ReportFileSystemChanged(path); + } + catch (Exception ex) + { + Logger.ErrorException("Error in ReportFileSystemChanged for {0}", ex, path); + } } } -- cgit v1.2.3 From 3ad2703c35ef6872eee169d5892bf80b9573a484 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 15 Sep 2016 16:31:08 -0400 Subject: add guide data error handling --- .../LiveTv/Listings/SchedulesDirect.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 6d2f79fa0..d863c4587 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -349,9 +349,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings channelNumber = channelNumber.TrimStart('0'); _logger.Debug("Found channel: " + channelNumber + " in Schedules Direct"); - var schChannel = root.stations.FirstOrDefault(item => item.stationID == map.stationID); - - AddToChannelPairCache(listingsId, channelNumber, schChannel); + if (root.stations != null) + { + var schChannel = root.stations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); + if (schChannel != null) + { + AddToChannelPairCache(listingsId, channelNumber, schChannel); + } + } } _logger.Info("Added " + GetChannelPairCacheCount(listingsId) + " channels to the dictionary"); -- cgit v1.2.3 From cc6680f48f76278fecc0efc6b6732effe6fe1e2a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 15 Sep 2016 19:19:27 -0400 Subject: save nfo during recording process --- .../Library/Resolvers/TV/SeriesResolver.cs | 5 +- .../LiveTv/EmbyTV/EmbyTV.cs | 83 ++++++++++++++++++---- .../LiveTv/Listings/SchedulesDirect.cs | 49 ++++++++----- 3 files changed, 102 insertions(+), 35 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 4c6254330..207486f26 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { if (args.IsDirectory) { - if (args.HasParent()) + if (args.HasParent() || args.HasParent()) { return null; } @@ -80,7 +80,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { return null; } - if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false)) + if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false) || + args.ContainsFileSystemEntryByName("tvshow.nfo")) { return new Series { diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3eb032ba8..b508110cf 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -22,12 +22,15 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Xml; using CommonIO; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.FileOrganization; using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV @@ -867,10 +870,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private string GetRecordingPath(TimerInfo timer) + private string GetRecordingPath(TimerInfo timer, out string seriesPath) { var recordPath = RecordingPath; var config = GetConfiguration(); + seriesPath = null; if (timer.IsProgramSeries) { @@ -892,6 +896,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV // Can't use the year here in the folder name because it is the year of the episode, not the series. recordPath = Path.Combine(recordPath, folderName); + seriesPath = recordPath; + if (timer.SeasonNumber.HasValue) { folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); @@ -980,7 +986,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); } - var recordPath = GetRecordingPath(timer); + string seriesPath = null; + var recordPath = GetRecordingPath(timer, out seriesPath); var recordingStatus = RecordingStatus.New; var isResourceOpen = false; SemaphoreSlim semaphore = null; @@ -1064,7 +1071,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Status = RecordingStatus.Completed; _timerProvider.Delete(timer); - OnSuccessfulRecording(timer.IsProgramSeries, recordPath); + OnSuccessfulRecording(timer, recordPath, seriesPath); } else if (DateTime.UtcNow < timer.EndDate) { @@ -1132,26 +1139,72 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return new DirectRecorder(_logger, _httpClient, _fileSystem); } - private async void OnSuccessfulRecording(bool isSeries, string path) + private async void OnSuccessfulRecording(TimerInfo timer, string path, string seriesPath) { - if (GetConfiguration().EnableAutoOrganize) + if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize) { - if (isSeries) + try { - try - { - // this is to account for the library monitor holding a lock for additional time after the change is complete. - // ideally this shouldn't be hard-coded - await Task.Delay(30000).ConfigureAwait(false); + // this is to account for the library monitor holding a lock for additional time after the change is complete. + // ideally this shouldn't be hard-coded + await Task.Delay(30000).ConfigureAwait(false); + + var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); + var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false); - var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false); + if (result.Status == FileSortingStatus.Success) + { + return; } - catch (Exception ex) + } + catch (Exception ex) + { + _logger.ErrorException("Error processing new recording", ex); + } + } + + SaveNfo(timer, path, seriesPath); + } + + private void SaveNfo(TimerInfo timer, string recordingPath, string seriesPath) + { + if (timer.IsProgramSeries) + { + SaveSeriesNfo(timer, recordingPath, seriesPath); + } + } + + private void SaveSeriesNfo(TimerInfo timer, string recordingPath, string seriesPath) + { + var nfoPath = Path.Combine(seriesPath, "tvshow.nfo"); + + if (File.Exists(nfoPath)) + { + return; + } + + using (var stream = _fileSystem.GetFileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + var settings = new XmlWriterSettings + { + Indent = true, + Encoding = Encoding.UTF8, + CloseOutput = false + }; + + using (XmlWriter writer = XmlWriter.Create(stream, settings)) + { + writer.WriteStartDocument(true); + writer.WriteStartElement("tvshow"); + + if (!string.IsNullOrWhiteSpace(timer.Name)) { - _logger.ErrorException("Error processing new recording", ex); + writer.WriteElementString("title", timer.Name); } + + writer.WriteEndElement(); + writer.WriteEndDocument(); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index d863c4587..c30263388 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -171,22 +171,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var data = images[imageIndex].data ?? new List(); data = data.OrderByDescending(GetSizeOrder).ToList(); - programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true); + programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 1280); //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false); //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? // GetProgramImage(ApiUrl, data, "Banner-LOT", false); - - if (!string.IsNullOrWhiteSpace(programEntry.thumbImage)) - { - var b = true; - } - - if (!string.IsNullOrWhiteSpace(programEntry.bannerImage)) - { - var b = true; - } } } @@ -201,10 +191,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings private int GetSizeOrder(ScheduleDirect.ImageData image) { - if (!string.IsNullOrWhiteSpace(image.size)) + if (!string.IsNullOrWhiteSpace(image.height)) { int value; - if (int.TryParse(image.size, out value)) + if (int.TryParse(image.height, out value)) { return value; } @@ -517,20 +507,43 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return date; } - private string GetProgramImage(string apiUrl, List images, string category, bool returnDefaultImage) + private string GetProgramImage(string apiUrl, List images, string category, bool returnDefaultImage, int desiredWidth) { string url = null; - var logoIndex = images.FindIndex(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase)); - if (logoIndex == -1) + var matches = images + .Where(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (matches.Count == 0) { if (!returnDefaultImage) { return null; } - logoIndex = 0; + matches = images; } - var uri = images[logoIndex].uri; + + var match = matches.FirstOrDefault(i => + { + if (!string.IsNullOrWhiteSpace(i.width)) + { + int value; + if (int.TryParse(i.width, out value)) + { + return value <= desiredWidth; + } + } + + return false; + }) ?? matches.FirstOrDefault(); + + if (match == null) + { + return null; + } + + var uri = match.uri; if (!string.IsNullOrWhiteSpace(uri)) { -- cgit v1.2.3 From 7a67dba8efbd2d586b0576f18f128f76ff92efab Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 17 Sep 2016 02:08:38 -0400 Subject: add disposed check to FileRefresher --- MediaBrowser.Server.Implementations/IO/FileRefresher.cs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 2f4605c5c..8e28aa2a0 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -68,6 +68,11 @@ namespace MediaBrowser.Server.Implementations.IO lock (_timerLock) { + if (_disposed) + { + return; + } + if (_timer == null) { _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); @@ -287,6 +292,7 @@ namespace MediaBrowser.Server.Implementations.IO if (_timer != null) { _timer.Dispose(); + _timer = null; } } } -- cgit v1.2.3 From 512740cfb24ab4c79bd20f32b75f778f38885b6e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 17 Sep 2016 02:09:02 -0400 Subject: exclude unrated from intros --- MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs index 4a43befed..9b23d5be4 100644 --- a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs +++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs @@ -99,8 +99,9 @@ namespace MediaBrowser.Server.Implementations.Intros IncludeItemTypes = new[] { typeof(Trailer).Name }, TrailerTypes = trailerTypes.ToArray(), SimilarTo = item, - IsPlayed = config.EnableIntrosForWatchedContent ? (bool?) null : false, + IsPlayed = config.EnableIntrosForWatchedContent ? (bool?)null : false, MaxParentalRating = config.EnableIntrosParentalControl ? ratingLevel : null, + BlockUnratedItems = config.EnableIntrosParentalControl ? new[] { UnratedItem.Trailer } : new UnratedItem[] { }, Limit = config.TrailerLimit }); @@ -110,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.Intros Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer, LibraryManager = _libraryManager })); - } + } return GetResult(item, candidates, config); } @@ -197,7 +198,7 @@ namespace MediaBrowser.Server.Implementations.Intros } returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1)); - + return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase); } catch (IOException) -- cgit v1.2.3 From 5cfae1ada1114cb03561565c0e8a9d354e89562e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 17 Sep 2016 02:09:29 -0400 Subject: update startup tasks --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 4 ++-- MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs | 2 +- MediaBrowser.Server.Startup.Common/ApplicationHost.cs | 10 +++++----- MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs | 2 +- .../Migrations/IVersionMigration.cs | 5 +++-- .../Migrations/MovieDbEpisodeProviderMigration.cs | 3 ++- 6 files changed, 14 insertions(+), 12 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 13d559773..98bef334b 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -602,7 +602,7 @@ namespace MediaBrowser.Model.Dlna private int GetAudioBitrate(string subProtocol, int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream) { - var defaultBitrate = audioStream == null ? 192000 : audioStream.BitRate ?? 192000; + int defaultBitrate = audioStream == null ? 192000 : audioStream.BitRate ?? 192000; // Reduce the bitrate if we're downmixing if (targetAudioChannels.HasValue && audioStream != null && audioStream.Channels.HasValue && targetAudioChannels.Value < audioStream.Channels.Value) { @@ -615,7 +615,7 @@ namespace MediaBrowser.Model.Dlna { if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3")) { - if (string.Equals(subProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + if (StringHelper.EqualsIgnoreCase(subProtocol, "hls")) { defaultBitrate = Math.Max(384000, defaultBitrate); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index b3ced55a5..65d023e88 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1011,7 +1011,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false); - programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1) + programs = programList.OrderBy(i => i.StartDate.Date) .ThenByDescending(i => GetRecommendationScore(i, user.Id, factorChannelWatchCount)) .ThenBy(i => i.StartDate); diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index f5419e5cf..d8d3614e6 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -315,6 +315,8 @@ namespace MediaBrowser.Server.Startup.Common /// public override async Task RunStartupTasks() { + await PerformPreInitMigrations().ConfigureAwait(false); + if (ServerConfigurationManager.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion && ServerConfigurationManager.Configuration.IsStartupWizardCompleted) { @@ -366,23 +368,21 @@ namespace MediaBrowser.Server.Startup.Common HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; - PerformPreInitMigrations(); - return base.Init(progress); } - private void PerformPreInitMigrations() + private async Task PerformPreInitMigrations() { var migrations = new List { - new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename) + new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename, Logger) }; foreach (var task in migrations) { try { - task.Run(); + await task.Run().ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs index f0cb9e84e..6bcdcca87 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Server.Startup.Common.Migrations _taskManager = taskManager; } - public void Run() + public async Task Run() { // If a forced migration is required, do that now if (_config.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion) diff --git a/MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs index a6a8c1a35..6ef08fae9 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs @@ -1,8 +1,9 @@ - +using System.Threading.Tasks; + namespace MediaBrowser.Server.Startup.Common.Migrations { public interface IVersionMigration { - void Run(); + Task Run(); } } diff --git a/MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs index 3ad5f577f..cd2122e57 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Configuration; using System.Linq; +using System.Threading.Tasks; namespace MediaBrowser.Server.Startup.Common.Migrations { @@ -13,7 +14,7 @@ namespace MediaBrowser.Server.Startup.Common.Migrations _config = config; } - public void Run() + public async Task Run() { var migrationKey = this.GetType().FullName; var migrationKeyList = _config.Configuration.Migrations.ToList(); -- cgit v1.2.3 From c6419babaa75cedfbd021369f825d3a75e522c9b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 17 Sep 2016 13:01:58 -0400 Subject: update nlog, simpleinjector --- .../MediaBrowser.Common.Implementations.csproj | 6 +++--- MediaBrowser.Common.Implementations/packages.config | 4 ++-- .../MediaBrowser.Server.Implementations.csproj | 4 ++-- MediaBrowser.Server.Implementations/packages.config | 2 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index ced2dd5a3..7e8823bc0 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -55,7 +55,7 @@ ..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll - ..\packages\NLog.4.3.6\lib\net45\NLog.dll + ..\packages\NLog.4.3.8\lib\net45\NLog.dll True @@ -65,8 +65,8 @@ False ..\ThirdParty\SharpCompress\SharpCompress.dll - - ..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll + + ..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll True diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config index 594b4c7c5..f444a3a05 100644 --- a/MediaBrowser.Common.Implementations/packages.config +++ b/MediaBrowser.Common.Implementations/packages.config @@ -2,7 +2,7 @@ - + - + \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index e182ad6a5..b25c07fe3 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -69,8 +69,8 @@ ..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll - - ..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll + + ..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll True diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 94522cd50..5d58aea19 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index ff52aebce..d4f3df3a5 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -13,8 +13,8 @@ Copyright © Emby 2013 - - + + -- cgit v1.2.3 From be491f73211bd8a3e4c4bc6924ad13318844be91 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 18 Sep 2016 01:52:27 -0400 Subject: don't take movies as series --- .../LiveTv/Listings/XmlTvListingsProvider.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index d1d8df2e8..d3549aef5 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -11,6 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.XmlTv.Classes; +using Emby.XmlTv.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -115,7 +116,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var reader = new XmlTvReader(path, GetLanguage(), null); var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken); - return results.Select(p => new ProgramInfo() + return results.Select(p => GetProgramInfo(p, info)); + } + + private ProgramInfo GetProgramInfo(XmlTvProgram p, ListingsProviderInfo info) + { + var programInfo = new ProgramInfo { ChannelId = p.ChannelId, EndDate = GetDate(p.EndDate), @@ -141,7 +147,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings OfficialRating = p.Rating != null && !String.IsNullOrEmpty(p.Rating.Value) ? p.Rating.Value : null, CommunityRating = p.StarRating.HasValue ? p.StarRating.Value : (float?)null, SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null - }); + }; + + if (programInfo.IsMovie) + { + programInfo.IsSeries = false; + programInfo.EpisodeNumber = null; + programInfo.EpisodeTitle = null; + } + + return programInfo; } private DateTime GetDate(DateTime date) -- cgit v1.2.3 From 05edb9f4bdcdf7733bcd7e6e74a876bbc2b5c064 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 18 Sep 2016 16:38:38 -0400 Subject: update timeshifting --- MediaBrowser.Api/ApiEntryPoint.cs | 13 ++++++++++- MediaBrowser.Api/Playback/BaseStreamingService.cs | 26 ++++++++++++++++------ MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 15 +++---------- MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs | 4 +--- MediaBrowser.Api/Playback/StreamState.cs | 4 ++++ MediaBrowser.Api/Subtitles/SubtitleService.cs | 2 +- .../Library/IMediaSourceManager.cs | 5 +---- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 7 +++++- .../Library/MediaSourceManager.cs | 22 +++++++++++++++++- .../LiveTv/EmbyTV/EmbyTV.cs | 6 ++--- .../LiveTv/TunerHosts/BaseTunerHost.cs | 3 +-- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 17 ++++++++------ .../Session/SessionManager.cs | 8 +++---- 13 files changed, 86 insertions(+), 46 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 7c5f7cde4..214fb7488 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Model.Dto; namespace MediaBrowser.Api { @@ -187,7 +188,8 @@ namespace MediaBrowser.Api CancellationTokenSource = cancellationTokenSource, Id = transcodingJobId, PlaySessionId = playSessionId, - LiveStreamId = liveStreamId + LiveStreamId = liveStreamId, + MediaSource = state.MediaSource }; _activeTranscodingJobs.Add(job); @@ -281,6 +283,14 @@ namespace MediaBrowser.Api } } + public TranscodingJob GetTranscodingJob(string playSessionId) + { + lock (_activeTranscodingJobs) + { + return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase)); + } + } + /// /// Called when [transcode begin request]. /// @@ -656,6 +666,7 @@ namespace MediaBrowser.Api /// Gets or sets the path. /// /// The path. + public MediaSourceInfo MediaSource { get; set; } public string Path { get; set; } /// /// Gets or sets the type. diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 4a62da6f6..eee6bfb65 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1847,18 +1847,30 @@ namespace MediaBrowser.Api.Playback var archivable = item as IArchivable; state.IsInputArchive = archivable != null && archivable.IsArchive; - MediaSourceInfo mediaSource; + MediaSourceInfo mediaSource = null; if (string.IsNullOrWhiteSpace(request.LiveStreamId)) { - var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList(); + //TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? + // ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) + // : null; - mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); + //if (currentJob != null) + //{ + // mediaSource = currentJob.MediaSource; + //} - if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + if (mediaSource == null) { - mediaSource = mediaSources.First(); + var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList(); + + mediaSource = string.IsNullOrEmpty(request.MediaSourceId) + ? mediaSources.First() + : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); + + if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + { + mediaSource = mediaSources.First(); + } } } else diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index a0ac96b9d..1e056f670 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Api.Playback.Hls throw; } - var waitForSegments = state.SegmentLength >= 10 ? 2 : 3; + var waitForSegments = state.SegmentLength >= 10 ? 2 : (state.SegmentLength > 3 || !isLive ? 3 : 4); await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false); } } @@ -128,10 +128,9 @@ namespace MediaBrowser.Api.Playback.Hls var audioBitrate = state.OutputAudioBitrate ?? 0; var videoBitrate = state.OutputVideoBitrate ?? 0; - var appendBaselineStream = false; var baselineStreamBitrate = 64000; - var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); + var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); @@ -161,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Hls } } - private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate) + private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate) { var builder = new StringBuilder(); @@ -175,14 +174,6 @@ namespace MediaBrowser.Api.Playback.Hls var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8"); builder.AppendLine(playlistUrl); - // Low bitrate stream - if (includeBaselineStream) - { - builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + baselineStreamBitrate.ToString(UsCulture)); - playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "-low/stream.m3u8"); - builder.AppendLine(playlistUrl); - } - return builder.ToString(); } diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 27deaf25e..976fed3f0 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -68,8 +68,6 @@ namespace MediaBrowser.Api.Playback.Hls [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] public class GetHlsVideoSegmentLegacy : VideoStreamRequest { - // TODO: Deprecate with new iOS app - public string PlaylistId { get; set; } /// @@ -113,7 +111,7 @@ namespace MediaBrowser.Api.Playback.Hls var file = request.SegmentId + Path.GetExtension(Request.PathInfo); file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file); - var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty); + var normalizedPlaylistId = request.PlaylistId; var playlistPath = Directory.EnumerateFiles(_config.ApplicationPaths.TranscodingTempPath, "*") .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 2e92c4a49..d6ccdd1fd 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -88,6 +88,10 @@ namespace MediaBrowser.Api.Playback return 10; } + if (!RunTimeTicks.HasValue) + { + return 10; + } return 6; } diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index fe13e8b21..b07a31a87 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Api.Subtitles { var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); - var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, false).ConfigureAwait(false); + var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false); var builder = new StringBuilder(); diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index a77d88049..1df77cdc9 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -60,11 +60,8 @@ namespace MediaBrowser.Controller.Library /// /// Gets the static media source. /// - /// The item. - /// The media source identifier. - /// if set to true [enable path substitution]. /// MediaSourceInfo. - Task GetMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution); + Task GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken); /// /// Opens the media source. diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index d958d0e37..6345e2105 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -827,6 +827,7 @@ namespace MediaBrowser.Dlna.PlayTo public string DeviceId { get; set; } public string MediaSourceId { get; set; } + public string LiveStreamId { get; set; } public BaseItem Item { get; set; } public MediaSourceInfo MediaSource { get; set; } @@ -910,6 +911,10 @@ namespace MediaBrowser.Dlna.PlayTo { request.StartPositionTicks = long.Parse(val, CultureInfo.InvariantCulture); } + else if (i == 22) + { + request.LiveStreamId = val; + } } request.Item = string.IsNullOrWhiteSpace(request.ItemId) @@ -920,7 +925,7 @@ namespace MediaBrowser.Dlna.PlayTo request.MediaSource = hasMediaSources == null ? null - : (await mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, false).ConfigureAwait(false)); + : (await mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, request.LiveStreamId, false, CancellationToken.None).ConfigureAwait(false)); return request; } diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 4a533ff93..c20245a6e 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -221,8 +221,28 @@ namespace MediaBrowser.Server.Implementations.Library } } - public async Task GetMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution) + public async Task GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken) { + if (!string.IsNullOrWhiteSpace(liveStreamId)) + { + return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false); + } + //await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + //try + //{ + // var stream = _openStreams.Values.FirstOrDefault(i => string.Equals(i.MediaSource.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); + + // if (stream != null) + // { + // return stream.MediaSource; + // } + //} + //finally + //{ + // _liveStreamSemaphore.Release(); + //} + var sources = await GetPlayackMediaSources(item.Id.ToString("N"), null, enablePathSubstitution, new[] { MediaType.Audio, MediaType.Video }, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index b508110cf..3d7b4abef 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -763,7 +763,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV throw new ApplicationException("Tuner not found."); } - private async Task> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) + private async Task> GetChannelStreamInternal(string channelId, CancellationToken cancellationToken) { _logger.Info("Streaming Channel " + channelId); @@ -771,7 +771,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { try { - var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); + var result = await hostInstance.GetChannelStream(channelId, null, cancellationToken).ConfigureAwait(false); return new Tuple(result.Item1, hostInstance, result.Item2); } @@ -994,7 +994,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { - var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false); + var result = await GetChannelStreamInternal(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); isResourceOpen = true; semaphore = result.Item3; var mediaStreamInfo = result.Item1; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 7aa9eb1cf..3f6bb140b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -223,8 +223,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } } - var stream = - await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); + var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); if (EnableMediaProbing) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index fd4775938..c5bd648cf 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -319,18 +319,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun videoBitrate = 1000000; } - if (string.IsNullOrWhiteSpace(videoCodec)) + var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); + var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase)); + if (channel != null) { - var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); - var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase)); - if (channel != null) + if (string.IsNullOrWhiteSpace(videoCodec)) { videoCodec = channel.VideoCodec; - audioCodec = channel.AudioCodec; + } + audioCodec = channel.AudioCodec; + if (!videoBitrate.HasValue) + { videoBitrate = (channel.IsHD ?? true) ? 15000000 : 2000000; - audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000; } + audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000; } // normalize @@ -380,7 +383,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun BitRate = audioBitrate } }, - RequiresOpening = false, + RequiresOpening = true, RequiresClosing = false, BufferMs = 0, Container = "ts", diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index afcdf9d90..f56af5b61 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -307,9 +307,9 @@ namespace MediaBrowser.Server.Implementations.Session } } - private Task GetMediaSource(IHasMediaSources item, string mediaSourceId) + private Task GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId) { - return _mediaSourceManager.GetMediaSource(item, mediaSourceId, false); + return _mediaSourceManager.GetMediaSource(item, mediaSourceId, liveStreamId, false, CancellationToken.None); } /// @@ -337,7 +337,7 @@ namespace MediaBrowser.Server.Implementations.Session var hasMediaSources = libraryItem as IHasMediaSources; if (hasMediaSources != null) { - mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false); + mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); if (mediaSource != null) { @@ -792,7 +792,7 @@ namespace MediaBrowser.Server.Implementations.Session var hasMediaSources = libraryItem as IHasMediaSources; if (hasMediaSources != null) { - mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false); + mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); } info.Item = GetItemInfo(libraryItem, libraryItem, mediaSource); -- cgit v1.2.3 From 6798a8f92c3ef35b496b8446add497d6d074a2d2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 19 Sep 2016 11:41:35 -0400 Subject: always supply SeriesTimerId --- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 3 +- MediaBrowser.Api/Playback/StreamState.cs | 12 ++++-- MediaBrowser.Controller/Entities/BaseItem.cs | 10 +++++ .../LiveTv/EmbyTV/EmbyTV.cs | 6 +-- .../LiveTv/LiveTvManager.cs | 44 +++++++++++++++++++--- 5 files changed, 62 insertions(+), 13 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 1e056f670..761b1eb4e 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -150,9 +150,10 @@ namespace MediaBrowser.Api.Playback.Hls { var text = reader.ReadToEnd(); + text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT"); + var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture); - // ffmpeg pads the reported length by a full second text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase); return text; diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index d6ccdd1fd..109aa85de 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -73,6 +73,10 @@ namespace MediaBrowser.Api.Playback { get { + if (!RunTimeTicks.HasValue) + { + return 6; + } if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { var userAgent = UserAgent ?? string.Empty; @@ -88,13 +92,13 @@ namespace MediaBrowser.Api.Playback return 10; } - if (!RunTimeTicks.HasValue) - { - return 10; - } return 6; } + if (!RunTimeTicks.HasValue) + { + return 6; + } return 3; } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 2a49168ed..6f358dfb4 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -254,6 +254,16 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public string ExternalSeriesId + { + get { return this.GetProviderId("ProviderExternalSeriesId"); } + set + { + this.SetProviderId("ProviderExternalSeriesId", value); + } + } + /// /// Gets or sets the etag. /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3d7b4abef..b508110cf 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -763,7 +763,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV throw new ApplicationException("Tuner not found."); } - private async Task> GetChannelStreamInternal(string channelId, CancellationToken cancellationToken) + private async Task> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) { _logger.Info("Streaming Channel " + channelId); @@ -771,7 +771,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { try { - var result = await hostInstance.GetChannelStream(channelId, null, cancellationToken).ConfigureAwait(false); + var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); return new Tuple(result.Item1, hostInstance, result.Item2); } @@ -994,7 +994,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { - var result = await GetChannelStreamInternal(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); + var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false); isResourceOpen = true; semaphore = result.Item3; var mediaStreamInfo = result.Item1; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 65d023e88..df1ef3962 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -668,6 +668,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.EpisodeTitle = info.EpisodeTitle; item.ExternalId = info.Id; + item.ExternalSeriesId = info.SeriesId; item.Genres = info.Genres; item.IsHD = info.IsHD; item.IsKids = info.IsKids; @@ -903,8 +904,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user); - var list = new List>(); - list.Add(new Tuple(dto, program.ServiceName, program.ExternalId)); + var list = new List>(); + list.Add(new Tuple(dto, program.ServiceName, program.ExternalId, program.ExternalSeriesId)); await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false); @@ -1092,15 +1093,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv return score; } - private async Task AddRecordingInfo(IEnumerable> programs, CancellationToken cancellationToken) + private async Task AddRecordingInfo(IEnumerable> programs, CancellationToken cancellationToken) { var timers = new Dictionary>(); + var seriesTimers = new Dictionary>(); foreach (var programTuple in programs) { var program = programTuple.Item1; var serviceName = programTuple.Item2; var externalProgramId = programTuple.Item3; + string externalSeriesId = programTuple.Item4; if (string.IsNullOrWhiteSpace(serviceName)) { @@ -1123,6 +1126,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase)); + var foundSeriesTimer = false; if (timer != null) { @@ -1133,8 +1137,38 @@ namespace MediaBrowser.Server.Implementations.LiveTv { program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, timer.SeriesTimerId) .ToString("N"); + + foundSeriesTimer = true; } } + + if (foundSeriesTimer || string.IsNullOrWhiteSpace(externalSeriesId)) + { + continue; + } + + List seriesTimerList; + if (!seriesTimers.TryGetValue(serviceName, out seriesTimerList)) + { + try + { + var tempTimers = await GetService(serviceName).GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); + seriesTimers[serviceName] = seriesTimerList = tempTimers.ToList(); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting series timer infos", ex); + seriesTimers[serviceName] = seriesTimerList = new List(); + } + } + + var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase)); + + if (seriesTimer != null) + { + program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, seriesTimer.Id) + .ToString("N"); + } } } @@ -1659,7 +1693,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task AddInfoToProgramDto(List> tuples, List fields, User user = null) { - var recordingTuples = new List>(); + var recordingTuples = new List>(); foreach (var tuple in tuples) { @@ -1727,7 +1761,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.ServiceName = serviceName; } - recordingTuples.Add(new Tuple(dto, serviceName, program.ExternalId)); + recordingTuples.Add(new Tuple(dto, serviceName, program.ExternalId, program.ExternalSeriesId)); } await AddRecordingInfo(recordingTuples, CancellationToken.None).ConfigureAwait(false); -- cgit v1.2.3 From dd8601f515f2b306d9f656ba6de62652cccd685f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 20 Sep 2016 11:21:44 -0400 Subject: update guide image quality --- .../LiveTv/Listings/SchedulesDirect.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index c30263388..0bc84a2e5 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -171,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var data = images[imageIndex].data ?? new List(); data = data.OrderByDescending(GetSizeOrder).ToList(); - programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 1280); + programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 800); //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false); //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? @@ -536,7 +536,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings } return false; - }) ?? matches.FirstOrDefault(); + }); + + if (match == null) + { + // Get the second lowest quality image, when possible + if (matches.Count > 1) + { + match = matches[matches.Count - 2]; + } + else + { + match = matches.FirstOrDefault(); + } + } if (match == null) { -- cgit v1.2.3 From 55bbc051c2e5da93baef2efeb105684e4ff40c10 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 20 Sep 2016 11:22:00 -0400 Subject: exclude backdrops from photos --- MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 3f9475480..d4ebb8457 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers return false; } - if (IgnoreFiles.Any(i => filename.IndexOf("-" + i, StringComparison.OrdinalIgnoreCase) != -1)) + if (IgnoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) { return false; } -- cgit v1.2.3 From bcfef5dd8377879963abf28ff80ffcbc8eaf52bd Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 20 Sep 2016 15:38:53 -0400 Subject: update recording nfo saving --- .../LiveTv/EmbyTV/EmbyTV.cs | 97 ++++++++++++++++++++-- .../LiveTv/EmbyTV/RecordingHelper.cs | 5 ++ 2 files changed, 96 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index b508110cf..2dd23857a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -27,6 +27,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; using CommonIO; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Configuration; @@ -1026,6 +1027,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV result.Item3.Release(); isResourceOpen = false; + + SaveNfo(timer, recordPath, seriesPath); }; var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration); @@ -1071,7 +1074,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Status = RecordingStatus.Completed; _timerProvider.Delete(timer); - OnSuccessfulRecording(timer, recordPath, seriesPath); + OnSuccessfulRecording(timer, recordPath); } else if (DateTime.UtcNow < timer.EndDate) { @@ -1139,7 +1142,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return new DirectRecorder(_logger, _httpClient, _fileSystem); } - private async void OnSuccessfulRecording(TimerInfo timer, string path, string seriesPath) + private async void OnSuccessfulRecording(TimerInfo timer, string path) { if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize) { @@ -1163,15 +1166,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.ErrorException("Error processing new recording", ex); } } - - SaveNfo(timer, path, seriesPath); } private void SaveNfo(TimerInfo timer, string recordingPath, string seriesPath) { - if (timer.IsProgramSeries) + try + { + if (timer.IsProgramSeries) + { + SaveSeriesNfo(timer, recordingPath, seriesPath); + } + else if (!timer.IsMovie || timer.IsSports) + { + SaveVideoNfo(timer, recordingPath); + } + } + catch (Exception ex) { - SaveSeriesNfo(timer, recordingPath, seriesPath); + _logger.ErrorException("Error saving nfo", ex); } } @@ -1209,6 +1221,79 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } + public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; + private void SaveVideoNfo(TimerInfo timer, string recordingPath) + { + var nfoPath = Path.ChangeExtension(recordingPath, ".nfo"); + + if (File.Exists(nfoPath)) + { + return; + } + + using (var stream = _fileSystem.GetFileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + var settings = new XmlWriterSettings + { + Indent = true, + Encoding = Encoding.UTF8, + CloseOutput = false + }; + + using (XmlWriter writer = XmlWriter.Create(stream, settings)) + { + writer.WriteStartDocument(true); + writer.WriteStartElement("movie"); + + if (!string.IsNullOrWhiteSpace(timer.Name)) + { + writer.WriteElementString("title", timer.Name); + } + + writer.WriteElementString("dateadded", DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat)); + + if (timer.ProductionYear.HasValue) + { + writer.WriteElementString("year", timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)); + } + if (!string.IsNullOrEmpty(timer.OfficialRating)) + { + writer.WriteElementString("mpaa", timer.OfficialRating); + } + + var overview = (timer.Overview ?? string.Empty) + .StripHtml() + .Replace(""", "'"); + + writer.WriteElementString("plot", overview); + writer.WriteElementString("lockdata", true.ToString().ToLower()); + + if (timer.CommunityRating.HasValue) + { + writer.WriteElementString("rating", timer.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); + } + + foreach (var genre in timer.Genres) + { + writer.WriteElementString("genre", genre); + } + + if (!string.IsNullOrWhiteSpace(timer.ShortOverview)) + { + writer.WriteElementString("outline", timer.ShortOverview); + } + + if (!string.IsNullOrWhiteSpace(timer.HomePageUrl)) + { + writer.WriteElementString("website", timer.HomePageUrl); + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + } + } + private ProgramInfo GetProgramInfoFromCache(string channelId, string programId) { var epgData = GetEpgDataForChannel(channelId); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 236439bc5..67356da2f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -46,6 +46,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timerInfo.EpisodeTitle = programInfo.EpisodeTitle; timerInfo.OriginalAirDate = programInfo.OriginalAirDate; timerInfo.IsProgramSeries = programInfo.IsSeries; + + timerInfo.HomePageUrl = programInfo.HomePageUrl; + timerInfo.CommunityRating = programInfo.CommunityRating; + timerInfo.ShortOverview = programInfo.ShortOverview; + timerInfo.OfficialRating = programInfo.OfficialRating; } public static string GetRecordingName(TimerInfo info) -- cgit v1.2.3 From 61ee765de96eeffa28b0042d633166ba5d214850 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 20 Sep 2016 15:43:27 -0400 Subject: update library monitor --- MediaBrowser.Server.Implementations/IO/FileRefresher.cs | 4 +++- MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 8e28aa2a0..3df7a03d4 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations.ScheduledTasks; +using MoreLinq; namespace MediaBrowser.Server.Implementations.IO { @@ -136,9 +137,10 @@ namespace MediaBrowser.Server.Implementations.IO private async Task ProcessPathChanges(List paths) { var itemsToRefresh = paths + .Distinct(StringComparer.OrdinalIgnoreCase) .Select(GetAffectedBaseItem) .Where(item => item != null) - .Distinct() + .DistinctBy(i => i.Id) .ToList(); foreach (var p in paths) diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 8bb40a00e..6e0f9a3c4 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -404,7 +404,20 @@ namespace MediaBrowser.Server.Implementations.IO { Logger.Debug("Changed detected of type " + e.ChangeType + " to " + e.FullPath); - ReportFileSystemChanged(e.FullPath); + var path = e.FullPath; + + // For deletes, use the parent path + if (e.ChangeType == WatcherChangeTypes.Deleted) + { + var parentPath = Path.GetDirectoryName(path); + + if (!string.IsNullOrWhiteSpace(parentPath)) + { + path = parentPath; + } + } + + ReportFileSystemChanged(path); } catch (Exception ex) { -- cgit v1.2.3 From d94598a75e303c0b4f76392e1e26b819d73e87c1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 21 Sep 2016 13:07:18 -0400 Subject: update recording screens --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 4 +++- MediaBrowser.Controller/Entities/IHasMetadata.cs | 2 ++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 21 ------------------- .../LiveTv/EmbyTV/EmbyTV.cs | 18 ++++++++++++++++ .../LiveTv/Listings/SchedulesDirect.cs | 24 ++++++++++++++-------- .../ApplicationHost.cs | 17 +++++++++++++++ 6 files changed, 56 insertions(+), 30 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 9a3659f07..7ef9a1d5b 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -367,6 +367,8 @@ namespace MediaBrowser.Api.Playback { param += " -crf 23"; } + + param += " -tune zerolatency"; } else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) @@ -1232,7 +1234,7 @@ namespace MediaBrowser.Api.Playback private void StartThrottler(StreamState state, TranscodingJob transcodingJob) { - if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EnableThrottling(state)) { transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager); state.TranscodingThrottler.Start(); diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index cf2f7db64..ae73dbeed 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { @@ -62,5 +63,6 @@ namespace MediaBrowser.Controller.Entities int? GetInheritedParentalRatingValue(); int InheritedParentalRatingValue { get; set; } + List GetInheritedTags(); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a2707aace..b2c2b71f8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -108,11 +108,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { get { - if (_hasExternalEncoder) - { - return "External"; - } - if (string.IsNullOrWhiteSpace(FFMpegPath)) { return null; @@ -177,12 +172,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { ConfigureEncoderPaths(); - if (_hasExternalEncoder) - { - LogPaths(); - return; - } - // If the path was passed in, save it into config now. var encodingOptions = GetEncodingOptions(); var appPath = encodingOptions.EncoderAppPath; @@ -207,11 +196,6 @@ namespace MediaBrowser.MediaEncoding.Encoder public async Task UpdateEncoderPath(string path, string pathType) { - if (_hasExternalEncoder) - { - return; - } - Tuple newPaths; if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) @@ -256,11 +240,6 @@ namespace MediaBrowser.MediaEncoding.Encoder private void ConfigureEncoderPaths() { - if (_hasExternalEncoder) - { - return; - } - var appPath = GetEncodingOptions().EncoderAppPath; if (string.IsNullOrWhiteSpace(appPath)) diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 2dd23857a..d4b21102d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -843,6 +843,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (recordingEndDate <= DateTime.UtcNow) { _logger.Warn("Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id); + _timerProvider.Delete(timer); return; } @@ -1273,6 +1274,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV writer.WriteElementString("rating", timer.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); } + if (timer.IsSports) + { + AddGenre(timer.Genres, "Sports"); + } + if (timer.IsKids) + { + AddGenre(timer.Genres, "Kids"); + } + foreach (var genre in timer.Genres) { writer.WriteElementString("genre", genre); @@ -1294,6 +1304,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } + private void AddGenre(List genres, string genre) + { + if (!genres.Contains(genre, StringComparer.OrdinalIgnoreCase)) + { + genres.Add(genre); + } + } + private ProgramInfo GetProgramInfoFromCache(string channelId, string programId) { var epgData = GetEpgDataForChannel(channelId); diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 0bc84a2e5..c3907c045 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -339,13 +339,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings channelNumber = channelNumber.TrimStart('0'); _logger.Debug("Found channel: " + channelNumber + " in Schedules Direct"); - if (root.stations != null) + + var schChannel = (root.stations ?? new List()).FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); + if (schChannel != null) { - var schChannel = root.stations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); - if (schChannel != null) + AddToChannelPairCache(listingsId, channelNumber, schChannel); + } + else + { + AddToChannelPairCache(listingsId, channelNumber, new ScheduleDirect.Station { - AddToChannelPairCache(listingsId, channelNumber, schChannel); - } + stationID = map.stationID + }); } } _logger.Info("Added " + GetChannelPairCacheCount(listingsId) + " channels to the dictionary"); @@ -361,8 +366,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings channel.ImageUrl = station.logo.URL; channel.HasImage = true; } - string channelName = station.name; - channel.Name = channelName; + + if (!string.IsNullOrWhiteSpace(station.name)) + { + channel.Name = station.name; + } } else { @@ -993,7 +1001,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var name = channelNumber; var station = GetStation(listingsId, channelNumber, null); - if (station != null) + if (station != null && !string.IsNullOrWhiteSpace(station.name)) { name = station.name; } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index d8d3614e6..9312e1501 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -870,6 +870,23 @@ namespace MediaBrowser.Server.Startup.Common { CertificatePath = GetCertificatePath(true); + try + { + ServerManager.Start(GetUrlPrefixes(), CertificatePath); + return; + } + catch (Exception ex) + { + Logger.ErrorException("Error starting http server", ex); + + if (HttpPort == 8096) + { + throw; + } + } + + HttpPort = 8096; + try { ServerManager.Start(GetUrlPrefixes(), CertificatePath); -- cgit v1.2.3 From 6999017bc9823fac9f9ff0f0b6f5eab39b2519e2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 21 Sep 2016 17:09:14 -0400 Subject: update recording dialogs --- MediaBrowser.Controller/Entities/BaseItem.cs | 4 ++++ MediaBrowser.Controller/Entities/IHasMetadata.cs | 1 + MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs | 5 +++++ MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs | 4 ++++ MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 4 +++- MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs | 4 ++++ 6 files changed, 21 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 6f358dfb4..6e0a33620 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -44,6 +44,7 @@ namespace MediaBrowser.Controller.Entities ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); LockedFields = new List(); ImageInfos = new List(); + InheritedTags = new List(); } public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; @@ -794,6 +795,9 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public int InheritedParentalRatingValue { get; set; } + [IgnoreDataMember] + public List InheritedTags { get; set; } + /// /// Gets or sets the critic rating. /// diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index ae73dbeed..aee58b445 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -64,5 +64,6 @@ namespace MediaBrowser.Controller.Entities int? GetInheritedParentalRatingValue(); int InheritedParentalRatingValue { get; set; } List GetInheritedTags(); + List InheritedTags { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 2d79473f0..12308adda 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -53,6 +53,10 @@ namespace MediaBrowser.Controller.LiveTv /// true if [record any channel]; otherwise, false. public bool RecordAnyChannel { get; set; } + public int KeepUpTo { get; set; } + + public bool SkipEpisodesInLibrary { get; set; } + /// /// Gets or sets a value indicating whether [record new only]. /// @@ -104,6 +108,7 @@ namespace MediaBrowser.Controller.LiveTv public SeriesTimerInfo() { Days = new List(); + SkipEpisodesInLibrary = true; } } } diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs index 4b88e42f3..9d4bab40c 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs @@ -18,12 +18,16 @@ namespace MediaBrowser.Model.LiveTv /// true if [record any time]; otherwise, false. public bool RecordAnyTime { get; set; } + public bool SkipEpisodesInLibrary { get; set; } + /// /// Gets or sets a value indicating whether [record any channel]. /// /// true if [record any channel]; otherwise, false. public bool RecordAnyChannel { get; set; } + public int KeepUpTo { get; set; } + /// /// Gets or sets a value indicating whether [record new only]. /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index d4b21102d..a3ca54b4a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -541,6 +541,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV instance.RecordAnyChannel = info.RecordAnyChannel; instance.RecordAnyTime = info.RecordAnyTime; instance.RecordNewOnly = info.RecordNewOnly; + instance.SkipEpisodesInLibrary = info.SkipEpisodesInLibrary; + instance.KeepUpTo = info.KeepUpTo; instance.StartDate = info.StartDate; _seriesTimerProvider.Update(instance); @@ -1381,7 +1383,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); - if (filterByCurrentRecordings) + if (filterByCurrentRecordings && seriesTimer.SkipEpisodesInLibrary) { allPrograms = allPrograms.Where(i => !IsProgramAlreadyInLibrary(i)); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 683377c61..4681d0a7b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -100,6 +100,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv Priority = info.Priority, RecordAnyChannel = info.RecordAnyChannel, RecordAnyTime = info.RecordAnyTime, + SkipEpisodesInLibrary = info.SkipEpisodesInLibrary, + KeepUpTo = info.KeepUpTo, RecordNewOnly = info.RecordNewOnly, ExternalChannelId = info.ChannelId, ExternalProgramId = info.ProgramId, @@ -308,6 +310,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv Priority = dto.Priority, RecordAnyChannel = dto.RecordAnyChannel, RecordAnyTime = dto.RecordAnyTime, + SkipEpisodesInLibrary = dto.SkipEpisodesInLibrary, + KeepUpTo = dto.KeepUpTo, RecordNewOnly = dto.RecordNewOnly, ProgramId = dto.ExternalProgramId, ChannelId = dto.ExternalChannelId, -- cgit v1.2.3 From eee9c0e0489087f4a8f611165e547f8f2dd353d5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 22 Sep 2016 02:57:31 -0400 Subject: update recording dialogs --- .../ScheduledTasks/ScheduledTaskWorker.cs | 2 +- .../Encoder/EncoderValidator.cs | 24 ++++++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 55 +++++++++------------- MediaBrowser.Providers/Manager/MetadataService.cs | 7 +++ .../Persistence/SqliteItemRepository.cs | 9 +++- 5 files changed, 63 insertions(+), 34 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 446e399bd..ced85780f 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -334,7 +334,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks trigger.Stop(); - TaskManager.QueueScheduledTask(ScheduledTask); + TaskManager.QueueScheduledTask(ScheduledTask, e.Argument); await Task.Delay(1000).ConfigureAwait(false); diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index a450097fd..24de4e77e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -26,6 +26,30 @@ namespace MediaBrowser.MediaEncoding.Encoder return new Tuple, List>(decoders, encoders); } + public bool ValidateVersion(string encoderAppPath) + { + string output = string.Empty; + try + { + output = GetProcessOutput(encoderAppPath, "-version"); + } + catch + { + } + + if (string.IsNullOrWhiteSpace(output)) + { + return false; + } + + if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) + { + return false; + } + + return true; + } + private List GetDecoders(string encoderAppPath) { string output = string.Empty; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index b2c2b71f8..d3131eb5a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -82,6 +82,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly List _runningProcesses = new List(); private readonly bool _hasExternalEncoder; + private string _originalFFMpegPath; + private string _originalFFProbePath; public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, IHttpClient httpClient, IZipClient zipClient) { @@ -100,6 +102,8 @@ namespace MediaBrowser.MediaEncoding.Encoder _zipClient = zipClient; FFProbePath = ffProbePath; FFMpegPath = ffMpegPath; + _originalFFProbePath = ffProbePath; + _originalFFMpegPath = ffMpegPath; _hasExternalEncoder = hasExternalEncoder; } @@ -231,6 +235,11 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ResourceNotFoundException("ffprobe not found"); } + if (!ValidateVersion(path)) + { + throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); + } + var config = GetEncodingOptions(); config.EncoderAppPath = path; ConfigurationManager.SaveConfiguration("encoding", config); @@ -238,6 +247,11 @@ namespace MediaBrowser.MediaEncoding.Encoder Init(); } + private bool ValidateVersion(string path) + { + return new EncoderValidator(_logger).ValidateVersion(path); + } + private void ConfigureEncoderPaths() { var appPath = GetEncodingOptions().EncoderAppPath; @@ -287,45 +301,22 @@ namespace MediaBrowser.MediaEncoding.Encoder string encoderPath = null; string probePath = null; - if (TestSystemInstalled("ffmpeg")) + if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath)) { - encoderPath = "ffmpeg"; + encoderPath = _originalFFMpegPath; + probePath = _originalFFProbePath; } - if (TestSystemInstalled("ffprobe")) - { - probePath = "ffprobe"; - } - - return new Tuple(encoderPath, probePath); - } - private bool TestSystemInstalled(string app) - { - try + if (string.IsNullOrWhiteSpace(encoderPath)) { - var startInfo = new ProcessStartInfo + if (ValidateVersion("ffmpeg") && ValidateVersion("ffprobe")) { - FileName = app, - Arguments = "-v", - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }; - - using (var process = Process.Start(startInfo)) - { - process.WaitForExit(); + encoderPath = "ffmpeg"; + probePath = "ffprobe"; } - - _logger.Debug("System app installed: " + app); - return true; - } - catch - { - _logger.Debug("System app not installed: " + app); - return false; } + + return new Tuple(encoderPath, probePath); } private Tuple GetPathsFromDirectory(string path) diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index e16e76bfc..18405aae9 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -316,6 +316,13 @@ namespace MediaBrowser.Providers.Manager updateType |= ItemUpdateType.MetadataImport; } + var inheritedTags = item.GetInheritedTags(); + if (!inheritedTags.SequenceEqual(item.InheritedTags, StringComparer.Ordinal)) + { + item.InheritedTags = inheritedTags; + updateType |= ItemUpdateType.MetadataImport; + } + return updateType; } diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 5ece3dd82..05c282687 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -412,7 +412,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "SeriesId", "SeriesSortName", "PresentationUniqueKey", - "InheritedParentalRatingValue" + "InheritedParentalRatingValue", + "InheritedTags" }; private readonly string[] _mediaStreamSaveColumns = @@ -1459,6 +1460,12 @@ namespace MediaBrowser.Server.Implementations.Persistence } index++; + if (!reader.IsDBNull(index)) + { + item.InheritedTags = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + index++; + return item; } -- cgit v1.2.3 From bb117d6b9894c9d6572af1dc348bed6574ddc8ea Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 23 Sep 2016 01:45:39 -0400 Subject: update SocketHttpListener --- .../MediaBrowser.Server.Implementations.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index b25c07fe3..eb3da1a12 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -73,8 +73,8 @@ ..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll True - - ..\packages\SocketHttpListener.1.0.0.39\lib\net45\SocketHttpListener.dll + + ..\packages\SocketHttpListener.1.0.0.40\lib\net45\SocketHttpListener.dll True -- cgit v1.2.3 From bfb2f64ea480a35c973362f014e1caee7918b713 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 23 Sep 2016 02:20:56 -0400 Subject: check against repeat programs --- .../Connect/ConnectManager.cs | 22 ++++++++++++++-------- .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 +- .../LiveTv/Listings/SchedulesDirect.cs | 4 ++-- .../LiveTv/LiveTvManager.cs | 14 ++++++++++++++ .../packages.config | 2 +- MediaBrowser.WebDashboard/Api/PackageCreator.cs | 2 +- 6 files changed, 33 insertions(+), 13 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs index e7e52a887..b2dd8ec86 100644 --- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs +++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs @@ -403,6 +403,14 @@ namespace MediaBrowser.Server.Implementations.Connect public async Task LinkUser(string userId, string connectUsername) { + if (string.IsNullOrWhiteSpace(userId)) + { + throw new ArgumentNullException("userId"); + } + if (string.IsNullOrWhiteSpace(connectUsername)) + { + throw new ArgumentNullException("connectUsername"); + } if (string.IsNullOrWhiteSpace(ConnectServerId)) { await UpdateConnectInfo().ConfigureAwait(false); @@ -422,14 +430,6 @@ namespace MediaBrowser.Server.Implementations.Connect private async Task LinkUserInternal(string userId, string connectUsername) { - if (string.IsNullOrWhiteSpace(userId)) - { - throw new ArgumentNullException("userId"); - } - if (string.IsNullOrWhiteSpace(connectUsername)) - { - throw new ArgumentNullException("connectUsername"); - } if (string.IsNullOrWhiteSpace(ConnectServerId)) { throw new ArgumentNullException("ConnectServerId"); @@ -446,6 +446,12 @@ namespace MediaBrowser.Server.Implementations.Connect throw new ArgumentException("The Emby account has been disabled."); } + var existingUser = _userManager.Users.FirstOrDefault(i => string.Equals(i.ConnectUserId, connectUser.Id) && !string.IsNullOrWhiteSpace(i.ConnectAccessKey)); + if (existingUser != null) + { + throw new InvalidOperationException("This connect user is already linked to local user " + existingUser.Name); + } + var user = GetUser(userId); if (!string.IsNullOrWhiteSpace(user.ConnectUserId)) diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 560b0d5b4..4e7f637b1 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -253,7 +253,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); - var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -re -i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; + var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; if (mediaSource.ReadAtNativeFramerate) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index c3907c045..b98919282 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -171,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var data = images[imageIndex].data ?? new List(); data = data.OrderByDescending(GetSizeOrder).ToList(); - programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 800); + programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 600); //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false); //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? @@ -485,7 +485,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings } } - if (!string.IsNullOrWhiteSpace(details.originalAirDate)) + if (!string.IsNullOrWhiteSpace(details.originalAirDate) && (!info.IsSeries || info.IsRepeat)) { info.OriginalAirDate = DateTime.Parse(details.originalAirDate); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index df1ef3962..dcef6e84a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2837,6 +2837,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv feature = "embytvseriesrecordings"; } + if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase)) + { + var config = GetConfiguration(); + if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && + config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) + { + return Task.FromResult(new MBRegistrationRecord + { + IsRegistered = true, + IsValid = true + }); + } + } + if (string.Equals(feature, "dvr", StringComparison.OrdinalIgnoreCase)) { var config = GetConfiguration(); diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 5d58aea19..043257fc8 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -8,5 +8,5 @@ - + \ No newline at end of file diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 02e8ad6f2..eaac3e2a0 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -346,7 +346,7 @@ namespace MediaBrowser.WebDashboard.Api if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { - sb.Append(""); + sb.Append(""); } else { -- cgit v1.2.3 From 8b096ccc0e87883655bcf9ea1f9e22e9961450d3 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 23 Sep 2016 02:21:54 -0400 Subject: stub out storage for new path substitution --- .../Library/LibraryStructureService.cs | 20 ++- MediaBrowser.Controller/Entities/Audio/Audio.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 4 +- .../Entities/CollectionFolder.cs | 31 +++-- MediaBrowser.Controller/Entities/Video.cs | 2 +- MediaBrowser.Controller/Library/ILibraryManager.cs | 6 +- MediaBrowser.Model/Configuration/LibraryOptions.cs | 8 ++ .../Dto/DtoService.cs | 4 +- .../Library/CoreResolutionIgnoreRule.cs | 3 +- .../Library/LibraryManager.cs | 140 ++++++++++++++------- .../LiveTv/EmbyTV/EmbyTV.cs | 8 +- 11 files changed, 160 insertions(+), 68 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 72966a7cd..0ed4ee2a2 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -112,6 +112,8 @@ namespace MediaBrowser.Api.Library /// The name. public string Path { get; set; } + public MediaPathInfo PathInfo { get; set; } + /// /// Gets or sets a value indicating whether [refresh library]. /// @@ -212,7 +214,12 @@ namespace MediaBrowser.Api.Library { var libraryOptions = request.LibraryOptions ?? new LibraryOptions(); - _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, libraryOptions, request.RefreshLibrary); + if (request.Paths != null && request.Paths.Length > 0) + { + libraryOptions.PathInfos = request.Paths.Select(i => new MediaPathInfo { Path = i }).ToArray(); + } + + _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, libraryOptions, request.RefreshLibrary); } /// @@ -308,7 +315,16 @@ namespace MediaBrowser.Api.Library try { - _libraryManager.AddMediaPath(request.Name, request.Path); + var mediaPath = request.PathInfo; + + if (mediaPath == null) + { + mediaPath = new MediaPathInfo + { + Path = request.Path + }; + } + _libraryManager.AddMediaPath(request.Name, mediaPath); } finally { diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 1af55a389..599e72314 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -262,7 +262,7 @@ namespace MediaBrowser.Controller.Entities.Audio Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File, MediaStreams = MediaSourceManager.GetMediaStreams(i.Id).ToList(), Name = i.Name, - Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path, + Path = enablePathSubstituion ? GetMappedPath(i, i.Path, locationType) : i.Path, RunTimeTicks = i.RunTimeTicks, Container = i.Container, Size = i.Size diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 6e0a33620..30e0f3ee7 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2121,11 +2121,11 @@ namespace MediaBrowser.Controller.Entities return hasChanges; } - protected static string GetMappedPath(string path, LocationType locationType) + protected static string GetMappedPath(BaseItem item, string path, LocationType locationType) { if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) { - return LibraryManager.GetPathAfterNetworkSubstitution(path); + return LibraryManager.GetPathAfterNetworkSubstitution(path, item); } return path; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 30ea26eb6..77d7ca7f2 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -48,24 +48,14 @@ namespace MediaBrowser.Controller.Entities private static readonly Dictionary LibraryOptions = new Dictionary(); public LibraryOptions GetLibraryOptions() { - lock (LibraryOptions) - { - LibraryOptions options; - if (!LibraryOptions.TryGetValue(Path, out options)) - { - options = LoadLibraryOptions(); - LibraryOptions[Path] = options; - } - - return options; - } + return GetLibraryOptions(Path); } - private LibraryOptions LoadLibraryOptions() + private static LibraryOptions LoadLibraryOptions(string path) { try { - var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(Path)) as LibraryOptions; + var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) as LibraryOptions; if (result == null) { @@ -100,6 +90,21 @@ namespace MediaBrowser.Controller.Entities SaveLibraryOptions(Path, options); } + public static LibraryOptions GetLibraryOptions(string path) + { + lock (LibraryOptions) + { + LibraryOptions options; + if (!LibraryOptions.TryGetValue(path, out options)) + { + options = LoadLibraryOptions(path); + LibraryOptions[path] = options; + } + + return options; + } + } + public static void SaveLibraryOptions(string path, LibraryOptions options) { lock (LibraryOptions) diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 8809f155c..1406a05ce 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -600,7 +600,7 @@ namespace MediaBrowser.Controller.Entities Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File, MediaStreams = mediaStreams, Name = GetMediaSourceName(i, mediaStreams), - Path = enablePathSubstitution ? GetMappedPath(i.Path, locationType) : i.Path, + Path = enablePathSubstitution ? GetMappedPath(i, i.Path, locationType) : i.Path, RunTimeTicks = i.RunTimeTicks, Video3DFormat = i.Video3DFormat, VideoType = i.VideoType, diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index d5c2fcd20..184a245c1 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -506,7 +506,7 @@ namespace MediaBrowser.Controller.Library /// QueryResult<BaseItem>. QueryResult QueryItems(InternalItemsQuery query); - string GetPathAfterNetworkSubstitution(string path); + string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem = null); /// /// Substitutes the path. @@ -556,9 +556,9 @@ namespace MediaBrowser.Controller.Library /// true if XXXX, false otherwise. bool IgnoreFile(FileSystemMetadata file, BaseItem parent); - void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary); + void AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary); void RemoveVirtualFolder(string name, bool refreshLibrary); - void AddMediaPath(string virtualFolderName, string path); + void AddMediaPath(string virtualFolderName, MediaPathInfo path); void RemoveMediaPath(string virtualFolderName, string path); QueryResult> GetGenres(InternalItemsQuery query); diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 770ad433d..460ee0918 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -9,11 +9,19 @@ public bool EnableChapterImageExtraction { get; set; } public bool ExtractChapterImagesDuringLibraryScan { get; set; } public bool DownloadImagesInAdvance { get; set; } + public MediaPathInfo[] PathInfos { get; set; } public LibraryOptions() { EnablePhotos = true; EnableRealtimeMonitor = true; + PathInfos = new MediaPathInfo[] { }; } } + + public class MediaPathInfo + { + public string Path { get; set; } + public string NetworkPath { get; set; } + } } diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index ae676626d..e4f1460c6 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1509,7 +1509,7 @@ namespace MediaBrowser.Server.Implementations.Dto } } - private string GetMappedPath(IHasMetadata item) + private string GetMappedPath(BaseItem item) { var path = item.Path; @@ -1517,7 +1517,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (locationType == LocationType.FileSystem || locationType == LocationType.Offline) { - path = _libraryManager.GetPathAfterNetworkSubstitution(path); + path = _libraryManager.GetPathAfterNetworkSubstitution(path, item); } return path; diff --git a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index ba7e33890..b550d1dda 100644 --- a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -33,7 +33,8 @@ namespace MediaBrowser.Server.Implementations.Library // Synology "@eaDir", - "eaDir" + "eaDir", + "#recycle" }; diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 1f8c77953..cef0022cf 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2527,8 +2527,29 @@ namespace MediaBrowser.Server.Implementations.Library }).OrderBy(i => i.Path).ToList(); } - public string GetPathAfterNetworkSubstitution(string path) + public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem) { + if (ownerItem != null) + { + var libraryOptions = GetLibraryOptions(ownerItem); + if (libraryOptions != null) + { + foreach (var pathInfo in libraryOptions.PathInfos) + { + if (string.IsNullOrWhiteSpace(pathInfo.NetworkPath)) + { + continue; + } + + var substitutionResult = SubstitutePathInternal(path, pathInfo.Path, pathInfo.NetworkPath); + if (substitutionResult.Item2) + { + return substitutionResult.Item1; + } + } + } + } + foreach (var map in ConfigurationManager.Configuration.PathSubstitutions) { path = SubstitutePath(path, map.From, map.To); @@ -2538,6 +2559,11 @@ namespace MediaBrowser.Server.Implementations.Library } public string SubstitutePath(string path, string from, string to) + { + return SubstitutePathInternal(path, from, to).Item1; + } + + private Tuple SubstitutePathInternal(string path, string from, string to) { if (string.IsNullOrWhiteSpace(path)) { @@ -2552,7 +2578,11 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("to"); } - var newPath = path.Replace(from.Trim(), to.Trim(), StringComparison.OrdinalIgnoreCase); + from = from.Trim(); + to = to.Trim(); + + var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase); + var changed = false; if (!string.Equals(newPath, path)) { @@ -2564,9 +2594,11 @@ namespace MediaBrowser.Server.Implementations.Library { newPath = newPath.Replace('/', '\\'); } + + changed = true; } - return newPath; + return new Tuple(newPath, changed); } private void SetExtraTypeFromFilename(Video item) @@ -2695,7 +2727,7 @@ namespace MediaBrowser.Server.Implementations.Library throw new InvalidOperationException(); } - public void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary) + public void AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) { @@ -2713,12 +2745,13 @@ namespace MediaBrowser.Server.Implementations.Library virtualFolderPath = Path.Combine(rootFolderPath, name); } - if (mediaPaths != null) + var mediaPathInfos = options.PathInfos; + if (mediaPathInfos != null) { - var invalidpath = mediaPaths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i)); + var invalidpath = mediaPathInfos.FirstOrDefault(i => !_fileSystem.DirectoryExists(i.Path)); if (invalidpath != null) { - throw new ArgumentException("The specified path does not exist: " + invalidpath + "."); + throw new ArgumentException("The specified path does not exist: " + invalidpath.Path + "."); } } @@ -2740,11 +2773,11 @@ namespace MediaBrowser.Server.Implementations.Library CollectionFolder.SaveLibraryOptions(virtualFolderPath, options); - if (mediaPaths != null) + if (mediaPathInfos != null) { - foreach (var path in mediaPaths) + foreach (var path in mediaPathInfos) { - AddMediaPath(name, path); + AddMediaPathInternal(name, path, false); } } } @@ -2770,6 +2803,61 @@ namespace MediaBrowser.Server.Implementations.Library } } + private const string ShortcutFileExtension = ".mblink"; + private const string ShortcutFileSearch = "*" + ShortcutFileExtension; + public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo) + { + AddMediaPathInternal(virtualFolderName, pathInfo, true); + } + + private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions) + { + if (pathInfo == null) + { + throw new ArgumentNullException("path"); + } + + var path = pathInfo.Path; + + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentNullException("path"); + } + + if (!_fileSystem.DirectoryExists(path)) + { + throw new DirectoryNotFoundException("The path does not exist."); + } + + var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); + + var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path); + + var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); + + while (_fileSystem.FileExists(lnk)) + { + shortcutFilename += "1"; + lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); + } + + _fileSystem.CreateShortcut(lnk, path); + + RemoveContentTypeOverrides(path); + + if (saveLibraryOptions) + { + var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); + + var list = libraryOptions.PathInfos.ToList(); + list.Add(pathInfo); + libraryOptions.PathInfos = list.ToArray(); + + CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); + } + } + public void RemoveVirtualFolder(string name, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) @@ -2814,38 +2902,6 @@ namespace MediaBrowser.Server.Implementations.Library } } - private const string ShortcutFileExtension = ".mblink"; - private const string ShortcutFileSearch = "*" + ShortcutFileExtension; - public void AddMediaPath(string virtualFolderName, string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - if (!_fileSystem.DirectoryExists(path)) - { - throw new DirectoryNotFoundException("The path does not exist."); - } - - var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; - var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); - - var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path); - - var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); - - while (_fileSystem.FileExists(lnk)) - { - shortcutFilename += "1"; - lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension); - } - - _fileSystem.CreateShortcut(lnk, path); - - RemoveContentTypeOverrides(path); - } - private void RemoveContentTypeOverrides(string path) { if (string.IsNullOrWhiteSpace(path)) diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index a3ca54b4a..e358f9d25 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -143,9 +143,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV continue; } + var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray(); + + var libraryOptions = new LibraryOptions + { + PathInfos = mediaPathInfos + }; try { - _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, pathsToCreate.ToArray(), new LibraryOptions(), true); + _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, libraryOptions, true); } catch (Exception ex) { -- cgit v1.2.3 From fdc2826709495c5313c4a61060fd420d0f8214b8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 24 Sep 2016 02:22:03 -0400 Subject: add network share config --- .../Library/LibraryStructureService.cs | 26 ++++++++++++++ MediaBrowser.Controller/Library/ILibraryManager.cs | 1 + .../IO/LibraryMonitor.cs | 5 ++- .../Library/LibraryManager.cs | 41 ++++++++++++++++++++-- .../LiveTv/LiveTvManager.cs | 6 +++- 5 files changed, 74 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 0ed4ee2a2..e872061a7 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -121,6 +121,18 @@ namespace MediaBrowser.Api.Library public bool RefreshLibrary { get; set; } } + [Route("/Library/VirtualFolders/Paths/Update", "POST")] + public class UpdateMediaPath : IReturnVoid + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + public MediaPathInfo PathInfo { get; set; } + } + [Route("/Library/VirtualFolders/Paths", "DELETE")] public class RemoveMediaPath : IReturnVoid { @@ -348,6 +360,20 @@ namespace MediaBrowser.Api.Library } } + /// + /// Posts the specified request. + /// + /// The request. + public void Post(UpdateMediaPath request) + { + if (string.IsNullOrWhiteSpace(request.Name)) + { + throw new ArgumentNullException("request"); + } + + _libraryManager.UpdateMediaPath(request.Name, request.PathInfo); + } + /// /// Deletes the specified request. /// diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 184a245c1..975c59659 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -559,6 +559,7 @@ namespace MediaBrowser.Controller.Library void AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary); void RemoveVirtualFolder(string name, bool refreshLibrary); void AddMediaPath(string virtualFolderName, MediaPathInfo path); + void UpdateMediaPath(string virtualFolderName, MediaPathInfo path); void RemoveMediaPath(string virtualFolderName, string path); QueryResult> GetGenres(InternalItemsQuery query); diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 6e0f9a3c4..bcc4e5dcf 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -43,7 +43,10 @@ namespace MediaBrowser.Server.Implementations.IO // WMC temp recording directories that will constantly be written to "TempRec", - "TempSBE" + "TempSBE", + "@eaDir", + "eaDir", + "#recycle" }; private readonly IReadOnlyList _alwaysIgnoreSubstrings = new List diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index cef0022cf..f62847bcc 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2858,6 +2858,32 @@ namespace MediaBrowser.Server.Implementations.Library } } + public void UpdateMediaPath(string virtualFolderName, MediaPathInfo pathInfo) + { + if (pathInfo == null) + { + throw new ArgumentNullException("path"); + } + + var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); + + var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); + + var list = libraryOptions.PathInfos.ToList(); + foreach (var originalPathInfo in list) + { + if (string.Equals(pathInfo.Path, originalPathInfo.Path, StringComparison.Ordinal)) + { + originalPathInfo.NetworkPath = pathInfo.NetworkPath; + break; + } + } + libraryOptions.PathInfos = list.ToArray(); + + CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); + } + public void RemoveVirtualFolder(string name, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) @@ -2938,19 +2964,28 @@ namespace MediaBrowser.Server.Implementations.Library } var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; - var path = Path.Combine(rootFolderPath, virtualFolderName); + var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); - if (!_fileSystem.DirectoryExists(path)) + if (!_fileSystem.DirectoryExists(virtualFolderPath)) { throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName)); } - var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); + var shortcut = Directory.EnumerateFiles(virtualFolderPath, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrEmpty(shortcut)) { _fileSystem.DeleteFile(shortcut); } + + var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); + + libraryOptions.PathInfos = libraryOptions + .PathInfos + .Where(i => !string.Equals(i.Path, mediaPath, StringComparison.Ordinal)) + .ToArray(); + + CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index dcef6e84a..9b340a9f9 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -699,7 +699,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.HomePageUrl = info.HomePageUrl; item.ProductionYear = info.ProductionYear; - item.PremiereDate = info.OriginalAirDate; + + if (!info.IsSeries || info.IsRepeat) + { + item.PremiereDate = info.OriginalAirDate; + } item.IndexNumber = info.EpisodeNumber; item.ParentIndexNumber = info.SeasonNumber; -- cgit v1.2.3 From 48d7f686eb2212a19ee988c18c39d9fe1027d483 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 24 Sep 2016 13:58:17 -0400 Subject: update network share settings --- MediaBrowser.Api/StartupWizardService.cs | 6 --- .../MediaBrowser.Controller.csproj | 1 - .../RelatedMedia/IRelatedMediaProvider.cs | 11 ------ MediaBrowser.Dlna/PlayTo/Device.cs | 2 +- .../Library/LibraryManager.cs | 44 ++++++++++++++++++++++ .../LiveTv/LiveTvManager.cs | 14 ------- .../Session/SessionManager.cs | 6 +-- MediaBrowser.Server.Mono/app.config | 2 +- .../ApplicationPathHelper.cs | 8 +++- 9 files changed, 55 insertions(+), 39 deletions(-) delete mode 100644 MediaBrowser.Controller/RelatedMedia/IRelatedMediaProvider.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index ef898eb53..176b497d7 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -148,12 +148,6 @@ namespace MediaBrowser.Api { var user = _userManager.Users.First(); - // TODO: This should be handled internally by xbmc metadata - const string metadataKey = "xbmcmetadata"; - var metadata = _config.GetConfiguration(metadataKey); - metadata.UserId = user.Id.ToString("N"); - _config.SaveConfiguration(metadataKey, metadata); - user.Name = request.Name; await _userManager.UpdateUser(user).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 8fae46906..cb36afa5f 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -318,7 +318,6 @@ - diff --git a/MediaBrowser.Controller/RelatedMedia/IRelatedMediaProvider.cs b/MediaBrowser.Controller/RelatedMedia/IRelatedMediaProvider.cs deleted file mode 100644 index bb2a0cd89..000000000 --- a/MediaBrowser.Controller/RelatedMedia/IRelatedMediaProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MediaBrowser.Controller.RelatedMedia -{ - public interface IRelatedMediaProvider - { - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - } -} diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index d1802b3ad..b656bc66e 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -474,7 +474,7 @@ namespace MediaBrowser.Dlna.PlayTo if (_disposed) return; - _logger.ErrorException("Error updating device info for {0}", ex, Properties.Name); + //_logger.ErrorException("Error updating device info for {0}", ex, Properties.Name); _successiveStopCount++; _connectFailureCount++; diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index f62847bcc..7c3196065 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2829,6 +2829,16 @@ namespace MediaBrowser.Server.Implementations.Library throw new DirectoryNotFoundException("The path does not exist."); } + if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !_fileSystem.DirectoryExists(pathInfo.NetworkPath)) + { + throw new DirectoryNotFoundException("The network path does not exist."); + } + + if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !_fileSystem.DirectoryExists(pathInfo.NetworkPath)) + { + throw new DirectoryNotFoundException("The network path does not exist."); + } + var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); @@ -2850,6 +2860,8 @@ namespace MediaBrowser.Server.Implementations.Library { var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); + SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); + var list = libraryOptions.PathInfos.ToList(); list.Add(pathInfo); libraryOptions.PathInfos = list.ToArray(); @@ -2865,11 +2877,18 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("path"); } + if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !_fileSystem.DirectoryExists(pathInfo.NetworkPath)) + { + throw new DirectoryNotFoundException("The network path does not exist."); + } + var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); + SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); + var list = libraryOptions.PathInfos.ToList(); foreach (var originalPathInfo in list) { @@ -2879,11 +2898,36 @@ namespace MediaBrowser.Server.Implementations.Library break; } } + libraryOptions.PathInfos = list.ToArray(); CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); } + private void SyncLibraryOptionsToLocations(string virtualFolderPath, LibraryOptions options) + { + var topLibraryFolders = GetUserRootFolder().Children.ToList(); + var info = GetVirtualFolderInfo(virtualFolderPath, topLibraryFolders); + + if (info.Locations.Count > 0 && info.Locations.Count != options.PathInfos.Length) + { + var list = options.PathInfos.ToList(); + + foreach (var location in info.Locations) + { + if (!list.Any(i => string.Equals(i.Path, location, StringComparison.Ordinal))) + { + list.Add(new MediaPathInfo + { + Path = location + }); + } + } + + options.PathInfos = list.ToArray(); + } + } + public void RemoveVirtualFolder(string name, bool refreshLibrary) { if (string.IsNullOrWhiteSpace(name)) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 9b340a9f9..8a4572813 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2855,20 +2855,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } - if (string.Equals(feature, "dvr", StringComparison.OrdinalIgnoreCase)) - { - var config = GetConfiguration(); - if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && - config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) - { - return Task.FromResult(new MBRegistrationRecord - { - IsRegistered = true, - IsValid = true - }); - } - } - return _security.GetRegistrationStatus(feature); } diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index f56af5b61..48f48cdcc 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -294,11 +294,9 @@ namespace MediaBrowser.Server.Implementations.Session var key = GetSessionKey(session.Client, session.DeviceId); SessionInfo removed; + _activeConnections.TryRemove(key, out removed); - if (_activeConnections.TryRemove(key, out removed)) - { - OnSessionEnded(removed); - } + OnSessionEnded(session); } } finally diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config index e14b908ad..e8c7a9326 100644 --- a/MediaBrowser.Server.Mono/app.config +++ b/MediaBrowser.Server.Mono/app.config @@ -8,7 +8,7 @@ - + diff --git a/MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs b/MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs index 285806791..254a782db 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationPathHelper.cs @@ -18,10 +18,16 @@ namespace MediaBrowser.Server.Startup.Common useDebugPath = true; #endif - var programDataPath = useDebugPath ? ConfigurationManager.AppSettings["DebugProgramDataPath"] : ConfigurationManager.AppSettings["ReleaseProgramDataPath"]; + var programDataPath = useDebugPath ? + ConfigurationManager.AppSettings["DebugProgramDataPath"] : + ConfigurationManager.AppSettings["ReleaseProgramDataPath"]; programDataPath = programDataPath.Replace("%ApplicationData%", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); + programDataPath = programDataPath + .Replace('/', Path.DirectorySeparatorChar) + .Replace('\\', Path.DirectorySeparatorChar); + // If it's a relative path, e.g. "..\" if (!Path.IsPathRooted(programDataPath)) { -- cgit v1.2.3 From d596053ec7830d89a83723b0ae2f8439c6319f6f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 25 Sep 2016 14:39:13 -0400 Subject: rework live stream handling --- MediaBrowser.Api/ApiEntryPoint.cs | 39 +++++- MediaBrowser.Api/LiveTv/LiveTvService.cs | 33 ++++- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 44 +++--- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 7 +- .../Progressive/BaseProgressiveStreamingService.cs | 23 ++- .../Progressive/ProgressiveStreamWriter.cs | 4 +- MediaBrowser.Api/Playback/StreamState.cs | 10 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 18 +-- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 2 +- MediaBrowser.Controller/LiveTv/LiveStream.cs | 30 ++++ .../MediaBrowser.Controller.csproj | 1 + .../IO/LibraryMonitor.cs | 8 +- .../Library/LibraryManager.cs | 20 ++- .../LiveTv/EmbyTV/DirectRecorder.cs | 18 ++- .../LiveTv/EmbyTV/EmbyTV.cs | 100 +++++++------ .../LiveTv/EmbyTV/EncodedRecorder.cs | 84 +---------- .../LiveTv/TunerHosts/BaseTunerHost.cs | 145 +++++++------------ .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 33 ++++- .../TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 156 +++++++++++++++++++++ .../LiveTv/TunerHosts/M3UTunerHost.cs | 13 +- .../LiveTv/TunerHosts/SatIp/SatIpHost.cs | 10 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../MediaBrowser.WebDashboard.csproj | 18 +-- MediaBrowser.XbmcMetadata/EntryPoint.cs | 10 ++ 24 files changed, 520 insertions(+), 307 deletions(-) create mode 100644 MediaBrowser.Controller/LiveTv/LiveStream.cs create mode 100644 MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 214fb7488..2f5b9e1e0 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Session; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -44,7 +45,13 @@ namespace MediaBrowser.Api private readonly IFileSystem _fileSystem; private readonly IMediaSourceManager _mediaSourceManager; - public readonly SemaphoreSlim TranscodingStartLock = new SemaphoreSlim(1, 1); + /// + /// The active transcoding jobs + /// + private readonly List _activeTranscodingJobs = new List(); + + private readonly Dictionary _transcodingLocks = + new Dictionary(); /// /// Initializes a new instance of the class. @@ -67,6 +74,21 @@ namespace MediaBrowser.Api _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; } + public SemaphoreSlim GetTranscodingLock(string outputPath) + { + lock (_transcodingLocks) + { + SemaphoreSlim result; + if (!_transcodingLocks.TryGetValue(outputPath, out result)) + { + result = new SemaphoreSlim(1, 1); + _transcodingLocks[outputPath] = result; + } + + return result; + } + } + private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) { if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) @@ -148,11 +170,6 @@ namespace MediaBrowser.Api } } - /// - /// The active transcoding jobs - /// - private readonly List _activeTranscodingJobs = new List(); - /// /// Called when [transcode beginning]. /// @@ -258,6 +275,11 @@ namespace MediaBrowser.Api } } + lock (_transcodingLocks) + { + _transcodingLocks.Remove(path); + } + if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) { _sessionManager.ClearTranscodingInfo(state.Request.DeviceId); @@ -497,6 +519,11 @@ namespace MediaBrowser.Api } } + lock (_transcodingLocks) + { + _transcodingLocks.Remove(job.Path); + } + lock (job.ProcessLock) { if (job.TranscodingThrottler != null) diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 3ad0ec1ba..a5f8fce6e 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -12,9 +12,13 @@ using ServiceStack; using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using CommonIO; +using MediaBrowser.Api.Playback.Progressive; +using MediaBrowser.Controller.Configuration; namespace MediaBrowser.Api.LiveTv { @@ -613,16 +617,24 @@ namespace MediaBrowser.Api.LiveTv } + [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")] + public class GetLiveStreamFile + { + public string Id { get; set; } + public string Container { get; set; } + } + public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; private readonly IUserManager _userManager; - private readonly IConfigurationManager _config; + private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; + private readonly IFileSystem _fileSystem; - public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService) + public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -630,6 +642,23 @@ namespace MediaBrowser.Api.LiveTv _httpClient = httpClient; _libraryManager = libraryManager; _dtoService = dtoService; + _fileSystem = fileSystem; + } + + public object Get(GetLiveStreamFile request) + { + var filePath = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, request.Id + ".ts"); + + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath); + + var streamSource = new ProgressiveFileCopier(_fileSystem, filePath, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + + return ResultFactory.GetAsyncStreamWriter(streamSource); } public object Get(GetDefaultListingProvider request) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 761b1eb4e..319e4bbb6 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -87,7 +87,8 @@ namespace MediaBrowser.Api.Playback.Hls if (!FileSystem.FileExists(playlist)) { - await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlist); + await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (!FileSystem.FileExists(playlist)) @@ -104,13 +105,13 @@ namespace MediaBrowser.Api.Playback.Hls throw; } - var waitForSegments = state.SegmentLength >= 10 ? 2 : (state.SegmentLength > 3 || !isLive ? 3 : 4); + var waitForSegments = state.SegmentLength >= 10 ? 2 : (state.SegmentLength > 3 || !isLive ? 3 : 3); await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false); } } finally { - ApiEntryPoint.Instance.TranscodingStartLock.Release(); + transcodingLock.Release(); } } @@ -182,32 +183,41 @@ namespace MediaBrowser.Api.Playback.Hls { Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist); - while (true) + while (!cancellationToken.IsCancellationRequested) { - // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written - using (var fileStream = GetPlaylistFileStream(playlist)) + try { - using (var reader = new StreamReader(fileStream)) + // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written + using (var fileStream = GetPlaylistFileStream(playlist)) { - var count = 0; - - while (!reader.EndOfStream) + using (var reader = new StreamReader(fileStream)) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); + var count = 0; - if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) + while (!reader.EndOfStream) { - count++; - if (count >= segmentCount) + var line = await reader.ReadLineAsync().ConfigureAwait(false); + + if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) { - Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist); - return; + count++; + if (count >= segmentCount) + { + Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist); + return; + } } } + await Task.Delay(100, cancellationToken).ConfigureAwait(false); } - await Task.Delay(100, cancellationToken).ConfigureAwait(false); } } + catch (IOException) + { + // May get an error if the file is locked + } + + await Task.Delay(50, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 9cd55528d..d4ddbd7c5 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -171,14 +171,15 @@ namespace MediaBrowser.Api.Playback.Hls return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } - await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlistPath); + await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); var released = false; try { if (FileSystem.FileExists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - ApiEntryPoint.Instance.TranscodingStartLock.Release(); + transcodingLock.Release(); released = true; return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } @@ -242,7 +243,7 @@ namespace MediaBrowser.Api.Playback.Hls { if (!released) { - ApiEntryPoint.Instance.TranscodingStartLock.Release(); + transcodingLock.Release(); } } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index b8cb6b14f..a68319109 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -17,6 +17,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using CommonIO; +using ServiceStack; namespace MediaBrowser.Api.Playback.Progressive { @@ -129,6 +130,23 @@ namespace MediaBrowser.Api.Playback.Progressive using (state) { + if (state.MediaPath.IndexOf("/livestreamfiles/", StringComparison.OrdinalIgnoreCase) != -1) + { + var parts = state.MediaPath.Split('/'); + var filename = parts[parts.Length - 2] + Path.GetExtension(parts[parts.Length - 1]); + var filePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, filename); + + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath); + + var streamSource = new ProgressiveFileCopier(FileSystem, filePath, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); + } + return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource) .ConfigureAwait(false); } @@ -345,7 +363,8 @@ namespace MediaBrowser.Api.Playback.Progressive return streamResult; } - await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); + await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { TranscodingJob job; @@ -376,7 +395,7 @@ namespace MediaBrowser.Api.Playback.Progressive } finally { - ApiEntryPoint.Instance.TranscodingStartLock.Release(); + transcodingLock.Release(); } } diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 0a9a44641..80b5e357d 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -24,6 +24,8 @@ namespace MediaBrowser.Api.Playback.Progressive private long _bytesWritten = 0; + public bool AllowEndOfFile = true; + public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _fileSystem = fileSystem; @@ -50,7 +52,7 @@ namespace MediaBrowser.Api.Playback.Progressive using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) { - while (eofCount < 15) + while (eofCount < 15 || !AllowEndOfFile) { var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 109aa85de..ef0282abc 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -73,10 +73,6 @@ namespace MediaBrowser.Api.Playback { get { - if (!RunTimeTicks.HasValue) - { - return 6; - } if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { var userAgent = UserAgent ?? string.Empty; @@ -92,12 +88,16 @@ namespace MediaBrowser.Api.Playback return 10; } + if (!RunTimeTicks.HasValue) + { + return 3; + } return 6; } if (!RunTimeTicks.HasValue) { - return 6; + return 3; } return 3; } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index a8e42749b..41c5dbdbb 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// Task{SeriesTimerInfoDto}. Task GetNewTimerDefaults(string programId, CancellationToken cancellationToken); - + /// /// Deletes the recording. /// @@ -51,7 +51,7 @@ namespace MediaBrowser.Controller.LiveTv /// The recording. /// Task. Task DeleteRecording(BaseItem recording); - + /// /// Cancels the timer. /// @@ -83,7 +83,7 @@ namespace MediaBrowser.Controller.LiveTv /// The user. /// Task{RecordingInfoDto}. Task GetRecording(string id, DtoOptions options, CancellationToken cancellationToken, User user = null); - + /// /// Gets the timer. /// @@ -125,14 +125,14 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// Task{QueryResult{SeriesTimerInfoDto}}. Task> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken); - + /// /// Gets the channel. /// /// The identifier. /// Channel. LiveTvChannel GetInternalChannel(string id); - + /// /// Gets the recording. /// @@ -157,7 +157,7 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// Task{StreamResponseInfo}. Task GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken); - + /// /// Gets the program. /// @@ -331,8 +331,8 @@ namespace MediaBrowser.Controller.LiveTv /// The fields. /// The user. /// Task. - Task AddInfoToProgramDto(List> programs, List fields, User user = null); - + Task AddInfoToProgramDto(List> programs, List fields, User user = null); + /// /// Saves the tuner host. /// @@ -395,7 +395,7 @@ namespace MediaBrowser.Controller.LiveTv Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); - List ListingProviders { get;} + List ListingProviders { get; } event EventHandler> SeriesTimerCancelled; event EventHandler> TimerCancelled; diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 1e7aa3de5..3c8b964a2 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.LiveTv /// The stream identifier. /// The cancellation token. /// Task<MediaSourceInfo>. - Task> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken); + Task GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken); /// /// Gets the channel stream media sources. /// diff --git a/MediaBrowser.Controller/LiveTv/LiveStream.cs b/MediaBrowser.Controller/LiveTv/LiveStream.cs new file mode 100644 index 000000000..15d09d857 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveStream.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Controller.LiveTv +{ + public class LiveStream + { + public MediaSourceInfo OriginalMediaSource { get; set; } + public MediaSourceInfo PublicMediaSource { get; set; } + public string Id { get; set; } + + public LiveStream(MediaSourceInfo mediaSource) + { + OriginalMediaSource = mediaSource; + PublicMediaSource = mediaSource; + Id = mediaSource.Id; + } + + public virtual Task Open(CancellationToken cancellationToken) + { + return Task.FromResult(true); + } + + public virtual Task Close() + { + return Task.FromResult(true); + } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index cb36afa5f..d70fba742 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -201,6 +201,7 @@ + diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index bcc4e5dcf..76f0e6a1d 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -43,16 +43,14 @@ namespace MediaBrowser.Server.Implementations.IO // WMC temp recording directories that will constantly be written to "TempRec", - "TempSBE", - "@eaDir", - "eaDir", - "#recycle" + "TempSBE" }; private readonly IReadOnlyList _alwaysIgnoreSubstrings = new List { // Synology - "@eaDir", + "eaDir", + "#recycle", ".wd_tv", ".actors" }; diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 7c3196065..b076996df 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2803,6 +2803,17 @@ namespace MediaBrowser.Server.Implementations.Library } } + private bool ValidateNetworkPath(string path) + { + if (Environment.OSVersion.Platform == PlatformID.Win32NT || !path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase)) + { + return Directory.Exists(path); + } + + // Without native support for unc, we cannot validate this when running under mono + return true; + } + private const string ShortcutFileExtension = ".mblink"; private const string ShortcutFileSearch = "*" + ShortcutFileExtension; public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo) @@ -2829,12 +2840,7 @@ namespace MediaBrowser.Server.Implementations.Library throw new DirectoryNotFoundException("The path does not exist."); } - if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !_fileSystem.DirectoryExists(pathInfo.NetworkPath)) - { - throw new DirectoryNotFoundException("The network path does not exist."); - } - - if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !_fileSystem.DirectoryExists(pathInfo.NetworkPath)) + if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath)) { throw new DirectoryNotFoundException("The network path does not exist."); } @@ -2877,7 +2883,7 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("path"); } - if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !_fileSystem.DirectoryExists(pathInfo.NetworkPath)) + if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath)) { throw new DirectoryNotFoundException("The network path does not exist."); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 2e3edf3e9..0d043669a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -69,11 +69,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } private const int BufferSize = 81920; - public static async Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken) + public static Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken) + { + return CopyUntilCancelled(source, target, null, cancellationToken); + } + public static async Task CopyUntilCancelled(Stream source, Stream target, Action onStarted, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, cancellationToken).ConfigureAwait(false); + 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); @@ -85,7 +91,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private static async Task CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken) + private static async Task CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, Action onStarted, CancellationToken cancellationToken) { byte[] buffer = new byte[bufferSize]; int bytesRead; @@ -96,6 +102,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); totalBytesRead += bytesRead; + + if (onStarted != null) + { + onStarted(); + } + onStarted = null; } return totalBytesRead; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index e358f9d25..6585e92be 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -746,33 +746,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV throw new NotImplementedException(); } + private readonly SemaphoreSlim _liveStreamsSemaphore = new SemaphoreSlim(1, 1); + private readonly Dictionary _liveStreams = new Dictionary(); + public async Task GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) { - _logger.Info("Streaming Channel " + channelId); + var result = await GetChannelStreamInternal(channelId, streamId, cancellationToken).ConfigureAwait(false); - foreach (var hostInstance in _liveTvManager.TunerHosts) - { - try - { - var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); - - result.Item2.Release(); - - return result.Item1; - } - catch (FileNotFoundException) - { - } - catch (Exception e) - { - _logger.ErrorException("Error getting channel stream", e); - } - } - - throw new ApplicationException("Tuner not found."); + return result.Item1.PublicMediaSource; } - private async Task> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) + private async Task> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) { _logger.Info("Streaming Channel " + channelId); @@ -782,7 +766,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); - return new Tuple(result.Item1, hostInstance, result.Item2); + await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false); + _liveStreams[result.Id] = result; + _liveStreamsSemaphore.Release(); + + return new Tuple(result, hostInstance); } catch (FileNotFoundException) { @@ -823,9 +811,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV throw new NotImplementedException(); } - public Task CloseLiveStream(string id, CancellationToken cancellationToken) + public async Task CloseLiveStream(string id, CancellationToken cancellationToken) { - return Task.FromResult(0); + await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false); + + try + { + LiveStream stream; + if (_liveStreams.TryGetValue(id, out stream)) + { + _liveStreams.Remove(id); + + try + { + await stream.Close().ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error closing live stream", ex); + } + } + } + finally + { + _liveStreamsSemaphore.Release(); + } } public Task RecordLiveStream(string id, CancellationToken cancellationToken) @@ -999,15 +1009,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV string seriesPath = null; var recordPath = GetRecordingPath(timer, out seriesPath); var recordingStatus = RecordingStatus.New; - var isResourceOpen = false; - SemaphoreSlim semaphore = null; + + LiveStream liveStream = null; try { - var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false); - isResourceOpen = true; - semaphore = result.Item3; - var mediaStreamInfo = result.Item1; + var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); + + var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None).ConfigureAwait(false); + liveStream = liveStreamInfo.Item1; + var mediaStreamInfo = liveStreamInfo.Item1.PublicMediaSource; + var tunerHost = liveStreamInfo.Item2; // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg //await Task.Delay(3000, cancellationToken).ConfigureAwait(false); @@ -1034,13 +1046,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Status = RecordingStatus.InProgress; _timerProvider.AddOrUpdate(timer, false); - result.Item3.Release(); - isResourceOpen = false; - SaveNfo(timer, recordPath, seriesPath); }; - var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration); + var pathWithDuration = tunerHost.ApplyDuration(mediaStreamInfo.Path, duration); // If it supports supplying duration via url if (!string.Equals(pathWithDuration, mediaStreamInfo.Path, StringComparison.OrdinalIgnoreCase)) @@ -1064,19 +1073,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.ErrorException("Error recording to {0}", ex, recordPath); recordingStatus = RecordingStatus.Error; } - finally + + if (liveStream != null) { - if (isResourceOpen && semaphore != null) + try + { + await CloseLiveStream(liveStream.Id, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) { - semaphore.Release(); + _logger.ErrorException("Error closing live stream", ex); } + } - _libraryManager.UnRegisterIgnoredPath(recordPath); - _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); + _libraryManager.UnRegisterIgnoredPath(recordPath); + _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); - ActiveRecordingInfo removed; - _activeRecordings.TryRemove(timer.Id, out removed); - } + ActiveRecordingInfo removed; + _activeRecordings.TryRemove(timer.Id, out removed); if (recordingStatus == RecordingStatus.Completed) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 4e7f637b1..f74a76e3f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -68,18 +68,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { - if (mediaSource.Path.IndexOf("m3u8", StringComparison.OrdinalIgnoreCase) != -1) - { - await RecordWithoutTempFile(mediaSource, targetFile, duration, onStarted, cancellationToken) - .ConfigureAwait(false); - - return; - } + var durationToken = new CancellationTokenSource(duration); + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts"); + await RecordFromFile(mediaSource, mediaSource.Path, targetFile, false, duration, onStarted, cancellationToken).ConfigureAwait(false); - await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) - .ConfigureAwait(false); + _logger.Info("Recording completed to file {0}", targetFile); } private async void DeleteTempFile(string path) @@ -108,76 +102,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private async Task RecordWithoutTempFile(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) - { - var durationToken = new CancellationTokenSource(duration); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - - await RecordFromFile(mediaSource, mediaSource.Path, targetFile, false, duration, onStarted, cancellationToken).ConfigureAwait(false); - - _logger.Info("Recording completed to file {0}", targetFile); - } - - private async Task RecordWithTempFile(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) - { - var httpRequestOptions = new HttpRequestOptions() - { - Url = mediaSource.Path - }; - - httpRequestOptions.BufferContent = false; - - using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false)) - { - _logger.Info("Opened recording stream from tuner provider"); - - Directory.CreateDirectory(Path.GetDirectoryName(tempFile)); - - using (var output = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - //onStarted(); - - _logger.Info("Copying recording stream to file {0}", tempFile); - - var bufferMs = 5000; - - if (mediaSource.RunTimeTicks.HasValue) - { - // The media source already has a fixed duration - // But add another stop 1 minute later just in case the recording gets stuck for any reason - var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1))); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - } - else - { - // The media source if infinite so we need to handle stopping ourselves - var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMilliseconds(bufferMs))); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - } - - var tempFileTask = DirectRecorder.CopyUntilCancelled(response.Content, output, cancellationToken); - - // Give the temp file a little time to build up - await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false); - - var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, true, duration, onStarted, cancellationToken), CancellationToken.None); - - try - { - await tempFileTask.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - - } - - await recordTask.ConfigureAwait(false); - } - } - - _logger.Info("Recording completed to file {0}", targetFile); - } - private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, bool deleteInputFileAfterCompletion, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { _targetPath = targetFile; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 3f6bb140b..6beea352a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -10,6 +10,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Serialization; @@ -18,7 +19,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts { public abstract class BaseTunerHost { - protected readonly IConfigurationManager Config; + protected readonly IServerConfigurationManager Config; protected readonly ILogger Logger; protected IJsonSerializer JsonSerializer; protected readonly IMediaEncoder MediaEncoder; @@ -26,7 +27,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts private readonly ConcurrentDictionary _channelCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - protected BaseTunerHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder) + protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder) { Config = config; Logger = logger; @@ -125,12 +126,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts foreach (var host in hostsWithChannel) { - var resourcePool = GetLock(host.Url); - Logger.Debug("GetChannelStreamMediaSources - Waiting on tuner resource pool"); - - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - Logger.Debug("GetChannelStreamMediaSources - Unlocked resource pool"); - try { // Check to make sure the tuner is available @@ -156,93 +151,63 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts { Logger.Error("Error opening tuner", ex); } - finally - { - resourcePool.Release(); - } } } return new List(); } - protected abstract Task GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken); + protected abstract Task GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken); - public async Task> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) + public async Task GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) { - if (IsValidChannelId(channelId)) + if (!IsValidChannelId(channelId)) { - var hosts = GetTunerHosts(); - - var hostsWithChannel = new List(); + throw new FileNotFoundException(); + } - foreach (var host in hosts) - { - if (string.IsNullOrWhiteSpace(streamId)) - { - try - { - var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false); + var hosts = GetTunerHosts(); - if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase))) - { - hostsWithChannel.Add(host); - } - } - catch (Exception ex) - { - Logger.Error("Error getting channels", ex); - } - } - else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase)) - { - hostsWithChannel = new List {host}; - streamId = streamId.Substring(host.Id.Length); - break; - } - } + var hostsWithChannel = new List(); - foreach (var host in hostsWithChannel) + foreach (var host in hosts) + { + if (string.IsNullOrWhiteSpace(streamId)) { - var resourcePool = GetLock(host.Url); - Logger.Debug("GetChannelStream - Waiting on tuner resource pool"); - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - Logger.Debug("GetChannelStream - Unlocked resource pool"); try { - // Check to make sure the tuner is available - // If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error - // If a streamId is specified then availibility has already been checked in GetChannelStreamMediaSources - if (string.IsNullOrWhiteSpace(streamId) && hostsWithChannel.Count > 1) - { - if (!await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false)) - { - Logger.Error("Tuner is not currently available"); - resourcePool.Release(); - continue; - } - } - - var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); + var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false); - if (EnableMediaProbing) + if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase))) { - await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false); + hostsWithChannel.Add(host); } - - return new Tuple(stream, resourcePool); } catch (Exception ex) { - Logger.Error("Error opening tuner", ex); - - resourcePool.Release(); + Logger.Error("Error getting channels", ex); } } + else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase)) + { + hostsWithChannel = new List { host }; + streamId = streamId.Substring(host.Id.Length); + break; + } } - else + + foreach (var host in hostsWithChannel) { - throw new FileNotFoundException(); + try + { + var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); + await liveStream.Open(cancellationToken).ConfigureAwait(false); + return liveStream; + } + catch (Exception ex) + { + Logger.Error("Error opening tuner", ex); + } } throw new LiveTvConflictException(); @@ -268,37 +233,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts protected abstract Task IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken); - /// - /// The _semaphoreLocks - /// - private readonly ConcurrentDictionary _semaphoreLocks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - /// - /// Gets the lock. - /// - /// The filename. - /// System.Object. - private SemaphoreSlim GetLock(string url) - { - return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1)); - } - - private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + private async Task AddMediaInfo(LiveStream stream, bool isAudio, CancellationToken cancellationToken) { - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + //await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - await AddMediaInfoInternal(mediaSource, isAudio, 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(); + // // Leave the resource locked. it will be released upstream + //} + //catch (Exception) + //{ + // // Release the resource if there's some kind of failure. + // resourcePool.Release(); - throw; - } + // throw; + //} } private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index c5bd648cf..b40b74436 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -14,7 +14,10 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using CommonIO; using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Net; @@ -24,11 +27,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost { private readonly IHttpClient _httpClient; + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationHost _appHost; - public HdHomerunHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient) + public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost) : base(config, logger, jsonSerializer, mediaEncoder) { _httpClient = httpClient; + _fileSystem = fileSystem; + _appHost = appHost; } public string Name @@ -355,6 +362,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun url += "?transcode=" + profile; } + var id = profile; + if (string.IsNullOrWhiteSpace(id)) + { + id = "native"; + } + id += "_" + url.GetMD5().ToString("N"); + var mediaSource = new MediaSourceInfo { Path = url, @@ -387,9 +401,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun RequiresClosing = false, BufferMs = 0, Container = "ts", - Id = profile, - SupportsDirectPlay = true, - SupportsDirectStream = false, + Id = id, + SupportsDirectPlay = false, + SupportsDirectStream = true, SupportsTranscoding = true }; @@ -452,9 +466,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); } - protected override async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) + protected override async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) { - Logger.Info("GetChannelStream: channel id: {0}. stream id: {1}", channelId, streamId ?? string.Empty); + var profile = streamId.Split('_')[0]; + + Logger.Info("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelId, streamId, profile); if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase)) { @@ -462,7 +478,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun } var hdhrId = GetHdHrIdFromChannelId(channelId); - return await GetMediaSource(info, hdhrId, streamId).ConfigureAwait(false); + var mediaSource = await GetMediaSource(info, hdhrId, profile).ConfigureAwait(false); + + var liveStream = new HdHomerunLiveStream(mediaSource, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + 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 new file mode 100644 index 000000000..6078c4a70 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -0,0 +1,156 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using CommonIO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Server.Implementations.LiveTv.EmbyTV; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun +{ + public class HdHomerunLiveStream : LiveStream + { + private readonly ILogger _logger; + private readonly IHttpClient _httpClient; + private readonly IFileSystem _fileSystem; + private readonly IServerApplicationPaths _appPaths; + private readonly IServerApplicationHost _appHost; + + private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); + + public HdHomerunLiveStream(MediaSourceInfo mediaSource, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost) + : base(mediaSource) + { + _fileSystem = fileSystem; + _httpClient = httpClient; + _logger = logger; + _appPaths = appPaths; + _appHost = appHost; + } + + public override async Task Open(CancellationToken openCancellationToken) + { + _liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); + + 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); + + var taskCompletionSource = new TaskCompletionSource(); + + StartStreamingToTempFile(output, tempFile, url, taskCompletionSource, _liveStreamCancellationTokenSource.Token); + + await taskCompletionSource.Task.ConfigureAwait(false); + + PublicMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + Path.GetFileNameWithoutExtension(tempFile) + "/stream.ts"; + + PublicMediaSource.Protocol = MediaProtocol.Http; + } + + public override Task Close() + { + _liveStreamCancellationTokenSource.Cancel(); + + return base.Close(); + } + + private async Task StartStreamingToTempFile(Stream outputStream, string tempFilePath, string url, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) + { + await Task.Run(async () => + { + using (outputStream) + { + var isFirstAttempt = true; + + while (!cancellationToken.IsCancellationRequested) + { + try + { + using (var response = await _httpClient.SendAsync(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + BufferContent = false + + }, "GET").ConfigureAwait(false)) + { + _logger.Info("Opened HDHR stream from {0}", url); + + if (!cancellationToken.IsCancellationRequested) + { + _logger.Info("Beginning DirectRecorder.CopyUntilCancelled"); + + Action onStarted = null; + if (isFirstAttempt) + { + onStarted = () => openTaskCompletionSource.TrySetResult(true); + } + await DirectRecorder.CopyUntilCancelled(response.Content, outputStream, onStarted, cancellationToken).ConfigureAwait(false); + } + } + } + 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); + } + + isFirstAttempt = false; + } + } + + await Task.Delay(5000).ConfigureAwait(false); + + DeleteTempFile(tempFilePath); + + }).ConfigureAwait(false); + } + + private async void DeleteTempFile(string path) + { + 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); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 5c508aacd..d9c0bb8bf 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -13,8 +13,10 @@ using System.Threading; using System.Threading.Tasks; using CommonIO; using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Serialization; +using MediaBrowser.Server.Implementations.LiveTv.EmbyTV; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts { @@ -23,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; - public M3UTunerHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient) + public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient) : base(config, logger, jsonSerializer, mediaEncoder) { _fileSystem = fileSystem; @@ -63,11 +65,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(list); } - protected override async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) + protected override async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) { var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false); - return sources.First(); + var liveStream = new LiveStream(sources.First()); + return liveStream; } public async Task Validate(TunerHostInfo info) @@ -136,7 +139,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts RequiresOpening = false, RequiresClosing = false, - ReadAtNativeFramerate = false + ReadAtNativeFramerate = false, + + Id = channel.Path.GetMD5().ToString("N") }; return new List { mediaSource }; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs index b1e349a86..81deb2995 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs @@ -8,6 +8,7 @@ using CommonIO; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dto; @@ -16,6 +17,7 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; +using MediaBrowser.Server.Implementations.LiveTv.EmbyTV; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp { @@ -24,7 +26,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp private readonly IFileSystem _fileSystem; private readonly IHttpClient _httpClient; - public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient) + public SatIpHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient) : base(config, logger, jsonSerializer, mediaEncoder) { _fileSystem = fileSystem; @@ -113,11 +115,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp return new List(); } - protected override async Task GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken) + protected override async Task GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken) { var sources = await GetChannelStreamMediaSources(tuner, channelId, cancellationToken).ConfigureAwait(false); - return sources.First(); + var liveStream = new LiveStream(sources.First()); + + return liveStream; } protected override async Task IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index eb3da1a12..12691a69b 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -241,6 +241,7 @@ + diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 00279fb05..b57416fab 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -104,6 +104,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -437,15 +443,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -470,9 +467,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index 0844f1f74..bf3d3c303 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -91,6 +91,16 @@ namespace MediaBrowser.XbmcMetadata return; } + if (!item.SupportsLocalMetadata) + { + return; + } + + if (!item.IsSaveLocalMetadataEnabled()) + { + return; + } + try { await _providerManager.SaveMetadata(item, updateReason, new[] { BaseNfoSaver.SaverName }).ConfigureAwait(false); -- cgit v1.2.3 From adb39f40904947917a14e260579098cbc0d9829d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 26 Sep 2016 14:59:18 -0400 Subject: update recording layouts --- MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs | 3 + MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs | 9 + .../MediaInfo/SubtitleResolver.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 201 +++++++++++++++++---- .../LiveTv/EmbyTV/TimerManager.cs | 41 +++-- .../LiveTv/LiveTvDtoService.cs | 2 + MediaBrowser.Server.Mono/app.config | 2 +- .../ApplicationHost.cs | 2 +- 8 files changed, 200 insertions(+), 62 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 12308adda..a828e22e3 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using MediaBrowser.Model.LiveTv; namespace MediaBrowser.Controller.LiveTv { @@ -54,6 +55,7 @@ namespace MediaBrowser.Controller.LiveTv public bool RecordAnyChannel { get; set; } public int KeepUpTo { get; set; } + public KeepUntil KeepUntil { get; set; } public bool SkipEpisodesInLibrary { get; set; } @@ -109,6 +111,7 @@ namespace MediaBrowser.Controller.LiveTv { Days = new List(); SkipEpisodesInLibrary = true; + KeepUntil = KeepUntil.UntilDeleted; } } } diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs index 9d4bab40c..77b915a75 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs @@ -27,6 +27,7 @@ namespace MediaBrowser.Model.LiveTv public bool RecordAnyChannel { get; set; } public int KeepUpTo { get; set; } + public KeepUntil KeepUntil { get; set; } /// /// Gets or sets a value indicating whether [record new only]. @@ -68,4 +69,12 @@ namespace MediaBrowser.Model.LiveTv Days = new List(); } } + + public enum KeepUntil + { + UntilDeleted, + UntilSpaceNeeded, + UntilWatched, + UntilDate + } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index f64b7b792..024171f40 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -117,7 +117,7 @@ namespace MediaBrowser.Providers.MediaInfo { get { - return new[] { ".srt", ".ssa", ".ass", ".sub" }; + return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami" }; } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 6585e92be..7cdb623f3 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -330,11 +330,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id)) { - _timerProvider.Delete(timer); + OnTimerOutOfDate(timer); } } } + private void OnTimerOutOfDate(TimerInfo timer) + { + _timerProvider.Delete(timer); + } + private List _channelCache = null; private async Task> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken) { @@ -424,7 +429,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV foreach (var timer in timers) { - CancelTimerInternal(timer.Id); + CancelTimerInternal(timer.Id, true); } var remove = _seriesTimerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase)); @@ -435,12 +440,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return Task.FromResult(true); } - private void CancelTimerInternal(string timerId) + private void CancelTimerInternal(string timerId, bool isSeriesCancelled) { - var remove = _timerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase)); - if (remove != null) + var timer = _timerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase)); + if (timer != null) { - _timerProvider.Delete(remove); + if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled) + { + _timerProvider.Delete(timer); + } + else + { + timer.Status = RecordingStatus.Cancelled; + _timerProvider.AddOrUpdate(timer, false); + } } ActiveRecordingInfo activeRecordingInfo; @@ -452,7 +465,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public Task CancelTimerAsync(string timerId, CancellationToken cancellationToken) { - CancelTimerInternal(timerId); + CancelTimerInternal(timerId, false); return Task.FromResult(true); } @@ -463,6 +476,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken) { + var existingTimer = _timerProvider.GetAll() + .FirstOrDefault(i => string.Equals(info.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); + + if (existingTimer != null) + { + if (existingTimer.Status == RecordingStatus.Cancelled) + { + existingTimer.Status = RecordingStatus.New; + _timerProvider.Update(existingTimer); + } + else + { + throw new ArgumentException("A scheduled recording already exists for this program."); + } + } + return CreateTimer(info, cancellationToken); } @@ -549,6 +578,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV instance.RecordNewOnly = info.RecordNewOnly; instance.SkipEpisodesInLibrary = info.SkipEpisodesInLibrary; instance.KeepUpTo = info.KeepUpTo; + instance.KeepUntil = info.KeepUntil; instance.StartDate = info.StartDate; _seriesTimerProvider.Update(instance); @@ -569,12 +599,54 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - public Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken) + public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken) { - _timerProvider.Update(info); + var existingTimer = _timerProvider + .GetAll() + .FirstOrDefault(i => string.Equals(i.Id, updatedTimer.Id, StringComparison.OrdinalIgnoreCase)); + + if (existingTimer == null) + { + throw new ResourceNotFoundException(); + } + + // Only update if not currently active + ActiveRecordingInfo activeRecordingInfo; + if (!_activeRecordings.TryGetValue(updatedTimer.Id, out activeRecordingInfo)) + { + UpdateExistingTimerWithNewData(existingTimer, updatedTimer); + + _timerProvider.Update(existingTimer); + } + return Task.FromResult(true); } + private void UpdateExistingTimerWithNewData(TimerInfo existingTimer, TimerInfo updatedTimer) + { + // Update the program info but retain the status + existingTimer.ChannelId = updatedTimer.ChannelId; + existingTimer.CommunityRating = updatedTimer.CommunityRating; + existingTimer.EndDate = updatedTimer.EndDate; + existingTimer.EpisodeNumber = updatedTimer.EpisodeNumber; + existingTimer.EpisodeTitle = updatedTimer.EpisodeTitle; + existingTimer.Genres = updatedTimer.Genres; + existingTimer.HomePageUrl = updatedTimer.HomePageUrl; + existingTimer.IsKids = updatedTimer.IsKids; + existingTimer.IsMovie = updatedTimer.IsMovie; + existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries; + existingTimer.IsSports = updatedTimer.IsSports; + existingTimer.Name = updatedTimer.Name; + existingTimer.OfficialRating = updatedTimer.OfficialRating; + existingTimer.OriginalAirDate = updatedTimer.OriginalAirDate; + existingTimer.Overview = updatedTimer.Overview; + existingTimer.ProductionYear = updatedTimer.ProductionYear; + existingTimer.ProgramId = updatedTimer.ProgramId; + existingTimer.SeasonNumber = updatedTimer.SeasonNumber; + existingTimer.ShortOverview = updatedTimer.ShortOverview; + existingTimer.StartDate = updatedTimer.StartDate; + } + public Task GetChannelImageAsync(string channelId, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -597,7 +669,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public Task> GetTimersAsync(CancellationToken cancellationToken) { - return Task.FromResult((IEnumerable)_timerProvider.GetAll()); + var excludeStatues = new List + { + RecordingStatus.Completed, + RecordingStatus.Cancelled + }; + + var timers = _timerProvider.GetAll() + .Where(i => !excludeStatues.Contains(i.Status)); + + return Task.FromResult(timers); } public Task GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null) @@ -630,6 +711,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV defaults.ProgramId = program.Id; } + defaults.SkipEpisodesInLibrary = true; + defaults.KeepUntil = KeepUntil.UntilDeleted; + return Task.FromResult(defaults); } @@ -860,8 +944,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (recordingEndDate <= DateTime.UtcNow) { - _logger.Warn("Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id); - _timerProvider.Delete(timer); + _logger.Warn("Recording timer fired for updatedTimer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id); + OnTimerOutOfDate(timer); return; } @@ -982,7 +1066,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return Path.Combine(recordPath, recordingFileName); } - private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) + private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, + ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) { if (timer == null) { @@ -1014,9 +1099,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { - var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); + var allMediaSources = + await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); - var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None).ConfigureAwait(false); + var liveStreamInfo = + await + GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) + .ConfigureAwait(false); liveStream = liveStreamInfo.Item1; var mediaStreamInfo = liveStreamInfo.Item1.PublicMediaSource; var tunerHost = liveStreamInfo.Item2; @@ -1036,7 +1125,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var duration = recordingEndDate - DateTime.UtcNow; - _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + _logger.Info("Beginning recording. Will record for {0} minutes.", + duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); _logger.Info("Writing file to path: " + recordPath); _logger.Info("Opening recording stream from tuner provider"); @@ -1058,7 +1148,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV mediaStreamInfo.RunTimeTicks = duration.Ticks; } - await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false); + await + recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken) + .ConfigureAwait(false); recordingStatus = RecordingStatus.Completed; _logger.Info("Recording completed: {0}", recordPath); @@ -1092,14 +1184,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV ActiveRecordingInfo removed; _activeRecordings.TryRemove(timer.Id, out removed); - if (recordingStatus == RecordingStatus.Completed) - { - timer.Status = RecordingStatus.Completed; - _timerProvider.Delete(timer); - - OnSuccessfulRecording(timer, recordPath); - } - else if (DateTime.UtcNow < timer.EndDate) + if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate) { const int retryIntervalSeconds = 60; _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds); @@ -1108,6 +1193,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds); _timerProvider.AddOrUpdate(timer); } + else if (File.Exists(recordPath)) + { + timer.Status = RecordingStatus.Completed; + _timerProvider.AddOrUpdate(timer, false); + OnSuccessfulRecording(timer, recordPath); + } else { _timerProvider.Delete(timer); @@ -1353,41 +1444,78 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return _config.GetConfiguration("livetv"); } + private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer) + { + return seriesTimer.SkipEpisodesInLibrary && IsProgramAlreadyInLibrary(timer); + } + private async Task UpdateTimersForSeriesTimer(List epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers) { - var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList(); + var allTimers = GetTimersForSeries(seriesTimer, epgData) + .ToList(); var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); if (registration.IsValid) { - foreach (var timer in newTimers) + foreach (var timer in allTimers) { - _timerProvider.AddOrUpdate(timer); + var existingTimer = _timerProvider + .GetAll() + .FirstOrDefault(i => string.Equals(i.Id, timer.Id, StringComparison.OrdinalIgnoreCase)); + + if (existingTimer == null) + { + if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) + { + timer.Status = RecordingStatus.Cancelled; + } + _timerProvider.Add(timer); + } + else + { + // Only update if not currently active + ActiveRecordingInfo activeRecordingInfo; + if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo)) + { + UpdateExistingTimerWithNewData(existingTimer, timer); + + if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) + { + existingTimer.Status = RecordingStatus.Cancelled; + } + _timerProvider.Update(existingTimer); + } + } } } if (deleteInvalidTimers) { - var allTimers = GetTimersForSeries(seriesTimer, epgData, false) + var allTimerIds = allTimers .Select(i => i.Id) .ToList(); + var deleteStatuses = new List + { + RecordingStatus.New + }; + var deletes = _timerProvider.GetAll() .Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase)) - .Where(i => !allTimers.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow) + .Where(i => !allTimerIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow) + .Where(i => deleteStatuses.Contains(i.Status)) .ToList(); foreach (var timer in deletes) { - await CancelTimerAsync(timer.Id, CancellationToken.None).ConfigureAwait(false); + CancelTimerInternal(timer.Id, false); } } } private IEnumerable GetTimersForSeries(SeriesTimerInfo seriesTimer, - IEnumerable allPrograms, - bool filterByCurrentRecordings) + IEnumerable allPrograms) { if (seriesTimer == null) { @@ -1403,15 +1531,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); - if (filterByCurrentRecordings && seriesTimer.SkipEpisodesInLibrary) - { - allPrograms = allPrograms.Where(i => !IsProgramAlreadyInLibrary(i)); - } - return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer)); } - private bool IsProgramAlreadyInLibrary(ProgramInfo program) + private bool IsProgramAlreadyInLibrary(TimerInfo program) { if ((program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) || !string.IsNullOrWhiteSpace(program.EpisodeTitle)) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index a7e34a373..28cec34f4 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV foreach (var item in GetAll().ToList()) { - AddTimer(item); + AddOrUpdateSystemTimer(item); } } @@ -55,17 +55,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public override void Update(TimerInfo item) { base.Update(item); - - Timer timer; - if (_timers.TryGetValue(item.Id, out timer)) - { - var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow; - timer.Change(timespan, TimeSpan.Zero); - } - else - { - AddTimer(item); - } + AddOrUpdateSystemTimer(item); } public void AddOrUpdate(TimerInfo item, bool resetTimer) @@ -96,12 +86,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } base.Add(item); - AddTimer(item); + AddOrUpdateSystemTimer(item); } - private void AddTimer(TimerInfo item) + private bool ShouldStartTimer(TimerInfo item) { - if (item.Status == RecordingStatus.Completed) + if (item.Status == RecordingStatus.Completed || + item.Status == RecordingStatus.Cancelled) + { + return false; + } + + return true; + } + + private void AddOrUpdateSystemTimer(TimerInfo item) + { + StopTimer(item); + + if (!ShouldStartTimer(item)) { return; } @@ -115,14 +118,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return; } - var timerLength = startDate - now; - StartTimer(item, timerLength); + var dueTime = startDate - now; + StartTimer(item, dueTime); } - public void StartTimer(TimerInfo item, TimeSpan dueTime) + private void StartTimer(TimerInfo item, TimeSpan dueTime) { - StopTimer(item); - var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero); if (_timers.TryAdd(item.Id, timer)) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 4681d0a7b..462bb2721 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -102,6 +102,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv RecordAnyTime = info.RecordAnyTime, SkipEpisodesInLibrary = info.SkipEpisodesInLibrary, KeepUpTo = info.KeepUpTo, + KeepUntil = info.KeepUntil, RecordNewOnly = info.RecordNewOnly, ExternalChannelId = info.ChannelId, ExternalProgramId = info.ProgramId, @@ -312,6 +313,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv RecordAnyTime = dto.RecordAnyTime, SkipEpisodesInLibrary = dto.SkipEpisodesInLibrary, KeepUpTo = dto.KeepUpTo, + KeepUntil = dto.KeepUntil, RecordNewOnly = dto.RecordNewOnly, ProgramId = dto.ExternalProgramId, ChannelId = dto.ExternalChannelId, diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config index e8c7a9326..e14b908ad 100644 --- a/MediaBrowser.Server.Mono/app.config +++ b/MediaBrowser.Server.Mono/app.config @@ -8,7 +8,7 @@ - + diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 4bd2f6c72..cb3d6a4c9 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -1219,7 +1219,7 @@ namespace MediaBrowser.Server.Startup.Common var apiUrl = GetLocalApiUrl(address); apiUrl += "/system/ping"; - if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 5) + if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 10) { _lastAddressCacheClear = DateTime.UtcNow; _validAddressResults.Clear(); -- cgit v1.2.3 From 64d63c1b4b1f5088cff899541b71c782e26a0a24 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 27 Sep 2016 01:13:56 -0400 Subject: implement keep up to --- MediaBrowser.Controller/LiveTv/TimerInfo.cs | 3 + MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs | 1 + MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs | 1 - .../LiveTv/EmbyTV/EmbyTV.cs | 138 ++++++++++++++++++--- .../LiveTv/EmbyTV/RecordingHelper.cs | 1 + .../LiveTv/EmbyTV/TimerManager.cs | 5 + .../LiveTv/LiveTvDtoService.cs | 2 + 7 files changed, 130 insertions(+), 21 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index ea21ba46e..42c3480ce 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -9,6 +9,7 @@ namespace MediaBrowser.Controller.LiveTv public TimerInfo() { Genres = new List(); + KeepUntil = KeepUntil.UntilDeleted; } /// @@ -109,5 +110,7 @@ namespace MediaBrowser.Controller.LiveTv public string ShortOverview { get; set; } public string OfficialRating { get; set; } public List Genres { get; set; } + public string RecordingPath { get; set; } + public KeepUntil KeepUntil { get; set; } } } diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs index 4d863c6eb..0ceed70c0 100644 --- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs @@ -105,5 +105,6 @@ namespace MediaBrowser.Model.LiveTv /// /// true if this instance is post padding required; otherwise, false. public bool IsPostPaddingRequired { get; set; } + public KeepUntil KeepUntil { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs index 77b915a75..ce65cc7bf 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs @@ -27,7 +27,6 @@ namespace MediaBrowser.Model.LiveTv public bool RecordAnyChannel { get; set; } public int KeepUpTo { get; set; } - public KeepUntil KeepUntil { get; set; } /// /// Gets or sets a value indicating whether [record new only]. diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7cdb623f3..6ea3fbc5e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -442,7 +442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private void CancelTimerInternal(string timerId, bool isSeriesCancelled) { - var timer = _timerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase)); + var timer = _timerProvider.GetTimer(timerId); if (timer != null) { if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled) @@ -474,10 +474,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return Task.FromResult(true); } + public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken) { var existingTimer = _timerProvider.GetAll() - .FirstOrDefault(i => string.Equals(info.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); if (existingTimer != null) { @@ -485,6 +495,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { existingTimer.Status = RecordingStatus.New; _timerProvider.Update(existingTimer); + return Task.FromResult(existingTimer.Id); } else { @@ -492,16 +503,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - return CreateTimer(info, cancellationToken); - } - - public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) - { - return CreateSeriesTimer(info, cancellationToken); - } - - public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken) - { timer.Id = Guid.NewGuid().ToString("N"); ProgramInfo programInfo = null; @@ -601,9 +602,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken) { - var existingTimer = _timerProvider - .GetAll() - .FirstOrDefault(i => string.Equals(i.Id, updatedTimer.Id, StringComparison.OrdinalIgnoreCase)); + var existingTimer = _timerProvider.GetTimer(updatedTimer.Id); if (existingTimer == null) { @@ -1137,6 +1136,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _timerProvider.AddOrUpdate(timer, false); SaveNfo(timer, recordPath, seriesPath); + EnforceKeepUpTo(timer); }; var pathWithDuration = tunerHost.ApplyDuration(mediaStreamInfo.Path, duration); @@ -1148,8 +1148,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV mediaStreamInfo.RunTimeTicks = duration.Ticks; } - await - recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken) + await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken) .ConfigureAwait(false); recordingStatus = RecordingStatus.Completed; @@ -1195,6 +1194,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } else if (File.Exists(recordPath)) { + timer.RecordingPath = recordPath; timer.Status = RecordingStatus.Completed; _timerProvider.AddOrUpdate(timer, false); OnSuccessfulRecording(timer, recordPath); @@ -1205,6 +1205,102 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } + private async void EnforceKeepUpTo(TimerInfo timer) + { + if (string.IsNullOrWhiteSpace(timer.SeriesTimerId)) + { + return; + } + + var seriesTimerId = timer.SeriesTimerId; + var seriesTimer = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, seriesTimerId, StringComparison.OrdinalIgnoreCase)); + + if (seriesTimer == null || seriesTimer.KeepUpTo <= 1) + { + return; + } + + if (_disposed) + { + return; + } + + await _recordingDeleteSemaphore.WaitAsync().ConfigureAwait(false); + + try + { + if (_disposed) + { + return; + } + + var timersToDelete = _timerProvider.GetAll() + .Where(i => i.Status == RecordingStatus.Completed && !string.IsNullOrWhiteSpace(i.RecordingPath)) + .Where(i => string.Equals(i.SeriesTimerId, seriesTimerId, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(i => i.EndDate) + .Where(i => File.Exists(i.RecordingPath)) + .Skip(seriesTimer.KeepUpTo - 1) + .ToList(); + + await DeleteLibraryItemsForTimers(timersToDelete).ConfigureAwait(false); + } + finally + { + _recordingDeleteSemaphore.Release(); + } + } + + private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1,1); + private async Task DeleteLibraryItemsForTimers(List timers) + { + foreach (var timer in timers) + { + if (_disposed) + { + return; + } + + try + { + await DeleteLibraryItemForTimer(timer).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error deleting recording", ex); + } + } + } + + private async Task DeleteLibraryItemForTimer(TimerInfo timer) + { + var libraryItem = _libraryManager.FindByPath(timer.RecordingPath, false); + + if (libraryItem != null) + { + await _libraryManager.DeleteItem(libraryItem, new DeleteOptions + { + DeleteFileLocation = true + }); + } + else + { + try + { + File.Delete(timer.RecordingPath); + } + catch (DirectoryNotFoundException) + { + + } + catch (FileNotFoundException) + { + + } + } + + _timerProvider.Delete(timer); + } + private string EnsureFileUnique(string path, string timerId) { var originalPath = path; @@ -1460,9 +1556,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { foreach (var timer in allTimers) { - var existingTimer = _timerProvider - .GetAll() - .FirstOrDefault(i => string.Equals(i.Id, timer.Id, StringComparison.OrdinalIgnoreCase)); + var existingTimer = _timerProvider.GetTimer(timer.Id); if (existingTimer == null) { @@ -1484,6 +1578,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { existingTimer.Status = RecordingStatus.Cancelled; } + + existingTimer.SeriesTimerId = seriesTimer.Id; _timerProvider.Update(existingTimer); } } @@ -1649,8 +1745,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return channelIds.SelectMany(GetEpgDataForChannel).ToList(); } + private bool _disposed; public void Dispose() { + _disposed = true; foreach (var pair in _activeRecordings.ToList()) { pair.Value.CancellationTokenSource.Cancel(); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 67356da2f..f9c04abc5 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.PostPaddingSeconds = series.PostPaddingSeconds; timer.IsPostPaddingRequired = series.IsPostPaddingRequired; timer.IsPrePaddingRequired = series.IsPrePaddingRequired; + timer.KeepUntil = series.KeepUntil; timer.Priority = series.Priority; timer.Name = parent.Name; timer.Overview = parent.Overview; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 28cec34f4..bddce0420 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -156,5 +156,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV EventHelper.FireEventIfNotNull(TimerFired, this, new GenericEventArgs { Argument = timer }, Logger); } } + + public TimerInfo GetTimer(string id) + { + return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase)); + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 462bb2721..348bcd97b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -52,6 +52,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv PostPaddingSeconds = info.PostPaddingSeconds, IsPostPaddingRequired = info.IsPostPaddingRequired, IsPrePaddingRequired = info.IsPrePaddingRequired, + KeepUntil = info.KeepUntil, ExternalChannelId = info.ChannelId, ExternalSeriesTimerId = info.SeriesTimerId, ServiceName = service.Name, @@ -247,6 +248,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv PostPaddingSeconds = dto.PostPaddingSeconds, IsPostPaddingRequired = dto.IsPostPaddingRequired, IsPrePaddingRequired = dto.IsPrePaddingRequired, + KeepUntil = dto.KeepUntil, Priority = dto.Priority, SeriesTimerId = dto.ExternalSeriesTimerId, ProgramId = dto.ExternalProgramId, -- cgit v1.2.3 From 68268cfb73d83c21df939a7992f3e4dcee9234f5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 27 Sep 2016 13:51:01 -0400 Subject: update closing of live streams --- .../Configuration/ServerConfiguration.cs | 1 + .../Library/LibraryManager.cs | 18 +++++++++++++++++- .../LiveTv/EmbyTV/EmbyTV.cs | 1 + .../LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 5 +++-- 4 files changed, 22 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index e45aa58c5..1d2928f67 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -73,6 +73,7 @@ namespace MediaBrowser.Model.Configuration /// /// The metadata path. public string MetadataPath { get; set; } + public string MetadataNetworkPath { get; set; } public string LastVersion { get; set; } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index b076996df..a3930f36a 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2550,9 +2550,25 @@ namespace MediaBrowser.Server.Implementations.Library } } + var metadataPath = ConfigurationManager.Configuration.MetadataPath; + var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath; + + if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath)) + { + var metadataSubstitutionResult = SubstitutePathInternal(path, metadataPath, metadataNetworkPath); + if (metadataSubstitutionResult.Item2) + { + return metadataSubstitutionResult.Item1; + } + } + foreach (var map in ConfigurationManager.Configuration.PathSubstitutions) { - path = SubstitutePath(path, map.From, map.To); + var substitutionResult = SubstitutePathInternal(path, map.From, map.To); + if (substitutionResult.Item2) + { + return substitutionResult.Item1; + } } return path; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 6ea3fbc5e..f53ec3ee8 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -908,6 +908,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { await stream.Close().ConfigureAwait(false); + _logger.Info("Live stream {0} closed successfully", id); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs index 6078c4a70..d3540d180 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -22,6 +22,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IServerApplicationHost _appHost; private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource(); + private readonly TaskCompletionSource _liveStreamTaskCompletionSource = new TaskCompletionSource(); public HdHomerunLiveStream(MediaSourceInfo mediaSource, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost) : base(mediaSource) @@ -62,7 +63,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { _liveStreamCancellationTokenSource.Cancel(); - return base.Close(); + return _liveStreamTaskCompletionSource.Task; } private async Task StartStreamingToTempFile(Stream outputStream, string tempFilePath, string url, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) @@ -120,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - await Task.Delay(5000).ConfigureAwait(false); + _liveStreamTaskCompletionSource.TrySetResult(true); DeleteTempFile(tempFilePath); -- cgit v1.2.3 From f5d37ed659d88bfa00483deddbbf93c33c2e10d6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 28 Sep 2016 01:11:41 -0400 Subject: add guide settings --- .../Channels/RefreshChannelsScheduledTask.cs | 2 +- MediaBrowser.Server.Implementations/Library/LibraryManager.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 005bbb852..5ac3da3db 100644 --- a/MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Channels public string Category { - get { return "Channels"; } + get { return "Internet Channels"; } } public async Task Execute(System.Threading.CancellationToken cancellationToken, IProgress progress) diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index a3930f36a..f6fb158ae 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -2882,12 +2882,12 @@ namespace MediaBrowser.Server.Implementations.Library { var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); - SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); - var list = libraryOptions.PathInfos.ToList(); list.Add(pathInfo); libraryOptions.PathInfos = list.ToArray(); + SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); + CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); } } -- cgit v1.2.3 From 76c7bfcb6795771cb06ef354fbf76d6e39de8948 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 29 Sep 2016 08:55:49 -0400 Subject: update closing of streams --- MediaBrowser.Api/ApiEntryPoint.cs | 8 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 46 ++++++- MediaBrowser.Api/Playback/BaseStreamingService.cs | 16 +-- MediaBrowser.Api/Playback/MediaInfoService.cs | 2 +- .../Progressive/BaseProgressiveStreamingService.cs | 2 - MediaBrowser.Api/Playback/StreamState.cs | 2 +- .../Entities/InternalItemsQuery.cs | 2 + .../Library/IMediaSourceManager.cs | 3 +- .../Library/IMediaSourceProvider.cs | 3 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 3 +- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 5 +- MediaBrowser.Controller/LiveTv/LiveStream.cs | 23 +++- MediaBrowser.Controller/LiveTv/LiveTvChannel.cs | 53 +++++++- MediaBrowser.Controller/LiveTv/TimerInfo.cs | 1 + MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs | 2 +- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 7 +- MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs | 24 ++++ MediaBrowser.Model/LiveTv/ProgramQuery.cs | 7 + .../LiveTv/RecommendedProgramQuery.cs | 6 + MediaBrowser.Model/LiveTv/RecordingQuery.cs | 1 + .../Channels/ChannelDynamicMediaSourceProvider.cs | 2 +- .../Library/MediaSourceManager.cs | 59 +++++---- .../LiveTv/EmbyTV/DirectRecorder.cs | 16 +-- .../LiveTv/EmbyTV/EmbyTV.cs | 143 +++++++++++++-------- .../LiveTv/EmbyTV/EncodedRecorder.cs | 39 +----- .../LiveTv/EmbyTV/RecordingHelper.cs | 1 + .../LiveTv/LiveTvManager.cs | 90 ++++++++----- .../LiveTv/LiveTvMediaSourceProvider.cs | 4 +- .../LiveTv/TunerHosts/BaseTunerHost.cs | 4 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 8 -- .../TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 7 +- .../LiveTv/TunerHosts/M3UTunerHost.cs | 5 - .../Persistence/SqliteItemRepository.cs | 113 +++++++++++++--- .../Session/SessionManager.cs | 2 +- .../Sync/SyncedMediaSourceProvider.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 90 +------------ 36 files changed, 475 insertions(+), 326 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 2f5b9e1e0..c6e45c61a 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -567,7 +567,7 @@ namespace MediaBrowser.Api { try { - await _mediaSourceManager.CloseLiveStream(job.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false); } catch (Exception ex) { @@ -789,12 +789,12 @@ namespace MediaBrowser.Api { if (KillTimer == null) { - Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + //Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); KillTimer = new Timer(callback, this, intervalMs, Timeout.Infinite); } else { - Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); KillTimer.Change(intervalMs, Timeout.Infinite); } } @@ -813,7 +813,7 @@ namespace MediaBrowser.Api { var intervalMs = PingTimeout; - Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); + //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); KillTimer.Change(intervalMs, Timeout.Infinite); } } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index a5f8fce6e..9c1105082 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -48,6 +48,21 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? StartIndex { get; set; } + [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsMovie { get; set; } + + [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSeries { get; set; } + + [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsNews { get; set; } + + [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsKids { get; set; } + + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSports { get; set; } + /// /// The maximum number of items to return /// @@ -163,6 +178,7 @@ namespace MediaBrowser.Api.LiveTv public bool? IsSeries { get; set; } public bool? IsKids { get; set; } public bool? IsSports { get; set; } + public bool? IsNews { get; set; } public GetRecordings() { @@ -309,6 +325,12 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsMovie { get; set; } + [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSeries { get; set; } + + [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsNews { get; set; } + [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsKids { get; set; } @@ -380,15 +402,21 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? HasAired { get; set; } - [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSports { get; set; } + [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSeries { get; set; } - [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsMovie { get; set; } - [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsNews { get; set; } + + [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsKids { get; set; } + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsSports { get; set; } + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableImages { get; set; } @@ -815,6 +843,11 @@ namespace MediaBrowser.Api.LiveTv IsLiked = request.IsLiked, IsDisliked = request.IsDisliked, EnableFavoriteSorting = request.EnableFavoriteSorting, + IsMovie = request.IsMovie, + IsSeries = request.IsSeries, + IsNews = request.IsNews, + IsKids = request.IsKids, + IsSports = request.IsSports, AddCurrentProgram = request.AddCurrentProgram }, CancellationToken.None).ConfigureAwait(false); @@ -897,7 +930,9 @@ namespace MediaBrowser.Api.LiveTv query.Limit = request.Limit; query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); query.SortOrder = request.SortOrder; + query.IsNews = request.IsNews; query.IsMovie = request.IsMovie; + query.IsSeries = request.IsSeries; query.IsKids = request.IsKids; query.IsSports = request.IsSports; query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); @@ -915,8 +950,10 @@ namespace MediaBrowser.Api.LiveTv IsAiring = request.IsAiring, Limit = request.Limit, HasAired = request.HasAired, + IsSeries = request.IsSeries, IsMovie = request.IsMovie, IsKids = request.IsKids, + IsNews = request.IsNews, IsSports = request.IsSports, EnableTotalRecordCount = request.EnableTotalRecordCount }; @@ -948,6 +985,7 @@ namespace MediaBrowser.Api.LiveTv IsInProgress = request.IsInProgress, EnableTotalRecordCount = request.EnableTotalRecordCount, IsMovie = request.IsMovie, + IsNews = request.IsNews, IsSeries = request.IsSeries, IsKids = request.IsKids, IsSports = request.IsSports diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index fe9869664..8e57650b4 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1861,14 +1861,14 @@ namespace MediaBrowser.Api.Playback MediaSourceInfo mediaSource = null; if (string.IsNullOrWhiteSpace(request.LiveStreamId)) { - //TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? - // ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) - // : null; - - //if (currentJob != null) - //{ - // mediaSource = currentJob.MediaSource; - //} + TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? + ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) + : null; + + if (currentJob != null) + { + mediaSource = currentJob.MediaSource; + } if (mediaSource == null) { diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 656b2ee08..761538c83 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback public void Post(CloseMediaSource request) { - var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None); + var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId); Task.WaitAll(task); } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index a68319109..4bb62f47f 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -27,12 +27,10 @@ namespace MediaBrowser.Api.Playback.Progressive public abstract class BaseProgressiveStreamingService : BaseStreamingService { protected readonly IImageProcessor ImageProcessor; - protected readonly IHttpClient HttpClient; protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer) { ImageProcessor = imageProcessor; - HttpClient = httpClient; } /// diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index ef0282abc..019f378c5 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -225,7 +225,7 @@ namespace MediaBrowser.Api.Playback { try { - await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index deea63112..d917b7d6d 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -101,6 +101,8 @@ namespace MediaBrowser.Controller.Entities public bool? IsMovie { get; set; } public bool? IsSports { get; set; } public bool? IsKids { get; set; } + public bool? IsNews { get; set; } + public bool? IsSeries { get; set; } public int? MinPlayers { get; set; } public int? MaxPlayers { get; set; } diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 1df77cdc9..c06470c5e 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -92,8 +92,7 @@ namespace MediaBrowser.Controller.Library /// Closes the media source. /// /// The live stream identifier. - /// The cancellation token. /// Task. - Task CloseLiveStream(string id, CancellationToken cancellationToken); + Task CloseLiveStream(string id); } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs index 5b033af4a..56366e5a8 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs @@ -28,8 +28,7 @@ namespace MediaBrowser.Controller.Library /// Closes the media source. /// /// The live stream identifier. - /// The cancellation token. /// Task. - Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken); + Task CloseMediaSource(string liveStreamId); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 41c5dbdbb..d65d1ae30 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -220,9 +220,8 @@ namespace MediaBrowser.Controller.LiveTv /// Closes the live stream. /// /// The identifier. - /// The cancellation token. /// Task. - Task CloseLiveStream(string id, CancellationToken cancellationToken); + Task CloseLiveStream(string id); /// /// Gets the guide information. diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 3c8b964a2..89d035649 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -22,9 +22,8 @@ namespace MediaBrowser.Controller.LiveTv /// /// Gets the channels. /// - /// The cancellation token. /// Task<IEnumerable<ChannelInfo>>. - Task> GetChannels(CancellationToken cancellationToken); + Task> GetChannels(bool enableCache, CancellationToken cancellationToken); /// /// Gets the tuner infos. /// @@ -46,8 +45,6 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// Task<List<MediaSourceInfo>>. Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); - - string ApplyDuration(string streamPath, TimeSpan duration); } public interface IConfigurableTunerHost { diff --git a/MediaBrowser.Controller/LiveTv/LiveStream.cs b/MediaBrowser.Controller/LiveTv/LiveStream.cs index 15d09d857..1bb198632 100644 --- a/MediaBrowser.Controller/LiveTv/LiveStream.cs +++ b/MediaBrowser.Controller/LiveTv/LiveStream.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Dto; @@ -7,17 +8,27 @@ namespace MediaBrowser.Controller.LiveTv public class LiveStream { public MediaSourceInfo OriginalMediaSource { get; set; } - public MediaSourceInfo PublicMediaSource { get; set; } - public string Id { get; set; } + public MediaSourceInfo OpenedMediaSource { get; set; } + public DateTime DateOpened { get; set; } + public int ConsumerCount { get; set; } + public ITunerHost TunerHost { get; set; } + public string OriginalStreamId { get; set; } public LiveStream(MediaSourceInfo mediaSource) { OriginalMediaSource = mediaSource; - PublicMediaSource = mediaSource; - Id = mediaSource.Id; + OpenedMediaSource = mediaSource; } - public virtual Task Open(CancellationToken cancellationToken) + public async Task Open(CancellationToken cancellationToken) + { + await OpenInternal(cancellationToken).ConfigureAwait(false); + DateOpened = DateTime.UtcNow; + + OpenedMediaSource.DateLiveStreamOpened = DateOpened; + } + + protected virtual Task OpenInternal(CancellationToken cancellationToken) { return Task.FromResult(true); } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 50aeed27d..69a1c24ea 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -9,7 +9,7 @@ using System.Runtime.Serialization; namespace MediaBrowser.Controller.LiveTv { - public class LiveTvChannel : BaseItem, IHasMediaSources + public class LiveTvChannel : BaseItem, IHasMediaSources, IHasProgramAttributes { public override List GetUserDataKeys() { @@ -137,5 +137,56 @@ namespace MediaBrowser.Controller.LiveTv { return false; } + + [IgnoreDataMember] + public bool IsMovie { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// true if this instance is sports; otherwise, false. + [IgnoreDataMember] + public bool IsSports { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is series. + /// + /// true if this instance is series; otherwise, false. + [IgnoreDataMember] + public bool IsSeries { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is live. + /// + /// true if this instance is live; otherwise, false. + [IgnoreDataMember] + public bool IsLive { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is news. + /// + /// true if this instance is news; otherwise, false. + [IgnoreDataMember] + public bool IsNews { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is kids. + /// + /// true if this instance is kids; otherwise, false. + [IgnoreDataMember] + public bool IsKids { get; set; } + + [IgnoreDataMember] + public bool IsPremiere { get; set; } + + [IgnoreDataMember] + public bool IsRepeat { get; set; } + + /// + /// Gets or sets the episode title. + /// + /// The episode title. + [IgnoreDataMember] + public string EpisodeTitle { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 42c3480ce..978e9e1a8 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -101,6 +101,7 @@ namespace MediaBrowser.Controller.LiveTv public bool IsMovie { get; set; } public bool IsKids { get; set; } public bool IsSports { get; set; } + public bool IsNews { get; set; } public int? ProductionYear { get; set; } public string EpisodeTitle { get; set; } public DateTime? OriginalAirDate { get; set; } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index 490a51128..d3738d903 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { try { - await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index bb07d9cb6..2be69c8e4 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Entities; +using System; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; using System.Collections.Generic; @@ -52,7 +53,9 @@ namespace MediaBrowser.Model.Dto public string TranscodingUrl { get; set; } public string TranscodingSubProtocol { get; set; } public string TranscodingContainer { get; set; } - + + public DateTime? DateLiveStreamOpened { get; set; } + public MediaSourceInfo() { Formats = new List(); diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index 0ece1e4a0..f76368a7b 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -61,6 +61,30 @@ namespace MediaBrowser.Model.LiveTv public bool AddCurrentProgram { get; set; } public bool EnableUserData { get; set; } + /// + /// Used to specific whether to return news or not + /// + /// If set to null, all programs will be returned + public bool? IsNews { get; set; } + + /// + /// Used to specific whether to return movies or not + /// + /// If set to null, all programs will be returned + public bool? IsMovie { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is kids. + /// + /// null if [is kids] contains no value, true if [is kids]; otherwise, false. + public bool? IsKids { get; set; } + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// null if [is sports] contains no value, true if [is sports]; otherwise, false. + public bool? IsSports { get; set; } + public bool? IsSeries { get; set; } + public LiveTvChannelQuery() { EnableUserData = true; diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs index bf459237a..7886342e7 100644 --- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs @@ -62,6 +62,12 @@ namespace MediaBrowser.Model.LiveTv /// public DateTime? MaxEndDate { get; set; } + /// + /// Used to specific whether to return news or not + /// + /// If set to null, all programs will be returned + public bool? IsNews { get; set; } + /// /// Used to specific whether to return movies or not /// @@ -83,6 +89,7 @@ namespace MediaBrowser.Model.LiveTv /// Skips over a given number of items within the results. Use for paging. /// public int? StartIndex { get; set; } + public bool? IsSeries { get; set; } /// /// Gets or sets a value indicating whether this instance has aired. diff --git a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs index 0e6d081a1..4bc506bf6 100644 --- a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs @@ -45,11 +45,17 @@ namespace MediaBrowser.Model.LiveTv /// The limit. public int? Limit { get; set; } + /// + /// Gets or sets a value indicating whether this instance is movie. + /// + /// null if [is movie] contains no value, true if [is movie]; otherwise, false. + public bool? IsNews { get; set; } /// /// Gets or sets a value indicating whether this instance is movie. /// /// null if [is movie] contains no value, true if [is movie]; otherwise, false. public bool? IsMovie { get; set; } + public bool? IsSeries { get; set; } /// /// Gets or sets a value indicating whether this instance is kids. /// diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index 0ba5f1779..265aad335 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -68,6 +68,7 @@ namespace MediaBrowser.Model.LiveTv /// The fields. public ItemFields[] Fields { get; set; } public bool? EnableImages { get; set; } + public bool? IsNews { get; set; } public bool? IsMovie { get; set; } public bool? IsSeries { get; set; } public bool? IsKids { get; set; } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs index 3239b20b2..6cba1b441 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Channels throw new NotImplementedException(); } - public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId) { throw new NotImplementedException(); } diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index c20245a6e..c7650102f 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -355,7 +355,7 @@ namespace MediaBrowser.Server.Implementations.Library .ToList(); } - private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _openStreams = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); public async Task OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken) @@ -383,7 +383,7 @@ namespace MediaBrowser.Server.Implementations.Library Id = mediaSource.LiveStreamId, MediaSource = mediaSource }; - _openStreams.AddOrUpdate(mediaSource.LiveStreamId, info, (key, i) => info); + _openStreams[mediaSource.LiveStreamId] = info; if (enableAutoClose) { @@ -421,7 +421,7 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("id"); } - _logger.Debug("Getting live stream {0}", id); + _logger.Debug("Getting already opened live stream {0}", id); await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -465,17 +465,16 @@ namespace MediaBrowser.Server.Implementations.Library } } - private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId, CancellationToken cancellationToken) + private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId) { _logger.Info("Closing live stream {0} with provider {1}", streamId, provider.GetType().Name); try { - await provider.CloseMediaSource(streamId, cancellationToken).ConfigureAwait(false); + await provider.CloseMediaSource(streamId).ConfigureAwait(false); } catch (NotImplementedException) { - } catch (Exception ex) { @@ -483,37 +482,35 @@ namespace MediaBrowser.Server.Implementations.Library } } - public async Task CloseLiveStream(string id, CancellationToken cancellationToken) + public async Task CloseLiveStream(string id) { if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentNullException("id"); } - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false); try { LiveStreamInfo current; + if (_openStreams.TryGetValue(id, out current)) { + _openStreams.Remove(id); + current.Closed = true; + if (current.MediaSource.RequiresClosing) { var tuple = GetProvider(id); - await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); + await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2).ConfigureAwait(false); } - } - LiveStreamInfo removed; - if (_openStreams.TryRemove(id, out removed)) - { - removed.Closed = true; - } - - if (_openStreams.Count == 0) - { - StopCloseTimer(); + if (_openStreams.Count == 0) + { + StopCloseTimer(); + } } } finally @@ -565,10 +562,20 @@ namespace MediaBrowser.Server.Implementations.Library private async void CloseTimerCallback(object state) { - var infos = _openStreams - .Values - .Where(i => i.EnableCloseTimer && DateTime.UtcNow - i.Date > _openStreamMaxAge) - .ToList(); + List infos; + await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false); + + try + { + infos = _openStreams + .Values + .Where(i => i.EnableCloseTimer && DateTime.UtcNow - i.Date > _openStreamMaxAge) + .ToList(); + } + finally + { + _liveStreamSemaphore.Release(); + } foreach (var info in infos) { @@ -576,7 +583,7 @@ namespace MediaBrowser.Server.Implementations.Library { try { - await CloseLiveStream(info.Id, CancellationToken.None).ConfigureAwait(false); + await CloseLiveStream(info.Id).ConfigureAwait(false); } catch (Exception ex) { @@ -608,12 +615,10 @@ namespace MediaBrowser.Server.Implementations.Library { foreach (var key in _openStreams.Keys.ToList()) { - var task = CloseLiveStream(key, CancellationToken.None); + var task = CloseLiveStream(key); Task.WaitAll(task); } - - _openStreams.Clear(); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 0d043669a..0f8c15e71 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -47,19 +47,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Info("Copying recording stream to file {0}", targetFile); - if (mediaSource.RunTimeTicks.HasValue) - { - // The media source already has a fixed duration - // But add another stop 1 minute later just in case the recording gets stuck for any reason - var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1))); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - } - else - { - // The media source if infinite so we need to handle stopping ourselves - var durationToken = new CancellationTokenSource(duration); - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - } + // The media source if infinite so we need to handle stopping ourselves + var durationToken = new CancellationTokenSource(duration); + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index f53ec3ee8..ef19dcbc9 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -340,22 +340,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _timerProvider.Delete(timer); } - private List _channelCache = null; private async Task> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken) { - if (enableCache && _channelCache != null) - { - - return _channelCache.ToList(); - } - var list = new List(); foreach (var hostInstance in _liveTvManager.TunerHosts) { try { - var channels = await hostInstance.GetChannels(cancellationToken).ConfigureAwait(false); + var channels = await hostInstance.GetChannels(enableCache, cancellationToken).ConfigureAwait(false); list.AddRange(channels); } @@ -388,7 +381,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - _channelCache = list.ToList(); return list; } @@ -400,7 +392,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { try { - var channels = await hostInstance.GetChannels(cancellationToken).ConfigureAwait(false); + var channels = await hostInstance.GetChannels(false, cancellationToken).ConfigureAwait(false); list.AddRange(channels); } @@ -632,6 +624,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV existingTimer.Genres = updatedTimer.Genres; existingTimer.HomePageUrl = updatedTimer.HomePageUrl; existingTimer.IsKids = updatedTimer.IsKids; + existingTimer.IsNews = updatedTimer.IsNews; existingTimer.IsMovie = updatedTimer.IsMovie; existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries; existingTimer.IsSports = updatedTimer.IsSports; @@ -836,33 +829,68 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { var result = await GetChannelStreamInternal(channelId, streamId, cancellationToken).ConfigureAwait(false); - return result.Item1.PublicMediaSource; + return result.Item2; + } + + private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, int consumerId) + { + var json = _jsonSerializer.SerializeToString(mediaSource); + mediaSource = _jsonSerializer.DeserializeFromString(json); + + mediaSource.Id = consumerId.ToString(CultureInfo.InvariantCulture) + "_" + mediaSource.Id; + + return mediaSource; } - private async Task> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) + private async Task> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) { _logger.Info("Streaming Channel " + channelId); - foreach (var hostInstance in _liveTvManager.TunerHosts) + await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + var result = _liveStreams.Values.FirstOrDefault(i => string.Equals(i.OriginalStreamId, streamId, StringComparison.OrdinalIgnoreCase)); + + if (result != null) { - try - { - var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); + //result.ConsumerCount++; - await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false); - _liveStreams[result.Id] = result; - _liveStreamsSemaphore.Release(); + //_logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount); - return new Tuple(result, hostInstance); - } - catch (FileNotFoundException) - { - } - catch (Exception e) + //_liveStreamsSemaphore.Release(); + //return new Tuple(result, CloneMediaSource(result.OpenedMediaSource, result.ConsumerCount - 1), result.TunerHost); + } + + try + { + foreach (var hostInstance in _liveTvManager.TunerHosts) { - _logger.ErrorException("Error getting channel stream", e); + try + { + result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); + + _liveStreams[result.OpenedMediaSource.Id] = result; + + result.ConsumerCount++; + result.TunerHost = hostInstance; + result.OriginalStreamId = streamId; + + _logger.Info("Returning mediasource streamId {0}, mediaSource.Id {1}, mediaSource.LiveStreamId {2}", + streamId, result.OpenedMediaSource.Id, result.OpenedMediaSource.LiveStreamId); + + return new Tuple(result, CloneMediaSource(result.OpenedMediaSource, 0), hostInstance); + } + catch (FileNotFoundException) + { + } + catch (OperationCanceledException) + { + } } } + finally + { + _liveStreamsSemaphore.Release(); + } throw new ApplicationException("Tuner not found."); } @@ -896,25 +924,41 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public async Task CloseLiveStream(string id, CancellationToken cancellationToken) { - await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false); + // Ignore the consumer id + id = id.Substring(id.IndexOf('_') + 1); + + await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { LiveStream stream; if (_liveStreams.TryGetValue(id, out stream)) { - _liveStreams.Remove(id); + stream.ConsumerCount--; - try + _logger.Info("Live stream {0} consumer count is now {1}", id, stream.ConsumerCount); + + if (stream.ConsumerCount <= 0) { + _liveStreams.Remove(id); + + _logger.Info("Closing live stream {0}", id); + await stream.Close().ConfigureAwait(false); _logger.Info("Live stream {0} closed successfully", id); } - catch (Exception ex) - { - _logger.ErrorException("Error closing live stream", ex); - } } + else + { + _logger.Warn("Live stream not found: {0}, unable to close", id); + } + } + catch (OperationCanceledException) + { + } + catch (Exception ex) + { + _logger.ErrorException("Error closing live stream", ex); } finally { @@ -1095,20 +1139,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var recordPath = GetRecordingPath(timer, out seriesPath); var recordingStatus = RecordingStatus.New; - LiveStream liveStream = null; + string liveStreamId = null; try { var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); - var liveStreamInfo = - await - GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) + var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) .ConfigureAwait(false); - liveStream = liveStreamInfo.Item1; - var mediaStreamInfo = liveStreamInfo.Item1.PublicMediaSource; - var tunerHost = liveStreamInfo.Item2; + + var mediaStreamInfo = liveStreamInfo.Item2; + liveStreamId = mediaStreamInfo.Id; // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg //await Task.Delay(3000, cancellationToken).ConfigureAwait(false); @@ -1140,15 +1182,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV EnforceKeepUpTo(timer); }; - var pathWithDuration = tunerHost.ApplyDuration(mediaStreamInfo.Path, duration); - - // If it supports supplying duration via url - if (!string.Equals(pathWithDuration, mediaStreamInfo.Path, StringComparison.OrdinalIgnoreCase)) - { - mediaStreamInfo.Path = pathWithDuration; - mediaStreamInfo.RunTimeTicks = duration.Ticks; - } - await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken) .ConfigureAwait(false); @@ -1166,11 +1199,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordingStatus = RecordingStatus.Error; } - if (liveStream != null) + if (!string.IsNullOrWhiteSpace(liveStreamId)) { try { - await CloseLiveStream(liveStream.Id, CancellationToken.None).ConfigureAwait(false); + await CloseLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -1251,7 +1284,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1,1); + private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1); private async Task DeleteLibraryItemsForTimers(List timers) { foreach (var timer in timers) @@ -1295,7 +1328,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } catch (FileNotFoundException) { - + } } @@ -1492,6 +1525,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { AddGenre(timer.Genres, "Kids"); } + if (timer.IsNews) + { + AddGenre(timer.Genres, "News"); + } foreach (var genre in timer.Genres) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index f74a76e3f..ce5f14f4b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -71,38 +71,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var durationToken = new CancellationTokenSource(duration); cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - await RecordFromFile(mediaSource, mediaSource.Path, targetFile, false, duration, onStarted, cancellationToken).ConfigureAwait(false); + await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false); _logger.Info("Recording completed to file {0}", targetFile); } - private async void DeleteTempFile(string path) - { - 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 recording temp file", ex); - } - - await Task.Delay(1000).ConfigureAwait(false); - } - } - - private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, bool deleteInputFileAfterCompletion, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { _targetPath = targetFile; _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile)); @@ -143,7 +117,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length); - process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile, deleteInputFileAfterCompletion); + process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile); process.Start(); @@ -252,7 +226,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV /// /// Processes the exited. /// - private void OnFfMpegProcessExited(Process process, string inputFile, bool deleteInputFileAfterCompletion) + private void OnFfMpegProcessExited(Process process, string inputFile) { _hasExited = true; @@ -278,11 +252,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Error("FFMpeg recording exited with an error for {0}.", _targetPath); _taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed", _targetPath))); } - - if (deleteInputFileAfterCompletion) - { - DeleteTempFile(inputFile); - } } private void DisposeLogStream() diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index f9c04abc5..bb6935e8e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -42,6 +42,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timerInfo.EpisodeNumber = programInfo.EpisodeNumber; timerInfo.IsMovie = programInfo.IsMovie; timerInfo.IsKids = programInfo.IsKids; + timerInfo.IsNews = programInfo.IsNews; timerInfo.IsSports = programInfo.IsSports; timerInfo.ProductionYear = programInfo.ProductionYear; timerInfo.EpisodeTitle = programInfo.EpisodeTitle; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 8a4572813..7c61d2396 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -62,9 +62,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly List _services = new List(); - private readonly ConcurrentDictionary _openStreams = - new ConcurrentDictionary(); - private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1); private readonly List _tunerHosts = new List(); @@ -153,6 +150,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channels = _libraryManager.GetItemList(new InternalItemsQuery { + IsMovie = query.IsMovie, + IsNews = query.IsNews, + IsKids = query.IsKids, + IsSports = query.IsSports, + IsSeries = query.IsSeries, IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }, SortBy = new[] { ItemSortBy.SortName }, TopParentIds = new[] { topFolder.Id.ToString("N") } @@ -407,15 +409,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); Normalize(info, service, isVideo); - var data = new LiveStreamData - { - Info = info, - IsChannel = isChannel, - ItemId = id - }; - - _openStreams.AddOrUpdate(info.Id, data, (key, i) => data); - return info; } catch (Exception ex) @@ -937,8 +930,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv MaxStartDate = query.MaxStartDate, ChannelIds = query.ChannelIds, IsMovie = query.IsMovie, + IsSeries = query.IsSeries, IsSports = query.IsSports, IsKids = query.IsKids, + IsNews = query.IsNews, Genres = query.Genres, StartIndex = query.StartIndex, Limit = query.Limit, @@ -985,7 +980,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv { IncludeItemTypes = new[] { typeof(LiveTvProgram).Name }, IsAiring = query.IsAiring, + IsNews = query.IsNews, IsMovie = query.IsMovie, + IsSeries = query.IsSeries, IsSports = query.IsSports, IsKids = query.IsKids, EnableTotalRecordCount = query.EnableTotalRecordCount, @@ -1014,7 +1011,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var programList = programs.ToList(); - var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false); + var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false) || (query.IsNews ?? false) || (query.IsSeries ?? false); programs = programList.OrderBy(i => i.StartDate.Date) .ThenByDescending(i => GetRecommendationScore(i, user.Id, factorChannelWatchCount)) @@ -1305,6 +1302,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv var start = DateTime.UtcNow.AddHours(-1); var end = start.AddDays(guideDays); + var isMovie = false; + var isSports = false; + var isNews = false; + var isKids = false; + var iSSeries = false; + var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false); foreach (var program in channelPrograms) @@ -1312,7 +1315,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv var programItem = await GetProgram(program, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false); programs.Add(programItem.Id); + + if (program.IsMovie) + { + isMovie = true; + } + + if (program.IsSeries) + { + iSSeries = true; + } + + if (program.IsSports) + { + isSports = true; + } + + if (program.IsNews) + { + isNews = true; + } + + if (program.IsKids) + { + isKids = true; + } } + + currentChannel.IsMovie = isMovie; + currentChannel.IsNews = isNews; + currentChannel.IsSports = isSports; + currentChannel.IsKids = isKids; + currentChannel.IsSeries = iSSeries; + + await currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -1647,6 +1683,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv recordings = recordings.Where(i => i.IsMovie == val); } + if (query.IsNews.HasValue) + { + var val = query.IsNews.Value; + recordings = recordings.Where(i => i.IsNews == val); + } + if (query.IsSeries.HasValue) { var val = query.IsSeries.Value; @@ -2444,9 +2486,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv internal bool IsChannel; } - public async Task CloseLiveStream(string id, CancellationToken cancellationToken) + public async Task CloseLiveStream(string id) { - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false); try { @@ -2461,12 +2503,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv id = parts[1]; - LiveStreamData data; - _openStreams.TryRemove(id, out data); - _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); - await service.CloseLiveStream(id, cancellationToken).ConfigureAwait(false); + await service.CloseLiveStream(id, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { @@ -2500,7 +2539,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv Dispose(true); } - private readonly object _disposeLock = new object(); private bool _isDisposed = false; /// /// Releases unmanaged and - optionally - managed resources. @@ -2511,18 +2549,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (dispose) { _isDisposed = true; - - lock (_disposeLock) - { - foreach (var stream in _openStreams.Values.ToList()) - { - var task = CloseLiveStream(stream.Info.Id, CancellationToken.None); - - Task.WaitAll(task); - } - - _openStreams.Clear(); - } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index cdba1873e..aacc0c22b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -204,9 +204,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } - public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId) { - return _liveTvManager.CloseLiveStream(liveStreamId, cancellationToken); + return _liveTvManager.CloseLiveStream(liveStreamId); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 6beea352a..a4236763f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -73,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts .ToList(); } - public async Task> GetChannels(CancellationToken cancellationToken) + public async Task> GetChannels(bool enableCache, CancellationToken cancellationToken) { var list = new List(); @@ -83,7 +83,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts { try { - var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false); + var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false); var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))).ToList(); list.AddRange(newChannels); diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index b40b74436..9f71940e1 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -67,14 +67,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun return id; } - public string ApplyDuration(string streamPath, TimeSpan duration) - { - streamPath += streamPath.IndexOf('?') == -1 ? "?" : "&"; - streamPath += "duration=" + Convert.ToInt32(duration.TotalSeconds).ToString(CultureInfo.InvariantCulture); - - return streamPath; - } - private async Task> GetLineup(TunerHostInfo info, CancellationToken cancellationToken) { var options = new HttpRequestOptions diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs index d3540d180..d6574db22 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun _appHost = appHost; } - public override async Task Open(CancellationToken openCancellationToken) + protected override async Task OpenInternal(CancellationToken openCancellationToken) { _liveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested(); @@ -54,13 +54,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun await taskCompletionSource.Task.ConfigureAwait(false); - PublicMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + Path.GetFileNameWithoutExtension(tempFile) + "/stream.ts"; + OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + Path.GetFileNameWithoutExtension(tempFile) + "/stream.ts"; - PublicMediaSource.Protocol = MediaProtocol.Http; + OpenedMediaSource.Protocol = MediaProtocol.Http; } public override Task Close() { + _logger.Info("Closing HDHR live stream"); _liveStreamCancellationTokenSource.Cancel(); return _liveStreamTaskCompletionSource.Task; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index d9c0bb8bf..e5d6102b1 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -153,10 +153,5 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts { return Task.FromResult(true); } - - public string ApplyDuration(string streamPath, TimeSpan duration) - { - return streamPath; - } } } diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 05c282687..097118418 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -2530,38 +2530,111 @@ namespace MediaBrowser.Server.Implementations.Persistence whereClauses.Add("IsOffline=@IsOffline"); cmd.Parameters.Add(cmd, "@IsOffline", DbType.Boolean).Value = query.IsOffline; } - if (query.IsMovie.HasValue) + + var exclusiveProgramAttribtues = !(query.IsMovie ?? true) || + !(query.IsSports ?? true) || + !(query.IsKids ?? true) || + !(query.IsNews ?? true) || + !(query.IsSeries ?? true); + + if (exclusiveProgramAttribtues) { - var alternateTypes = new List(); - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) + if (query.IsMovie.HasValue) + { + var alternateTypes = new List(); + if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) + { + alternateTypes.Add(typeof(Movie).FullName); + } + if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) + { + alternateTypes.Add(typeof(Trailer).FullName); + } + + if (alternateTypes.Count == 0) + { + whereClauses.Add("IsMovie=@IsMovie"); + cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie; + } + else + { + whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); + cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie; + } + } + if (query.IsSeries.HasValue) { - alternateTypes.Add(typeof(Movie).FullName); + whereClauses.Add("IsSeries=@IsSeries"); + cmd.Parameters.Add(cmd, "@IsSeries", DbType.Boolean).Value = query.IsSeries; } - if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) + if (query.IsNews.HasValue) { - alternateTypes.Add(typeof(Trailer).FullName); + whereClauses.Add("IsNews=@IsNews"); + cmd.Parameters.Add(cmd, "@IsNews", DbType.Boolean).Value = query.IsNews; } - - if (alternateTypes.Count == 0) + if (query.IsKids.HasValue) { - whereClauses.Add("IsMovie=@IsMovie"); + whereClauses.Add("IsKids=@IsKids"); + cmd.Parameters.Add(cmd, "@IsKids", DbType.Boolean).Value = query.IsKids; } - else + if (query.IsSports.HasValue) { - whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)"); + whereClauses.Add("IsSports=@IsSports"); + cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports; } - cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie; } - if (query.IsKids.HasValue) - { - whereClauses.Add("IsKids=@IsKids"); - cmd.Parameters.Add(cmd, "@IsKids", DbType.Boolean).Value = query.IsKids; - } - if (query.IsSports.HasValue) + else { - whereClauses.Add("IsSports=@IsSports"); - cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports; + var programAttribtues = new List(); + if (query.IsMovie ?? false) + { + var alternateTypes = new List(); + if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name)) + { + alternateTypes.Add(typeof(Movie).FullName); + } + if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name)) + { + alternateTypes.Add(typeof(Trailer).FullName); + } + + if (alternateTypes.Count == 0) + { + programAttribtues.Add("IsMovie=@IsMovie"); + } + else + { + programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)"); + } + + cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = true; + } + if (query.IsSports ?? false) + { + programAttribtues.Add("IsSports=@IsSports"); + cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = true; + } + if (query.IsNews ?? false) + { + programAttribtues.Add("IsNews=@IsNews"); + cmd.Parameters.Add(cmd, "@IsNews", DbType.Boolean).Value = true; + } + if (query.IsSeries ?? false) + { + programAttribtues.Add("IsSeries=@IsSeries"); + cmd.Parameters.Add(cmd, "@IsSeries", DbType.Boolean).Value = true; + } + if (query.IsKids ?? false) + { + programAttribtues.Add("IsKids=@IsKids"); + cmd.Parameters.Add(cmd, "@IsKids", DbType.Boolean).Value = true; + } + if (programAttribtues.Count > 0) + { + whereClauses.Add("("+string.Join(" OR ", programAttribtues.ToArray())+")"); + } } + if (query.IsFolder.HasValue) { whereClauses.Add("IsFolder=@IsFolder"); diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 48f48cdcc..5857e9dbc 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -818,7 +818,7 @@ namespace MediaBrowser.Server.Implementations.Session { try { - await _mediaSourceManager.CloseLiveStream(info.LiveStreamId, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(info.LiveStreamId).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index a2b5851ac..cb666b6ac 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -150,7 +150,7 @@ namespace MediaBrowser.Server.Implementations.Sync } } - public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId) { throw new NotImplementedException(); } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 2f8b34879..4850b6fe0 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -289,7 +289,7 @@ namespace MediaBrowser.WebDashboard.Api return list; } - private List> GetDeployIgnoreFilenames() + private List> GetDeployIgnoreFilenames() { var list = new List>(); @@ -313,8 +313,11 @@ namespace MediaBrowser.WebDashboard.Api public async Task Get(GetDashboardPackage request) { - var path = Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, - "webclient-dump"); + var mode = request.Mode; + + var path = string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? + Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump") + : "C:\\dev\\emby-web-mobile\\src"; try { @@ -333,8 +336,6 @@ namespace MediaBrowser.WebDashboard.Api var appVersion = _appHost.ApplicationVersion.ToString(); - var mode = request.Mode; - // Try to trim the output size a bit var bowerPath = Path.Combine(path, "bower_components"); @@ -372,11 +373,6 @@ namespace MediaBrowser.WebDashboard.Api // Delete things that are unneeded in an attempt to keep the output as trim as possible _fileSystem.DeleteDirectory(Path.Combine(path, "css", "images", "tour"), true); } - else - { - MinifyCssDirectory(path); - MinifyJsDirectory(path); - } await DumpHtml(creator.DashboardUIPath, path, mode, culture, appVersion); @@ -444,78 +440,6 @@ namespace MediaBrowser.WebDashboard.Api } } - private void MinifyCssDirectory(string path) - { - foreach (var file in Directory.GetFiles(path, "*.css", SearchOption.AllDirectories)) - { - if (file.IndexOf(".min.", StringComparison.OrdinalIgnoreCase) != -1) - { - continue; - } - if (file.IndexOf("bower_", StringComparison.OrdinalIgnoreCase) != -1) - { - continue; - } - - try - { - var text = _fileSystem.ReadAllText(file, Encoding.UTF8); - - var result = new KristensenCssMinifier().Minify(text, false, Encoding.UTF8); - - if (result.Errors.Count > 0) - { - Logger.Error("Error minifying css: " + result.Errors[0].Message); - } - else - { - text = result.MinifiedContent; - _fileSystem.WriteAllText(file, text, Encoding.UTF8); - } - } - catch (Exception ex) - { - Logger.ErrorException("Error minifying css", ex); - } - } - } - - private void MinifyJsDirectory(string path) - { - foreach (var file in Directory.GetFiles(path, "*.js", SearchOption.AllDirectories)) - { - if (file.IndexOf(".min.", StringComparison.OrdinalIgnoreCase) != -1) - { - continue; - } - if (file.IndexOf("bower_", StringComparison.OrdinalIgnoreCase) != -1) - { - continue; - } - - try - { - var text = _fileSystem.ReadAllText(file, Encoding.UTF8); - - var result = new CrockfordJsMinifier().Minify(text, false, Encoding.UTF8); - - if (result.Errors.Count > 0) - { - Logger.Error("Error minifying javascript: " + result.Errors[0].Message); - } - else - { - text = result.MinifiedContent; - _fileSystem.WriteAllText(file, text, Encoding.UTF8); - } - } - catch (Exception ex) - { - Logger.ErrorException("Error minifying css", ex); - } - } - } - private async Task DumpHtml(string source, string destination, string mode, string culture, string appVersion) { foreach (var file in Directory.GetFiles(source, "*", SearchOption.TopDirectoryOnly)) @@ -528,7 +452,7 @@ namespace MediaBrowser.WebDashboard.Api private async Task DumpFile(string resourceVirtualPath, string destinationFilePath, string mode, string culture, string appVersion) { - using (var stream = await GetPackageCreator().GetResource(resourceVirtualPath, mode, culture, appVersion, true).ConfigureAwait(false)) + using (var stream = await GetPackageCreator().GetResource(resourceVirtualPath, mode, culture, appVersion, false).ConfigureAwait(false)) { using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) { -- cgit v1.2.3 From fdbcccc65f5bd684b3a838f6cd835667391d9004 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 29 Sep 2016 22:21:24 -0400 Subject: add infinite property --- .../Playback/Progressive/BaseProgressiveStreamingService.cs | 13 +++++++++++++ .../Playback/Progressive/ProgressiveStreamWriter.cs | 7 ++++++- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 2 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 3 ++- 4 files changed, 22 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 4bb62f47f..4adf6fbca 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -170,6 +170,19 @@ namespace MediaBrowser.Api.Playback.Progressive using (state) { + if (state.MediaSource.IsInfiniteStream) + { + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + outputHeaders["Content-Type"] = contentType; + + var streamSource = new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); + } + TimeSpan? cacheDuration = null; if (!string.IsNullOrEmpty(request.Tag)) diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 80b5e357d..f601f4aa3 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Api.Playback.Progressive private const int BufferSize = 81920; private long _bytesWritten = 0; - + public long StartPosition { get; set; } public bool AllowEndOfFile = true; public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) @@ -52,6 +52,11 @@ namespace MediaBrowser.Api.Playback.Progressive using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) { + if (StartPosition > 0) + { + fs.Position = StartPosition; + } + while (eofCount < 15 || !AllowEndOfFile) { var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 2be69c8e4..1c7875890 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.Model.Dto public bool SupportsTranscoding { get; set; } public bool SupportsDirectStream { get; set; } public bool SupportsDirectPlay { get; set; } - + public bool IsInfiniteStream { get; set; } public bool RequiresOpening { get; set; } public string OpenToken { get; set; } public bool RequiresClosing { get; set; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 9f71940e1..68450105e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -396,7 +396,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun Id = id, SupportsDirectPlay = false, SupportsDirectStream = true, - SupportsTranscoding = true + SupportsTranscoding = true, + IsInfiniteStream = true }; return mediaSource; -- cgit v1.2.3 From dcfda6d7779d8ee75eb2baa5f7cb798b2b5261c8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 29 Sep 2016 22:21:59 -0400 Subject: add infinite property --- MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index e5d6102b1..b03feefe4 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -141,7 +141,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts ReadAtNativeFramerate = false, - Id = channel.Path.GetMD5().ToString("N") + Id = channel.Path.GetMD5().ToString("N"), + IsInfiniteStream = true }; return new List { mediaSource }; -- cgit v1.2.3 From 6a7fabc3bd4ab8496a90bd04e9decf48bf16edc2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 30 Sep 2016 02:50:06 -0400 Subject: add new guide settings --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 52 +++++++++- MediaBrowser.Api/Playback/BaseStreamingService.cs | 1 + .../Entities/InternalItemsQuery.cs | 3 + MediaBrowser.Controller/LiveTv/LiveStream.cs | 2 + MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 + MediaBrowser.Model/Dlna/StreamInfo.cs | 17 +++- MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs | 12 ++- MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 1 - .../LiveTv/EmbyTV/EmbyTV.cs | 38 ++++--- .../LiveTv/EmbyTV/EncodedRecorder.cs | 25 ++++- .../LiveTv/LiveTvManager.cs | 109 +++------------------ .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 70 ++++++++++--- .../TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 9 +- .../Persistence/SqliteItemRepository.cs | 81 ++++++++------- 14 files changed, 256 insertions(+), 166 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 9c1105082..5b821df0b 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -104,6 +104,26 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableUserData { get; set; } + public string SortBy { get; set; } + + public SortOrder? SortOrder { get; set; } + + /// + /// Gets the order by. + /// + /// IEnumerable{ItemSortBy}. + public string[] GetOrderBy() + { + var val = SortBy; + + if (string.IsNullOrEmpty(val)) + { + return new string[] { }; + } + + return val.Split(','); + } + public GetChannels() { AddCurrentProgram = true; @@ -650,6 +670,8 @@ namespace MediaBrowser.Api.LiveTv { public string Id { get; set; } public string Container { get; set; } + public long T { get; set; } + public long S { get; set; } } public class LiveTvService : BaseApiService @@ -681,9 +703,35 @@ namespace MediaBrowser.Api.LiveTv outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath); + long startPosition = 0; + + if (request.T > 0) + { + var now = DateTime.UtcNow; + + var totalTicks = now.Ticks - request.S; + + if (totalTicks > 0) + { + double requestedOffset = request.T; + requestedOffset = Math.Max(0, requestedOffset - TimeSpan.FromSeconds(10).Ticks); + + var pct = requestedOffset / totalTicks; + + Logger.Info("Live stream offset pct {0}", pct); + + var bytes = new FileInfo(filePath).Length; + Logger.Info("Live stream total bytes {0}", bytes); + startPosition = Convert.ToInt64(pct * bytes); + } + } + + Logger.Info("Live stream starting byte position {0}", startPosition); + var streamSource = new ProgressiveFileCopier(_fileSystem, filePath, outputHeaders, null, Logger, CancellationToken.None) { - AllowEndOfFile = false + AllowEndOfFile = false, + StartPosition = startPosition }; return ResultFactory.GetAsyncStreamWriter(streamSource); @@ -848,6 +896,8 @@ namespace MediaBrowser.Api.LiveTv IsNews = request.IsNews, IsKids = request.IsKids, IsSports = request.IsSports, + SortBy = request.GetOrderBy(), + SortOrder = request.SortOrder ?? SortOrder.Ascending, AddCurrentProgram = request.AddCurrentProgram }, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 8e57650b4..fb4bd9244 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -2602,6 +2602,7 @@ namespace MediaBrowser.Api.Playback inputModifier += " " + GetFastSeekCommandLineParameter(state.Request); inputModifier = inputModifier.Trim(); + //inputModifier += " -fflags +genpts+ignidx+igndts"; if (state.VideoRequest != null && genPts) { inputModifier += " -fflags +genpts"; diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index d917b7d6d..5e70cd587 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -151,6 +151,8 @@ namespace MediaBrowser.Controller.Entities public Dictionary ExcludeProviderIds { get; set; } public bool EnableGroupByMetadataKey { get; set; } + public List> OrderBy { get; set; } + public InternalItemsQuery() { GroupByPresentationUniqueKey = true; @@ -193,6 +195,7 @@ namespace MediaBrowser.Controller.Entities TrailerTypes = new TrailerType[] { }; AirDays = new DayOfWeek[] { }; SeriesStatuses = new SeriesStatus[] { }; + OrderBy = new List>(); } public InternalItemsQuery(User user) diff --git a/MediaBrowser.Controller/LiveTv/LiveStream.cs b/MediaBrowser.Controller/LiveTv/LiveStream.cs index 1bb198632..7d44fbd90 100644 --- a/MediaBrowser.Controller/LiveTv/LiveStream.cs +++ b/MediaBrowser.Controller/LiveTv/LiveStream.cs @@ -13,11 +13,13 @@ namespace MediaBrowser.Controller.LiveTv public int ConsumerCount { get; set; } public ITunerHost TunerHost { get; set; } public string OriginalStreamId { get; set; } + public bool EnableStreamSharing { get; set; } public LiveStream(MediaSourceInfo mediaSource) { OriginalMediaSource = mediaSource; OpenedMediaSource = mediaSource; + EnableStreamSharing = true; } public async Task Open(CancellationToken cancellationToken) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index d3131eb5a..44103e720 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -235,6 +235,8 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ResourceNotFoundException("ffprobe not found"); } + path = newPaths.Item1; + if (!ValidateVersion(path)) { throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index ac024f00b..f7dc893c8 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -215,13 +215,26 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty)); list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty)); - if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls")) + var forceStartPosition = false; + long startPositionTicks = item.StartPositionTicks; + //if (item.MediaSource.DateLiveStreamOpened.HasValue && startPositionTicks == 0) + //{ + // var elapsed = DateTime.UtcNow - item.MediaSource.DateLiveStreamOpened.Value; + // elapsed -= TimeSpan.FromSeconds(20); + // if (elapsed.TotalSeconds >= 0) + // { + // startPositionTicks = elapsed.Ticks + startPositionTicks; + // forceStartPosition = true; + // } + //} + + if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls") && !forceStartPosition) { list.Add(new NameValuePair("StartTimeTicks", string.Empty)); } else { - list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks))); + list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(startPositionTicks))); } list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty)); diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index f76368a7b..4505b80a0 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -1,4 +1,5 @@ - +using MediaBrowser.Model.Entities; + namespace MediaBrowser.Model.LiveTv { /// @@ -85,9 +86,18 @@ namespace MediaBrowser.Model.LiveTv public bool? IsSports { get; set; } public bool? IsSeries { get; set; } + public string[] SortBy { get; set; } + + /// + /// The sort order to return results with + /// + /// The sort order. + public SortOrder? SortOrder { get; set; } + public LiveTvChannelQuery() { EnableUserData = true; + SortBy = new string[] { }; } } } diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index ee7dd8b98..a6240afe6 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -60,7 +60,6 @@ namespace MediaBrowser.Model.LiveTv public TunerHostInfo() { IsEnabled = true; - AllowHWTranscoding = true; } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index ef19dcbc9..3db764ae1 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -483,7 +483,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (existingTimer != null) { - if (existingTimer.Status == RecordingStatus.Cancelled) + if (existingTimer.Status == RecordingStatus.Cancelled || + existingTimer.Status == RecordingStatus.Completed) { existingTimer.Status = RecordingStatus.New; _timerProvider.Update(existingTimer); @@ -832,12 +833,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return result.Item2; } - private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, int consumerId) + private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, int consumerId, bool enableStreamSharing) { var json = _jsonSerializer.SerializeToString(mediaSource); mediaSource = _jsonSerializer.DeserializeFromString(json); - mediaSource.Id = consumerId.ToString(CultureInfo.InvariantCulture) + "_" + mediaSource.Id; + mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id; + + 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; } @@ -850,14 +858,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var result = _liveStreams.Values.FirstOrDefault(i => string.Equals(i.OriginalStreamId, streamId, StringComparison.OrdinalIgnoreCase)); - if (result != null) + if (result != null && result.EnableStreamSharing) { - //result.ConsumerCount++; + result.ConsumerCount++; - //_logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount); + _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount); - //_liveStreamsSemaphore.Release(); - //return new Tuple(result, CloneMediaSource(result.OpenedMediaSource, result.ConsumerCount - 1), result.TunerHost); + var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.ConsumerCount - 1, result.EnableStreamSharing); + _liveStreamsSemaphore.Release(); + return new Tuple(result, openedMediaSource, result.TunerHost); } try @@ -868,16 +877,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); - _liveStreams[result.OpenedMediaSource.Id] = result; + var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, 0, result.EnableStreamSharing); + + _liveStreams[openedMediaSource.Id] = result; result.ConsumerCount++; result.TunerHost = hostInstance; result.OriginalStreamId = streamId; _logger.Info("Returning mediasource streamId {0}, mediaSource.Id {1}, mediaSource.LiveStreamId {2}", - streamId, result.OpenedMediaSource.Id, result.OpenedMediaSource.LiveStreamId); + streamId, openedMediaSource.Id, openedMediaSource.LiveStreamId); - return new Tuple(result, CloneMediaSource(result.OpenedMediaSource, 0), hostInstance); + return new Tuple(result, openedMediaSource, hostInstance); } catch (FileNotFoundException) { @@ -925,7 +936,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public async Task CloseLiveStream(string id, CancellationToken cancellationToken) { // Ignore the consumer id - id = id.Substring(id.IndexOf('_') + 1); + //id = id.Substring(id.IndexOf('_') + 1); await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -1143,8 +1154,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { - var allMediaSources = - await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); + var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) .ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index ce5f14f4b..65e653551 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -141,7 +141,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { var maxBitrate = 25000000; videoArgs = string.Format( - "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41", + "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41 -tune zerolatency", GetOutputSizeParam(), maxBitrate.ToString(CultureInfo.InvariantCulture)); } @@ -151,16 +151,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); - var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; + var inputModifiers = "-fflags +genpts -async 1 -vsync -1"; + var commandLineArgs = "-i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; + + long startTimeTicks = 0; + //if (mediaSource.DateLiveStreamOpened.HasValue) + //{ + // var elapsed = DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value; + // elapsed -= TimeSpan.FromSeconds(10); + // if (elapsed.TotalSeconds >= 0) + // { + // startTimeTicks = elapsed.Ticks + startTimeTicks; + // } + //} if (mediaSource.ReadAtNativeFramerate) { - commandLineArgs = "-re " + commandLineArgs; + inputModifiers += " -re"; + } + + if (startTimeTicks > 0) + { + inputModifiers = "-ss " + _mediaEncoder.GetTimeParameter(startTimeTicks) + " " + inputModifiers; } commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), durationParam); - return commandLineArgs; + return inputModifiers + " " + commandLineArgs; } private string GetAudioArgs(MediaSourceInfo mediaSource) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 7c61d2396..f736307f0 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false); - var channels = _libraryManager.GetItemList(new InternalItemsQuery + var internalQuery = new InternalItemsQuery(user) { IsMovie = query.IsMovie, IsNews = query.IsNews, @@ -156,109 +156,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv IsSports = query.IsSports, IsSeries = query.IsSeries, IncludeItemTypes = new[] { typeof(LiveTvChannel).Name }, - SortBy = new[] { ItemSortBy.SortName }, - TopParentIds = new[] { topFolder.Id.ToString("N") } + SortOrder = query.SortOrder ?? SortOrder.Ascending, + TopParentIds = new[] { topFolder.Id.ToString("N") }, + IsFavorite = query.IsFavorite, + IsLiked = query.IsLiked, + StartIndex = query.StartIndex, + Limit = query.Limit + }; - }).Cast(); + internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple(i, query.SortOrder ?? SortOrder.Ascending))); - if (user != null) + if (query.EnableFavoriteSorting) { - // Avoid implicitly captured closure - var currentUser = user; - - channels = channels - .Where(i => i.IsVisible(currentUser)) - .OrderBy(i => - { - double number = 0; - - if (!string.IsNullOrEmpty(i.Number)) - { - double.TryParse(i.Number, out number); - } - - return number; - - }); - - if (query.IsFavorite.HasValue) - { - var val = query.IsFavorite.Value; - - channels = channels - .Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val); - } - - if (query.IsLiked.HasValue) - { - var val = query.IsLiked.Value; - - channels = channels - .Where(i => - { - var likes = _userDataManager.GetUserData(user, i).Likes; - - return likes.HasValue && likes.Value == val; - }); - } - - if (query.IsDisliked.HasValue) - { - var val = query.IsDisliked.Value; - - channels = channels - .Where(i => - { - var likes = _userDataManager.GetUserData(user, i).Likes; - - return likes.HasValue && likes.Value != val; - }); - } + internalQuery.OrderBy.Insert(0, new Tuple(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); } - var enableFavoriteSorting = query.EnableFavoriteSorting; - - channels = channels.OrderBy(i => - { - if (enableFavoriteSorting) - { - var userData = _userDataManager.GetUserData(user, i); - - if (userData.IsFavorite) - { - return 0; - } - if (userData.Likes.HasValue) - { - if (!userData.Likes.Value) - { - return 3; - } - - return 1; - } - } - - return 2; - }); - - var allChannels = channels.ToList(); - IEnumerable allEnumerable = allChannels; - - if (query.StartIndex.HasValue) + if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase))) { - allEnumerable = allEnumerable.Skip(query.StartIndex.Value); + internalQuery.OrderBy.Add(new Tuple(ItemSortBy.SortName, SortOrder.Ascending)); } - if (query.Limit.HasValue) - { - allEnumerable = allEnumerable.Take(query.Limit.Value); - } + var channelResult = _libraryManager.GetItemsResult(internalQuery); var result = new QueryResult { - Items = allEnumerable.ToArray(), - TotalRecordCount = allChannels.Count + Items = channelResult.Items.Cast().ToArray(), + TotalRecordCount = channelResult.TotalRecordCount }; return result; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 68450105e..14ae45228 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -104,8 +104,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun }); } + private Dictionary _modelCache = new Dictionary(); private async Task GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken) { + lock (_modelCache) + { + DiscoverResponse response; + if (_modelCache.TryGetValue(info.Url, out response)) + { + return response.ModelNumber; + } + } + try { using (var stream = await _httpClient.Get(new HttpRequestOptions() @@ -119,6 +129,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var response = JsonSerializer.DeserializeFromStream(stream); + lock (_modelCache) + { + _modelCache[info.Id] = response; + } + return response.ModelNumber; } } @@ -126,8 +141,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound) { + var defaultValue = "HDHR"; // HDHR4 doesn't have this api - return "HDHR"; + lock (_modelCache) + { + _modelCache[info.Id] = new DiscoverResponse + { + ModelNumber = defaultValue + }; + } + return defaultValue; } throw; @@ -427,18 +450,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun try { - string model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false); - model = model ?? string.Empty; - - if (info.AllowHWTranscoding && (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)) + if (info.AllowHWTranscoding) { - list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false)); + string model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false); + model = model ?? string.Empty; + + if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)) + { + list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false)); - list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false)); + list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false)); + list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false)); + list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false)); + list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false)); + list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false)); + } } } catch @@ -474,6 +500,23 @@ 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) + { + 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; } @@ -484,6 +527,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun return; } + lock (_modelCache) + { + _modelCache.Clear(); + } + try { // Test it by pulling down the lineup diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs index d6574db22..57722881d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -52,11 +52,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun StartStreamingToTempFile(output, tempFile, url, taskCompletionSource, _liveStreamCancellationTokenSource.Token); - await taskCompletionSource.Task.ConfigureAwait(false); + //OpenedMediaSource.Protocol = MediaProtocol.File; + //OpenedMediaSource.Path = tempFile; + //OpenedMediaSource.ReadAtNativeFramerate = true; OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + Path.GetFileNameWithoutExtension(tempFile) + "/stream.ts"; - OpenedMediaSource.Protocol = MediaProtocol.Http; + + await taskCompletionSource.Task.ConfigureAwait(false); + + //await Task.Delay(5000).ConfigureAwait(false); } public override Task Close() diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 097118418..672cd1bc2 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -1730,28 +1730,28 @@ namespace MediaBrowser.Server.Implementations.Persistence return true; } - if (query.SortBy != null && query.SortBy.Length > 0) + var sortingFields = query.SortBy.ToList(); + sortingFields.AddRange(query.OrderBy.Select(i => i.Item1)); + + if (sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)) { - if (query.SortBy.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - if (query.SortBy.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - if (query.SortBy.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - if (query.SortBy.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase)) - { - return true; - } + return true; + } + if (sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase)) + { + return true; + } + if (sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase)) + { + return true; + } + if (sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase)) + { + return true; + } + if (sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase)) + { + return true; } if (query.IsFavoriteOrLiked.HasValue) @@ -2151,34 +2151,41 @@ namespace MediaBrowser.Server.Implementations.Persistence private string GetOrderByText(InternalItemsQuery query) { + var orderBy = query.OrderBy.ToList(); + var enableOrderInversion = true; + + if (orderBy.Count == 0) + { + orderBy.AddRange(query.SortBy.Select(i => new Tuple(i, query.SortOrder))); + } + else + { + enableOrderInversion = false; + } + if (query.SimilarTo != null) { - if (query.SortBy == null || query.SortBy.Length == 0) + if (orderBy.Count == 0) { - if (query.User != null) - { - query.SortBy = new[] { "SimilarityScore", ItemSortBy.Random }; - } - else - { - query.SortBy = new[] { "SimilarityScore", ItemSortBy.Random }; - } + orderBy.Add(new Tuple("SimilarityScore", SortOrder.Descending)); + orderBy.Add(new Tuple(ItemSortBy.Random, SortOrder.Ascending)); query.SortOrder = SortOrder.Descending; + enableOrderInversion = false; } } - if (query.SortBy == null || query.SortBy.Length == 0) + query.OrderBy = orderBy; + + if (orderBy.Count == 0) { return string.Empty; } - var isAscending = query.SortOrder != SortOrder.Descending; - - return " ORDER BY " + string.Join(",", query.SortBy.Select(i => + return " ORDER BY " + string.Join(",", orderBy.Select(i => { - var columnMap = MapOrderByField(i, query); - var columnAscending = isAscending; - if (columnMap.Item2) + var columnMap = MapOrderByField(i.Item1, query); + var columnAscending = i.Item2 == SortOrder.Ascending; + if (columnMap.Item2 && enableOrderInversion) { columnAscending = !columnAscending; } -- cgit v1.2.3 From 229172da500d8fd11676144779d004172f3d41a1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 30 Sep 2016 14:43:59 -0400 Subject: update series recording editor --- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 1 + .../MediaEncoding/IMediaEncoder.cs | 4 ++-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 23 ++++++++++++---------- .../MediaInfo/VideoImageProvider.cs | 4 ++-- .../LiveTv/EmbyTV/EncodedRecorder.cs | 4 +++- .../MediaEncoder/EncodingManager.cs | 4 +++- 6 files changed, 24 insertions(+), 16 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index d4ddbd7c5..51455c141 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -907,6 +907,7 @@ namespace MediaBrowser.Api.Playback.Hls ).Trim(); } + // TODO: check libavformat version for 57 50.100 and use -hls_flags split_by_time return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"", inputModifier, GetInputArgument(state), diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 7fdbf020c..f97fe9560 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -45,9 +45,9 @@ namespace MediaBrowser.Controller.MediaEncoding /// The offset. /// The cancellation token. /// Task{Stream}. - Task ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); + Task ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); - Task ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken); + Task ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken); /// /// Extracts the video images on interval. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 44103e720..06867969e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -771,20 +771,20 @@ namespace MediaBrowser.MediaEncoding.Encoder public Task ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken) { - return ExtractImage(new[] { path }, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken); + return ExtractImage(new[] { path }, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken); } - public Task ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) + public Task ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFiles, null, protocol, false, threedFormat, offset, cancellationToken); + return ExtractImage(inputFiles, container, null, protocol, false, threedFormat, offset, cancellationToken); } - public Task ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken) + public Task ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken) { - return ExtractImage(inputFiles, imageStreamIndex, protocol, false, null, null, cancellationToken); + return ExtractImage(inputFiles, container, imageStreamIndex, protocol, false, null, null, cancellationToken); } - private async Task ExtractImage(string[] inputFiles, int? imageStreamIndex, MediaProtocol protocol, bool isAudio, + private async Task ExtractImage(string[] inputFiles, string container, int? imageStreamIndex, MediaProtocol protocol, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { var resourcePool = isAudio ? _audioImageResourcePool : _videoImageResourcePool; @@ -803,7 +803,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { try { - return await ExtractImageInternal(inputArgument, imageStreamIndex, protocol, threedFormat, offset, true, resourcePool, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, true, resourcePool, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -815,10 +815,10 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return await ExtractImageInternal(inputArgument, imageStreamIndex, protocol, threedFormat, offset, false, resourcePool, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, false, resourcePool, cancellationToken).ConfigureAwait(false); } - private async Task ExtractImageInternal(string inputPath, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + private async Task ExtractImageInternal(string inputPath, string container, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { @@ -859,8 +859,11 @@ namespace MediaBrowser.MediaEncoding.Encoder var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; + var enableThumbnail = !new List { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase); + var thumbnail = enableThumbnail ? ",thumbnail=30" : string.Empty; + // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. - var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg) : + var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) : string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg); var probeSize = GetProbeSizeArgument(new[] { inputPath }, protocol); diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 2ad02da2e..6a934240a 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -134,7 +134,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, protocol, videoIndex, cancellationToken).ConfigureAwait(false); + extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, protocol, videoIndex, cancellationToken).ConfigureAwait(false); } else { @@ -145,7 +145,7 @@ namespace MediaBrowser.Providers.MediaInfo ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1)) : TimeSpan.FromSeconds(10); - extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, protocol, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); + extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, protocol, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); } return new DynamicImageResponse diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 65e653551..3e9d186e3 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -131,6 +131,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback StartStreamingLog(process.StandardError.BaseStream, _logFileStream); + _logger.Info("ffmpeg recording process started for {0}", _targetPath); + return _taskCompletionSource.Task; } @@ -141,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { var maxBitrate = 25000000; videoArgs = string.Format( - "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41 -tune zerolatency", + "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41", GetOutputSizeParam(), maxBitrate.ToString(CultureInfo.InvariantCulture)); } diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index 7d0841fa6..06c109dfc 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -160,7 +160,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); - var tempFile = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false); + var container = video.Container; + + var tempFile = await _encoder.ExtractVideoImage(inputPath, container, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false); File.Copy(tempFile, path, true); try -- cgit v1.2.3 From 3f77a9a8a220e94bd9111e948a495343b3d3b1a2 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 1 Oct 2016 03:06:00 -0400 Subject: update series timer editor --- MediaBrowser.Api/ItemUpdateService.cs | 6 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 3 + MediaBrowser.Controller/Entities/BaseItem.cs | 5 +- .../Entities/InternalItemsQuery.cs | 1 + MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs | 2 + MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 7 +- MediaBrowser.Model/LiveTv/ProgramQuery.cs | 1 + .../LiveTv/LiveTvManager.cs | 90 +++++++++++++++++++++- .../Persistence/SqliteItemRepository.cs | 21 ++++- 9 files changed, 120 insertions(+), 16 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index cda7ce0c6..64b6b18d6 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -243,11 +243,7 @@ namespace MediaBrowser.Api hasBudget.Revenue = request.Revenue; } - var hasOriginalTitle = item as IHasOriginalTitle; - if (hasOriginalTitle != null) - { - hasOriginalTitle.OriginalTitle = hasOriginalTitle.OriginalTitle; - } + item.OriginalTitle = string.IsNullOrWhiteSpace(request.OriginalTitle) ? null : request.OriginalTitle; var hasCriticRating = item as IHasCriticRating; if (hasCriticRating != null) diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 5b821df0b..ecc17374f 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -386,6 +386,8 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableUserData { get; set; } + public string SeriesTimerId { get; set; } + /// /// Fields to return within the items, in addition to basic information /// @@ -985,6 +987,7 @@ namespace MediaBrowser.Api.LiveTv query.IsSeries = request.IsSeries; query.IsKids = request.IsKids; query.IsSports = request.IsSports; + query.SeriesTimerId = request.SeriesTimerId; query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var result = await _liveTvManager.GetPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 30e0f3ee7..492058f98 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -256,7 +256,10 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public string ExternalSeriesId + public string ExternalSeriesId { get; set; } + + [IgnoreDataMember] + public string ExternalSeriesIdLegacy { get { return this.GetProviderId("ProviderExternalSeriesId"); } set diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 5e70cd587..60af2c56a 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -139,6 +139,7 @@ namespace MediaBrowser.Controller.Entities public DayOfWeek[] AirDays { get; set; } public SeriesStatus[] SeriesStatuses { get; set; } public string AlbumArtistStartsWithOrGreater { get; set; } + public string ExternalSeriesId { get; set; } public string[] AlbumNames { get; set; } public string[] ArtistNames { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index a828e22e3..5c73ed833 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -27,6 +27,8 @@ namespace MediaBrowser.Controller.LiveTv /// public string Name { get; set; } + public string ServiceName { get; set; } + /// /// Description of the recording. /// diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index 5d2231da8..51e115cc2 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -339,12 +339,9 @@ namespace MediaBrowser.Dlna.Main if (_Publisher != null) { var devices = _Publisher.Devices.ToList(); + var tasks = devices.Select(i => _Publisher.RemoveDevice(i)).ToArray(); - foreach (var device in devices) - { - var task = _Publisher.RemoveDevice(device); - Task.WaitAll(task); - } + Task.WaitAll(tasks); //foreach (var device in devices) //{ // try diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs index 7886342e7..ad57d1473 100644 --- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs +++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs @@ -41,6 +41,7 @@ namespace MediaBrowser.Model.LiveTv /// /// The user identifier. public string UserId { get; set; } + public string SeriesTimerId { get; set; } /// /// The earliest date for which a program starts to return diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index f736307f0..025f5a162 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -565,6 +565,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv }; } + var seriesId = info.SeriesId; + if (string.IsNullOrWhiteSpace(seriesId) && info.IsSeries) + { + seriesId = info.Name.GetMD5().ToString("N"); + } + if (!item.ParentId.Equals(channel.Id)) { forceUpdate = true; @@ -584,7 +590,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.EpisodeTitle = info.EpisodeTitle; item.ExternalId = info.Id; - item.ExternalSeriesId = info.SeriesId; + item.ExternalSeriesIdLegacy = seriesId; + + if (!string.IsNullOrWhiteSpace(seriesId) && !string.Equals(item.ExternalSeriesId, seriesId, StringComparison.Ordinal)) + { + forceUpdate = true; + } + item.ExternalSeriesId = seriesId; + item.Genres = info.Genres; item.IsHD = info.IsHD; item.IsKids = info.IsKids; @@ -825,7 +838,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user); var list = new List>(); - list.Add(new Tuple(dto, program.ServiceName, program.ExternalId, program.ExternalSeriesId)); + list.Add(new Tuple(dto, program.ServiceName, program.ExternalId, program.ExternalSeriesIdLegacy)); await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false); @@ -866,6 +879,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv TopParentIds = new[] { topFolder.Id.ToString("N") } }; + if (!string.IsNullOrWhiteSpace(query.SeriesTimerId)) + { + var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery {}, cancellationToken).ConfigureAwait(false); + var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase)); + if (seriesTimer != null) + { + internalQuery.ExternalSeriesId = seriesTimer.SeriesId; + + if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId)) + { + // Better to return nothing than every program in the database + return new QueryResult(); + } + } + else + { + // Better to return nothing than every program in the database + return new QueryResult(); + } + } + if (query.HasAired.HasValue) { if (query.HasAired.Value) @@ -1730,7 +1764,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.ServiceName = serviceName; } - recordingTuples.Add(new Tuple(dto, serviceName, program.ExternalId, program.ExternalSeriesId)); + recordingTuples.Add(new Tuple(dto, serviceName, program.ExternalId, program.ExternalSeriesIdLegacy)); } await AddRecordingInfo(recordingTuples, CancellationToken.None).ConfigureAwait(false); @@ -2005,6 +2039,56 @@ namespace MediaBrowser.Server.Implementations.LiveTv return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); } + private async Task> GetSeriesTimersInternal(SeriesTimerQuery query, CancellationToken cancellationToken) + { + var tasks = _services.Select(async i => + { + try + { + var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); + return recs.Select(r => + { + r.ServiceName = i.Name; + return new Tuple(r, i); + }); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting recordings", ex); + return new List>(); + } + }); + var results = await Task.WhenAll(tasks).ConfigureAwait(false); + var timers = results.SelectMany(i => i.ToList()); + + if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase)) + { + timers = query.SortOrder == SortOrder.Descending ? + timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) : + timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name); + } + else + { + timers = query.SortOrder == SortOrder.Descending ? + timers.OrderByStringDescending(i => i.Item1.Name) : + timers.OrderByString(i => i.Item1.Name); + } + + var returnArray = timers + .Select(i => + { + return i.Item1; + + }) + .ToArray(); + + return new QueryResult + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + } + public async Task> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken) { var tasks = _services.Select(async i => diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 672cd1bc2..c843ab596 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -270,6 +270,7 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID"); _connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID"); _connection.AddColumn(Logger, "TypedBaseItems", "SeriesSortName", "Text"); + _connection.AddColumn(Logger, "TypedBaseItems", "ExternalSeriesId", "Text"); _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); @@ -413,7 +414,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "SeriesSortName", "PresentationUniqueKey", "InheritedParentalRatingValue", - "InheritedTags" + "InheritedTags", + "ExternalSeriesId" }; private readonly string[] _mediaStreamSaveColumns = @@ -535,7 +537,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "SeasonName", "SeasonId", "SeriesId", - "SeriesSortName" + "SeriesSortName", + "ExternalSeriesId" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -975,6 +978,8 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveItemCommand.GetParameter(index++).Value = null; } + _saveItemCommand.GetParameter(index++).Value = item.ExternalSeriesId; + _saveItemCommand.Transaction = transaction; _saveItemCommand.ExecuteNonQuery(); @@ -1466,6 +1471,12 @@ namespace MediaBrowser.Server.Implementations.Persistence } index++; + if (!reader.IsDBNull(index)) + { + item.ExternalSeriesId = reader.GetString(index); + } + index++; + return item; } @@ -2852,6 +2863,12 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.Parameters.Add(cmd, "@MinSortName", DbType.String).Value = query.MinSortName; } + if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId)) + { + whereClauses.Add("ExternalSeriesId=@ExternalSeriesId"); + cmd.Parameters.Add(cmd, "@ExternalSeriesId", DbType.String).Value = query.ExternalSeriesId; + } + if (!string.IsNullOrWhiteSpace(query.Name)) { whereClauses.Add("CleanName=@Name"); -- cgit v1.2.3 From 951e2b6de30631e8b30c9290fc9e0cb5eea5ea2e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 1 Oct 2016 16:29:24 -0400 Subject: allow channel items in collections and playlists --- MediaBrowser.Controller/Entities/Folder.cs | 26 +++++++--------------- .../Movies/GenericMovieDbInfo.cs | 6 +---- .../LiveTv/LiveTvManager.cs | 4 ---- .../TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 3 +++ 4 files changed, 12 insertions(+), 27 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index f1d8def4b..d1e089850 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1149,29 +1149,19 @@ namespace MediaBrowser.Controller.Entities return LinkedChildren .Select(i => { - var requiresPostFilter = true; - - if (!string.IsNullOrWhiteSpace(i.Path)) - { - requiresPostFilter = false; - - if (!locations.Any(l => FileSystem.ContainsSubPath(l, i.Path))) - { - return null; - } - } - var child = GetLinkedChild(i); - if (requiresPostFilter && child != null) + if (child != null) { - if (string.IsNullOrWhiteSpace(child.Path)) + var childLocationType = child.LocationType; + if (childLocationType == LocationType.Remote || childLocationType == LocationType.Virtual) { - Logger.Debug("Found LinkedChild with null path: {0}", child.Name); - return child; + if (!child.IsVisibleStandalone(user)) + { + return null; + } } - - if (!locations.Any(l => FileSystem.ContainsSubPath(l, child.Path))) + else if (childLocationType == LocationType.FileSystem && !locations.Any(l => FileSystem.ContainsSubPath(l, child.Path))) { return null; } diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs index 54302f39a..41a061467 100644 --- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs +++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs @@ -126,11 +126,7 @@ namespace MediaBrowser.Providers.Movies movie.Name = movieData.GetTitle() ?? movie.Name; - var hasOriginalTitle = movie as IHasOriginalTitle; - if (hasOriginalTitle != null) - { - hasOriginalTitle.OriginalTitle = movieData.GetOriginalTitle(); - } + movie.OriginalTitle = movieData.GetOriginalTitle(); // Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException. movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 025f5a162..e34868428 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -566,10 +566,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv } var seriesId = info.SeriesId; - if (string.IsNullOrWhiteSpace(seriesId) && info.IsSeries) - { - seriesId = info.Name.GetMD5().ToString("N"); - } if (!item.ParentId.Equals(channel.Id)) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs index 57722881d..2cdd0571b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -58,6 +58,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + Path.GetFileNameWithoutExtension(tempFile) + "/stream.ts"; OpenedMediaSource.Protocol = MediaProtocol.Http; + OpenedMediaSource.SupportsDirectPlay = false; + OpenedMediaSource.SupportsDirectStream = true; + OpenedMediaSource.SupportsTranscoding = true; await taskCompletionSource.Task.ConfigureAwait(false); -- cgit v1.2.3 From 55a53cfab7e0dcb320f4fe3f4e4273dafe236305 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 1 Oct 2016 22:09:33 -0400 Subject: add expires response header --- .../HttpServer/HttpResultFactory.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index c55e98388..04085d3c7 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -93,12 +93,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } } - - if (responseHeaders != null) + if (responseHeaders == null) { - AddResponseHeaders(result, responseHeaders); + responseHeaders = new Dictionary(); } + responseHeaders["Expires"] = "-1"; + AddResponseHeaders(result, responseHeaders); + return result; } -- cgit v1.2.3 From 911d9f4598e4cafe24a8eb6d40a0d95356d3437c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 2 Oct 2016 00:31:47 -0400 Subject: move more metadata settings to per library --- MediaBrowser.Api/StartupWizardService.cs | 6 ----- MediaBrowser.Controller/Entities/BaseItem.cs | 6 +++-- .../Entities/CollectionFolder.cs | 2 +- MediaBrowser.Model/Configuration/ChapterOptions.cs | 6 ----- MediaBrowser.Model/Configuration/LibraryOptions.cs | 4 ++++ .../MediaInfo/FFProbeVideoInfo.cs | 4 ++-- .../TV/MissingEpisodeProvider.cs | 2 +- .../TV/TheTVDB/TvdbPrescanTask.cs | 17 ++++++++------ .../Library/LibraryManager.cs | 27 ++++++++++++++-------- .../MediaEncoder/EncodingManager.cs | 26 ++------------------- .../Session/SessionManager.cs | 3 +-- 11 files changed, 42 insertions(+), 61 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 176b497d7..ebb3204a4 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -88,8 +88,6 @@ namespace MediaBrowser.Api var result = new StartupConfiguration { UICulture = _config.Configuration.UICulture, - EnableInternetProviders = _config.Configuration.EnableInternetProviders, - SaveLocalMeta = _config.Configuration.SaveLocalMeta, MetadataCountryCode = _config.Configuration.MetadataCountryCode, PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage }; @@ -123,8 +121,6 @@ namespace MediaBrowser.Api public void Post(UpdateStartupConfiguration request) { _config.Configuration.UICulture = request.UICulture; - _config.Configuration.EnableInternetProviders = request.EnableInternetProviders; - _config.Configuration.SaveLocalMeta = request.SaveLocalMeta; _config.Configuration.MetadataCountryCode = request.MetadataCountryCode; _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage; _config.SaveConfiguration(); @@ -215,8 +211,6 @@ namespace MediaBrowser.Api public class StartupConfiguration { public string UICulture { get; set; } - public bool EnableInternetProviders { get; set; } - public bool SaveLocalMeta { get; set; } public string MetadataCountryCode { get; set; } public string PreferredMetadataLanguage { get; set; } public string LiveTvTunerType { get; set; } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 492058f98..be88c535e 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -422,7 +422,7 @@ namespace MediaBrowser.Controller.Entities public virtual bool IsInternetMetadataEnabled() { - return ConfigurationManager.Configuration.EnableInternetProviders; + return LibraryManager.GetLibraryOptions(this).EnableInternetProviders; } public virtual bool CanDelete() @@ -1341,7 +1341,9 @@ namespace MediaBrowser.Controller.Entities return false; } - return ConfigurationManager.Configuration.SaveLocalMeta; + var libraryOptions = LibraryManager.GetLibraryOptions(this); + + return libraryOptions.SaveLocalMetadata; } /// diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 77d7ca7f2..04ba53263 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.Entities { LibraryOptions[path] = options; - options.SchemaVersion = 2; + options.SchemaVersion = 3; XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path)); } } diff --git a/MediaBrowser.Model/Configuration/ChapterOptions.cs b/MediaBrowser.Model/Configuration/ChapterOptions.cs index f9ff6b4f9..c7bb6f861 100644 --- a/MediaBrowser.Model/Configuration/ChapterOptions.cs +++ b/MediaBrowser.Model/Configuration/ChapterOptions.cs @@ -2,17 +2,11 @@ { public class ChapterOptions { - public bool EnableMovieChapterImageExtraction { get; set; } - public bool EnableEpisodeChapterImageExtraction { get; set; } - public bool EnableOtherVideoChapterImageExtraction { get; set; } - public bool DownloadMovieChapters { get; set; } public bool DownloadEpisodeChapters { get; set; } public string[] FetcherOrder { get; set; } public string[] DisabledFetchers { get; set; } - - public bool ExtractDuringLibraryScan { get; set; } public ChapterOptions() { diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 460ee0918..5f8c77ccd 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -11,11 +11,15 @@ public bool DownloadImagesInAdvance { get; set; } public MediaPathInfo[] PathInfos { get; set; } + public bool SaveLocalMetadata { get; set; } + public bool EnableInternetProviders { get; set; } + public LibraryOptions() { EnablePhotos = true; EnableRealtimeMonitor = true; PathInfos = new MediaPathInfo[] { }; + EnableInternetProviders = true; } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 0f8cf93fb..66fe7ea81 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -262,8 +262,8 @@ namespace MediaBrowser.Providers.MediaInfo NormalizeChapterNames(chapters); var libraryOptions = _libraryManager.GetLibraryOptions(video); - var extractDuringScan = chapterOptions.ExtractDuringLibraryScan; - if (libraryOptions != null && libraryOptions.SchemaVersion >= 2) + var extractDuringScan = false; + if (libraryOptions != null) { extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan; } diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index a12402f4f..9c212e8a0 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -118,7 +118,7 @@ namespace MediaBrowser.Providers.TV var hasNewEpisodes = false; - if (_config.Configuration.EnableInternetProviders && addNewItems) + if (addNewItems && !group.Any(i => !i.IsInternetMetadataEnabled())) { var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase)); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs index 215e0640f..d9e1037d8 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -74,12 +74,6 @@ namespace MediaBrowser.Providers.TV /// Task. public async Task Run(IProgress progress, CancellationToken cancellationToken) { - if (!_config.Configuration.EnableInternetProviders) - { - progress.Report(100); - return; - } - var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase)); if (seriesConfig != null && seriesConfig.DisabledMetadataFetchers.Contains(TvdbSeriesProvider.Current.Name, StringComparer.OrdinalIgnoreCase)) @@ -116,7 +110,9 @@ namespace MediaBrowser.Providers.TV IncludeItemTypes = new[] { typeof(Series).Name }, Recursive = true, GroupByPresentationUniqueKey = false - }).Cast(); + + }).Cast() + .ToList(); var seriesIdsInLibrary = seriesList .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb))) @@ -126,6 +122,13 @@ namespace MediaBrowser.Providers.TV var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase) .ToList(); + var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsInternetMetadataEnabled(); + if (!enableInternetProviders) + { + progress.Report(100); + return; + } + // If this is our first time, update all series if (string.IsNullOrEmpty(lastUpdateTime)) { diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index f6fb158ae..93ee91c21 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1216,12 +1216,7 @@ namespace MediaBrowser.Server.Implementations.Library if (libraryFolder != null) { info.ItemId = libraryFolder.Id.ToString("N"); - } - - var collectionFolder = libraryFolder as CollectionFolder; - if (collectionFolder != null) - { - info.LibraryOptions = collectionFolder.GetLibraryOptions(); + info.LibraryOptions = GetLibraryOptions(libraryFolder); } return info; @@ -1889,11 +1884,23 @@ namespace MediaBrowser.Server.Implementations.Library public LibraryOptions GetLibraryOptions(BaseItem item) { - var collectionFolder = GetCollectionFolders(item) - .OfType() - .FirstOrDefault(); + var collectionFolder = item as CollectionFolder; + if (collectionFolder == null) + { + collectionFolder = GetCollectionFolders(item) + .OfType() + .FirstOrDefault(); + } + + var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); + + if (options.SchemaVersion < 3) + { + options.SaveLocalMetadata = ConfigurationManager.Configuration.SaveLocalMeta; + options.EnableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders; + } - return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions(); + return options; } public string GetContentType(BaseItem item) diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index 06c109dfc..21e847c68 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder } var libraryOptions = _libraryManager.GetLibraryOptions(video); - if (libraryOptions != null && libraryOptions.SchemaVersion >= 2) + if (libraryOptions != null) { if (!libraryOptions.EnableChapterImageExtraction) { @@ -70,29 +70,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder } else { - var options = _chapterManager.GetConfiguration(); - - if (video is Movie) - { - if (!options.EnableMovieChapterImageExtraction) - { - return false; - } - } - else if (video is Episode) - { - if (!options.EnableEpisodeChapterImageExtraction) - { - return false; - } - } - else - { - if (!options.EnableOtherVideoChapterImageExtraction) - { - return false; - } - } + return false; } // Can't extract images if there are no video streams diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index d2bdee9fa..2fcc76588 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -242,8 +242,7 @@ namespace MediaBrowser.Server.Implementations.Session var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue; user.LastActivityDate = activityDate; - // Don't log in the db anymore frequently than 10 seconds - if ((activityDate - userLastActivityDate).TotalSeconds > 10) + if ((activityDate - userLastActivityDate).TotalSeconds > 60) { try { -- cgit v1.2.3 From cf4f5039bffa504b65569e2be5b2711e803e105b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 2 Oct 2016 02:13:04 -0400 Subject: update components --- MediaBrowser.Api/Playback/MediaInfoService.cs | 2 +- MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 761538c83..7fe7d5a21 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -107,7 +107,7 @@ namespace MediaBrowser.Api.Playback { var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); - var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false); + var result = await _mediaSourceManager.OpenLiveStream(request, true, CancellationToken.None).ConfigureAwait(false); var profile = request.DeviceProfile; if (profile == null) diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index c7650102f..ae32bdaf7 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -456,7 +456,7 @@ namespace MediaBrowser.Server.Implementations.Library } else { - _logger.Error("Failed to update MediaSource timestamp for {0}", id); + _logger.Error("Failed to ping live stream {0}", id); } } finally @@ -540,7 +540,7 @@ namespace MediaBrowser.Server.Implementations.Library } private Timer _closeTimer; - private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(60); + private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(180); private void StartCloseTimer() { -- cgit v1.2.3 From d3583c14605dbf31aed7dd921fb2085c1c0b7be1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 3 Oct 2016 02:28:45 -0400 Subject: update lists --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 6 ++++-- MediaBrowser.Api/Playback/StreamState.cs | 13 +++++++++++++ MediaBrowser.Controller/Entities/InternalItemsQuery.cs | 3 +++ MediaBrowser.Dlna/Didl/DidlBuilder.cs | 6 ++++-- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 3 ++- MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs | 13 +++++++++++++ MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs | 3 ++- MediaBrowser.Model/Dlna/ConditionProcessor.cs | 5 ++++- MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs | 6 ++++-- MediaBrowser.Model/Dlna/DeviceProfile.cs | 5 +++-- MediaBrowser.Model/Dlna/ProfileConditionValue.cs | 3 ++- MediaBrowser.Model/Dlna/StreamBuilder.cs | 10 ++++++---- MediaBrowser.Model/Dlna/StreamInfo.cs | 13 +++++++++++++ MediaBrowser.Providers/Manager/ImageSaver.cs | 14 ++++++++++++++ .../LiveTv/EmbyTV/EmbyTV.cs | 13 +++++++------ .../LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 12 +++++++++++- .../Persistence/SqliteItemRepository.cs | 12 ++++++++++++ 17 files changed, 117 insertions(+), 23 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index fb4bd9244..7f41cb059 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -2329,7 +2329,8 @@ namespace MediaBrowser.Api.Playback state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, - state.TargetVideoCodecTag); + state.TargetVideoCodecTag, + state.IsTargetAVC); if (mediaProfile != null) { @@ -2547,7 +2548,8 @@ namespace MediaBrowser.Api.Playback state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, - state.TargetVideoCodecTag + state.TargetVideoCodecTag, + state.IsTargetAVC ).FirstOrDefault() ?? string.Empty; } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 019f378c5..863bc0193 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -516,5 +516,18 @@ namespace MediaBrowser.Api.Playback return false; } } + + public bool? IsTargetAVC + { + get + { + if (Request.Static) + { + return VideoStream == null ? null : VideoStream.IsAVC; + } + + return true; + } + } } } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 60af2c56a..89fe71d3c 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -154,6 +154,9 @@ namespace MediaBrowser.Controller.Entities public List> OrderBy { get; set; } + public DateTime? MinDateCreated { get; set; } + public DateTime? MinDateLastSaved { get; set; } + public InternalItemsQuery() { GroupByPresentationUniqueKey = true; diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 84c1b3bde..f53dec3bf 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -177,7 +177,8 @@ namespace MediaBrowser.Dlna.Didl streamInfo.TargetRefFrames, streamInfo.TargetVideoStreamCount, streamInfo.TargetAudioStreamCount, - streamInfo.TargetVideoCodecTag); + streamInfo.TargetVideoCodecTag, + streamInfo.IsTargetAVC); foreach (var contentFeature in contentFeatureList) { @@ -322,7 +323,8 @@ namespace MediaBrowser.Dlna.Didl streamInfo.TargetRefFrames, streamInfo.TargetVideoStreamCount, streamInfo.TargetAudioStreamCount, - streamInfo.TargetVideoCodecTag); + streamInfo.TargetVideoCodecTag, + streamInfo.IsTargetAVC); var filename = url.Substring(0, url.IndexOf('?')); diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 6345e2105..f6b24e615 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -540,7 +540,8 @@ namespace MediaBrowser.Dlna.PlayTo streamInfo.TargetRefFrames, streamInfo.TargetVideoStreamCount, streamInfo.TargetAudioStreamCount, - streamInfo.TargetVideoCodecTag); + streamInfo.TargetVideoCodecTag, + streamInfo.IsTargetAVC); return list.FirstOrDefault(); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index d3738d903..0c7ff1b76 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -392,6 +392,19 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + public bool? IsTargetAVC + { + get + { + if (Options.Static) + { + return VideoStream == null ? null : VideoStream.IsAVC; + } + + return false; + } + } + public int? TargetVideoStreamCount { get diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs index c72532669..b66a3ed96 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs @@ -823,7 +823,8 @@ namespace MediaBrowser.MediaEncoding.Encoder state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, - state.TargetVideoCodecTag); + state.TargetVideoCodecTag, + state.IsTargetAVC); if (mediaProfile != null) { diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index 69f1369dc..ec13cacfa 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -20,12 +20,15 @@ namespace MediaBrowser.Model.Dlna int? refFrames, int? numVideoStreams, int? numAudioStreams, - string videoCodecTag) + string videoCodecTag, + bool? isAvc) { switch (condition.Property) { case ProfileConditionValue.IsAnamorphic: return IsConditionSatisfied(condition, isAnamorphic); + case ProfileConditionValue.IsAvc: + return IsConditionSatisfied(condition, isAvc); case ProfileConditionValue.VideoFramerate: return IsConditionSatisfied(condition, videoFramerate); case ProfileConditionValue.VideoLevel: diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index c4b3383a2..4a16a2780 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -118,7 +118,8 @@ namespace MediaBrowser.Model.Dlna int? refFrames, int? numVideoStreams, int? numAudioStreams, - string videoCodecTag) + string videoCodecTag, + bool? isAvc) { // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo); @@ -159,7 +160,8 @@ namespace MediaBrowser.Model.Dlna refFrames, numVideoStreams, numAudioStreams, - videoCodecTag); + videoCodecTag, + isAvc); List orgPnValues = new List(); diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index d6a322322..884a9f29d 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -285,7 +285,8 @@ namespace MediaBrowser.Model.Dlna int? refFrames, int? numVideoStreams, int? numAudioStreams, - string videoCodecTag) + string videoCodecTag, + bool? isAvc) { container = StringHelper.TrimStart(container ?? string.Empty, '.'); @@ -319,7 +320,7 @@ namespace MediaBrowser.Model.Dlna var anyOff = false; foreach (ProfileCondition c in i.Conditions) { - if (!conditionProcessor.IsVideoConditionSatisfied(c, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag)) + if (!conditionProcessor.IsVideoConditionSatisfied(c, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) { anyOff = true; break; diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs index c17a09c3f..7e2002f17 100644 --- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs +++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs @@ -20,6 +20,7 @@ NumAudioStreams = 16, NumVideoStreams = 17, IsSecondaryAudio = 18, - VideoCodecTag = 19 + VideoCodecTag = 19, + IsAvc = 20 } } \ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 30d1498bf..52b7fd43a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -541,6 +541,7 @@ namespace MediaBrowser.Model.Dlna float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate; bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic; string videoCodecTag = videoStream == null ? null : videoStream.CodecTag; + bool? isAvc = videoStream == null ? null : videoStream.IsAVC; TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; int? packetLength = videoStream == null ? null : videoStream.PacketLength; @@ -549,7 +550,7 @@ namespace MediaBrowser.Model.Dlna int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); - if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag)) + if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) { LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item); applyConditions = false; @@ -718,6 +719,7 @@ namespace MediaBrowser.Model.Dlna float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate; bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic; string videoCodecTag = videoStream == null ? null : videoStream.CodecTag; + bool? isAvc = videoStream == null ? null : videoStream.IsAVC; int? audioBitrate = audioStream == null ? null : audioStream.BitRate; int? audioChannels = audioStream == null ? null : audioStream.Channels; @@ -733,7 +735,7 @@ namespace MediaBrowser.Model.Dlna // Check container conditions foreach (ProfileCondition i in conditions) { - if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag)) + if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) { LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource); @@ -760,7 +762,7 @@ namespace MediaBrowser.Model.Dlna bool applyConditions = true; foreach (ProfileCondition applyCondition in i.ApplyConditions) { - if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag)) + if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) { LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource); applyConditions = false; @@ -780,7 +782,7 @@ namespace MediaBrowser.Model.Dlna foreach (ProfileCondition i in conditions) { - if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag)) + if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) { LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource); diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index f7dc893c8..c9cb873e1 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -676,6 +676,19 @@ namespace MediaBrowser.Model.Dlna } } + public bool? IsTargetAVC + { + get + { + if (IsDirectStream) + { + return TargetVideoStream == null ? null : TargetVideoStream.IsAVC; + } + + return true; + } + } + public int? TargetWidth { get diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 3de330557..36b5987a0 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -211,6 +211,20 @@ namespace MediaBrowser.Providers.Manager throw; } } + catch (IOException ex) + { + var retry = !string.IsNullOrWhiteSpace(retryPath) && + !string.Equals(path, retryPath, StringComparison.OrdinalIgnoreCase); + + if (retry) + { + _logger.Error("IOException saving to {0}. {2}. Will retry saving to {1}", path, retryPath, ex.Message); + } + else + { + throw; + } + } source.Position = 0; await SaveImageToLocation(source, retryPath, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3db764ae1..4530bfcb6 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -606,15 +606,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV ActiveRecordingInfo activeRecordingInfo; if (!_activeRecordings.TryGetValue(updatedTimer.Id, out activeRecordingInfo)) { - UpdateExistingTimerWithNewData(existingTimer, updatedTimer); - - _timerProvider.Update(existingTimer); + existingTimer.PrePaddingSeconds = updatedTimer.PrePaddingSeconds; + existingTimer.PostPaddingSeconds = updatedTimer.PostPaddingSeconds; + existingTimer.IsPostPaddingRequired = updatedTimer.IsPostPaddingRequired; + existingTimer.IsPrePaddingRequired = updatedTimer.IsPrePaddingRequired; } return Task.FromResult(true); } - private void UpdateExistingTimerWithNewData(TimerInfo existingTimer, TimerInfo updatedTimer) + private void UpdateExistingTimerWithNewMetadata(TimerInfo existingTimer, TimerInfo updatedTimer) { // Update the program info but retain the status existingTimer.ChannelId = updatedTimer.ChannelId; @@ -1430,7 +1431,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { SaveSeriesNfo(timer, recordingPath, seriesPath); } - else if (!timer.IsMovie || timer.IsSports) + else if (!timer.IsMovie || timer.IsSports || timer.IsNews) { SaveVideoNfo(timer, recordingPath); } @@ -1620,7 +1621,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV ActiveRecordingInfo activeRecordingInfo; if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo)) { - UpdateExistingTimerWithNewData(existingTimer, timer); + UpdateExistingTimerWithNewMetadata(existingTimer, timer); if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs index 2cdd0571b..d49e3f258 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun Action onStarted = null; if (isFirstAttempt) { - onStarted = () => openTaskCompletionSource.TrySetResult(true); + onStarted = () => ResolveWhenExists(openTaskCompletionSource, tempFilePath, cancellationToken); } await DirectRecorder.CopyUntilCancelled(response.Content, outputStream, onStarted, cancellationToken).ConfigureAwait(false); } @@ -137,6 +137,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun }).ConfigureAwait(false); } + private async void ResolveWhenExists(TaskCompletionSource taskCompletionSource, string file, CancellationToken cancellationToken) + { + while (!File.Exists(file) && !cancellationToken.IsCancellationRequested) + { + await Task.Delay(50).ConfigureAwait(false); + } + + taskCompletionSource.TrySetResult(true); + } + private async void DeleteTempFile(string path) { for (var i = 0; i < 10; i++) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index c843ab596..5a11742dc 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -2724,6 +2724,18 @@ namespace MediaBrowser.Server.Implementations.Persistence cmd.Parameters.Add(cmd, "@MinIndexNumber", DbType.Int32).Value = query.MinIndexNumber.Value; } + if (query.MinDateCreated.HasValue) + { + whereClauses.Add("DateCreated>=@MinDateCreated"); + cmd.Parameters.Add(cmd, "@MinDateCreated", DbType.DateTime).Value = query.MinDateCreated.Value; + } + + if (query.MinDateLastSaved.HasValue) + { + whereClauses.Add("DateLastSaved>=@MinDateLastSaved"); + cmd.Parameters.Add(cmd, "@MinDateLastSaved", DbType.DateTime).Value = query.MinDateLastSaved.Value; + } + //if (query.MinPlayers.HasValue) //{ // whereClauses.Add("Players>=@MinPlayers"); -- cgit v1.2.3 From c4e137e6cf7a52937e1169d1f170da4b8e72ba6a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 4 Oct 2016 01:15:39 -0400 Subject: update timer listings --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 5 ++- MediaBrowser.Api/Playback/BaseStreamingService.cs | 10 +++-- MediaBrowser.Controller/LiveTv/TimerInfo.cs | 1 + MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs | 10 +++-- MediaBrowser.Model/Dto/BaseItemDto.cs | 1 + MediaBrowser.Model/LiveTv/TimerQuery.cs | 2 + .../LiveTv/EmbyTV/EmbyTV.cs | 44 ++++++++++++---------- .../LiveTv/EmbyTV/RecordingHelper.cs | 20 +++++----- .../LiveTv/LiveTvDtoService.cs | 1 + .../LiveTv/LiveTvManager.cs | 14 +++++++ 10 files changed, 71 insertions(+), 37 deletions(-) (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index ecc17374f..4baae031f 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -315,6 +315,8 @@ namespace MediaBrowser.Api.LiveTv public string SeriesTimerId { get; set; } public bool? IsActive { get; set; } + + public bool? IsScheduled { get; set; } } [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")] @@ -1095,7 +1097,8 @@ namespace MediaBrowser.Api.LiveTv { ChannelId = request.ChannelId, SeriesTimerId = request.SeriesTimerId, - IsActive = request.IsActive + IsActive = request.IsActive, + IsScheduled = request.IsScheduled }, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 7f41cb059..bcf3bd28e 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -463,13 +463,15 @@ namespace MediaBrowser.Api.Playback var level = NormalizeTranscodingLevel(state.OutputVideoCodec, state.VideoRequest.Level); // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format + // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { switch (level) { case "30": - param += " -level 3"; + param += " -level 3.0"; break; case "31": param += " -level 3.1"; @@ -478,7 +480,7 @@ namespace MediaBrowser.Api.Playback param += " -level 3.2"; break; case "40": - param += " -level 4"; + param += " -level 4.0"; break; case "41": param += " -level 4.1"; @@ -487,7 +489,7 @@ namespace MediaBrowser.Api.Playback param += " -level 4.2"; break; case "50": - param += " -level 5"; + param += " -level 5.0"; break; case "51": param += " -level 5.1"; diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 978e9e1a8..fd614253a 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -106,6 +106,7 @@ namespace MediaBrowser.Controller.LiveTv public string EpisodeTitle { get; set; } public DateTime? OriginalAirDate { get; set; } public bool IsProgramSeries { get; set; } + public bool IsRepeat { get; set; } public string HomePageUrl { get; set; } public float? CommunityRating { get; set; } public string ShortOverview { get; set; } diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index e8121d178..f1e2f7241 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -726,13 +726,15 @@ namespace MediaBrowser.MediaEncoding.Encoder levelString = NormalizeTranscodingLevel(state.OutputVideoCodec, levelString); // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format + // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { switch (levelString) { case "30": - param += " -level 3"; + param += " -level 3.0"; break; case "31": param += " -level 3.1"; @@ -741,7 +743,7 @@ namespace MediaBrowser.MediaEncoding.Encoder param += " -level 3.2"; break; case "40": - param += " -level 4"; + param += " -level 4.0"; break; case "41": param += " -level 4.1"; @@ -750,7 +752,7 @@ namespace MediaBrowser.MediaEncoding.Encoder param += " -level 4.2"; break; case "50": - param += " -level 5"; + param += " -level 5.0"; break; case "51": param += " -level 5.1"; diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 9434ab03a..19853fd2b 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -1215,6 +1215,7 @@ namespace MediaBrowser.Model.Dto /// /// The timer identifier. public string TimerId { get; set; } + public RecordingStatus TimerStatus { get; set; } /// /// Gets or sets the current program. /// diff --git a/MediaBrowser.Model/LiveTv/TimerQuery.cs b/MediaBrowser.Model/LiveTv/TimerQuery.cs index 87b6b89ac..310dc486f 100644 --- a/MediaBrowser.Model/LiveTv/TimerQuery.cs +++ b/MediaBrowser.Model/LiveTv/TimerQuery.cs @@ -15,5 +15,7 @@ public string SeriesTimerId { get; set; } public bool? IsActive { get; set; } + + public bool? IsScheduled { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 4530bfcb6..a3ba2c6f8 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -629,6 +629,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV existingTimer.IsNews = updatedTimer.IsNews; existingTimer.IsMovie = updatedTimer.IsMovie; existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries; + existingTimer.IsRepeat = updatedTimer.IsRepeat; existingTimer.IsSports = updatedTimer.IsSports; existingTimer.Name = updatedTimer.Name; existingTimer.OfficialRating = updatedTimer.OfficialRating; @@ -665,8 +666,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { var excludeStatues = new List { - RecordingStatus.Completed, - RecordingStatus.Cancelled + RecordingStatus.Completed }; var timers = _timerProvider.GetAll() @@ -1591,6 +1591,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer) { + if (!seriesTimer.RecordAnyTime) + { + if (Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(5).Ticks) + { + return true; + } + + if (!seriesTimer.Days.Contains(timer.StartDate.ToLocalTime().DayOfWeek)) + { + return true; + } + } + + if (seriesTimer.RecordNewOnly && timer.IsRepeat) + { + return true; + } + + if (!seriesTimer.RecordAnyChannel && !string.Equals(timer.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + return seriesTimer.SkipEpisodesInLibrary && IsProgramAlreadyInLibrary(timer); } @@ -1734,23 +1757,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private IEnumerable GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable allPrograms) { - if (!seriesTimer.RecordAnyTime) - { - allPrograms = allPrograms.Where(epg => Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - epg.StartDate.TimeOfDay.Ticks) < TimeSpan.FromMinutes(5).Ticks); - - allPrograms = allPrograms.Where(i => seriesTimer.Days.Contains(i.StartDate.ToLocalTime().DayOfWeek)); - } - - if (seriesTimer.RecordNewOnly) - { - allPrograms = allPrograms.Where(epg => !epg.IsRepeat); - } - - if (!seriesTimer.RecordAnyChannel) - { - allPrograms = allPrograms.Where(epg => string.Equals(epg.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase)); - } - if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId)) { _logger.Error("seriesTimer.SeriesId is null. Cannot find programs for series"); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index bb6935e8e..f7b4b3fde 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.LiveTv; using System; using System.Globalization; +using MediaBrowser.Model.LiveTv; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { @@ -12,24 +13,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return timer.StartDate.AddSeconds(-timer.PrePaddingSeconds); } - public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo series) + public static TimerInfo CreateTimer(ProgramInfo parent, SeriesTimerInfo seriesTimer) { var timer = new TimerInfo(); timer.ChannelId = parent.ChannelId; - timer.Id = (series.Id + parent.Id).GetMD5().ToString("N"); + timer.Id = (seriesTimer.Id + parent.Id).GetMD5().ToString("N"); timer.StartDate = parent.StartDate; timer.EndDate = parent.EndDate; timer.ProgramId = parent.Id; - timer.PrePaddingSeconds = series.PrePaddingSeconds; - timer.PostPaddingSeconds = series.PostPaddingSeconds; - timer.IsPostPaddingRequired = series.IsPostPaddingRequired; - timer.IsPrePaddingRequired = series.IsPrePaddingRequired; - timer.KeepUntil = series.KeepUntil; - timer.Priority = series.Priority; + timer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds; + timer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds; + timer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired; + timer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired; + timer.KeepUntil = seriesTimer.KeepUntil; + timer.Priority = seriesTimer.Priority; timer.Name = parent.Name; timer.Overview = parent.Overview; - timer.SeriesTimerId = series.Id; + timer.SeriesTimerId = seriesTimer.Id; CopyProgramInfoToTimerInfo(parent, timer); @@ -53,6 +54,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timerInfo.CommunityRating = programInfo.CommunityRating; timerInfo.ShortOverview = programInfo.ShortOverview; timerInfo.OfficialRating = programInfo.OfficialRating; + timerInfo.IsRepeat = programInfo.IsRepeat; } public static string GetRecordingName(TimerInfo info) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 348bcd97b..2189cf627 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -72,6 +72,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.ProgramInfo = _dtoService.GetBaseItemDto(program, new DtoOptions()); dto.ProgramInfo.TimerId = dto.Id; + dto.ProgramInfo.SeriesTimerId = dto.SeriesTimerId; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index e34868428..84b162286 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1087,6 +1087,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id) .ToString("N"); + program.TimerStatus = timer.Status; + if (!string.IsNullOrEmpty(timer.SeriesTimerId)) { program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, timer.SeriesTimerId) @@ -1875,6 +1877,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } + if (query.IsScheduled.HasValue) + { + if (query.IsScheduled.Value) + { + timers = timers.Where(i => i.Item1.Status == RecordingStatus.New || i.Item1.Status == RecordingStatus.Scheduled); + } + else + { + timers = timers.Where(i => !(i.Item1.Status == RecordingStatus.New || i.Item1.Status == RecordingStatus.Scheduled)); + } + } + if (!string.IsNullOrEmpty(query.ChannelId)) { var guid = new Guid(query.ChannelId); -- cgit v1.2.3 From b9cacd8076c00bfc371898c50315304927efaff8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 5 Oct 2016 03:15:29 -0400 Subject: update live streams --- MediaBrowser.Api/Library/ChapterService.cs | 30 --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 2 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - MediaBrowser.Api/Playback/BaseStreamingService.cs | 4 +- .../Progressive/BaseProgressiveStreamingService.cs | 35 ++-- .../Progressive/ProgressiveStreamWriter.cs | 29 ++- MediaBrowser.Api/Playback/StreamState.cs | 1 + .../Chapters/ChapterResponse.cs | 19 -- .../Chapters/ChapterSearchRequest.cs | 31 --- .../Chapters/IChapterManager.cs | 47 +---- .../Chapters/IChapterProvider.cs | 39 ---- .../Library/IMediaSourceManager.cs | 8 + .../Library/IMediaSourceProvider.cs | 3 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 3 +- MediaBrowser.Controller/LiveTv/ILiveTvService.cs | 6 + .../MediaBrowser.Controller.csproj | 3 - .../MediaBrowser.Model.Portable.csproj | 9 - .../MediaBrowser.Model.net35.csproj | 9 - MediaBrowser.Model/Chapters/ChapterProviderInfo.cs | 8 - MediaBrowser.Model/Chapters/RemoteChapterInfo.cs | 18 -- MediaBrowser.Model/Chapters/RemoteChapterResult.cs | 48 ----- MediaBrowser.Model/Configuration/ChapterOptions.cs | 16 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 3 - MediaBrowser.Providers/Chapters/ChapterManager.cs | 220 --------------------- .../MediaInfo/FFProbeVideoInfo.cs | 62 ------ .../MediaInfo/SubtitleScheduledTask.cs | 90 +++++++-- .../Channels/ChannelDynamicMediaSourceProvider.cs | 2 +- .../Library/LibraryManager.cs | 19 ++ .../Library/MediaSourceManager.cs | 19 +- .../LiveTv/EmbyTV/EmbyTV.cs | 11 +- .../LiveTv/LiveTvManager.cs | 28 ++- .../LiveTv/LiveTvMediaSourceProvider.cs | 11 +- .../TunerHosts/HdHomerun/HdHomerunLiveStream.cs | 79 +++++++- .../Sync/SyncedMediaSourceProvider.cs | 4 +- .../ApplicationHost.cs | 1 - 35 files changed, 293 insertions(+), 625 deletions(-) delete mode 100644 MediaBrowser.Api/Library/ChapterService.cs delete mode 100644 MediaBrowser.Controller/Chapters/ChapterResponse.cs delete mode 100644 MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs delete mode 100644 MediaBrowser.Controller/Chapters/IChapterProvider.cs delete mode 100644 MediaBrowser.Model/Chapters/ChapterProviderInfo.cs delete mode 100644 MediaBrowser.Model/Chapters/RemoteChapterInfo.cs delete mode 100644 MediaBrowser.Model/Chapters/RemoteChapterResult.cs (limited to 'MediaBrowser.Server.Implementations') diff --git a/MediaBrowser.Api/Library/ChapterService.cs b/MediaBrowser.Api/Library/ChapterService.cs deleted file mode 100644 index 6b8dd18f1..000000000 --- a/MediaBrowser.Api/Library/ChapterService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MediaBrowser.Controller.Chapters; -using MediaBrowser.Controller.Net; -using ServiceStack; -using System.Linq; - -namespace MediaBrowser.Api.Library -{ - [Route("/Providers/Chapters", "GET")] - public class GetChapterProviders : IReturnVoid - { - } - - [Authenticated] - public class ChapterService : BaseApiService - { - private readonly IChapterManager _chapterManager; - - public ChapterService(IChapterManager chapterManager) - { - _chapterManager = chapterManager; - } - - public object Get(GetChapterProviders request) - { - var result = _chapterManager.GetProviders().ToList(); - - return ToOptimizedResult(result); - } - } -} diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 4baae031f..ed7a0990f 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -705,7 +705,7 @@ namespace MediaBrowser.Api.LiveTv var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath); + outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType(filePath); long startPosition = 0; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 77949d179..a98637650 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -79,7 +79,6 @@ - diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index bcf3bd28e..0b2fad580 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1888,7 +1888,9 @@ namespace MediaBrowser.Api.Playback } else { - mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false); + var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false); + mediaSource = liveStreamInfo.Item1; + state.DirectStreamProvider = liveStreamInfo.Item2; } var videoRequest = request as VideoStreamRequest; diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 4adf6fbca..809eabef8 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -121,32 +121,33 @@ namespace MediaBrowser.Api.Playback.Progressive var responseHeaders = new Dictionary(); - // Static remote stream - if (request.Static && state.InputProtocol == MediaProtocol.Http) + if (request.Static && state.DirectStreamProvider != null) { AddDlnaHeaders(state, responseHeaders, true); using (state) { - if (state.MediaPath.IndexOf("/livestreamfiles/", StringComparison.OrdinalIgnoreCase) != -1) - { - var parts = state.MediaPath.Split('/'); - var filename = parts[parts.Length - 2] + Path.GetExtension(parts[parts.Length - 1]); - var filePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, filename); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + // TODO: Don't hardcode this + outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts"); - outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath); + var streamSource = new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); + } + } - var streamSource = new ProgressiveFileCopier(FileSystem, filePath, outputHeaders, null, Logger, CancellationToken.None) - { - AllowEndOfFile = false - }; - return ResultFactory.GetAsyncStreamWriter(streamSource); - } + // Static remote stream + if (request.Static && state.InputProtocol == MediaProtocol.Http) + { + AddDlnaHeaders(state, responseHeaders, true); - return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource) - .ConfigureAwait(false); + using (state) + { + return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); } } diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index f601f4aa3..3477ad57b 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -7,6 +7,7 @@ using CommonIO; using MediaBrowser.Controller.Net; using System.Collections.Generic; using ServiceStack.Web; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Api.Playback.Progressive { @@ -26,6 +27,8 @@ namespace MediaBrowser.Api.Playback.Progressive public long StartPosition { get; set; } public bool AllowEndOfFile = true; + private IDirectStreamProvider _directStreamProvider; + public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _fileSystem = fileSystem; @@ -36,6 +39,15 @@ namespace MediaBrowser.Api.Playback.Progressive _cancellationToken = cancellationToken; } + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) + { + _directStreamProvider = directStreamProvider; + _outputHeaders = outputHeaders; + _job = job; + _logger = logger; + _cancellationToken = cancellationToken; + } + public IDictionary Options { get @@ -44,22 +56,33 @@ namespace MediaBrowser.Api.Playback.Progressive } } + private Stream GetInputStream() + { + return _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); + } + public async Task WriteToAsync(Stream outputStream) { try { + if (_directStreamProvider != null) + { + await _directStreamProvider.CopyToAsync(outputStream, _cancellationToken).ConfigureAwait(false); + return; + } + var eofCount = 0; - using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var inputStream = GetInputStream()) { if (StartPosition > 0) { - fs.Position = StartPosition; + inputStream.Position = StartPosition; } while (eofCount < 15 || !AllowEndOfFile) { - var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); + var bytesRead = await CopyToAsyncInternal(inputStream, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 863bc0193..a59a7fe09 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -37,6 +37,7 @@ namespace MediaBrowser.Api.Playback /// /// The log file stream. public Stream LogFileStream { get; set; } + public IDirectStreamProvider DirectStreamProvider { get; set; } public string InputContainer { get; set; } diff --git a/MediaBrowser.Controller/Chapters/ChapterResponse.cs b/MediaBrowser.Controller/Chapters/ChapterResponse.cs deleted file mode 100644 index 3c1b8ed07..000000000 --- a/MediaBrowser.Controller/Chapters/ChapterResponse.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Chapters -{ - public class ChapterResponse - { - /// - /// Gets or sets the chapters. - /// - /// The chapters. - public List Chapters { get; set; } - - public ChapterResponse() - { - Chapters = new List(); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs b/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs deleted file mode 100644 index 982dc35bb..000000000 --- a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Chapters -{ - public class ChapterSearchRequest : IHasProviderIds - { - public string Language { get; set; } - - public VideoContentType ContentType { get; set; } - - public string MediaPath { get; set; } - public string SeriesName { get; set; } - public string Name { get; set; } - public int? IndexNumber { get; set; } - public int? IndexNumberEnd { get; set; } - public int? ParentIndexNumber { get; set; } - public int? ProductionYear { get; set; } - public long? RuntimeTicks { get; set; } - public Dictionary ProviderIds { get; set; } - - public bool SearchAllProviders { get; set; } - - public ChapterSearchRequest() - { - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs index 27e06fb8d..4b39e66cc 100644 --- a/MediaBrowser.Controller/Chapters/IChapterManager.cs +++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs @@ -1,6 +1,4 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Configuration; @@ -13,12 +11,6 @@ namespace MediaBrowser.Controller.Chapters /// public interface IChapterManager { - /// - /// Adds the parts. - /// - /// The chapter providers. - void AddParts(IEnumerable chapterProviders); - /// /// Gets the chapters. /// @@ -35,43 +27,6 @@ namespace MediaBrowser.Controller.Chapters /// Task. Task SaveChapters(string itemId, List chapters, CancellationToken cancellationToken); - /// - /// Searches the specified video. - /// - /// The video. - /// The cancellation token. - /// Task{IEnumerable{RemoteChapterResult}}. - Task> Search(Video video, CancellationToken cancellationToken); - - /// - /// Searches the specified request. - /// - /// The request. - /// The cancellation token. - /// Task{IEnumerable{RemoteChapterResult}}. - Task> Search(ChapterSearchRequest request, CancellationToken cancellationToken); - - /// - /// Gets the chapters. - /// - /// The identifier. - /// The cancellation token. - /// Task{ChapterResponse}. - Task GetChapters(string id, CancellationToken cancellationToken); - - /// - /// Gets the providers. - /// - /// The item identifier. - /// IEnumerable{ChapterProviderInfo}. - IEnumerable GetProviders(string itemId); - - /// - /// Gets the providers. - /// - /// IEnumerable{ChapterProviderInfo}. - IEnumerable GetProviders(); - /// /// Gets the configuration. /// diff --git a/MediaBrowser.Controller/Chapters/IChapterProvider.cs b/MediaBrowser.Controller/Chapters/IChapterProvider.cs deleted file mode 100644 index a7505347b..000000000 --- a/MediaBrowser.Controller/Chapters/IChapterProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Chapters -{ - public interface IChapterProvider - { - /// - /// Gets the name. - /// - /// The name. - string Name { get; } - - /// - /// Gets the supported media types. - /// - /// The supported media types. - IEnumerable SupportedMediaTypes { get; } - - /// - /// Searches the specified request. - /// - /// The request. - /// The cancellation token. - /// Task{IEnumerable{RemoteChapterResult}}. - Task> Search(ChapterSearchRequest request, CancellationToken cancellationToken); - - /// - /// Gets the chapters. - /// - /// The identifier. - /// The cancellation token. - /// Task{ChapterResponse}. - Task GetChapters(string id, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index c06470c5e..1ab0e4cb0 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.IO; namespace MediaBrowser.Controller.Library { @@ -79,6 +80,8 @@ namespace MediaBrowser.Controller.Library /// The cancellation token. /// Task<MediaSourceInfo>. Task GetLiveStream(string id, CancellationToken cancellationToken); + + Task> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken); /// /// Pings the media source. @@ -95,4 +98,9 @@ namespace MediaBrowser.Controller.Library /// Task. Task CloseLiveStream(string id); } + + public interface IDirectStreamProvider + { + Task CopyToAsync(Stream stream, CancellationToken cancellationToken); + } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs index 56366e5a8..b0881ba7c 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Dto; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System; namespace MediaBrowser.Controller.Library { @@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Library /// The open token. /// The cancellation token. /// Task<MediaSourceInfo>. - Task OpenMediaSource(string openToken, CancellationToken cancellationToken); + Task> OpenMediaSource(string openToken, CancellationToken cancellationToken); /// /// Closes the media source. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index d65d1ae30..a381c7980 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Events; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.LiveTv { @@ -156,7 +157,7 @@ namespace MediaBrowser.Controller.LiveTv /// The media source identifier. /// The cancellation token. /// Task{StreamResponseInfo}. - Task GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken); + Task> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken); /// /// Gets the program. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index d7d8336d0..94082b42e 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.LiveTv { @@ -245,4 +246,9 @@ namespace MediaBrowser.Controller.LiveTv /// Task. Task CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken); } + + public interface ISupportsDirectStreamProvider + { + Task> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, CancellationToken cancellationToken); + } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index d70fba742..e9d2054da 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -91,10 +91,7 @@ - - - diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index ad7dea0a5..59e29087c 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -160,15 +160,6 @@ Channels\ChannelQuery.cs - - Chapters\ChapterProviderInfo.cs - - - Chapters\RemoteChapterInfo.cs - - - Chapters\RemoteChapterResult.cs - Collections\CollectionCreationResult.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 61f2f3f13..47ebb3a92 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -132,15 +132,6 @@ Channels\ChannelQuery.cs - - Chapters\ChapterProviderInfo.cs - - - Chapters\RemoteChapterInfo.cs - - - Chapters\RemoteChapterResult.cs - Collections\CollectionCreationResult.cs diff --git a/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs b/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs deleted file mode 100644 index 570407c57..000000000 --- a/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MediaBrowser.Model.Chapters -{ - public class ChapterProviderInfo - { - public string Name { get; set; } - public string Id { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs b/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs deleted file mode 100644 index f2674c842..000000000 --- a/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ - -namespace MediaBrowser.Model.Chapters -{ - public class RemoteChapterInfo - { - /// - /// Gets or sets the start position ticks. - /// - /// The start position ticks. - public long StartPositionTicks { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - } -} diff --git a/MediaBrowser.Model/Chapters/RemoteChapterResult.cs b/MediaBrowser.Model/Chapters/RemoteChapterResult.cs deleted file mode 100644 index 425c3ded8..000000000 --- a/MediaBrowser.Model/Chapters/RemoteChapterResult.cs +++ /dev/null @@ -1,48 +0,0 @@ - -namespace MediaBrowser.Model.Chapters -{ - public class RemoteChapterResult - { - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - - /// - /// Gets or sets the run time ticks. - /// - /// The run time ticks. - public long? RunTimeTicks { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the name of the provider. - /// - /// The name of the provider. - public string ProviderName { get; set; } - - /// - /// Gets or sets the community rating. - /// - /// The community rating. - public float? CommunityRating { get; set; } - - /// - /// Gets or sets the chapter count. - /// - /// The chapter count. - public int? ChapterCount { get; set; } - - /// - /// Gets or sets the name of the three letter iso language. - /// - /// The name of the three letter iso language. - public string ThreeLetterISOLanguageName { get; set; } - } -} diff --git a/MediaBrowser.Model/Configuration/ChapterOptions.cs b/MediaBrowser.Model/Configuration/ChapterOptions.cs index c7bb6f861..1c2b0c527 100644 --- a/MediaBrowser.Model/Configuration/ChapterOptions.cs +++ b/MediaBrowser.Model/Configuration/ChapterOptions.cs @@ -2,18 +2,10 @@ { public class ChapterOptions { - public bool DownloadMovieChapters { get; set; } - public bool DownloadEpisodeChapters { get; set; } + public bool EnableMovieChapterImageExtraction { get; set; } + public bool EnableEpisodeChapterImageExtraction { get; set; } + public bool EnableOtherVideoChapterImageExtraction { get; set; } - public string[] FetcherOrder { get; set; } - public string[] DisabledFetchers { get; set; } - - public ChapterOptions() - { - DownloadMovieChapters = true; - - DisabledFetchers = new string[] { }; - FetcherOrder = new string[] { }; - } + public bool ExtractDuringLibraryScan { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index c1a01680d..69754204e 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -84,9 +84,6 @@ - - - diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs index 88811c850..87aaafb39 100644 --- a/MediaBrowser.Providers/Chapters/ChapterManager.cs +++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs @@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Chapters; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -22,7 +21,6 @@ namespace MediaBrowser.Providers.Chapters { public class ChapterManager : IChapterManager { - private IChapterProvider[] _providers; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; @@ -36,224 +34,6 @@ namespace MediaBrowser.Providers.Chapters _itemRepo = itemRepo; } - public void AddParts(IEnumerable chapterProviders) - { - _providers = chapterProviders.ToArray(); - } - - public Task> Search(Video video, CancellationToken cancellationToken) - { - VideoContentType mediaType; - - if (video is Episode) - { - mediaType = VideoContentType.Episode; - } - else if (video is Movie) - { - mediaType = VideoContentType.Movie; - } - else - { - // These are the only supported types - return Task.FromResult>(new List()); - } - - var request = new ChapterSearchRequest - { - ContentType = mediaType, - IndexNumber = video.IndexNumber, - Language = video.GetPreferredMetadataLanguage(), - MediaPath = video.Path, - Name = video.Name, - ParentIndexNumber = video.ParentIndexNumber, - ProductionYear = video.ProductionYear, - ProviderIds = video.ProviderIds, - RuntimeTicks = video.RunTimeTicks, - SearchAllProviders = false - }; - - var episode = video as Episode; - - if (episode != null) - { - request.IndexNumberEnd = episode.IndexNumberEnd; - request.SeriesName = episode.SeriesName; - } - - return Search(request, cancellationToken); - } - - public async Task> Search(ChapterSearchRequest request, CancellationToken cancellationToken) - { - var contentType = request.ContentType; - var providers = GetInternalProviders(false) - .Where(i => i.SupportedMediaTypes.Contains(contentType)) - .ToList(); - - // If not searching all, search one at a time until something is found - if (!request.SearchAllProviders) - { - foreach (var provider in providers) - { - try - { - var currentResults = await Search(request, provider, cancellationToken).ConfigureAwait(false); - - if (currentResults.Count > 0) - { - return currentResults; - } - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading subtitles from {0}", ex, provider.Name); - } - } - return new List(); - } - - var tasks = providers.Select(async i => - { - try - { - return await Search(request, i, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading subtitles from {0}", ex, i.Name); - return new List(); - } - }); - - var results = await Task.WhenAll(tasks).ConfigureAwait(false); - - return results.SelectMany(i => i); - } - - private async Task> Search(ChapterSearchRequest request, - IChapterProvider provider, - CancellationToken cancellationToken) - { - var searchResults = await provider.Search(request, cancellationToken).ConfigureAwait(false); - - var list = searchResults.ToList(); - - foreach (var result in list) - { - result.Id = GetProviderId(provider.Name) + "_" + result.Id; - result.ProviderName = provider.Name; - } - - return list; - } - - public Task GetChapters(string id, CancellationToken cancellationToken) - { - var parts = id.Split(new[] { '_' }, 2); - - var provider = GetProvider(parts.First()); - id = parts.Last(); - - return provider.GetChapters(id, cancellationToken); - } - - public IEnumerable GetProviders(string itemId) - { - var video = _libraryManager.GetItemById(itemId) as Video; - VideoContentType mediaType; - - if (video is Episode) - { - mediaType = VideoContentType.Episode; - } - else if (video is Movie) - { - mediaType = VideoContentType.Movie; - } - else - { - // These are the only supported types - return new List(); - } - - var providers = GetInternalProviders(false) - .Where(i => i.SupportedMediaTypes.Contains(mediaType)); - - return GetInfos(providers); - } - - public IEnumerable GetProviders() - { - return GetInfos(GetInternalProviders(true)); - } - - private IEnumerable GetInternalProviders(bool includeDisabledProviders) - { - var providers = _providers; - - if (!includeDisabledProviders) - { - var options = GetConfiguration(); - - providers = providers - .Where(i => !options.DisabledFetchers.Contains(i.Name)) - .ToArray(); - } - - return providers - .OrderBy(GetConfiguredOrder) - .ThenBy(GetDefaultOrder) - .ToArray(); - } - - private IEnumerable GetInfos(IEnumerable providers) - { - return providers.Select(i => new ChapterProviderInfo - { - Name = i.Name, - Id = GetProviderId(i.Name) - }); - } - - private string GetProviderId(string name) - { - return name.ToLower().GetMD5().ToString("N"); - } - - private IChapterProvider GetProvider(string id) - { - return _providers.First(i => string.Equals(id, GetProviderId(i.Name))); - } - - private int GetConfiguredOrder(IChapterProvider provider) - { - var options = GetConfiguration(); - - // See if there's a user-defined order - var index = Array.IndexOf(options.FetcherOrder, provider.Name); - - if (index != -1) - { - return index; - } - - // Not configured. Just return some high number to put it at the end. - return 100; - } - - private int GetDefaultOrder(IChapterProvider provider) - { - var hasOrder = provider as IHasOrder; - - if (hasOrder != null) - { - return hasOrder.Order; - } - - return 0; - } - public IEnumerable GetChapters(string itemId) { return _itemRepo.GetChapters(new Guid(itemId)); diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 66fe7ea81..8c87e991e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -238,22 +238,6 @@ namespace MediaBrowser.Providers.MediaInfo if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || options.MetadataRefreshMode == MetadataRefreshMode.Default) { - var chapterOptions = _chapterManager.GetConfiguration(); - - try - { - var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false); - - if (remoteChapters.Count > 0) - { - chapters = remoteChapters; - } - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading chapters", ex); - } - if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video)) { AddDummyChapters(video, chapters); @@ -561,52 +545,6 @@ namespace MediaBrowser.Providers.MediaInfo currentStreams.AddRange(externalSubtitleStreams); } - private async Task> DownloadChapters(Video video, List currentChapters, ChapterOptions options, CancellationToken cancellationToken) - { - if ((options.DownloadEpisodeChapters && - video is Episode) || - (options.DownloadMovieChapters && - video is Movie)) - { - var results = await _chapterManager.Search(video, cancellationToken).ConfigureAwait(false); - - var result = results.FirstOrDefault(); - - if (result != null) - { - var chapters = await _chapterManager.GetChapters(result.Id, cancellationToken).ConfigureAwait(false); - - var chapterInfos = chapters.Chapters.Select(i => new ChapterInfo - { - Name = i.Name, - StartPositionTicks = i.StartPositionTicks - - }).ToList(); - - if (chapterInfos.All(i => i.StartPositionTicks == 0)) - { - if (currentChapters.Count >= chapterInfos.Count) - { - var index = 0; - foreach (var info in chapterInfos) - { - info.StartPositionTicks = currentChapters[index].StartPositionTicks; - index++; - } - } - else - { - chapterInfos.Clear(); - } - } - - return chapterInfos; - } - } - - return new List(); - } - /// /// The dummy chapter duration /// diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 79da291b7..2490f7145 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -14,6 +14,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.IO; +using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.MediaInfo { @@ -24,14 +26,16 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; + private IJsonSerializer _json; - public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager) + public SubtitleScheduledTask(ILibraryManager libraryManager, IJsonSerializer json, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager) { _libraryManager = libraryManager; _config = config; _subtitleManager = subtitleManager; _logger = logger; _mediaSourceManager = mediaSourceManager; + _json = json; } public string Name @@ -58,38 +62,65 @@ namespace MediaBrowser.Providers.MediaInfo { var options = GetOptions(); - var videos = _libraryManager.RootFolder - .GetRecursiveChildren(i => - { - if (!(i is Video)) - { - return false; - } + var types = new List(); - if (i.LocationType == LocationType.Remote || i.LocationType == LocationType.Virtual) - { - return false; - } + if (options.DownloadEpisodeSubtitles) + { + types.Add("Episode"); + } + if (options.DownloadMovieSubtitles) + { + types.Add("Movie"); + } - return (options.DownloadEpisodeSubtitles && - i is Episode) || - (options.DownloadMovieSubtitles && - i is Movie); - }) - .Cast