< 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: 92
Coverable lines: 92
Total lines: 219
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 4
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/13/2026 - 12:10:41 AM Line coverage: 0% (0/14) Total lines: 2194/19/2026 - 12:14:27 AM Line coverage: 0% (0/92) Branch coverage: 0% (0/4) Total lines: 219 4/19/2026 - 12:14:27 AM Line coverage: 0% (0/92) Branch coverage: 0% (0/4) Total lines: 219

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetUdpPortFromRange(...)100%210%
Open()0%2040%
StartStreaming()100%210%
CopyTo()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        {
 075            LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
 76
 077            var mediaSource = OriginalMediaSource;
 78
 079            var uri = new Uri(mediaSource.Path);
 80            // Temporary code to reduce PR size. This will be updated by a future network pr.
 081            var localPort = GetUdpPortFromRange((49152, 65535));
 82
 083            Directory.CreateDirectory(Path.GetDirectoryName(TempFilePath));
 84
 085            Logger.LogInformation("Opening HDHR UDP Live stream from {Host}", uri.Host);
 86
 087            var remoteAddress = IPAddress.Parse(uri.Host);
 88            IPAddress localAddress;
 089            using (var tcpClient = new TcpClient())
 90            {
 91                try
 92                {
 093                    await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort, openCancellationToken).C
 094                    localAddress = ((IPEndPoint)tcpClient.Client.LocalEndPoint).Address;
 095                    tcpClient.Close();
 096                }
 097                catch (Exception ex)
 98                {
 099                    Logger.LogError(ex, "Unable to determine local ip address for Legacy HDHomerun stream.");
 0100                    return;
 101                }
 0102            }
 103
 0104            if (localAddress.IsIPv4MappedToIPv6)
 105            {
 0106                localAddress = localAddress.MapToIPv4();
 107            }
 108
 0109            var udpClient = new UdpClient(localPort, AddressFamily.InterNetwork);
 0110            var hdHomerunManager = new HdHomerunManager();
 111
 112            try
 113            {
 114                // send url to start streaming
 0115                await hdHomerunManager.StartStreaming(
 0116                    remoteAddress,
 0117                    localAddress,
 0118                    localPort,
 0119                    _channelCommands,
 0120                    _numTuners,
 0121                    openCancellationToken).ConfigureAwait(false);
 0122            }
 0123            catch (Exception ex)
 124            {
 0125                using (udpClient)
 0126                using (hdHomerunManager)
 127                {
 0128                    if (ex is not OperationCanceledException)
 129                    {
 0130                        Logger.LogError(ex, "Error opening live stream:");
 131                    }
 132
 0133                    throw;
 134                }
 135            }
 136
 0137            var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously
 138
 0139            _ = StartStreaming(
 0140                udpClient,
 0141                hdHomerunManager,
 0142                remoteAddress,
 0143                taskCompletionSource,
 0144                LiveStreamCancellationTokenSource.Token);
 145
 146            // OpenedMediaSource.Protocol = MediaProtocol.File;
 147            // OpenedMediaSource.Path = tempFile;
 148            // OpenedMediaSource.ReadAtNativeFramerate = true;
 149
 0150            MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"
 0151            MediaSource.Protocol = MediaProtocol.Http;
 152            // OpenedMediaSource.SupportsDirectPlay = false;
 153            // OpenedMediaSource.SupportsDirectStream = true;
 154            // OpenedMediaSource.SupportsTranscoding = true;
 155
 156            // await Task.Delay(5000).ConfigureAwait(false);
 0157            await taskCompletionSource.Task.ConfigureAwait(false);
 0158        }
 159
 160        private async Task StartStreaming(UdpClient udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddres
 161        {
 0162            using (udpClient)
 0163            using (hdHomerunManager)
 164            {
 165                try
 166                {
 0167                    await CopyTo(udpClient, TempFilePath, openTaskCompletionSource, cancellationToken).ConfigureAwait(fa
 0168                }
 0169                catch (Exception ex) when (ex is OperationCanceledException || ex is TimeoutException)
 170                {
 0171                    Logger.LogInformation("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
 0172                    openTaskCompletionSource.TrySetException(ex);
 0173                }
 0174                catch (Exception ex)
 175                {
 0176                    Logger.LogError(ex, "Error opening live stream:");
 0177                    openTaskCompletionSource.TrySetException(ex);
 0178                }
 179
 0180                EnableStreamSharing = false;
 0181            }
 182
 0183            await DeleteTempFiles(TempFilePath).ConfigureAwait(false);
 0184        }
 185
 186        private async Task CopyTo(UdpClient udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource,
 187        {
 0188            var resolved = false;
 189
 0190            var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read);
 0191            await using (fileStream.ConfigureAwait(false))
 192            {
 0193                while (true)
 194                {
 0195                    cancellationToken.ThrowIfCancellationRequested();
 0196                    var res = await udpClient.ReceiveAsync(cancellationToken)
 0197                        .AsTask()
 0198                        .WaitAsync(TimeSpan.FromMilliseconds(30000), CancellationToken.None)
 0199                        .ConfigureAwait(false);
 0200                    var buffer = res.Buffer;
 201
 0202                    var read = buffer.Length - RtpHeaderBytes;
 203
 0204                    if (read > 0)
 205                    {
 0206                        await fileStream.WriteAsync(buffer.AsMemory(RtpHeaderBytes, read), cancellationToken).ConfigureA
 207                    }
 208
 0209                    if (!resolved)
 210                    {
 0211                        resolved = true;
 0212                        DateOpened = DateTime.UtcNow;
 0213                        openTaskCompletionSource.TrySetResult(true);
 214                    }
 215                }
 216            }
 217        }
 218    }
 219}