Merge remote-tracking branch 'upstream/master' into api-stream-return

pull/4037/head
crobibero 4 years ago
commit d5eb246557

@ -14,7 +14,7 @@ COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
FROM debian:buster-slim FROM debian:buster-slim

@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists # Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r RUN find . -type d -name obj | xargs -r rm -r
# Build # Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM multiarch/qemu-user-static:x86_64-arm as qemu

@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists # Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r RUN find . -type d -name obj | xargs -r rm -r
# Build # Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM arm64v8/debian:buster-slim FROM arm64v8/debian:buster-slim

@ -1,8 +1,8 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -18,8 +18,8 @@ namespace Emby.Dlna.ConnectionManager
IDlnaManager dlna, IDlnaManager dlna,
IServerConfigurationManager config, IServerConfigurationManager config,
ILogger<ConnectionManagerService> logger, ILogger<ConnectionManagerService> logger,
IHttpClient httpClient) IHttpClientFactory httpClientFactory)
: base(logger, httpClient) : base(logger, httpClientFactory)
{ {
_dlna = dlna; _dlna = dlna;
_config = config; _config = config;

@ -2,11 +2,11 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@ -41,7 +41,7 @@ namespace Emby.Dlna.ContentDirectory
IServerConfigurationManager config, IServerConfigurationManager config,
IUserManager userManager, IUserManager userManager,
ILogger<ContentDirectoryService> logger, ILogger<ContentDirectoryService> logger,
IHttpClient httpClient, IHttpClientFactory httpClient,
ILocalizationManager localization, ILocalizationManager localization,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IUserViewManager userViewManager, IUserViewManager userViewManager,

@ -80,6 +80,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Mime;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
@ -20,13 +21,13 @@ namespace Emby.Dlna.Eventing
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase); new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public DlnaEventManager(ILogger logger, IHttpClient httpClient) public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory)
{ {
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_logger = logger; _logger = logger;
} }
@ -167,24 +168,17 @@ namespace Emby.Dlna.Eventing
builder.Append("</e:propertyset>"); builder.Append("</e:propertyset>");
var options = new HttpRequestOptions using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
{ options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml);
RequestContent = builder.ToString(), options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType);
RequestContentType = "text/xml", options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange");
Url = subscription.CallbackUrl, options.Headers.TryAddWithoutValidation("SID", subscription.Id);
BufferContent = false options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture));
};
options.RequestHeaders.Add("NT", subscription.NotificationType);
options.RequestHeaders.Add("NTS", "upnp:propchange");
options.RequestHeaders.Add("SID", subscription.Id);
options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture));
try try
{ {
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false)) using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
{ .SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {

@ -2,6 +2,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Net.Http;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -36,7 +37,7 @@ namespace Emby.Dlna.Main
private readonly ILogger<DlnaEntryPoint> _logger; private readonly ILogger<DlnaEntryPoint> _logger;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager; private readonly IDlnaManager _dlnaManager;
@ -61,7 +62,7 @@ namespace Emby.Dlna.Main
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IServerApplicationHost appHost, IServerApplicationHost appHost,
ISessionManager sessionManager, ISessionManager sessionManager,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IUserManager userManager, IUserManager userManager,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
@ -79,7 +80,7 @@ namespace Emby.Dlna.Main
_config = config; _config = config;
_appHost = appHost; _appHost = appHost;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userManager = userManager; _userManager = userManager;
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
@ -101,7 +102,7 @@ namespace Emby.Dlna.Main
config, config,
userManager, userManager,
loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(), loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
httpClient, httpClientFactory,
localizationManager, localizationManager,
mediaSourceManager, mediaSourceManager,
userViewManager, userViewManager,
@ -112,11 +113,11 @@ namespace Emby.Dlna.Main
dlnaManager, dlnaManager,
config, config,
loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(), loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
httpClient); httpClientFactory);
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService( MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(), loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
httpClient, httpClientFactory,
config); config);
Current = this; Current = this;
} }
@ -364,7 +365,7 @@ namespace Emby.Dlna.Main
_appHost, _appHost,
_imageProcessor, _imageProcessor,
_deviceDiscovery, _deviceDiscovery,
_httpClient, _httpClientFactory,
_config, _config,
_userDataManager, _userDataManager,
_localization, _localization,

@ -1,8 +1,8 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -14,9 +14,9 @@ namespace Emby.Dlna.MediaReceiverRegistrar
public MediaReceiverRegistrarService( public MediaReceiverRegistrarService(
ILogger<MediaReceiverRegistrarService> logger, ILogger<MediaReceiverRegistrarService> logger,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
IServerConfigurationManager config) IServerConfigurationManager config)
: base(logger, httpClient) : base(logger, httpClientFactory)
{ {
_config = config; _config = config;
} }

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Security; using System.Security;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -21,7 +22,7 @@ namespace Emby.Dlna.PlayTo
{ {
private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -34,10 +35,10 @@ namespace Emby.Dlna.PlayTo
private int _connectFailureCount; private int _connectFailureCount;
private bool _disposed; private bool _disposed;
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger) public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, ILogger logger)
{ {
Properties = deviceProperties; Properties = deviceProperties;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_logger = logger; _logger = logger;
} }
@ -236,7 +237,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Setting mute"); _logger.LogDebug("Setting mute");
var value = mute ? 1 : 0; var value = mute ? 1 : 0;
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
.ConfigureAwait(false); .ConfigureAwait(false);
IsMuted = mute; IsMuted = mute;
@ -271,7 +272,7 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better // Remote control will perform better
Volume = value; Volume = value;
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -292,7 +293,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service"); throw new InvalidOperationException("Unable to find service");
} }
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME")) await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
.ConfigureAwait(false); .ConfigureAwait(false);
RestartTimer(true); RestartTimer(true);
@ -326,7 +327,7 @@ namespace Emby.Dlna.PlayTo
} }
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header) await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
.ConfigureAwait(false); .ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false); await Task.Delay(50).ConfigureAwait(false);
@ -368,7 +369,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service"); throw new InvalidOperationException("Unable to find service");
} }
return new SsdpHttpClient(_httpClient).SendCommandAsync( return new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl, Properties.BaseUrl,
service, service,
command.Name, command.Name,
@ -397,7 +398,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService(); var service = GetAvTransportService();
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false); .ConfigureAwait(false);
RestartTimer(true); RestartTimer(true);
@ -415,7 +416,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService(); var service = GetAvTransportService();
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false); .ConfigureAwait(false);
TransportState = TransportState.Paused; TransportState = TransportState.Paused;
@ -542,7 +543,7 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl, Properties.BaseUrl,
service, service,
command.Name, command.Name,
@ -592,7 +593,7 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl, Properties.BaseUrl,
service, service,
command.Name, command.Name,
@ -625,7 +626,7 @@ namespace Emby.Dlna.PlayTo
return null; return null;
} }
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl, Properties.BaseUrl,
service, service,
command.Name, command.Name,
@ -667,7 +668,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl, Properties.BaseUrl,
service, service,
command.Name, command.Name,
@ -734,7 +735,7 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
Properties.BaseUrl, Properties.BaseUrl,
service, service,
command.Name, command.Name,
@ -912,7 +913,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
var httpClient = new SsdpHttpClient(_httpClient); var httpClient = new SsdpHttpClient(_httpClientFactory);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
@ -940,7 +941,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
var httpClient = new SsdpHttpClient(_httpClient); var httpClient = new SsdpHttpClient(_httpClientFactory);
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
@ -969,9 +970,9 @@ namespace Emby.Dlna.PlayTo
return baseUrl + url; return baseUrl + url;
} }
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, ILogger logger, CancellationToken cancellationToken) public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
{ {
var ssdpHttpClient = new SsdpHttpClient(httpClient); var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
@ -1079,7 +1080,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
return new Device(deviceProperties, httpClient, logger); return new Device(deviceProperties, httpClientFactory, logger);
} }
private static DeviceIcon CreateIcon(XElement element) private static DeviceIcon CreateIcon(XElement element)

@ -4,6 +4,7 @@ using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
@ -33,7 +34,7 @@ namespace Emby.Dlna.PlayTo
private readonly IDlnaManager _dlnaManager; private readonly IDlnaManager _dlnaManager;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
@ -46,7 +47,7 @@ namespace Emby.Dlna.PlayTo
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
{ {
_logger = logger; _logger = logger;
_sessionManager = sessionManager; _sessionManager = sessionManager;
@ -56,7 +57,7 @@ namespace Emby.Dlna.PlayTo
_appHost = appHost; _appHost = appHost;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_deviceDiscovery = deviceDiscovery; _deviceDiscovery = deviceDiscovery;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_config = config; _config = config;
_userDataManager = userDataManager; _userDataManager = userDataManager;
_localization = localization; _localization = localization;
@ -174,7 +175,7 @@ namespace Emby.Dlna.PlayTo
if (controller == null) if (controller == null)
{ {
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _logger, cancellationToken).ConfigureAwait(false); var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
string deviceName = device.Properties.Name; string deviceName = device.Properties.Name;

@ -4,6 +4,8 @@ using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -20,11 +22,11 @@ namespace Emby.Dlna.PlayTo
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
public SsdpHttpClient(IHttpClient httpClient) public SsdpHttpClient(IHttpClientFactory httpClientFactory)
{ {
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
} }
public async Task<XDocument> SendCommandAsync( public async Task<XDocument> SendCommandAsync(
@ -36,20 +38,18 @@ namespace Emby.Dlna.PlayTo
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
using (var response = await PostSoapDataAsync( using var response = await PostSoapDataAsync(
url, url,
$"\"{service.ServiceType}#{command}\"", $"\"{service.ServiceType}#{command}\"",
postData, postData,
header, header,
cancellationToken) cancellationToken)
.ConfigureAwait(false)) .ConfigureAwait(false);
using (var stream = response.Content) await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
using (var reader = new StreamReader(stream, Encoding.UTF8)) using var reader = new StreamReader(stream, Encoding.UTF8);
{ return XDocument.Parse(
return XDocument.Parse( await reader.ReadToEndAsync().ConfigureAwait(false),
await reader.ReadToEndAsync().ConfigureAwait(false), LoadOptions.PreserveWhitespace);
LoadOptions.PreserveWhitespace);
}
} }
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl) private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
@ -76,49 +76,32 @@ namespace Emby.Dlna.PlayTo
int eventport, int eventport,
int timeOut = 3600) int timeOut = 3600)
{ {
var options = new HttpRequestOptions using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url);
{ options.Headers.UserAgent.ParseAdd(USERAGENT);
Url = url, options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture));
UserAgent = USERAGENT, options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">");
LogErrorResponseBody = true, options.Headers.TryAddWithoutValidation("NT", "upnp:event");
BufferContent = false, options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture));
};
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture); .SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"; .ConfigureAwait(false);
options.RequestHeaders["NT"] = "upnp:event";
options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
{
}
} }
public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken) public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
{ {
var options = new HttpRequestOptions using var options = new HttpRequestMessage(HttpMethod.Get, url);
{ options.Headers.UserAgent.ParseAdd(USERAGENT);
Url = url, options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
UserAgent = USERAGENT, using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
LogErrorResponseBody = true, await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
BufferContent = false, using var reader = new StreamReader(stream, Encoding.UTF8);
return XDocument.Parse(
CancellationToken = cancellationToken await reader.ReadToEndAsync().ConfigureAwait(false),
}; LoadOptions.PreserveWhitespace);
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
using (var stream = response.Content)
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
return XDocument.Parse(
await reader.ReadToEndAsync().ConfigureAwait(false),
LoadOptions.PreserveWhitespace);
}
} }
private Task<HttpResponseInfo> PostSoapDataAsync( private Task<HttpResponseMessage> PostSoapDataAsync(
string url, string url,
string soapAction, string soapAction,
string postData, string postData,
@ -130,29 +113,20 @@ namespace Emby.Dlna.PlayTo
soapAction = $"\"{soapAction}\""; soapAction = $"\"{soapAction}\"";
} }
var options = new HttpRequestOptions using var options = new HttpRequestMessage(HttpMethod.Post, url);
{ options.Headers.UserAgent.ParseAdd(USERAGENT);
Url = url, options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
UserAgent = USERAGENT, options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
LogErrorResponseBody = true, options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
BufferContent = false,
CancellationToken = cancellationToken
};
options.RequestHeaders["SOAPAction"] = soapAction;
options.RequestHeaders["Pragma"] = "no-cache";
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
if (!string.IsNullOrEmpty(header)) if (!string.IsNullOrEmpty(header))
{ {
options.RequestHeaders["contentFeatures.dlna.org"] = header; options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
} }
options.RequestContentType = "text/xml"; options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
options.RequestContent = postData;
return _httpClient.Post(options); return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
} }
} }
} }

@ -1,25 +1,21 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Net.Http;
using Emby.Dlna.Eventing; using Emby.Dlna.Eventing;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Dlna.Service namespace Emby.Dlna.Service
{ {
public class BaseService : IDlnaEventManager public class BaseService : IDlnaEventManager
{ {
protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient) protected BaseService(ILogger<BaseService> logger, IHttpClientFactory httpClientFactory)
{ {
Logger = logger; Logger = logger;
HttpClient = httpClient; EventManager = new DlnaEventManager(logger, httpClientFactory);
EventManager = new DlnaEventManager(logger, HttpClient);
} }
protected IDlnaEventManager EventManager { get; } protected IDlnaEventManager EventManager { get; }
protected IHttpClient HttpClient { get; }
protected ILogger Logger { get; } protected ILogger Logger { get; }
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)

@ -49,6 +49,7 @@ using Jellyfin.Api.Helpers;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events; using MediaBrowser.Common.Events;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
@ -122,8 +123,8 @@ namespace Emby.Server.Implementations
private IMediaEncoder _mediaEncoder; private IMediaEncoder _mediaEncoder;
private ISessionManager _sessionManager; private ISessionManager _sessionManager;
private IHttpClientFactory _httpClientFactory;
private IWebSocketManager _webSocketManager; private IWebSocketManager _webSocketManager;
private IHttpClient _httpClient;
private string[] _urlPrefixes; private string[] _urlPrefixes;
@ -526,8 +527,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton(_fileSystemManager); ServiceCollection.AddSingleton(_fileSystemManager);
ServiceCollection.AddSingleton<TvdbClientManager>(); ServiceCollection.AddSingleton<TvdbClientManager>();
ServiceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
ServiceCollection.AddSingleton(_networkManager); ServiceCollection.AddSingleton(_networkManager);
ServiceCollection.AddSingleton<IIsoManager, IsoManager>(); ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
@ -654,8 +653,8 @@ namespace Emby.Server.Implementations
_mediaEncoder = Resolve<IMediaEncoder>(); _mediaEncoder = Resolve<IMediaEncoder>();
_sessionManager = Resolve<ISessionManager>(); _sessionManager = Resolve<ISessionManager>();
_httpClientFactory = Resolve<IHttpClientFactory>();
_webSocketManager = Resolve<IWebSocketManager>(); _webSocketManager = Resolve<IWebSocketManager>();
_httpClient = Resolve<IHttpClient>();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
@ -1300,25 +1299,17 @@ namespace Emby.Server.Implementations
try try
{ {
using (var response = await _httpClient.SendAsync( using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
new HttpRequestOptions using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
{ .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
Url = apiUrl,
LogErrorResponseBody = false,
BufferContent = false,
CancellationToken = cancellationToken
}, HttpMethod.Post).ConfigureAwait(false))
{
using (var reader = new StreamReader(response.Content))
{
var result = await reader.ReadToEndAsync().ConfigureAwait(false);
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid); var result = await System.Text.Json.JsonSerializer.DeserializeAsync<string>(stream, JsonDefaults.GetOptions(), cancellationToken).ConfigureAwait(false);
return valid; var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
}
} _validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
return valid;
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {

@ -1,335 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpClientManager
{
/// <summary>
/// Class HttpClientManager.
/// </summary>
public class HttpClientManager : IHttpClient
{
private readonly ILogger<HttpClientManager> _logger;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly IApplicationHost _appHost;
/// <summary>
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
/// DON'T dispose it after use.
/// </summary>
/// <value>The HTTP clients.</value>
private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>();
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientManager" /> class.
/// </summary>
public HttpClientManager(
IApplicationPaths appPaths,
ILogger<HttpClientManager> logger,
IFileSystem fileSystem,
IApplicationHost appHost)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_fileSystem = fileSystem;
_appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths));
_appHost = appHost;
}
/// <summary>
/// Gets the correct http client for the given url.
/// </summary>
/// <param name="url">The url.</param>
/// <returns>HttpClient.</returns>
private HttpClient GetHttpClient(string url)
{
var key = GetHostFromUrl(url);
if (!_httpClients.TryGetValue(key, out var client))
{
client = new HttpClient()
{
BaseAddress = new Uri(url)
};
_httpClients.TryAdd(key, client);
}
return client;
}
private HttpRequestMessage GetRequestMessage(HttpRequestOptions options, HttpMethod method)
{
string url = options.Url;
var uriAddress = new Uri(url);
string userInfo = uriAddress.UserInfo;
if (!string.IsNullOrWhiteSpace(userInfo))
{
_logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal);
}
var request = new HttpRequestMessage(method, url);
foreach (var header in options.RequestHeaders)
{
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
if (options.EnableDefaultUserAgent
&& !request.Headers.TryGetValues(HeaderNames.UserAgent, out _))
{
request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent);
}
switch (options.DecompressionMethod)
{
case CompressionMethods.Deflate | CompressionMethods.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" });
break;
case CompressionMethods.Deflate:
request.Headers.Add(HeaderNames.AcceptEncoding, "deflate");
break;
case CompressionMethods.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, "gzip");
break;
default:
break;
}
if (options.EnableKeepAlive)
{
request.Headers.Add(HeaderNames.Connection, "Keep-Alive");
}
// request.Headers.Add(HeaderNames.CacheControl, "no-cache");
/*
if (!string.IsNullOrWhiteSpace(userInfo))
{
var parts = userInfo.Split(':');
if (parts.Length == 2)
{
request.Headers.Add(HeaderNames., GetCredential(url, parts[0], parts[1]);
}
}
*/
return request;
}
/// <summary>
/// Gets the response internal.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
=> SendAsync(options, HttpMethod.Get);
/// <summary>
/// Performs a GET request and returns the resulting stream.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task{Stream}.</returns>
public async Task<Stream> Get(HttpRequestOptions options)
{
var response = await GetResponse(options).ConfigureAwait(false);
return response.Content;
}
/// <summary>
/// send as an asynchronous operation.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="httpMethod">The HTTP method.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
public Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
=> SendAsync(options, new HttpMethod(httpMethod));
/// <summary>
/// send as an asynchronous operation.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="httpMethod">The HTTP method.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod)
{
if (options.CacheMode == CacheMode.None)
{
return await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
}
var url = options.Url;
var urlHash = url.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
var response = GetCachedResponse(responseCachePath, options.CacheLength, url);
if (response != null)
{
return response;
}
response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
await CacheResponse(response, responseCachePath).ConfigureAwait(false);
}
return response;
}
private HttpResponseInfo GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
{
if (File.Exists(responseCachePath)
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
{
var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
return new HttpResponseInfo
{
ResponseUrl = url,
Content = stream,
StatusCode = HttpStatusCode.OK,
ContentLength = stream.Length
};
}
return null;
}
private async Task CacheResponse(HttpResponseInfo response, string responseCachePath)
{
Directory.CreateDirectory(Path.GetDirectoryName(responseCachePath));
using (var fileStream = new FileStream(
responseCachePath,
FileMode.Create,
FileAccess.Write,
FileShare.None,
IODefaults.FileStreamBufferSize,
true))
{
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
response.Content.Position = 0;
}
}
private async Task<HttpResponseInfo> SendAsyncInternal(HttpRequestOptions options, HttpMethod httpMethod)
{
ValidateParams(options);
options.CancellationToken.ThrowIfCancellationRequested();
var client = GetHttpClient(options.Url);
var httpWebRequest = GetRequestMessage(options, httpMethod);
if (!string.IsNullOrEmpty(options.RequestContent)
|| httpMethod == HttpMethod.Post)
{
if (options.RequestContent != null)
{
httpWebRequest.Content = new StringContent(
options.RequestContent,
null,
options.RequestContentType);
}
else
{
httpWebRequest.Content = new ByteArrayContent(Array.Empty<byte>());
}
}
options.CancellationToken.ThrowIfCancellationRequested();
var response = await client.SendAsync(
httpWebRequest,
options.BufferContent || options.CacheMode == CacheMode.Unconditional ? HttpCompletionOption.ResponseContentRead : HttpCompletionOption.ResponseHeadersRead,
options.CancellationToken).ConfigureAwait(false);
await EnsureSuccessStatusCode(response, options).ConfigureAwait(false);
options.CancellationToken.ThrowIfCancellationRequested();
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
return new HttpResponseInfo(response.Headers, response.Content.Headers)
{
Content = stream,
StatusCode = response.StatusCode,
ContentType = response.Content.Headers.ContentType?.MediaType,
ContentLength = response.Content.Headers.ContentLength,
ResponseUrl = response.Content.Headers.ContentLocation?.ToString()
};
}
/// <inheritdoc />
public Task<HttpResponseInfo> Post(HttpRequestOptions options)
=> SendAsync(options, HttpMethod.Post);
private void ValidateParams(HttpRequestOptions options)
{
if (string.IsNullOrEmpty(options.Url))
{
throw new ArgumentNullException(nameof(options));
}
}
/// <summary>
/// Gets the host from URL.
/// </summary>
/// <param name="url">The URL.</param>
/// <returns>System.String.</returns>
private static string GetHostFromUrl(string url)
{
var index = url.IndexOf("://", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
url = url.Substring(index + 3);
var host = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(host))
{
return host;
}
}
return url;
}
private async Task EnsureSuccessStatusCode(HttpResponseMessage response, HttpRequestOptions options)
{
if (response.IsSuccessStatusCode)
{
return;
}
if (options.LogErrorResponseBody)
{
string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
_logger.LogError("HTTP request failed with message: {Message}", msg);
}
throw new HttpException(response.ReasonPhrase)
{
StatusCode = response.StatusCode
};
}
}
}

@ -16,13 +16,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public class DirectRecorder : IRecorder public class DirectRecorder : IRecorder
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IStreamHelper _streamHelper; private readonly IStreamHelper _streamHelper;
public DirectRecorder(ILogger logger, IHttpClient httpClient, IStreamHelper streamHelper) public DirectRecorder(ILogger logger, IHttpClientFactory httpClientFactory, IStreamHelper streamHelper)
{ {
_logger = logger; _logger = logger;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_streamHelper = streamHelper; _streamHelper = streamHelper;
} }
@ -63,42 +63,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{ {
var httpRequestOptions = new HttpRequestOptions using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
{ .GetAsync(mediaSource.Path, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
Url = mediaSource.Path,
BufferContent = false,
// Some remote urls will expect a user agent to be supplied
UserAgent = "Emby/3.0",
// Shouldn't matter but may cause issues _logger.LogInformation("Opened recording stream from tuner provider");
DecompressionMethod = CompressionMethods.None,
CancellationToken = cancellationToken
};
using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false)) Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
{
_logger.LogInformation("Opened recording stream from tuner provider");
Directory.CreateDirectory(Path.GetDirectoryName(targetFile)); await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read);
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read)) onStarted();
{
onStarted();
_logger.LogInformation("Copying recording stream to file {0}", targetFile); _logger.LogInformation("Copying recording stream to file {0}", targetFile);
// The media source if infinite so we need to handle stopping ourselves // The media source if infinite so we need to handle stopping ourselves
using var durationToken = new CancellationTokenSource(duration); var durationToken = new CancellationTokenSource(duration);
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
await _streamHelper.CopyUntilCancelled( await _streamHelper.CopyUntilCancelled(
response.Content, await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
output, output,
IODefaults.CopyToBufferSize, IODefaults.CopyToBufferSize,
cancellationTokenSource.Token).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
}
}
_logger.LogInformation("Recording completed to file {0}", targetFile); _logger.LogInformation("Recording completed to file {0}", targetFile);
} }

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -48,7 +49,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ILogger<EmbyTV> _logger; private readonly ILogger<EmbyTV> _logger;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
ILogger<EmbyTV> logger, ILogger<EmbyTV> logger,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
IServerConfigurationManager config, IServerConfigurationManager config,
ILiveTvManager liveTvManager, ILiveTvManager liveTvManager,
IFileSystem fileSystem, IFileSystem fileSystem,
@ -94,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_appHost = appHost; _appHost = appHost;
_logger = logger; _logger = logger;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_config = config; _config = config;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_libraryManager = libraryManager; _libraryManager = libraryManager;
@ -1637,7 +1638,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer); return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer);
} }
return new DirectRecorder(_logger, _httpClient, _streamHelper); return new DirectRecorder(_logger, _httpClientFactory, _streamHelper);
} }
private void OnSuccessfulRecording(TimerInfo timer, string path) private void OnSuccessfulRecording(TimerInfo timer, string path)

@ -8,6 +8,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common; using MediaBrowser.Common;
@ -28,19 +30,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ILogger<SchedulesDirect> _logger; private readonly ILogger<SchedulesDirect> _logger;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
private readonly IApplicationHost _appHost; private readonly IApplicationHost _appHost;
public SchedulesDirect( public SchedulesDirect(
ILogger<SchedulesDirect> logger, ILogger<SchedulesDirect> logger,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
IApplicationHost appHost) IApplicationHost appHost)
{ {
_logger = logger; _logger = logger;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_appHost = appHost; _appHost = appHost;
} }
@ -102,95 +104,78 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var requestString = _jsonSerializer.SerializeToString(requestList); var requestString = _jsonSerializer.SerializeToString(requestList);
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString); _logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
var httpOptions = new HttpRequestOptions() using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules");
{ options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json);
Url = ApiUrl + "/schedules", options.Headers.TryAddWithoutValidation("token", token);
UserAgent = UserAgent, using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
CancellationToken = cancellationToken, await using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
LogErrorResponseBody = true, var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(responseStream).ConfigureAwait(false);
RequestContent = requestString _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
};
httpOptions.RequestHeaders["token"] = token; using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
using (var response = await Post(httpOptions, true, info).ConfigureAwait(false)) var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct();
{ programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
var dailySchedules = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Day>>(response.Content).ConfigureAwait(false);
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
httpOptions = new HttpRequestOptions() using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
{ await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
Url = ApiUrl + "/programs", var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream).ConfigureAwait(false);
UserAgent = UserAgent, var programDict = programDetails.ToDictionary(p => p.programID, y => y);
CancellationToken = cancellationToken,
LogErrorResponseBody = true
};
httpOptions.RequestHeaders["token"] = token;
var programsID = dailySchedules.SelectMany(d => d.programs.Select(s => s.programID)).Distinct(); var programIdsWithImages =
httpOptions.RequestContent = "[\"" + string.Join("\", \"", programsID) + "\"]"; programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID)
.ToList();
using (var innerResponse = await Post(httpOptions, true, info).ConfigureAwait(false)) var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
{
var programDetails = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ProgramDetails>>(innerResponse.Content).ConfigureAwait(false);
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
var programIdsWithImages = var programsInfo = new List<ProgramInfo>();
programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID) foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs))
.ToList(); {
// _logger.LogDebug("Proccesing Schedule for statio ID " + stationID +
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); // " which corresponds to channel " + channelNumber + " and program id " +
// schedule.programID + " which says it has images? " +
// programDict[schedule.programID].hasImageArtwork);
var programsInfo = new List<ProgramInfo>(); if (images != null)
foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs)) {
var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
if (imageIndex > -1)
{ {
// _logger.LogDebug("Proccesing Schedule for statio ID " + stationID + var programEntry = programDict[schedule.programID];
// " which corresponds to channel " + channelNumber + " and program id " +
// schedule.programID + " which says it has images? " +
// programDict[schedule.programID].hasImageArtwork);
if (images != null)
{
var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
if (imageIndex > -1)
{
var programEntry = programDict[schedule.programID];
var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>(); var allImages = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)); var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase));
var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase)); var imagesWithoutText = allImages.Where(i => string.Equals(i.text, "no", StringComparison.OrdinalIgnoreCase));
const double DesiredAspect = 2.0 / 3; const double DesiredAspect = 2.0 / 3;
programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ?? programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ??
GetProgramImage(ApiUrl, allImages, true, DesiredAspect); GetProgramImage(ApiUrl, allImages, true, DesiredAspect);
const double WideAspect = 16.0 / 9; const double WideAspect = 16.0 / 9;
programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect); programEntry.thumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect);
// Don't supply the same image twice // Don't supply the same image twice
if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal)) if (string.Equals(programEntry.primaryImage, programEntry.thumbImage, StringComparison.Ordinal))
{ {
programEntry.thumbImage = null; programEntry.thumbImage = null;
}
programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
// GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
// GetProgramImage(ApiUrl, data, "Banner-LOT", false);
}
} }
programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID])); programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect);
}
return programsInfo; // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
// GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
// GetProgramImage(ApiUrl, data, "Banner-LOT", false);
}
} }
programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
} }
return programsInfo;
} }
private static int GetSizeOrder(ScheduleDirect.ImageData image) private static int GetSizeOrder(ScheduleDirect.ImageData image)
@ -483,22 +468,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
imageIdString = imageIdString.TrimEnd(',') + "]"; imageIdString = imageIdString.TrimEnd(',') + "]";
var httpOptions = new HttpRequestOptions() using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs");
{ message.Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json);
Url = ApiUrl + "/metadata/programs",
UserAgent = UserAgent,
CancellationToken = cancellationToken,
RequestContent = imageIdString,
LogErrorResponseBody = true,
};
try try
{ {
using (var innerResponse2 = await Post(httpOptions, true, info).ConfigureAwait(false)) using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
{ await using var response = await innerResponse2.Content.ReadAsStreamAsync();
return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>( return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>(
innerResponse2.Content).ConfigureAwait(false); response).ConfigureAwait(false);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -519,41 +497,33 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return lineups; return lineups;
} }
var options = new HttpRequestOptions() using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/headends?country=" + country + "&postalcode=" + location);
{ options.Headers.TryAddWithoutValidation("token", token);
Url = ApiUrl + "/headends?country=" + country + "&postalcode=" + location,
UserAgent = UserAgent,
CancellationToken = cancellationToken,
LogErrorResponseBody = true
};
options.RequestHeaders["token"] = token;
try try
{ {
using (var httpResponse = await Get(options, false, info).ConfigureAwait(false)) using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
using (Stream responce = httpResponse.Content) await using var response = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
{
var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(responce).ConfigureAwait(false); var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(response).ConfigureAwait(false);
if (root != null) if (root != null)
{
foreach (ScheduleDirect.Headends headend in root)
{ {
foreach (ScheduleDirect.Headends headend in root) foreach (ScheduleDirect.Lineup lineup in headend.lineups)
{ {
foreach (ScheduleDirect.Lineup lineup in headend.lineups) lineups.Add(new NameIdPair
{ {
lineups.Add(new NameIdPair Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name,
{ Id = lineup.uri.Substring(18)
Name = string.IsNullOrWhiteSpace(lineup.name) ? lineup.lineup : lineup.name, });
Id = lineup.uri.Substring(18)
});
}
} }
} }
else }
{ else
_logger.LogInformation("No lineups available"); {
} _logger.LogInformation("No lineups available");
} }
} }
catch (Exception ex) catch (Exception ex)
@ -634,17 +604,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
} }
} }
private async Task<HttpResponseInfo> Post( private async Task<HttpResponseMessage> Send(
HttpRequestOptions options, HttpRequestMessage options,
bool enableRetry, bool enableRetry,
ListingsProviderInfo providerInfo) ListingsProviderInfo providerInfo,
CancellationToken cancellationToken,
HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead)
{ {
// Schedules direct requires that the client support compression and will return a 400 response without it
options.DecompressionMethod = CompressionMethods.Deflate;
try try
{ {
return await _httpClient.Post(options).ConfigureAwait(false); return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, completionOption, cancellationToken).ConfigureAwait(false);
} }
catch (HttpException ex) catch (HttpException ex)
{ {
@ -661,39 +630,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
} }
} }
options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false); options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false));
return await Post(options, false, providerInfo).ConfigureAwait(false); return await Send(options, false, providerInfo, cancellationToken).ConfigureAwait(false);
}
private async Task<HttpResponseInfo> Get(
HttpRequestOptions options,
bool enableRetry,
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
options.DecompressionMethod = CompressionMethods.Deflate;
try
{
return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
}
catch (HttpException ex)
{
_tokens.Clear();
if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500)
{
enableRetry = false;
}
if (!enableRetry)
{
throw;
}
}
options.RequestHeaders["token"] = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
return await Get(options, false, providerInfo).ConfigureAwait(false);
} }
private async Task<string> GetTokenInternal( private async Task<string> GetTokenInternal(
@ -701,28 +639,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
string password, string password,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var httpOptions = new HttpRequestOptions() using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
{ options.Content = new StringContent("{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}", Encoding.UTF8, MediaTypeNames.Application.Json);
Url = ApiUrl + "/token",
UserAgent = UserAgent,
RequestContent = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}",
CancellationToken = cancellationToken,
LogErrorResponseBody = true
};
// _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
// httpOptions.RequestContent);
using (var response = await Post(httpOptions, false, null).ConfigureAwait(false)) using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(stream).ConfigureAwait(false);
if (root.message == "OK")
{ {
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(response.Content).ConfigureAwait(false); _logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
if (root.message == "OK") return root.token;
{
_logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
return root.token;
}
throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
} }
throw new Exception("Could not authenticate with Schedules Direct Error: " + root.message);
} }
private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken) private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken)
@ -741,20 +670,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_logger.LogInformation("Adding new LineUp "); _logger.LogInformation("Adding new LineUp ");
var httpOptions = new HttpRequestOptions() using var options = new HttpRequestMessage(HttpMethod.Put, ApiUrl + "/lineups/" + info.ListingsId);
{ options.Headers.TryAddWithoutValidation("token", token);
Url = ApiUrl + "/lineups/" + info.ListingsId, using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
UserAgent = UserAgent,
CancellationToken = cancellationToken,
LogErrorResponseBody = true,
BufferContent = false
};
httpOptions.RequestHeaders["token"] = token;
using (await _httpClient.SendAsync(httpOptions, HttpMethod.Put).ConfigureAwait(false))
{
}
} }
private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken) private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken)
@ -773,25 +691,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_logger.LogInformation("Headends on account "); _logger.LogInformation("Headends on account ");
var options = new HttpRequestOptions() using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups");
{ options.Headers.TryAddWithoutValidation("token", token);
Url = ApiUrl + "/lineups",
UserAgent = UserAgent,
CancellationToken = cancellationToken,
LogErrorResponseBody = true
};
options.RequestHeaders["token"] = token;
try try
{ {
using (var httpResponse = await Get(options, false, null).ConfigureAwait(false)) using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
using (var response = httpResponse.Content) await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
{ using var response = httpResponse.Content;
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(response).ConfigureAwait(false); var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(stream).ConfigureAwait(false);
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase)); return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
}
} }
catch (HttpException ex) catch (HttpException ex)
{ {
@ -856,55 +766,43 @@ namespace Emby.Server.Implementations.LiveTv.Listings
throw new Exception("token required"); throw new Exception("token required");
} }
var httpOptions = new HttpRequestOptions() using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId);
{ options.Headers.TryAddWithoutValidation("token", token);
Url = ApiUrl + "/lineups/" + listingsId,
UserAgent = UserAgent,
CancellationToken = cancellationToken,
LogErrorResponseBody = true,
};
httpOptions.RequestHeaders["token"] = token;
var list = new List<ChannelInfo>(); var list = new List<ChannelInfo>();
using (var httpResponse = await Get(httpOptions, true, info).ConfigureAwait(false)) using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
using (var response = httpResponse.Content) await using var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
{ var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(stream).ConfigureAwait(false);
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(response).ConfigureAwait(false); _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count); _logger.LogInformation("Mapping Stations to Channel");
_logger.LogInformation("Mapping Stations to Channel");
var allStations = root.stations ?? Enumerable.Empty<ScheduleDirect.Station>();
foreach (ScheduleDirect.Map map in root.map) var allStations = root.stations ?? Enumerable.Empty<ScheduleDirect.Station>();
{
var channelNumber = GetChannelNumber(map);
var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); foreach (ScheduleDirect.Map map in root.map)
if (station == null) {
{ var channelNumber = GetChannelNumber(map);
station = new ScheduleDirect.Station
{
stationID = map.stationID
};
}
var channelInfo = new ChannelInfo var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
{ if (station == null)
Id = station.stationID, {
CallSign = station.callsign, station = new ScheduleDirect.Station { stationID = map.stationID };
Number = channelNumber, }
Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
};
if (station.logo != null) var channelInfo = new ChannelInfo
{ {
channelInfo.ImageUrl = station.logo.URL; Id = station.stationID,
} CallSign = station.callsign,
Number = channelNumber,
Name = string.IsNullOrWhiteSpace(station.name) ? channelNumber : station.name
};
list.Add(channelInfo); if (station.logo != null)
{
channelInfo.ImageUrl = station.logo.URL;
} }
list.Add(channelInfo);
} }
return list; return list;

@ -25,20 +25,20 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public class XmlTvListingsProvider : IListingsProvider public class XmlTvListingsProvider : IListingsProvider
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<XmlTvListingsProvider> _logger; private readonly ILogger<XmlTvListingsProvider> _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IZipClient _zipClient; private readonly IZipClient _zipClient;
public XmlTvListingsProvider( public XmlTvListingsProvider(
IServerConfigurationManager config, IServerConfigurationManager config,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
ILogger<XmlTvListingsProvider> logger, ILogger<XmlTvListingsProvider> logger,
IFileSystem fileSystem, IFileSystem fileSystem,
IZipClient zipClient) IZipClient zipClient)
{ {
_config = config; _config = config;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_zipClient = zipClient; _zipClient = zipClient;
@ -78,28 +78,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
using (var res = await _httpClient.SendAsync( using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(path, cancellationToken).ConfigureAwait(false);
new HttpRequestOptions await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
{ await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew))
CancellationToken = cancellationToken,
Url = path,
DecompressionMethod = CompressionMethods.Gzip,
},
HttpMethod.Get).ConfigureAwait(false))
using (var stream = res.Content)
using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew))
{ {
if (res.ContentHeaders.ContentEncoding.Contains("gzip")) await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
{
using (var gzStream = new GZipStream(stream, CompressionMode.Decompress))
{
await gzStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
}
else
{
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
} }
return UnzipIfNeeded(path, cacheFile); return UnzipIfNeeded(path, cacheFile);

@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IServerConfigurationManager config, IServerConfigurationManager config,
ILogger<HdHomerunHost> logger, ILogger<HdHomerunHost> logger,
IFileSystem fileSystem, IFileSystem fileSystem,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
IServerApplicationHost appHost, IServerApplicationHost appHost,
ISocketFactory socketFactory, ISocketFactory socketFactory,
INetworkManager networkManager, INetworkManager networkManager,
@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
IMemoryCache memoryCache) IMemoryCache memoryCache)
: base(config, logger, fileSystem, memoryCache) : base(config, logger, fileSystem, memoryCache)
{ {
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_appHost = appHost; _appHost = appHost;
_socketFactory = socketFactory; _socketFactory = socketFactory;
_networkManager = networkManager; _networkManager = networkManager;
@ -71,15 +71,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
var options = new HttpRequestOptions using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
{ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
Url = model.LineupURL,
CancellationToken = cancellationToken,
BufferContent = false
};
using var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
await using var stream = response.Content;
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, cancellationToken: cancellationToken) var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false) ?? new List<Channels>(); .ConfigureAwait(false) ?? new List<Channels>();
@ -133,14 +126,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try try
{ {
using var response = await _httpClient.SendAsync( using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
new HttpRequestOptions .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
{ .ConfigureAwait(false);
Url = string.Format(CultureInfo.InvariantCulture, "{0}/discover.json", GetApiUrl(info)), await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
CancellationToken = cancellationToken,
BufferContent = false
}, HttpMethod.Get).ConfigureAwait(false);
await using var stream = response.Content;
var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, cancellationToken: cancellationToken) var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, cancellationToken: cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -183,48 +172,41 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
using (var response = await _httpClient.SendAsync( using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
new HttpRequestOptions() .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
{ .ConfigureAwait(false);
Url = string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
CancellationToken = cancellationToken, using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
BufferContent = false var tuners = new List<LiveTvTunerInfo>();
}, while (!sr.EndOfStream)
HttpMethod.Get).ConfigureAwait(false)) {
using (var stream = response.Content) string line = StripXML(sr.ReadLine());
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8)) if (line.Contains("Channel", StringComparison.Ordinal))
{
var tuners = new List<LiveTvTunerInfo>();
while (!sr.EndOfStream)
{ {
string line = StripXML(sr.ReadLine()); LiveTvTunerStatus status;
if (line.Contains("Channel", StringComparison.Ordinal)) var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
var name = line.Substring(0, index - 1);
var currentChannel = line.Substring(index + 7);
if (currentChannel != "none")
{ {
LiveTvTunerStatus status; status = LiveTvTunerStatus.LiveTv;
var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase); }
var name = line.Substring(0, index - 1); else
var currentChannel = line.Substring(index + 7); {
if (currentChannel != "none") status = LiveTvTunerStatus.Available;
{
status = LiveTvTunerStatus.LiveTv;
}
else
{
status = LiveTvTunerStatus.Available;
}
tuners.Add(new LiveTvTunerInfo
{
Name = name,
SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
ProgramName = currentChannel,
Status = status
});
} }
}
return tuners; tuners.Add(new LiveTvTunerInfo
{
Name = name,
SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
ProgramName = currentChannel,
Status = status
});
}
} }
return tuners;
} }
private static string StripXML(string source) private static string StripXML(string source)
@ -634,7 +616,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
info, info,
streamId, streamId,
FileSystem, FileSystem,
_httpClient, _httpClientFactory,
Logger, Logger,
Config, Config,
_appHost, _appHost,

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
@ -37,14 +38,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
ILogger<M3UTunerHost> logger, ILogger<M3UTunerHost> logger,
IFileSystem fileSystem, IFileSystem fileSystem,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
IServerApplicationHost appHost, IServerApplicationHost appHost,
INetworkManager networkManager, INetworkManager networkManager,
IStreamHelper streamHelper, IStreamHelper streamHelper,
IMemoryCache memoryCache) IMemoryCache memoryCache)
: base(config, logger, fileSystem, memoryCache) : base(config, logger, fileSystem, memoryCache)
{ {
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_appHost = appHost; _appHost = appHost;
_networkManager = networkManager; _networkManager = networkManager;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
var channelIdPrefix = GetFullChannelIdPrefix(info); var channelIdPrefix = GetFullChannelIdPrefix(info);
return await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false); return await new M3uParser(Logger, _httpClientFactory, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
} }
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken) public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
@ -116,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{ {
return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper); return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
} }
} }
@ -125,7 +126,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info) public async Task Validate(TunerHostInfo info)
{ {
using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false)) using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
{ {
} }
} }

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -19,13 +20,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class M3uParser public class M3uParser
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost) public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory, IServerApplicationHost appHost)
{ {
_logger = logger; _logger = logger;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_appHost = appHost; _appHost = appHost;
} }
@ -51,13 +52,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{ {
return _httpClient.Get(new HttpRequestOptions return _httpClientFactory.CreateClient(NamedClient.Default)
{ .GetStreamAsync(url);
Url = url,
CancellationToken = cancellationToken,
// Some data providers will require a user agent
UserAgent = _appHost.ApplicationUserAgent
});
} }
return Task.FromResult((Stream)File.OpenRead(url)); return Task.FromResult((Stream)File.OpenRead(url));

@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
public class SharedHttpStream : LiveStream, IDirectStreamProvider public class SharedHttpStream : LiveStream, IDirectStreamProvider
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
public SharedHttpStream( public SharedHttpStream(
@ -29,14 +29,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
TunerHostInfo tunerHostInfo, TunerHostInfo tunerHostInfo,
string originalStreamId, string originalStreamId,
IFileSystem fileSystem, IFileSystem fileSystem,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
ILogger logger, ILogger logger,
IConfigurationManager configurationManager, IConfigurationManager configurationManager,
IServerApplicationHost appHost, IServerApplicationHost appHost,
IStreamHelper streamHelper) IStreamHelper streamHelper)
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper) : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
{ {
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_appHost = appHost; _appHost = appHost;
OriginalStreamId = originalStreamId; OriginalStreamId = originalStreamId;
EnableStreamSharing = true; EnableStreamSharing = true;
@ -55,25 +55,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var typeName = GetType().Name; var typeName = GetType().Name;
Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url); Logger.LogInformation("Opening " + typeName + " Live stream from {0}", url);
var httpRequestOptions = new HttpRequestOptions using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
{ .GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None)
Url = url, .ConfigureAwait(false);
CancellationToken = CancellationToken.None,
BufferContent = false,
DecompressionMethod = CompressionMethods.None
};
foreach (var header in mediaSource.RequiredHttpHeaders)
{
httpRequestOptions.RequestHeaders[header.Key] = header.Value;
}
var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false);
var extension = "ts"; var extension = "ts";
var requiresRemux = false; var requiresRemux = false;
var contentType = response.ContentType ?? string.Empty; var contentType = response.Content.Headers.ContentType.ToString();
if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1) if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1)
{ {
requiresRemux = true; requiresRemux = true;
@ -132,24 +121,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
} }
} }
private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken) private Task StartStreaming(HttpResponseMessage response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{ {
return Task.Run(async () => return Task.Run(async () =>
{ {
try try
{ {
Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath); Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
using (response) using var message = response;
using (var stream = response.Content) await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
using (var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
{ await StreamHelper.CopyToAsync(
await StreamHelper.CopyToAsync( stream,
stream, fileStream,
fileStream, IODefaults.CopyToBufferSize,
IODefaults.CopyToBufferSize, () => Resolve(openTaskCompletionSource),
() => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false);
cancellationToken).ConfigureAwait(false);
}
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
{ {

@ -18,7 +18,7 @@
"MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன", "MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன",
"MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது", "MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது",
"MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது", "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
"Inherit": "மரபரிமையாகப் பெறு", "Inherit": "மரபரிமையாகப் பெறு",
"HeaderRecordingGroups": "பதிவு குழுக்கள்", "HeaderRecordingGroups": "பதிவு குழுக்கள்",
"HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்", "HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
"Folders": "கோப்புறைகள்", "Folders": "கோப்புறைகள்",
@ -31,7 +31,7 @@
"TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு", "TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
"TaskRefreshChannels": "சேனல்களை புதுப்பி", "TaskRefreshChannels": "சேனல்களை புதுப்பி",
"TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி", "TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி",
"TaskRefreshLibrary": "மீடியா நூலகத்தை ஆராய்", "TaskRefreshLibrary": "ஊடக நூலகத்தை ஆராய்",
"TasksChannelsCategory": "இணைய சேனல்கள்", "TasksChannelsCategory": "இணைய சேனல்கள்",
"TasksApplicationCategory": "செயலி", "TasksApplicationCategory": "செயலி",
"TasksLibraryCategory": "நூலகம்", "TasksLibraryCategory": "நூலகம்",
@ -46,7 +46,7 @@
"Sync": "ஒத்திசைவு", "Sync": "ஒத்திசைவு",
"StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.", "StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
"Songs": "பாடல்கள்", "Songs": "பாடல்கள்",
"Shows": "தொடர்கள்", "Shows": "நிகழ்ச்சிகள்",
"ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்", "ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
"ScheduledTaskStartedWithName": "{0} துவங்கியது", "ScheduledTaskStartedWithName": "{0} துவங்கியது",
"ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது", "ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது",
@ -67,20 +67,20 @@
"NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது", "NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது",
"NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது", "NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது",
"NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்", "NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்",
"NameSeasonUnknown": "பருவம் அறியப்படாதவை", "NameSeasonUnknown": "அறியப்படாத பருவம்",
"NameSeasonNumber": "பருவம் {0}", "NameSeasonNumber": "பருவம் {0}",
"NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது", "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது",
"MusicVideos": "இசைப்படங்கள்", "MusicVideos": "இசைப்படங்கள்",
"Music": "இசை", "Music": "இசை",
"Movies": "திரைப்படங்கள்", "Movies": "திரைப்படங்கள்",
"Latest": "புதிய", "Latest": "புதியவை",
"LabelRunningTimeValue": "ஓடும் நேரம்: {0}", "LabelRunningTimeValue": "ஓடும் நேரம்: {0}",
"LabelIpAddressValue": "ஐபி முகவரி: {0}", "LabelIpAddressValue": "ஐபி முகவரி: {0}",
"ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது", "ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது",
"ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது", "ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது",
"HeaderNextUp": "அடுத்ததாக", "HeaderNextUp": "அடுத்தத",
"HeaderLiveTV": "நேரடித் தொலைக்காட்சி", "HeaderLiveTV": "நேரடித் தொலைக்காட்சி",
"HeaderFavoriteSongs": "பிடித்த பாடடுகள்", "HeaderFavoriteSongs": "பிடித்த பாட்கள்",
"HeaderFavoriteShows": "பிடித்த தொடர்கள்", "HeaderFavoriteShows": "பிடித்த தொடர்கள்",
"HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்", "HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்",
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்", "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
@ -93,25 +93,25 @@
"Channels": "சேனல்கள்", "Channels": "சேனல்கள்",
"Books": "புத்தகங்கள்", "Books": "புத்தகங்கள்",
"AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது", "AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
"Artists": "கலைஞர்", "Artists": "கலைஞர்கள்",
"Application": "செயலி", "Application": "செயலி",
"Albums": "ஆல்பங்கள்", "Albums": "ஆல்பங்கள்",
"NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.", "NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
"MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது", "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0} புதுப்பிக்கப்பட்டது",
"TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.", "TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
"UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது", "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
"SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0 } இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன", "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
"TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.", "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
"TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.", "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
"TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.", "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
"TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.", "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.",
"TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.", "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
"TaskCleanLogs": "பதிவு அடைவ சுத்தம் செய்யுங்கள்", "TaskCleanLogs": "பதிவு அடைவ சுத்தம் செய்யுங்கள்",
"TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.", "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் ஊடக நூலகத்தை ஆராய்ந்து மீத்தரவை புதுப்பிக்கும்.",
"TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.", "TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
"ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது", "ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
"UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்", "UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
"HomeVideos": "முகப்பு வீடியோக்கள்", "HomeVideos": "முகப்பு வீடியோக்கள்",
"UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது", "UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது",
"UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது" "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
} }

@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Updates
/// </summary> /// </summary>
private readonly ILogger<InstallationManager> _logger; private readonly ILogger<InstallationManager> _logger;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Updates
ILogger<InstallationManager> logger, ILogger<InstallationManager> logger,
IApplicationHost appHost, IApplicationHost appHost,
IApplicationPaths appPaths, IApplicationPaths appPaths,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
IJsonSerializer jsonSerializer, IJsonSerializer jsonSerializer,
IServerConfigurationManager config, IServerConfigurationManager config,
IFileSystem fileSystem, IFileSystem fileSystem,
@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Updates
_logger = logger; _logger = logger;
_applicationHost = appHost; _applicationHost = appHost;
_appPaths = appPaths; _appPaths = appPaths;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_config = config; _config = config;
_fileSystem = fileSystem; _fileSystem = fileSystem;
@ -116,26 +116,18 @@ namespace Emby.Server.Implementations.Updates
{ {
try try
{ {
using (var response = await _httpClient.SendAsync( using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
new HttpRequestOptions .GetAsync(manifest, cancellationToken).ConfigureAwait(false);
{ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
Url = manifest,
CancellationToken = cancellationToken, try
CacheMode = CacheMode.Unconditional,
CacheLength = TimeSpan.FromMinutes(3)
},
HttpMethod.Get).ConfigureAwait(false))
using (Stream stream = response.Content)
{ {
try return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
{ }
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false); catch (SerializationException ex)
} {
catch (SerializationException ex) _logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest);
{ return Array.Empty<PackageInfo>();
_logger.LogError(ex, "Failed to deserialize the plugin manifest retrieved from {Manifest}", manifest);
return Array.Empty<PackageInfo>();
}
} }
} }
catch (UriFormatException ex) catch (UriFormatException ex)
@ -360,42 +352,34 @@ namespace Emby.Server.Implementations.Updates
// Always override the passed-in target (which is a file) and figure it out again // Always override the passed-in target (which is a file) and figure it out again
string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name); string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.GetAsync(package.SourceUrl, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
// CA5351: Do Not Use Broken Cryptographic Algorithms // CA5351: Do Not Use Broken Cryptographic Algorithms
#pragma warning disable CA5351 #pragma warning disable CA5351
using (var res = await _httpClient.SendAsync( using var md5 = MD5.Create();
new HttpRequestOptions cancellationToken.ThrowIfCancellationRequested();
{
Url = package.SourceUrl,
CancellationToken = cancellationToken,
// We need it to be buffered for setting the position
BufferContent = true
},
HttpMethod.Get).ConfigureAwait(false))
using (var stream = res.Content)
using (var md5 = MD5.Create())
{
cancellationToken.ThrowIfCancellationRequested();
var hash = Hex.Encode(md5.ComputeHash(stream)); var hash = Hex.Encode(md5.ComputeHash(stream));
if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase))
{ {
_logger.LogError( _logger.LogError(
"The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}", "The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}",
package.Name, package.Name,
package.Checksum, package.Checksum,
hash); hash);
throw new InvalidDataException("The checksum of the received data doesn't match."); throw new InvalidDataException("The checksum of the received data doesn't match.");
} }
if (Directory.Exists(targetDir))
{
Directory.Delete(targetDir, true);
}
stream.Position = 0; if (Directory.Exists(targetDir))
_zipClient.ExtractAllFromZip(stream, targetDir, true); {
Directory.Delete(targetDir, true);
} }
stream.Position = 0;
_zipClient.ExtractAllFromZip(stream, targetDir, true);
#pragma warning restore CA5351 #pragma warning restore CA5351
} }

@ -1,4 +1,5 @@
using System.Net.Mime; using System.Net.Mime;
using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api namespace Jellyfin.Api
@ -8,7 +9,10 @@ namespace Jellyfin.Api
/// </summary> /// </summary>
[ApiController] [ApiController]
[Route("[controller]")] [Route("[controller]")]
[Produces(MediaTypeNames.Application.Json)] [Produces(
MediaTypeNames.Application.Json,
JsonDefaults.CamelCaseMediaType,
JsonDefaults.PascalCaseMediaType)]
public class BaseJellyfinApiController : ControllerBase public class BaseJellyfinApiController : ControllerBase
{ {
} }

@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -28,38 +29,20 @@ namespace Jellyfin.Api.Controllers
{ {
private readonly ILogger<DashboardController> _logger; private readonly ILogger<DashboardController> _logger;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IConfiguration _appConfig;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IResourceFileManager _resourceFileManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DashboardController"/> class. /// Initializes a new instance of the <see cref="DashboardController"/> class.
/// </summary> /// </summary>
/// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param> /// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param> /// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="appConfig">Instance of <see cref="IConfiguration"/> interface.</param>
/// <param name="resourceFileManager">Instance of <see cref="IResourceFileManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
public DashboardController( public DashboardController(
ILogger<DashboardController> logger, ILogger<DashboardController> logger,
IServerApplicationHost appHost, IServerApplicationHost appHost)
IConfiguration appConfig,
IResourceFileManager resourceFileManager,
IServerConfigurationManager serverConfigurationManager)
{ {
_logger = logger; _logger = logger;
_appHost = appHost; _appHost = appHost;
_appConfig = appConfig;
_resourceFileManager = resourceFileManager;
_serverConfigurationManager = serverConfigurationManager;
} }
/// <summary>
/// Gets the path of the directory containing the static web interface content, or null if the server is not
/// hosting the web client.
/// </summary>
private string? WebClientUiPath => GetWebClientUiPath(_appConfig, _serverConfigurationManager);
/// <summary> /// <summary>
/// Gets the configuration pages. /// Gets the configuration pages.
/// </summary> /// </summary>
@ -172,86 +155,6 @@ namespace Jellyfin.Api.Controllers
return NotFound(); return NotFound();
} }
/// <summary>
/// Gets the robots.txt.
/// </summary>
/// <response code="200">Robots.txt returned.</response>
/// <returns>The robots.txt.</returns>
[HttpGet("robots.txt")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ApiExplorerSettings(IgnoreApi = true)]
public ActionResult GetRobotsTxt()
{
return GetWebClientResource("robots.txt");
}
/// <summary>
/// Gets a resource from the web client.
/// </summary>
/// <param name="resourceName">The resource name.</param>
/// <response code="200">Web client returned.</response>
/// <response code="404">Server does not host a web client.</response>
/// <returns>The resource.</returns>
[HttpGet("web/{*resourceName}")]
[ApiExplorerSettings(IgnoreApi = true)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetWebClientResource([FromRoute] string resourceName)
{
if (!_appConfig.HostWebClient() || WebClientUiPath == null)
{
return NotFound("Server does not host a web client.");
}
var path = resourceName;
var basePath = WebClientUiPath;
var requestPathAndQuery = Request.GetEncodedPathAndQuery();
// Bounce them to the startup wizard if it hasn't been completed yet
if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted
&& !requestPathAndQuery.Contains("wizard", StringComparison.OrdinalIgnoreCase)
&& requestPathAndQuery.Contains("index", StringComparison.OrdinalIgnoreCase))
{
return Redirect("index.html?start=wizard#!/wizardstart.html");
}
return PhysicalFile(_resourceFileManager.GetResourcePath(basePath, path), MimeTypes.GetMimeType(path));
}
/// <summary>
/// Gets the favicon.
/// </summary>
/// <response code="200">Favicon.ico returned.</response>
/// <returns>The favicon.</returns>
[HttpGet("favicon.ico")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ApiExplorerSettings(IgnoreApi = true)]
public ActionResult GetFavIcon()
{
return GetWebClientResource("favicon.ico");
}
/// <summary>
/// Gets the path of the directory containing the static web interface content.
/// </summary>
/// <param name="appConfig">The app configuration.</param>
/// <param name="serverConfigManager">The server configuration manager.</param>
/// <returns>The directory path, or null if the server is not hosting the web client.</returns>
public static string? GetWebClientUiPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager)
{
if (!appConfig.HostWebClient())
{
return null;
}
if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath))
{
return serverConfigManager.Configuration.DashboardSourcePath;
}
return serverConfigManager.ApplicationPaths.WebPath;
}
private IEnumerable<ConfigurationPageInfo> GetConfigPages(IPlugin plugin) private IEnumerable<ConfigurationPageInfo> GetConfigPages(IPlugin plugin)
{ {
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1)); return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1));

@ -17,6 +17,7 @@ using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
@ -1071,7 +1072,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Application.Json)] [ProducesFile(MediaTypeNames.Application.Json)]
public async Task<ActionResult> GetSchedulesDirectCountries() public async Task<ActionResult> GetSchedulesDirectCountries()
{ {
var client = _httpClientFactory.CreateClient(); var client = _httpClientFactory.CreateClient(NamedClient.Default);
// https://json.schedulesdirect.org/20141201/available/countries // https://json.schedulesdirect.org/20141201/available/countries
// Can't dispose the response as it's required up the call chain. // Can't dispose the response as it's required up the call chain.
var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries") var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -246,7 +247,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Task.</returns> /// <returns>Task.</returns>
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath) private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
{ {
var httpClient = _httpClientFactory.CreateClient(); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
using var response = await httpClient.GetAsync(url).ConfigureAwait(false); using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last(); var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
var fullCachePath = GetFullCachePath(urlHash + "." + ext); var fullCachePath = GetFullCachePath(urlHash + "." + ext);

@ -12,6 +12,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -475,7 +476,7 @@ namespace Jellyfin.Api.Controllers
{ {
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
var httpClient = _httpClientFactory.CreateClient(); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false); return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false);
} }

@ -3,6 +3,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -138,7 +139,7 @@ namespace Jellyfin.Api.Helpers
{ {
StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);
var httpClient = _httpClientFactory.CreateClient(); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false); return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false);
} }

@ -1,56 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace Jellyfin.Api
{
/// <summary>
/// Route prefixing for ASP.NET MVC.
/// </summary>
public static class MvcRoutePrefix
{
/// <summary>
/// Adds route prefixes to the MVC conventions.
/// </summary>
/// <param name="opts">The MVC options.</param>
/// <param name="prefixes">The list of prefixes.</param>
public static void UseGeneralRoutePrefix(this MvcOptions opts, params string[] prefixes)
{
opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes));
}
private class RoutePrefixConvention : IApplicationModelConvention
{
private readonly AttributeRouteModel[] _routePrefixes;
public RoutePrefixConvention(IEnumerable<string> prefixes)
{
_routePrefixes = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p))).ToArray();
}
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
if (controller.Selectors == null)
{
continue;
}
var newSelectors = new List<SelectorModel>();
foreach (var selector in controller.Selectors)
{
newSelectors.AddRange(_routePrefixes.Select(routePrefix => new SelectorModel(selector)
{
AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(routePrefix, selector.AttributeRouteModel)
}));
}
controller.Selectors.Clear();
newSelectors.ForEach(selector => controller.Selectors.Add(selector));
}
}
}
}
}

@ -1,6 +1,8 @@
using System.Collections.Generic;
using Jellyfin.Server.Middleware; using Jellyfin.Server.Middleware;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.OpenApi.Models;
namespace Jellyfin.Server.Extensions namespace Jellyfin.Server.Extensions
{ {
@ -23,6 +25,7 @@ namespace Jellyfin.Server.Extensions
// specifying the Swagger JSON endpoint. // specifying the Swagger JSON endpoint.
var baseUrl = serverConfigurationManager.Configuration.BaseUrl.Trim('/'); var baseUrl = serverConfigurationManager.Configuration.BaseUrl.Trim('/');
var apiDocBaseUrl = serverConfigurationManager.Configuration.BaseUrl;
if (!string.IsNullOrEmpty(baseUrl)) if (!string.IsNullOrEmpty(baseUrl))
{ {
baseUrl += '/'; baseUrl += '/';
@ -32,21 +35,25 @@ namespace Jellyfin.Server.Extensions
.UseSwagger(c => .UseSwagger(c =>
{ {
// Custom path requires {documentName}, SwaggerDoc documentName is 'api-docs' // Custom path requires {documentName}, SwaggerDoc documentName is 'api-docs'
c.RouteTemplate = $"/{baseUrl}{{documentName}}/openapi.json"; c.RouteTemplate = "{documentName}/openapi.json";
c.PreSerializeFilters.Add((swagger, httpReq) =>
{
swagger.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{apiDocBaseUrl}" } };
});
}) })
.UseSwaggerUI(c => .UseSwaggerUI(c =>
{ {
c.DocumentTitle = "Jellyfin API"; c.DocumentTitle = "Jellyfin API";
c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API"); c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
c.RoutePrefix = $"{baseUrl}api-docs/swagger";
c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css"); c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css");
c.RoutePrefix = "api-docs/swagger";
}) })
.UseReDoc(c => .UseReDoc(c =>
{ {
c.DocumentTitle = "Jellyfin API"; c.DocumentTitle = "Jellyfin API";
c.SpecUrl($"/{baseUrl}api-docs/openapi.json"); c.SpecUrl($"/{baseUrl}api-docs/openapi.json");
c.RoutePrefix = $"{baseUrl}api-docs/redoc";
c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css"); c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css");
c.RoutePrefix = "api-docs/redoc";
}); });
} }

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Jellyfin.Api;
using Jellyfin.Api.Auth; using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Auth.DownloadPolicy; using Jellyfin.Api.Auth.DownloadPolicy;
@ -19,7 +18,6 @@ using Jellyfin.Api.Controllers;
using Jellyfin.Server.Filters; using Jellyfin.Server.Filters;
using Jellyfin.Server.Formatters; using Jellyfin.Server.Formatters;
using Jellyfin.Server.Models; using Jellyfin.Server.Models;
using MediaBrowser.Common;
using MediaBrowser.Common.Json; using MediaBrowser.Common.Json;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
@ -136,10 +134,9 @@ namespace Jellyfin.Server.Extensions
/// Extension method for adding the jellyfin API to the service collection. /// Extension method for adding the jellyfin API to the service collection.
/// </summary> /// </summary>
/// <param name="serviceCollection">The service collection.</param> /// <param name="serviceCollection">The service collection.</param>
/// <param name="baseUrl">The base url for the API.</param> /// <param name="pluginAssemblies">An IEnumerable containing all plugin assemblies with API controllers.</param>
/// <param name="pluginAssemblies">An IEnumberable containing all plugin assemblies with API controllers.</param>
/// <returns>The MVC builder.</returns> /// <returns>The MVC builder.</returns>
public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl, IEnumerable<Assembly> pluginAssemblies) public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable<Assembly> pluginAssemblies)
{ {
IMvcBuilder mvcBuilder = serviceCollection IMvcBuilder mvcBuilder = serviceCollection
.AddCors(options => .AddCors(options =>
@ -152,7 +149,9 @@ namespace Jellyfin.Server.Extensions
}) })
.AddMvc(opts => .AddMvc(opts =>
{ {
opts.UseGeneralRoutePrefix(baseUrl); // Allow requester to change between camelCase and PascalCase
opts.RespectBrowserAcceptHeader = true;
opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter()); opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter());
opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter()); opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());

@ -15,7 +15,7 @@ namespace Jellyfin.Server.Formatters
public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions()) public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions())
{ {
SupportedMediaTypes.Clear(); SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\"")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType));
} }
} }
} }

@ -1,3 +1,4 @@
using System.Net.Mime;
using MediaBrowser.Common.Json; using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -16,8 +17,8 @@ namespace Jellyfin.Server.Formatters
{ {
SupportedMediaTypes.Clear(); SupportedMediaTypes.Clear();
// Add application/json for default formatter // Add application/json for default formatter
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json));
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"PascalCase\"")); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.PascalCaseMediaType));
} }
} }
} }

@ -16,8 +16,9 @@ namespace Jellyfin.Server.Formatters
/// </summary> /// </summary>
public XmlOutputFormatter() public XmlOutputFormatter()
{ {
SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeNames.Text.Xml); SupportedMediaTypes.Add(MediaTypeNames.Text.Xml);
SupportedMediaTypes.Add("text/xml;charset=UTF-8");
SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode); SupportedEncodings.Add(Encoding.Unicode);
} }

@ -1,36 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Server.Implementations;
using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace Jellyfin.Server.HealthChecks
{
/// <summary>
/// Checks connectivity to the database.
/// </summary>
public class JellyfinDbHealthCheck : IHealthCheck
{
private readonly JellyfinDbProvider _dbProvider;
/// <summary>
/// Initializes a new instance of the <see cref="JellyfinDbHealthCheck"/> class.
/// </summary>
/// <param name="dbProvider">The jellyfin db provider.</param>
public JellyfinDbHealthCheck(JellyfinDbProvider dbProvider)
{
_dbProvider = dbProvider;
}
/// <inheritdoc />
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
await using var jellyfinDb = _dbProvider.CreateContext();
if (await jellyfinDb.Database.CanConnectAsync(cancellationToken).ConfigureAwait(false))
{
return HealthCheckResult.Healthy("Database connection successful.");
}
return HealthCheckResult.Unhealthy("Unable to connect to the database.");
}
}
}

@ -44,6 +44,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.7" />
<PackageReference Include="prometheus-net" Version="3.6.0" /> <PackageReference Include="prometheus-net" Version="3.6.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" /> <PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" /> <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />

@ -44,11 +44,7 @@ namespace Jellyfin.Server.Middleware
var localPath = httpContext.Request.Path.ToString(); var localPath = httpContext.Request.Path.ToString();
var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl; var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl;
if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) if (!localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
|| string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.IsNullOrEmpty(localPath)
|| !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
{ {
// Always redirect back to the default path if the base prefix is invalid or missing // Always redirect back to the default path if the base prefix is invalid or missing
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);

@ -169,7 +169,7 @@ namespace Jellyfin.Server
// If hosting the web client, validate the client content path // If hosting the web client, validate the client content path
if (startupConfig.HostWebClient()) if (startupConfig.HostWebClient())
{ {
string? webContentPath = DashboardController.GetWebClientUiPath(startupConfig, appHost.ServerConfigurationManager); string? webContentPath = appHost.ServerConfigurationManager.ApplicationPaths.WebPath;
if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0) if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(

@ -3,15 +3,18 @@ using System.ComponentModel;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using Jellyfin.Api.TypeConverters; using Jellyfin.Api.TypeConverters;
using Jellyfin.Server.Extensions; using Jellyfin.Server.Extensions;
using Jellyfin.Server.HealthChecks; using Jellyfin.Server.Implementations;
using Jellyfin.Server.Middleware; using Jellyfin.Server.Middleware;
using Jellyfin.Server.Models; using Jellyfin.Server.Models;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Prometheus; using Prometheus;
@ -50,9 +53,7 @@ namespace Jellyfin.Server
{ {
options.HttpsPort = _serverApplicationHost.HttpsPort; options.HttpsPort = _serverApplicationHost.HttpsPort;
}); });
services.AddJellyfinApi( services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies());
_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'),
_serverApplicationHost.GetApiPluginAssemblies());
services.AddJellyfinApiSwagger(); services.AddJellyfinApiSwagger();
@ -79,7 +80,7 @@ namespace Jellyfin.Server
.ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler()); .ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
services.AddHealthChecks() services.AddHealthChecks()
.AddCheck<JellyfinDbHealthCheck>("JellyfinDb"); .AddDbContextCheck<JellyfinDb>();
} }
/// <summary> /// <summary>
@ -87,57 +88,78 @@ namespace Jellyfin.Server
/// </summary> /// </summary>
/// <param name="app">The application builder.</param> /// <param name="app">The application builder.</param>
/// <param name="env">The webhost environment.</param> /// <param name="env">The webhost environment.</param>
/// <param name="appConfig">The application config.</param>
public void Configure( public void Configure(
IApplicationBuilder app, IApplicationBuilder app,
IWebHostEnvironment env) IWebHostEnvironment env,
IConfiguration appConfig)
{ {
if (env.IsDevelopment()) // Only add base url redirection if a base url is set.
if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.BaseUrl))
{ {
app.UseDeveloperExceptionPage(); app.UseBaseUrlRedirection();
} }
app.UseMiddleware<ExceptionMiddleware>(); // Wrap rest of configuration so everything only listens on BaseUrl.
app.Map(_serverConfigurationManager.Configuration.BaseUrl, mainApp =>
{
if (env.IsDevelopment())
{
mainApp.UseDeveloperExceptionPage();
}
app.UseMiddleware<ResponseTimeMiddleware>(); mainApp.UseMiddleware<ExceptionMiddleware>();
app.UseWebSockets(); mainApp.UseMiddleware<ResponseTimeMiddleware>();
app.UseResponseCompression(); mainApp.UseWebSockets();
app.UseCors(ServerCorsPolicy.DefaultPolicyName); mainApp.UseResponseCompression();
if (_serverConfigurationManager.Configuration.RequireHttps mainApp.UseCors(ServerCorsPolicy.DefaultPolicyName);
&& _serverApplicationHost.ListenWithHttps)
{
app.UseHttpsRedirection();
}
app.UseStaticFiles(); if (_serverConfigurationManager.Configuration.RequireHttps
app.UseAuthentication(); && _serverApplicationHost.ListenWithHttps)
app.UseJellyfinApiSwagger(_serverConfigurationManager); {
app.UseRouting(); mainApp.UseHttpsRedirection();
app.UseAuthorization(); }
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
// Must be registered after any middleware that could chagne HTTP response codes or the data will be bad
app.UseHttpMetrics();
}
app.UseLanFiltering(); mainApp.UseStaticFiles();
app.UseIpBasedAccessValidation(); if (appConfig.HostWebClient())
app.UseBaseUrlRedirection(); {
app.UseWebSocketHandler(); mainApp.UseStaticFiles(new StaticFileOptions
app.UseServerStartupMessage(); {
FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
RequestPath = "/web"
});
}
mainApp.UseAuthentication();
mainApp.UseJellyfinApiSwagger(_serverConfigurationManager);
mainApp.UseRouting();
mainApp.UseAuthorization();
mainApp.UseLanFiltering();
mainApp.UseIpBasedAccessValidation();
mainApp.UseWebSocketHandler();
mainApp.UseServerStartupMessage();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
if (_serverConfigurationManager.Configuration.EnableMetrics) if (_serverConfigurationManager.Configuration.EnableMetrics)
{ {
endpoints.MapMetrics(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/metrics"); // Must be registered after any middleware that could change HTTP response codes or the data will be bad
mainApp.UseHttpMetrics();
} }
endpoints.MapHealthChecks(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/health"); mainApp.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
endpoints.MapMetrics("/metrics");
}
endpoints.MapHealthChecks("/health");
});
}); });
// Add type descriptor for legacy datetime parsing. // Add type descriptor for legacy datetime parsing.

@ -1,5 +1,3 @@
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Common.Configuration namespace MediaBrowser.Common.Configuration
{ {
/// <summary> /// <summary>
@ -17,8 +15,7 @@ namespace MediaBrowser.Common.Configuration
/// Gets the path to the web UI resources folder. /// Gets the path to the web UI resources folder.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This value is not relevant if the server is configured to not host any static web content. Additionally, /// This value is not relevant if the server is configured to not host any static web content.
/// the value for <see cref="ServerConfiguration.DashboardSourcePath"/> takes precedence over this one.
/// </remarks> /// </remarks>
string WebPath { get; } string WebPath { get; }

@ -9,6 +9,16 @@ namespace MediaBrowser.Common.Json
/// </summary> /// </summary>
public static class JsonDefaults public static class JsonDefaults
{ {
/// <summary>
/// Pascal case json profile media type.
/// </summary>
public const string PascalCaseMediaType = "application/json; profile=\"PascalCase\"";
/// <summary>
/// Camel case json profile media type.
/// </summary>
public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\"";
/// <summary> /// <summary>
/// Gets the default <see cref="JsonSerializerOptions" /> options. /// Gets the default <see cref="JsonSerializerOptions" /> options.
/// </summary> /// </summary>

@ -1,11 +0,0 @@
#pragma warning disable CS1591
#pragma warning disable SA1602
namespace MediaBrowser.Common.Net
{
public enum CacheMode
{
None = 0,
Unconditional = 1
}
}

@ -1,15 +0,0 @@
#pragma warning disable CS1591
#pragma warning disable SA1602
using System;
namespace MediaBrowser.Common.Net
{
[Flags]
public enum CompressionMethods
{
None = 0b00000001,
Deflate = 0b00000010,
Gzip = 0b00000100
}
}

@ -1,105 +0,0 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Common.Net
{
/// <summary>
/// Class HttpRequestOptions.
/// </summary>
public class HttpRequestOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="HttpRequestOptions"/> class.
/// </summary>
public HttpRequestOptions()
{
RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
CacheMode = CacheMode.None;
DecompressionMethod = CompressionMethods.Deflate;
}
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
public string Url { get; set; }
public CompressionMethods DecompressionMethod { get; set; }
/// <summary>
/// Gets or sets the accept header.
/// </summary>
/// <value>The accept header.</value>
public string AcceptHeader
{
get => GetHeaderValue(HeaderNames.Accept);
set => RequestHeaders[HeaderNames.Accept] = value;
}
/// <summary>
/// Gets or sets the cancellation token.
/// </summary>
/// <value>The cancellation token.</value>
public CancellationToken CancellationToken { get; set; }
/// <summary>
/// Gets or sets the user agent.
/// </summary>
/// <value>The user agent.</value>
public string UserAgent
{
get => GetHeaderValue(HeaderNames.UserAgent);
set => RequestHeaders[HeaderNames.UserAgent] = value;
}
/// <summary>
/// Gets or sets the referrer.
/// </summary>
/// <value>The referrer.</value>
public string Referer
{
get => GetHeaderValue(HeaderNames.Referer);
set => RequestHeaders[HeaderNames.Referer] = value;
}
/// <summary>
/// Gets or sets the host.
/// </summary>
/// <value>The host.</value>
public string Host
{
get => GetHeaderValue(HeaderNames.Host);
set => RequestHeaders[HeaderNames.Host] = value;
}
public Dictionary<string, string> RequestHeaders { get; private set; }
public string RequestContentType { get; set; }
public string RequestContent { get; set; }
public bool BufferContent { get; set; }
public bool LogErrorResponseBody { get; set; }
public bool EnableKeepAlive { get; set; }
public CacheMode CacheMode { get; set; }
public TimeSpan CacheLength { get; set; }
public bool EnableDefaultUserAgent { get; set; }
private string GetHeaderValue(string name)
{
RequestHeaders.TryGetValue(name, out var value);
return value;
}
}
}

@ -1,80 +0,0 @@
using System;
using System.IO;
using System.Net;
using System.Net.Http.Headers;
namespace MediaBrowser.Common.Net
{
/// <summary>
/// Class HttpResponseInfo.
/// </summary>
public sealed class HttpResponseInfo : IDisposable
{
#pragma warning disable CS1591
public HttpResponseInfo()
{
}
public HttpResponseInfo(HttpResponseHeaders headers, HttpContentHeaders contentHeader)
{
Headers = headers;
ContentHeaders = contentHeader;
}
#pragma warning restore CS1591
/// <summary>
/// Gets or sets the type of the content.
/// </summary>
/// <value>The type of the content.</value>
public string ContentType { get; set; }
/// <summary>
/// Gets or sets the response URL.
/// </summary>
/// <value>The response URL.</value>
public string ResponseUrl { get; set; }
/// <summary>
/// Gets or sets the content.
/// </summary>
/// <value>The content.</value>
public Stream Content { get; set; }
/// <summary>
/// Gets or sets the status code.
/// </summary>
/// <value>The status code.</value>
public HttpStatusCode StatusCode { get; set; }
/// <summary>
/// Gets or sets the temp file path.
/// </summary>
/// <value>The temp file path.</value>
public string TempFilePath { get; set; }
/// <summary>
/// Gets or sets the length of the content.
/// </summary>
/// <value>The length of the content.</value>
public long? ContentLength { get; set; }
/// <summary>
/// Gets or sets the headers.
/// </summary>
/// <value>The headers.</value>
public HttpResponseHeaders Headers { get; set; }
/// <summary>
/// Gets or sets the content headers.
/// </summary>
/// <value>The content headers.</value>
public HttpContentHeaders ContentHeaders { get; set; }
/// <inheritdoc />
public void Dispose()
{
// backwards compatibility
}
}
}

@ -1,53 +0,0 @@
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Net
{
/// <summary>
/// Interface IHttpClient.
/// </summary>
public interface IHttpClient
{
/// <summary>
/// Gets the response.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> GetResponse(HttpRequestOptions options);
/// <summary>
/// Gets the specified options.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task{Stream}.</returns>
Task<Stream> Get(HttpRequestOptions options);
/// <summary>
/// Warning: Deprecated function,
/// use 'Task{HttpResponseInfo} SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead
/// Sends the asynchronous.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="httpMethod">The HTTP method.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
[Obsolete("Use 'Task{HttpResponseInfo} SendAsync(HttpRequestOptions options, HttpMethod httpMethod);' instead")]
Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod);
/// <summary>
/// Sends the asynchronous.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="httpMethod">The HTTP method.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, HttpMethod httpMethod);
/// <summary>
/// Posts the specified options.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> Post(HttpRequestOptions options);
}
}

@ -60,8 +60,6 @@ namespace MediaBrowser.Controller.Entities
protected BaseItem() protected BaseItem()
{ {
ThemeSongIds = Array.Empty<Guid>();
ThemeVideoIds = Array.Empty<Guid>();
Tags = Array.Empty<string>(); Tags = Array.Empty<string>();
Genres = Array.Empty<string>(); Genres = Array.Empty<string>();
Studios = Array.Empty<string>(); Studios = Array.Empty<string>();
@ -100,12 +98,52 @@ namespace MediaBrowser.Controller.Entities
}; };
[JsonIgnore] [JsonIgnore]
public Guid[] ThemeSongIds { get; set; } public Guid[] ThemeSongIds
{
get
{
if (_themeSongIds == null)
{
_themeSongIds = GetExtras()
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong)
.Select(song => song.Id)
.ToArray();
}
return _themeSongIds;
}
private set
{
_themeSongIds = value;
}
}
[JsonIgnore] [JsonIgnore]
public Guid[] ThemeVideoIds { get; set; } public Guid[] ThemeVideoIds
{
get
{
if (_themeVideoIds == null)
{
_themeVideoIds = GetExtras()
.Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo)
.Select(song => song.Id)
.ToArray();
}
return _themeVideoIds;
}
private set
{
_themeVideoIds = value;
}
}
[JsonIgnore] [JsonIgnore]
public string PreferredMetadataCountryCode { get; set; } public string PreferredMetadataCountryCode { get; set; }
[JsonIgnore] [JsonIgnore]
public string PreferredMetadataLanguage { get; set; } public string PreferredMetadataLanguage { get; set; }
@ -635,6 +673,9 @@ namespace MediaBrowser.Controller.Entities
} }
private string _sortName; private string _sortName;
private Guid[] _themeSongIds;
private Guid[] _themeVideoIds;
/// <summary> /// <summary>
/// Gets the name of the sort. /// Gets the name of the sort.
/// </summary> /// </summary>
@ -1582,7 +1623,8 @@ namespace MediaBrowser.Controller.Entities
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
item.ThemeVideoIds = newThemeVideoIds; // They are expected to be sorted by SortName
item.ThemeVideoIds = newThemeVideos.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
return themeVideosChanged; return themeVideosChanged;
} }
@ -1619,7 +1661,8 @@ namespace MediaBrowser.Controller.Entities
await Task.WhenAll(tasks).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false);
item.ThemeSongIds = newThemeSongIds; // They are expected to be sorted by SortName
item.ThemeSongIds = newThemeSongs.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
return themeSongsChanged; return themeSongsChanged;
} }
@ -2910,12 +2953,12 @@ namespace MediaBrowser.Controller.Entities
public IEnumerable<BaseItem> GetThemeSongs() public IEnumerable<BaseItem> GetThemeSongs()
{ {
return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeSong)).OrderBy(i => i.SortName); return ThemeSongIds.Select(LibraryManager.GetItemById);
} }
public IEnumerable<BaseItem> GetThemeVideos() public IEnumerable<BaseItem> GetThemeVideos()
{ {
return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeVideo)).OrderBy(i => i.SortName); return ThemeVideoIds.Select(LibraryManager.GetItemById);
} }
/// <summary> /// <summary>

@ -456,6 +456,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isNvencHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
@ -515,6 +516,24 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
} }
if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
{
var isColorDepth10 = IsColorDepth10(state);
if (isNvencHevcDecoder && isColorDepth10
&& _mediaEncoder.SupportsHwaccel("opencl")
&& encodingOptions.EnableTonemapping
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange)
&& state.VideoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
{
arg.Append("-init_hw_device opencl=ocl:")
.Append(encodingOptions.OpenclDevice)
.Append(' ')
.Append("-filter_hw_device ocl ");
}
}
if (state.IsVideoRequest if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) && string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
{ {
@ -1003,11 +1022,33 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
{ {
param = "-pix_fmt yuv420p " + param; param = "-pix_fmt yuv420p " + param;
} }
if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
var videoStream = state.VideoStream;
var isColorDepth10 = IsColorDepth10(state);
if (videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1
&& isColorDepth10
&& _mediaEncoder.SupportsHwaccel("opencl")
&& encodingOptions.EnableTonemapping
&& !string.IsNullOrEmpty(videoStream.VideoRange)
&& videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt nv12 " + param;
}
else
{
param = "-pix_fmt yuv420p " + param;
}
}
if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
{ {
param = "-pix_fmt nv21 " + param; param = "-pix_fmt nv21 " + param;
@ -1611,64 +1652,45 @@ namespace MediaBrowser.Controller.MediaEncoding
var outputSizeParam = ReadOnlySpan<char>.Empty; var outputSizeParam = ReadOnlySpan<char>.Empty;
var request = state.BaseRequest; var request = state.BaseRequest;
// Add resolution params, if specified outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
if (request.Width.HasValue
|| request.Height.HasValue // All possible beginning of video filters
|| request.MaxHeight.HasValue // Don't break the order
|| request.MaxWidth.HasValue) string[] beginOfOutputSizeParam = new[]
{ {
outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); // for tonemap_opencl
"hwupload,tonemap_opencl",
// hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux) // hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux)
var index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase); "hwupload=extra_hw_frames",
// vpp_qsv
"vpp",
// hwdownload,format=p010le (hardware decode + software encode for vaapi)
"hwdownload",
// format=nv12|vaapi,hwupload,scale_vaapi
"format",
// bwdif,scale=expr
"bwdif",
// yadif,scale=expr
"yadif",
// scale=expr
"scale"
};
var index = -1;
foreach (var param in beginOfOutputSizeParam)
{
index = outputSizeParam.IndexOf(param, StringComparison.OrdinalIgnoreCase);
if (index != -1) if (index != -1)
{ {
outputSizeParam = outputSizeParam.Slice(index); outputSizeParam = outputSizeParam.Slice(index);
} break;
else
{
// vpp_qsv
index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{
// hwdownload,format=p010le (hardware decode + software encode for vaapi)
index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{
// format=nv12|vaapi,hwupload,scale_vaapi
index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{
// yadif,scale=expr
index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
else
{
// scale=expr
index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = outputSizeParam.Slice(index);
}
}
}
}
}
} }
} }
@ -1747,9 +1769,9 @@ namespace MediaBrowser.Controller.MediaEncoding
*/ */
if (isLinux) if (isLinux)
{ {
retStr = !outputSizeParam.IsEmpty ? retStr = !outputSizeParam.IsEmpty
" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" : ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""
" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
} }
} }
@ -2084,8 +2106,10 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isColorDepth10 = IsColorDepth10(state);
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@ -2093,6 +2117,50 @@ namespace MediaBrowser.Controller.MediaEncoding
// If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices
var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30; var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30;
// Currently only with the use of NVENC decoder can we get a decent performance.
// Currently only the HEVC/H265 format is supported.
// NVIDIA Pascal and Turing or higher are recommended.
if (isNvdecHevcDecoder && isColorDepth10
&& _mediaEncoder.SupportsHwaccel("opencl")
&& options.EnableTonemapping
&& !string.IsNullOrEmpty(videoStream.VideoRange)
&& videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
{
var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
if (options.TonemappingParam != 0)
{
parameters += ":param={4}";
}
if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
{
parameters += ":range={5}";
}
// Upload the HDR10 or HLG data to the OpenCL device,
// use tonemap_opencl filter for tone mapping,
// and then download the SDR data to memory.
filters.Add("hwupload");
filters.Add(
string.Format(
CultureInfo.InvariantCulture,
parameters,
options.TonemappingAlgorithm,
options.TonemappingDesat,
options.TonemappingThreshold,
options.TonemappingPeak,
options.TonemappingParam,
options.TonemappingRange));
filters.Add("hwdownload");
if (hasGraphicalSubs || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)
|| string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12");
}
}
// When the input may or may not be hardware VAAPI decodable // When the input may or may not be hardware VAAPI decodable
if (isVaapiH264Encoder) if (isVaapiH264Encoder)
{ {
@ -2110,7 +2178,6 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder) else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
{ {
var codec = videoStream.Codec.ToLowerInvariant(); var codec = videoStream.Codec.ToLowerInvariant();
var isColorDepth10 = IsColorDepth10(state);
// Assert 10-bit hardware VAAPI decodable // Assert 10-bit hardware VAAPI decodable
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)

@ -24,6 +24,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BDInfo" Version="0.7.6.1" /> <PackageReference Include="BDInfo" Version="0.7.6.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
<PackageReference Include="UTF.Unknown" Version="2.3.0" /> <PackageReference Include="UTF.Unknown" Version="2.3.0" />
</ItemGroup> </ItemGroup>

@ -282,6 +282,20 @@ namespace MediaBrowser.MediaEncoding.Probing
[JsonPropertyName("disposition")] [JsonPropertyName("disposition")]
public IReadOnlyDictionary<string, int> Disposition { get; set; } public IReadOnlyDictionary<string, int> Disposition { get; set; }
/// <summary>
/// Gets or sets the color range.
/// </summary>
/// <value>The color range.</value>
[JsonPropertyName("color_range")]
public string ColorRange { get; set; }
/// <summary>
/// Gets or sets the color space.
/// </summary>
/// <value>The color space.</value>
[JsonPropertyName("color_space")]
public string ColorSpace { get; set; }
/// <summary> /// <summary>
/// Gets or sets the color transfer. /// Gets or sets the color transfer.
/// </summary> /// </summary>

@ -714,6 +714,16 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.RefFrames = streamInfo.Refs; stream.RefFrames = streamInfo.Refs;
} }
if (!string.IsNullOrEmpty(streamInfo.ColorRange))
{
stream.ColorRange = streamInfo.ColorRange;
}
if (!string.IsNullOrEmpty(streamInfo.ColorSpace))
{
stream.ColorSpace = streamInfo.ColorSpace;
}
if (!string.IsNullOrEmpty(streamInfo.ColorTransfer)) if (!string.IsNullOrEmpty(streamInfo.ColorTransfer))
{ {
stream.ColorTransfer = streamInfo.ColorTransfer; stream.ColorTransfer = streamInfo.ColorTransfer;

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -31,7 +32,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IHttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
/// <summary> /// <summary>
@ -46,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IApplicationPaths appPaths, IApplicationPaths appPaths,
IFileSystem fileSystem, IFileSystem fileSystem,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IHttpClient httpClient, IHttpClientFactory httpClientFactory,
IMediaSourceManager mediaSourceManager) IMediaSourceManager mediaSourceManager)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
@ -54,7 +55,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_appPaths = appPaths; _appPaths = appPaths;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_httpClient = httpClient; _httpClientFactory = httpClientFactory;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
} }
@ -750,24 +751,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles
} }
} }
private Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken) private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{ {
switch (protocol) switch (protocol)
{ {
case MediaProtocol.Http: case MediaProtocol.Http:
var opts = new HttpRequestOptions() {
{ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
Url = path, .GetAsync(path, cancellationToken)
CancellationToken = cancellationToken, .ConfigureAwait(false);
return await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
// Needed for seeking }
BufferContent = true
};
return _httpClient.Get(opts);
case MediaProtocol.File: case MediaProtocol.File:
return Task.FromResult<Stream>(File.OpenRead(path)); return File.OpenRead(path);
default: default:
throw new ArgumentOutOfRangeException(nameof(protocol)); throw new ArgumentOutOfRangeException(nameof(protocol));
} }

@ -31,6 +31,22 @@ namespace MediaBrowser.Model.Configuration
public string VaapiDevice { get; set; } public string VaapiDevice { get; set; }
public string OpenclDevice { get; set; }
public bool EnableTonemapping { get; set; }
public string TonemappingAlgorithm { get; set; }
public string TonemappingRange { get; set; }
public double TonemappingDesat { get; set; }
public double TonemappingThreshold { get; set; }
public double TonemappingPeak { get; set; }
public double TonemappingParam { get; set; }
public int H264Crf { get; set; } public int H264Crf { get; set; }
public int H265Crf { get; set; } public int H265Crf { get; set; }
@ -58,8 +74,19 @@ namespace MediaBrowser.Model.Configuration
EnableThrottling = false; EnableThrottling = false;
ThrottleDelaySeconds = 180; ThrottleDelaySeconds = 180;
EncodingThreadCount = -1; EncodingThreadCount = -1;
// This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything // This is a DRM device that is almost guaranteed to be there on every intel platform,
// plus it's the default one in ffmpeg if you don't specify anything
VaapiDevice = "/dev/dri/renderD128"; VaapiDevice = "/dev/dri/renderD128";
// This is the OpenCL device that is used for tonemapping.
// The left side of the dot is the platform number, and the right side is the device number on the platform.
OpenclDevice = "0.0";
EnableTonemapping = false;
TonemappingAlgorithm = "reinhard";
TonemappingRange = "auto";
TonemappingDesat = 0;
TonemappingThreshold = 0.8;
TonemappingPeak = 0;
TonemappingParam = 0;
H264Crf = 23; H264Crf = 23;
H265Crf = 28; H265Crf = 28;
DeinterlaceDoubleRate = false; DeinterlaceDoubleRate = false;

@ -166,12 +166,6 @@ namespace MediaBrowser.Model.Configuration
/// <value><c>true</c> if [enable dashboard response caching]; otherwise, <c>false</c>.</value> /// <value><c>true</c> if [enable dashboard response caching]; otherwise, <c>false</c>.</value>
public bool EnableDashboardResponseCaching { get; set; } public bool EnableDashboardResponseCaching { get; set; }
/// <summary>
/// Gets or sets a custom path to serve the dashboard from.
/// </summary>
/// <value>The dashboard source path, or null if the default path should be used.</value>
public string DashboardSourcePath { get; set; }
/// <summary> /// <summary>
/// Gets or sets the image saving convention. /// Gets or sets the image saving convention.
/// </summary> /// </summary>

@ -35,6 +35,18 @@ namespace MediaBrowser.Model.Entities
/// <value>The language.</value> /// <value>The language.</value>
public string Language { get; set; } public string Language { get; set; }
/// <summary>
/// Gets or sets the color range.
/// </summary>
/// <value>The color range.</value>
public string ColorRange { get; set; }
/// <summary>
/// Gets or sets the color space.
/// </summary>
/// <value>The color space.</value>
public string ColorSpace { get; set; }
/// <summary> /// <summary>
/// Gets or sets the color transfer. /// Gets or sets the color transfer.
/// </summary> /// </summary>
@ -47,12 +59,6 @@ namespace MediaBrowser.Model.Entities
/// <value>The color primaries.</value> /// <value>The color primaries.</value>
public string ColorPrimaries { get; set; } public string ColorPrimaries { get; set; }
/// <summary>
/// Gets or sets the color space.
/// </summary>
/// <value>The color space.</value>
public string ColorSpace { get; set; }
/// <summary> /// <summary>
/// Gets or sets the comment. /// Gets or sets the comment.
/// </summary> /// </summary>

@ -125,7 +125,7 @@ namespace MediaBrowser.Model.Net
{ ".wmv", "video/x-ms-wmv" }, { ".wmv", "video/x-ms-wmv" },
// Type audio // Type audio
{ ".aac", "audio/mp4" }, { ".aac", "audio/aac" },
{ ".ac3", "audio/ac3" }, { ".ac3", "audio/ac3" },
{ ".ape", "audio/x-ape" }, { ".ape", "audio/x-ape" },
{ ".dsf", "audio/dsf" }, { ".dsf", "audio/dsf" },

@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/> /// <inheritdoc/>
public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken) public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{ {
var httpClient = _httpClientFactory.CreateClient(); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
var contentType = response.Content.Headers.ContentType.MediaType; var contentType = response.Content.Headers.ContentType.MediaType;

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -96,7 +97,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
var httpClient = _httpClientFactory.CreateClient(); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return httpClient.GetAsync(url, cancellationToken); return httpClient.GetAsync(url, cancellationToken);
} }

@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -173,7 +174,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
using var response = await _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -137,7 +138,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
var httpClient = _httpClientFactory.CreateClient(); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return httpClient.GetAsync(url, cancellationToken); return httpClient.GetAsync(url, cancellationToken);
} }

@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -154,7 +155,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
using var response = await _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));

@ -14,6 +14,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -765,7 +766,7 @@ namespace MediaBrowser.Providers.Music
_logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
_stopWatchMusicBrainz.Restart(); _stopWatchMusicBrainz.Restart();
response = await _httpClientFactory.CreateClient().SendAsync(options).ConfigureAwait(false); response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options).ConfigureAwait(false);
// We retry a finite number of times, and only whilst MB is indicating 503 (throttling) // We retry a finite number of times, and only whilst MB is indicating 503 (throttling)
} }

@ -6,6 +6,7 @@ using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
@ -82,7 +83,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
public string Name => "The Open Movie Database"; public string Name => "The Open Movie Database";

@ -9,6 +9,7 @@ using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
@ -129,7 +130,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var url = OmdbProvider.GetOmdbUrl(urlQuery); var url = OmdbProvider.GetOmdbUrl(urlQuery);
using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false); using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var resultList = new List<SearchResult>(); var resultList = new List<SearchResult>();
@ -274,7 +275,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
class SearchResult class SearchResult

@ -10,6 +10,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -296,7 +297,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
"i={0}&plot=short&tomatoes=true&r=json", "i={0}&plot=short&tomatoes=true&r=json",
imdbParam)); imdbParam));
using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false); using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false); var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
@ -334,7 +335,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
imdbParam, imdbParam,
seasonId)); seasonId));
using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false); using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false); var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));

@ -6,6 +6,7 @@ using System.Net.Http;
using System.Globalization; using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -116,7 +117,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -244,7 +245,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
public int Order => 0; public int Order => 0;

@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
@ -106,7 +107,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -148,7 +149,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -146,7 +147,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -8,6 +8,7 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -410,7 +411,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -155,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -10,6 +10,7 @@ using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -271,7 +272,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -7,6 +7,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -204,7 +205,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common; using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
@ -383,7 +384,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
internal Task<HttpResponseMessage> GetMovieDbResponse(HttpRequestMessage message) internal Task<HttpResponseMessage> GetMovieDbResponse(HttpRequestMessage message)
{ {
message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent); message.Headers.UserAgent.ParseAdd(_appHost.ApplicationUserAgent);
return _httpClientFactory.CreateClient().SendAsync(message); return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(message);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -392,7 +393,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
/// <inheritdoc /> /// <inheritdoc />
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -131,7 +132,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -267,7 +268,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -7,6 +7,7 @@ using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -138,7 +139,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
protected Task<HttpResponseMessage> GetResponse(string url, CancellationToken cancellationToken) protected Task<HttpResponseMessage> GetResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -7,6 +7,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -38,7 +39,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)

@ -9,6 +9,7 @@ using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -124,7 +125,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
private async Task<SeasonResult> GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage, private async Task<SeasonResult> GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage,

@ -6,6 +6,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -182,7 +183,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -10,6 +10,7 @@ using System.Net.Http.Headers;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
@ -551,7 +552,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
@ -36,7 +37,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken); return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
} }
} }
} }

@ -8,6 +8,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -122,7 +123,7 @@ namespace MediaBrowser.Providers.Studios
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
{ {
var httpClient = _httpClientFactory.CreateClient(); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return httpClient.GetAsync(url, cancellationToken); return httpClient.GetAsync(url, cancellationToken);
} }
@ -140,7 +141,7 @@ namespace MediaBrowser.Providers.Studios
if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1) if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1)
{ {
var httpClient = _httpClientFactory.CreateClient(); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
Directory.CreateDirectory(Path.GetDirectoryName(file)); Directory.CreateDirectory(Path.GetDirectoryName(file));
await using var response = await httpClient.GetStreamAsync(url).ConfigureAwait(false); await using var response = await httpClient.GetStreamAsync(url).ConfigureAwait(false);

2
debian/rules vendored

@ -40,7 +40,7 @@ override_dh_clistrip:
override_dh_auto_build: override_dh_auto_build:
dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \ dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
"-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
override_dh_auto_clean: override_dh_auto_clean:
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true

@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"

@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"

@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"

@ -16,7 +16,7 @@ else
fi fi
# Build archives # Build archives
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true" dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version} tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version} rm -rf dist/jellyfin-server_${version}

@ -16,7 +16,7 @@ else
fi fi
# Build archives # Build archives
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true" dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version} tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version} rm -rf dist/jellyfin-server_${version}

@ -16,7 +16,7 @@ else
fi fi
# Build archives # Build archives
dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true" dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version} tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version} rm -rf dist/jellyfin-server_${version}

@ -23,7 +23,7 @@ fi
output_dir="dist/jellyfin-server_${version}" output_dir="dist/jellyfin-server_${version}"
# Build binary # Build binary
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true" dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
# Prepare addins # Prepare addins
addin_build_dir="$( mktemp -d )" addin_build_dir="$( mktemp -d )"

@ -54,7 +54,7 @@ The Jellyfin media server backend.
export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \ dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
"-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf %{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json

Loading…
Cancel
Save