aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj2
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs2
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs1
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj2
-rw-r--r--src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj2
-rw-r--r--src/Jellyfin.Drawing.Skia/SkiaEncoder.cs100
-rw-r--r--src/Jellyfin.Drawing/ImageProcessor.cs1
-rw-r--r--src/Jellyfin.Drawing/Jellyfin.Drawing.csproj2
-rw-r--r--src/Jellyfin.Extensions/AlphanumericComparator.cs112
-rw-r--r--src/Jellyfin.Extensions/Jellyfin.Extensions.csproj2
-rw-r--r--src/Jellyfin.Extensions/StringExtensions.cs2
-rw-r--r--src/Jellyfin.LiveTv/IO/EncodedRecorder.cs7
-rw-r--r--src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj3
-rw-r--r--src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs17
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj5
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj6
-rw-r--r--src/Jellyfin.Networking/Jellyfin.Networking.csproj2
-rw-r--r--src/Jellyfin.Networking/Manager/NetworkManager.cs84
18 files changed, 143 insertions, 209 deletions
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj
index 28c4972d21..0b29a71cbd 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs
index 7bcc7eeca4..76ffa5a9ea 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/OptimisticLockBehavior.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CA1873
+
using System;
using System.Data.Common;
using System.Linq;
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs
index 2d6bc69028..404292e8eb 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Locking/PessimisticLockBehavior.cs
@@ -1,5 +1,6 @@
#pragma warning disable MT1013 // Releasing lock without guarantee of execution
#pragma warning disable MT1012 // Acquiring lock without guarantee of releasing
+#pragma warning disable CA1873
using System;
using System.Data;
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj
index 03e5fc4958..aeee527016 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj
+++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/Jellyfin.Database.Providers.Sqlite.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index ba402dfe09..f7c20463f0 100644
--- a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- TODO: Remove once we update SkiaSharp > 2.88.5 -->
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index c6eab92ead..3f7ae4d2cd 100644
--- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -24,61 +24,29 @@ public class SkiaEncoder : IImageEncoder
private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private readonly ILogger<SkiaEncoder> _logger;
private readonly IApplicationPaths _appPaths;
- private static readonly SKImageFilter _imageFilter;
- private static readonly SKTypeface[] _typefaces;
+ private static readonly SKTypeface?[] _typefaces = InitializeTypefaces();
+ private static readonly SKImageFilter _imageFilter = SKImageFilter.CreateMatrixConvolution(
+ new SKSizeI(3, 3),
+ [
+ 0, -.1f, 0,
+ -.1f, 1.4f, -.1f,
+ 0, -.1f, 0
+ ],
+ 1f,
+ 0f,
+ new SKPointI(1, 1),
+ SKShaderTileMode.Clamp,
+ true);
/// <summary>
/// The default sampling options, equivalent to old high quality filter settings when upscaling.
/// </summary>
- public static readonly SKSamplingOptions UpscaleSamplingOptions;
+ public static readonly SKSamplingOptions UpscaleSamplingOptions = new SKSamplingOptions(SKCubicResampler.Mitchell);
/// <summary>
/// The sampling options, used for downscaling images, equivalent to old high quality filter settings when not upscaling.
/// </summary>
- public static readonly SKSamplingOptions DefaultSamplingOptions;
-
-#pragma warning disable CA1810
- static SkiaEncoder()
-#pragma warning restore CA1810
- {
- var kernel = new[]
- {
- 0, -.1f, 0,
- -.1f, 1.4f, -.1f,
- 0, -.1f, 0,
- };
-
- var kernelSize = new SKSizeI(3, 3);
- var kernelOffset = new SKPointI(1, 1);
- _imageFilter = SKImageFilter.CreateMatrixConvolution(
- kernelSize,
- kernel,
- 1f,
- 0f,
- kernelOffset,
- SKShaderTileMode.Clamp,
- true);
-
- // Initialize the list of typefaces
- // We have to statically build a list of typefaces because MatchCharacter only accepts a single character or code point
- // But in reality a human-readable character (grapheme cluster) could be multiple code points. For example, 🚵🏻‍♀️ is a single emoji but 5 code points (U+1F6B5 + U+1F3FB + U+200D + U+2640 + U+FE0F)
- _typefaces =
- [
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, '鸡'), // CJK Simplified Chinese
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, '雞'), // CJK Traditional Chinese
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ノ'), // CJK Japanese
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, '각'), // CJK Korean
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 128169), // Emojis, 128169 is the 💩emoji
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ז'), // Hebrew
- SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'ي'), // Arabic
- SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright) // Default font
- ];
-
- // use cubic for upscaling
- UpscaleSamplingOptions = new SKSamplingOptions(SKCubicResampler.Mitchell);
- // use bilinear for everything else
- DefaultSamplingOptions = new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear);
- }
+ public static readonly SKSamplingOptions DefaultSamplingOptions = new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear);
/// <summary>
/// Initializes a new instance of the <see cref="SkiaEncoder"/> class.
@@ -132,7 +100,7 @@ public class SkiaEncoder : IImageEncoder
/// <summary>
/// Gets the default typeface to use.
/// </summary>
- public static SKTypeface DefaultTypeFace => _typefaces.Last();
+ public static SKTypeface? DefaultTypeFace => _typefaces.Last();
/// <summary>
/// Check if the native lib is available.
@@ -153,6 +121,40 @@ public class SkiaEncoder : IImageEncoder
}
/// <summary>
+ /// Initialize the list of typefaces
+ /// We have to statically build a list of typefaces because MatchCharacter only accepts a single character or code point
+ /// But in reality a human-readable character (grapheme cluster) could be multiple code points. For example, 🚵🏻‍♀️ is a single emoji but 5 code points (U+1F6B5 + U+1F3FB + U+200D + U+2640 + U+FE0F).
+ /// </summary>
+ /// <returns>The list of typefaces.</returns>
+ private static SKTypeface?[] InitializeTypefaces()
+ {
+ int[] chars = [
+ '鸡', // CJK Simplified Chinese
+ '雞', // CJK Traditional Chinese
+ 'ノ', // CJK Japanese
+ '각', // CJK Korean
+ 128169, // Emojis, 128169 is the Pile of Poo (💩) emoji
+ 'ז', // Hebrew
+ 'ي' // Arabic
+ ];
+ var fonts = new List<SKTypeface>(chars.Length + 1);
+ foreach (var ch in chars)
+ {
+ var font = SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, ch);
+ if (font is not null)
+ {
+ fonts.Add(font);
+ }
+ }
+
+ // Default font
+ fonts.Add(SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright)
+ ?? SKFontManager.Default.MatchCharacter(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright, null, 'a'));
+
+ return fonts.ToArray();
+ }
+
+ /// <summary>
/// Convert a <see cref="ImageFormat"/> to a <see cref="SKEncodedImageFormat"/>.
/// </summary>
/// <param name="selectedFormat">The format to convert.</param>
@@ -809,7 +811,7 @@ public class SkiaEncoder : IImageEncoder
{
foreach (var typeface in _typefaces)
{
- if (typeface.ContainsGlyphs(c))
+ if (typeface is not null && typeface.ContainsGlyphs(c))
{
return typeface;
}
diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs
index 46e5213a8c..6ffb022842 100644
--- a/src/Jellyfin.Drawing/ImageProcessor.cs
+++ b/src/Jellyfin.Drawing/ImageProcessor.cs
@@ -85,7 +85,6 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
"jpeg",
"jpg",
"png",
- "aiff",
"cr2",
"crw",
"nef",
diff --git a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
index 5f4b3fe8d4..a442f74576 100644
--- a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
+++ b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.Extensions/AlphanumericComparator.cs b/src/Jellyfin.Extensions/AlphanumericComparator.cs
deleted file mode 100644
index 299e2f94ae..0000000000
--- a/src/Jellyfin.Extensions/AlphanumericComparator.cs
+++ /dev/null
@@ -1,112 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Jellyfin.Extensions
-{
- /// <summary>
- /// Alphanumeric <see cref="IComparer{T}" />.
- /// </summary>
- public class AlphanumericComparator : IComparer<string?>
- {
- /// <summary>
- /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.
- /// </summary>
- /// <param name="s1">The first object to compare.</param>
- /// <param name="s2">The second object to compare.</param>
- /// <returns>A signed integer that indicates the relative values of <c>x</c> and <c>y</c>.</returns>
- public static int CompareValues(string? s1, string? s2)
- {
- if (s1 is null && s2 is null)
- {
- return 0;
- }
-
- if (s1 is null)
- {
- return -1;
- }
-
- if (s2 is null)
- {
- return 1;
- }
-
- int len1 = s1.Length;
- int len2 = s2.Length;
-
- // Early return for empty strings
- if (len1 == 0 && len2 == 0)
- {
- return 0;
- }
-
- if (len1 == 0)
- {
- return -1;
- }
-
- if (len2 == 0)
- {
- return 1;
- }
-
- int pos1 = 0;
- int pos2 = 0;
-
- do
- {
- int start1 = pos1;
- int start2 = pos2;
-
- bool isNum1 = char.IsDigit(s1[pos1++]);
- bool isNum2 = char.IsDigit(s2[pos2++]);
-
- while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1)
- {
- pos1++;
- }
-
- while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2)
- {
- pos2++;
- }
-
- var span1 = s1.AsSpan(start1, pos1 - start1);
- var span2 = s2.AsSpan(start2, pos2 - start2);
-
- if (isNum1 && isNum2)
- {
- // Trim leading zeros so we can compare the length
- // of the strings to find the largest number
- span1 = span1.TrimStart('0');
- span2 = span2.TrimStart('0');
- var span1Len = span1.Length;
- var span2Len = span2.Length;
- if (span1Len < span2Len)
- {
- return -1;
- }
-
- if (span1Len > span2Len)
- {
- return 1;
- }
- }
-
- int result = span1.CompareTo(span2, StringComparison.InvariantCulture);
- if (result != 0)
- {
- return result;
- }
- } while (pos1 < len1 && pos2 < len2);
-
- return len1 - len2;
- }
-
- /// <inheritdoc />
- public int Compare(string? x, string? y)
- {
- return CompareValues(x, y);
- }
- }
-}
diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
index f52fd014da..9a7cf4aabe 100644
--- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
+++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
diff --git a/src/Jellyfin.Extensions/StringExtensions.cs b/src/Jellyfin.Extensions/StringExtensions.cs
index 60df47113a..c7e8319f59 100644
--- a/src/Jellyfin.Extensions/StringExtensions.cs
+++ b/src/Jellyfin.Extensions/StringExtensions.cs
@@ -131,7 +131,7 @@ namespace Jellyfin.Extensions
/// </summary>
/// <param name="values">The enumerable of strings to trim.</param>
/// <returns>The enumeration of trimmed strings.</returns>
- public static IEnumerable<string> Trimmed(this IEnumerable<string> values)
+ public static IEnumerable<string> Trimmed(this IEnumerable<string?> values)
{
return values.Select(i => (i ?? string.Empty).Trim());
}
diff --git a/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs b/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs
index be7ff52977..d877a0d124 100644
--- a/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs
+++ b/src/Jellyfin.LiveTv/IO/EncodedRecorder.cs
@@ -156,6 +156,13 @@ namespace Jellyfin.LiveTv.IO
if (mediaSource.ReadAtNativeFramerate)
{
inputModifier += " -re";
+
+ // Set a larger catchup value to revert to the old behavior,
+ // otherwise, remuxing might stall due to this new option
+ if (_mediaEncoder.EncoderVersion >= new Version(8, 0))
+ {
+ inputModifier += " -readrate_catchup 100";
+ }
}
if (mediaSource.RequiresLooping)
diff --git a/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj b/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj
index f04c02504c..575441de92 100644
--- a/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj
+++ b/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -13,7 +13,6 @@
<ItemGroup>
<PackageReference Include="AsyncKeyedLock" />
<PackageReference Include="Jellyfin.XmlTv" />
- <PackageReference Include="System.Linq.Async" />
</ItemGroup>
<ItemGroup>
diff --git a/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs b/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
index 2270758454..5da7762f6f 100644
--- a/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
@@ -93,6 +93,13 @@ namespace Jellyfin.LiveTv.TunerHosts
}
else if (!string.IsNullOrWhiteSpace(extInf) && !trimmedLine.StartsWith('#'))
{
+ if (!IsValidChannelUrl(trimmedLine))
+ {
+ _logger.LogWarning("Skipping M3U channel entry with non-HTTP path: {Path}", trimmedLine);
+ extInf = string.Empty;
+ continue;
+ }
+
var channel = GetChannelInfo(extInf, tunerHostId, trimmedLine);
channel.Id = channelIdPrefix + trimmedLine.GetMD5().ToString("N", CultureInfo.InvariantCulture);
@@ -247,6 +254,16 @@ namespace Jellyfin.LiveTv.TunerHosts
return numberString;
}
+ private static bool IsValidChannelUrl(string url)
+ {
+ return Uri.TryCreate(url, UriKind.Absolute, out var uri)
+ && (string.Equals(uri.Scheme, "http", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(uri.Scheme, "rtsp", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(uri.Scheme, "rtp", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(uri.Scheme, "udp", StringComparison.OrdinalIgnoreCase));
+ }
+
private static bool IsValidChannelNumber(string numberString)
{
if (string.IsNullOrWhiteSpace(numberString)
diff --git a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
index 80b5aa84e4..902f513768 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
+++ b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -12,9 +12,6 @@
<ProjectReference Include="../Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" />
</ItemGroup>
- <ItemGroup>
- <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
- </ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
index cc8d942ebb..5e7e2090cd 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
+++ b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
@@ -23,10 +23,6 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
- </ItemGroup>
-
- <ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Jellyfin.MediaEncoding.Keyframes.Tests</_Parameter1>
</AssemblyAttribute>
diff --git a/src/Jellyfin.Networking/Jellyfin.Networking.csproj b/src/Jellyfin.Networking/Jellyfin.Networking.csproj
index 1a146549de..36b9581a7b 100644
--- a/src/Jellyfin.Networking/Jellyfin.Networking.csproj
+++ b/src/Jellyfin.Networking/Jellyfin.Networking.csproj
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs
index 126d9f15cf..6a8a91fa51 100644
--- a/src/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -16,7 +16,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
-using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
namespace Jellyfin.Networking.Manager;
@@ -115,7 +114,7 @@ public class NetworkManager : INetworkManager, IDisposable
public static string MockNetworkSettings { get; set; } = string.Empty;
/// <summary>
- /// Gets a value indicating whether IP4 is enabled.
+ /// Gets a value indicating whether IPv4 is enabled.
/// </summary>
public bool IsIPv4Enabled => _configurationManager.GetNetworkConfiguration().EnableIPv4;
@@ -341,12 +340,12 @@ public class NetworkManager : INetworkManager, IDisposable
}
else
{
- _lanSubnets = lanSubnets;
+ _lanSubnets = lanSubnets.Select(x => x.Subnet).ToArray();
}
_excludedSubnets = NetworkUtils.TryParseToSubnets(subnets, out var excludedSubnets, true)
- ? excludedSubnets
- : new List<IPNetwork>();
+ ? excludedSubnets.Select(x => x.Subnet).ToArray()
+ : Array.Empty<IPNetwork>();
}
}
@@ -362,7 +361,7 @@ public class NetworkManager : INetworkManager, IDisposable
}
/// <summary>
- /// Filteres a list of bind addresses and exclusions on available interfaces.
+ /// Filters a list of bind addresses and exclusions on available interfaces.
/// </summary>
/// <param name="config">The network config to be filtered by.</param>
/// <param name="interfaces">A list of possible interfaces to be filtered.</param>
@@ -376,7 +375,7 @@ public class NetworkManager : INetworkManager, IDisposable
if (localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]))
{
var bindAddresses = localNetworkAddresses.Select(p => NetworkUtils.TryParseToSubnet(p, out var network)
- ? network.Prefix
+ ? network.Address
: (interfaces.Where(x => x.Name.Equals(p, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Address)
.FirstOrDefault() ?? IPAddress.None))
@@ -445,7 +444,7 @@ public class NetworkManager : INetworkManager, IDisposable
var remoteFilteredSubnets = remoteIPFilter.Where(x => x.Contains('/', StringComparison.OrdinalIgnoreCase)).ToArray();
if (NetworkUtils.TryParseToSubnets(remoteFilteredSubnets, out var remoteAddressFilterResult, false))
{
- remoteAddressFilter = remoteAddressFilterResult.ToList();
+ remoteAddressFilter = remoteAddressFilterResult.Select(x => x.Subnet).ToList();
}
// Parse everything else as an IP and construct subnet with a single IP
@@ -545,7 +544,7 @@ public class NetworkManager : INetworkManager, IDisposable
{
foreach (var lan in _lanSubnets)
{
- var lanPrefix = lan.Prefix;
+ var lanPrefix = lan.BaseAddress;
publishedServerUrls.Add(
new PublishedServerUriOverride(
new IPData(lanPrefix, new IPNetwork(lanPrefix, lan.PrefixLength)),
@@ -554,12 +553,11 @@ public class NetworkManager : INetworkManager, IDisposable
false));
}
}
- else if (NetworkUtils.TryParseToSubnet(identifier, out var result) && result is not null)
+ else if (NetworkUtils.TryParseToSubnet(identifier, out var result))
{
- var data = new IPData(result.Prefix, result);
publishedServerUrls.Add(
new PublishedServerUriOverride(
- data,
+ result,
replacement,
true,
true));
@@ -621,16 +619,12 @@ public class NetworkManager : INetworkManager, IDisposable
foreach (var details in interfaceList)
{
var parts = details.Split(',');
- if (NetworkUtils.TryParseToSubnet(parts[0], out var subnet))
+ if (NetworkUtils.TryParseToSubnet(parts[0], out var data))
{
- var address = subnet.Prefix;
- var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
- if (address.AddressFamily == AddressFamily.InterNetwork || address.AddressFamily == AddressFamily.InterNetworkV6)
+ data.Index = int.Parse(parts[1], CultureInfo.InvariantCulture);
+ if (data.AddressFamily == AddressFamily.InterNetwork || data.AddressFamily == AddressFamily.InterNetworkV6)
{
- var data = new IPData(address, subnet, parts[2])
- {
- Index = index
- };
+ data.Name = parts[2];
interfaces.Add(data);
}
}
@@ -753,12 +747,13 @@ public class NetworkManager : INetworkManager, IDisposable
/// <inheritdoc/>
public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
{
- return NetworkManager.GetAllBindInterfaces(individualInterfaces, _configurationManager, _interfaces, IsIPv4Enabled, IsIPv6Enabled);
+ return NetworkManager.GetAllBindInterfaces(_logger, individualInterfaces, _configurationManager, _interfaces, IsIPv4Enabled, IsIPv6Enabled);
}
/// <summary>
/// Reads the jellyfin configuration of the configuration manager and produces a list of interfaces that should be bound.
/// </summary>
+ /// <param name="logger">Logger to use for messages.</param>
/// <param name="individualInterfaces">Defines that only known interfaces should be used.</param>
/// <param name="configurationManager">The ConfigurationManager.</param>
/// <param name="knownInterfaces">The known interfaces that gets returned if possible or instructed.</param>
@@ -766,6 +761,7 @@ public class NetworkManager : INetworkManager, IDisposable
/// <param name="readIpv6">Include IPV6 type interfaces.</param>
/// <returns>A list of ip address of which jellyfin should bind to.</returns>
public static IReadOnlyList<IPData> GetAllBindInterfaces(
+ ILogger<NetworkManager> logger,
bool individualInterfaces,
IConfigurationManager configurationManager,
IReadOnlyList<IPData> knownInterfaces,
@@ -779,6 +775,13 @@ public class NetworkManager : INetworkManager, IDisposable
return knownInterfaces;
}
+ // TODO: remove when upgrade to dotnet 11 is done
+ if (readIpv6 && !Socket.OSSupportsIPv6)
+ {
+ logger.LogWarning("IPv6 Unsupported by OS, not listening on IPv6");
+ readIpv6 = false;
+ }
+
// No bind address and no exclusions, so listen on all interfaces.
var result = new List<IPData>();
if (readIpv4 && readIpv6)
@@ -875,7 +878,20 @@ public class NetworkManager : INetworkManager, IDisposable
if (availableInterfaces.Count == 0)
{
// There isn't any others, so we'll use the loopback.
- result = IsIPv4Enabled && !IsIPv6Enabled ? "127.0.0.1" : "::1";
+ // Prefer loopback address matching the source's address family
+ if (source is not null && source.AddressFamily == AddressFamily.InterNetwork && IsIPv4Enabled)
+ {
+ result = "127.0.0.1";
+ }
+ else if (source is not null && source.AddressFamily == AddressFamily.InterNetworkV6 && IsIPv6Enabled)
+ {
+ result = "::1";
+ }
+ else
+ {
+ result = IsIPv4Enabled ? "127.0.0.1" : "::1";
+ }
+
_logger.LogWarning("{Source}: Only loopback {Result} returned, using that as bind address.", source, result);
return result;
}
@@ -900,9 +916,19 @@ public class NetworkManager : INetworkManager, IDisposable
}
}
- // Fallback to first available interface
+ // Fallback to an interface matching the source's address family, or first available
+ var preferredInterface = availableInterfaces
+ .FirstOrDefault(x => x.Address.AddressFamily == source.AddressFamily);
+
+ if (preferredInterface is not null)
+ {
+ result = NetworkUtils.FormatIPString(preferredInterface.Address);
+ _logger.LogDebug("{Source}: No matching subnet found, using interface with matching address family: {Result}", source, result);
+ return result;
+ }
+
result = NetworkUtils.FormatIPString(availableInterfaces[0].Address);
- _logger.LogDebug("{Source}: No matching interfaces found, using preferred interface as bind address: {Result}", source, result);
+ _logger.LogDebug("{Source}: No matching interfaces found, using first available interface as bind address: {Result}", source, result);
return result;
}
@@ -920,7 +946,7 @@ public class NetworkManager : INetworkManager, IDisposable
{
if (NetworkUtils.TryParseToSubnet(address, out var subnet))
{
- return IsInLocalNetwork(subnet.Prefix);
+ return IsInLocalNetwork(subnet.Address);
}
return NetworkUtils.TryParseHost(address, out var addresses, IsIPv4Enabled, IsIPv6Enabled)
@@ -1171,13 +1197,13 @@ public class NetworkManager : INetworkManager, IDisposable
var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
if (_logger.IsEnabled(logLevel))
{
- _logger.Log(logLevel, "Defined LAN subnets: {Subnets}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
- _logger.Log(logLevel, "Defined LAN exclusions: {Subnets}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
- _logger.Log(logLevel, "Used LAN subnets: {Subnets}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
+ _logger.Log(logLevel, "Defined LAN subnets: {Subnets}", _lanSubnets.Select(s => s.BaseAddress + "/" + s.PrefixLength));
+ _logger.Log(logLevel, "Defined LAN exclusions: {Subnets}", _excludedSubnets.Select(s => s.BaseAddress + "/" + s.PrefixLength));
+ _logger.Log(logLevel, "Used LAN subnets: {Subnets}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.BaseAddress + "/" + s.PrefixLength));
_logger.Log(logLevel, "Filtered interface addresses: {Addresses}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
_logger.Log(logLevel, "Bind Addresses {Addresses}", GetAllBindInterfaces(false).OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
_logger.Log(logLevel, "Remote IP filter is {Type}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowlist");
- _logger.Log(logLevel, "Filtered subnets: {Subnets}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength));
+ _logger.Log(logLevel, "Filtered subnets: {Subnets}", _remoteAddressFilter.Select(s => s.BaseAddress + "/" + s.PrefixLength));
}
}
}