< Summary - Jellyfin

Information
Class: Jellyfin.LiveTv.TunerHosts.HdHomerun.HdHomerunUdpStream
Assembly: Jellyfin.LiveTv
File(s): /srv/git/jellyfin/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 14
Coverable lines: 14
Total lines: 219
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetUdpPortFromRange(...)100%210%

File(s)

/srv/git/jellyfin/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CA1711
 4#pragma warning disable CS1591
 5
 6using System;
 7using System.IO;
 8using System.Linq;
 9using System.Net;
 10using System.Net.NetworkInformation;
 11using System.Net.Sockets;
 12using System.Threading;
 13using System.Threading.Tasks;
 14using MediaBrowser.Common.Configuration;
 15using MediaBrowser.Controller;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Model.Dto;
 18using MediaBrowser.Model.IO;
 19using MediaBrowser.Model.LiveTv;
 20using MediaBrowser.Model.MediaInfo;
 21using Microsoft.Extensions.Logging;
 22
 23namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
 24{
 25    public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
 26    {
 27        private const int RtpHeaderBytes = 12;
 28
 29        private readonly IServerApplicationHost _appHost;
 30        private readonly IHdHomerunChannelCommands _channelCommands;
 31        private readonly int _numTuners;
 32
 33        public HdHomerunUdpStream(
 34            MediaSourceInfo mediaSource,
 35            TunerHostInfo tunerHostInfo,
 36            string originalStreamId,
 37            IHdHomerunChannelCommands channelCommands,
 38            int numTuners,
 39            IFileSystem fileSystem,
 40            ILogger logger,
 41            IConfigurationManager configurationManager,
 42            IServerApplicationHost appHost,
 43            IStreamHelper streamHelper)
 044            : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
 45        {
 046            _appHost = appHost;
 047            OriginalStreamId = originalStreamId;
 048            _channelCommands = channelCommands;
 049            _numTuners = numTuners;
 050            EnableStreamSharing = true;
 051        }
 52
 53        /// <summary>
 54        /// Returns an unused UDP port number in the range specified.
 55        /// Temporarily placed here until future network PR merged.
 56        /// </summary>
 57        /// <param name="range">Upper and Lower boundary of ports to select.</param>
 58        /// <returns>System.Int32.</returns>
 59        private static int GetUdpPortFromRange((int Min, int Max) range)
 60        {
 061            var properties = IPGlobalProperties.GetIPGlobalProperties();
 62
 63            // Get active udp listeners.
 064            var udpListenerPorts = properties.GetActiveUdpListeners()
 065                        .Where(n => n.Port >= range.Min && n.Port <= range.Max)
 066                        .Select(n => n.Port);
 67
 068            return Enumerable
 069                .Range(range.Min, range.Max)
 070                .FirstOrDefault(i => !udpListenerPorts.Contains(i));
 71        }
 72
 73        public override async Task Open(CancellationToken openCancellationToken)
 74        {
 75            LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
 76
 77            var mediaSource = OriginalMediaSource;
 78
 79            var uri = new Uri(mediaSource.Path);
 80            // Temporary code to reduce PR size. This will be updated by a future network pr.
 81            var localPort = GetUdpPortFromRange((49152, 65535));
 82
 83            Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
 84
 85            Logger.LogInformation("Opening HDHR UDP Live stream from {Host}", uri.Host);
 86
 87            var remoteAddress = IPAddress.Parse(uri.Host);
 88            IPAddress localAddress;
 89            using (var tcpClient = new TcpClient())
 90            {
 91                try
 92                {
 93                    await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort, openCancellationToken).C
 94                    localAddress = ((IPEndPoint)tcpClient.Client.LocalEndPoint).Address;
 95                    tcpClient.Close();
 96                }
 97                catch (Exception ex)
 98                {
 99                    Logger.LogError(ex, "Unable to determine local ip address for Legacy HDHomerun stream.");
 100                    return;
 101                }
 102            }
 103
 104            if (localAddress.IsIPv4MappedToIPv6)
 105            {
 106                localAddress = localAddress.MapToIPv4();
 107            }
 108
 109            var udpClient = new UdpClient(localPort, AddressFamily.InterNetwork);
 110            var hdHomerunManager = new HdHomerunManager();
 111
 112            try
 113            {
 114                // send url to start streaming
 115                await hdHomerunManager.StartStreaming(
 116                    remoteAddress,
 117                    localAddress,
 118                    localPort,
 119                    _channelCommands,
 120                    _numTuners,
 121                    openCancellationToken).ConfigureAwait(false);
 122            }
 123            catch (Exception ex)
 124            {
 125                using (udpClient)
 126                using (hdHomerunManager)
 127                {
 128                    if (ex is not OperationCanceledException)
 129                    {
 130                        Logger.LogError(ex, "Error opening live stream:");
 131                    }
 132
 133                    throw;
 134                }
 135            }
 136
 137            var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously
 138
 139            _ = StartStreaming(
 140                udpClient,
 141                hdHomerunManager,
 142                remoteAddress,
 143                taskCompletionSource,
 144                LiveStreamCancellationTokenSource.Token);
 145
 146            // OpenedMediaSource.Protocol = MediaProtocol.File;
 147            // OpenedMediaSource.Path = tempFile;
 148            // OpenedMediaSource.ReadAtNativeFramerate = true;
 149
 150            MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"
 151            MediaSource.Protocol = MediaProtocol.Http;
 152            // OpenedMediaSource.SupportsDirectPlay = false;
 153            // OpenedMediaSource.SupportsDirectStream = true;
 154            // OpenedMediaSource.SupportsTranscoding = true;
 155
 156            // await Task.Delay(5000).ConfigureAwait(false);
 157            await taskCompletionSource.Task.ConfigureAwait(false);
 158        }
 159
 160        private async Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddres
 161        {
 162            using (udpClient)
 163            using (hdHomerunManager)
 164            {
 165                try
 166                {
 167                    await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(fa
 168                }
 169                catch (Exception ex) when (ex is OperationCanceledException || ex is TimeoutException)
 170                {
 171                    Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
 172                    openTaskCompletionSource.TrySetException(ex);
 173                }
 174                catch (Exception ex)
 175                {
 176                    Logger.LogError(ex, "Error opening live stream:");
 177                    openTaskCompletionSource.TrySetException(ex);
 178                }
 179
 180                EnableStreamSharing = false;
 181            }
 182
 183            await DeleteTempFiles(TempFilePath).ConfigureAwait(false);
 184        }
 185
 186        private async Task CopyTo(UdpClient udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource,
 187        {
 188            var resolved = false;
 189
 190            var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
 191            await using (fileStream.ConfigureAwait(false))
 192            {
 193                while (true)
 194                {
 195                    cancellationToken.ThrowIfCancellationRequested();
 196                    var res = await udpClient.ReceiveAsync(cancellationToken)
 197                        .AsTask()
 198                        .WaitAsync(TimeSpan.FromMilliseconds(30000), CancellationToken.None)
 199                        .ConfigureAwait(false);
 200                    var buffer = res.Buffer;
 201
 202                    var read = buffer.Length - RtpHeaderBytes;
 203
 204                    if (read > 0)
 205                    {
 206                        await fileStream.WriteAsync(buffer.AsMemory(RtpHeaderBytes, read), cancellationToken).ConfigureA
 207                    }
 208
 209                    if (!resolved)
 210                    {
 211                        resolved = true;
 212                        DateOpened = DateTime.UtcNow;
 213                        openTaskCompletionSource.TrySetResult(true);
 214                    }
 215                }
 216            }
 217        }
 218    }
 219}