From c1208a2f8d73d4c567562a044b77dfe5e6941bd6 Mon Sep 17 00:00:00 2001 From: gerrit507 Date: Sun, 5 Mar 2017 04:02:03 +0100 Subject: [PATCH 01/67] Fix naming of nvenc codecs. It should be nvenc_h264. --- .../MediaEncoding/EncodingHelper.cs | 10 +++++----- MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ef356c7cd..090232ff0 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase)) { - return GetAvailableEncoder("h264_nvenc", defaultEncoder); + return GetAvailableEncoder("nvenc_h264", defaultEncoder); } if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase)) { @@ -591,8 +591,8 @@ namespace MediaBrowser.Controller.MediaEncoding } - // h264 (h264_nvenc) - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + // h264 (nvenc_h264) + else if (string.Equals(videoEncoder, "nvenc_h264", StringComparison.OrdinalIgnoreCase)) { param += "-preset default"; } @@ -670,10 +670,10 @@ namespace MediaBrowser.Controller.MediaEncoding { var level = NormalizeTranscodingLevel(state.OutputVideoCodec, request.Level); - // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format + // h264_qsv and nvenc_h264 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, "nvenc_h264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { switch (level) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index b37e783b8..7bcbc97eb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -133,7 +133,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "libopus", "libvorbis", "srt", - "h264_nvenc", + "nvenc_h264", "h264_qsv", "h264_omx", "h264_vaapi", From 46b6235b8a974c5f8ab880af97d498c635c3365a Mon Sep 17 00:00:00 2001 From: gerrit507 Date: Sun, 5 Mar 2017 13:25:36 +0100 Subject: [PATCH 02/67] Revert "Fix naming of nvenc codecs. It should be nvenc_h264." This reverts commit 44ae8c94fc720fc14d4fc551cd375006691d3d3e. --- .../MediaEncoding/EncodingHelper.cs | 10 +++++----- MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 090232ff0..ef356c7cd 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase)) { - return GetAvailableEncoder("nvenc_h264", defaultEncoder); + return GetAvailableEncoder("h264_nvenc", defaultEncoder); } if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase)) { @@ -591,8 +591,8 @@ namespace MediaBrowser.Controller.MediaEncoding } - // h264 (nvenc_h264) - else if (string.Equals(videoEncoder, "nvenc_h264", StringComparison.OrdinalIgnoreCase)) + // h264 (h264_nvenc) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) { param += "-preset default"; } @@ -670,10 +670,10 @@ namespace MediaBrowser.Controller.MediaEncoding { var level = NormalizeTranscodingLevel(state.OutputVideoCodec, request.Level); - // h264_qsv and nvenc_h264 expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format + // 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, "nvenc_h264", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { switch (level) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 7bcbc97eb..b37e783b8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -133,7 +133,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "libopus", "libvorbis", "srt", - "nvenc_h264", + "h264_nvenc", "h264_qsv", "h264_omx", "h264_vaapi", From a74373e851f98c66ce39f73b288419a3781b1449 Mon Sep 17 00:00:00 2001 From: gerrit507 Date: Sun, 5 Mar 2017 13:35:23 +0100 Subject: [PATCH 03/67] Remove level param for nvenc, because it fails to encode with it. --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ef356c7cd..8afee9f35 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -671,9 +671,8 @@ namespace MediaBrowser.Controller.MediaEncoding var level = NormalizeTranscodingLevel(state.OutputVideoCodec, request.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 + // 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, "libx264", StringComparison.OrdinalIgnoreCase)) { switch (level) @@ -710,10 +709,15 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } + // nvenc doesn't decode with param -level set ?! + if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)){ + param += ""; + } else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; } + } if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) From fb7c7c578d6ce2e23eb814a3addcd47f872fd865 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 8 Mar 2017 14:29:21 -0500 Subject: [PATCH 04/67] 3.2.7.1 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index f9ef9432f..635a449a6 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.6.2")] +[assembly: AssemblyVersion("3.2.7.1")] From 8931b949f88f5f5a40544354bec2baa70c8878d8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 10 Mar 2017 13:32:38 -0500 Subject: [PATCH 05/67] generate genre images --- .../Images/BaseDynamicImageProvider.cs | 2 +- .../Playlists/PlaylistImageProvider.cs | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 7a36691df..38908c2bd 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Images { return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); } - if (item is Playlist || item is MusicGenre) + if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre) { return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index ef7d6dba8..9514c12ca 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading.Tasks; using Emby.Server.Implementations.Images; using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Library; @@ -101,4 +102,35 @@ namespace Emby.Server.Implementations.Playlists //} } + public class GenreImageProvider : BaseDynamicImageProvider + { + private readonly ILibraryManager _libraryManager; + + public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) + { + _libraryManager = libraryManager; + } + + protected override Task> GetItemsWithImages(IHasImages item) + { + var items = _libraryManager.GetItemList(new InternalItemsQuery + { + Genres = new[] { item.Name }, + IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name }, + SortBy = new[] { ItemSortBy.Random }, + Limit = 4, + Recursive = true, + ImageTypes = new[] { ImageType.Primary } + + }).ToList(); + + return Task.FromResult(GetFinalItems(items)); + } + + //protected override Task CreateImage(IHasImages item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + //{ + // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); + //} + } + } From fc2a5acfca4b12f1f5b7dc17ce34bdd890641dc6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 10 Mar 2017 13:33:17 -0500 Subject: [PATCH 06/67] move loopback util --- Emby.Server.Core/ApplicationHost.cs | 46 ++- .../Emby.Server.Implementations.csproj | 1 + MediaBrowser.Api/Playback/MediaInfoService.cs | 70 ++-- .../MediaEncoding/EncodingHelper.cs | 5 + .../MediaInfo/PlaybackInfoRequest.cs | 2 + MediaBrowser.Server.Mac/MacAppHost.cs | 36 -- .../MediaBrowser.ServerApplication.csproj | 1 - .../Native/LoopbackUtil.cs | 358 ------------------ .../WindowsAppHost.cs | 6 +- .../Net/HttpListenerRequest.cs | 2 +- 10 files changed, 82 insertions(+), 445 deletions(-) delete mode 100644 MediaBrowser.ServerApplication/Native/LoopbackUtil.cs diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 4425d1a0b..7dbc7760b 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -107,7 +107,7 @@ using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations; using Emby.Server.Implementations.ServerManager; using Emby.Server.Implementations.Session; -using Emby.Server.Implementations.Social; +using Emby.Server.Implementations.Windows; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using MediaBrowser.Model.Activity; @@ -796,17 +796,25 @@ namespace Emby.Server.Core info.FFMpegFilename = "ffmpeg"; info.FFProbeFilename = "ffprobe"; info.ArchiveType = "7z"; - info.Version = "20160215"; + info.Version = "20170308"; info.DownloadUrls = GetLinuxDownloadUrls(); } else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) { info.FFMpegFilename = "ffmpeg.exe"; info.FFProbeFilename = "ffprobe.exe"; - info.Version = "20160410"; + info.Version = "20170308"; info.ArchiveType = "7z"; info.DownloadUrls = GetWindowsDownloadUrls(); } + else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) + { + info.FFMpegFilename = "ffmpeg"; + info.FFProbeFilename = "ffprobe"; + info.ArchiveType = "7z"; + info.Version = "20170308"; + info.DownloadUrls = GetMacDownloadUrls(); + } else { // No version available - user requirement @@ -816,6 +824,20 @@ namespace Emby.Server.Core return info; } + private string[] GetMacDownloadUrls() + { + switch (EnvironmentInfo.SystemArchitecture) + { + case Architecture.X64: + return new[] + { + "https://embydata.com/downloads/ffmpeg/osx/ffmpeg-x64-20170308.7z" + }; + } + + return new string[] { }; + } + private string[] GetWindowsDownloadUrls() { switch (EnvironmentInfo.SystemArchitecture) @@ -823,12 +845,12 @@ namespace Emby.Server.Core case Architecture.X64: return new[] { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win64.7z" + "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win64.7z" }; case Architecture.X86: return new[] { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win32.7z" + "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win32.7z" }; } @@ -842,12 +864,12 @@ namespace Emby.Server.Core case Architecture.X64: return new[] { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z" + "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-64bit-static.7z" }; case Architecture.X86: return new[] { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z" + "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-32bit-static.7z" }; } @@ -1716,12 +1738,10 @@ namespace Emby.Server.Core public void EnableLoopback(string appName) { - EnableLoopbackInternal(appName); - } - - protected virtual void EnableLoopbackInternal(string appName) - { - + if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) + { + LoopUtil.Run(appName); + } } private void RegisterModules() diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index c704d0e4e..9affe0fdd 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -269,6 +269,7 @@ + diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index ed8449b83..4e4e8858e 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -127,7 +127,7 @@ namespace MediaBrowser.Api.Playback SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, - request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, true, true, true); + request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, true, true, true, true); } else { @@ -169,7 +169,7 @@ namespace MediaBrowser.Api.Playback { var mediaSourceId = request.MediaSourceId; - SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, request.EnableDirectStream, request.EnableTranscoding); + SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, request.ForceDirectPlayRemoteMediaSource, request.EnableDirectStream, request.EnableTranscoding); } return info; @@ -253,6 +253,7 @@ namespace MediaBrowser.Api.Playback int? maxAudioChannels, string userId, bool enableDirectPlay, + bool forceDirectPlayRemoteMediaSource, bool enableDirectStream, bool enableTranscoding) { @@ -260,7 +261,7 @@ namespace MediaBrowser.Api.Playback foreach (var mediaSource in result.MediaSources) { - SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, enableDirectStream, enableTranscoding); + SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, forceDirectPlayRemoteMediaSource, enableDirectStream, enableTranscoding); } SortMediaSources(result, maxBitrate); @@ -279,6 +280,7 @@ namespace MediaBrowser.Api.Playback string playSessionId, string userId, bool enableDirectPlay, + bool forceDirectPlayRemoteMediaSource, bool enableDirectStream, bool enableTranscoding) { @@ -318,43 +320,49 @@ namespace MediaBrowser.Api.Playback if (mediaSource.SupportsDirectPlay) { - var supportsDirectStream = mediaSource.SupportsDirectStream; - - // Dummy this up to fool StreamBuilder - mediaSource.SupportsDirectStream = true; - options.MaxBitrate = maxBitrate; - - if (item is Audio) + if (mediaSource.IsRemote && forceDirectPlayRemoteMediaSource) { - if (!user.Policy.EnableAudioPlaybackTranscoding) + } + else + { + var supportsDirectStream = mediaSource.SupportsDirectStream; + + // Dummy this up to fool StreamBuilder + mediaSource.SupportsDirectStream = true; + options.MaxBitrate = maxBitrate; + + if (item is Audio) { - options.ForceDirectPlay = true; + if (!user.Policy.EnableAudioPlaybackTranscoding) + { + options.ForceDirectPlay = true; + } } - } - else if (item is Video) - { - if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing) + else if (item is Video) { - options.ForceDirectPlay = true; + if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing) + { + options.ForceDirectPlay = true; + } } - } - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectPlay = false; - } + if (streamInfo == null || !streamInfo.IsDirectStream) + { + mediaSource.SupportsDirectPlay = false; + } - // Set this back to what it was - mediaSource.SupportsDirectStream = supportsDirectStream; + // Set this back to what it was + mediaSource.SupportsDirectStream = supportsDirectStream; - if (streamInfo != null) - { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + if (streamInfo != null) + { + SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); + } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 80b9cc154..ebcc9853a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1670,6 +1670,11 @@ namespace MediaBrowser.Controller.MediaEncoding case "h264": if (_mediaEncoder.SupportsDecoder("h264_qsv")) { + // qsv decoder does not support 10-bit input + if ((state.VideoStream.BitDepth ?? 8) > 8) + { + return null; + } return "-c:v h264_qsv "; } break; diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index 57a2254b0..16c9464ac 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -27,9 +27,11 @@ namespace MediaBrowser.Model.MediaInfo public bool EnableDirectPlay { get; set; } public bool EnableDirectStream { get; set; } public bool EnableTranscoding { get; set; } + public bool ForceDirectPlayRemoteMediaSource { get; set; } public PlaybackInfoRequest() { + ForceDirectPlayRemoteMediaSource = true; EnableDirectPlay = true; EnableDirectStream = true; EnableTranscoding = true; diff --git a/MediaBrowser.Server.Mac/MacAppHost.cs b/MediaBrowser.Server.Mac/MacAppHost.cs index 59e72e71e..f84e96126 100644 --- a/MediaBrowser.Server.Mac/MacAppHost.cs +++ b/MediaBrowser.Server.Mac/MacAppHost.cs @@ -56,42 +56,6 @@ namespace MediaBrowser.Server.Mac return new SyncManager(); } - protected override FFMpegInstallInfo GetFfmpegInstallInfo() - { - var info = new FFMpegInstallInfo(); - - info.ArchiveType = "7z"; - - switch (EnvironmentInfo.SystemArchitecture) - { - case Architecture.X64: - info.Version = "20160124"; - break; - case Architecture.X86: - info.Version = "20150110"; - break; - } - - info.DownloadUrls = GetDownloadUrls(); - - return info; - } - - private string[] GetDownloadUrls() - { - switch (EnvironmentInfo.SystemArchitecture) - { - case Architecture.X64: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z" - }; - } - - // No version available - return new string[] { }; - } - protected override void RestartInternal() { MainClass.Restart(); diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 7ebbc3809..02916f555 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -143,7 +143,6 @@ - diff --git a/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs b/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs deleted file mode 100644 index 5b260685b..000000000 --- a/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs +++ /dev/null @@ -1,358 +0,0 @@ -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(string appName) - { - var util = new LoopUtil(); - util.LoadApps(); - - var hasChanges = util.CreateExcemptions(appName); - - 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.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index cc899462a..915a2fa86 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -10,6 +10,7 @@ using Emby.Server.Core; using Emby.Server.Implementations; using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.FFMpeg; +using Emby.Server.Implementations.Windows; using Emby.Server.Sync; using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Sync; @@ -107,11 +108,6 @@ namespace MediaBrowser.ServerApplication } } - protected override void EnableLoopbackInternal(string appName) - { - LoopUtil.Run(appName); - } - public override bool SupportsRunningAsService { get diff --git a/SocketHttpListener.Portable/Net/HttpListenerRequest.cs b/SocketHttpListener.Portable/Net/HttpListenerRequest.cs index 811cc6ddb..cfbd49203 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerRequest.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerRequest.cs @@ -456,7 +456,7 @@ namespace SocketHttpListener.Net public long ContentLength64 { - get { return content_length; } + get { return is_chunked ? -1 : content_length; } } public string ContentType From d6b0dcaddbeed84962a0ce07e9404f22ddeead02 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 10 Mar 2017 13:33:38 -0500 Subject: [PATCH 07/67] update components --- .../Windows/LoopUtil.cs | 358 ++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 Emby.Server.Implementations/Windows/LoopUtil.cs diff --git a/Emby.Server.Implementations/Windows/LoopUtil.cs b/Emby.Server.Implementations/Windows/LoopUtil.cs new file mode 100644 index 000000000..6eded2cec --- /dev/null +++ b/Emby.Server.Implementations/Windows/LoopUtil.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 Emby.Server.Implementations.Windows +{ + /// + /// 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(string appName) + { + var util = new LoopUtil(); + util.LoadApps(); + + var hasChanges = util.CreateExcemptions(appName); + + 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); + } + + } +} From 79b28e2465b23b015694e67f665d0ff7671fdff5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 10 Mar 2017 13:34:07 -0500 Subject: [PATCH 08/67] 3.2.7.2 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 635a449a6..e3fd5e349 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.7.1")] +[assembly: AssemblyVersion("3.2.7.2")] From 38badd4d28e6538e1c33b0d714b50cb24f0f6897 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 10 Mar 2017 14:51:29 -0500 Subject: [PATCH 09/67] rework file system libs --- .../IO}/LnkShortcutHandler.cs | 3 +-- Emby.Common.Implementations/IO/ManagedFileSystem.cs | 11 ++++++----- Emby.Server.Core/ApplicationHost.cs | 11 +++++++++-- MediaBrowser.Model/IO/IFileSystem.cs | 2 ++ MediaBrowser.Server.Mac/Main.cs | 7 +++---- MediaBrowser.Server.Mono/Native/MonoFileSystem.cs | 5 +++-- MediaBrowser.Server.Mono/Program.cs | 7 +++---- MediaBrowser.ServerApplication/MainStartup.cs | 8 ++++---- .../MediaBrowser.ServerApplication.csproj | 1 - MediaBrowser.ServerApplication/WindowsAppHost.cs | 1 + 10 files changed, 32 insertions(+), 24 deletions(-) rename {MediaBrowser.ServerApplication/Native => Emby.Common.Implementations/IO}/LnkShortcutHandler.cs (99%) diff --git a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs similarity index 99% rename from MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs rename to Emby.Common.Implementations/IO/LnkShortcutHandler.cs index b4a87b9b4..5d5f46057 100644 --- a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs +++ b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs @@ -6,7 +6,7 @@ using System.Security; using System.Text; using MediaBrowser.Model.IO; -namespace MediaBrowser.ServerApplication.Native +namespace Emby.Common.Implementations.IO { public class LnkShortcutHandler :IShortcutHandler { @@ -35,7 +35,6 @@ namespace MediaBrowser.ServerApplication.Native /// /// Class NativeMethods /// - [SuppressUnmanagedCodeSecurity] public static class NativeMethods { /// diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index 3fe20f659..2c9388a41 100644 --- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; namespace Emby.Common.Implementations.IO { @@ -18,17 +19,17 @@ namespace Emby.Common.Implementations.IO private readonly bool _supportsAsyncFileStreams; private char[] _invalidFileNameChars; private readonly List _shortcutHandlers = new List(); - private bool EnableFileSystemRequestConcat = true; + private bool EnableFileSystemRequestConcat; private string _tempPath; - public ManagedFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, bool enableFileSystemRequestConcat, string tempPath) + public ManagedFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath) { Logger = logger; - _supportsAsyncFileStreams = supportsAsyncFileStreams; + _supportsAsyncFileStreams = true; _tempPath = tempPath; - EnableFileSystemRequestConcat = enableFileSystemRequestConcat; - SetInvalidFileNameChars(enableManagedInvalidFileNameChars); + EnableFileSystemRequestConcat = false; + SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows); } public void AddShortcutHandler(IShortcutHandler handler) diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 7dbc7760b..6133a3343 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -61,7 +61,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Common.Implementations; using Emby.Common.Implementations.Archiving; -using Emby.Common.Implementations.Networking; +using Emby.Common.Implementations.IO; using Emby.Common.Implementations.Reflection; using Emby.Common.Implementations.Serialization; using Emby.Common.Implementations.TextEncoding; @@ -93,7 +93,7 @@ using Emby.Server.Implementations.Social; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Dto; -using Emby.Server.Implementations.EntryPoints; +using Emby.Server.Implementations.IO; using Emby.Server.Implementations.FileOrganization; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.Security; @@ -294,6 +294,13 @@ namespace Emby.Server.Core ImageEncoder = imageEncoder; SetBaseExceptionMessage(); + + if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) + { + fileSystem.AddShortcutHandler(new LnkShortcutHandler()); + } + + fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); } private Version _version; diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index f6d1bb351..f90119cf3 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -10,6 +10,8 @@ namespace MediaBrowser.Model.IO /// public interface IFileSystem { + void AddShortcutHandler(IShortcutHandler handler); + /// /// Determines whether the specified filename is shortcut. /// diff --git a/MediaBrowser.Server.Mac/Main.cs b/MediaBrowser.Server.Mac/Main.cs index debd5f539..d703f7d0d 100644 --- a/MediaBrowser.Server.Mac/Main.cs +++ b/MediaBrowser.Server.Mac/Main.cs @@ -105,13 +105,12 @@ namespace MediaBrowser.Server.Mac // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); - var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), false, false, appPaths.TempDirectory); - fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + var environmentInfo = GetEnvironmentInfo(); + + var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory); _fileSystem = fileSystem; - var environmentInfo = GetEnvironmentInfo(); - var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, diff --git a/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs b/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs index a5dc691a7..91c064efe 100644 --- a/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs +++ b/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs @@ -1,13 +1,14 @@ using Emby.Common.Implementations.IO; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; using Mono.Unix.Native; namespace MediaBrowser.Server.Mono.Native { public class MonoFileSystem : ManagedFileSystem { - public MonoFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, string tempPath) - : base(logger, supportsAsyncFileStreams, enableManagedInvalidFileNameChars, true, tempPath) + public MonoFileSystem(ILogger logger, IEnvironmentInfo environment, string tempPath) + : base(logger, environment, tempPath) { } diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index 649283410..66851f7e9 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -114,13 +114,12 @@ namespace MediaBrowser.Server.Mono // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); - var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), false, false, appPaths.TempDirectory); - fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + var environmentInfo = GetEnvironmentInfo(); + + var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory); FileSystem = fileSystem; - var environmentInfo = GetEnvironmentInfo(); - var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); _appHost = new MonoAppHost(appPaths, diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index b41e7607c..b02c5d6ac 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -331,9 +331,9 @@ namespace MediaBrowser.ServerApplication /// The options. private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService, StartupOptions options) { - var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), true, true, false, appPaths.TempDirectory); - fileSystem.AddShortcutHandler(new LnkShortcutHandler()); - fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + var environmentInfo = new EnvironmentInfo(); + + var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory); var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); @@ -345,7 +345,7 @@ namespace MediaBrowser.ServerApplication fileSystem, new PowerManagement(), "emby.windows.zip", - new EnvironmentInfo(), + environmentInfo, imageEncoder, new Server.Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")), new RecyclableMemoryStreamProvider(), diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 02916f555..63e10df76 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -142,7 +142,6 @@ MainForm.cs - diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index 915a2fa86..2d3d8a85b 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.InteropServices.ComTypes; +using Emby.Common.Implementations.IO; using Emby.Server.CinemaMode; using Emby.Server.Connect; using Emby.Server.Core; From a60c85f4f8b9e6828aa999e4c443cbef336611b0 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 11 Mar 2017 23:26:46 -0500 Subject: [PATCH 10/67] update naming project --- .../Emby.Server.Implementations.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 9affe0fdd..4e9b7c4c9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -303,8 +303,8 @@ ..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll True - - ..\packages\MediaBrowser.Naming.1.0.4\lib\portable-net45+win8\MediaBrowser.Naming.dll + + ..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll True From 29185eb9bd964e800b29282d91c8e36828460eb3 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 11 Mar 2017 23:27:06 -0500 Subject: [PATCH 11/67] update ResponseHelper --- .../Services/ResponseHelper.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index d4ce1cabf..eea4bf049 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -141,16 +141,44 @@ namespace Emby.Server.Implementations.Services response.ContentType += "; charset=utf-8"; } - var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); - if (writeToOutputStreamResult) + var asyncStreamWriter = result as IAsyncStreamWriter; + if (asyncStreamWriter != null) { + await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); + return; + } + + var streamWriter = result as IStreamWriter; + if (streamWriter != null) + { + streamWriter.WriteTo(response.OutputStream); + return; + } + + var stream = result as Stream; + if (stream != null) + { + using (stream) + { + await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); + return; + } + } + + var bytes = result as byte[]; + if (bytes != null) + { + response.ContentType = "application/octet-stream"; + response.SetContentLength(bytes.Length); + + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; } var responseText = result as string; if (responseText != null) { - var bytes = Encoding.UTF8.GetBytes(responseText); + bytes = Encoding.UTF8.GetBytes(responseText); response.SetContentLength(bytes.Length); await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; @@ -163,7 +191,7 @@ namespace Emby.Server.Implementations.Services { var contentType = request.ResponseContentType; var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); - + using (var ms = new MemoryStream()) { serializer(result, ms); From a660aa001eb31e91d040e066787fa764cf5f0fb4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 12 Mar 2017 15:27:26 -0400 Subject: [PATCH 12/67] re-organize file streaming --- .../Net/NetAcceptSocket.cs | 43 +++- .../Net/SocketFactory.cs | 1 + Emby.Server.Core/ApplicationHost.cs | 2 +- Emby.Server.Core/HttpServerFactory.cs | 4 +- .../Emby.Server.Implementations.csproj | 1 + .../HttpServer/FileWriter.cs | 188 ++++++++++++++++++ .../HttpServer/HttpListenerHost.cs | 8 +- .../HttpServer/HttpResultFactory.cs | 43 ++-- .../SocketSharp/WebSocketSharpListener.cs | 6 +- .../SocketSharp/WebSocketSharpResponse.cs | 8 + .../LiveTv/EmbyTV/EmbyTV.cs | 11 + .../Services/ResponseHelper.cs | 46 +---- Emby.Server.Implementations/packages.config | 2 +- .../Entities/IHasImages.cs | 2 + .../Net/StaticResultOptions.cs | 13 +- MediaBrowser.Model/Net/IAcceptSocket.cs | 3 + .../Services/IAsyncStreamWriter.cs | 5 + MediaBrowser.Model/Services/IRequest.cs | 5 +- SharedVersion.cs | 2 +- .../Net/EndPointListener.cs | 6 +- .../Net/EndPointManager.cs | 3 +- .../Net/HttpConnection.cs | 16 +- .../Net/HttpListener.cs | 12 +- .../Net/HttpListenerContext.cs | 6 +- .../Net/HttpListenerResponse.cs | 14 +- .../Net/ResponseStream.cs | 84 +++++++- 26 files changed, 438 insertions(+), 96 deletions(-) create mode 100644 Emby.Server.Implementations/HttpServer/FileWriter.cs diff --git a/Emby.Common.Implementations/Net/NetAcceptSocket.cs b/Emby.Common.Implementations/Net/NetAcceptSocket.cs index 731ad1b74..e21ffe553 100644 --- a/Emby.Common.Implementations/Net/NetAcceptSocket.cs +++ b/Emby.Common.Implementations/Net/NetAcceptSocket.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Sockets; using System.Threading; +using System.Threading.Tasks; using Emby.Common.Implementations.Networking; using MediaBrowser.Model.Net; using MediaBrowser.Model.Logging; @@ -59,7 +60,7 @@ namespace Emby.Common.Implementations.Net #if NET46 Socket.Close(); #else - Socket.Dispose(); + Socket.Dispose(); #endif } @@ -96,6 +97,46 @@ namespace Emby.Common.Implementations.Net _acceptor.StartAccept(); } +#if NET46 + public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken) + { + var options = TransmitFileOptions.Disconnect | TransmitFileOptions.ReuseSocket | TransmitFileOptions.UseKernelApc; + + var completionSource = new TaskCompletionSource(); + + var result = Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), new Tuple>(Socket, path, completionSource)); + + return completionSource.Task; + } + + private void FileSendCallback(IAsyncResult ar) + { + // Retrieve the socket from the state object. + Tuple> data = (Tuple>)ar.AsyncState; + + var client = data.Item1; + var path = data.Item2; + var taskCompletion = data.Item3; + + // Complete sending the data to the remote device. + try { + client.EndSendFile(ar); + taskCompletion.TrySetResult(true); +} + catch(SocketException ex){ + _logger.Info("Socket.SendFile failed for {0}. error code {1}", path, ex.SocketErrorCode); + taskCompletion.TrySetException(ex); +}catch(Exception ex){ + taskCompletion.TrySetException(ex); +} + } +#else + public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +#endif + public void Dispose() { Socket.Dispose(); diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 523f4da85..021613e57 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -59,6 +59,7 @@ namespace Emby.Common.Implementations.Net if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", "remotePort"); var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 6133a3343..971378ea7 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -613,7 +613,7 @@ namespace Emby.Server.Core CertificatePath = GetCertificatePath(true); Certificate = GetCertificate(CertificatePath); - HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, SupportsDualModeSockets); + HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, FileSystemManager, SupportsDualModeSockets); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); RegisterSingleInstance(HttpServer, false); progress.Report(10); diff --git a/Emby.Server.Core/HttpServerFactory.cs b/Emby.Server.Core/HttpServerFactory.cs index deed3c6f3..dfd435c33 100644 --- a/Emby.Server.Core/HttpServerFactory.cs +++ b/Emby.Server.Core/HttpServerFactory.cs @@ -45,6 +45,7 @@ namespace Emby.Server.Core IXmlSerializer xml, IEnvironmentInfo environment, ICertificate certificate, + IFileSystem fileSystem, bool enableDualModeSockets) { var logger = logManager.GetLogger("HttpServer"); @@ -65,7 +66,8 @@ namespace Emby.Server.Core certificate, new StreamFactory(), GetParseFn, - enableDualModeSockets); + enableDualModeSockets, + fileSystem); } private static Func GetParseFn(Type propertyType) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 4e9b7c4c9..d873cd77f 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -84,6 +84,7 @@ + diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs new file mode 100644 index 000000000..b80a40962 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.HttpServer +{ + public class FileWriter : IHttpResult + { + private ILogger Logger { get; set; } + + private string RangeHeader { get; set; } + private bool IsHeadRequest { get; set; } + + private long RangeStart { get; set; } + private long RangeEnd { get; set; } + private long RangeLength { get; set; } + private long TotalContentLength { get; set; } + + public Action OnComplete { get; set; } + public Action OnError { get; set; } + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + public List Cookies { get; private set; } + + /// + /// The _options + /// + private readonly IDictionary _options = new Dictionary(); + /// + /// Gets the options. + /// + /// The options. + public IDictionary Headers + { + get { return _options; } + } + + public string Path { get; set; } + + public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem) + { + if (string.IsNullOrEmpty(contentType)) + { + throw new ArgumentNullException("contentType"); + } + + Path = path; + Logger = logger; + RangeHeader = rangeHeader; + + Headers["Content-Type"] = contentType; + + TotalContentLength = fileSystem.GetFileInfo(path).Length; + + if (string.IsNullOrWhiteSpace(rangeHeader)) + { + Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); + StatusCode = HttpStatusCode.OK; + } + else + { + Headers["Accept-Ranges"] = "bytes"; + StatusCode = HttpStatusCode.PartialContent; + SetRangeValues(); + } + + Cookies = new List(); + } + + /// + /// Sets the range values. + /// + private void SetRangeValues() + { + var requestedRange = RequestedRanges[0]; + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + RangeEnd = TotalContentLength - 1; + } + else + { + RangeEnd = requestedRange.Value.Value; + } + + RangeStart = requestedRange.Key; + RangeLength = 1 + RangeEnd - RangeStart; + + // Content-Length is the length of what we're serving, not the original content + Headers["Content-Length"] = RangeLength.ToString(UsCulture); + Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + } + + /// + /// The _requested ranges + /// + private List> _requestedRanges; + /// + /// Gets the requested ranges. + /// + /// The requested ranges. + protected List> RequestedRanges + { + get + { + if (_requestedRanges == null) + { + _requestedRanges = new List>(); + + // Example: bytes=0-,32-63 + var ranges = RangeHeader.Split('=')[1].Split(','); + + foreach (var range in ranges) + { + var vals = range.Split('-'); + + long start = 0; + long? end = null; + + if (!string.IsNullOrEmpty(vals[0])) + { + start = long.Parse(vals[0], UsCulture); + } + if (!string.IsNullOrEmpty(vals[1])) + { + end = long.Parse(vals[1], UsCulture); + } + + _requestedRanges.Add(new KeyValuePair(start, end)); + } + } + + return _requestedRanges; + } + } + + public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken) + { + try + { + // Headers only + if (IsHeadRequest) + { + return; + } + + if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) + { + Logger.Info("Transmit file {0}", Path); + await response.TransmitFile(Path, 0, 0, cancellationToken).ConfigureAwait(false); + return; + } + + await response.TransmitFile(Path, RangeStart, RangeEnd, cancellationToken).ConfigureAwait(false); + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } + } + + public string ContentType { get; set; } + + public IRequest RequestContext { get; set; } + + public object Response { get; set; } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get { return (HttpStatusCode)Status; } + set { Status = (int)value; } + } + + public string StatusDescription { get; set; } + + } +} diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index b5a3c2992..6d15cc619 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.HttpServer private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; + private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; private readonly ICertificate _certificate; @@ -70,8 +71,7 @@ namespace Emby.Server.Implementations.HttpServer ILogger logger, IServerConfigurationManager config, string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func> funcParseFn, bool enableDualModeSockets) - : base() + string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) { Instance = this; @@ -89,6 +89,7 @@ namespace Emby.Server.Implementations.HttpServer _streamFactory = streamFactory; _funcParseFn = funcParseFn; _enableDualModeSockets = enableDualModeSockets; + _fileSystem = fileSystem; _config = config; _logger = logger; @@ -226,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer _cryptoProvider, _streamFactory, _enableDualModeSockets, - GetRequest); + GetRequest, + _fileSystem); } private IHttpRequest GetRequest(HttpListenerContext httpContext) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 6bfd83110..e3f105941 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -474,10 +474,6 @@ namespace Emby.Server.Implementations.HttpServer { throw new ArgumentNullException("cacheKey"); } - if (options.ContentFactory == null) - { - throw new ArgumentNullException("factoryFn"); - } var key = cacheKey.ToString("N"); @@ -560,30 +556,43 @@ namespace Emby.Server.Implementations.HttpServer { var rangeHeader = requestContext.Headers.Get("Range"); - var stream = await factoryFn().ConfigureAwait(false); + if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path) && options.FileShare == FileShareMode.Read) + { + return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) + { + OnComplete = options.OnComplete, + OnError = options.OnError + }; + } if (!string.IsNullOrEmpty(rangeHeader)) { + var stream = await factoryFn().ConfigureAwait(false); + return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) { OnComplete = options.OnComplete }; } - - responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); - - if (isHeadRequest) + else { - stream.Dispose(); + var stream = await factoryFn().ConfigureAwait(false); - return GetHttpResult(new byte[] { }, contentType, true); + responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); + + if (isHeadRequest) + { + stream.Dispose(); + + return GetHttpResult(new byte[] { }, contentType, true); + } + + return new StreamWriter(stream, contentType, _logger) + { + OnComplete = options.OnComplete, + OnError = options.OnError + }; } - - return new StreamWriter(stream, contentType, _logger) - { - OnComplete = options.OnComplete, - OnError = options.OnError - }; } using (var stream = await factoryFn().ConfigureAwait(false)) diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index 652fc4f83..b11b2fe88 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -27,10 +27,11 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; private readonly IStreamFactory _streamFactory; + private readonly IFileSystem _fileSystem; private readonly Func _httpRequestFactory; private readonly bool _enableDualMode; - public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func httpRequestFactory) + public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func httpRequestFactory, IFileSystem fileSystem) { _logger = logger; _certificate = certificate; @@ -42,6 +43,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _streamFactory = streamFactory; _enableDualMode = enableDualMode; _httpRequestFactory = httpRequestFactory; + _fileSystem = fileSystem; } public Action ErrorHandler { get; set; } @@ -54,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Start(IEnumerable urlPrefixes) { if (_listener == null) - _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider); + _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem); _listener.EnableDualMode = _enableDualMode; diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index 36f795411..a497ee715 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using SocketHttpListener.Net; using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; @@ -189,5 +192,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void ClearCookies() { } + + public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + { + return _response.TransmitFile(path, offset, count, cancellationToken); + } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 926d82f94..d9060a066 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1276,6 +1276,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } + var registration = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false); + if (!registration.IsValid) + { + _logger.Warn("Emby Premiere required to use Emby DVR."); + OnTimerOutOfDate(timer); + return; + } + var activeRecordingInfo = new ActiveRecordingInfo { CancellationTokenSource = new CancellationTokenSource(), @@ -2319,6 +2327,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { UpdateExistingTimerWithNewMetadata(existingTimer, timer); + // Needed by ShouldCancelTimerForSeriesTimer + timer.IsManual = existingTimer.IsManual; + if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) { existingTimer.Status = RecordingStatus.Cancelled; diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index eea4bf049..3c2af60db 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -12,45 +12,6 @@ namespace Emby.Server.Implementations.Services { public static class ResponseHelper { - private static async Task WriteToOutputStream(IResponse response, object result) - { - var asyncStreamWriter = result as IAsyncStreamWriter; - if (asyncStreamWriter != null) - { - await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); - return true; - } - - var streamWriter = result as IStreamWriter; - if (streamWriter != null) - { - streamWriter.WriteTo(response.OutputStream); - return true; - } - - var stream = result as Stream; - if (stream != null) - { - using (stream) - { - await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); - return true; - } - } - - var bytes = result as byte[]; - if (bytes != null) - { - response.ContentType = "application/octet-stream"; - response.SetContentLength(bytes.Length); - - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - return true; - } - - return false; - } - public static Task WriteToResponse(IResponse httpRes, IRequest httpReq, object result) { if (result == null) @@ -155,6 +116,13 @@ namespace Emby.Server.Implementations.Services return; } + var fileWriter = result as FileWriter; + if (fileWriter != null) + { + await fileWriter.WriteToAsync(response, CancellationToken.None).ConfigureAwait(false); + return; + } + var stream = result as Stream; if (stream != null) { diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 7e638e171..ccabbc27b 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -1,7 +1,7 @@  - + diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index 4c033dc00..2104bee09 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -206,6 +206,8 @@ namespace MediaBrowser.Controller.Entities void SetImage(ItemImageInfo image, int index); double? GetDefaultPrimaryImageAspectRatio(); + + int? ProductionYear { get; set; } } public static class HasImagesExtensions diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs index 272fa8b3b..62c13fbb5 100644 --- a/MediaBrowser.Controller/Net/StaticResultOptions.cs +++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs @@ -23,21 +23,18 @@ namespace MediaBrowser.Controller.Net public Action OnComplete { get; set; } public Action OnError { get; set; } + public string Path { get; set; } + + public FileShareMode FileShare { get; set; } + public StaticResultOptions() { ResponseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + FileShare = FileShareMode.Read; } } public class StaticFileResultOptions : StaticResultOptions { - public string Path { get; set; } - - public FileShareMode FileShare { get; set; } - - public StaticFileResultOptions() - { - FileShare = FileShareMode.Read; - } } } diff --git a/MediaBrowser.Model/Net/IAcceptSocket.cs b/MediaBrowser.Model/Net/IAcceptSocket.cs index cac23b337..4262e2390 100644 --- a/MediaBrowser.Model/Net/IAcceptSocket.cs +++ b/MediaBrowser.Model/Net/IAcceptSocket.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Model.Net { @@ -13,6 +15,7 @@ namespace MediaBrowser.Model.Net void Bind(IpEndPointInfo endpoint); void Connect(IpEndPointInfo endPoint); void StartAccept(Action onAccept, Func isClosed); + Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken); } public class SocketCreateException : Exception diff --git a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs index b10e12813..f61a94f6e 100644 --- a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs +++ b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs @@ -8,4 +8,9 @@ namespace MediaBrowser.Model.Services { Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken); } + + public interface IFileWriter + { + Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken); + } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index e9a9f1c5b..40cef4ec0 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Net; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Model.Services { @@ -151,6 +153,7 @@ namespace MediaBrowser.Model.Services //Add Metadata to Response Dictionary Items { get; } - } + Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken); + } } diff --git a/SharedVersion.cs b/SharedVersion.cs index e3fd5e349..3f2c5d97e 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.7.2")] +[assembly: AssemblyVersion("3.2.7.3")] diff --git a/SocketHttpListener.Portable/Net/EndPointListener.cs b/SocketHttpListener.Portable/Net/EndPointListener.cs index c7642d5d1..4f1a17fc0 100644 --- a/SocketHttpListener.Portable/Net/EndPointListener.cs +++ b/SocketHttpListener.Portable/Net/EndPointListener.cs @@ -32,8 +32,9 @@ namespace SocketHttpListener.Net private readonly ISocketFactory _socketFactory; private readonly ITextEncoding _textEncoding; private readonly IMemoryStreamFactory _memoryStreamFactory; + private readonly IFileSystem _fileSystem; - public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) { this.listener = listener; _logger = logger; @@ -42,6 +43,7 @@ namespace SocketHttpListener.Net _socketFactory = socketFactory; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; + _fileSystem = fileSystem; this.secure = secure; this.cert = cert; @@ -107,7 +109,7 @@ namespace SocketHttpListener.Net return; } - HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding).ConfigureAwait(false); + HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem).ConfigureAwait(false); //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); lock (listener.unregistered) diff --git a/SocketHttpListener.Portable/Net/EndPointManager.cs b/SocketHttpListener.Portable/Net/EndPointManager.cs index 797684b3e..11f774915 100644 --- a/SocketHttpListener.Portable/Net/EndPointManager.cs +++ b/SocketHttpListener.Portable/Net/EndPointManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Reflection; using System.Threading.Tasks; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using SocketHttpListener.Primitives; @@ -105,7 +106,7 @@ namespace SocketHttpListener.Net } else { - epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding); + epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem); p[port] = epl; } diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs index 4a2cf3f5b..5fe47fc63 100644 --- a/SocketHttpListener.Portable/Net/HttpConnection.cs +++ b/SocketHttpListener.Portable/Net/HttpConnection.cs @@ -35,13 +35,14 @@ namespace SocketHttpListener.Net ICertificate cert; Stream ssl_stream; - private ILogger _logger; + private readonly ILogger _logger; private readonly ICryptoProvider _cryptoProvider; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; private readonly IStreamFactory _streamFactory; + private readonly IFileSystem _fileSystem; - private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) { _logger = logger; this.sock = sock; @@ -51,6 +52,7 @@ namespace SocketHttpListener.Net _cryptoProvider = cryptoProvider; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; + _fileSystem = fileSystem; _streamFactory = streamFactory; } @@ -82,9 +84,9 @@ namespace SocketHttpListener.Net Init(); } - public static async Task Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + public static async Task Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) { - var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding); + var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem); await connection.InitStream().ConfigureAwait(false); @@ -121,7 +123,7 @@ namespace SocketHttpListener.Net position = 0; input_state = InputState.RequestLine; line_state = LineState.None; - context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding); + context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem); } public bool IsClosed @@ -213,7 +215,9 @@ namespace SocketHttpListener.Net if (context.Response.SendChunked || isExpect100Continue || context.Request.IsWebSocketRequest || true) { - o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding); + var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure; + + o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess); } else { diff --git a/SocketHttpListener.Portable/Net/HttpListener.cs b/SocketHttpListener.Portable/Net/HttpListener.cs index 2b0f75d01..c2e7acd8e 100644 --- a/SocketHttpListener.Portable/Net/HttpListener.cs +++ b/SocketHttpListener.Portable/Net/HttpListener.cs @@ -18,6 +18,7 @@ namespace SocketHttpListener.Net internal ICryptoProvider CryptoProvider { get; private set; } internal IStreamFactory StreamFactory { get; private set; } internal ISocketFactory SocketFactory { get; private set; } + internal IFileSystem FileSystem { get; private set; } internal ITextEncoding TextEncoding { get; private set; } internal IMemoryStreamFactory MemoryStreamFactory { get; private set; } internal INetworkManager NetworkManager { get; private set; } @@ -39,7 +40,7 @@ namespace SocketHttpListener.Net public Action OnContext { get; set; } - public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory) + public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem) { _logger = logger; CryptoProvider = cryptoProvider; @@ -48,19 +49,20 @@ namespace SocketHttpListener.Net NetworkManager = networkManager; TextEncoding = textEncoding; MemoryStreamFactory = memoryStreamFactory; + FileSystem = fileSystem; prefixes = new HttpListenerPrefixCollection(logger, this); registry = new Dictionary(); connections = new Dictionary(); auth_schemes = AuthenticationSchemes.Anonymous; } - public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory) - :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory) + public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem) + :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem) { } - public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory) - : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory) + public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem) + : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem) { _certificate = certificate; } diff --git a/SocketHttpListener.Portable/Net/HttpListenerContext.cs b/SocketHttpListener.Portable/Net/HttpListenerContext.cs index 182fd2d2a..58d769f22 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerContext.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerContext.cs @@ -18,20 +18,18 @@ namespace SocketHttpListener.Net HttpConnection cnc; string error; int err_status = 400; - private readonly ILogger _logger; private readonly ICryptoProvider _cryptoProvider; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; - internal HttpListenerContext(HttpConnection cnc, ILogger logger, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + internal HttpListenerContext(HttpConnection cnc, ILogger logger, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) { this.cnc = cnc; - _logger = logger; _cryptoProvider = cryptoProvider; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; request = new HttpListenerRequest(this, _textEncoding); - response = new HttpListenerResponse(this, _logger, _textEncoding); + response = new HttpListenerResponse(this, logger, _textEncoding, fileSystem); } internal int ErrorStatus diff --git a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs index 9a5862cb9..d8011f05e 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs @@ -3,6 +3,9 @@ using System.Globalization; using System.IO; using System.Net; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -32,12 +35,14 @@ namespace SocketHttpListener.Net private readonly ILogger _logger; private readonly ITextEncoding _textEncoding; + private readonly IFileSystem _fileSystem; - internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding) + internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding, IFileSystem fileSystem) { this.context = context; _logger = logger; _textEncoding = textEncoding; + _fileSystem = fileSystem; } internal bool CloseConnection @@ -366,7 +371,7 @@ namespace SocketHttpListener.Net { if (chunked) { - return ; + return; } Version v = context.Request.ProtocolVersion; @@ -509,5 +514,10 @@ namespace SocketHttpListener.Net cookies.Add(cookie); } + + public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + { + return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, cancellationToken); + } } } \ No newline at end of file diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index a79a18791..ccc0efc55 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -22,12 +23,18 @@ namespace SocketHttpListener.Net Stream stream; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; + private readonly IFileSystem _fileSystem; + private readonly IAcceptSocket _socket; + private readonly bool _supportsDirectSocketAccess; - internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess) { this.response = response; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; + _fileSystem = fileSystem; + _socket = socket; + _supportsDirectSocketAccess = supportsDirectSocketAccess; this.stream = stream; } @@ -299,5 +306,80 @@ namespace SocketHttpListener.Net { throw new NotSupportedException(); } + + public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + { + //if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !response.SendChunked) + //{ + // return TransmitFileOverSocket(path, offset, count, cancellationToken); + //} + return TransmitFileManaged(path, offset, count, cancellationToken); + } + + private readonly byte[] _emptyBuffer = new byte[] { }; + private async Task TransmitFileOverSocket(string path, long offset, long count, CancellationToken cancellationToken) + { + MemoryStream ms = GetHeaders(response, _memoryStreamFactory, false); + + var buffer = new byte[] {}; + if (ms != null) + { + ms.Position = 0; + + byte[] msBuffer; + _memoryStreamFactory.TryGetBuffer(ms, out msBuffer); + buffer = msBuffer; + } + + await _socket.SendFile(path, buffer, _emptyBuffer, cancellationToken).ConfigureAwait(false); + } + + private async Task TransmitFileManaged(string path, long offset, long count, CancellationToken cancellationToken) + { + var chunked = response.SendChunked; + + if (!chunked) + { + await WriteAsync(_emptyBuffer, 0, 0, cancellationToken).ConfigureAwait(false); + } + + using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true)) + { + if (offset > 0) + { + fs.Position = offset; + } + + var targetStream = chunked ? this : stream; + + if (count > 0) + { + await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false); + } + else + { + await fs.CopyToAsync(targetStream, 81920, cancellationToken).ConfigureAwait(false); + } + } + } + + private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) + { + var array = new byte[81920]; + int count; + while ((count = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + var bytesToCopy = Math.Min(count, copyLength); + + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false); + + copyLength -= bytesToCopy; + + if (copyLength <= 0) + { + break; + } + } + } } } From 5e821947491c07eb65db4653db674af55bc4e90b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 12 Mar 2017 15:27:45 -0400 Subject: [PATCH 13/67] improve tvdb lookups by imdb --- .../TV/TheTVDB/TvdbEpisodeProvider.cs | 2 +- .../TV/TheTVDB/TvdbPrescanTask.cs | 2 +- .../TV/TheTVDB/TvdbSeasonImageProvider.cs | 2 +- .../TV/TheTVDB/TvdbSeriesImageProvider.cs | 2 +- .../TV/TheTVDB/TvdbSeriesProvider.cs | 41 +++++++++++-------- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs index 46be61486..4a52b972f 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs @@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.TV if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) && (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue)) { - await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); + await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, null, null, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs index f6af365fd..72bd62d9f 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -389,7 +389,7 @@ namespace MediaBrowser.Providers.TV _fileSystem.CreateDirectory(seriesDataPath); - return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken); + return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken); } } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs index 2c4ccddd7..e68b7ad1d 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.TV var seriesProviderIds = series.ProviderIds; var seasonNumber = season.IndexNumber.Value; - var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); + var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.Name, series.ProductionYear, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(seriesDataPath)) { diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs index 97eedfa92..cdb9ac51e 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs @@ -70,7 +70,7 @@ namespace MediaBrowser.Providers.TV { var language = item.GetPreferredMetadataLanguage(); - var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, language, cancellationToken).ConfigureAwait(false); + var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, item.Name, item.ProductionYear, language, cancellationToken).ConfigureAwait(false); var path = Path.Combine(seriesDataPath, "banners.xml"); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 44550d2be..c340d03f5 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.TV if (IsValidSeries(itemId.ProviderIds)) { - await EnsureSeriesInfo(itemId.ProviderIds, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false); + await EnsureSeriesInfo(itemId.ProviderIds, itemId.Name, itemId.Year, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false); result.Item = new Series(); result.HasMetadata = true; @@ -160,19 +160,11 @@ namespace MediaBrowser.Providers.TV /// /// Downloads the series zip. /// - /// The series id. - /// Type of the identifier. - /// The series data path. - /// The last tv database update time. - /// The preferred metadata language. - /// The cancellation token. - /// Task. - /// seriesId - internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken) + internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken) { try { - await DownloadSeriesZip(seriesId, idType, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); return; } catch (HttpException ex) @@ -185,11 +177,11 @@ namespace MediaBrowser.Providers.TV if (!string.Equals(preferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase)) { - await DownloadSeriesZip(seriesId, idType, seriesDataPath, lastTvDbUpdateTime, "en", preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, "en", preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); } } - private async Task DownloadSeriesZip(string seriesId, string idType, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, string saveAsMetadataLanguage, CancellationToken cancellationToken) + private async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, string saveAsMetadataLanguage, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(seriesId)) { @@ -201,6 +193,23 @@ namespace MediaBrowser.Providers.TV seriesId = await GetSeriesByRemoteId(seriesId, idType, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); } + // If searching by remote id came up empty, then do a regular search + if (string.IsNullOrWhiteSpace(seriesId) && !string.IsNullOrWhiteSpace(seriesName)) + { + var searchInfo = new SeriesInfo + { + Name = seriesName, + Year = seriesYear, + MetadataLanguage = preferredMetadataLanguage + }; + var results = await GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false); + var result = results.FirstOrDefault(); + if (result != null) + { + seriesId = result.GetProviderId(MetadataProviders.Tvdb); + } + } + if (string.IsNullOrWhiteSpace(seriesId)) { throw new ArgumentNullException("seriesId"); @@ -380,7 +389,7 @@ namespace MediaBrowser.Providers.TV } private SemaphoreSlim _ensureSemaphore = new SemaphoreSlim(1, 1); - internal async Task EnsureSeriesInfo(Dictionary seriesProviderIds, string preferredMetadataLanguage, CancellationToken cancellationToken) + internal async Task EnsureSeriesInfo(Dictionary seriesProviderIds, string seriesName, int? seriesYear, string preferredMetadataLanguage, CancellationToken cancellationToken) { await _ensureSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -395,7 +404,7 @@ namespace MediaBrowser.Providers.TV // The post-scan task will take care of updates so we don't need to re-download here if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage)) { - await DownloadSeriesZip(seriesId, MetadataProviders.Tvdb.ToString(), seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, MetadataProviders.Tvdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); } return seriesDataPath; @@ -409,7 +418,7 @@ namespace MediaBrowser.Providers.TV // The post-scan task will take care of updates so we don't need to re-download here if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage)) { - await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); } return seriesDataPath; From b38b7a706268fe5d92d8cbe703a188b58ed7ec4d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 13 Mar 2017 00:08:23 -0400 Subject: [PATCH 14/67] rework filestream --- .../Net/NetAcceptSocket.cs | 2 +- .../Net/SocketFactory.cs | 27 ++++++++++++++++--- .../HttpServer/FileWriter.cs | 7 +++-- .../HttpServer/HttpResultFactory.cs | 5 ++-- .../SocketSharp/WebSocketSharpResponse.cs | 4 +-- .../Library/LibraryManager.cs | 27 ++++++++++++------- MediaBrowser.Api/StartupWizardService.cs | 1 + .../Entities/Audio/MusicArtist.cs | 7 ++++- .../Entities/Audio/MusicGenre.cs | 7 ++++- MediaBrowser.Controller/Entities/GameGenre.cs | 7 ++++- MediaBrowser.Controller/Entities/Genre.cs | 7 ++++- MediaBrowser.Controller/Entities/Person.cs | 7 ++++- MediaBrowser.Controller/Entities/Studio.cs | 7 ++++- MediaBrowser.Controller/Entities/Year.cs | 7 ++++- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 2 ++ .../Configuration/ServerConfiguration.cs | 1 + MediaBrowser.Model/Net/ISocketFactory.cs | 2 ++ MediaBrowser.Model/Services/IRequest.cs | 3 ++- .../Net/HttpListenerResponse.cs | 4 +-- .../Net/ResponseStream.cs | 8 +++--- 20 files changed, 108 insertions(+), 34 deletions(-) diff --git a/Emby.Common.Implementations/Net/NetAcceptSocket.cs b/Emby.Common.Implementations/Net/NetAcceptSocket.cs index e21ffe553..3721709e6 100644 --- a/Emby.Common.Implementations/Net/NetAcceptSocket.cs +++ b/Emby.Common.Implementations/Net/NetAcceptSocket.cs @@ -100,7 +100,7 @@ namespace Emby.Common.Implementations.Net #if NET46 public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken) { - var options = TransmitFileOptions.Disconnect | TransmitFileOptions.ReuseSocket | TransmitFileOptions.UseKernelApc; + var options = TransmitFileOptions.UseKernelApc; var completionSource = new TaskCompletionSource(); diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 021613e57..0f4306a6b 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -97,10 +97,31 @@ namespace Emby.Common.Implementations.Net } } + public ISocket CreateUdpBroadcastSocket(int localPort) + { + if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); + + var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + try + { + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); + + return new UdpSocket(retVal, localPort, IPAddress.Any); + } + catch + { + if (retVal != null) + retVal.Dispose(); + + throw; + } + } + /// - /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port. - /// - /// An implementation of the interface used by RSSDP components to perform acceptSocket operations. + /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port. + /// + /// An implementation of the interface used by RSSDP components to perform acceptSocket operations. public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index b80a40962..d230a9b91 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -27,6 +27,8 @@ namespace Emby.Server.Implementations.HttpServer private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); public List Cookies { get; private set; } + public FileShareMode FileShare { get; set; } + /// /// The _options /// @@ -69,6 +71,7 @@ namespace Emby.Server.Implementations.HttpServer SetRangeValues(); } + FileShare = FileShareMode.Read; Cookies = new List(); } @@ -153,11 +156,11 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) { Logger.Info("Transmit file {0}", Path); - await response.TransmitFile(Path, 0, 0, cancellationToken).ConfigureAwait(false); + await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); return; } - await response.TransmitFile(Path, RangeStart, RangeEnd, cancellationToken).ConfigureAwait(false); + await response.TransmitFile(Path, RangeStart, RangeEnd, FileShare, cancellationToken).ConfigureAwait(false); } finally { diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index e3f105941..310161d41 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -556,12 +556,13 @@ namespace Emby.Server.Implementations.HttpServer { var rangeHeader = requestContext.Headers.Get("Range"); - if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path) && options.FileShare == FileShareMode.Read) + if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path)) { return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) { OnComplete = options.OnComplete, - OnError = options.OnError + OnError = options.OnError, + FileShare = options.FileShare }; } diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index a497ee715..fd30b227f 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -193,9 +193,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp { } - public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) { - return _response.TransmitFile(path, offset, count, cancellationToken); + return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f7706db47..026486efc 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -513,6 +513,11 @@ namespace Emby.Server.Implementations.Library } public Guid GetNewItemId(string key, Type type) + { + return GetNewItemIdInternal(key, type, false); + } + + private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive) { if (string.IsNullOrWhiteSpace(key)) { @@ -531,7 +536,7 @@ namespace Emby.Server.Implementations.Library .Replace("/", "\\"); } - if (!ConfigurationManager.Configuration.EnableCaseSensitiveItemIds) + if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds) { key = key.ToLower(); } @@ -865,7 +870,7 @@ namespace Emby.Server.Implementations.Library /// Task{Person}. public Person GetPerson(string name) { - return CreateItemByName(Person.GetPath(name), name); + return CreateItemByName(Person.GetPath, name); } /// @@ -875,7 +880,7 @@ namespace Emby.Server.Implementations.Library /// Task{Studio}. public Studio GetStudio(string name) { - return CreateItemByName(Studio.GetPath(name), name); + return CreateItemByName(Studio.GetPath, name); } /// @@ -885,7 +890,7 @@ namespace Emby.Server.Implementations.Library /// Task{Genre}. public Genre GetGenre(string name) { - return CreateItemByName(Genre.GetPath(name), name); + return CreateItemByName(Genre.GetPath, name); } /// @@ -895,7 +900,7 @@ namespace Emby.Server.Implementations.Library /// Task{MusicGenre}. public MusicGenre GetMusicGenre(string name) { - return CreateItemByName(MusicGenre.GetPath(name), name); + return CreateItemByName(MusicGenre.GetPath, name); } /// @@ -905,7 +910,7 @@ namespace Emby.Server.Implementations.Library /// Task{GameGenre}. public GameGenre GetGameGenre(string name) { - return CreateItemByName(GameGenre.GetPath(name), name); + return CreateItemByName(GameGenre.GetPath, name); } /// @@ -923,7 +928,7 @@ namespace Emby.Server.Implementations.Library var name = value.ToString(CultureInfo.InvariantCulture); - return CreateItemByName(Year.GetPath(name), name); + return CreateItemByName(Year.GetPath, name); } /// @@ -933,10 +938,10 @@ namespace Emby.Server.Implementations.Library /// Task{Genre}. public MusicArtist GetArtist(string name) { - return CreateItemByName(MusicArtist.GetPath(name), name); + return CreateItemByName(MusicArtist.GetPath, name); } - private T CreateItemByName(string path, string name) + private T CreateItemByName(Func getPathFn, string name) where T : BaseItem, new() { if (typeof(T) == typeof(MusicArtist)) @@ -957,7 +962,9 @@ namespace Emby.Server.Implementations.Library } } - var id = GetNewItemId(path, typeof(T)); + var path = getPathFn(name); + var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds; + var id = GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId); var item = GetItemById(id) as T; diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index e010f122c..02154b98d 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -120,6 +120,7 @@ namespace MediaBrowser.Api config.EnableSeriesPresentationUniqueKey = true; config.EnableLocalizedGuids = true; config.EnableSimpleArtistDetection = true; + config.EnableNormalizedItemByNameIds = true; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 20b2529c0..8d83f8a35 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -289,7 +289,12 @@ namespace MediaBrowser.Controller.Entities.Audio } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 74679b474..e26e0dfce 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -118,7 +118,12 @@ namespace MediaBrowser.Controller.Entities.Audio return LibraryManager.GetItemList(query); } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index 22a8675c5..4187167b9 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -96,7 +96,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 1b746ae51..9769efdd0 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -108,7 +108,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index ee1aea938..b68681d36 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -133,7 +133,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validFilename = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index b8ad691a9..2e5e6ce43 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -114,7 +114,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 75fb69435..b352950a0 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -122,7 +122,12 @@ namespace MediaBrowser.Controller.Entities } } - public static string GetPath(string name, bool normalizeName = true) + public static string GetPath(string name) + { + return GetPath(name, true); + } + + public static string GetPath(string name, bool normalizeName) { // Trim the period at the end because windows will have a hard time with that var validName = normalizeName ? diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 5615649c2..af1c0d12e 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -44,6 +44,8 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// Task<List<MediaSourceInfo>>. Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); + + Task> DiscoverDevices(int discoveryDurationMs); } public interface IConfigurableTunerHost { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index c2b1e3c89..0562d0ac5 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Configuration public bool EnableHttps { get; set; } public bool EnableSeriesPresentationUniqueKey { get; set; } public bool EnableLocalizedGuids { get; set; } + public bool EnableNormalizedItemByNameIds { get; set; } /// /// Gets or sets the value pointing to the file system where the ssl certiifcate is located.. diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs index 4b70f3362..e7dbf6cb1 100644 --- a/MediaBrowser.Model/Net/ISocketFactory.cs +++ b/MediaBrowser.Model/Net/ISocketFactory.cs @@ -14,6 +14,8 @@ namespace MediaBrowser.Model.Net /// A implementation. ISocket CreateUdpSocket(int localPort); + ISocket CreateUdpBroadcastSocket(int localPort); + ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort); /// diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 40cef4ec0..115ba25ce 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -4,6 +4,7 @@ using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.IO; namespace MediaBrowser.Model.Services { @@ -154,6 +155,6 @@ namespace MediaBrowser.Model.Services //Add Metadata to Response Dictionary Items { get; } - Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken); + Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken); } } diff --git a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs index d8011f05e..d9f91c0cc 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs @@ -515,9 +515,9 @@ namespace SocketHttpListener.Net cookies.Add(cookie); } - public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) { - return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, cancellationToken); + return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken); } } } \ No newline at end of file diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index ccc0efc55..19821f954 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -307,13 +307,13 @@ namespace SocketHttpListener.Net throw new NotSupportedException(); } - public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) { //if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !response.SendChunked) //{ // return TransmitFileOverSocket(path, offset, count, cancellationToken); //} - return TransmitFileManaged(path, offset, count, cancellationToken); + return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken); } private readonly byte[] _emptyBuffer = new byte[] { }; @@ -334,7 +334,7 @@ namespace SocketHttpListener.Net await _socket.SendFile(path, buffer, _emptyBuffer, cancellationToken).ConfigureAwait(false); } - private async Task TransmitFileManaged(string path, long offset, long count, CancellationToken cancellationToken) + private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) { var chunked = response.SendChunked; @@ -343,7 +343,7 @@ namespace SocketHttpListener.Net await WriteAsync(_emptyBuffer, 0, 0, cancellationToken).ConfigureAwait(false); } - using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true)) + using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, true)) { if (offset > 0) { From 0650be4780afb8a8a32b2819596ca37047e5a90b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 13 Mar 2017 00:08:49 -0400 Subject: [PATCH 15/67] rework tuner discovery --- .../Emby.Server.Implementations.csproj | 1 - .../HdHomerun/HdHomerunDiscovery.cs | 158 ------------------ .../TunerHosts/HdHomerun/HdHomerunHost.cs | 71 +++++++- .../LiveTv/TunerHosts/M3UTunerHost.cs | 5 + 4 files changed, 75 insertions(+), 160 deletions(-) delete mode 100644 Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d873cd77f..ecd86d507 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -171,7 +171,6 @@ - diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs deleted file mode 100644 index 336469c50..000000000 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs +++ /dev/null @@ -1,158 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Logging; -using System; -using System.Linq; -using System.Threading; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Serialization; - -namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun -{ - public class HdHomerunDiscovery : IServerEntryPoint - { - private readonly IDeviceDiscovery _deviceDiscovery; - private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; - private readonly ILiveTvManager _liveTvManager; - private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); - private readonly IHttpClient _httpClient; - private readonly IJsonSerializer _json; - - public HdHomerunDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json) - { - _deviceDiscovery = deviceDiscovery; - _config = config; - _logger = logger; - _liveTvManager = liveTvManager; - _httpClient = httpClient; - _json = json; - } - - public void Run() - { - _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; - } - - void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs e) - { - string server = null; - var info = e.Argument; - - if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1) - { - string location; - if (info.Headers.TryGetValue("Location", out location)) - { - //_logger.Debug("HdHomerun found at {0}", location); - - // Just get the beginning of the url - Uri uri; - if (Uri.TryCreate(location, UriKind.Absolute, out uri)) - { - var apiUrl = location.Replace(uri.LocalPath, String.Empty, StringComparison.OrdinalIgnoreCase) - .TrimEnd('/'); - - //_logger.Debug("HdHomerun api url: {0}", apiUrl); - AddDevice(apiUrl); - } - } - } - } - - private async void AddDevice(string url) - { - await _semaphore.WaitAsync().ConfigureAwait(false); - - try - { - var options = GetConfiguration(); - - if (options.TunerHosts.Any(i => - string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) && - UriEquals(i.Url, url))) - { - return; - } - - // Strip off the port - url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/'); - - // Test it by pulling down the lineup - using (var stream = await _httpClient.Get(new HttpRequestOptions - { - Url = string.Format("{0}/discover.json", url), - CancellationToken = CancellationToken.None, - BufferContent = false - })) - { - var response = _json.DeserializeFromStream(stream); - - var existing = GetConfiguration().TunerHosts - .FirstOrDefault(i => string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.DeviceId, response.DeviceID, StringComparison.OrdinalIgnoreCase)); - - if (existing == null) - { - await _liveTvManager.SaveTunerHost(new TunerHostInfo - { - Type = HdHomerunHost.DeviceType, - Url = url, - DeviceId = response.DeviceID - - }).ConfigureAwait(false); - } - else - { - if (!string.Equals(existing.Url, url, StringComparison.OrdinalIgnoreCase)) - { - existing.Url = url; - await _liveTvManager.SaveTunerHost(existing).ConfigureAwait(false); - } - } - } - } - catch (Exception ex) - { - _logger.ErrorException("Error saving device", ex); - } - finally - { - _semaphore.Release(); - } - } - - private bool UriEquals(string savedUri, string location) - { - return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase); - } - - private string NormalizeUrl(string url) - { - if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - url = "http://" + url; - } - - url = url.TrimEnd('/'); - - // Strip off the port - return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped); - } - - private LiveTvOptions GetConfiguration() - { - return _config.GetConfiguration("livetv"); - } - - public void Dispose() - { - } - } -} diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 62385e172..487aa7ca9 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var hdhomerunChannel = channelInfo as HdHomerunChannelInfo; if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner) - { + { var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo); var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); @@ -652,5 +652,74 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public string LineupURL { get; set; } public int TunerCount { get; set; } } + + public async Task> DiscoverDevices(int discoveryDurationMs) + { + var cancellationToken = new CancellationTokenSource(discoveryDurationMs).Token; + var list = new List(); + + // Create udp broadcast discovery message + byte[] discBytes = { 0, 2, 0, 12, 1, 4, 255, 255, 255, 255, 2, 4, 255, 255, 255, 255, 115, 204, 125, 143 }; + using (var udpClient = _socketFactory.CreateUdpBroadcastSocket(0)) + { + // Need a way to set the Receive timeout on the socket otherwise this might never timeout? + try + { + await udpClient.SendAsync(discBytes, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken); + while (!cancellationToken.IsCancellationRequested) + { + var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + var deviceIp = response.RemoteEndPoint.IpAddress.Address; + + // check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte + if (response.ReceivedBytes > 13 && response.Buffer[1] == 3) + { + var deviceAddress = "http://" + deviceIp; + + var info = await TryGetTunerHostInfo(deviceAddress, cancellationToken).ConfigureAwait(false); + + if (info != null) + { + list.Add(info); + } + } + } + + } + catch (OperationCanceledException) + { + } + catch + { + // Socket timeout indicates all messages have been received. + } + } + + return list; + } + + private async Task TryGetTunerHostInfo(string url, CancellationToken cancellationToken) + { + var hostInfo = new TunerHostInfo + { + Type = Type, + Url = url + }; + + try + { + var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false); + + hostInfo.DeviceId = modelInfo.DeviceID; + + return hostInfo; + } + catch + { + // logged at lower levels + } + + return null; + } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index a939cec7b..a09da3e53 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -176,5 +176,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { return Task.FromResult(true); } + + public Task> DiscoverDevices(int discoveryDurationMs) + { + return Task.FromResult(new List()); + } } } \ No newline at end of file From f05dc08c06a386aca052a2abb981e309bd005d91 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 13 Mar 2017 00:49:10 -0400 Subject: [PATCH 16/67] update tuner discovery --- Emby.Common.Implementations/Net/UdpSocket.cs | 18 ++++--- .../LiveTv/EmbyTV/EmbyTV.cs | 54 +++++++++++++++++++ .../LiveTv/LiveTvManager.cs | 2 + .../TunerHosts/HdHomerun/HdHomerunHost.cs | 4 +- .../LiveTv/TunerHosts/M3UTunerHost.cs | 2 +- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 2 +- 6 files changed, 70 insertions(+), 12 deletions(-) diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 8e2a1da6f..b85245ba1 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -60,6 +60,8 @@ namespace Emby.Common.Implementations.Net var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); state.TaskCompletionSource = tcs; + cancellationToken.Register(() => tcs.TrySetCanceled()); + #if NETSTANDARD1_6 _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer), SocketFlags.None, state.RemoteEndPoint) .ContinueWith((task, asyncState) => @@ -160,7 +162,7 @@ namespace Emby.Common.Implementations.Net var bytesRead = receiveData(); var ipEndPoint = state.RemoteEndPoint as IPEndPoint; - state.TaskCompletionSource.SetResult( + state.TaskCompletionSource.TrySetResult( new SocketReceiveResult { Buffer = state.Buffer, @@ -172,18 +174,18 @@ namespace Emby.Common.Implementations.Net } catch (ObjectDisposedException) { - state.TaskCompletionSource.SetCanceled(); + state.TaskCompletionSource.TrySetCanceled(); } catch (SocketException se) { if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown) - state.TaskCompletionSource.SetException(se); + state.TaskCompletionSource.TrySetException(se); else - state.TaskCompletionSource.SetCanceled(); + state.TaskCompletionSource.TrySetCanceled(); } catch (Exception ex) { - state.TaskCompletionSource.SetException(ex); + state.TaskCompletionSource.TrySetException(ex); } } @@ -206,7 +208,7 @@ namespace Emby.Common.Implementations.Net var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint); var ipEndPoint = state.RemoteEndPoint as IPEndPoint; - state.TaskCompletionSource.SetResult( + state.TaskCompletionSource.TrySetResult( new SocketReceiveResult { Buffer = state.Buffer, @@ -218,11 +220,11 @@ namespace Emby.Common.Implementations.Net } catch (ObjectDisposedException) { - state.TaskCompletionSource.SetCanceled(); + state.TaskCompletionSource.TrySetCanceled(); } catch (Exception ex) { - state.TaskCompletionSource.SetException(ex); + state.TaskCompletionSource.TrySetException(ex); } #endif } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index d9060a066..5c7c28cc8 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2542,6 +2542,60 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public ProgramInfo Program { get; set; } public CancellationTokenSource CancellationTokenSource { get; set; } } + + public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken) + { + foreach (var host in _liveTvManager.TunerHosts) + { + await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false); + } + } + + private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken) + { + var discoveredDevices = await DiscoverDevices(host, 2000, cancellationToken).ConfigureAwait(false); + + var configuredDevices = GetConfiguration().TunerHosts + .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var device in discoveredDevices) + { + var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase)); + + if (configuredDevice != null) + { + if (!string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase)) + { + _logger.Info("Tuner url has changed from {0} to {1}", configuredDevice.Url, device.Url); + + configuredDevice.Url = device.Url; + await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false); + } + } + } + } + + private async Task> DiscoverDevices(ITunerHost host, int discoveryDuationMs, CancellationToken cancellationToken) + { + try + { + var discoveredDevices = await host.DiscoverDevices(discoveryDuationMs, cancellationToken).ConfigureAwait(false); + + foreach (var device in discoveredDevices) + { + _logger.Info("Discovered tuner device {0} at {1}", host.Name, device.Url); + } + + return discoveredDevices; + } + catch (Exception ex) + { + _logger.ErrorException("Error discovering tuner devices", ex); + + return new List(); + } + } } public static class ConfigurationExtension { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 618cd1d45..fc2e0ce72 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1180,6 +1180,8 @@ namespace Emby.Server.Implementations.LiveTv { EmbyTV.EmbyTV.Current.CreateRecordingFolders(); + await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false); + var numComplete = 0; double progressPerService = _services.Count == 0 ? 0 diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 487aa7ca9..6b80cec19 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -653,9 +653,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public int TunerCount { get; set; } } - public async Task> DiscoverDevices(int discoveryDurationMs) + public async Task> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken) { - var cancellationToken = new CancellationTokenSource(discoveryDurationMs).Token; + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token; var list = new List(); // Create udp broadcast discovery message diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index a09da3e53..cc0ae0983 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(true); } - public Task> DiscoverDevices(int discoveryDurationMs) + public Task> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken) { return Task.FromResult(new List()); } diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index af1c0d12e..fc344298b 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Controller.LiveTv /// Task<List<MediaSourceInfo>>. Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); - Task> DiscoverDevices(int discoveryDurationMs); + Task> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken); } public interface IConfigurableTunerHost { From 2ff7608b7ad28bd2ba20d7ff95513c96656a42d4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 13 Mar 2017 00:56:41 -0400 Subject: [PATCH 17/67] remove tuner host enabled property --- Emby.Server.Implementations/LiveTv/LiveTvManager.cs | 4 ++-- .../LiveTv/RefreshChannelsScheduledTask.cs | 2 +- .../LiveTv/TunerHosts/BaseTunerHost.cs | 2 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 7 +------ MediaBrowser.Api/StartupWizardService.cs | 1 - MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 2 -- 6 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index fc2e0ce72..7904024bf 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2750,7 +2750,7 @@ namespace Emby.Server.Implementations.LiveTv private bool IsLiveTvEnabled(User user) { - return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count(i => i.IsEnabled) > 0); + return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count > 0); } public IEnumerable GetEnabledUsers() @@ -2988,7 +2988,7 @@ namespace Emby.Server.Implementations.LiveTv if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase)) { var config = GetConfiguration(); - if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && + if (config.TunerHosts.Count > 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 diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index f2806292d..5582d8f35 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv public bool IsHidden { - get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count(i => i.IsEnabled) == 0; } + get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count == 0; } } public bool IsEnabled diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 5ac3812b0..79fa9bb61 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected virtual List GetTunerHosts() { return GetConfiguration().TunerHosts - .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)) + .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)) .ToList(); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 6b80cec19..dbcabd174 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var list = new List(); foreach (var host in GetConfiguration().TunerHosts - .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))) + .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))) { try { @@ -605,11 +605,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public async Task Validate(TunerHostInfo info) { - if (!info.IsEnabled) - { - return; - } - lock (_modelCache) { _modelCache.Clear(); diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 02154b98d..0155ef31b 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -188,7 +188,6 @@ namespace MediaBrowser.Api // Add tuner await _liveTvManager.SaveTunerHost(new TunerHostInfo { - IsEnabled = true, Type = request.LiveTvTunerType, Url = request.LiveTvTunerPath diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 79a484a46..c595f9cea 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -48,12 +48,10 @@ namespace MediaBrowser.Model.LiveTv public string DeviceId { get; set; } public bool ImportFavoritesOnly { get; set; } public bool AllowHWTranscoding { get; set; } - public bool IsEnabled { get; set; } public bool EnableTvgId { get; set; } public TunerHostInfo() { - IsEnabled = true; AllowHWTranscoding = true; } } From a8fc4804893cb1ca8d54cd85019f53f621b97e91 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 13 Mar 2017 14:57:45 -0400 Subject: [PATCH 18/67] rework tuner setup --- .../LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../LiveTv/LiveTvManager.cs | 54 +++----------- MediaBrowser.Api/LiveTv/LiveTvService.cs | 53 +++++-------- MediaBrowser.Api/StartupWizardService.cs | 74 +------------------ .../LiveTv/ILiveTvManager.cs | 10 +-- MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 1 + 6 files changed, 36 insertions(+), 158 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 5c7c28cc8..a7ccafd69 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2553,7 +2553,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken) { - var discoveredDevices = await DiscoverDevices(host, 2000, cancellationToken).ConfigureAwait(false); + var discoveredDevices = await DiscoverDevices(host, 3000, cancellationToken).ConfigureAwait(false); var configuredDevices = GetConfiguration().TunerHosts .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 7904024bf..de39d3838 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -150,6 +150,16 @@ namespace Emby.Server.Implementations.LiveTv get { return _listingProviders; } } + public List GetTunerHostTypes() + { + return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair + { + Name = i.Name, + Id = i.Type + + }).ToList(); + } + void service_DataSourceChanged(object sender, EventArgs e) { if (!_isDisposed) @@ -3002,50 +3012,6 @@ namespace Emby.Server.Implementations.LiveTv return _security.GetRegistrationStatus(feature); } - public List GetSatIniMappings() - { - return new List(); - //var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini", StringComparison.OrdinalIgnoreCase) != -1).ToList(); - - //return names.Select(GetSatIniMappings).Where(i => i != null).DistinctBy(i => i.Value.Split('|')[0]).ToList(); - } - - public NameValuePair GetSatIniMappings(string resource) - { - return new NameValuePair(); - //using (var stream = GetType().Assembly.GetManifestResourceStream(resource)) - //{ - // using (var reader = new StreamReader(stream)) - // { - // var parser = new StreamIniDataParser(); - // IniData data = parser.ReadData(reader); - - // var satType1 = data["SATTYPE"]["1"]; - // var satType2 = data["SATTYPE"]["2"]; - - // if (string.IsNullOrWhiteSpace(satType2)) - // { - // return null; - // } - - // var srch = "SatIp.ini."; - // var filename = Path.GetFileName(resource); - - // return new NameValuePair - // { - // Name = satType1 + " " + satType2, - // Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length) - // }; - // } - //} - } - - public Task> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken) - { - return Task.FromResult(new List()); - //return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken); - } - public Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken) { var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index dc4e57155..639021762 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -582,13 +582,13 @@ namespace MediaBrowser.Api.LiveTv } [Route("/LiveTv/ListingProviders/Default", "GET")] - [Authenticated(AllowBeforeStartupWizard = true)] + [Authenticated] public class GetDefaultListingProvider : ListingsProviderInfo, IReturn { } [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")] - [Authenticated(AllowBeforeStartupWizard = true)] + [Authenticated] public class AddListingProvider : ListingsProviderInfo, IReturn { public bool ValidateLogin { get; set; } @@ -596,7 +596,7 @@ namespace MediaBrowser.Api.LiveTv } [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")] - [Authenticated(AllowBeforeStartupWizard = true)] + [Authenticated] public class DeleteListingProvider : IReturnVoid { [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")] @@ -604,7 +604,7 @@ namespace MediaBrowser.Api.LiveTv } [Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")] - [Authenticated(AllowBeforeStartupWizard = true)] + [Authenticated] public class GetLineups : IReturn> { [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -621,13 +621,13 @@ namespace MediaBrowser.Api.LiveTv } [Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")] - [Authenticated(AllowBeforeStartupWizard = true)] + [Authenticated] public class GetSchedulesDirectCountries { } [Route("/LiveTv/ChannelMappingOptions")] - [Authenticated(AllowBeforeStartupWizard = true)] + [Authenticated] public class GetChannelMappingOptions { [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -635,7 +635,7 @@ namespace MediaBrowser.Api.LiveTv } [Route("/LiveTv/ChannelMappings")] - [Authenticated(AllowBeforeStartupWizard = true)] + [Authenticated] public class SetChannelMapping { [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -660,20 +660,6 @@ namespace MediaBrowser.Api.LiveTv public string Feature { get; set; } } - [Route("/LiveTv/TunerHosts/Satip/IniMappings", "GET", Summary = "Gets available mappings")] - [Authenticated(AllowBeforeStartupWizard = true)] - public class GetSatIniMappings : IReturn> - { - - } - - [Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")] - [Authenticated(AllowBeforeStartupWizard = true)] - public class GetSatChannnelScanResult : TunerHostInfo - { - - } - [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")] public class GetLiveStreamFile { @@ -687,6 +673,13 @@ namespace MediaBrowser.Api.LiveTv public string Id { get; set; } } + [Route("/LiveTv/TunerHosts/Types", "GET")] + [Authenticated] + public class GetTunerHostTypes : IReturn> + { + + } + public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; @@ -712,6 +705,12 @@ namespace MediaBrowser.Api.LiveTv _sessionContext = sessionContext; } + public object Get(GetTunerHostTypes request) + { + var list = _liveTvManager.GetTunerHostTypes(); + return ToOptimizedResult(list); + } + public object Get(GetLiveRecordingFile request) { var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id); @@ -749,13 +748,6 @@ namespace MediaBrowser.Api.LiveTv return ToOptimizedResult(new ListingsProviderInfo()); } - public async Task Get(GetSatChannnelScanResult request) - { - var result = await _liveTvManager.GetSatChannelScanResult(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - public async Task Get(GetLiveTvRegistrationInfo request) { var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false); @@ -803,11 +795,6 @@ namespace MediaBrowser.Api.LiveTv return ToOptimizedResult(result); } - public object Get(GetSatIniMappings request) - { - return ToOptimizedResult(_liveTvManager.GetSatIniMappings()); - } - public async Task Get(GetSchedulesDirectCountries request) { // https://json.schedulesdirect.org/20141201/available/countries diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 0155ef31b..b0f52dd85 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -1,12 +1,9 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller; +using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.LiveTv; using System; using System.Linq; using System.Threading.Tasks; @@ -52,16 +49,14 @@ namespace MediaBrowser.Api private readonly IServerApplicationHost _appHost; private readonly IUserManager _userManager; private readonly IConnectManager _connectManager; - private readonly ILiveTvManager _liveTvManager; private readonly IMediaEncoder _mediaEncoder; - public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager, IMediaEncoder mediaEncoder) + public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, IMediaEncoder mediaEncoder) { _config = config; _appHost = appHost; _userManager = userManager; _connectManager = connectManager; - _liveTvManager = liveTvManager; _mediaEncoder = mediaEncoder; } @@ -92,20 +87,6 @@ namespace MediaBrowser.Api PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage }; - var tvConfig = GetLiveTVConfiguration(); - - if (tvConfig.TunerHosts.Count > 0) - { - result.LiveTvTunerPath = tvConfig.TunerHosts[0].Url; - result.LiveTvTunerType = tvConfig.TunerHosts[0].Type; - } - - if (tvConfig.ListingProviders.Count > 0) - { - result.LiveTvGuideProviderId = tvConfig.ListingProviders[0].Id; - result.LiveTvGuideProviderType = tvConfig.ListingProviders[0].Type; - } - return result; } @@ -129,9 +110,6 @@ namespace MediaBrowser.Api _config.Configuration.MetadataCountryCode = request.MetadataCountryCode; _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage; _config.SaveConfiguration(); - - var task = UpdateTuners(request); - Task.WaitAll(task); } public object Get(GetStartupUser request) @@ -166,50 +144,6 @@ namespace MediaBrowser.Api return result; } - - private async Task UpdateTuners(UpdateStartupConfiguration request) - { - var config = GetLiveTVConfiguration(); - var save = false; - - if (string.IsNullOrWhiteSpace(request.LiveTvTunerPath) || - string.IsNullOrWhiteSpace(request.LiveTvTunerType)) - { - if (config.TunerHosts.Count > 0) - { - config.TunerHosts.Clear(); - save = true; - } - } - else - { - if (!config.TunerHosts.Any(i => string.Equals(i.Type, request.LiveTvTunerType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.Url, request.LiveTvTunerPath, StringComparison.OrdinalIgnoreCase))) - { - // Add tuner - await _liveTvManager.SaveTunerHost(new TunerHostInfo - { - Type = request.LiveTvTunerType, - Url = request.LiveTvTunerPath - - }).ConfigureAwait(false); - } - } - - if (save) - { - SaveLiveTVConfiguration(config); - } - } - - private void SaveLiveTVConfiguration(LiveTvOptions config) - { - _config.SaveConfiguration("livetv", config); - } - - private LiveTvOptions GetLiveTVConfiguration() - { - return _config.GetConfiguration("livetv"); - } } public class StartupConfiguration @@ -217,10 +151,6 @@ namespace MediaBrowser.Api public string UICulture { get; set; } public string MetadataCountryCode { get; set; } public string PreferredMetadataLanguage { get; set; } - public string LiveTvTunerType { get; set; } - public string LiveTvTunerPath { get; set; } - public string LiveTvGuideProviderId { get; set; } - public string LiveTvGuideProviderType { get; set; } } public class StartupInfo diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index a908d2d3f..b3467fbbc 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -376,19 +376,13 @@ namespace MediaBrowser.Controller.LiveTv /// Task. Task OnRecordingFileDeleted(BaseItem recording); - /// - /// Gets the sat ini mappings. - /// - /// List<NameValuePair>. - List GetSatIniMappings(); - - Task> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken); - Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); List ListingProviders { get; } + List GetTunerHostTypes(); + event EventHandler> SeriesTimerCancelled; event EventHandler> TimerCancelled; event EventHandler> TimerCreated; diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index c595f9cea..6a0fdede3 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -46,6 +46,7 @@ namespace MediaBrowser.Model.LiveTv public string Url { get; set; } public string Type { get; set; } public string DeviceId { get; set; } + public string FriendlyName { get; set; } public bool ImportFavoritesOnly { get; set; } public bool AllowHWTranscoding { get; set; } public bool EnableTvgId { get; set; } From 203fc64a9752f50989a04d4c6c173fe13a9dcd14 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 13 Mar 2017 14:58:00 -0400 Subject: [PATCH 19/67] improve clean db task --- .../Data/CleanDatabaseScheduledTask.cs | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 2819a249f..0096f2284 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities.Audio; @@ -22,13 +23,15 @@ namespace Emby.Server.Implementations.Data private readonly IItemRepository _itemRepo; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; + private readonly IApplicationPaths _appPaths; - public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem) + public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths) { _libraryManager = libraryManager; _itemRepo = itemRepo; _logger = logger; _fileSystem = fileSystem; + _appPaths = appPaths; } public string Name @@ -150,13 +153,27 @@ namespace Emby.Server.Implementations.Data try { - if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path)) + var isPathInLibrary = false; + + if (allLibraryPaths.Any(i => path.StartsWith(i, StringComparison.Ordinal)) || + allLibraryPaths.Contains(path, StringComparer.Ordinal) || + path.StartsWith(_appPaths.ProgramDataPath, StringComparison.Ordinal)) { - continue; + isPathInLibrary = true; + + if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path)) + { + continue; + } } var libraryItem = _libraryManager.GetItemById(item.Item1); + if (libraryItem == null) + { + continue; + } + if (libraryItem.IsTopParent) { continue; @@ -180,7 +197,14 @@ namespace Emby.Server.Implementations.Data continue; } - _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty); + if (isPathInLibrary) + { + _logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty); + } + else + { + _logger.Info("Deleting item from database {0} because path is no longer in the server library. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty); + } await libraryItem.OnFileDeleted().ConfigureAwait(false); } From a9b61af1549770b5a3c613c6b552f8bb698e9870 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 13 Mar 2017 15:01:20 -0400 Subject: [PATCH 20/67] 3.2.7.4 --- Emby.Drawing/ImageProcessor.cs | 20 ++++++++++---------- SharedVersion.cs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index a15f75c9a..5b04ceea2 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -238,7 +238,7 @@ namespace Emby.Drawing var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); - var imageProcessingLockTaken = false; + //var imageProcessingLockTaken = false; try { @@ -253,9 +253,9 @@ namespace Emby.Drawing var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath)); - await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false); + //await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false); - imageProcessingLockTaken = true; + //imageProcessingLockTaken = true; _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat); CopyFile(tmpPath, cacheFilePath); @@ -273,13 +273,13 @@ namespace Emby.Drawing // Just spit out the original file if all the options are default return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - finally - { - if (imageProcessingLockTaken) - { - _imageProcessingSemaphore.Release(); - } - } + //finally + //{ + // if (imageProcessingLockTaken) + // { + // _imageProcessingSemaphore.Release(); + // } + //} } private void CopyFile(string src, string destination) diff --git a/SharedVersion.cs b/SharedVersion.cs index 3f2c5d97e..825a8b919 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.7.3")] +[assembly: AssemblyVersion("3.2.7.4")] From a0934e62261f0fc8788efb13c653973c16d02d3e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 13 Mar 2017 16:42:21 -0400 Subject: [PATCH 21/67] continue with tuner discovery --- .../LiveTv/EmbyTV/EmbyTV.cs | 14 ++++++++++++++ .../LiveTv/LiveTvManager.cs | 5 +++++ .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 1 + MediaBrowser.Api/LiveTv/LiveTvService.cs | 15 ++++++++++++++- MediaBrowser.Controller/Entities/Video.cs | 3 ++- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 1 + .../MediaEncoding/EncodingHelper.cs | 6 ++++++ 7 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index a7ccafd69..42e8c4f5f 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2543,6 +2543,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public CancellationTokenSource CancellationTokenSource { get; set; } } + public async Task> DiscoverTuners(CancellationToken cancellationToken) + { + var list = new List(); + + foreach (var host in _liveTvManager.TunerHosts) + { + var discoveredDevices = await DiscoverDevices(host, 3000, cancellationToken).ConfigureAwait(false); + + list.AddRange(discoveredDevices); + } + + return list; + } + public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken) { foreach (var host in _liveTvManager.TunerHosts) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index de39d3838..92ef24dea 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -160,6 +160,11 @@ namespace Emby.Server.Implementations.LiveTv }).ToList(); } + public Task> DiscoverTuners(CancellationToken cancellationToken) + { + return EmbyTV.EmbyTV.Current.DiscoverTuners(cancellationToken); + } + void service_DataSourceChanged(object sender, EventArgs e) { if (!_isDisposed) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index dbcabd174..6d6730855 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -706,6 +706,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false); hostInfo.DeviceId = modelInfo.DeviceID; + hostInfo.FriendlyName = modelInfo.FriendlyName; return hostInfo; } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 639021762..2f7d04936 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -677,7 +677,14 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class GetTunerHostTypes : IReturn> { - + + } + + [Route("/LiveTv/Tuners/Discvover", "GET")] + [Authenticated] + public class DiscoverTuners : IReturn> + { + } public class LiveTvService : BaseApiService @@ -730,6 +737,12 @@ namespace MediaBrowser.Api.LiveTv }; } + public async Task Get(DiscoverTuners request) + { + var result = await _liveTvManager.DiscoverTuners(CancellationToken.None).ConfigureAwait(false); + return ToOptimizedResult(result); + } + public async Task Get(GetLiveStreamFile request) { var directStreamProvider = (await _liveTvManager.GetEmbyTvLiveStream(request.Id).ConfigureAwait(false)) as IDirectStreamProvider; diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 78f907d61..890626419 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -614,7 +614,8 @@ namespace MediaBrowser.Controller.Entities Timestamp = i.Timestamp, Type = type, PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(), - SupportsDirectStream = i.VideoType == VideoType.VideoFile + SupportsDirectStream = i.VideoType == VideoType.VideoFile, + IsRemote = i.IsShortcut }; if (info.Protocol == MediaProtocol.File) diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index b3467fbbc..0dda303a3 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -382,6 +382,7 @@ namespace MediaBrowser.Controller.LiveTv List ListingProviders { get; } List GetTunerHostTypes(); + Task> DiscoverTuners(CancellationToken cancellationToken); event EventHandler> SeriesTimerCancelled; event EventHandler> TimerCancelled; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ebcc9853a..6482b6829 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -185,6 +185,12 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + // obviously don't do this for strm files + if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + return container; } From 8d72c7e881c14961ef51b08658f4c30e66d86ac8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 14 Mar 2017 15:44:11 -0400 Subject: [PATCH 22/67] add option to refresh metadata every N number of days --- Emby.Server.Implementations/Dto/DtoService.cs | 4 ++-- .../Configuration/LibraryOptions.cs | 2 ++ .../Manager/MetadataService.cs | 21 ++++++++----------- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 2 +- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 147abd171..d477008a5 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -491,7 +491,7 @@ namespace Emby.Server.Implementations.Dto } } - //if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess)) + if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess)) { dto.PlayAccess = item.GetPlayAccess(user); } @@ -1639,7 +1639,7 @@ namespace Emby.Server.Implementations.Dto var width = size.Width; var height = size.Height; - if (width == 0 || height == 0) + if (width.Equals(0) || height.Equals(0)) { return null; } diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 7af0acc59..9cd656fa7 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -16,6 +16,8 @@ public bool EnableAutomaticSeriesGrouping { get; set; } public bool EnableEmbeddedTitles { get; set; } + public int AutomaticRefreshIntervalDays { get; set; } + /// /// Gets or sets the preferred metadata language. /// diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index bdfe13c1d..59a2278a1 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -45,12 +45,21 @@ namespace MediaBrowser.Providers.Manager var updateType = ItemUpdateType.None; var requiresRefresh = false; + var libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item); + if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None) { // TODO: If this returns true, should we instead just change metadata refresh mode to Full? requiresRefresh = item.RequiresRefresh(); } + if (!requiresRefresh && + libraryOptions.AutomaticRefreshIntervalDays > 0 && + (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= libraryOptions.AutomaticRefreshIntervalDays) + { + requiresRefresh = true; + } + var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem); var localImagesFailed = false; @@ -116,8 +125,6 @@ namespace MediaBrowser.Providers.Manager } } - LibraryOptions libraryOptions = null; - // Next run remote image providers, but only if local image providers didn't throw an exception if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly) { @@ -125,11 +132,6 @@ namespace MediaBrowser.Providers.Manager if (providers.Count > 0) { - if (libraryOptions == null) - { - libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item); - } - var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false); updateType = updateType | result.UpdateType; @@ -177,11 +179,6 @@ namespace MediaBrowser.Providers.Manager item.DateLastRefreshed = default(DateTime); } - if (libraryOptions == null) - { - libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item); - } - // Save to database await SaveItem(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false); } diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 1e03f1169..54133b718 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.696 + 3.0.697 Emby.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index d6777dad1..e59460c94 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.696 + 3.0.697 Emby.Server.Core Emby Team ebr,Luke,scottisafool From 296e5bfe32bd0e859d20632b2f20eca6d00a698d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 14 Mar 2017 15:44:35 -0400 Subject: [PATCH 23/67] update handling of new programs for xml tv --- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 6 ++++-- .../LiveTv/Listings/XmlTvListingsProvider.cs | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 42e8c4f5f..d18004f96 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2543,13 +2543,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public CancellationTokenSource CancellationTokenSource { get; set; } } + private const int TunerDiscoveryDurationMs = 3000; + public async Task> DiscoverTuners(CancellationToken cancellationToken) { var list = new List(); foreach (var host in _liveTvManager.TunerHosts) { - var discoveredDevices = await DiscoverDevices(host, 3000, cancellationToken).ConfigureAwait(false); + var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); list.AddRange(discoveredDevices); } @@ -2567,7 +2569,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken) { - var discoveredDevices = await DiscoverDevices(host, 3000, cancellationToken).ConfigureAwait(false); + var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); var configuredDevices = GetConfiguration().TunerHosts .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index c22bb1171..21c4006a6 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -205,6 +205,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings } programInfo.ShowId = uniqueString.GetMD5().ToString("N"); + + // If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped + if (programInfo.IsSeries && !programInfo.IsRepeat) + { + if ((programInfo.EpisodeNumber ?? 0) == 0) + { + programInfo.ShowId = programInfo.ShowId + programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture); + } + } } // Construct an id from the channel and start date From f069559b1e9f27adf3816f7035bf3ee638404862 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 14 Mar 2017 15:44:54 -0400 Subject: [PATCH 24/67] update tuner host interface --- .../LiveTv/TunerHosts/BaseTunerHost.cs | 25 ++++++++++++++++--- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 22 ++++++---------- .../LiveTv/TunerHosts/M3UTunerHost.cs | 12 --------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 79fa9bb61..c9cd289e7 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -135,8 +135,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { // 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 (hostsWithChannel.Count > 1 && - !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false)) + if (hostsWithChannel.Count > 1 && !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false)) { Logger.Error("Tuner is not currently available"); continue; @@ -208,6 +207,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts foreach (var host in hostsWithChannel) { + if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + try { var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); @@ -243,7 +247,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected abstract Task IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken); - protected abstract bool IsValidChannelId(string channelId); + protected virtual string ChannelIdPrefix + { + get + { + return Type + "_"; + } + } + protected virtual bool IsValidChannelId(string channelId) + { + if (string.IsNullOrWhiteSpace(channelId)) + { + throw new ArgumentNullException("channelId"); + } + + return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); + } protected LiveTvOptions GetConfiguration() { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 6d6730855..d9c0807de 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -56,7 +56,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun get { return "hdhomerun"; } } - private const string ChannelIdPrefix = "hdhr_"; + protected override string ChannelIdPrefix + { + get + { + return "hdhr_"; + } + } private string GetChannelId(TunerHostInfo info, Channels i) { @@ -559,26 +565,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return list; } - protected override bool IsValidChannelId(string channelId) - { - if (string.IsNullOrWhiteSpace(channelId)) - { - throw new ArgumentNullException("channelId"); - } - - return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); - } - protected override async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) { 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)) - { - throw new ArgumentException("Channel not found"); - } var hdhrId = GetHdHrIdFromChannelId(channelId); var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index cc0ae0983..4ec70f802 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -46,8 +46,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts get { return "M3U Tuner"; } } - private const string ChannelIdPrefix = "m3u_"; - protected override async Task> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken) { var result = await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false); @@ -87,16 +85,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - protected override bool IsValidChannelId(string channelId) - { - if (string.IsNullOrWhiteSpace(channelId)) - { - throw new ArgumentNullException("channelId"); - } - - return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); - } - protected override async Task> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) { var urlHash = info.Url.GetMD5().ToString("N"); From aaa244be4c3228bfb20e8f20c1c46c8e13472c9d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 14 Mar 2017 15:55:54 -0400 Subject: [PATCH 25/67] 3.2.7.5 --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 1 + SharedVersion.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 120f445c2..64a41acef 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -408,6 +408,7 @@ namespace Emby.Server.Implementations.Localization new LocalizatonOption{ Name="Italian", Value="it"}, new LocalizatonOption{ Name="Kazakh", Value="kk"}, new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"}, + new LocalizatonOption{ Name="Persian", Value="fa"}, new LocalizatonOption{ Name="Polish", Value="pl"}, new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"}, new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"}, diff --git a/SharedVersion.cs b/SharedVersion.cs index 825a8b919..b001cfb52 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.7.4")] +[assembly: AssemblyVersion("3.2.7.5")] From ce1ed2bea7492a4e6c4dd26e1a8d73aa65a88236 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 15 Mar 2017 15:57:18 -0400 Subject: [PATCH 26/67] update hls to support mpeg2video --- Emby.Dlna/Didl/DidlBuilder.cs | 4 +- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 12 +- .../LiveTv/LiveTvManager.cs | 4 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 4 +- .../Playback/BaseStreamingService.cs | 14 ++ .../Playback/Hls/BaseHlsService.cs | 28 +++- .../Playback/Hls/DynamicHlsService.cs | 41 +++--- .../Playback/Hls/HlsSegmentService.cs | 2 +- .../Playback/Hls/VideoHlsService.cs | 12 +- MediaBrowser.Api/Playback/StreamRequest.cs | 1 + .../LiveTv/ILiveTvManager.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 10 +- .../MediaEncoding/EncodingJobOptions.cs | 4 +- .../Subtitles/SubtitleEncoder.cs | 19 +-- .../Dlna/ProfileConditionValue.cs | 3 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 34 ++++- MediaBrowser.Model/Dlna/StreamInfo.cs | 52 ++++++- .../Music/MusicBrainzAlbumProvider.cs | 130 +++++++++++++----- SharedVersion.cs | 2 +- 20 files changed, 268 insertions(+), 112 deletions(-) diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index d048dcde1..c7eae91dd 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -208,7 +208,7 @@ namespace Emby.Dlna.Didl var targetHeight = streamInfo.TargetHeight; var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container, - streamInfo.VideoCodec, + streamInfo.TargetVideoCodec, streamInfo.TargetAudioCodec, targetWidth, targetHeight, @@ -352,7 +352,7 @@ namespace Emby.Dlna.Didl var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container, streamInfo.TargetAudioCodec, - streamInfo.VideoCodec, + streamInfo.TargetVideoCodec, streamInfo.TargetAudioBitrate, targetWidth, targetHeight, diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 3c07e95db..b73332c4b 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -542,7 +542,7 @@ namespace Emby.Dlna.PlayTo { var list = new ContentFeatureBuilder(profile) .BuildVideoHeader(streamInfo.Container, - streamInfo.VideoCodec, + streamInfo.TargetVideoCodec, streamInfo.TargetAudioCodec, streamInfo.TargetWidth, streamInfo.TargetHeight, diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index d18004f96..eea562524 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2545,14 +2545,24 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private const int TunerDiscoveryDurationMs = 3000; - public async Task> DiscoverTuners(CancellationToken cancellationToken) + public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) { var list = new List(); + var configuredDeviceIds = GetConfiguration().TunerHosts + .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId)) + .Select(i => i.DeviceId) + .ToList(); + foreach (var host in _liveTvManager.TunerHosts) { var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false); + if (newDevicesOnly) + { + discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase)) + .ToList(); + } list.AddRange(discoveredDevices); } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 92ef24dea..8406d44a7 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -160,9 +160,9 @@ namespace Emby.Server.Implementations.LiveTv }).ToList(); } - public Task> DiscoverTuners(CancellationToken cancellationToken) + public Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken) { - return EmbyTV.EmbyTV.Current.DiscoverTuners(cancellationToken); + return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken); } void service_DataSourceChanged(object sender, EventArgs e) diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 2f7d04936..2d8744f70 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -684,7 +684,7 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class DiscoverTuners : IReturn> { - + public bool NewDevicesOnly { get; set; } } public class LiveTvService : BaseApiService @@ -739,7 +739,7 @@ namespace MediaBrowser.Api.LiveTv public async Task Get(DiscoverTuners request) { - var result = await _liveTvManager.DiscoverTuners(CancellationToken.None).ConfigureAwait(false); + var result = await _liveTvManager.DiscoverTuners(request.NewDevicesOnly, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index e1559cabf..b3a00cc92 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -697,6 +697,20 @@ namespace MediaBrowser.Api.Playback { request.SubtitleCodec = val; } + else if (i == 31) + { + if (videoRequest != null) + { + videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + } + else if (i == 32) + { + if (videoRequest != null) + { + videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + } } } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index aaca1793c..98115b840 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -41,9 +41,16 @@ namespace MediaBrowser.Api.Playback.Hls /// /// Gets the segment file extension. /// - /// The state. - /// System.String. - protected abstract string GetSegmentFileExtension(StreamState state); + protected string GetSegmentFileExtension(StreamRequest request) + { + var segmentContainer = request.SegmentContainer; + if (!string.IsNullOrWhiteSpace(segmentContainer)) + { + return "." + segmentContainer; + } + + return ".ts"; + } /// /// Gets the type of the transcoding job. @@ -261,11 +268,17 @@ namespace MediaBrowser.Api.Playback.Hls var useGenericSegmenter = false; if (useGenericSegmenter) { - var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state); + var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); var timeDeltaParam = String.Empty; - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); + if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) + { + segmentFormat = "mpegts"; + } + + return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), threads, @@ -276,7 +289,8 @@ namespace MediaBrowser.Api.Playback.Hls startNumberParam, outputPath, outputTsArg, - timeDeltaParam + timeDeltaParam, + segmentFormat ).Trim(); } @@ -286,7 +300,7 @@ namespace MediaBrowser.Api.Playback.Hls var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"", itsOffset, inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), + EncodingHelper.GetInputArgument(state, encodingOptions), threads, EncodingHelper.GetMapArgs(state), GetVideoArguments(state), diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 1074a8bc1..f77a66f8e 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Api.Playback.Hls { } - [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.ts", "GET")] + [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] public class GetHlsVideoSegment : VideoStreamRequest { public string PlaylistId { get; set; } @@ -77,8 +77,7 @@ namespace MediaBrowser.Api.Playback.Hls public string SegmentId { get; set; } } - [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.aac", "GET")] - [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.ts", "GET")] + [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] public class GetHlsAudioSegment : StreamRequest { public string PlaylistId { get; set; } @@ -158,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Hls var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex); - var segmentExtension = GetSegmentFileExtension(state); + var segmentExtension = GetSegmentFileExtension(state.Request); TranscodingJob job = null; @@ -420,7 +419,7 @@ namespace MediaBrowser.Api.Playback.Hls var filename = Path.GetFileNameWithoutExtension(playlist); - return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state)); + return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state.Request)); } private async Task GetSegmentResult(StreamState state, string playlistPath, @@ -740,7 +739,7 @@ namespace MediaBrowser.Api.Playback.Hls name, index.ToString(UsCulture), - GetSegmentFileExtension(isOutputVideo), + GetSegmentFileExtension(request), queryString)); index++; @@ -848,7 +847,7 @@ namespace MediaBrowser.Api.Playback.Hls var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg; + args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg; //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; @@ -897,7 +896,7 @@ namespace MediaBrowser.Api.Playback.Hls if (useGenericSegmenter) { - var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state); + var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); var timeDeltaParam = String.Empty; @@ -907,7 +906,13 @@ namespace MediaBrowser.Api.Playback.Hls timeDeltaParam = string.Format("-segment_time_delta -{0}", startTime); } - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); + if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) + { + segmentFormat = "mpegts"; + } + + return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), threads, @@ -918,7 +923,8 @@ namespace MediaBrowser.Api.Playback.Hls startNumberParam, outputPath, outputTsArg, - timeDeltaParam + timeDeltaParam, + segmentFormat ).Trim(); } @@ -935,20 +941,5 @@ namespace MediaBrowser.Api.Playback.Hls outputPath ).Trim(); } - - /// - /// Gets the segment file extension. - /// - /// The state. - /// System.String. - protected override string GetSegmentFileExtension(StreamState state) - { - return GetSegmentFileExtension(state.IsOutputVideo); - } - - protected string GetSegmentFileExtension(bool isOutputVideo) - { - return isOutputVideo ? ".ts" : ".ts"; - } } } \ No newline at end of file diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index f3683c6cb..bf78e16b3 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Hls /// /// Class GetHlsVideoSegment /// - [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")] + [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] public class GetHlsVideoSegmentLegacy : VideoStreamRequest { public string PlaylistId { get; set; } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index c9c6acc1b..f9164c36f 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.Api.Playback.Hls var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg; + args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg; // Add resolution params, if specified if (!hasGraphicalSubs) @@ -118,16 +118,6 @@ namespace MediaBrowser.Api.Playback.Hls return args; } - /// - /// Gets the segment file extension. - /// - /// The state. - /// System.String. - protected override string GetSegmentFileExtension(StreamState state) - { - return ".ts"; - } - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) { } diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index f223c99ef..d1bf9c120 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -41,6 +41,7 @@ namespace MediaBrowser.Api.Playback public string PlaySessionId { get; set; } public string LiveStreamId { get; set; } public string Tag { get; set; } + public string SegmentContainer { get; set; } } public class VideoStreamRequest : StreamRequest diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 0dda303a3..5242c5b1f 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -382,7 +382,7 @@ namespace MediaBrowser.Controller.LiveTv List ListingProviders { get; } List GetTunerHostTypes(); - Task> DiscoverTuners(CancellationToken cancellationToken); + Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken); event EventHandler> SeriesTimerCancelled; event EventHandler> TimerCancelled; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 6482b6829..db23712ba 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -733,12 +733,18 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream.IsInterlaced) { - return false; + if (request.DeInterlace) + { + return false; + } } if (videoStream.IsAnamorphic ?? false) { - return false; + if (request.RequireNonAnamorphic) + { + return false; + } } // Can't stream copy if we're burning in subtitles diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index 4bb180d4b..6baf87a04 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.MediaEncoding AudioBitRate = info.AudioBitrate; AudioSampleRate = info.TargetAudioSampleRate; DeviceProfile = deviceProfile; - VideoCodec = info.VideoCodec; + VideoCodec = info.TargetVideoCodec; VideoBitRate = info.VideoBitrate; AudioStreamIndex = info.AudioStreamIndex; MaxRefFrames = info.MaxRefFrames; @@ -185,6 +185,8 @@ namespace MediaBrowser.Controller.MediaEncoding [ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MaxVideoBitDepth { get; set; } public bool RequireAvc { get; set; } + public bool DeInterlace { get; set; } + public bool RequireNonAnamorphic { get; set; } public int? TranscodingMaxAudioChannels { get; set; } public int? CpuCoreLimit { get; set; } public string OutputContainer { get; set; } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 4a1bf5305..77b976206 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -734,16 +734,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - var charsetFromLanguage = string.IsNullOrWhiteSpace(language) - ? null - : GetSubtitleFileCharacterSetFromLanguage(language); - - // This assumption should only be made for external subtitles - if (!string.IsNullOrWhiteSpace(charsetFromLanguage) && !string.Equals(charsetFromLanguage, "windows-1252", StringComparison.OrdinalIgnoreCase)) - { - return charsetFromLanguage; - } - var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(charset)) @@ -756,7 +746,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles return charset; } - return charsetFromLanguage; + if (!string.IsNullOrWhiteSpace(language)) + { + return GetSubtitleFileCharacterSetFromLanguage(language); + } + + return null; } public string GetSubtitleFileCharacterSetFromLanguage(string language) @@ -854,4 +849,4 @@ namespace MediaBrowser.MediaEncoding.Subtitles throw new ArgumentOutOfRangeException("protocol"); } } -} +} \ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs index 7e2002f17..dbd574f86 100644 --- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs +++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs @@ -21,6 +21,7 @@ NumVideoStreams = 17, IsSecondaryAudio = 18, VideoCodecTag = 19, - IsAvc = 20 + IsAvc = 20, + IsInterlaced = 21 } } \ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 480eb23a5..80d3ea3fb 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -231,6 +231,7 @@ namespace MediaBrowser.Model.Dlna { playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(','); } + playlistItem.SubProtocol = transcodingProfile.Protocol; List audioCodecProfiles = new List(); @@ -479,7 +480,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(','); - playlistItem.VideoCodec = transcodingProfile.VideoCodec; + playlistItem.VideoCodecs = transcodingProfile.VideoCodec.Split(','); playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps; playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; @@ -1137,6 +1138,37 @@ namespace MediaBrowser.Model.Dlna break; } case ProfileConditionValue.IsAnamorphic: + { + bool isAnamorphic; + if (bool.TryParse(value, out isAnamorphic)) + { + if (isAnamorphic && condition.Condition == ProfileConditionType.Equals) + { + item.RequireNonAnamorphic = true; + } + else if (!isAnamorphic && condition.Condition == ProfileConditionType.NotEquals) + { + item.RequireNonAnamorphic = true; + } + } + break; + } + case ProfileConditionValue.IsInterlaced: + { + bool isInterlaced; + if (bool.TryParse(value, out isInterlaced)) + { + if (isInterlaced && condition.Condition == ProfileConditionType.Equals) + { + item.DeInterlace = true; + } + else if (!isInterlaced && condition.Condition == ProfileConditionType.NotEquals) + { + item.DeInterlace = true; + } + } + break; + } case ProfileConditionValue.AudioProfile: case ProfileConditionValue.Has64BitOffsets: case ProfileConditionValue.PacketLength: diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index a85e6085b..7e42e7fae 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -18,6 +18,7 @@ namespace MediaBrowser.Model.Dlna public StreamInfo() { AudioCodecs = new string[] { }; + VideoCodecs = new string[] { }; SubtitleCodecs = new string[] { }; } @@ -34,13 +35,15 @@ namespace MediaBrowser.Model.Dlna public long StartPositionTicks { get; set; } - public string VideoCodec { get; set; } public string VideoProfile { get; set; } public bool RequireAvc { get; set; } + public bool DeInterlace { get; set; } + public bool RequireNonAnamorphic { get; set; } public bool CopyTimestamps { get; set; } public bool EnableSubtitlesInManifest { get; set; } public string[] AudioCodecs { get; set; } + public string[] VideoCodecs { get; set; } public int? AudioStreamIndex { get; set; } @@ -204,11 +207,15 @@ namespace MediaBrowser.Model.Dlna string.Empty : string.Join(",", item.AudioCodecs); + string videoCodecs = item.VideoCodecs.Length == 0 ? + string.Empty : + string.Join(",", item.VideoCodecs); + list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty)); list.Add(new NameValuePair("Static", item.IsDirectStream.ToString().ToLower())); - list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty)); + list.Add(new NameValuePair("VideoCodec", videoCodecs)); list.Add(new NameValuePair("AudioCodec", audioCodecs)); list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty)); list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty)); @@ -232,7 +239,9 @@ namespace MediaBrowser.Model.Dlna // } //} - if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls") && !forceStartPosition) + var isHls = StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls"); + + if (isHls && !forceStartPosition) { list.Add(new NameValuePair("StartTimeTicks", string.Empty)); } @@ -276,6 +285,14 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty)); + list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString().ToLower())); + list.Add(new NameValuePair("DeInterlace", item.DeInterlace.ToString().ToLower())); + + if (!isDlna && isHls) + { + list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty)); + } + return list; } @@ -609,9 +626,34 @@ namespace MediaBrowser.Model.Dlna } } + public string TargetVideoCodec + { + get + { + MediaStream stream = TargetVideoStream; + + string inputCodec = stream == null ? null : stream.Codec; + + if (IsDirectStream) + { + return inputCodec; + } + + foreach (string codec in VideoCodecs) + { + if (StringHelper.EqualsIgnoreCase(codec, inputCodec)) + { + return codec; + } + } + + return VideoCodecs.Length == 0 ? null : VideoCodecs[0]; + } + } + /// - /// Predicts the audio channels that will be in the output stream - /// + /// Predicts the audio channels that will be in the output stream + /// public long? TargetSize { get diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index e6eb0951d..4aefb62c8 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -45,14 +45,21 @@ namespace MediaBrowser.Providers.Music public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) { var releaseId = searchInfo.GetReleaseId(); + var releaseGroupId = searchInfo.GetReleaseGroupId(); string url = null; var isNameSearch = false; + bool forceMusicBrainzProper = false; if (!string.IsNullOrEmpty(releaseId)) { url = string.Format("/ws/2/release/?query=reid:{0}", releaseId); } + else if (!string.IsNullOrEmpty(releaseGroupId)) + { + url = string.Format("/ws/2/release?release-group={0}", releaseGroupId); + forceMusicBrainzProper = true; + } else { var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId(); @@ -75,7 +82,7 @@ namespace MediaBrowser.Providers.Music if (!string.IsNullOrWhiteSpace(url)) { - using (var stream = await GetMusicBrainzResponse(url, isNameSearch, cancellationToken).ConfigureAwait(false)) + using (var stream = await GetMusicBrainzResponse(url, isNameSearch, forceMusicBrainzProper, cancellationToken).ConfigureAwait(false)) { return GetResultsFromResponse(stream); } @@ -131,7 +138,14 @@ namespace MediaBrowser.Providers.Music Item = new MusicAlbum() }; - if (string.IsNullOrEmpty(releaseId)) + // If we have a release group Id but not a release Id... + if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId)) + { + releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false); + result.HasMetadata = true; + } + + if (string.IsNullOrWhiteSpace(releaseId)) { var artistMusicBrainzId = id.GetMusicBrainzArtistId(); @@ -139,13 +153,13 @@ namespace MediaBrowser.Providers.Music if (releaseResult != null) { - if (!string.IsNullOrEmpty(releaseResult.ReleaseId)) + if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId)) { releaseId = releaseResult.ReleaseId; result.HasMetadata = true; } - if (!string.IsNullOrEmpty(releaseResult.ReleaseGroupId)) + if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId)) { releaseGroupId = releaseResult.ReleaseGroupId; result.HasMetadata = true; @@ -157,13 +171,13 @@ namespace MediaBrowser.Providers.Music } // If we have a release Id but not a release group Id... - if (!string.IsNullOrEmpty(releaseId) && string.IsNullOrEmpty(releaseGroupId)) + if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId)) { - releaseGroupId = await GetReleaseGroupId(releaseId, cancellationToken).ConfigureAwait(false); + releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false); result.HasMetadata = true; } - if (!string.IsNullOrEmpty(releaseId) || !string.IsNullOrEmpty(releaseGroupId)) + if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId)) { result.HasMetadata = true; } @@ -411,13 +425,42 @@ namespace MediaBrowser.Providers.Music } } + private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) + { + var url = string.Format("/ws/2/release?release-group={0}", releaseGroupId); + + using (var stream = await GetMusicBrainzResponse(url, true, true, cancellationToken).ConfigureAwait(false)) + { + using (var oReader = new StreamReader(stream, Encoding.UTF8)) + { + var settings = _xmlSettings.Create(false); + + settings.CheckCharacters = false; + settings.IgnoreProcessingInstructions = true; + settings.IgnoreComments = true; + + using (var reader = XmlReader.Create(oReader, settings)) + { + var result = ReleaseResult.Parse(reader).FirstOrDefault(); + + if (result != null) + { + return result.ReleaseId; + } + } + } + } + + return null; + } + /// /// Gets the release group id internal. /// /// The release entry id. /// The cancellation token. /// Task{System.String}. - private async Task GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken) + private async Task GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken) { var url = string.Format("/ws/2/release-group/?query=reid:{0}", releaseEntryId); @@ -514,11 +557,11 @@ namespace MediaBrowser.Providers.Music private List _mbzUrls = null; private MbzUrl _chosenUrl; - private async Task GetMbzUrl() + private async Task GetMbzUrl(bool forceMusicBrainzProper = false) { if (_chosenUrl == null || _mbzUrls == null || (DateTime.UtcNow.Ticks - _lastMbzUrlQueryTicks) > TimeSpan.FromHours(12).Ticks) { - var urls = await RefreshMzbUrls().ConfigureAwait(false); + var urls = await RefreshMzbUrls(forceMusicBrainzProper).ConfigureAwait(false); if (urls.Count > 1) { @@ -533,30 +576,12 @@ namespace MediaBrowser.Providers.Music return _chosenUrl; } - private async Task> RefreshMzbUrls() + private async Task> RefreshMzbUrls(bool forceMusicBrainzProper = false) { List list; - try + if (forceMusicBrainzProper) { - var options = new HttpRequestOptions - { - Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls", - UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion - }; - - using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) - { - var results = _json.DeserializeFromStream>(stream); - - list = results; - } - _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks; - } - catch (Exception ex) - { - _logger.ErrorException("Error getting music brainz info", ex); - list = new List { new MbzUrl @@ -566,22 +591,55 @@ namespace MediaBrowser.Providers.Music } }; } + else + { + try + { + var options = new HttpRequestOptions + { + Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls", + UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion + }; + + using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) + { + var results = _json.DeserializeFromStream>(stream); + + list = results; + } + _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks; + } + catch (Exception ex) + { + _logger.ErrorException("Error getting music brainz info", ex); + + list = new List + { + new MbzUrl + { + url = MusicBrainzBaseUrl, + throttleMs = 1000 + } + }; + } + } _mbzUrls = list.ToList(); return list; } + internal Task GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken) + { + return GetMusicBrainzResponse(url, isSearch, false, cancellationToken); + } + /// /// Gets the music brainz response. /// - /// The URL. - /// if set to true [is search]. - /// The cancellation token. - /// Task{XmlDocument}. - internal async Task GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken) + internal async Task GetMusicBrainzResponse(string url, bool isSearch, bool forceMusicBrainzProper, CancellationToken cancellationToken) { - var urlInfo = await GetMbzUrl().ConfigureAwait(false); + var urlInfo = await GetMbzUrl(forceMusicBrainzProper).ConfigureAwait(false); var throttleMs = urlInfo.throttleMs; if (throttleMs > 0) diff --git a/SharedVersion.cs b/SharedVersion.cs index b001cfb52..1c2997eed 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.7.5")] +[assembly: AssemblyVersion("3.2.8.1")] From 44a4e662ac0e2b067266011c686ce79bbef5e93c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 16 Mar 2017 13:21:24 -0400 Subject: [PATCH 27/67] update hdhomerun model info caching --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 14 +++++++++++--- .../Encoder/EncoderValidator.cs | 1 + MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 5 ++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index d9c0807de..02a6bf85d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -131,7 +131,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun DiscoverResponse response; if (_modelCache.TryGetValue(info.Url, out response)) { - return response; + if ((DateTime.UtcNow - response.DateQueried).TotalHours <= 12) + { + return response; + } } } @@ -141,8 +144,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), CancellationToken = cancellationToken, - CacheLength = TimeSpan.FromDays(1), - CacheMode = CacheMode.Unconditional, TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), BufferContent = false @@ -638,6 +639,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public string BaseURL { get; set; } public string LineupURL { get; set; } public int TunerCount { get; set; } + + public DateTime DateQueried { get; set; } + + public DiscoverResponse() + { + DateQueried = DateTime.UtcNow; + } } public async Task> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 9c1189f6c..15221c2ac 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -89,6 +89,7 @@ namespace MediaBrowser.MediaEncoding.Encoder var found = new List(); var required = new[] { + "mpeg2video", "h264_qsv", "hevc_qsv", "mpeg2_qsv", diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index 8a150e0c1..650d7a138 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -464,10 +464,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - item.ProductionLocations = val.Split('/') + item.ProductionLocations.AddRange(val.Split('/') .Select(i => i.Trim()) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToList(); + .Where(i => !string.IsNullOrWhiteSpace(i))); } break; } From db904832388278a36d5f1b1e7c1609931694e9e7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 16 Mar 2017 13:23:30 -0400 Subject: [PATCH 28/67] 3.2.8.2 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 1c2997eed..1148ee0e7 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.1")] +[assembly: AssemblyVersion("3.2.8.2")] From c9ee7633caacb548fd54c33c7c9df1be29b71d29 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 17 Mar 2017 16:23:34 -0400 Subject: [PATCH 29/67] embed recording button into video player --- .../Net/SocketFactory.cs | 12 ++++++++++++ .../Playback/Hls/BaseHlsService.cs | 7 +++++-- MediaBrowser.Api/Playback/StreamRequest.cs | 3 +++ MediaBrowser.Api/Playback/StreamState.cs | 18 ++++++++++++++++++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 9 +++++++++ MediaBrowser.Model/Dlna/StreamInfo.cs | 14 ++++++++++++++ MediaBrowser.Model/Dlna/TranscodingProfile.cs | 6 ++++++ 7 files changed, 67 insertions(+), 2 deletions(-) diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 0f4306a6b..1fd367afb 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -52,6 +52,18 @@ namespace Emby.Common.Implementations.Net { throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); } + catch (ArgumentException ex) + { + if (dualMode) + { + // Mono for BSD incorrectly throws ArgumentException instead of SocketException + throw new SocketCreateException("AddressFamilyNotSupported", ex); + } + else + { + throw; + } + } } public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 98115b840..d2f14f4b8 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -110,8 +110,11 @@ namespace MediaBrowser.Api.Playback.Hls throw; } - var waitForSegments = state.SegmentLength >= 10 ? 2 : 3; - await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false); + var minSegments = state.MinSegments; + if (minSegments > 0) + { + await WaitForMinimumSegmentCount(playlist, minSegments, cancellationTokenSource.Token).ConfigureAwait(false); + } } } finally diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index d1bf9c120..f49fd87e0 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -42,6 +42,9 @@ namespace MediaBrowser.Api.Playback public string LiveStreamId { get; set; } public string Tag { get; set; } public string SegmentContainer { get; set; } + + public int? SegmentLength { get; set; } + public int? MinSegments { get; set; } } public class VideoStreamRequest : StreamRequest diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 912d60889..22a0fa7c9 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -60,6 +60,11 @@ namespace MediaBrowser.Api.Playback { get { + if (Request.SegmentLength.HasValue) + { + return Request.SegmentLength.Value; + } + if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { var userAgent = UserAgent ?? string.Empty; @@ -86,6 +91,19 @@ namespace MediaBrowser.Api.Playback } } + public int MinSegments + { + get + { + if (Request.MinSegments.HasValue) + { + return Request.MinSegments.Value; + } + + return SegmentLength >= 10 ? 2 : 3; + } + } + public bool IsSegmentedLiveStream { get diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 80d3ea3fb..bfb65ed42 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -484,6 +484,15 @@ namespace MediaBrowser.Model.Dlna playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps; playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; + if (transcodingProfile.MinSegments > 0) + { + playlistItem.MinSegments = transcodingProfile.MinSegments; + } + if (transcodingProfile.SegmentLength > 0) + { + playlistItem.SegmentLength = transcodingProfile.SegmentLength; + } + if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels)) { int transcodingMaxAudioChannels; diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 7e42e7fae..5705e6477 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace MediaBrowser.Model.Dlna @@ -37,6 +38,9 @@ namespace MediaBrowser.Model.Dlna public string VideoProfile { get; set; } + public int? SegmentLength { get; set; } + public int? MinSegments { get; set; } + public bool RequireAvc { get; set; } public bool DeInterlace { get; set; } public bool RequireNonAnamorphic { get; set; } @@ -291,6 +295,16 @@ namespace MediaBrowser.Model.Dlna if (!isDlna && isHls) { list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty)); + + if (item.SegmentLength.HasValue) + { + list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture))); + } + + if (item.MinSegments.HasValue) + { + list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture))); + } } return list; diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 76f2332f2..350556e90 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -42,6 +42,12 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("maxAudioChannels")] public string MaxAudioChannels { get; set; } + [XmlAttribute("minSegments")] + public int MinSegments { get; set; } + + [XmlAttribute("segmentLength")] + public int SegmentLength { get; set; } + public List GetAudioCodecs() { List list = new List(); From 3dcaeea20c3a7055dd8759e351d30c591ce1360d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 17 Mar 2017 16:24:44 -0400 Subject: [PATCH 30/67] 3.2.8.3 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 1148ee0e7..0374cd9c0 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.2")] +[assembly: AssemblyVersion("3.2.8.3")] From 79f3eb3fe2b33a49ce43fe0c616671483e97efc3 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 18 Mar 2017 19:37:29 -0400 Subject: [PATCH 31/67] update live tv osd --- Emby.Common.Implementations/IO/ManagedFileSystem.cs | 6 +++++- MediaBrowser.Model/Dlna/StreamBuilder.cs | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index 2c9388a41..0c1c02cd5 100644 --- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs @@ -28,7 +28,11 @@ namespace Emby.Common.Implementations.IO Logger = logger; _supportsAsyncFileStreams = true; _tempPath = tempPath; - EnableFileSystemRequestConcat = false; + + // On Linux, this needs to be true or symbolic links are ignored + EnableFileSystemRequestConcat = environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows && + environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.OSX; + SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows); } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index bfb65ed42..1c7aa81e5 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1037,6 +1037,12 @@ namespace MediaBrowser.Model.Dlna private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate) { + // Don't restrict by bitrate if coming from an external domain + if (item.IsRemote) + { + return true; + } + if (!maxBitrate.HasValue) { _logger.Info("Cannot direct play due to unknown supported bitrate"); From 41060c9a38cb7cf8875cb85fd3a4577ae8546f8a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 18 Mar 2017 19:39:11 -0400 Subject: [PATCH 32/67] 3.2.8.4 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 0374cd9c0..f818da847 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.3")] +[assembly: AssemblyVersion("3.2.8.4")] From 59ac045c6b875fd9ed8dc2ecc2ca580ba4fdab2d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 19 Mar 2017 02:10:11 -0400 Subject: [PATCH 33/67] increase unification of param building --- .../Playback/Hls/HlsSegmentService.cs | 6 +- .../BaseProgressiveStreamingService.cs | 3 - .../Playback/Progressive/VideoService.cs | 74 +------------------ MediaBrowser.Api/Playback/StreamRequest.cs | 2 - MediaBrowser.Api/Playback/StreamState.cs | 3 - .../MediaEncoding/EncodingHelper.cs | 74 +++++++++++++++++++ .../MediaEncoding/EncodingJobInfo.cs | 5 +- .../MediaEncoding/EncodingJobOptions.cs | 2 + .../Encoder/BaseEncoder.cs | 2 +- .../Encoder/EncodingJob.cs | 1 - .../Encoder/EncodingJobFactory.cs | 11 ++- .../Encoder/VideoEncoder.cs | 45 +---------- 12 files changed, 98 insertions(+), 130 deletions(-) diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index bf78e16b3..03291670b 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -109,11 +109,13 @@ namespace MediaBrowser.Api.Playback.Hls public Task Get(GetHlsVideoSegmentLegacy request) { var file = request.SegmentId + Path.GetExtension(Request.PathInfo); - file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file); + + var transcodeFolderPath = _config.ApplicationPaths.TranscodingTempPath; + file = Path.Combine(transcodeFolderPath, file); var normalizedPlaylistId = request.PlaylistId; - var playlistPath = _fileSystem.GetFilePaths(_config.ApplicationPaths.TranscodingTempPath) + var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath) .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); return GetFileResult(file, playlistPath); diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 23a84e480..441fff849 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -15,9 +15,6 @@ using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Playback.Progressive diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index bc600d3ea..228f50eab 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -1,4 +1,3 @@ -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; @@ -9,12 +8,8 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using System; using System.IO; -using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Services; @@ -116,11 +111,6 @@ namespace MediaBrowser.Api.Playback.Progressive var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); - var subtitleArguments = state.SubtitleStream != null && - state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed - ? GetSubtitleArguments(state) - : string.Empty; - return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), @@ -128,30 +118,13 @@ namespace MediaBrowser.Api.Playback.Progressive EncodingHelper.GetMapArgs(state), GetVideoArguments(state, videoCodec), threads, - GetAudioArguments(state), - subtitleArguments, + EncodingHelper.GetProgressiveVideoAudioArguments(state, encodingOptions), + EncodingHelper.GetSubtitleEmbedArguments(state), format, outputPath ).Trim(); } - private string GetSubtitleArguments(StreamState state) - { - var format = state.SupportedSubtitleCodecs.FirstOrDefault(); - string codec; - - if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase)) - { - codec = "copy"; - } - else - { - codec = format; - } - - return " -codec:s:0 " + codec; - } - /// /// Gets video arguments to pass to ffmpeg /// @@ -233,48 +206,5 @@ namespace MediaBrowser.Api.Playback.Progressive return args; } - - /// - /// Gets audio arguments to pass to ffmpeg - /// - /// The state. - /// System.String. - private string GetAudioArguments(StreamState state) - { - // If the video doesn't have an audio stream, return a default. - if (state.AudioStream == null && state.VideoStream != null) - { - return string.Empty; - } - - // Get the output codec name - var codec = EncodingHelper.GetAudioEncoder(state); - - var args = "-codec:a:0 " + codec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return args; - } - - // Add the number of audio channels - var channels = state.OutputAudioChannels; - - if (channels.HasValue) - { - args += " -ac " + channels.Value; - } - - var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) - { - args += " -ab " + bitrate.Value.ToString(UsCulture); - } - - args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), false); - - return args; - } } } \ No newline at end of file diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index f49fd87e0..551dbf378 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -32,8 +32,6 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string AudioCodec { get; set; } - public string SubtitleCodec { get; set; } - [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string DeviceProfileId { get; set; } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 22a0fa7c9..afc48b64c 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -120,7 +120,6 @@ namespace MediaBrowser.Api.Playback } } - public List SupportedSubtitleCodecs { get; set; } public string UserAgent { get; set; } public TranscodingJobType TranscodingType { get; set; } @@ -129,7 +128,6 @@ namespace MediaBrowser.Api.Playback { _mediaSourceManager = mediaSourceManager; _logger = logger; - SupportedSubtitleCodecs = new List(); TranscodingType = transcodingType; } @@ -209,7 +207,6 @@ namespace MediaBrowser.Api.Playback } public string OutputFilePath { get; set; } - public int? OutputAudioBitrate; public string ActualOutputVideoCodec { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index c73170fcf..8a1e2698a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1736,5 +1736,79 @@ namespace MediaBrowser.Controller.MediaEncoding return threads; } + + public string GetSubtitleEmbedArguments(EncodingJobInfo state) + { + if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed) + { + return string.Empty; + } + + var format = state.SupportedSubtitleCodecs.FirstOrDefault(); + string codec; + + if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + codec = "copy"; + } + else + { + codec = format; + } + + // Muxing in dvbsub via either copy or -codec dvbsub does not seem to work + // It doesn't throw any errors but vlc on android will not render them + // They will need to be converted to an alternative format + // TODO: This is incorrectly assuming that dvdsub will be supported by the player + // The api will need to be expanded to accomodate this. + if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)) + { + codec = "dvdsub"; + } + + var args = " -codec:s:0 " + codec; + + args += " -disposition:s:0 default"; + + return args; + } + + public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions) + { + // If the video doesn't have an audio stream, return a default. + if (state.AudioStream == null && state.VideoStream != null) + { + return string.Empty; + } + + // Get the output codec name + var codec = GetAudioEncoder(state); + + var args = "-codec:a:0 " + codec; + + if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + { + return args; + } + + // Add the number of audio channels + var channels = state.OutputAudioChannels; + + if (channels.HasValue) + { + args += " -ac " + channels.Value; + } + + var bitrate = state.OutputAudioBitrate; + + if (bitrate.HasValue) + { + args += " -ab " + bitrate.Value.ToString(_usCulture); + } + + args += " " + GetAudioFilterParam(state, encodingOptions, false); + + return args; + } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index a18b86432..ac33b8c8c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.MediaEncoding public int? OutputVideoBitrate { get; set; } public MediaStream SubtitleStream { get; set; } public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } + public List SupportedSubtitleCodecs { get; set; } public int InternalSubtitleStreamOffset { get; set; } public MediaSourceInfo MediaSource { get; set; } @@ -64,6 +65,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { return BaseRequest.CopyTimestamps; } } + public int? OutputAudioBitrate; public int? OutputAudioChannels; public int? OutputAudioSampleRate; public bool DeInterlace { get; set; } @@ -74,8 +76,9 @@ namespace MediaBrowser.Controller.MediaEncoding _logger = logger; RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); PlayableStreamFileNames = new List(); + SupportedAudioCodecs = new List(); SupportedVideoCodecs = new List(); - SupportedVideoCodecs = new List(); + SupportedSubtitleCodecs = new List(); } /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index 6baf87a04..41f146375 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -198,6 +198,8 @@ namespace MediaBrowser.Controller.MediaEncoding [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string VideoCodec { get; set; } + public string SubtitleCodec { get; set; } + /// /// Gets or sets the index of the audio stream. /// diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index b0b37f2d6..635c8deac 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.MediaEncoding.Encoder IProgress progress, CancellationToken cancellationToken) { - var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager) + var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager, MediaEncoder) .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index f6895696a..1b26d3b2a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -109,7 +109,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } public string OutputFilePath { get; set; } - public int? OutputAudioBitrate; public string ActualOutputVideoCodec { diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs index 4b336e671..3e99d68ce 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs @@ -22,15 +22,17 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly ILibraryManager _libraryManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly IConfigurationManager _config; + private readonly IMediaEncoder _mediaEncoder; protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config) + public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder) { _logger = logger; _libraryManager = libraryManager; _mediaSourceManager = mediaSourceManager; _config = config; + _mediaEncoder = mediaEncoder; } public async Task CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress progress, CancellationToken cancellationToken) @@ -61,6 +63,13 @@ namespace MediaBrowser.MediaEncoding.Encoder request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); } + if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) + { + state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToSubtitleCodec(i)) + ?? state.SupportedSubtitleCodecs.FirstOrDefault(); + } + var item = _libraryManager.GetItemById(request.ItemId); state.ItemType = item.GetType().Name; diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs index 52ef4d834..a779c1bca 100644 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Encoder EncodingHelper.GetMapArgs(state), videoArguments, threads, - GetAudioArguments(state), + EncodingHelper.GetProgressiveVideoAudioArguments(state, encodingOptions), format, state.OutputFilePath ).Trim(); @@ -114,49 +114,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return args; } - /// - /// Gets audio arguments to pass to ffmpeg - /// - /// The state. - /// System.String. - private string GetAudioArguments(EncodingJob state) - { - // If the video doesn't have an audio stream, return a default. - if (state.AudioStream == null && state.VideoStream != null) - { - return string.Empty; - } - - // Get the output codec name - var codec = EncodingHelper.GetAudioEncoder(state); - - var args = "-codec:a:0 " + codec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return args; - } - - // Add the number of audio channels - var channels = state.OutputAudioChannels; - - if (channels.HasValue) - { - args += " -ac " + channels.Value; - } - - var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) - { - args += " -ab " + bitrate.Value.ToString(UsCulture); - } - - args += " " + EncodingHelper.GetAudioFilterParam(state, GetEncodingOptions(), false); - - return args; - } - protected override string GetOutputFileExtension(EncodingJob state) { var ext = base.GetOutputFileExtension(state); From 38e05b11e2cef0ee15d8c6d0ee063db08dafde24 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 19 Mar 2017 14:59:05 -0400 Subject: [PATCH 34/67] unify encodng param creation --- .../Data/SqliteItemRepository.cs | 79 +++++++++--- .../Emby.Server.Implementations.csproj | 2 - .../HttpServer/GetSwaggerResource.cs | 17 --- .../HttpServer/SwaggerService.cs | 47 -------- .../Playback/Progressive/VideoService.cs | 114 +----------------- MediaBrowser.Api/Playback/StreamState.cs | 1 - .../MediaEncoding/EncodingHelper.cs | 108 +++++++++++++++++ .../MediaEncoding/EncodingJobInfo.cs | 2 + .../MediaEncoding/EncodingJobOptions.cs | 4 +- .../Encoder/AudioEncoder.cs | 4 +- .../Encoder/BaseEncoder.cs | 4 +- .../Encoder/EncodingJob.cs | 1 - .../Encoder/VideoEncoder.cs | 92 +------------- 13 files changed, 180 insertions(+), 295 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs delete mode 100644 Emby.Server.Implementations/HttpServer/SwaggerService.cs diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index e65ebeb04..c5ba6c892 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3204,6 +3204,40 @@ namespace Emby.Server.Implementations.Data } } + private bool IsAlphaNumeric(string str) + { + if (string.IsNullOrWhiteSpace(str)) + return false; + + for (int i = 0; i < str.Length; i++) + { + if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i])))) + return false; + } + + return true; + } + + private bool IsValidType(string value) + { + return IsAlphaNumeric(value); + } + + private bool IsValidMediaType(string value) + { + return IsAlphaNumeric(value); + } + + private bool IsValidId(string value) + { + return IsAlphaNumeric(value); + } + + private bool IsValidPersonType(string value) + { + return IsAlphaNumeric(value); + } + private List GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "") { if (query.IsResumable ?? false) @@ -3423,9 +3457,9 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@ChannelId", query.ChannelIds[0]); } } - if (query.ChannelIds.Length > 1) + else if (query.ChannelIds.Length > 1) { - var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i + "'").ToArray()); + var inClause = string.Join(",", query.ChannelIds.Where(IsValidId).Select(i => "'" + i + "'").ToArray()); whereClauses.Add(string.Format("ChannelId in ({0})", inClause)); } @@ -4157,17 +4191,18 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))"); } } - if (query.MediaTypes.Length == 1) + var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray(); + if (queryMediaTypes.Length == 1) { whereClauses.Add("MediaType=@MediaTypes"); if (statement != null) { - statement.TryBind("@MediaTypes", query.MediaTypes[0]); + statement.TryBind("@MediaTypes", queryMediaTypes[0]); } } - if (query.MediaTypes.Length > 1) + else if (queryMediaTypes.Length > 1) { - var val = string.Join(",", query.MediaTypes.Select(i => "'" + i + "'").ToArray()); + var val = string.Join(",", queryMediaTypes.Select(i => "'" + i + "'").ToArray()); whereClauses.Add("MediaType in (" + val + ")"); } @@ -4273,7 +4308,9 @@ namespace Emby.Server.Implementations.Data //var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0; var enableItemsByName = query.IncludeItemsByName ?? false; - if (query.TopParentIds.Length == 1) + var queryTopParentIds = query.TopParentIds.Where(IsValidId).ToArray(); + + if (queryTopParentIds.Length == 1) { if (enableItemsByName) { @@ -4289,12 +4326,12 @@ namespace Emby.Server.Implementations.Data } if (statement != null) { - statement.TryBind("@TopParentId", query.TopParentIds[0]); + statement.TryBind("@TopParentId", queryTopParentIds[0]); } } - if (query.TopParentIds.Length > 1) + else if (queryTopParentIds.Length > 1) { - var val = string.Join(",", query.TopParentIds.Select(i => "'" + i + "'").ToArray()); + var val = string.Join(",", queryTopParentIds.Select(i => "'" + i + "'").ToArray()); if (enableItemsByName) { @@ -4544,7 +4581,7 @@ namespace Emby.Server.Implementations.Data return result; } - return new[] { value }; + return new[] { value }.Where(IsValidType); } public async Task DeleteItem(Guid id, CancellationToken cancellationToken) @@ -4696,31 +4733,35 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidParamValue()); } } - if (query.PersonTypes.Count == 1) + var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); + + if (queryPersonTypes.Count == 1) { whereClauses.Add("PersonType=@PersonType"); if (statement != null) { - statement.TryBind("@PersonType", query.PersonTypes[0]); + statement.TryBind("@PersonType", queryPersonTypes[0]); } } - if (query.PersonTypes.Count > 1) + else if (queryPersonTypes.Count > 1) { - var val = string.Join(",", query.PersonTypes.Select(i => "'" + i + "'").ToArray()); + var val = string.Join(",", queryPersonTypes.Select(i => "'" + i + "'").ToArray()); whereClauses.Add("PersonType in (" + val + ")"); } - if (query.ExcludePersonTypes.Count == 1) + var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList(); + + if (queryExcludePersonTypes.Count == 1) { whereClauses.Add("PersonType<>@PersonType"); if (statement != null) { - statement.TryBind("@PersonType", query.ExcludePersonTypes[0]); + statement.TryBind("@PersonType", queryExcludePersonTypes[0]); } } - if (query.ExcludePersonTypes.Count > 1) + else if (queryExcludePersonTypes.Count > 1) { - var val = string.Join(",", query.ExcludePersonTypes.Select(i => "'" + i + "'").ToArray()); + var val = string.Join(",", queryExcludePersonTypes.Select(i => "'" + i + "'").ToArray()); whereClauses.Add("PersonType not in (" + val + ")"); } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index ecd86d507..afd437fe8 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -85,7 +85,6 @@ - @@ -103,7 +102,6 @@ - diff --git a/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs b/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs deleted file mode 100644 index 819ede1ab..000000000 --- a/Emby.Server.Implementations/HttpServer/GetSwaggerResource.cs +++ /dev/null @@ -1,17 +0,0 @@ -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.HttpServer -{ - /// - /// Class GetDashboardResource - /// - [Route("/swagger-ui/{ResourceName*}", "GET")] - public class GetSwaggerResource - { - /// - /// Gets or sets the name. - /// - /// The name. - public string ResourceName { get; set; } - } -} \ No newline at end of file diff --git a/Emby.Server.Implementations/HttpServer/SwaggerService.cs b/Emby.Server.Implementations/HttpServer/SwaggerService.cs deleted file mode 100644 index d41946645..000000000 --- a/Emby.Server.Implementations/HttpServer/SwaggerService.cs +++ /dev/null @@ -1,47 +0,0 @@ -using MediaBrowser.Controller; -using MediaBrowser.Controller.Net; -using System.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.HttpServer -{ - public class SwaggerService : IService, IRequiresRequest - { - private readonly IServerApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - - public SwaggerService(IServerApplicationPaths appPaths, IFileSystem fileSystem, IHttpResultFactory resultFactory) - { - _appPaths = appPaths; - _fileSystem = fileSystem; - _resultFactory = resultFactory; - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSwaggerResource request) - { - var swaggerDirectory = Path.Combine(_appPaths.ApplicationResourcesPath, "swagger-ui"); - - var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', _fileSystem.DirectorySeparatorChar)); - - return _resultFactory.GetStaticFileResult(Request, requestedFile).Result; - } - - /// - /// Gets or sets the result factory. - /// - /// The result factory. - private readonly IHttpResultFactory _resultFactory; - - /// - /// Gets or sets the request context. - /// - /// The request context. - public IRequest Request { get; set; } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 228f50eab..8394e016c 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -6,11 +6,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; -using System; -using System.IO; using System.Threading.Tasks; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Playback.Progressive @@ -95,116 +92,7 @@ namespace MediaBrowser.Api.Playback.Progressive { var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - // Get the output codec name - var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - var format = string.Empty; - var keyFrame = string.Empty; - - if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase)) - { - // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js - format = " -f mp4 -movflags frag_keyframe+empty_moov"; - } - - var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); - - var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); - - return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", - inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), - keyFrame, - EncodingHelper.GetMapArgs(state), - GetVideoArguments(state, videoCodec), - threads, - EncodingHelper.GetProgressiveVideoAudioArguments(state, encodingOptions), - EncodingHelper.GetSubtitleEmbedArguments(state), - format, - outputPath - ).Trim(); - } - - /// - /// Gets video arguments to pass to ffmpeg - /// - /// The state. - /// The video codec. - /// System.String. - private string GetVideoArguments(StreamState state, string videoCodec) - { - var args = "-codec:v:0 " + videoCodec; - - if (state.EnableMpegtsM2TsMode) - { - args += " -mpegts_m2ts_mode 1"; - } - - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) - { - args += " -bsf:v h264_mp4toannexb"; - } - - if (state.RunTimeTicks.HasValue && state.VideoRequest.CopyTimestamps) - { - args += " -copyts -avoid_negative_ts disabled -start_at_zero"; - } - - if (!state.RunTimeTicks.HasValue) - { - args += " -flags -global_header -fflags +genpts"; - } - - return args; - } - - var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", - 5.ToString(UsCulture)); - - args += keyFrameArg; - - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; - - var hasCopyTs = false; - // Add resolution params, if specified - if (!hasGraphicalSubs) - { - var outputSizeParam = EncodingHelper.GetOutputSizeParam(state, videoCodec); - args += outputSizeParam; - hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; - } - - if (state.RunTimeTicks.HasValue && state.VideoRequest.CopyTimestamps) - { - if (!hasCopyTs) - { - args += " -copyts"; - } - args += " -avoid_negative_ts disabled -start_at_zero"; - } - - var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, encodingOptions, GetDefaultH264Preset()); - - if (!string.IsNullOrEmpty(qualityParam)) - { - args += " " + qualityParam.Trim(); - } - - // This is for internal graphical subs - if (hasGraphicalSubs) - { - args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec); - } - - if (!state.RunTimeTicks.HasValue) - { - args += " -flags -global_header"; - } - - return args; + return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset()); } } } \ No newline at end of file diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index afc48b64c..24ba0606d 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -134,7 +134,6 @@ namespace MediaBrowser.Api.Playback public string MimeType { get; set; } public bool EstimateContentLength { get; set; } - public bool EnableMpegtsM2TsMode { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public long? EncodingDurationTicks { get; set; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 8a1e2698a..939a473c5 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1773,6 +1773,114 @@ namespace MediaBrowser.Controller.MediaEncoding return args; } + public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultH264Preset) + { + // Get the output codec name + var videoCodec = GetVideoEncoder(state, encodingOptions); + + var format = string.Empty; + var keyFrame = string.Empty; + + if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) && + state.BaseRequest.Context == EncodingContext.Streaming) + { + // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js + format = " -f mp4 -movflags frag_keyframe+empty_moov"; + } + + var threads = GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); + + var inputModifier = GetInputModifier(state, encodingOptions); + + return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", + inputModifier, + GetInputArgument(state, encodingOptions), + keyFrame, + GetMapArgs(state), + GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultH264Preset), + threads, + GetProgressiveVideoAudioArguments(state, encodingOptions), + GetSubtitleEmbedArguments(state), + format, + outputPath + ).Trim(); + } + + public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset) + { + var args = "-codec:v:0 " + videoCodec; + + if (state.EnableMpegtsM2TsMode) + { + args += " -mpegts_m2ts_mode 1"; + } + + if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) + { + args += " -bsf:v h264_mp4toannexb"; + } + + if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) + { + args += " -copyts -avoid_negative_ts disabled -start_at_zero"; + } + + if (!state.RunTimeTicks.HasValue) + { + args += " -flags -global_header -fflags +genpts"; + } + + return args; + } + + var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", + 5.ToString(_usCulture)); + + args += keyFrameArg; + + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.BaseRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; + + var hasCopyTs = false; + // Add resolution params, if specified + if (!hasGraphicalSubs) + { + var outputSizeParam = GetOutputSizeParam(state, videoCodec); + args += outputSizeParam; + hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1; + } + + if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) + { + if (!hasCopyTs) + { + args += " -copyts"; + } + args += " -avoid_negative_ts disabled -start_at_zero"; + } + + var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset); + + if (!string.IsNullOrEmpty(qualityParam)) + { + args += " " + qualityParam.Trim(); + } + + // This is for internal graphical subs + if (hasGraphicalSubs) + { + args += GetGraphicalSubtitleParam(state, videoCodec); + } + + if (!state.RunTimeTicks.HasValue) + { + args += " -flags -global_header"; + } + + return args; + } + public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions) { // If the video doesn't have an audio stream, return a default. diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index ac33b8c8c..6f566515e 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -53,6 +53,8 @@ namespace MediaBrowser.Controller.MediaEncoding public string InputContainer { get; set; } public IsoType? IsoType { get; set; } + public bool EnableMpegtsM2TsMode { get; set; } + public BaseEncodingJobOptions BaseRequest { get; set; } public long? StartTimeTicks diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index 41f146375..73be78dc9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -14,7 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding public string AudioCodec { get; set; } public DeviceProfile DeviceProfile { get; set; } - public EncodingContext Context { get; set; } public bool ReadInputAtNativeFramerate { get; set; } @@ -214,9 +213,12 @@ namespace MediaBrowser.Controller.MediaEncoding [ApiMember(Name = "VideoStreamIndex", Description = "Optional. The index of the video stream to use. If omitted the first video stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? VideoStreamIndex { get; set; } + public EncodingContext Context { get; set; } + public BaseEncodingJobOptions() { EnableAutoStreamCopy = true; + Context = EncodingContext.Streaming; } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs index a4db8f3b8..05b3ca5fc 100644 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { } - protected override Task GetCommandLineArguments(EncodingJob state) + protected override string GetCommandLineArguments(EncodingJob state) { var audioTranscodeParams = new List(); @@ -78,7 +78,7 @@ namespace MediaBrowser.MediaEncoding.Encoder mapArgs, metadata).Trim(); - return Task.FromResult(result); + return result; } protected override string GetOutputFileExtension(EncodingJob state) diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index 635c8deac..ef68fb90a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.MediaEncoding.Encoder await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false); - var commandLineArgs = await GetCommandLineArguments(encodingJob).ConfigureAwait(false); + var commandLineArgs = GetCommandLineArguments(encodingJob); var process = ProcessFactory.Create(new ProcessOptions { @@ -265,7 +265,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return ConfigurationManager.GetConfiguration("encoding"); } - protected abstract Task GetCommandLineArguments(EncodingJob job); + protected abstract string GetCommandLineArguments(EncodingJob job); private string GetOutputFilePath(EncodingJob state) { diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index 1b26d3b2a..449c31ace 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -36,7 +36,6 @@ namespace MediaBrowser.MediaEncoding.Encoder public string MimeType { get; set; } public bool EstimateContentLength { get; set; } - public bool EnableMpegtsM2TsMode { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public long? EncodingDurationTicks { get; set; } public string LiveStreamId { get; set; } diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs index a779c1bca..96c126923 100644 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs @@ -18,100 +18,12 @@ namespace MediaBrowser.MediaEncoding.Encoder { } - protected override async Task GetCommandLineArguments(EncodingJob state) + protected override string GetCommandLineArguments(EncodingJob state) { // Get the output codec name var encodingOptions = GetEncodingOptions(); - var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - var format = string.Empty; - var keyFrame = string.Empty; - - if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase) && - state.Options.Context == EncodingContext.Streaming) - { - // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js - format = " -f mp4 -movflags frag_keyframe+empty_moov"; - } - - var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); - - var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); - - var videoArguments = await GetVideoArguments(state, videoCodec).ConfigureAwait(false); - - return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"", - inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), - keyFrame, - EncodingHelper.GetMapArgs(state), - videoArguments, - threads, - EncodingHelper.GetProgressiveVideoAudioArguments(state, encodingOptions), - format, - state.OutputFilePath - ).Trim(); - } - - /// - /// Gets video arguments to pass to ffmpeg - /// - /// The state. - /// The video codec. - /// System.String. - private async Task GetVideoArguments(EncodingJob state, string videoCodec) - { - var args = "-codec:v:0 " + videoCodec; - - if (state.EnableMpegtsM2TsMode) - { - args += " -mpegts_m2ts_mode 1"; - } - - var isOutputMkv = string.Equals(state.Options.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase); - - if (state.RunTimeTicks.HasValue) - { - //args += " -copyts -avoid_negative_ts disabled -start_at_zero"; - } - - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) - { - args += " -bsf:v h264_mp4toannexb"; - } - - return args; - } - - var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", - 5.ToString(UsCulture)); - - args += keyFrameArg; - - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode; - - // Add resolution params, if specified - if (!hasGraphicalSubs) - { - args += EncodingHelper.GetOutputSizeParam(state, videoCodec); - } - - var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, GetEncodingOptions(), "superfast"); - - if (!string.IsNullOrEmpty(qualityParam)) - { - args += " " + qualityParam.Trim(); - } - - // This is for internal graphical subs - if (hasGraphicalSubs) - { - args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec); - } - - return args; + return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, state.OutputFilePath, "superfast"); } protected override string GetOutputFileExtension(EncodingJob state) From 5b3b18b51dec8421d3ffc8c00874e22b026586b6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 19 Mar 2017 14:59:23 -0400 Subject: [PATCH 35/67] 3.2.8.5 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index f818da847..cae8e0a25 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.4")] +[assembly: AssemblyVersion("3.2.8.5")] From f1b1458ee8b94a5c75ed009581f5e9cb22ba70b7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 20 Mar 2017 13:56:44 -0400 Subject: [PATCH 36/67] 3.2.8.6 --- Emby.Server.Implementations/HttpServer/FileWriter.cs | 2 +- SharedVersion.cs | 2 +- SocketHttpListener.Portable/Net/ResponseStream.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index d230a9b91..dbaf97b1e 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -160,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - await response.TransmitFile(Path, RangeStart, RangeEnd, FileShare, cancellationToken).ConfigureAwait(false); + await response.TransmitFile(Path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false); } finally { diff --git a/SharedVersion.cs b/SharedVersion.cs index cae8e0a25..6582f8f0f 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.5")] +[assembly: AssemblyVersion("3.2.8.6")] diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index 19821f954..3c79f47c2 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -363,7 +363,7 @@ namespace SocketHttpListener.Net } } - private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) + private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { var array = new byte[81920]; int count; From 2bac4f85ad1dfe23710554ceee5ffea093092157 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 21 Mar 2017 13:31:40 -0400 Subject: [PATCH 37/67] improve next up performance --- .../TV/TVSeriesManager.cs | 31 +++++++++++-------- MediaBrowser.Api/TvShowsService.cs | 9 +++++- MediaBrowser.Model/Querying/NextUpQuery.cs | 3 ++ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 9dfaa102a..b5e64bc23 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.TV // Avoid implicitly captured closure var episodes = GetNextUpEpisodes(request, user, items); - return GetResult(episodes, null, request); + return GetResult(episodes, request); } public QueryResult GetNextUp(NextUpQuery request, List parentsFolders) @@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.TV // Avoid implicitly captured closure var episodes = GetNextUpEpisodes(request, user, items); - return GetResult(episodes, null, request); + return GetResult(episodes, request); } public IEnumerable GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable seriesKeys) @@ -163,8 +163,7 @@ namespace Emby.Server.Implementations.TV return false; }) .Select(i => i.Item2()) - .Where(i => i != null) - .Take(request.Limit ?? int.MaxValue); + .Where(i => i != null); } private string GetUniqueSeriesKey(BaseItem series) @@ -232,24 +231,30 @@ namespace Emby.Server.Implementations.TV return new Tuple>(DateTime.MinValue, getEpisode); } - private QueryResult GetResult(IEnumerable items, int? totalRecordLimit, NextUpQuery query) + private QueryResult GetResult(IEnumerable items, NextUpQuery query) { - var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray(); - var totalCount = itemsArray.Length; + int totalCount = 0; + if (query.EnableTotalRecordCount) + { + var list = items.ToList(); + totalCount = list.Count; + items = list; + } + + if (query.StartIndex.HasValue) + { + items = items.Skip(query.StartIndex.Value); + } if (query.Limit.HasValue) { - itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray(); - } - else if (query.StartIndex.HasValue) - { - itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray(); + items = items.Take(query.Limit.Value); } return new QueryResult { TotalRecordCount = totalCount, - Items = itemsArray + Items = items.ToArray() }; } } diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 71c5e732a..43fe74d5f 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -72,6 +72,12 @@ namespace MediaBrowser.Api [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableUserData { get; set; } + public bool EnableTotalRecordCount { get; set; } + + public GetNextUpEpisodes() + { + EnableTotalRecordCount = true; + } } [Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")] @@ -376,7 +382,8 @@ namespace MediaBrowser.Api ParentId = request.ParentId, SeriesId = request.SeriesId, StartIndex = request.StartIndex, - UserId = request.UserId + UserId = request.UserId, + EnableTotalRecordCount = request.EnableTotalRecordCount }); var user = _userManager.GetUserById(request.UserId); diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index b5f50bde0..e3b0d578a 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -55,9 +55,12 @@ namespace MediaBrowser.Model.Querying /// The enable image types. public ImageType[] EnableImageTypes { get; set; } + public bool EnableTotalRecordCount { get; set; } + public NextUpQuery() { EnableImageTypes = new ImageType[] {}; + EnableTotalRecordCount = true; } } } From d5b861c88e1b81097c75122d10e848dd9cf50e53 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 21 Mar 2017 13:31:56 -0400 Subject: [PATCH 38/67] 3.2.8.7 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 6582f8f0f..34981db41 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.6")] +[assembly: AssemblyVersion("3.2.8.7")] From 696e82a2fbceef1f51881ca4ccd805727ca15a26 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 22 Mar 2017 02:03:49 -0400 Subject: [PATCH 39/67] 3.2.8.8 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 34981db41..28e89a683 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.7")] +[assembly: AssemblyVersion("3.2.8.8")] From 1823b4a6746670e310dac1ad431b741010f3616d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 22 Mar 2017 14:37:04 -0400 Subject: [PATCH 40/67] 3.2.8.9 --- Emby.Server.Core/ApplicationHost.cs | 7 +------ .../Emby.Server.Implementations.csproj | 1 - .../MediaBrowser.ServerApplication.csproj | 1 + .../Native}/LoopUtil.cs | 5 ++++- MediaBrowser.ServerApplication/WindowsAppHost.cs | 6 +++++- SharedVersion.cs | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) rename {Emby.Server.Implementations/Windows => MediaBrowser.ServerApplication/Native}/LoopUtil.cs (98%) diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 971378ea7..50c572b8c 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -107,7 +107,6 @@ using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations; using Emby.Server.Implementations.ServerManager; using Emby.Server.Implementations.Session; -using Emby.Server.Implementations.Windows; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using MediaBrowser.Model.Activity; @@ -1743,12 +1742,8 @@ namespace Emby.Server.Core ((IProcess)sender).Dispose(); } - public void EnableLoopback(string appName) + public virtual void EnableLoopback(string appName) { - if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) - { - LoopUtil.Run(appName); - } } private void RegisterModules() diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index afd437fe8..670acd37f 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -267,7 +267,6 @@ - diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 63e10df76..b968c2fb6 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -142,6 +142,7 @@ MainForm.cs + diff --git a/Emby.Server.Implementations/Windows/LoopUtil.cs b/MediaBrowser.ServerApplication/Native/LoopUtil.cs similarity index 98% rename from Emby.Server.Implementations/Windows/LoopUtil.cs rename to MediaBrowser.ServerApplication/Native/LoopUtil.cs index 6eded2cec..6160f853f 100644 --- a/Emby.Server.Implementations/Windows/LoopUtil.cs +++ b/MediaBrowser.ServerApplication/Native/LoopUtil.cs @@ -5,7 +5,10 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; -namespace Emby.Server.Implementations.Windows +/* + * Important - Even though this will compile in the shared projects, it will cause build failures within the mono runtime + */ +namespace MediaBrowser.ServerApplication.Native { /// /// http://blogs.msdn.com/b/fiddler/archive/2011/12/10/fiddler-windows-8-apps-enable-LoopUtil-network-isolation-exemption.aspx diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index 2d3d8a85b..8f1a88a74 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -11,7 +11,6 @@ using Emby.Server.Core; using Emby.Server.Implementations; using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.FFMpeg; -using Emby.Server.Implementations.Windows; using Emby.Server.Sync; using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Sync; @@ -49,6 +48,11 @@ namespace MediaBrowser.ServerApplication MainStartup.Restart(); } + public override void EnableLoopback(string appName) + { + LoopUtil.Run(appName); + } + protected override List GetAssembliesWithPartsInternal() { var list = new List(); diff --git a/SharedVersion.cs b/SharedVersion.cs index 28e89a683..3087dc497 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.8")] +[assembly: AssemblyVersion("3.2.8.9")] From da88fbb8241b35076ff368f94b49b9b97d8d826d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 23 Mar 2017 15:10:10 -0400 Subject: [PATCH 41/67] update hd homerun udp stream --- .../LiveTv/TunerHosts/BaseTunerHost.cs | 2 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 9 ++++++--- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index c9cd289e7..74b8a7764 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false); var list = result.ToList(); - Logger.Debug("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list)); + Logger.Info("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list)); if (!string.IsNullOrWhiteSpace(key) && list.Count > 0) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index a881d0ea1..867e07e87 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -120,7 +120,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // send url to start streaming await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false); - var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + var timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token; + var response = await udpClient.ReceiveAsync(timeoutToken).ConfigureAwait(false); _logger.Info("Opened HDHR UDP stream from {0}", remoteAddress); if (!cancellationToken.IsCancellationRequested) @@ -135,8 +136,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false); } } - catch (OperationCanceledException) + catch (OperationCanceledException ex) { + _logger.Info("HDHR UDP stream cancelled or timed out from {0}", remoteAddress); + openTaskCompletionSource.TrySetException(ex); break; } catch (Exception ex) @@ -291,4 +294,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun throw new NotImplementedException(); } } -} +} \ No newline at end of file diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 939a473c5..eb69a8f7b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -154,6 +154,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return "mpegts"; } + // For these need to find out the ffmpeg names if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase)) { @@ -179,6 +180,11 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + // Seeing reported failures here, not sure yet if this is related to specfying input format if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase)) { From b68e10c23cd929fd4ea44c90abe7a43e7844fe47 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 23 Mar 2017 15:10:30 -0400 Subject: [PATCH 42/67] 3.2.8.10 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 3087dc497..5b64033eb 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.9")] +[assembly: AssemblyVersion("3.2.8.10")] From c9be9b41415ea67356cb73820d41e19f822ec727 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 24 Mar 2017 11:03:49 -0400 Subject: [PATCH 43/67] update hls params --- .../LiveTv/EmbyTV/EmbyTV.cs | 27 ++++ .../Playback/BaseStreamingService.cs | 132 +----------------- .../Playback/Hls/BaseHlsService.cs | 9 +- MediaBrowser.Api/Playback/StreamState.cs | 8 ++ .../MediaBrowser.Controller.csproj | 1 + .../MediaEncoding/EncodingJobInfo.cs | 4 +- .../MediaEncoding}/JobLogger.cs | 47 +++++-- .../Encoder/BaseEncoder.cs | 2 +- .../Encoder/EncodingJob.cs | 6 +- .../MediaBrowser.MediaEncoding.csproj | 1 - MediaBrowser.Model/Dlna/StreamBuilder.cs | 14 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 17 +-- .../Manager/ItemImageProvider.cs | 17 +-- 13 files changed, 107 insertions(+), 178 deletions(-) rename {MediaBrowser.MediaEncoding/Encoder => MediaBrowser.Controller/MediaEncoding}/JobLogger.cs (64%) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index eea562524..3bd2dd1bf 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -710,7 +710,34 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new InvalidOperationException("SeriesId for program not found"); } + // If any timers have already been manually created, make sure they don't get cancelled + var existingTimers = (await GetTimersAsync(CancellationToken.None).ConfigureAwait(false)) + .Where(i => + { + if (string.Equals(i.ProgramId, info.ProgramId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.ProgramId)) + { + return true; + } + + //if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId)) + //{ + // return true; + //} + + return false; + }) + .ToList(); + _seriesTimerProvider.Add(info); + + foreach (var timer in existingTimers) + { + timer.SeriesTimerId = info.Id; + timer.IsManual = true; + + _timerProvider.AddOrUpdate(timer); + } + await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false); return info.Id; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index b3a00cc92..480508e6f 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -289,8 +289,10 @@ namespace MediaBrowser.Api.Playback // MUST read both stdout and stderr asynchronously or a deadlock may occurr //process.BeginOutputReadLine(); + state.TranscodingJob = transcodingJob; + // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream)); + new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream); // Wait for the file to exist before proceeeding while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited) @@ -340,134 +342,6 @@ namespace MediaBrowser.Api.Playback // string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase); } - private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target) - { - try - { - using (var reader = new StreamReader(source)) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - - ParseLogLine(line, transcodingJob, state); - - var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); - - await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - await target.FlushAsync().ConfigureAwait(false); - } - } - } - catch (ObjectDisposedException) - { - // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux - } - catch (Exception ex) - { - Logger.ErrorException("Error reading ffmpeg log", ex); - } - } - - private void ParseLogLine(string line, TranscodingJob transcodingJob, StreamState state) - { - float? framerate = null; - double? percent = null; - TimeSpan? transcodingPosition = null; - long? bytesTranscoded = null; - int? bitRate = null; - - var parts = line.Split(' '); - - var totalMs = state.RunTimeTicks.HasValue - ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds - : 0; - - var startMs = state.Request.StartTimeTicks.HasValue - ? TimeSpan.FromTicks(state.Request.StartTimeTicks.Value).TotalMilliseconds - : 0; - - for (var i = 0; i < parts.Length; i++) - { - var part = parts[i]; - - if (string.Equals(part, "fps=", StringComparison.OrdinalIgnoreCase) && - (i + 1 < parts.Length)) - { - var rate = parts[i + 1]; - float val; - - if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val)) - { - framerate = val; - } - } - else if (state.RunTimeTicks.HasValue && - part.StartsWith("time=", StringComparison.OrdinalIgnoreCase)) - { - var time = part.Split(new[] { '=' }, 2).Last(); - TimeSpan val; - - if (TimeSpan.TryParse(time, UsCulture, out val)) - { - var currentMs = startMs + val.TotalMilliseconds; - - var percentVal = currentMs / totalMs; - percent = 100 * percentVal; - - transcodingPosition = val; - } - } - else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase)) - { - var size = part.Split(new[] { '=' }, 2).Last(); - - int? scale = null; - if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1) - { - scale = 1024; - size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase); - } - - if (scale.HasValue) - { - long val; - - if (long.TryParse(size, NumberStyles.Any, UsCulture, out val)) - { - bytesTranscoded = val * scale.Value; - } - } - } - else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase)) - { - var rate = part.Split(new[] { '=' }, 2).Last(); - - int? scale = null; - if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1) - { - scale = 1024; - rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase); - } - - if (scale.HasValue) - { - float val; - - if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val)) - { - bitRate = (int)Math.Ceiling(val * scale.Value); - } - } - } - } - - if (framerate.HasValue || percent.HasValue) - { - ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate); - } - } - /// /// Processes the exited. /// diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index d2f14f4b8..53813860a 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -268,7 +268,7 @@ namespace MediaBrowser.Api.Playback.Hls "hls/" + Path.GetFileNameWithoutExtension(outputPath)); } - var useGenericSegmenter = false; + var useGenericSegmenter = true; if (useGenericSegmenter) { var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); @@ -281,7 +281,9 @@ namespace MediaBrowser.Api.Playback.Hls segmentFormat = "mpegts"; } - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + baseUrlParam = string.Format("\"{0}/\"", "hls/" + Path.GetFileNameWithoutExtension(outputPath)); + + return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_entry_prefix {12} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), threads, @@ -293,7 +295,8 @@ namespace MediaBrowser.Api.Playback.Hls outputPath, outputTsArg, timeDeltaParam, - segmentFormat + segmentFormat, + baseUrlParam ).Trim(); } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 24ba0606d..2f5f6227a 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -154,6 +154,8 @@ namespace MediaBrowser.Api.Playback DisposeLiveStream(); DisposeLogStream(); DisposeIsoMount(); + + TranscodingJob = null; } private void DisposeLogStream() @@ -476,5 +478,11 @@ namespace MediaBrowser.Api.Playback return true; } } + + public TranscodingJob TranscodingJob; + public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) + { + ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); + } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4ebee531f..88153868f 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -190,6 +190,7 @@ + diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 6f566515e..f5878864b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -12,7 +12,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Controller.MediaEncoding { // For now, a common base class until the API and MediaEncoding classes are unified - public class EncodingJobInfo + public abstract class EncodingJobInfo { private readonly ILogger _logger; @@ -115,5 +115,7 @@ namespace MediaBrowser.Controller.MediaEncoding IsoMount = null; } } + + public abstract void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs similarity index 64% rename from MediaBrowser.MediaEncoding/Encoder/JobLogger.cs rename to MediaBrowser.Controller/MediaEncoding/JobLogger.cs index cb6e58f17..03e4f7771 100644 --- a/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -6,7 +6,7 @@ using System.IO; using System.Linq; using System.Text; -namespace MediaBrowser.MediaEncoding.Encoder +namespace MediaBrowser.Controller.MediaEncoding { public class JobLogger { @@ -18,7 +18,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger = logger; } - public async void StartStreamingLog(EncodingJob transcodingJob, Stream source, Stream target) + public async void StartStreamingLog(EncodingJobInfo state, Stream source, Stream target) { try { @@ -28,35 +28,41 @@ namespace MediaBrowser.MediaEncoding.Encoder { var line = await reader.ReadLineAsync().ConfigureAwait(false); - ParseLogLine(line, transcodingJob); + ParseLogLine(line, state); var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await target.FlushAsync().ConfigureAwait(false); } } } + catch (ObjectDisposedException) + { + // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux + } catch (Exception ex) { _logger.ErrorException("Error reading ffmpeg log", ex); } } - private void ParseLogLine(string line, EncodingJob transcodingJob) + private void ParseLogLine(string line, EncodingJobInfo state) { float? framerate = null; double? percent = null; TimeSpan? transcodingPosition = null; long? bytesTranscoded = null; + int? bitRate = null; var parts = line.Split(' '); - var totalMs = transcodingJob.RunTimeTicks.HasValue - ? TimeSpan.FromTicks(transcodingJob.RunTimeTicks.Value).TotalMilliseconds + var totalMs = state.RunTimeTicks.HasValue + ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds : 0; - var startMs = transcodingJob.Options.StartTimeTicks.HasValue - ? TimeSpan.FromTicks(transcodingJob.Options.StartTimeTicks.Value).TotalMilliseconds + var startMs = state.BaseRequest.StartTimeTicks.HasValue + ? TimeSpan.FromTicks(state.BaseRequest.StartTimeTicks.Value).TotalMilliseconds : 0; for (var i = 0; i < parts.Length; i++) @@ -74,7 +80,7 @@ namespace MediaBrowser.MediaEncoding.Encoder framerate = val; } } - else if (transcodingJob.RunTimeTicks.HasValue && + else if (state.RunTimeTicks.HasValue && part.StartsWith("time=", StringComparison.OrdinalIgnoreCase)) { var time = part.Split(new[] { '=' }, 2).Last(); @@ -111,11 +117,32 @@ namespace MediaBrowser.MediaEncoding.Encoder } } } + else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase)) + { + var rate = part.Split(new[] { '=' }, 2).Last(); + + int? scale = null; + if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1) + { + scale = 1024; + rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase); + } + + if (scale.HasValue) + { + float val; + + if (float.TryParse(rate, NumberStyles.Any, _usCulture, out val)) + { + bitRate = (int)Math.Ceiling(val * scale.Value); + } + } + } } if (framerate.HasValue || percent.HasValue) { - transcodingJob.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded); + state.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded, bitRate); } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index ef68fb90a..b6e695f1b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -242,7 +242,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private void OnTranscodeBeginning(EncodingJob job) { - job.ReportTranscodingProgress(null, null, null, null); + job.ReportTranscodingProgress(null, null, null, null, null); } private void OnTranscodeFailedToStart(string path, EncodingJob job) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index 449c31ace..883709de8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -377,7 +377,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return count; } - public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded) + public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) { var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; @@ -385,8 +385,8 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue) { - var pct = ticks.Value/RunTimeTicks.Value; - percentComplete = pct*100; + var pct = ticks.Value / RunTimeTicks.Value; + percentComplete = pct * 100; } if (percentComplete.HasValue) diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 63e789a59..ee19c4b66 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -55,7 +55,6 @@ - diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 1c7aa81e5..36df67b34 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -324,7 +324,7 @@ namespace MediaBrowser.Model.Dlna if (directPlayProfile != null) { // While options takes the network and other factors into account. Only applies to direct stream - if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true)) && options.EnableDirectStream) + if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true), PlayMethod.DirectStream) && options.EnableDirectStream) { playMethods.Add(PlayMethod.DirectStream); } @@ -332,7 +332,7 @@ namespace MediaBrowser.Model.Dlna // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play if (item.SupportsDirectPlay && - IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true)) && options.EnableDirectPlay) + IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true), PlayMethod.DirectPlay) && options.EnableDirectPlay) { playMethods.Add(PlayMethod.DirectPlay); } @@ -905,7 +905,7 @@ namespace MediaBrowser.Model.Dlna } } - return IsAudioEligibleForDirectPlay(item, maxBitrate); + return IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod); } public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer) @@ -1035,7 +1035,7 @@ namespace MediaBrowser.Model.Dlna return null; } - private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate) + private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate, PlayMethod playMethod) { // Don't restrict by bitrate if coming from an external domain if (item.IsRemote) @@ -1045,19 +1045,19 @@ namespace MediaBrowser.Model.Dlna if (!maxBitrate.HasValue) { - _logger.Info("Cannot direct play due to unknown supported bitrate"); + _logger.Info("Cannot "+ playMethod + " due to unknown supported bitrate"); return false; } if (!item.Bitrate.HasValue) { - _logger.Info("Cannot direct play due to unknown content bitrate"); + _logger.Info("Cannot " + playMethod + " due to unknown content bitrate"); return false; } if (item.Bitrate.Value > maxBitrate.Value) { - _logger.Info("Bitrate exceeds DirectPlay limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture)); + _logger.Info("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture)); return false; } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 75c4b46b2..7c797133f 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -172,18 +172,11 @@ namespace MediaBrowser.Providers.Manager try { - var currentFile = _fileSystem.GetFileInfo(currentPath); - - // This will fail if the file is hidden - if (currentFile.Exists) - { - if (currentFile.IsHidden) - { - _fileSystem.SetHidden(currentFile.FullName, false); - } - - _fileSystem.DeleteFile(currentFile.FullName); - } + _fileSystem.DeleteFile(currentPath); + } + catch (FileNotFoundException) + { + } finally { diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 9dff243c1..31cdc164c 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -373,20 +373,15 @@ namespace MediaBrowser.Providers.Manager continue; } - // Delete the source file - var currentFile = _fileSystem.GetFileInfo(image.Path); - - // Deletion will fail if the file is hidden so remove the attribute first - if (currentFile.Exists) + try { - if (currentFile.IsHidden) - { - _fileSystem.SetHidden(currentFile.FullName, false); - } - - _fileSystem.DeleteFile(currentFile.FullName); + _fileSystem.DeleteFile(image.Path); deleted = true; } + catch (FileNotFoundException) + { + + } } foreach (var image in deletedImages) From aa8f3cd493509ce4d6af836d3f231e7437a8ae49 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 24 Mar 2017 11:04:12 -0400 Subject: [PATCH 44/67] 3.2.8.11 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 5b64033eb..b14f3b7e6 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.10")] +[assembly: AssemblyVersion("3.2.8.11")] From 0cffe00aae80342d050a40848050f7cae3af8f61 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 25 Mar 2017 18:08:20 -0400 Subject: [PATCH 45/67] handle -1 content length --- .../Playback/Progressive/BaseProgressiveStreamingService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 441fff849..646a91c27 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -295,7 +295,8 @@ namespace MediaBrowser.Api.Playback.Progressive responseHeaders["Accept-Ranges"] = "none"; } - if (response.ContentLength.HasValue) + // Seeing cases of -1 here + if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) { responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); } From b2f7352094cd2b844c200605372b22507f9134ce Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 25 Mar 2017 18:08:43 -0400 Subject: [PATCH 46/67] improve m3u parsing --- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 6 ++++++ Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3bd2dd1bf..2d9ea5853 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -442,6 +442,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false); + foreach (var channel in result) + { + _logger.Info("Found epg channel in {0} {1} {2} {3}", provider.Name, info.ListingsId, channel.Name, channel.Id); + } + _epgChannels.AddOrUpdate(info.Id, result, (k, v) => result); } @@ -1018,6 +1023,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (epgChannel == null) { + _logger.Debug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty); programs = new List(); } else diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 8c4b9bf60..fe677f6fa 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -144,6 +144,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.TunerChannelId = string.IsNullOrWhiteSpace(tvgId) ? channelId : tvgId; + if (!string.IsNullOrWhiteSpace(channel.TunerChannelId) && channel.TunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1) + { + channel.TunerChannelId = channel.TunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I'); + } + var channelIdValues = new List(); if (!string.IsNullOrWhiteSpace(channelId)) { From 68c8792946665c12ecbdb5088941ba25acfa4369 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 25 Mar 2017 18:09:00 -0400 Subject: [PATCH 47/67] 3.2.8.12 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index b14f3b7e6..4bb1f4610 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.11")] +[assembly: AssemblyVersion("3.2.8.12")] From 6e696d0b6348c816dee94ffeab598236f509eb36 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 25 Mar 2017 19:13:44 -0400 Subject: [PATCH 48/67] update mac release --- .../Emby.Server.Mac.csproj | 56 +++++++++---------- MediaBrowser.Server.Mac/MacAppHost.cs | 4 -- .../Native/MonoFileSystem.cs | 5 +- 3 files changed, 28 insertions(+), 37 deletions(-) diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj index 4d9f3e7c4..bb67a9e36 100644 --- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj +++ b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj @@ -386,14 +386,8 @@ Resources\dashboard-ui\livetvstatus.html - - Resources\dashboard-ui\livetvtunerprovider-hdhomerun.html - - - Resources\dashboard-ui\livetvtunerprovider-m3u.html - - - Resources\dashboard-ui\livetvtunerprovider-satip.html + + Resources\dashboard-ui\livetvtuner.html Resources\dashboard-ui\log.html @@ -560,12 +554,6 @@ Resources\dashboard-ui\wizardlibrary.html - - Resources\dashboard-ui\wizardlivetvguide.html - - - Resources\dashboard-ui\wizardlivetvtuner.html - Resources\dashboard-ui\wizardsettings.html @@ -800,6 +788,9 @@ Resources\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js + + Resources\dashboard-ui\bower_components\emby-webcomponents\staticbackdrops.js + Resources\dashboard-ui\bower_components\emby-webcomponents\thememediaplayer.js @@ -1253,6 +1244,9 @@ Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\empty.png + + Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingbutton.js + Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css @@ -1736,9 +1730,15 @@ Resources\dashboard-ui\components\iap.js + + Resources\dashboard-ui\components\maintabsmanager.js + Resources\dashboard-ui\components\remotecontrol.js + + Resources\dashboard-ui\components\tunerpicker.js + Resources\dashboard-ui\components\viewcontainer-lite.js @@ -2141,8 +2141,8 @@ Resources\dashboard-ui\dashboard\librarysettings.js - - Resources\dashboard-ui\dashboard\livetvtunerprovider-satip.js + + Resources\dashboard-ui\dashboard\livetvtuner.js Resources\dashboard-ui\dashboard\logpage.js @@ -2171,6 +2171,12 @@ Resources\dashboard-ui\legacy\selectmenu.js + + Resources\dashboard-ui\offline\offline.html + + + Resources\dashboard-ui\offline\offline.js + Resources\dashboard-ui\scripts\addpluginpage.js @@ -2189,9 +2195,6 @@ Resources\dashboard-ui\scripts\channels.js - - Resources\dashboard-ui\scripts\channelslatest.js - Resources\dashboard-ui\scripts\connectlogin.js @@ -2306,12 +2309,6 @@ Resources\dashboard-ui\scripts\livetvsuggested.js - - Resources\dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js - - - Resources\dashboard-ui\scripts\livetvtunerprovider-m3u.js - Resources\dashboard-ui\scripts\localsync.js @@ -2516,12 +2513,6 @@ Resources\dashboard-ui\scripts\wizardcontroller.js - - Resources\dashboard-ui\scripts\wizardlivetvguide.js - - - Resources\dashboard-ui\scripts\wizardlivetvtuner.js - Resources\dashboard-ui\scripts\wizardsettings.js @@ -2576,6 +2567,9 @@ Resources\dashboard-ui\strings\es.json + + Resources\dashboard-ui\strings\fa.json + Resources\dashboard-ui\strings\fi.json diff --git a/MediaBrowser.Server.Mac/MacAppHost.cs b/MediaBrowser.Server.Mac/MacAppHost.cs index f84e96126..304472529 100644 --- a/MediaBrowser.Server.Mac/MacAppHost.cs +++ b/MediaBrowser.Server.Mac/MacAppHost.cs @@ -87,10 +87,6 @@ namespace MediaBrowser.Server.Mac throw new NotImplementedException(); } - protected override void EnableLoopbackInternal(string appName) - { - } - public override bool SupportsRunningAsService { get diff --git a/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs b/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs index daf2b90e6..7aeff5ac8 100644 --- a/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs +++ b/MediaBrowser.Server.Mac/Native/MonoFileSystem.cs @@ -1,13 +1,14 @@ using Emby.Common.Implementations.IO; using MediaBrowser.Model.Logging; using Mono.Unix.Native; +using MediaBrowser.Model.System; namespace Emby.Server.Mac.Native { public class MonoFileSystem : ManagedFileSystem { - public MonoFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, string tempPath) - : base(logger, supportsAsyncFileStreams, enableManagedInvalidFileNameChars, true, tempPath) + public MonoFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath) + : base(logger, environmentInfo, tempPath) { } From abc54d1ea336f1d836c2a1664ea12995b0ea3562 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 00:20:00 -0400 Subject: [PATCH 49/67] determine interlaced from ffprobe data --- .../Encoder/MediaEncoder.cs | 162 +----------------- .../Probing/InternalMediaInfoResult.cs | 2 + .../Probing/ProbeResultNormalizer.cs | 5 + 3 files changed, 10 insertions(+), 159 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 580f5c615..97f2c57eb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -647,9 +647,9 @@ namespace MediaBrowser.MediaEncoding.Encoder var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - if (videoStream != null) + if (videoStream != null && !videoStream.IsInterlaced) { - var isInterlaced = await DetectInterlaced(mediaInfo, videoStream, inputPath, probeSizeArgument).ConfigureAwait(false); + var isInterlaced = DetectInterlaced(mediaInfo, videoStream); if (isInterlaced) { @@ -672,7 +672,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - private async Task DetectInterlaced(MediaSourceInfo video, MediaStream videoStream, string inputPath, string probeSizeArgument) + private bool DetectInterlaced(MediaSourceInfo video, MediaStream videoStream) { var formats = (video.Container ?? string.Empty).Split(',').ToList(); var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) || @@ -698,165 +698,9 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - if (video.Protocol != MediaProtocol.File) - { - return false; - } - - var args = "{0} -i {1} -map 0:v:{2} -an -filter:v idet -frames:v 500 -an -f null /dev/null"; - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both or ffmpeg may hang due to deadlocks. See comments below. - RedirectStandardError = true, - FileName = FFMpegPath, - Arguments = string.Format(args, probeSizeArgument, inputPath, videoStream.Index.ToString(CultureInfo.InvariantCulture)).Trim(), - - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - }); - - _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - var idetFoundInterlaced = false; - - using (var processWrapper = new ProcessWrapper(process, this, _logger)) - { - try - { - StartProcess(processWrapper); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting ffprobe", ex); - - throw; - } - - try - { - //process.BeginOutputReadLine(); - - using (var reader = new StreamReader(process.StandardError.BaseStream)) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - - if (line.StartsWith("[Parsed_idet", StringComparison.OrdinalIgnoreCase)) - { - var idetResult = AnalyzeIdetResult(line); - - if (idetResult.HasValue) - { - if (!idetResult.Value) - { - return false; - } - - idetFoundInterlaced = true; - } - } - } - } - - } - catch - { - StopProcess(processWrapper, 100); - - throw; - } - } - - return idetFoundInterlaced; - } - - private bool? AnalyzeIdetResult(string line) - { - // As you can see, the filter only guessed one frame as progressive. - // Results like this are pretty typical. So if less than 30% of the detections are in the "Undetermined" category, then I only consider the video to be interlaced if at least 65% of the identified frames are in either the TFF or BFF category. - // In this case (310 + 311)/(622) = 99.8% which is well over the 65% metric. I may refine that number with more testing but I honestly do not believe I will need to. - // http://awel.domblogger.net/videoTranscode/interlace.html - var index = line.IndexOf("detection:", StringComparison.OrdinalIgnoreCase); - - if (index == -1) - { - return null; - } - - line = line.Substring(index).Trim(); - var parts = line.Split(' ').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => i.Trim()).ToList(); - - if (parts.Count < 2) - { - return null; - } - double tff = 0; - double bff = 0; - double progressive = 0; - double undetermined = 0; - double total = 0; - - for (var i = 0; i < parts.Count - 1; i++) - { - var part = parts[i]; - - if (string.Equals(part, "tff:", StringComparison.OrdinalIgnoreCase)) - { - tff = GetNextPart(parts, i); - total += tff; - } - else if (string.Equals(part, "bff:", StringComparison.OrdinalIgnoreCase)) - { - bff = GetNextPart(parts, i); - total += tff; - } - else if (string.Equals(part, "progressive:", StringComparison.OrdinalIgnoreCase)) - { - progressive = GetNextPart(parts, i); - total += progressive; - } - else if (string.Equals(part, "undetermined:", StringComparison.OrdinalIgnoreCase)) - { - undetermined = GetNextPart(parts, i); - total += undetermined; - } - } - - if (total == 0) - { - return null; - } - - if ((undetermined / total) >= .3) - { - return false; - } - - if (((tff + bff) / total) >= .4) - { - return true; - } - return false; } - private int GetNextPart(List parts, int index) - { - var next = parts[index + 1]; - - int value; - if (int.TryParse(next, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) - { - return value; - } - return 0; - } - /// /// The us culture /// diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs index f32dd178f..eef273250 100644 --- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs +++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs @@ -264,6 +264,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// The loro_surmixlev. public string loro_surmixlev { get; set; } + public string field_order { get; set; } + /// /// Gets or sets the disposition. /// diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 7927ddb6a..8b20dca1b 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -508,6 +508,11 @@ namespace MediaBrowser.MediaEncoding.Probing stream.IsAVC = false; } + if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase)) + { + stream.IsInterlaced = true; + } + // Filter out junk if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) { From 6c160ff17c2e9fb37c4c1ea8b26d732927c5cca1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 00:20:28 -0400 Subject: [PATCH 50/67] switch default recording format to mkv --- MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 6a0fdede3..91ea977f7 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Model.LiveTv TunerHosts = new List(); ListingProviders = new List(); MediaLocationsCreated = new string[] { }; - RecordingEncodingFormat = "mp4"; + RecordingEncodingFormat = "mkv"; RecordingPostProcessorArguments = "\"{path}\""; EnableRecordingEncoding = true; } From ce0547abde8a245bd2e107156be2549582fbe5bc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 00:20:50 -0400 Subject: [PATCH 51/67] throw exceptions on bad input --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index eb69a8f7b..5eaab4110 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1583,6 +1583,15 @@ namespace MediaBrowser.Controller.MediaEncoding MediaSourceInfo mediaSource, string requestedUrl) { + if (state == null) + { + throw new ArgumentNullException("state"); + } + if (mediaSource == null) + { + throw new ArgumentNullException("mediaSource"); + } + state.MediaPath = mediaSource.Path; state.InputProtocol = mediaSource.Protocol; state.InputContainer = mediaSource.Container; From e6178a65f949cfdcbe6e77c945250343506f80cc Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 00:21:32 -0400 Subject: [PATCH 52/67] rework tuner channel id --- .../LiveTv/EmbyTV/EmbyTV.cs | 37 +++++++++++++------ .../LiveTv/EmbyTV/TimerManager.cs | 5 +++ .../LiveTv/LiveTvManager.cs | 35 ++++++++++-------- .../LiveTv/TunerHosts/M3uParser.cs | 5 --- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 2d9ea5853..368053b9d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -498,11 +498,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId)) { - var mappedTunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings); + var tunerChannelId = tunerChannel.TunerChannelId; + if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1) + { + tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I'); + } + + var mappedTunerChannelId = GetMappedChannel(tunerChannelId, mappings); if (string.IsNullOrWhiteSpace(mappedTunerChannelId)) { - mappedTunerChannelId = tunerChannel.TunerChannelId; + mappedTunerChannelId = tunerChannelId; } var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase)); @@ -644,8 +650,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken) { - var existingTimer = _timerProvider.GetAll() - .FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); + var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ? + null : + _timerProvider.GetTimerByProgramId(timer.ProgramId); if (existingTimer != null) { @@ -724,10 +731,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return true; } - //if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId)) - //{ - // return true; - //} + if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId)) + { + return true; + } return false; }) @@ -740,7 +747,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV timer.SeriesTimerId = info.Id; timer.IsManual = true; - _timerProvider.AddOrUpdate(timer); + _timerProvider.AddOrUpdate(timer, false); } await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false); @@ -2340,6 +2347,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var existingTimer = _timerProvider.GetTimer(timer.Id); + if (existingTimer == null) + { + existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) + ? null + : _timerProvider.GetTimerByProgramId(timer.ProgramId); + } + if (existingTimer == null) { if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) @@ -2354,9 +2368,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } else { - // Only update if not currently active + // Only update if not currently active - test both new timer and existing in case Id's are different + // Id's could be different if the timer was created manually prior to series timer creation ActiveRecordingInfo activeRecordingInfo; - if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo)) + if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo) && !_activeRecordings.TryGetValue(existingTimer.Id, out activeRecordingInfo)) { UpdateExistingTimerWithNewMetadata(existingTimer, timer); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 35868d318..2eec3df8a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -166,5 +166,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase)); } + + public TimerInfo GetTimerByProgramId(string programId) + { + return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase)); + } } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 8406d44a7..b9e73b62e 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1078,25 +1078,28 @@ namespace Emby.Server.Implementations.LiveTv var channel = GetInternalChannel(program.ChannelId); - var channelUserdata = _userDataManager.GetUserData(userId, channel); + if (channel != null) + { + var channelUserdata = _userDataManager.GetUserData(userId, channel); - if (channelUserdata.Likes ?? false) - { - score += 2; - } - else if (!(channelUserdata.Likes ?? true)) - { - score -= 2; - } + if (channelUserdata.Likes ?? false) + { + score += 2; + } + else if (!(channelUserdata.Likes ?? true)) + { + score -= 2; + } - if (channelUserdata.IsFavorite) - { - score += 3; - } + if (channelUserdata.IsFavorite) + { + score += 3; + } - if (factorChannelWatchCount) - { - score += channelUserdata.PlayCount; + if (factorChannelWatchCount) + { + score += channelUserdata.PlayCount; + } } return score; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index fe677f6fa..8c4b9bf60 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -144,11 +144,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts channel.TunerChannelId = string.IsNullOrWhiteSpace(tvgId) ? channelId : tvgId; - if (!string.IsNullOrWhiteSpace(channel.TunerChannelId) && channel.TunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1) - { - channel.TunerChannelId = channel.TunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I'); - } - var channelIdValues = new List(); if (!string.IsNullOrWhiteSpace(channelId)) { From 690ab4a2dc5f0e26e2133d42c78838e8fd1b7e97 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 00:22:30 -0400 Subject: [PATCH 53/67] update recording mp4 args --- .../LiveTv/EmbyTV/EncodedRecorder.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 0567bdfd9..6cc5b6920 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); var inputModifiers = "-fflags +genpts -async 1 -vsync -1"; - var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4} -y \"{1}\""; + var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4}{6} -y \"{1}\""; long startTimeTicks = 0; //if (mediaSource.DateLiveStreamOpened.HasValue) @@ -193,7 +193,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn"; - commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam); + var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ? + " -f mp4 -movflags frag_keyframe+empty_moov" : + string.Empty; + + commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam, outputParam); return inputModifiers + " " + commandLineArgs; } From 55bfb71baa3aa683b092c0432cbb5c7380a28395 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 12:26:52 -0400 Subject: [PATCH 54/67] update hdhomerun udp stream --- Emby.Common.Implementations/Net/UdpSocket.cs | 55 ++++++-- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 120 +++------------- .../HdHomerun/HdHomerunUdpStream.cs | 129 +----------------- .../LiveTv/TunerHosts/MulticastStream.cs | 48 ++++++- .../LiveTv/TunerHosts/QueueStream.cs | 20 +-- 5 files changed, 122 insertions(+), 250 deletions(-) diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index b85245ba1..45cb42ecd 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -16,12 +16,15 @@ namespace Emby.Common.Implementations.Net internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket { - - #region Fields - private Socket _Socket; private int _LocalPort; - #endregion + + private SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() + { + SocketFlags = SocketFlags.None + }; + + private TaskCompletionSource _currentReceiveTaskCompletionSource; public UdpSocket(Socket socket, int localPort, IPAddress ip) { @@ -32,6 +35,32 @@ namespace Emby.Common.Implementations.Net LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); _Socket.Bind(new IPEndPoint(ip, _LocalPort)); + + InitReceiveSocketAsyncEventArgs(); + } + + private void InitReceiveSocketAsyncEventArgs() + { + var buffer = new byte[8192]; + _receiveSocketAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length); + _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; + } + + private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) + { + var tcs = _currentReceiveTaskCompletionSource; + if (tcs != null) + { + _currentReceiveTaskCompletionSource = null; + + tcs.TrySetResult(new SocketReceiveResult + { + Buffer = e.Buffer, + ReceivedBytes = e.BytesTransferred, + RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint), + LocalIPAddress = LocalIPAddress + }); + } } public UdpSocket(Socket socket, IpEndPointInfo endPoint) @@ -40,6 +69,8 @@ namespace Emby.Common.Implementations.Net _Socket = socket; _Socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); + + InitReceiveSocketAsyncEventArgs(); } public IpAddressInfo LocalIPAddress @@ -57,12 +88,12 @@ namespace Emby.Common.Implementations.Net var tcs = new TaskCompletionSource(); EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); - var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); - state.TaskCompletionSource = tcs; - cancellationToken.Register(() => tcs.TrySetCanceled()); #if NETSTANDARD1_6 + var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); + state.TaskCompletionSource = tcs; + _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer), SocketFlags.None, state.RemoteEndPoint) .ContinueWith((task, asyncState) => { @@ -74,7 +105,15 @@ namespace Emby.Common.Implementations.Net } }, state); #else - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); + //var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); + //state.TaskCompletionSource = tcs; + + //_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); + + _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint; + _currentReceiveTaskCompletionSource = tcs; + + var isPending = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs); #endif return tcs.Task; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 02a6bf85d..17c57712e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -275,6 +275,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public int HD { get; set; } } + protected EncodingOptions GetEncodingOptions() + { + return Config.GetConfiguration("encoding"); + } + + private string GetHdHrIdFromChannelId(string channelId) + { + return channelId.Split('_')[1]; + } + private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, ChannelInfo channelInfo, string profile) { int? width = null; @@ -362,14 +372,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun nal = "0"; } - var url = GetApiUrl(info, true) + "/auto/v" + channelId; - - // If raw was used, the tuner doesn't support params - if (!string.IsNullOrWhiteSpace(profile) - && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) - { - url += "?transcode=" + profile; - } + var url = GetApiUrl(info, false); var id = profile; if (string.IsNullOrWhiteSpace(id)) @@ -378,92 +381,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } id += "_" + url.GetMD5().ToString("N"); - var mediaSource = new MediaSourceInfo - { - Path = url, - Protocol = MediaProtocol.Http, - MediaStreams = new List - { - new MediaStream - { - Type = MediaStreamType.Video, - // Set the index to -1 because we don't know the exact index of the video stream within the container - Index = -1, - IsInterlaced = isInterlaced, - Codec = videoCodec, - Width = width, - Height = height, - BitRate = videoBitrate, - NalLengthSize = nal - - }, - new MediaStream - { - Type = MediaStreamType.Audio, - // Set the index to -1 because we don't know the exact index of the audio stream within the container - Index = -1, - Codec = audioCodec, - BitRate = audioBitrate - } - }, - RequiresOpening = true, - RequiresClosing = false, - BufferMs = 0, - Container = "ts", - Id = id, - SupportsDirectPlay = false, - SupportsDirectStream = true, - SupportsTranscoding = true, - IsInfiniteStream = true - }; - - mediaSource.InferTotalBitrate(); - - return mediaSource; - } - - protected EncodingOptions GetEncodingOptions() - { - return Config.GetConfiguration("encoding"); - } - - private string GetHdHrIdFromChannelId(string channelId) - { - return channelId.Split('_')[1]; - } - - private MediaSourceInfo GetLegacyMediaSource(TunerHostInfo info, string channelId, ChannelInfo channel) - { - int? width = null; - int? height = null; - bool isInterlaced = true; - string videoCodec = null; - string audioCodec = null; - - int? videoBitrate = null; - int? audioBitrate = null; - - if (channel != null) - { - if (string.IsNullOrWhiteSpace(videoCodec)) - { - videoCodec = channel.VideoCodec; - } - audioCodec = channel.AudioCodec; - } - - // normalize - if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase)) - { - videoCodec = "mpeg2video"; - } - - string nal = null; - - var url = GetApiUrl(info, false); - var id = channelId; - id += "_" + url.GetMD5().ToString("N"); - var mediaSource = new MediaSourceInfo { Path = url, @@ -527,7 +444,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun if (isLegacyTuner) { - list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo)); + list.Add(GetMediaSource(info, hdhrId, channelInfo, "native")); } else { @@ -579,20 +496,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var hdhomerunChannel = channelInfo as HdHomerunChannelInfo; + var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile); + var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); + if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner) { - var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo); - var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); } else { - var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile); - //var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); - - return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); - //return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); + //return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 867e07e87..92000a1b3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -120,8 +120,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // send url to start streaming await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false); - var timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token; - var response = await udpClient.ReceiveAsync(timeoutToken).ConfigureAwait(false); _logger.Info("Opened HDHR UDP stream from {0}", remoteAddress); if (!cancellationToken.IsCancellationRequested) @@ -132,8 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun onStarted = () => openTaskCompletionSource.TrySetResult(true); } - var stream = new UdpClientStream(udpClient); - await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false); + await _multicastStream.CopyUntilCancelled(udpClient, onStarted, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException ex) @@ -158,7 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } await hdHomerunManager.StopStreaming().ConfigureAwait(false); - udpClient.Dispose(); _liveStreamTaskCompletionSource.TrySetResult(true); } } @@ -171,127 +167,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return _multicastStream.CopyToAsync(stream); } } - - // This handles the ReadAsync function only of a Stream object - // This is used to wrap a UDP socket into a stream for MulticastStream which only uses ReadAsync - public class UdpClientStream : Stream - { - private static int RtpHeaderBytes = 12; - private static int PacketSize = 1316; - private readonly ISocket _udpClient; - bool disposed; - - public UdpClientStream(ISocket udpClient) : base() - { - _udpClient = udpClient; - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - if (buffer == null) - throw new ArgumentNullException("buffer"); - - if (offset + count < 0) - throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count"); - - if (offset + count > buffer.Length) - throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count"); - - if (disposed) - throw new ObjectDisposedException(typeof(UdpClientStream).ToString()); - - // This will always receive a 1328 packet size (PacketSize + RtpHeaderSize) - // The RTP header will be stripped so see how many reads we need to make to fill the buffer. - int numReads = count / PacketSize; - int totalBytesRead = 0; - - for (int i = 0; i < numReads; ++i) - { - var data = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); - - var bytesRead = data.ReceivedBytes - RtpHeaderBytes; - - // remove rtp header - Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead); - offset += bytesRead; - totalBytesRead += bytesRead; - } - return totalBytesRead; - } - - protected override void Dispose(bool disposing) - { - disposed = true; - } - - public override bool CanRead - { - get - { - throw new NotImplementedException(); - } - } - - public override bool CanSeek - { - get - { - throw new NotImplementedException(); - } - } - - public override bool CanWrite - { - get - { - throw new NotImplementedException(); - } - } - - public override long Length - { - get - { - throw new NotImplementedException(); - } - } - - public override long Position - { - get - { - throw new NotImplementedException(); - } - - set - { - throw new NotImplementedException(); - } - } - - public override void Flush() - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - } } \ No newline at end of file diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs index 90ff36441..e3d0d1eba 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; namespace Emby.Server.Implementations.LiveTv.TunerHosts { @@ -40,7 +41,52 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var allStreams = _outputStreams.ToList(); foreach (var stream in allStreams) { - stream.Value.Queue(copy); + stream.Value.Queue(copy, 0, copy.Length); + } + + if (onStarted != null) + { + var onStartedCopy = onStarted; + onStarted = null; + Task.Run(onStartedCopy); + } + } + + else + { + await Task.Delay(100).ConfigureAwait(false); + } + } + } + + private static int RtpHeaderBytes = 12; + public async Task CopyUntilCancelled(ISocket udpClient, Action onStarted, CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + + while (!cancellationToken.IsCancellationRequested) + { + var receiveToken = cancellationToken; + + // On the first connection attempt, put a timeout to avoid being stuck indefinitely in the event of failure + if (onStarted != null) + { + receiveToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token; + } + + var data = await udpClient.ReceiveAsync(receiveToken).ConfigureAwait(false); + var bytesRead = data.ReceivedBytes - RtpHeaderBytes; + + if (bytesRead > 0) + { + byte[] copy = new byte[bytesRead]; + Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead); + + var allStreams = _outputStreams.ToList(); + foreach (var stream in allStreams) + { + //stream.Value.Queue(data.Buffer, RtpHeaderBytes, bytesRead); + stream.Value.Queue(copy, 0, copy.Length); } if (onStarted != null) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs index 7b48ce21a..27dd288a7 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public class QueueStream { private readonly Stream _outputStream; - private readonly ConcurrentQueue _queue = new ConcurrentQueue(); + private readonly ConcurrentQueue> _queue = new ConcurrentQueue>(); private CancellationToken _cancellationToken; public TaskCompletionSource TaskCompletion { get; private set; } @@ -28,9 +28,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts TaskCompletion = new TaskCompletionSource(); } - public void Queue(byte[] bytes) + public void Queue(byte[] bytes, int offset, int count) { - _queue.Enqueue(bytes); + _queue.Enqueue(new Tuple(bytes, offset, count)); } public void Start(CancellationToken cancellationToken) @@ -39,12 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Task.Run(() => StartInternal()); } - private byte[] Dequeue() + private Tuple Dequeue() { - byte[] bytes; - if (_queue.TryDequeue(out bytes)) + Tuple result; + if (_queue.TryDequeue(out result)) { - return bytes; + return result; } return null; @@ -58,10 +58,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { while (true) { - var bytes = Dequeue(); - if (bytes != null) + var result = Dequeue(); + if (result != null) { - await _outputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false); + await _outputStream.WriteAsync(result.Item1, result.Item2, result.Item3, cancellationToken).ConfigureAwait(false); } else { From 240000f965f5cb7df1c8c46245c5a93a9007c8f8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 12:27:14 -0400 Subject: [PATCH 55/67] 3.2.8.13 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 4bb1f4610..33259a0bc 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.12")] +[assembly: AssemblyVersion("3.2.8.13")] From caaa906604c759e6899ebf6be6f5ac4f9845db84 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 15:00:35 -0400 Subject: [PATCH 56/67] update socket methods --- Emby.Common.Implementations/Net/UdpSocket.cs | 256 +++++++------------ Emby.Server.Core/IO/LibraryMonitor.cs | 11 - Emby.Server.Implementations/Udp/UdpServer.cs | 26 +- MediaBrowser.Model/Net/ISocket.cs | 1 + RSSDP/SsdpCommunicationsServer.cs | 10 +- 5 files changed, 107 insertions(+), 197 deletions(-) diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 45cb42ecd..94d073bd2 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -19,12 +19,20 @@ namespace Emby.Common.Implementations.Net private Socket _Socket; private int _LocalPort; - private SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() + private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() + { + SocketFlags = SocketFlags.None + }; + + private readonly SocketAsyncEventArgs _sendSocketAsyncEventArgs = new SocketAsyncEventArgs() { SocketFlags = SocketFlags.None }; private TaskCompletionSource _currentReceiveTaskCompletionSource; + private TaskCompletionSource _currentSendTaskCompletionSource; + + private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1); public UdpSocket(Socket socket, int localPort, IPAddress ip) { @@ -41,9 +49,13 @@ namespace Emby.Common.Implementations.Net private void InitReceiveSocketAsyncEventArgs() { - var buffer = new byte[8192]; - _receiveSocketAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length); + var receiveBuffer = new byte[8192]; + _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; + + var sendBuffer = new byte[8192]; + _sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length); + _sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed; } private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) @@ -53,13 +65,38 @@ namespace Emby.Common.Implementations.Net { _currentReceiveTaskCompletionSource = null; - tcs.TrySetResult(new SocketReceiveResult + if (e.SocketError == SocketError.Success) { - Buffer = e.Buffer, - ReceivedBytes = e.BytesTransferred, - RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint), - LocalIPAddress = LocalIPAddress - }); + tcs.TrySetResult(new SocketReceiveResult + { + Buffer = e.Buffer, + ReceivedBytes = e.BytesTransferred, + RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint), + LocalIPAddress = LocalIPAddress + }); + } + else + { + tcs.TrySetException(new Exception("SocketError: " + e.SocketError)); + } + } + } + + private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) + { + var tcs = _currentSendTaskCompletionSource; + if (tcs != null) + { + _currentSendTaskCompletionSource = null; + + if (e.SocketError == SocketError.Success) + { + tcs.TrySetResult(e.BytesTransferred); + } + else + { + tcs.TrySetException(new Exception("SocketError: " + e.SocketError)); + } } } @@ -79,8 +116,6 @@ namespace Emby.Common.Implementations.Net private set; } - #region ISocket Members - public Task ReceiveAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); @@ -90,31 +125,15 @@ namespace Emby.Common.Implementations.Net EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); cancellationToken.Register(() => tcs.TrySetCanceled()); -#if NETSTANDARD1_6 - var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); - state.TaskCompletionSource = tcs; - - _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer), SocketFlags.None, state.RemoteEndPoint) - .ContinueWith((task, asyncState) => - { - if (task.Status != TaskStatus.Faulted) - { - var receiveState = asyncState as AsyncReceiveState; - receiveState.RemoteEndPoint = task.Result.RemoteEndPoint; - ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress); - } - }, state); -#else - //var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); - //state.TaskCompletionSource = tcs; - - //_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); - _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint; _currentReceiveTaskCompletionSource = tcs; - var isPending = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs); -#endif + var willRaiseEvent = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs); + + if (!willRaiseEvent) + { + _receiveSocketAsyncEventArgs_Completed(this, _receiveSocketAsyncEventArgs); + } return tcs.Task; } @@ -126,60 +145,42 @@ namespace Emby.Common.Implementations.Net if (buffer == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); - var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); - -#if NETSTANDARD1_6 - - if (size != buffer.Length) - { - byte[] copy = new byte[size]; - Buffer.BlockCopy(buffer, 0, copy, 0, size); - buffer = copy; - } - cancellationToken.ThrowIfCancellationRequested(); - _Socket.SendTo(buffer, ipEndPoint); - return Task.FromResult(true); -#else - var taskSource = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); + + cancellationToken.Register(() => tcs.TrySetCanceled()); + + _sendSocketAsyncEventArgs.SetBuffer(buffer, 0, size); + _sendSocketAsyncEventArgs.RemoteEndPoint = NetworkManager.ToIPEndPoint(endPoint); + _currentSendTaskCompletionSource = tcs; + + var willRaiseEvent = _Socket.SendAsync(_sendSocketAsyncEventArgs); + + if (!willRaiseEvent) + { + _sendSocketAsyncEventArgs_Completed(this, _sendSocketAsyncEventArgs); + } + + return tcs.Task; + } + + public async Task SendWithLockAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + + await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { - _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result => - { - if (cancellationToken.IsCancellationRequested) - { - taskSource.TrySetCanceled(); - return; - } - try - { - _Socket.EndSend(result); - taskSource.TrySetResult(true); - } - catch (Exception ex) - { - taskSource.TrySetException(ex); - } - - }, null); + await SendAsync(buffer, size, endPoint, cancellationToken).ConfigureAwait(false); } - catch (Exception ex) + finally { - taskSource.TrySetException(ex); + _sendLock.Release(); } - - //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port)); - - return taskSource.Task; -#endif } - #endregion - - #region Overrides - protected override void Dispose(bool disposing) { if (disposing) @@ -187,44 +188,19 @@ namespace Emby.Common.Implementations.Net var socket = _Socket; if (socket != null) socket.Dispose(); - } - } - #endregion + _sendLock.Dispose(); - #region Private Methods - - private static void ProcessResponse(AsyncReceiveState state, Func receiveData, IpAddressInfo localIpAddress) - { - try - { - var bytesRead = receiveData(); - - var ipEndPoint = state.RemoteEndPoint as IPEndPoint; - state.TaskCompletionSource.TrySetResult( - new SocketReceiveResult - { - Buffer = state.Buffer, - ReceivedBytes = bytesRead, - RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), - LocalIPAddress = localIpAddress - } - ); - } - catch (ObjectDisposedException) - { - state.TaskCompletionSource.TrySetCanceled(); - } - catch (SocketException se) - { - if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown) - state.TaskCompletionSource.TrySetException(se); - else - state.TaskCompletionSource.TrySetCanceled(); - } - catch (Exception ex) - { - state.TaskCompletionSource.TrySetException(ex); + var tcs = _currentReceiveTaskCompletionSource; + if (tcs != null) + { + tcs.TrySetCanceled(); + } + var sendTcs = _currentSendTaskCompletionSource; + if (sendTcs != null) + { + sendTcs.TrySetCanceled(); + } } } @@ -237,59 +213,5 @@ namespace Emby.Common.Implementations.Net return NetworkManager.ToIpEndPointInfo(endpoint); } - - private void ProcessResponse(IAsyncResult asyncResult) - { -#if NET46 - var state = asyncResult.AsyncState as AsyncReceiveState; - try - { - var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint); - - var ipEndPoint = state.RemoteEndPoint as IPEndPoint; - state.TaskCompletionSource.TrySetResult( - new SocketReceiveResult - { - Buffer = state.Buffer, - ReceivedBytes = bytesRead, - RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), - LocalIPAddress = LocalIPAddress - } - ); - } - catch (ObjectDisposedException) - { - state.TaskCompletionSource.TrySetCanceled(); - } - catch (Exception ex) - { - state.TaskCompletionSource.TrySetException(ex); - } -#endif - } - - #endregion - - #region Private Classes - - private class AsyncReceiveState - { - public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint) - { - this.Socket = socket; - this.RemoteEndPoint = remoteEndPoint; - } - - public EndPoint RemoteEndPoint; - public byte[] Buffer = new byte[8192]; - - public Socket Socket { get; private set; } - - public TaskCompletionSource TaskCompletionSource { get; set; } - - } - - #endregion - } } diff --git a/Emby.Server.Core/IO/LibraryMonitor.cs b/Emby.Server.Core/IO/LibraryMonitor.cs index 4df9b930e..e1e3186c3 100644 --- a/Emby.Server.Core/IO/LibraryMonitor.cs +++ b/Emby.Server.Core/IO/LibraryMonitor.cs @@ -421,17 +421,6 @@ namespace Emby.Server.Core.IO 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) diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index bb303d8fa..21ef3cab6 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -203,19 +203,6 @@ namespace Emby.Server.Implementations.Udp GC.SuppressFinalize(this); } - /// - /// Stops this instance. - /// - public void Stop() - { - _isDisposed = true; - - if (_udpClient != null) - { - _udpClient.Dispose(); - } - } - /// /// Releases unmanaged and - optionally - managed resources. /// @@ -224,7 +211,12 @@ namespace Emby.Server.Implementations.Udp { if (dispose) { - Stop(); + _isDisposed = true; + + if (_udpClient != null) + { + _udpClient.Dispose(); + } } } @@ -247,9 +239,13 @@ namespace Emby.Server.Implementations.Udp try { - await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false); + await _udpClient.SendWithLockAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false); _logger.Info("Udp message sent to {0}", remoteEndPoint); + } + catch (OperationCanceledException) + { + } catch (Exception ex) { diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs index 90070b128..61fc0e28b 100644 --- a/MediaBrowser.Model/Net/ISocket.cs +++ b/MediaBrowser.Model/Net/ISocket.cs @@ -24,5 +24,6 @@ namespace MediaBrowser.Model.Net /// Sends a UDP message to a particular end point (uni or multicast). /// Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken); + Task SendWithLockAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 99e3969aa..cc464e689 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -177,11 +177,15 @@ namespace Rssdp.Infrastructure { try { - await socket.SendAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false); + await socket.SendWithLockAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false); } catch (ObjectDisposedException) { + } + catch (OperationCanceledException) + { + } catch (Exception ex) { @@ -341,11 +345,9 @@ namespace Rssdp.Infrastructure foreach (var socket in sockets) { - await socket.SendAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false); + await SendFromSocket(socket, messageData, destination, cancellationToken).ConfigureAwait(false); } } - - ThrowIfDisposed(); } private ISocket ListenForBroadcastsAsync() From 07c43a1cd3979e67ac3547f3a76a7daab5cd4bc7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 15:05:47 -0400 Subject: [PATCH 57/67] 3.2.8.14 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 33259a0bc..f7f49dc75 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.13")] +[assembly: AssemblyVersion("3.2.8.14")] From 8a68c2383866c7e0b21dc164f1126bd78699c1fb Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 15:54:50 -0400 Subject: [PATCH 58/67] update socket send functions --- Emby.Common.Implementations/Net/UdpSocket.cs | 83 +++++++++++++++---- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 8 +- 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 94d073bd2..834f0a05c 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -145,31 +145,84 @@ namespace Emby.Common.Implementations.Net if (buffer == null) throw new ArgumentNullException("messageData"); if (endPoint == null) throw new ArgumentNullException("endPoint"); - cancellationToken.ThrowIfCancellationRequested(); + var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); - var tcs = new TaskCompletionSource(); +#if NETSTANDARD1_6 - cancellationToken.Register(() => tcs.TrySetCanceled()); - - _sendSocketAsyncEventArgs.SetBuffer(buffer, 0, size); - _sendSocketAsyncEventArgs.RemoteEndPoint = NetworkManager.ToIPEndPoint(endPoint); - _currentSendTaskCompletionSource = tcs; - - var willRaiseEvent = _Socket.SendAsync(_sendSocketAsyncEventArgs); - - if (!willRaiseEvent) + if (size != buffer.Length) { - _sendSocketAsyncEventArgs_Completed(this, _sendSocketAsyncEventArgs); + byte[] copy = new byte[size]; + Buffer.BlockCopy(buffer, 0, copy, 0, size); + buffer = copy; } - return tcs.Task; + cancellationToken.ThrowIfCancellationRequested(); + + _Socket.SendTo(buffer, ipEndPoint); + return Task.FromResult(true); +#else + var taskSource = new TaskCompletionSource(); + + try + { + _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result => + { + if (cancellationToken.IsCancellationRequested) + { + taskSource.TrySetCanceled(); + return; + } + try + { + _Socket.EndSend(result); + taskSource.TrySetResult(true); + } + catch (Exception ex) + { + taskSource.TrySetException(ex); + } + + }, null); + } + catch (Exception ex) + { + taskSource.TrySetException(ex); + } + + //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port)); + + return taskSource.Task; +#endif + //ThrowIfDisposed(); + + //if (buffer == null) throw new ArgumentNullException("messageData"); + //if (endPoint == null) throw new ArgumentNullException("endPoint"); + + //cancellationToken.ThrowIfCancellationRequested(); + + //var tcs = new TaskCompletionSource(); + + //cancellationToken.Register(() => tcs.TrySetCanceled()); + + //_sendSocketAsyncEventArgs.SetBuffer(buffer, 0, size); + //_sendSocketAsyncEventArgs.RemoteEndPoint = NetworkManager.ToIPEndPoint(endPoint); + //_currentSendTaskCompletionSource = tcs; + + //var willRaiseEvent = _Socket.SendAsync(_sendSocketAsyncEventArgs); + + //if (!willRaiseEvent) + //{ + // _sendSocketAsyncEventArgs_Completed(this, _sendSocketAsyncEventArgs); + //} + + //return tcs.Task; } public async Task SendWithLockAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken) { ThrowIfDisposed(); - await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); + //await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -177,7 +230,7 @@ namespace Emby.Common.Implementations.Net } finally { - _sendLock.Release(); + //_sendLock.Release(); } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 17c57712e..f0529c596 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -505,7 +505,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } else { - //return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + var enableHttpStream = false; + + if (enableHttpStream) + { + return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + } + return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); } } From 85f4f8dbc970730eba083dec1d8b586510d965a0 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 19:36:36 -0400 Subject: [PATCH 59/67] handle invalid filename chars in episode expressions --- .../FileOrganization/EpisodeFileOrganizer.cs | 46 ++++--------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index f841b8b6b..0a9c67285 100644 --- a/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/Emby.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -677,20 +677,7 @@ namespace Emby.Server.Implementations.FileOrganization var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options); - // MAX_PATH - trailing charachter - drive component: 260 - 1 - 3 = 256 - // Usually newPath would include the drive component, but use 256 to be sure - var maxFilenameLength = 256 - newPath.Length; - - if (!newPath.EndsWith(@"\")) - { - // Remove 1 for missing backslash combining path and filename - maxFilenameLength--; - } - - // Remove additional 4 chars to prevent PathTooLongException for downloaded subtitles (eg. filename.ext.eng.srt) - maxFilenameLength -= 4; - - var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options, maxFilenameLength); + var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options); if (string.IsNullOrEmpty(episodeFileName)) { @@ -742,7 +729,7 @@ namespace Emby.Server.Implementations.FileOrganization return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName)); } - private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options, int? maxLength) + private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options) { seriesName = _fileSystem.GetValidFilename(seriesName).Trim(); @@ -786,32 +773,15 @@ namespace Emby.Server.Implementations.FileOrganization .Replace("%0e", episodeNumber.ToString("00", _usCulture)) .Replace("%00e", episodeNumber.ToString("000", _usCulture)); - if (maxLength.HasValue && result.Contains("%#")) + if (result.Contains("%#")) { - // Substract 3 for the temp token length (%#1, %#2 or %#3) - int maxRemainingTitleLength = maxLength.Value - result.Length + 3; - string shortenedEpisodeTitle = string.Empty; - - if (maxRemainingTitleLength > 5) - { - // A title with fewer than 5 letters wouldn't be of much value - shortenedEpisodeTitle = episodeTitle.Substring(0, Math.Min(maxRemainingTitleLength, episodeTitle.Length)); - } - - result = result.Replace("%#1", shortenedEpisodeTitle) - .Replace("%#2", shortenedEpisodeTitle.Replace(" ", ".")) - .Replace("%#3", shortenedEpisodeTitle.Replace(" ", "_")); + result = result.Replace("%#1", episodeTitle) + .Replace("%#2", episodeTitle.Replace(" ", ".")) + .Replace("%#3", episodeTitle.Replace(" ", "_")); } - if (maxLength.HasValue && result.Length > maxLength.Value) - { - // There may be cases where reducing the title length may still not be sufficient to - // stay below maxLength - var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength); - throw new Exception(msg); - } - - return result; + // Finally, call GetValidFilename again in case user customized the episode expression with any invalid filename characters + return _fileSystem.GetValidFilename(result).Trim(); } private bool IsSameEpisode(string sourcePath, string newPath) From 5477bb95d9bf994f9c68bf5384d26db41c82d5de Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 19:36:52 -0400 Subject: [PATCH 60/67] update socket methods --- Emby.Common.Implementations/Net/UdpSocket.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 834f0a05c..fc23422d7 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -49,7 +49,7 @@ namespace Emby.Common.Implementations.Net private void InitReceiveSocketAsyncEventArgs() { - var receiveBuffer = new byte[8192]; + var receiveBuffer = new byte[81920]; _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; @@ -128,11 +128,18 @@ namespace Emby.Common.Implementations.Net _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint; _currentReceiveTaskCompletionSource = tcs; - var willRaiseEvent = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs); - - if (!willRaiseEvent) + try { - _receiveSocketAsyncEventArgs_Completed(this, _receiveSocketAsyncEventArgs); + var willRaiseEvent = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs); + + if (!willRaiseEvent) + { + _receiveSocketAsyncEventArgs_Completed(this, _receiveSocketAsyncEventArgs); + } + } + catch (Exception ex) + { + tcs.TrySetException(ex); } return tcs.Task; @@ -189,8 +196,6 @@ namespace Emby.Common.Implementations.Net taskSource.TrySetException(ex); } - //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port)); - return taskSource.Task; #endif //ThrowIfDisposed(); From 1a5a0d2cbbf70d5e62fdc8d371504010c7ed089c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 19:53:50 -0400 Subject: [PATCH 61/67] update socket methods --- Emby.Common.Implementations/Net/UdpSocket.cs | 58 +++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index fc23422d7..e44b484cb 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -119,10 +119,17 @@ namespace Emby.Common.Implementations.Net public Task ReceiveAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); - var tcs = new TaskCompletionSource(); - EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); + + var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); + state.TaskCompletionSource = tcs; + +#if NET46 + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); + return tcs.Task; +#endif + cancellationToken.Register(() => tcs.TrySetCanceled()); _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint; @@ -271,5 +278,52 @@ namespace Emby.Common.Implementations.Net return NetworkManager.ToIpEndPointInfo(endpoint); } + + private void ProcessResponse(IAsyncResult asyncResult) + { +#if NET46 + var state = asyncResult.AsyncState as AsyncReceiveState; + try + { + var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint); + + var ipEndPoint = state.RemoteEndPoint as IPEndPoint; + state.TaskCompletionSource.SetResult( + new SocketReceiveResult + { + Buffer = state.Buffer, + ReceivedBytes = bytesRead, + RemoteEndPoint = ToIpEndPointInfo(ipEndPoint), + LocalIPAddress = LocalIPAddress + } + ); + } + catch (ObjectDisposedException) + { + state.TaskCompletionSource.SetCanceled(); + } + catch (Exception ex) + { + state.TaskCompletionSource.SetException(ex); + } +#endif + } + + private class AsyncReceiveState + { + public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint) + { + this.Socket = socket; + this.RemoteEndPoint = remoteEndPoint; + } + + public EndPoint RemoteEndPoint; + public byte[] Buffer = new byte[8192]; + + public Socket Socket { get; private set; } + + public TaskCompletionSource TaskCompletionSource { get; set; } + + } } } From e004e6650099d3179da996fcccf29ffb65081c09 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 20:27:29 -0400 Subject: [PATCH 62/67] update tuner methods --- Emby.Common.Implementations/Net/UdpSocket.cs | 5 ---- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 27 ++++++++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index e44b484cb..a97b2e668 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -125,11 +125,6 @@ namespace Emby.Common.Implementations.Net var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); state.TaskCompletionSource = tcs; -#if NET46 - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state); - return tcs.Task; -#endif - cancellationToken.Register(() => tcs.TrySetCanceled()); _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index f0529c596..2fac96169 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -20,6 +20,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { @@ -30,8 +31,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IServerApplicationHost _appHost; private readonly ISocketFactory _socketFactory; private readonly INetworkManager _networkManager; + private readonly IEnvironmentInfo _environment; - public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager) + public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder) { _httpClient = httpClient; @@ -39,6 +41,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _appHost = appHost; _socketFactory = socketFactory; _networkManager = networkManager; + _environment = environment; } public string Name @@ -503,17 +506,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); } - else + + // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet + var enableHttpStream = _environment.OperatingSystem == OperatingSystem.OSX || + _environment.OperatingSystem == OperatingSystem.BSD; + + if (enableHttpStream) { - var enableHttpStream = false; + mediaSource.Protocol = MediaProtocol.Http; - if (enableHttpStream) + var httpUrl = GetApiUrl(info, true) + "/auto/v" + hdhrId; + + // If raw was used, the tuner doesn't support params + if (!string.IsNullOrWhiteSpace(profile) + && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) { - return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); + httpUrl += "?transcode=" + profile; } + mediaSource.Path = httpUrl; - return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); + return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); } + + return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); } public async Task Validate(TunerHostInfo info) From c071ec551b61a843e83c8b4c9af5e0c50ea9127e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 26 Mar 2017 20:27:44 -0400 Subject: [PATCH 63/67] 3.2.8.15 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index f7f49dc75..d173d9ec5 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.14")] +[assembly: AssemblyVersion("3.2.8.15")] From 7d3aa60db0800a6dd8a59dbdb9ce28e3ca06ba26 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 27 Mar 2017 15:31:24 -0400 Subject: [PATCH 64/67] update hdhr udp stream --- .../Library/MediaSourceManager.cs | 2 + .../TunerHosts/HdHomerun/HdHomerunHost.cs | 2 +- .../HdHomerun/HdHomerunUdpStream.cs | 125 +++++++++++++++++- .../LiveTv/TunerHosts/MulticastStream.cs | 37 ++++-- .../LiveTv/TunerHosts/QueueStream.cs | 39 +++++- 5 files changed, 187 insertions(+), 18 deletions(-) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index c1bd8fe91..ccd4c3631 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -369,6 +369,8 @@ namespace Emby.Server.Implementations.Library { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + enableAutoClose = false; + try { var tuple = GetProvider(request.OpenToken); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 2fac96169..8fa1bbe23 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -510,7 +510,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet var enableHttpStream = _environment.OperatingSystem == OperatingSystem.OSX || _environment.OperatingSystem == OperatingSystem.BSD; - + enableHttpStream = true; if (enableHttpStream) { mediaSource.Protocol = MediaProtocol.Http; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 92000a1b3..e1572ea3f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun onStarted = () => openTaskCompletionSource.TrySetResult(true); } - await _multicastStream.CopyUntilCancelled(udpClient, onStarted, cancellationToken).ConfigureAwait(false); + await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), onStarted, cancellationToken).ConfigureAwait(false); } } catch (OperationCanceledException ex) @@ -167,4 +167,127 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return _multicastStream.CopyToAsync(stream); } } + + // This handles the ReadAsync function only of a Stream object + // This is used to wrap a UDP socket into a stream for MulticastStream which only uses ReadAsync + public class UdpClientStream : Stream + { + private static int RtpHeaderBytes = 12; + private static int PacketSize = 1316; + private readonly ISocket _udpClient; + bool disposed; + + public UdpClientStream(ISocket udpClient) : base() + { + _udpClient = udpClient; + } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + + if (offset + count < 0) + throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count"); + + if (offset + count > buffer.Length) + throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count"); + + if (disposed) + throw new ObjectDisposedException(typeof(UdpClientStream).ToString()); + + // This will always receive a 1328 packet size (PacketSize + RtpHeaderSize) + // The RTP header will be stripped so see how many reads we need to make to fill the buffer. + int numReads = count / PacketSize; + int totalBytesRead = 0; + + for (int i = 0; i < numReads; ++i) + { + var data = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false); + + var bytesRead = data.ReceivedBytes - RtpHeaderBytes; + + // remove rtp header + Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead); + offset += bytesRead; + totalBytesRead += bytesRead; + } + return totalBytesRead; + } + + protected override void Dispose(bool disposing) + { + disposed = true; + } + + public override bool CanRead + { + get + { + throw new NotImplementedException(); + } + } + + public override bool CanSeek + { + get + { + throw new NotImplementedException(); + } + } + + public override bool CanWrite + { + get + { + throw new NotImplementedException(); + } + } + + public override long Length + { + get + { + throw new NotImplementedException(); + } + } + + public override long Position + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + public override void Flush() + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs index e3d0d1eba..281632590 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs @@ -35,13 +35,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (bytesRead > 0) { - byte[] copy = new byte[bytesRead]; - Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead); - var allStreams = _outputStreams.ToList(); - foreach (var stream in allStreams) + + if (allStreams.Count == 1) { - stream.Value.Queue(copy, 0, copy.Length); + await allStreams[0].Value.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + } + else + { + byte[] copy = new byte[bytesRead]; + Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead); + + foreach (var stream in allStreams) + { + stream.Value.Queue(copy, 0, copy.Length); + } } if (onStarted != null) @@ -79,14 +87,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (bytesRead > 0) { - byte[] copy = new byte[bytesRead]; - Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead); - var allStreams = _outputStreams.ToList(); - foreach (var stream in allStreams) + + if (allStreams.Count == 1) { - //stream.Value.Queue(data.Buffer, RtpHeaderBytes, bytesRead); - stream.Value.Queue(copy, 0, copy.Length); + await allStreams[0].Value.WriteAsync(data.Buffer, 0, bytesRead).ConfigureAwait(false); + } + else + { + byte[] copy = new byte[bytesRead]; + Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead); + + foreach (var stream in allStreams) + { + stream.Value.Queue(copy, 0, copy.Length); + } } if (onStarted != null) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs index 27dd288a7..543d2e373 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public class QueueStream { private readonly Stream _outputStream; - private readonly ConcurrentQueue> _queue = new ConcurrentQueue>(); + private readonly ConcurrentQueue> _queue = new ConcurrentQueue>(); private CancellationToken _cancellationToken; public TaskCompletionSource TaskCompletion { get; private set; } @@ -50,6 +50,38 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return null; } + private void OnClosed() + { + GC.Collect(); + if (OnFinished != null) + { + OnFinished(this); + } + } + + public async Task WriteAsync(byte[] bytes, int offset, int count) + { + //return _outputStream.WriteAsync(bytes, offset, count, cancellationToken); + var cancellationToken = _cancellationToken; + + try + { + await _outputStream.WriteAsync(bytes, offset, count, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + _logger.Debug("QueueStream cancelled"); + TaskCompletion.TrySetCanceled(); + OnClosed(); + } + catch (Exception ex) + { + _logger.ErrorException("Error in QueueStream", ex); + TaskCompletion.TrySetException(ex); + OnClosed(); + } + } + private async Task StartInternal() { var cancellationToken = _cancellationToken; @@ -81,10 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } finally { - if (OnFinished != null) - { - OnFinished(this); - } + OnClosed(); } } } From 0f01e3162d5ebfb0a018991d67f50438182c775e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 27 Mar 2017 15:31:49 -0400 Subject: [PATCH 65/67] 3.2.8.16 --- Emby.Server.Implementations/Security/PluginSecurityManager.cs | 2 ++ SharedVersion.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs index a26df7625..d42fae3ad 100644 --- a/Emby.Server.Implementations/Security/PluginSecurityManager.cs +++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs @@ -301,10 +301,12 @@ namespace Emby.Server.Implementations.Security if (reg.registered) { + _logger.Info("Registered for feature {0}", feature); LicenseFile.AddRegCheck(feature, reg.expDate); } else { + _logger.Info("Not registered for feature {0}", feature); LicenseFile.RemoveRegCheck(feature); } diff --git a/SharedVersion.cs b/SharedVersion.cs index d173d9ec5..8ed012105 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.8.15")] +[assembly: AssemblyVersion("3.2.8.16")] From d1e2ccdfeceeebad2b46fdd0ee3723051234dba6 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Mar 2017 13:30:47 -0400 Subject: [PATCH 66/67] update default library name --- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 368053b9d..0ea1b38b1 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2575,7 +2575,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV list.Add(new VirtualFolderInfo { Locations = new List { customPath }, - Name = "Recorded Series", + Name = "Recorded Shows", CollectionType = CollectionType.TvShows }); } From 2dbe162e45269df00fb44fbe5340b72758399307 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 28 Mar 2017 13:32:24 -0400 Subject: [PATCH 67/67] revert buffer size --- Emby.Common.Implementations/Net/UdpSocket.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index a97b2e668..f9181eb6a 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -49,7 +49,7 @@ namespace Emby.Common.Implementations.Net private void InitReceiveSocketAsyncEventArgs() { - var receiveBuffer = new byte[81920]; + var receiveBuffer = new byte[8192]; _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5eaab4110..b7b31509c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -164,6 +164,10 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase)) + { + return null; + } if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase)) { return null; @@ -1709,6 +1713,13 @@ namespace MediaBrowser.Controller.MediaEncoding return "-c:v h264_qsv "; } break; + //case "hevc": + //case "h265": + // if (_mediaEncoder.SupportsDecoder("hevc_qsv")) + // { + // return "-c:v hevc_qsv "; + // } + // break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_qsv")) {