aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNyanmisaka <nst799610810@gmail.com>2024-07-23 15:37:33 +0800
committerGitHub <noreply@github.com>2024-07-23 15:37:33 +0800
commit00088c295445fe2710cae468e1b09f98a32e40a5 (patch)
tree77614fb434409bc2ddf3d7d0b5830339a6374bfb /src
parentdeb36eeedaba2f1421b92d290d85d45bfe48d1f5 (diff)
parent19dca018b2604ff8666cabaf9d0f9c8974572756 (diff)
Merge branch 'master' into fix-hwa-video-rotation
Diffstat (limited to 'src')
-rw-r--r--src/Jellyfin.Drawing.Skia/SkiaEncoder.cs17
-rw-r--r--src/Jellyfin.Extensions/Jellyfin.Extensions.csproj4
-rw-r--r--src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs25
-rw-r--r--src/Jellyfin.Extensions/StreamExtensions.cs7
-rw-r--r--src/Jellyfin.LiveTv/Channels/ChannelManager.cs2
-rw-r--r--src/Jellyfin.LiveTv/Listings/ListingsManager.cs14
-rw-r--r--src/Jellyfin.LiveTv/LiveTvManager.cs2
-rw-r--r--src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs4
-rw-r--r--src/Jellyfin.LiveTv/Timers/ItemDataProvider.cs12
-rw-r--r--src/Jellyfin.LiveTv/Timers/TimerManager.cs2
-rw-r--r--src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs2
-rw-r--r--src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs2
-rw-r--r--src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs44
-rw-r--r--src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs4
-rw-r--r--src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs7
-rw-r--r--src/Jellyfin.Networking/AutoDiscoveryHost.cs69
-rw-r--r--src/Jellyfin.Networking/Manager/NetworkManager.cs57
17 files changed, 139 insertions, 135 deletions
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index a158e5c86..ede93aaa5 100644
--- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -219,7 +219,7 @@ public class SkiaEncoder : IImageEncoder
return path;
}
- var tempPath = Path.Combine(_appPaths.TempDirectory, string.Concat(Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan())));
+ var tempPath = Path.Join(_appPaths.TempDirectory, string.Concat("skia_", Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan())));
var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException($"Provided path ({tempPath}) is not valid.");
Directory.CreateDirectory(directory);
File.Copy(path, tempPath, true);
@@ -263,6 +263,11 @@ public class SkiaEncoder : IImageEncoder
return null;
}
+ if (codec.FrameCount != 0)
+ {
+ throw new ArgumentException("Cannot decode images with multiple frames");
+ }
+
// create the bitmap
var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height, !requiresTransparencyHack);
@@ -554,9 +559,13 @@ public class SkiaEncoder : IImageEncoder
/// <inheritdoc />
public void CreateSplashscreen(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops)
{
- var splashBuilder = new SplashscreenBuilder(this);
- var outputPath = Path.Combine(_appPaths.DataPath, "splashscreen.png");
- splashBuilder.GenerateSplash(posters, backdrops, outputPath);
+ // Only generate the splash screen if we have at least one poster and at least one backdrop/thumbnail.
+ if (posters.Count > 0 && backdrops.Count > 0)
+ {
+ var splashBuilder = new SplashscreenBuilder(this);
+ var outputPath = Path.Combine(_appPaths.DataPath, "splashscreen.png");
+ splashBuilder.GenerateSplash(posters, backdrops, outputPath);
+ }
}
/// <inheritdoc />
diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
index 98b567e30..f786cc3b4 100644
--- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
+++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
@@ -8,12 +8,14 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
+ <!-- ICU4N.Transliterator only has prerelease versions -->
+ <NoWarn>NU5104</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId>
- <VersionPrefix>10.9.0</VersionPrefix>
+ <VersionPrefix>10.10.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs
deleted file mode 100644
index cd582ced6..000000000
--- a/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Jellyfin.Extensions.Json.Converters
-{
- /// <summary>
- /// Converts an object to a lowercase string.
- /// </summary>
- /// <typeparam name="T">The object type.</typeparam>
- public class JsonLowerCaseConverter<T> : JsonConverter<T>
- {
- /// <inheritdoc />
- public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- return JsonSerializer.Deserialize<T>(ref reader, options);
- }
-
- /// <inheritdoc />
- public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
- {
- writer.WriteStringValue(value?.ToString()?.ToLowerInvariant());
- }
- }
-}
diff --git a/src/Jellyfin.Extensions/StreamExtensions.cs b/src/Jellyfin.Extensions/StreamExtensions.cs
index 182996852..0cfac384e 100644
--- a/src/Jellyfin.Extensions/StreamExtensions.cs
+++ b/src/Jellyfin.Extensions/StreamExtensions.cs
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Text;
+using System.Threading;
namespace Jellyfin.Extensions
{
@@ -48,11 +50,12 @@ namespace Jellyfin.Extensions
/// Reads all lines in the <see cref="TextReader" />.
/// </summary>
/// <param name="reader">The <see cref="TextReader" /> to read from.</param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>All lines in the stream.</returns>
- public static async IAsyncEnumerable<string> ReadAllLinesAsync(this TextReader reader)
+ public static async IAsyncEnumerable<string> ReadAllLinesAsync(this TextReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
string? line;
- while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) is not null)
+ while ((line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false)) is not null)
{
yield return line;
}
diff --git a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs
index cce2911dc..83f68ab50 100644
--- a/src/Jellyfin.LiveTv/Channels/ChannelManager.cs
+++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs
@@ -1130,7 +1130,7 @@ namespace Jellyfin.LiveTv.Channels
{
if (!item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
{
- item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
+ item.Tags = [..item.Tags, "livestream"];
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
forceUpdate = true;
}
diff --git a/src/Jellyfin.LiveTv/Listings/ListingsManager.cs b/src/Jellyfin.LiveTv/Listings/ListingsManager.cs
index 87f47611e..dfd376092 100644
--- a/src/Jellyfin.LiveTv/Listings/ListingsManager.cs
+++ b/src/Jellyfin.LiveTv/Listings/ListingsManager.cs
@@ -60,14 +60,13 @@ public class ListingsManager : IListingsManager
var config = _config.GetLiveTvConfiguration();
- var list = config.ListingProviders.ToList();
- int index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
+ var list = config.ListingProviders;
+ int index = Array.FindIndex(list, i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
{
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
- list.Add(info);
- config.ListingProviders = list.ToArray();
+ config.ListingProviders = [..list, info];
}
else
{
@@ -236,13 +235,12 @@ public class ListingsManager : IListingsManager
if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
{
- var list = listingsProviderInfo.ChannelMappings.ToList();
- list.Add(new NameValuePair
+ var newItem = new NameValuePair
{
Name = tunerChannelNumber,
Value = providerChannelNumber
- });
- listingsProviderInfo.ChannelMappings = list.ToArray();
+ };
+ listingsProviderInfo.ChannelMappings = [..listingsProviderInfo.ChannelMappings, newItem];
}
_config.SaveConfiguration("livetv", config);
diff --git a/src/Jellyfin.LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs
index c19d8195c..0c85dc434 100644
--- a/src/Jellyfin.LiveTv/LiveTvManager.cs
+++ b/src/Jellyfin.LiveTv/LiveTvManager.cs
@@ -939,7 +939,7 @@ namespace Jellyfin.LiveTv
{
var internalChannelId = _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId);
var channel = _libraryManager.GetItemById(internalChannelId);
- channelName = channel is null ? null : channel.Name;
+ channelName = channel?.Name;
}
return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName);
diff --git a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
index 92605a1eb..2f4caa386 100644
--- a/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
+++ b/src/Jellyfin.LiveTv/Recordings/RecordingsManager.cs
@@ -159,7 +159,7 @@ public sealed class RecordingsManager : IRecordingsManager, IDisposable
{
Locations = [customPath],
Name = "Recorded Movies",
- CollectionType = CollectionTypeOptions.Movies
+ CollectionType = CollectionTypeOptions.movies
};
}
@@ -172,7 +172,7 @@ public sealed class RecordingsManager : IRecordingsManager, IDisposable
{
Locations = [customPath],
Name = "Recorded Shows",
- CollectionType = CollectionTypeOptions.TvShows
+ CollectionType = CollectionTypeOptions.tvshows
};
}
}
diff --git a/src/Jellyfin.LiveTv/Timers/ItemDataProvider.cs b/src/Jellyfin.LiveTv/Timers/ItemDataProvider.cs
index 18e4810a2..9e7323f5b 100644
--- a/src/Jellyfin.LiveTv/Timers/ItemDataProvider.cs
+++ b/src/Jellyfin.LiveTv/Timers/ItemDataProvider.cs
@@ -115,11 +115,7 @@ namespace Jellyfin.LiveTv.Timers
throw new ArgumentException("item already exists", nameof(item));
}
- int oldLen = _items.Length;
- var newList = new T[oldLen + 1];
- _items.CopyTo(newList, 0);
- newList[oldLen] = item;
- _items = newList;
+ _items = [.._items, item];
SaveList();
}
@@ -134,11 +130,7 @@ namespace Jellyfin.LiveTv.Timers
int index = Array.FindIndex(_items, i => EqualityComparer(i, item));
if (index == -1)
{
- int oldLen = _items.Length;
- var newList = new T[oldLen + 1];
- _items.CopyTo(newList, 0);
- newList[oldLen] = item;
- _items = newList;
+ _items = [.._items, item];
}
else
{
diff --git a/src/Jellyfin.LiveTv/Timers/TimerManager.cs b/src/Jellyfin.LiveTv/Timers/TimerManager.cs
index da5deea36..1cf335159 100644
--- a/src/Jellyfin.LiveTv/Timers/TimerManager.cs
+++ b/src/Jellyfin.LiveTv/Timers/TimerManager.cs
@@ -22,7 +22,7 @@ namespace Jellyfin.LiveTv.Timers
public TimerManager(ILogger<TimerManager> logger, IConfigurationManager config)
: base(
logger,
- Path.Combine(config.CommonApplicationPaths.DataPath, "livetv"),
+ Path.Combine(config.CommonApplicationPaths.DataPath, "livetv/timers.json"),
(r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
}
diff --git a/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs
index afc2e4f9c..aba9627ba 100644
--- a/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs
@@ -219,7 +219,7 @@ namespace Jellyfin.LiveTv.TunerHosts
}
}
- throw new LiveTvConflictException();
+ throw new LiveTvConflictException("Unable to find host to play channel");
}
protected virtual bool IsValidChannelId(string channelId)
diff --git a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 861338727..1dd35da41 100644
--- a/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -145,7 +145,7 @@ namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
}
_activeTuner = -1;
- throw new LiveTvConflictException();
+ throw new LiveTvConflictException("No tuners available");
}
public async Task ChangeChannel(IHdHomerunChannelCommands commands, CancellationToken cancellationToken)
diff --git a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs
index 8d52151cb..365f0188d 100644
--- a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs
@@ -5,7 +5,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Linq;
+using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -28,15 +30,8 @@ namespace Jellyfin.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
- private static readonly string[] _disallowedMimeTypes =
- {
- "video/x-matroska",
- "video/mp4",
- "application/vnd.apple.mpegurl",
- "application/mpegurl",
- "application/x-mpegurl",
- "video/vnd.mpeg.dash.mpd"
- };
+ private static readonly string[] _mimeTypesCanShareHttpStream = ["video/MP2T"];
+ private static readonly string[] _extensionsCanShareHttpStream = [".ts", ".tsv", ".m2t"];
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
@@ -101,14 +96,31 @@ namespace Jellyfin.LiveTv.TunerHosts
if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping)
{
- using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path);
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .SendAsync(message, cancellationToken)
- .ConfigureAwait(false);
-
- response.EnsureSuccessStatusCode();
+ var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path);
- if (!_disallowedMimeTypes.Contains(response.Content.Headers.ContentType?.ToString(), StringComparison.OrdinalIgnoreCase))
+ if (string.IsNullOrEmpty(extension))
+ {
+ try
+ {
+ using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .SendAsync(message, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (response.IsSuccessStatusCode)
+ {
+ if (_mimeTypesCanShareHttpStream.Contains(response.Content.Headers.ContentType?.MediaType, StringComparison.OrdinalIgnoreCase))
+ {
+ return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
+ }
+ }
+ }
+ catch (Exception)
+ {
+ Logger.LogWarning("HEAD request to check MIME type failed, shared stream disabled");
+ }
+ }
+ else if (_extensionsCanShareHttpStream.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
diff --git a/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs b/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
index 5900d1c5b..c8d678e2f 100644
--- a/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
@@ -273,12 +273,12 @@ namespace Jellyfin.LiveTv.TunerHosts
var numberIndex = nameInExtInf.IndexOf(' ', StringComparison.Ordinal);
if (numberIndex > 0)
{
- var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
+ var numberPart = nameInExtInf.AsSpan(0, numberIndex).Trim(new[] { ' ', '.' });
if (double.TryParse(numberPart, CultureInfo.InvariantCulture, out _))
{
// channel.Number = number.ToString();
- nameInExtInf = nameInExtInf.Substring(numberIndex + 1).Trim(new[] { ' ', '-' });
+ nameInExtInf = nameInExtInf.AsSpan(numberIndex + 1).Trim(new[] { ' ', '-' }).ToString();
}
}
}
diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
index 60be19c68..473f00153 100644
--- a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
@@ -76,14 +76,13 @@ public class TunerHostManager : ITunerHostManager
var config = _config.GetLiveTvConfiguration();
- var list = config.TunerHosts.ToList();
- var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
+ var list = config.TunerHosts;
+ var index = Array.FindIndex(list, i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
{
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
- list.Add(info);
- config.TunerHosts = list.ToArray();
+ config.TunerHosts = [..list, info];
}
else
{
diff --git a/src/Jellyfin.Networking/AutoDiscoveryHost.cs b/src/Jellyfin.Networking/AutoDiscoveryHost.cs
index 5624c4ed1..ff0d179b9 100644
--- a/src/Jellyfin.Networking/AutoDiscoveryHost.cs
+++ b/src/Jellyfin.Networking/AutoDiscoveryHost.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
@@ -59,65 +57,58 @@ public sealed class AutoDiscoveryHost : BackgroundService
return;
}
- var udpServers = new List<Task>();
- // Linux needs to bind to the broadcast addresses to receive broadcast traffic
- if (OperatingSystem.IsLinux() && networkConfig.EnableIPv4)
- {
- udpServers.Add(ListenForAutoDiscoveryMessage(IPAddress.Broadcast, stoppingToken));
- }
-
- udpServers.AddRange(_networkManager.GetInternalBindAddresses()
- .Select(intf => ListenForAutoDiscoveryMessage(
- OperatingSystem.IsLinux() && intf.AddressFamily == AddressFamily.InterNetwork
- ? NetworkUtils.GetBroadcastAddress(intf.Subnet)
- : intf.Address,
- stoppingToken)));
-
- await Task.WhenAll(udpServers).ConfigureAwait(false);
+ await ListenForAutoDiscoveryMessage(IPAddress.Any, stoppingToken).ConfigureAwait(false);
}
- private async Task ListenForAutoDiscoveryMessage(IPAddress address, CancellationToken cancellationToken)
+ private async Task ListenForAutoDiscoveryMessage(IPAddress listenAddress, CancellationToken cancellationToken)
{
- using var udpClient = new UdpClient(new IPEndPoint(address, PortNumber));
- udpClient.MulticastLoopback = false;
-
- while (!cancellationToken.IsCancellationRequested)
+ try
{
- try
+ using var udpClient = new UdpClient(new IPEndPoint(listenAddress, PortNumber));
+ udpClient.MulticastLoopback = false;
+
+ while (!cancellationToken.IsCancellationRequested)
{
- var result = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
- var text = Encoding.UTF8.GetString(result.Buffer);
- if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
+ try
{
- await RespondToV2Message(udpClient, result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
+ var result = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ var text = Encoding.UTF8.GetString(result.Buffer);
+ if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
+ {
+ await RespondToV2Message(result.RemoteEndPoint, udpClient, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (SocketException ex)
+ {
+ _logger.LogError(ex, "Failed to receive data from socket");
}
- }
- catch (SocketException ex)
- {
- _logger.LogError(ex, "Failed to receive data from socket");
- }
- catch (OperationCanceledException)
- {
- _logger.LogDebug("Broadcast socket operation cancelled");
}
}
+ catch (OperationCanceledException)
+ {
+ _logger.LogDebug("Broadcast socket operation cancelled");
+ }
+ catch (Exception ex)
+ {
+ // Exception in this function will prevent the background service from restarting in-process.
+ _logger.LogError(ex, "Unable to bind to {Address}:{Port}", listenAddress, PortNumber);
+ }
}
- private async Task RespondToV2Message(UdpClient udpClient, IPEndPoint endpoint, CancellationToken cancellationToken)
+ private async Task RespondToV2Message(IPEndPoint endpoint, UdpClient broadCastUdpClient, CancellationToken cancellationToken)
{
var localUrl = _appHost.GetSmartApiUrl(endpoint.Address);
if (string.IsNullOrEmpty(localUrl))
{
- _logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined.");
+ _logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined");
return;
}
var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName);
-
try
{
_logger.LogDebug("Sending AutoDiscovery response");
- await udpClient
+ await broadCastUdpClient
.SendAsync(JsonSerializer.SerializeToUtf8Bytes(response).AsMemory(), endpoint, cancellationToken)
.ConfigureAwait(false);
}
diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs
index 1da44b048..cf6a2cc55 100644
--- a/src/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -11,7 +11,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
@@ -237,7 +236,7 @@ public class NetworkManager : INetworkManager, IDisposable
var mac = adapter.GetPhysicalAddress();
// Populate MAC list
- if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && PhysicalAddress.None.Equals(mac))
+ if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && !PhysicalAddress.None.Equals(mac))
{
macAddresses.Add(mac);
}
@@ -412,7 +411,9 @@ public class NetworkManager : INetworkManager, IDisposable
interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
}
- _interfaces = interfaces;
+ // Users may have complex networking configuration that multiple interfaces sharing the same IP address
+ // Only return one IP for binding, and let the OS handle the rest
+ _interfaces = interfaces.DistinctBy(iface => iface.Address).ToList();
}
}
@@ -737,7 +738,9 @@ public class NetworkManager : INetworkManager, IDisposable
/// <inheritdoc/>
public IReadOnlyList<IPData> GetAllBindInterfaces(bool individualInterfaces = false)
{
- if (_interfaces.Count > 0 || individualInterfaces)
+ var config = _configurationManager.GetNetworkConfiguration();
+ var localNetworkAddresses = config.LocalNetworkAddresses;
+ if ((localNetworkAddresses.Length > 0 && !string.IsNullOrWhiteSpace(localNetworkAddresses[0]) && _interfaces.Count > 0) || individualInterfaces)
{
return _interfaces;
}
@@ -900,15 +903,30 @@ public class NetworkManager : INetworkManager, IDisposable
return false;
}
+ /// <summary>
+ /// Get if the IPAddress is Link-local.
+ /// </summary>
+ /// <param name="address">The IP Address.</param>
+ /// <returns>Bool indicates if the address is link-local.</returns>
+ public bool IsLinkLocalAddress(IPAddress address)
+ {
+ ArgumentNullException.ThrowIfNull(address);
+ return NetworkConstants.IPv4RFC3927LinkLocal.Contains(address) || address.IsIPv6LinkLocal;
+ }
+
/// <inheritdoc/>
public bool IsInLocalNetwork(IPAddress address)
{
ArgumentNullException.ThrowIfNull(address);
- // See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
+ // Map IPv6 mapped IPv4 back to IPv4 (happens if Kestrel runs in dual-socket mode)
+ if (address.IsIPv4MappedToIPv6)
+ {
+ address = address.MapToIPv4();
+ }
+
if ((TrustAllIPv6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
- || address.Equals(IPAddress.Loopback)
- || address.Equals(IPAddress.IPv6Loopback))
+ || IPAddress.IsLoopback(address))
{
return true;
}
@@ -1017,7 +1035,7 @@ public class NetworkManager : INetworkManager, IDisposable
result = string.Empty;
int count = _interfaces.Count;
- if (count == 1 && (_interfaces[0].Equals(IPAddress.Any) || _interfaces[0].Equals(IPAddress.IPv6Any)))
+ if (count == 1 && (_interfaces[0].Address.Equals(IPAddress.Any) || _interfaces[0].Address.Equals(IPAddress.IPv6Any)))
{
// Ignore IPAny addresses.
count = 0;
@@ -1049,7 +1067,7 @@ public class NetworkManager : INetworkManager, IDisposable
return true;
}
- _logger.LogWarning("{Source}: External request received, no matching external bind address found, trying internal addresses.", source);
+ _logger.LogDebug("{Source}: External request received, no matching external bind address found, trying internal addresses", source);
}
else
{
@@ -1081,13 +1099,17 @@ public class NetworkManager : INetworkManager, IDisposable
private bool MatchesExternalInterface(IPAddress source, out string result)
{
// Get the first external interface address that isn't a loopback.
- var extResult = _interfaces.Where(p => !IsInLocalNetwork(p.Address)).OrderBy(x => x.Index).ToArray();
+ var extResult = _interfaces
+ .Where(p => !IsInLocalNetwork(p.Address))
+ .Where(p => p.Address.AddressFamily.Equals(source.AddressFamily))
+ .Where(p => !IsLinkLocalAddress(p.Address))
+ .OrderBy(x => x.Index).ToArray();
// No external interface found
if (extResult.Length == 0)
{
result = string.Empty;
- _logger.LogWarning("{Source}: External request received, but no external interface found. Need to route through internal network.", source);
+ _logger.LogDebug("{Source}: External request received, but no external interface found. Need to route through internal network", source);
return false;
}
@@ -1114,12 +1136,13 @@ public class NetworkManager : INetworkManager, IDisposable
var logLevel = debug ? LogLevel.Debug : LogLevel.Information;
if (_logger.IsEnabled(logLevel))
{
- _logger.Log(logLevel, "Defined LAN addresses: {0}", _lanSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
- _logger.Log(logLevel, "Defined LAN exclusions: {0}", _excludedSubnets.Select(s => s.Prefix + "/" + s.PrefixLength));
- _logger.Log(logLevel, "Using LAN addresses: {0}", _lanSubnets.Where(s => !_excludedSubnets.Contains(s)).Select(s => s.Prefix + "/" + s.PrefixLength));
- _logger.Log(logLevel, "Using bind addresses: {0}", _interfaces.OrderByDescending(x => x.AddressFamily == AddressFamily.InterNetwork).Select(x => x.Address));
- _logger.Log(logLevel, "Remote IP filter is {0}", config.IsRemoteIPFilterBlacklist ? "Blocklist" : "Allowlist");
- _logger.Log(logLevel, "Filter list: {0}", _remoteAddressFilter.Select(s => s.Prefix + "/" + s.PrefixLength));
+ _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, "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));
}
}
}