Browse Source

Clean up livestreaming code

Bond_009 6 years ago
parent
commit
237db8ae92

+ 22 - 14
Emby.Dlna/PlayTo/SsdpHttpClient.cs

@@ -16,6 +16,8 @@ namespace Emby.Dlna.PlayTo
         private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
         private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
         private const string FriendlyName = "Jellyfin";
         private const string FriendlyName = "Jellyfin";
 
 
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
         private readonly IHttpClient _httpClient;
         private readonly IHttpClient _httpClient;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
 
 
@@ -25,7 +27,8 @@ namespace Emby.Dlna.PlayTo
             _config = config;
             _config = config;
         }
         }
 
 
-        public async Task<XDocument> SendCommandAsync(string baseUrl,
+        public async Task<XDocument> SendCommandAsync(
+            string baseUrl,
             DeviceService service,
             DeviceService service,
             string command,
             string command,
             string postData,
             string postData,
@@ -35,12 +38,20 @@ namespace Emby.Dlna.PlayTo
             var cancellationToken = CancellationToken.None;
             var cancellationToken = CancellationToken.None;
 
 
             var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
             var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
-            using (var response = await PostSoapDataAsync(url, '\"' + service.ServiceType + '#' + command + '\"', postData, header, logRequest, cancellationToken)
+            using (var response = await PostSoapDataAsync(
+                url,
+                $"\"{service.ServiceType}#{command}\"",
+                postData,
+                header,
+                logRequest,
+                cancellationToken)
                 .ConfigureAwait(false))
                 .ConfigureAwait(false))
             using (var stream = response.Content)
             using (var stream = response.Content)
             using (var reader = new StreamReader(stream, Encoding.UTF8))
             using (var reader = new StreamReader(stream, Encoding.UTF8))
             {
             {
-                return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
+                return XDocument.Parse(
+                    await reader.ReadToEndAsync().ConfigureAwait(false),
+                    LoadOptions.PreserveWhitespace);
             }
             }
         }
         }
 
 
@@ -58,9 +69,8 @@ namespace Emby.Dlna.PlayTo
             return baseUrl + serviceUrl;
             return baseUrl + serviceUrl;
         }
         }
 
 
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        public async Task SubscribeAsync(string url,
+        public async Task SubscribeAsync(
+            string url,
             string ip,
             string ip,
             int port,
             int port,
             string localIp,
             string localIp,
@@ -101,14 +111,12 @@ namespace Emby.Dlna.PlayTo
             options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
             options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
 
 
             using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
             using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
+            using (var stream = response.Content)
+            using (var reader = new StreamReader(stream, Encoding.UTF8))
             {
             {
-                using (var stream = response.Content)
-                {
-                    using (var reader = new StreamReader(stream, Encoding.UTF8))
-                    {
-                        return XDocument.Parse(reader.ReadToEnd(), LoadOptions.PreserveWhitespace);
-                    }
-                }
+                return XDocument.Parse(
+                    await reader.ReadToEndAsync().ConfigureAwait(false),
+                    LoadOptions.PreserveWhitespace);
             }
             }
         }
         }
 
 
@@ -122,7 +130,7 @@ namespace Emby.Dlna.PlayTo
         {
         {
             if (soapAction[0] != '\"')
             if (soapAction[0] != '\"')
             {
             {
-                soapAction = '\"' + soapAction + '\"';
+                soapAction = $"\"{soapAction}\"";
             }
             }
 
 
             var options = new HttpRequestOptions
             var options = new HttpRequestOptions

+ 4 - 20
Emby.Server.Implementations/ApplicationHost.cs

@@ -315,8 +315,6 @@ namespace Emby.Server.Implementations
 
 
         private IMediaSourceManager MediaSourceManager { get; set; }
         private IMediaSourceManager MediaSourceManager { get; set; }
 
 
-        private IPlaylistManager PlaylistManager { get; set; }
-
         private readonly IConfiguration _configuration;
         private readonly IConfiguration _configuration;
 
 
         /// <summary>
         /// <summary>
@@ -325,14 +323,6 @@ namespace Emby.Server.Implementations
         /// <value>The installation manager.</value>
         /// <value>The installation manager.</value>
         protected IInstallationManager InstallationManager { get; private set; }
         protected IInstallationManager InstallationManager { get; private set; }
 
 
-        /// <summary>
-        /// Gets or sets the zip client.
-        /// </summary>
-        /// <value>The zip client.</value>
-        protected IZipClient ZipClient { get; private set; }
-
-        protected IHttpResultFactory HttpResultFactory { get; private set; }
-
         protected IAuthService AuthService { get; private set; }
         protected IAuthService AuthService { get; private set; }
 
 
         public IStartupOptions StartupOptions { get; }
         public IStartupOptions StartupOptions { get; }
@@ -680,8 +670,6 @@ namespace Emby.Server.Implementations
             await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
             await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false);
         }
         }
 
 
-        public static IStreamHelper StreamHelper { get; set; }
-
         /// <summary>
         /// <summary>
         /// Registers resources that classes will depend on
         /// Registers resources that classes will depend on
         /// </summary>
         /// </summary>
@@ -725,8 +713,7 @@ namespace Emby.Server.Implementations
             ProcessFactory = new ProcessFactory();
             ProcessFactory = new ProcessFactory();
             serviceCollection.AddSingleton(ProcessFactory);
             serviceCollection.AddSingleton(ProcessFactory);
 
 
-            ApplicationHost.StreamHelper = new StreamHelper();
-            serviceCollection.AddSingleton(StreamHelper);
+            serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
 
 
             serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
             serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
 
 
@@ -735,11 +722,9 @@ namespace Emby.Server.Implementations
 
 
             serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
             serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
 
 
-            ZipClient = new ZipClient();
-            serviceCollection.AddSingleton(ZipClient);
+            serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient));
 
 
-            HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper);
-            serviceCollection.AddSingleton(HttpResultFactory);
+            serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory));
 
 
             serviceCollection.AddSingleton<IServerApplicationHost>(this);
             serviceCollection.AddSingleton<IServerApplicationHost>(this);
             serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
             serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
@@ -836,8 +821,7 @@ namespace Emby.Server.Implementations
             CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
             CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
             serviceCollection.AddSingleton(CollectionManager);
             serviceCollection.AddSingleton(CollectionManager);
 
 
-            PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager);
-            serviceCollection.AddSingleton(PlaylistManager);
+            serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager));
 
 
             LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
             LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
             serviceCollection.AddSingleton(LiveTvManager);
             serviceCollection.AddSingleton(LiveTvManager);

+ 96 - 88
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -4,6 +4,7 @@ using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Net;
 using System.Net;
+using System.Net.Http;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
@@ -31,6 +32,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
         private readonly ISocketFactory _socketFactory;
         private readonly ISocketFactory _socketFactory;
         private readonly INetworkManager _networkManager;
         private readonly INetworkManager _networkManager;
+        private readonly IStreamHelper _streamHelper;
 
 
         public HdHomerunHost(
         public HdHomerunHost(
             IServerConfigurationManager config,
             IServerConfigurationManager config,
@@ -40,29 +42,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             IHttpClient httpClient,
             IHttpClient httpClient,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
             ISocketFactory socketFactory,
             ISocketFactory socketFactory,
-            INetworkManager networkManager)
+            INetworkManager networkManager,
+            IStreamHelper streamHelper)
             : base(config, logger, jsonSerializer, fileSystem)
             : base(config, logger, jsonSerializer, fileSystem)
         {
         {
             _httpClient = httpClient;
             _httpClient = httpClient;
             _appHost = appHost;
             _appHost = appHost;
             _socketFactory = socketFactory;
             _socketFactory = socketFactory;
             _networkManager = networkManager;
             _networkManager = networkManager;
+            _streamHelper = streamHelper;
         }
         }
 
 
         public string Name => "HD Homerun";
         public string Name => "HD Homerun";
 
 
-        public override string Type => DeviceType;
-
-        public static string DeviceType => "hdhomerun";
+        public override string Type => "hdhomerun";
 
 
         protected override string ChannelIdPrefix => "hdhr_";
         protected override string ChannelIdPrefix => "hdhr_";
 
 
         private string GetChannelId(TunerHostInfo info, Channels i)
         private string GetChannelId(TunerHostInfo info, Channels i)
-        {
-            var id = ChannelIdPrefix + i.GuideNumber;
-
-            return id;
-        }
+            => ChannelIdPrefix + i.GuideNumber;
 
 
         private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
         private async Task<List<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
         {
         {
@@ -74,19 +72,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 CancellationToken = cancellationToken,
                 CancellationToken = cancellationToken,
                 BufferContent = false
                 BufferContent = false
             };
             };
-            using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
-            {
-                using (var stream = response.Content)
-                {
-                    var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
 
 
-                    if (info.ImportFavoritesOnly)
-                    {
-                        lineup = lineup.Where(i => i.Favorite).ToList();
-                    }
+            using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
+            using (var stream = response.Content)
+            {
+                var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
 
 
-                    return lineup.Where(i => !i.DRM).ToList();
+                if (info.ImportFavoritesOnly)
+                {
+                    lineup = lineup.Where(i => i.Favorite).ToList();
                 }
                 }
+
+                return lineup.Where(i => !i.DRM).ToList();
             }
             }
         }
         }
 
 
@@ -139,23 +136,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     Url = string.Format("{0}/discover.json", GetApiUrl(info)),
                     Url = string.Format("{0}/discover.json", GetApiUrl(info)),
                     CancellationToken = cancellationToken,
                     CancellationToken = cancellationToken,
                     BufferContent = false
                     BufferContent = false
-
-                }, "GET").ConfigureAwait(false))
+                }, HttpMethod.Get).ConfigureAwait(false))
+                using (var stream = response.Content)
                 {
                 {
-                    using (var stream = response.Content)
-                    {
-                        var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
+                    var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
 
 
-                        if (!string.IsNullOrEmpty(cacheKey))
+                    if (!string.IsNullOrEmpty(cacheKey))
+                    {
+                        lock (_modelCache)
                         {
                         {
-                            lock (_modelCache)
-                            {
-                                _modelCache[cacheKey] = discoverResponse;
-                            }
+                            _modelCache[cacheKey] = discoverResponse;
                         }
                         }
-
-                        return discoverResponse;
                     }
                     }
+
+                    return discoverResponse;
                 }
                 }
             }
             }
             catch (HttpException ex)
             catch (HttpException ex)
@@ -186,36 +180,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         {
         {
             var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
             var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
 
 
-            using (var stream = await _httpClient.Get(new HttpRequestOptions()
+            using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
             {
             {
                 Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
                 Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
                 CancellationToken = cancellationToken,
                 CancellationToken = cancellationToken,
                 BufferContent = false
                 BufferContent = false
-            }))
+            }, HttpMethod.Get))
+            using (var stream = response.Content)
+            using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
             {
             {
                 var tuners = new List<LiveTvTunerInfo>();
                 var tuners = new List<LiveTvTunerInfo>();
-                using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
+                while (!sr.EndOfStream)
                 {
                 {
-                    while (!sr.EndOfStream)
+                    string line = StripXML(sr.ReadLine());
+                    if (line.Contains("Channel"))
                     {
                     {
-                        string line = StripXML(sr.ReadLine());
-                        if (line.Contains("Channel"))
+                        LiveTvTunerStatus status;
+                        var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
+                        var name = line.Substring(0, index - 1);
+                        var currentChannel = line.Substring(index + 7);
+                        if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
+                        tuners.Add(new LiveTvTunerInfo
                         {
                         {
-                            LiveTvTunerStatus status;
-                            var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
-                            var name = line.Substring(0, index - 1);
-                            var currentChannel = line.Substring(index + 7);
-                            if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
-                            tuners.Add(new LiveTvTunerInfo
-                            {
-                                Name = name,
-                                SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
-                                ProgramName = currentChannel,
-                                Status = status
-                            });
-                        }
+                            Name = name,
+                            SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
+                            ProgramName = currentChannel,
+                            Status = status
+                        });
                     }
                     }
                 }
                 }
+
                 return tuners;
                 return tuners;
             }
             }
         }
         }
@@ -245,6 +239,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     bufferIndex++;
                     bufferIndex++;
                 }
                 }
             }
             }
+
             return new string(buffer, 0, bufferIndex);
             return new string(buffer, 0, bufferIndex);
         }
         }
 
 
@@ -256,7 +251,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             var uri = new Uri(GetApiUrl(info));
             var uri = new Uri(GetApiUrl(info));
 
 
-            using (var manager = new HdHomerunManager(Logger))
+            using (var manager = new HdHomerunManager())
             {
             {
                 // Legacy HdHomeruns are IPv4 only
                 // Legacy HdHomeruns are IPv4 only
                 var ipInfo = IPAddress.Parse(uri.Host);
                 var ipInfo = IPAddress.Parse(uri.Host);
@@ -276,6 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     });
                     });
                 }
                 }
             }
             }
+
             return tuners;
             return tuners;
         }
         }
 
 
@@ -434,12 +430,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
             {
                 videoCodec = channelInfo.VideoCodec;
                 videoCodec = channelInfo.VideoCodec;
             }
             }
+
             string audioCodec = channelInfo.AudioCodec;
             string audioCodec = channelInfo.AudioCodec;
 
 
             if (!videoBitrate.HasValue)
             if (!videoBitrate.HasValue)
             {
             {
                 videoBitrate = isHd ? 15000000 : 2000000;
                 videoBitrate = isHd ? 15000000 : 2000000;
             }
             }
+
             int? audioBitrate = isHd ? 448000 : 192000;
             int? audioBitrate = isHd ? 448000 : 192000;
 
 
             // normalize
             // normalize
@@ -461,6 +459,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
             {
                 id = "native";
                 id = "native";
             }
             }
+
             id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
             id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture);
 
 
             var mediaSource = new MediaSourceInfo
             var mediaSource = new MediaSourceInfo
@@ -527,29 +526,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             }
             }
             else
             else
             {
             {
-                try
-                {
-                    var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+                var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
 
 
-                    if (modelInfo != null && modelInfo.SupportsTranscoding)
+                if (modelInfo != null && modelInfo.SupportsTranscoding)
+                {
+                    if (info.AllowHWTranscoding)
                     {
                     {
-                        if (info.AllowHWTranscoding)
-                        {
-                            list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
-
-                            list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
-                            list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
-                            list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
-                            list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
-                            list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
-                        }
+                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
 
 
-                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
+                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540"));
+                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480"));
+                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360"));
+                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
+                        list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
                     }
                     }
-                }
-                catch
-                {
 
 
+                    list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
                 }
                 }
 
 
                 if (list.Count == 0)
                 if (list.Count == 0)
@@ -582,7 +574,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
             if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
             {
             {
-                return new HdHomerunUdpStream(mediaSource, info, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+                return new HdHomerunUdpStream(
+                    mediaSource,
+                    info,
+                    streamId,
+                    new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path),
+                    modelInfo.TunerCount,
+                    FileSystem,
+                    Logger,
+                    Config.ApplicationPaths,
+                    _appHost,
+                    _socketFactory,
+                    _networkManager,
+                    _streamHelper);
             }
             }
 
 
             var enableHttpStream = true;
             var enableHttpStream = true;
@@ -599,10 +603,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 }
                 }
                 mediaSource.Path = httpUrl;
                 mediaSource.Path = httpUrl;
 
 
-                return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
-            }
-
-            return new HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
+                return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
+            }
+
+            return new HdHomerunUdpStream(
+                mediaSource,
+                info,
+                streamId,
+                new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
+                modelInfo.TunerCount,
+                FileSystem,
+                Logger,
+                Config.ApplicationPaths,
+                _appHost,
+                _socketFactory,
+                _networkManager,
+                _streamHelper);
         }
         }
 
 
         public async Task Validate(TunerHostInfo info)
         public async Task Validate(TunerHostInfo info)
@@ -701,9 +717,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 catch (OperationCanceledException)
                 catch (OperationCanceledException)
                 {
                 {
                 }
                 }
-                catch
+                catch (Exception ex)
                 {
                 {
                     // Socket timeout indicates all messages have been received.
                     // Socket timeout indicates all messages have been received.
+                    Logger.LogError(ex, "Error while sending discovery message");
                 }
                 }
             }
             }
 
 
@@ -718,21 +735,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 Url = url
                 Url = url
             };
             };
 
 
-            try
-            {
-                var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
-
-                hostInfo.DeviceId = modelInfo.DeviceID;
-                hostInfo.FriendlyName = modelInfo.FriendlyName;
+            var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
 
 
-                return hostInfo;
-            }
-            catch
-            {
-                // logged at lower levels
-            }
+            hostInfo.DeviceId = modelInfo.DeviceID;
+            hostInfo.FriendlyName = modelInfo.FriendlyName;
 
 
-            return null;
+            return hostInfo;
         }
         }
     }
     }
 }
 }

+ 56 - 59
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs

@@ -14,7 +14,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 {
 {
     public interface IHdHomerunChannelCommands
     public interface IHdHomerunChannelCommands
     {
     {
-        IEnumerable<Tuple<string, string>> GetCommands();
+        IEnumerable<(string, string)> GetCommands();
     }
     }
 
 
     public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
     public class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
@@ -33,16 +33,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             }
             }
         }
         }
 
 
-        public IEnumerable<Tuple<string, string>> GetCommands()
+        public IEnumerable<(string, string)> GetCommands()
         {
         {
-            var commands = new List<Tuple<string, string>>();
-
             if (!string.IsNullOrEmpty(_channel))
             if (!string.IsNullOrEmpty(_channel))
-                commands.Add(Tuple.Create("channel", _channel));
+            {
+                yield return ("channel", _channel);
+            }
 
 
             if (!string.IsNullOrEmpty(_program))
             if (!string.IsNullOrEmpty(_program))
-                commands.Add(Tuple.Create("program", _program));
-            return commands;
+            {
+                yield return ("program", _program);
+            }
         }
         }
     }
     }
 
 
@@ -57,23 +58,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             _profile = profile;
             _profile = profile;
         }
         }
 
 
-        public IEnumerable<Tuple<string, string>> GetCommands()
+        public IEnumerable<(string, string)> GetCommands()
         {
         {
-            var commands = new List<Tuple<string, string>>();
-
             if (!string.IsNullOrEmpty(_channel))
             if (!string.IsNullOrEmpty(_channel))
             {
             {
-                if (!string.IsNullOrEmpty(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
+                if (!string.IsNullOrEmpty(_profile)
+                    && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    commands.Add(Tuple.Create("vchannel", string.Format("{0} transcode={1}", _channel, _profile)));
+                    yield return ("vchannel", $"{_channel} transcode={_profile}");
                 }
                 }
                 else
                 else
                 {
                 {
-                    commands.Add(Tuple.Create("vchannel", _channel));
+                    yield return ("vchannel", _channel);
                 }
                 }
             }
             }
-
-            return commands;
         }
         }
     }
     }
 
 
@@ -87,19 +85,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         private const ushort GetSetRequest = 4;
         private const ushort GetSetRequest = 4;
         private const ushort GetSetReply = 5;
         private const ushort GetSetReply = 5;
 
 
-        private readonly ILogger _logger;
-
         private uint? _lockkey = null;
         private uint? _lockkey = null;
         private int _activeTuner = -1;
         private int _activeTuner = -1;
         private IPEndPoint _remoteEndPoint;
         private IPEndPoint _remoteEndPoint;
 
 
         private TcpClient _tcpClient;
         private TcpClient _tcpClient;
 
 
-        public HdHomerunManager(ILogger logger)
-        {
-            _logger = logger;
-        }
-
         public void Dispose()
         public void Dispose()
         {
         {
             using (var socket = _tcpClient)
             using (var socket = _tcpClient)
@@ -108,8 +99,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 {
                 {
                     _tcpClient = null;
                     _tcpClient = null;
 
 
-                    var task = StopStreaming(socket);
-                    Task.WaitAll(task);
+                    StopStreaming(socket).GetAwaiter().GetResult();
                 }
                 }
             }
             }
         }
         }
@@ -174,19 +164,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
                     await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
                     int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                     int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                     // parse response to make sure it worked
                     // parse response to make sure it worked
-                    if (!ParseReturnMessage(buffer, receivedBytes, out var returnVal))
+                    if (!ParseReturnMessage(buffer, receivedBytes, out _))
                     {
                     {
                         continue;
                         continue;
                     }
                     }
 
 
                     var commandList = commands.GetCommands();
                     var commandList = commands.GetCommands();
-                    foreach (Tuple<string, string> command in commandList)
+                    foreach (var command in commandList)
                     {
                     {
                         var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
                         var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
                         await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
                         await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
                         receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                         receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                         // parse response to make sure it worked
                         // parse response to make sure it worked
-                        if (!ParseReturnMessage(buffer, receivedBytes, out returnVal))
+                        if (!ParseReturnMessage(buffer, receivedBytes, out _))
                         {
                         {
                             await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
                             await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
                             continue;
                             continue;
@@ -199,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
                     await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
                     receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                     receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                     // parse response to make sure it worked
                     // parse response to make sure it worked
-                    if (!ParseReturnMessage(buffer, receivedBytes, out returnVal))
+                    if (!ParseReturnMessage(buffer, receivedBytes, out _))
                     {
                     {
                         await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
                         await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
                         continue;
                         continue;
@@ -231,13 +221,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
                 byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
                 try
                 try
                 {
                 {
-                    foreach (Tuple<string, string> command in commandList)
+                    foreach (var command in commandList)
                     {
                     {
                         var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
                         var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey);
                         await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
                         await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
                         int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                         int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
                         // parse response to make sure it worked
                         // parse response to make sure it worked
-                        if (!ParseReturnMessage(buffer, receivedBytes, out string returnVal))
+                        if (!ParseReturnMessage(buffer, receivedBytes, out _))
                         {
                         {
                             return;
                             return;
                         }
                         }
@@ -264,21 +254,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
         private async Task ReleaseLockkey(TcpClient client, uint lockKeyValue)
         private async Task ReleaseLockkey(TcpClient client, uint lockKeyValue)
         {
         {
-            _logger.LogInformation("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue);
-
             var stream = client.GetStream();
             var stream = client.GetStream();
 
 
             var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
             var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
-            await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length, CancellationToken.None).ConfigureAwait(false);
+            await stream.WriteAsync(releaseTarget, 0, releaseTarget.Length).ConfigureAwait(false);
 
 
             var buffer = ArrayPool<byte>.Shared.Rent(8192);
             var buffer = ArrayPool<byte>.Shared.Rent(8192);
             try
             try
             {
             {
-                await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
+                await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
                 var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
                 var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
                 _lockkey = null;
                 _lockkey = null;
-                await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, CancellationToken.None).ConfigureAwait(false);
-                await stream.ReadAsync(buffer, 0, buffer.Length, CancellationToken.None).ConfigureAwait(false);
+                await stream.WriteAsync(releaseKeyMsg, 0, releaseKeyMsg.Length).ConfigureAwait(false);
+                await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
             }
             }
             finally
             finally
             {
             {
@@ -316,7 +304,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             int messageLength = byteName.Length + byteValue.Length + 12;
             int messageLength = byteName.Length + byteValue.Length + 12;
             if (lockkey.HasValue)
             if (lockkey.HasValue)
+            {
                 messageLength += 6;
                 messageLength += 6;
+            }
 
 
             var message = new byte[messageLength];
             var message = new byte[messageLength];
 
 
@@ -324,21 +314,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             bool flipEndian = BitConverter.IsLittleEndian;
             bool flipEndian = BitConverter.IsLittleEndian;
 
 
-            message[offset] = GetSetValue;
-            offset++;
-            message[offset] = Convert.ToByte(byteValue.Length);
-            offset++;
+            message[offset++] = GetSetValue;
+            message[offset++] = Convert.ToByte(byteValue.Length);
             Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
             Buffer.BlockCopy(byteValue, 0, message, offset, byteValue.Length);
             offset += byteValue.Length;
             offset += byteValue.Length;
             if (lockkey.HasValue)
             if (lockkey.HasValue)
             {
             {
-                message[offset] = GetSetLockkey;
-                offset++;
-                message[offset] = (byte)4;
-                offset++;
+                message[offset++] = GetSetLockkey;
+                message[offset++] = (byte)4;
                 var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
                 var lockKeyBytes = BitConverter.GetBytes(lockkey.Value);
                 if (flipEndian)
                 if (flipEndian)
+                {
                     Array.Reverse(lockKeyBytes);
                     Array.Reverse(lockKeyBytes);
+                }
+
                 Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
                 Buffer.BlockCopy(lockKeyBytes, 0, message, offset, 4);
                 offset += 4;
                 offset += 4;
             }
             }
@@ -346,7 +335,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             // calculate crc and insert at the end of the message
             // calculate crc and insert at the end of the message
             var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
             var crcBytes = BitConverter.GetBytes(HdHomerunCrc.GetCrc32(message, messageLength - 4));
             if (flipEndian)
             if (flipEndian)
+            {
                 Array.Reverse(crcBytes);
                 Array.Reverse(crcBytes);
+            }
+
             Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
             Buffer.BlockCopy(crcBytes, 0, message, offset, 4);
 
 
             return message;
             return message;
@@ -375,10 +367,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             offset += 2;
             offset += 2;
 
 
             // insert tag name and length
             // insert tag name and length
-            message[offset] = GetSetName;
-            offset++;
-            message[offset] = Convert.ToByte(byteName.Length);
-            offset++;
+            message[offset++] = GetSetName;
+            message[offset++] = Convert.ToByte(byteName.Length);
 
 
             // insert name string
             // insert name string
             Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
             Buffer.BlockCopy(byteName, 0, message, offset, byteName.Length);
@@ -392,7 +382,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             returnVal = string.Empty;
             returnVal = string.Empty;
 
 
             if (numBytes < 4)
             if (numBytes < 4)
+            {
                 return false;
                 return false;
+            }
 
 
             var flipEndian = BitConverter.IsLittleEndian;
             var flipEndian = BitConverter.IsLittleEndian;
             int offset = 0;
             int offset = 0;
@@ -400,45 +392,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
             Buffer.BlockCopy(buf, offset, msgTypeBytes, 0, msgTypeBytes.Length);
 
 
             if (flipEndian)
             if (flipEndian)
+            {
                 Array.Reverse(msgTypeBytes);
                 Array.Reverse(msgTypeBytes);
+            }
 
 
             var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
             var msgType = BitConverter.ToUInt16(msgTypeBytes, 0);
             offset += 2;
             offset += 2;
 
 
             if (msgType != GetSetReply)
             if (msgType != GetSetReply)
+            {
                 return false;
                 return false;
+            }
 
 
             byte[] msgLengthBytes = new byte[2];
             byte[] msgLengthBytes = new byte[2];
             Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
             Buffer.BlockCopy(buf, offset, msgLengthBytes, 0, msgLengthBytes.Length);
             if (flipEndian)
             if (flipEndian)
+            {
                 Array.Reverse(msgLengthBytes);
                 Array.Reverse(msgLengthBytes);
+            }
 
 
             var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
             var msgLength = BitConverter.ToUInt16(msgLengthBytes, 0);
             offset += 2;
             offset += 2;
 
 
             if (numBytes < msgLength + 8)
             if (numBytes < msgLength + 8)
+            {
                 return false;
                 return false;
+            }
 
 
-            var nameTag = buf[offset];
-            offset++;
+            var nameTag = buf[offset++];
 
 
-            var nameLength = buf[offset];
-            offset++;
+            var nameLength = buf[offset++];
 
 
             // skip the name field to get to value for return
             // skip the name field to get to value for return
             offset += nameLength;
             offset += nameLength;
 
 
-            var valueTag = buf[offset];
-            offset++;
+            var valueTag = buf[offset++];
 
 
-            var valueLength = buf[offset];
-            offset++;
+            var valueLength = buf[offset++];
 
 
             returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
             returnVal = Encoding.UTF8.GetString(buf, offset, valueLength - 1); // remove null terminator
             return true;
             return true;
         }
         }
 
 
-        private class HdHomerunCrc
+        private static class HdHomerunCrc
         {
         {
             private static uint[] crc_table = {
             private static uint[] crc_table = {
             0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
             0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
@@ -510,15 +506,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
             {
                 var hash = 0xffffffff;
                 var hash = 0xffffffff;
                 for (var i = 0; i < numBytes; i++)
                 for (var i = 0; i < numBytes; i++)
+                {
                     hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
                     hash = (hash >> 8) ^ crc_table[(hash ^ bytes[i]) & 0xff];
+                }
 
 
                 var tmp = ~hash & 0xffffffff;
                 var tmp = ~hash & 0xffffffff;
                 var b0 = tmp & 0xff;
                 var b0 = tmp & 0xff;
                 var b1 = (tmp >> 8) & 0xff;
                 var b1 = (tmp >> 8) & 0xff;
                 var b2 = (tmp >> 16) & 0xff;
                 var b2 = (tmp >> 16) & 0xff;
                 var b3 = (tmp >> 24) & 0xff;
                 var b3 = (tmp >> 24) & 0xff;
-                hash = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
-                return hash;
+                return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
             }
             }
         }
         }
     }
     }

+ 39 - 38
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Buffers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Net;
 using System.Net;
@@ -18,6 +19,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 {
 {
     public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
     public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
     {
     {
+        private const int RtpHeaderBytes = 12;
+
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
         private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory;
         private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory;
 
 
@@ -32,13 +35,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             IHdHomerunChannelCommands channelCommands,
             IHdHomerunChannelCommands channelCommands,
             int numTuners,
             int numTuners,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
-            IHttpClient httpClient,
             ILogger logger,
             ILogger logger,
             IServerApplicationPaths appPaths,
             IServerApplicationPaths appPaths,
             IServerApplicationHost appHost,
             IServerApplicationHost appHost,
             MediaBrowser.Model.Net.ISocketFactory socketFactory,
             MediaBrowser.Model.Net.ISocketFactory socketFactory,
-            INetworkManager networkManager)
-            : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
+            INetworkManager networkManager,
+            IStreamHelper streamHelper)
+            : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
         {
         {
             _appHost = appHost;
             _appHost = appHost;
             _socketFactory = socketFactory;
             _socketFactory = socketFactory;
@@ -80,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             }
             }
 
 
             var udpClient = _socketFactory.CreateUdpSocket(localPort);
             var udpClient = _socketFactory.CreateUdpSocket(localPort);
-            var hdHomerunManager = new HdHomerunManager(Logger);
+            var hdHomerunManager = new HdHomerunManager();
 
 
             try
             try
             {
             {
@@ -103,7 +106,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             var taskCompletionSource = new TaskCompletionSource<bool>();
             var taskCompletionSource = new TaskCompletionSource<bool>();
 
 
-            await StartStreaming(udpClient, hdHomerunManager, remoteAddress, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
+            await StartStreaming(
+                udpClient,
+                hdHomerunManager,
+                remoteAddress,
+                taskCompletionSource,
+                LiveStreamCancellationTokenSource.Token).ConfigureAwait(false);
 
 
             //OpenedMediaSource.Protocol = MediaProtocol.File;
             //OpenedMediaSource.Protocol = MediaProtocol.File;
             //OpenedMediaSource.Path = tempFile;
             //OpenedMediaSource.Path = tempFile;
@@ -148,50 +156,43 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             });
             });
         }
         }
 
 
-        private static void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
-        {
-            Task.Run(() =>
-            {
-                openTaskCompletionSource.TrySetResult(true);
-            });
-        }
-
-        private const int RtpHeaderBytes = 12;
-
         private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
         private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
         {
         {
-            var bufferSize = 81920;
-
-            byte[] buffer = new byte[bufferSize];
-            int read;
-            var resolved = false;
-
-            using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
-            using (var fileStream = FileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
+            byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamDefaults.DefaultCopyToBufferSize);
+            try
             {
             {
-                var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
-
-                while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
+                using (var source = _socketFactory.CreateNetworkStream(udpClient, false))
+                using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
                 {
                 {
-                    cancellationToken.ThrowIfCancellationRequested();
+                    var currentCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token).Token;
+                    int read;
+                    var resolved = false;
+                    while ((read = await source.ReadAsync(buffer, 0, buffer.Length, currentCancellationToken).ConfigureAwait(false)) != 0)
+                    {
+                        cancellationToken.ThrowIfCancellationRequested();
 
 
-                    currentCancellationToken = cancellationToken;
+                        currentCancellationToken = cancellationToken;
 
 
-                    read -= RtpHeaderBytes;
+                        read -= RtpHeaderBytes;
 
 
-                    if (read > 0)
-                    {
-                        fileStream.Write(buffer, RtpHeaderBytes, read);
-                    }
+                        if (read > 0)
+                        {
+                            await fileStream.WriteAsync(buffer, RtpHeaderBytes, read).ConfigureAwait(false);
+                        }
 
 
-                    if (!resolved)
-                    {
-                        resolved = true;
-                        DateOpened = DateTime.UtcNow;
-                        Resolve(openTaskCompletionSource);
+                        if (!resolved)
+                        {
+                            resolved = true;
+                            DateOpened = DateTime.UtcNow;
+                            openTaskCompletionSource.TrySetResult(true);
+                        }
                     }
                     }
                 }
                 }
             }
             }
+            finally
+            {
+                ArrayPool<byte>.Shared.Return(buffer);
+            }
         }
         }
     }
     }
 }
 }

+ 44 - 34
Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs

@@ -16,27 +16,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 {
 {
     public class LiveStream : ILiveStream
     public class LiveStream : ILiveStream
     {
     {
-        public MediaSourceInfo OriginalMediaSource { get; set; }
-        public MediaSourceInfo MediaSource { get; set; }
-
-        public int ConsumerCount { get; set; }
-
-        public string OriginalStreamId { get; set; }
-        public bool EnableStreamSharing { get; set; }
-        public string UniqueId { get; }
-
         protected readonly IFileSystem FileSystem;
         protected readonly IFileSystem FileSystem;
         protected readonly IServerApplicationPaths AppPaths;
         protected readonly IServerApplicationPaths AppPaths;
+        protected readonly IStreamHelper StreamHelper;
 
 
         protected string TempFilePath;
         protected string TempFilePath;
         protected readonly ILogger Logger;
         protected readonly ILogger Logger;
         protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
         protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
 
 
-        public string TunerHostId { get; }
-
-        public DateTime DateOpened { get; protected set; }
-
-        public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
+        public LiveStream(
+            MediaSourceInfo mediaSource,
+            TunerHostInfo tuner,
+            IFileSystem fileSystem,
+            ILogger logger,
+            IServerApplicationPaths appPaths,
+            IStreamHelper streamHelper)
         {
         {
             OriginalMediaSource = mediaSource;
             OriginalMediaSource = mediaSource;
             FileSystem = fileSystem;
             FileSystem = fileSystem;
@@ -51,11 +45,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             }
             }
 
 
             AppPaths = appPaths;
             AppPaths = appPaths;
+            StreamHelper = streamHelper;
 
 
             ConsumerCount = 1;
             ConsumerCount = 1;
             SetTempFilePath("ts");
             SetTempFilePath("ts");
         }
         }
 
 
+        protected virtual int EmptyReadLimit => 1000;
+
+        public MediaSourceInfo OriginalMediaSource { get; set; }
+        public MediaSourceInfo MediaSource { get; set; }
+
+        public int ConsumerCount { get; set; }
+
+        public string OriginalStreamId { get; set; }
+        public bool EnableStreamSharing { get; set; }
+        public string UniqueId { get; }
+
+        public string TunerHostId { get; }
+
+        public DateTime DateOpened { get; protected set; }
+
         protected void SetTempFilePath(string extension)
         protected void SetTempFilePath(string extension)
         {
         {
             TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
             TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
@@ -71,24 +81,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         {
         {
             EnableStreamSharing = false;
             EnableStreamSharing = false;
 
 
-            Logger.LogInformation("Closing " + GetType().Name);
+            Logger.LogInformation("Closing {Type}", GetType().Name);
 
 
             LiveStreamCancellationTokenSource.Cancel();
             LiveStreamCancellationTokenSource.Cancel();
 
 
             return Task.CompletedTask;
             return Task.CompletedTask;
         }
         }
 
 
-        protected Stream GetInputStream(string path, bool allowAsyncFileRead)
-        {
-            var fileOpenOptions = FileOpenOptions.SequentialScan;
-
-            if (allowAsyncFileRead)
-            {
-                fileOpenOptions |= FileOpenOptions.Asynchronous;
-            }
-
-            return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
-        }
+        protected FileStream GetInputStream(string path, bool allowAsyncFileRead)
+            => new FileStream(
+                path,
+                FileMode.Open,
+                FileAccess.Read,
+                FileShare.ReadWrite,
+                StreamDefaults.DefaultFileStreamBufferSize,
+                allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
 
 
         public Task DeleteTempFiles()
         public Task DeleteTempFiles()
         {
         {
@@ -144,8 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
             bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
 
 
             var nextFileInfo = GetNextFile(null);
             var nextFileInfo = GetNextFile(null);
-            var nextFile = nextFileInfo.Item1;
-            var isLastFile = nextFileInfo.Item2;
+            var nextFile = nextFileInfo.file;
+            var isLastFile = nextFileInfo.isLastFile;
 
 
             while (!string.IsNullOrEmpty(nextFile))
             while (!string.IsNullOrEmpty(nextFile))
             {
             {
@@ -155,8 +162,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
                 seekFile = false;
                 seekFile = false;
                 nextFileInfo = GetNextFile(nextFile);
                 nextFileInfo = GetNextFile(nextFile);
-                nextFile = nextFileInfo.Item1;
-                isLastFile = nextFileInfo.Item2;
+                nextFile = nextFileInfo.file;
+                isLastFile = nextFileInfo.isLastFile;
             }
             }
 
 
             Logger.LogInformation("Live Stream ended.");
             Logger.LogInformation("Live Stream ended.");
@@ -180,19 +187,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
         private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
         private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
         {
         {
-            using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
+            using (var inputStream = GetInputStream(path, allowAsync))
             {
             {
                 if (seekFile)
                 if (seekFile)
                 {
                 {
                     TrySeek(inputStream, -20000);
                     TrySeek(inputStream, -20000);
                 }
                 }
 
 
-                await ApplicationHost.StreamHelper.CopyToAsync(inputStream, stream, 81920, emptyReadLimit, cancellationToken).ConfigureAwait(false);
+                await StreamHelper.CopyToAsync(
+                    inputStream,
+                    stream,
+                    StreamDefaults.DefaultCopyToBufferSize,
+                    emptyReadLimit,
+                    cancellationToken).ConfigureAwait(false);
             }
             }
         }
         }
 
 
-        protected virtual int EmptyReadLimit => 1000;
-
         private void TrySeek(FileStream stream, long offset)
         private void TrySeek(FileStream stream, long offset)
         {
         {
             if (!stream.CanSeek)
             if (!stream.CanSeek)

+ 15 - 4
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -28,14 +28,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
         private readonly INetworkManager _networkManager;
         private readonly INetworkManager _networkManager;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaSourceManager _mediaSourceManager;
-
-        public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
+        private readonly IStreamHelper _streamHelper;
+
+        public M3UTunerHost(
+            IServerConfigurationManager config,
+            IMediaSourceManager mediaSourceManager,
+            ILogger logger,
+            IJsonSerializer jsonSerializer,
+            IFileSystem fileSystem,
+            IHttpClient httpClient,
+            IServerApplicationHost appHost,
+            INetworkManager networkManager,
+            IStreamHelper streamHelper)
             : base(config, logger, jsonSerializer, fileSystem)
             : base(config, logger, jsonSerializer, fileSystem)
         {
         {
             _httpClient = httpClient;
             _httpClient = httpClient;
             _appHost = appHost;
             _appHost = appHost;
             _networkManager = networkManager;
             _networkManager = networkManager;
             _mediaSourceManager = mediaSourceManager;
             _mediaSourceManager = mediaSourceManager;
+            _streamHelper = streamHelper;
         }
         }
 
 
         public override string Type => "m3u";
         public override string Type => "m3u";
@@ -103,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
                 if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
                 if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
                 {
                 {
-                    return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
+                    return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper);
                 }
                 }
             }
             }
 
 
-            return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths);
+            return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper);
         }
         }
 
 
         public async Task Validate(TunerHostInfo info)
         public async Task Validate(TunerHostInfo info)

+ 18 - 3
Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs

@@ -19,8 +19,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         private readonly IHttpClient _httpClient;
         private readonly IHttpClient _httpClient;
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
 
 
-        public SharedHttpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
-            : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
+        public SharedHttpStream(
+            MediaSourceInfo mediaSource,
+            TunerHostInfo tunerHostInfo,
+            string originalStreamId,
+            IFileSystem fileSystem,
+            IHttpClient httpClient,
+            ILogger logger,
+            IServerApplicationPaths appPaths,
+            IServerApplicationHost appHost,
+            IStreamHelper streamHelper)
+            : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper)
         {
         {
             _httpClient = httpClient;
             _httpClient = httpClient;
             _appHost = appHost;
             _appHost = appHost;
@@ -118,7 +127,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
                     using (var stream = response.Content)
                     using (var stream = response.Content)
                     using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
                     using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
                     {
                     {
-                        await ApplicationHost.StreamHelper.CopyToAsync(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false);
+                        await StreamHelper.CopyToAsync(
+                            stream,
+                            fileStream,
+                            StreamDefaults.DefaultFileStreamBufferSize,
+                            () => Resolve(openTaskCompletionSource),
+                            cancellationToken).ConfigureAwait(false);
                     }
                     }
                 }
                 }
                 catch (OperationCanceledException)
                 catch (OperationCanceledException)
@@ -128,6 +142,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
                 {
                 {
                     Logger.LogError(ex, "Error copying live stream.");
                     Logger.LogError(ex, "Error copying live stream.");
                 }
                 }
+
                 EnableStreamSharing = false;
                 EnableStreamSharing = false;
                 await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
                 await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
             });
             });

+ 1 - 1
MediaBrowser.Model/IO/StreamDefaults.cs

@@ -13,6 +13,6 @@ namespace MediaBrowser.Model.IO
         /// <summary>
         /// <summary>
         /// The default file stream buffer size
         /// The default file stream buffer size
         /// </summary>
         /// </summary>
-        public const int DefaultFileStreamBufferSize = 81920;
+        public const int DefaultFileStreamBufferSize = 4096;
     }
     }
 }
 }