aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Jellyfin.Drawing.Skia/SkiaEncoder.cs2
-rw-r--r--src/Jellyfin.Extensions/StreamExtensions.cs7
-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.cs55
-rw-r--r--src/Jellyfin.Networking/Manager/NetworkManager.cs47
7 files changed, 64 insertions, 53 deletions
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index 75963226a..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);
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/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 b78b60c70..365f0188d 100644
--- a/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs
@@ -30,23 +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[] _disallowedSharedStreamExtensions =
- {
- ".mkv",
- ".mp4",
- ".m3u8",
- ".mpd"
- };
+ private static readonly string[] _mimeTypesCanShareHttpStream = ["video/MP2T"];
+ private static readonly string[] _extensionsCanShareHttpStream = [".ts", ".tsv", ".m2t"];
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
@@ -111,31 +96,33 @@ 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);
+ var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path);
- if (response.IsSuccessStatusCode)
+ if (string.IsNullOrEmpty(extension))
{
- if (!_disallowedMimeTypes.Contains(response.Content.Headers.ContentType?.ToString(), StringComparison.OrdinalIgnoreCase))
+ try
{
- return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
+ 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);
+ }
+ }
}
- }
- else if (response.StatusCode == HttpStatusCode.MethodNotAllowed || response.StatusCode == HttpStatusCode.NotImplemented)
- {
- // Fallback to check path extension when the server does not support HEAD method
- // Use UriBuilder to remove all query string as GetExtension will include them when used directly
- var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path);
- if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
+ catch (Exception)
{
- return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
+ Logger.LogWarning("HEAD request to check MIME type failed, shared stream disabled");
}
}
- else
+ else if (_extensionsCanShareHttpStream.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
- response.EnsureSuccessStatusCode();
+ return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
}
diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs
index 37160b3ba..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);
}
@@ -739,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;
}
@@ -902,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;
}
@@ -1083,7 +1099,11 @@ 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)
@@ -1116,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));
}
}
}