diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs
index 140ef9b463..243b097867 100644
--- a/Emby.Dlna/ControlResponse.cs
+++ b/Emby.Dlna/ControlResponse.cs
@@ -16,5 +16,11 @@ namespace Emby.Dlna
public string Xml { get; set; }
public bool IsSuccessful { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return Xml;
+ }
}
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 34818f12d8..cb63cc85ef 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -635,6 +635,9 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton();
ServiceCollection.AddSingleton();
+ ServiceCollection.AddScoped();
+ ServiceCollection.AddScoped();
+ ServiceCollection.AddScoped();
}
///
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index c967e9230e..f2c7118fe0 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -73,25 +73,6 @@ namespace Emby.Server.Implementations.Dto
_livetvManagerFactory = livetvManagerFactory;
}
- ///
- /// Converts a BaseItem to a DTOBaseItem.
- ///
- /// The item.
- /// The fields.
- /// The user.
- /// The owner.
- /// Task{DtoBaseItem}.
- /// item
- public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null)
- {
- var options = new DtoOptions
- {
- Fields = fields
- };
-
- return GetBaseItemDto(item, options, user, owner);
- }
-
///
public IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null)
{
@@ -443,17 +424,6 @@ namespace Emby.Server.Implementations.Dto
return folder.GetChildCount(user);
}
- ///
- /// Gets client-side Id of a server-side BaseItem.
- ///
- /// The item.
- /// System.String.
- /// item
- public string GetDtoId(BaseItem item)
- {
- return item.Id.ToString("N", CultureInfo.InvariantCulture);
- }
-
private static void SetBookProperties(BaseItemDto dto, Book item)
{
dto.SeriesName = item.SeriesName;
@@ -484,6 +454,11 @@ namespace Emby.Server.Implementations.Dto
}
}
+ private string GetDtoId(BaseItem item)
+ {
+ return item.Id.ToString("N", CultureInfo.InvariantCulture);
+ }
+
private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
{
if (!string.IsNullOrEmpty(item.Album))
@@ -513,19 +488,6 @@ namespace Emby.Server.Implementations.Dto
.ToArray();
}
- private string GetImageCacheTag(BaseItem item, ImageType type)
- {
- try
- {
- return _imageProcessor.GetImageCacheTag(item, type);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error getting {type} image info", type);
- return null;
- }
- }
-
private string GetImageCacheTag(BaseItem item, ItemImageInfo image)
{
try
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index f3fc413527..8a0c0043a9 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -19,8 +19,7 @@ namespace Emby.Server.Implementations.LiveTv
public class LiveTvMediaSourceProvider : IMediaSourceProvider
{
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
- private const char StreamIdDelimeter = '_';
- private const string StreamIdDelimeterString = "_";
+ private const char StreamIdDelimiter = '_';
private readonly ILiveTvManager _liveTvManager;
private readonly ILogger _logger;
@@ -47,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- return Task.FromResult>(Array.Empty());
+ return Task.FromResult(Enumerable.Empty());
}
private async Task> GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
@@ -98,7 +97,7 @@ namespace Emby.Server.Implementations.LiveTv
source.Id ?? string.Empty
};
- source.OpenToken = string.Join(StreamIdDelimeterString, openKeys);
+ source.OpenToken = string.Join(StreamIdDelimiter, openKeys);
}
// Dummy this up so that direct play checks can still run
@@ -116,7 +115,7 @@ namespace Emby.Server.Implementations.LiveTv
///
public async Task OpenMediaSource(string openToken, List currentLiveStreams, CancellationToken cancellationToken)
{
- var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
+ var keys = openToken.Split(StreamIdDelimiter, 3);
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json
index ca14d44715..1bd1909827 100644
--- a/Emby.Server.Implementations/Localization/Core/bn.json
+++ b/Emby.Server.Implementations/Localization/Core/bn.json
@@ -1,12 +1,12 @@
{
"DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
"DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
- "Collections": "সংকলন",
+ "Collections": "কলেক্শন",
"ChapterNameValue": "অধ্যায় {0}",
"Channels": "চ্যানেল",
- "CameraImageUploadedFrom": "একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে {0} থেকে",
+ "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে",
"Books": "বই",
- "AuthenticationSucceededWithUserName": "{0} যাচাই সফল",
+ "AuthenticationSucceededWithUserName": "{0} অনুমোদন সফল",
"Artists": "শিল্পীরা",
"Application": "অ্যাপ্লিকেশন",
"Albums": "অ্যালবামগুলো",
@@ -14,13 +14,13 @@
"HeaderFavoriteArtists": "প্রিয় শিল্পীরা",
"HeaderFavoriteAlbums": "প্রিয় এলবামগুলো",
"HeaderContinueWatching": "দেখতে থাকুন",
- "HeaderCameraUploads": "ক্যামেরার আপলোডগুলো",
- "HeaderAlbumArtists": "এলবামের শিল্পী",
- "Genres": "ঘরানা",
+ "HeaderCameraUploads": "ক্যামেরার আপলোড সমূহ",
+ "HeaderAlbumArtists": "এলবাম শিল্পী",
+ "Genres": "জেনার",
"Folders": "ফোল্ডারগুলো",
- "Favorites": "ফেভারিটগুলো",
+ "Favorites": "পছন্দসমূহ",
"FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে",
- "AppDeviceValues": "এপ: {0}, ডিভাইস: {0}",
+ "AppDeviceValues": "অ্যাপ: {0}, ডিভাইস: {0}",
"VersionNumber": "সংস্করণ {0}",
"ValueSpecialEpisodeName": "বিশেষ - {0}",
"ValueHasBeenAddedToLibrary": "আপনার লাইব্রেরিতে {0} যোগ করা হয়েছে",
@@ -74,20 +74,20 @@
"NameInstallFailed": "{0} ইন্সটল ব্যর্থ",
"MusicVideos": "গানের ভিডিও",
"Music": "গান",
- "Movies": "সিনেমা",
+ "Movies": "চলচ্চিত্র",
"MixedContent": "মিশ্র কন্টেন্ট",
- "MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন হালনাগাদ করা হয়েছে",
- "HeaderRecordingGroups": "রেকর্ডিং গ্রুপ",
- "MessageNamedServerConfigurationUpdatedWithValue": "সার্ভারের {0} কনফিগারেসন অংশ আপডেট করা হয়েছে",
- "MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে হালনাগাদ করা হয়েছে",
- "MessageApplicationUpdated": "জেলিফিন সার্ভার হালনাগাদ করা হয়েছে",
- "Latest": "একদম নতুন",
+ "MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন আপডেট করা হয়েছে",
+ "HeaderRecordingGroups": "রেকর্ডিং দল",
+ "MessageNamedServerConfigurationUpdatedWithValue": "সার্ভারের {0} কনফিগারেসনের অংশ আপডেট করা হয়েছে",
+ "MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে আপডেট করা হয়েছে",
+ "MessageApplicationUpdated": "জেলিফিন সার্ভার আপডেট করা হয়েছে",
+ "Latest": "সর্বশেষ",
"LabelRunningTimeValue": "চলার সময়: {0}",
- "LabelIpAddressValue": "আইপি ঠিকানা: {0}",
+ "LabelIpAddressValue": "আইপি এড্রেস: {0}",
"ItemRemovedWithName": "{0} লাইব্রেরি থেকে বাদ দেয়া হয়েছে",
"ItemAddedWithName": "{0} লাইব্রেরিতে যোগ করা হয়েছে",
"Inherit": "থেকে পাওয়া",
- "HomeVideos": "বাসার ভিডিও",
+ "HomeVideos": "হোম ভিডিও",
"HeaderNextUp": "এরপরে আসছে",
"HeaderLiveTV": "লাইভ টিভি",
"HeaderFavoriteSongs": "প্রিয় গানগুলো",
diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index eabdb9138a..ccb72ff937 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -7,8 +7,8 @@
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
"Latest": "Terbaru",
"LabelIpAddressValue": "Alamat IP: {0}",
- "ItemRemovedWithName": "{0} sudah dikeluarkan dari perpustakaan",
- "ItemAddedWithName": "{0} sudah dimasukkan ke dalam perpustakaan",
+ "ItemRemovedWithName": "{0} sudah dikeluarkan dari pustaka",
+ "ItemAddedWithName": "{0} telah dimasukkan ke dalam pustaka",
"Inherit": "Warisan",
"HomeVideos": "Video Rumah",
"HeaderRecordingGroups": "Grup Rekaman",
@@ -19,8 +19,8 @@
"HeaderFavoriteEpisodes": "Episode Favorit",
"HeaderFavoriteArtists": "Artis Favorit",
"HeaderFavoriteAlbums": "Album Favorit",
- "HeaderContinueWatching": "Masih Melihat",
- "HeaderCameraUploads": "Uplod Kamera",
+ "HeaderContinueWatching": "Lanjutkan Menonton",
+ "HeaderCameraUploads": "Unggahan Kamera",
"HeaderAlbumArtists": "Album Artis",
"Genres": "Genre",
"Folders": "Folder",
@@ -32,11 +32,11 @@
"ChapterNameValue": "Bagian {0}",
"Channels": "Saluran",
"TvShows": "Seri TV",
- "SubtitleDownloadFailureFromForItem": "Talop gagal diunduh dari {0} untuk {1}",
- "StartupEmbyServerIsLoading": "Peladen Jellyfin sedang dimuat. Silakan coba kembali beberapa saat lagi.",
+ "SubtitleDownloadFailureFromForItem": "Subtitel gagal diunduh dari {0} untuk {1}",
+ "StartupEmbyServerIsLoading": "Server Jellyfin sedang dimuat. Silakan coba lagi nanti.",
"Songs": "Lagu",
"Playlists": "Daftar putar",
- "NotificationOptionPluginUninstalled": "Plugin dilepas",
+ "NotificationOptionPluginUninstalled": "Plugin dihapus",
"MusicVideos": "Video musik",
"VersionNumber": "Versi {0}",
"ValueSpecialEpisodeName": "Spesial - {0}",
@@ -65,7 +65,7 @@
"Photos": "Foto",
"NotificationOptionUserLockedOut": "Pengguna terkunci",
"NotificationOptionTaskFailed": "Kegagalan tugas terjadwal",
- "NotificationOptionServerRestartRequired": "Restart peladen dibutuhkan",
+ "NotificationOptionServerRestartRequired": "Muat ulang server dibutuhkan",
"NotificationOptionPluginUpdateInstalled": "Pembaruan plugin terpasang",
"NotificationOptionPluginInstalled": "Plugin terpasang",
"NotificationOptionPluginError": "Kegagalan plugin",
@@ -74,14 +74,14 @@
"NotificationOptionCameraImageUploaded": "Gambar kamera terunggah",
"NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang",
"NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia",
- "NewVersionIsAvailable": "Sebuah versi baru dari Peladen Jellyfin tersedia untuk diunduh.",
+ "NewVersionIsAvailable": "Versi baru dari Jellyfin Server tersedia untuk diunduh.",
"NameSeasonUnknown": "Musim tak diketahui",
"NameSeasonNumber": "Musim {0}",
- "NameInstallFailed": "{0} instalasi gagal",
+ "NameInstallFailed": "{0} penginstalan gagal",
"Music": "Musik",
"Movies": "Film",
- "MessageServerConfigurationUpdated": "Konfigurasi peladen telah diperbarui",
- "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi peladen bagian {0} telah diperbarui",
+ "MessageServerConfigurationUpdated": "Konfigurasi server telah diperbarui",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Bagian konfigurasi server {0} telah diperbarui",
"FailedLoginAttemptWithUserName": "Percobaan login gagal dari {0}",
"CameraImageUploadedFrom": "Sebuah gambar baru telah diunggah dari {0}",
"DeviceOfflineWithName": "{0} telah terputus",
@@ -90,6 +90,28 @@
"NotificationOptionVideoPlayback": "Pemutaran video dimulai",
"NotificationOptionAudioPlaybackStopped": "Pemutaran audio berhenti",
"NotificationOptionAudioPlayback": "Pemutaran audio dimulai",
- "MixedContent": "Konten campur",
- "PluginUninstalledWithName": "{0} telah dihapus"
+ "MixedContent": "Konten campuran",
+ "PluginUninstalledWithName": "{0} telah dihapus",
+ "TaskRefreshChapterImagesDescription": "Membuat gambar mini untuk video yang memiliki bagian.",
+ "TaskRefreshChapterImages": "Ekstrak Gambar Bagian",
+ "TaskCleanCacheDescription": "Menghapus file cache yang tidak lagi dibutuhkan oleh sistem.",
+ "TaskCleanCache": "Bersihkan Cache Direktori",
+ "TasksLibraryCategory": "Pustaka",
+ "TasksMaintenanceCategory": "Perbaikan",
+ "TasksApplicationCategory": "Aplikasi",
+ "TaskRefreshPeopleDescription": "Memperbarui metadata untuk aktor dan sutradara di pustaka media Anda.",
+ "TaskRefreshLibraryDescription": "Memindai Pustaka media Anda untuk mencari file baru dan memperbarui metadata.",
+ "TasksChannelsCategory": "Saluran Online",
+ "TaskDownloadMissingSubtitlesDescription": "Mencari di internet untuk subtitle yang hilang berdasarkan konfigurasi metadata.",
+ "TaskDownloadMissingSubtitles": "Unduh subtitle yang hilang",
+ "TaskRefreshChannelsDescription": "Segarkan informasi saluran internet.",
+ "TaskRefreshChannels": "Segarkan Saluran",
+ "TaskCleanTranscodeDescription": "Menghapus file transcode yang berumur lebih dari satu hari.",
+ "TaskCleanTranscode": "Bersihkan Direktori Transcode",
+ "TaskUpdatePluginsDescription": "Unduh dan instal pembaruan untuk plugin yang dikonfigurasi untuk memperbarui secara otomatis.",
+ "TaskUpdatePlugins": "Perbarui Plugin",
+ "TaskRefreshPeople": "Muat ulang Orang",
+ "TaskCleanLogsDescription": "Menghapus file log yang lebih dari {0} hari.",
+ "TaskCleanLogs": "Bersihkan Log Direktori",
+ "TaskRefreshLibrary": "Pindai Pustaka Media"
}
diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json
index 0e27806dd9..bf1a0ef136 100644
--- a/Emby.Server.Implementations/Localization/Core/it.json
+++ b/Emby.Server.Implementations/Localization/Core/it.json
@@ -102,11 +102,11 @@
"TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin che sono stati configurati per essere aggiornati contemporaneamente.",
"TaskUpdatePlugins": "Aggiorna i Plugin",
"TaskRefreshPeopleDescription": "Aggiorna i metadati per gli attori e registi nella tua libreria multimediale.",
- "TaskRefreshPeople": "Aggiorna persone",
+ "TaskRefreshPeople": "Aggiornamento Persone",
"TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.",
"TaskCleanLogs": "Pulisci la cartella dei log",
"TaskRefreshLibraryDescription": "Analizza la tua libreria multimediale per nuovi file e rinnova i metadati.",
- "TaskRefreshLibrary": "Analizza la libreria dei contenuti multimediali",
+ "TaskRefreshLibrary": "Scan Librerie",
"TaskRefreshChapterImagesDescription": "Crea le thumbnail per i video che hanno capitoli.",
"TaskRefreshChapterImages": "Estrai immagini capitolo",
"TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.",
diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json
index 16136a90c9..d6be86da3e 100644
--- a/Emby.Server.Implementations/Localization/Core/ta.json
+++ b/Emby.Server.Implementations/Localization/Core/ta.json
@@ -45,7 +45,7 @@
"TvShows": "தொலைக்காட்சித் தொடர்கள்",
"Sync": "ஒத்திசைவு",
"StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
- "Songs": "பாட்டுகள்",
+ "Songs": "பாடல்கள்",
"Shows": "தொடர்கள்",
"ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
"ScheduledTaskStartedWithName": "{0} துவங்கியது",
@@ -97,5 +97,21 @@
"Application": "செயலி",
"Albums": "ஆல்பங்கள்",
"NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
- "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது"
+ "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது",
+ "TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
+ "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
+ "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0 } இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
+ "TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
+ "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
+ "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
+ "TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.",
+ "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
+ "TaskCleanLogs": "பதிவு அடைவு சுத்தம் செய்யுங்கள்",
+ "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.",
+ "TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
+ "ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
+ "UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
+ "HomeVideos": "முகப்பு வீடியோக்கள்",
+ "UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது",
+ "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
}
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index 4de87616c5..d300b4891e 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -1,93 +1,32 @@
using System;
using System.Collections.Generic;
-using System.Net.Http;
-using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
namespace Jellyfin.Api.Controllers
{
///
/// The audio controller.
///
- // TODO: In order to autheneticate this in the future, Dlna playback will require updating
+ // TODO: In order to authenticate this in the future, Dlna playback will require updating
public class AudioController : BaseJellyfinApiController
{
- private readonly IDlnaManager _dlnaManager;
- private readonly IAuthorizationContext _authContext;
- private readonly IUserManager _userManager;
- private readonly ILibraryManager _libraryManager;
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly IMediaEncoder _mediaEncoder;
- private readonly IFileSystem _fileSystem;
- private readonly ISubtitleEncoder _subtitleEncoder;
- private readonly IConfiguration _configuration;
- private readonly IDeviceManager _deviceManager;
- private readonly TranscodingJobHelper _transcodingJobHelper;
- private readonly IHttpClientFactory _httpClientFactory;
+ private readonly AudioHelper _audioHelper;
private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive;
///
/// Initializes a new instance of the class.
///
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// The singleton.
- /// Instance of the interface.
- public AudioController(
- IDlnaManager dlnaManager,
- IUserManager userManger,
- IAuthorizationContext authorizationContext,
- ILibraryManager libraryManager,
- IMediaSourceManager mediaSourceManager,
- IServerConfigurationManager serverConfigurationManager,
- IMediaEncoder mediaEncoder,
- IFileSystem fileSystem,
- ISubtitleEncoder subtitleEncoder,
- IConfiguration configuration,
- IDeviceManager deviceManager,
- TranscodingJobHelper transcodingJobHelper,
- IHttpClientFactory httpClientFactory)
+ /// Instance of .
+ public AudioController(AudioHelper audioHelper)
{
- _dlnaManager = dlnaManager;
- _authContext = authorizationContext;
- _userManager = userManger;
- _libraryManager = libraryManager;
- _mediaSourceManager = mediaSourceManager;
- _serverConfigurationManager = serverConfigurationManager;
- _mediaEncoder = mediaEncoder;
- _fileSystem = fileSystem;
- _subtitleEncoder = subtitleEncoder;
- _configuration = configuration;
- _deviceManager = deviceManager;
- _transcodingJobHelper = transcodingJobHelper;
- _httpClientFactory = httpClientFactory;
+ _audioHelper = audioHelper;
}
///
@@ -200,10 +139,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] EncodingContext? context,
[FromQuery] Dictionary? streamOptions)
{
- bool isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
-
- var cancellationTokenSource = new CancellationTokenSource();
-
StreamingRequestDto streamingRequest = new StreamingRequestDto
{
Id = itemId,
@@ -257,97 +192,7 @@ namespace Jellyfin.Api.Controllers
StreamOptions = streamOptions
};
- using var state = await StreamingHelpers.GetStreamingState(
- streamingRequest,
- Request,
- _authContext,
- _mediaSourceManager,
- _userManager,
- _libraryManager,
- _serverConfigurationManager,
- _mediaEncoder,
- _fileSystem,
- _subtitleEncoder,
- _configuration,
- _dlnaManager,
- _deviceManager,
- _transcodingJobHelper,
- _transcodingJobType,
- cancellationTokenSource.Token)
- .ConfigureAwait(false);
-
- if (@static.HasValue && @static.Value && state.DirectStreamProvider != null)
- {
- StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
-
- await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
- {
- AllowEndOfFile = false
- }.WriteToAsync(Response.Body, CancellationToken.None)
- .ConfigureAwait(false);
-
- // TODO (moved from MediaBrowser.Api): Don't hardcode contentType
- return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
- }
-
- // Static remote stream
- if (@static.HasValue && @static.Value && state.InputProtocol == MediaProtocol.Http)
- {
- StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
-
- using var httpClient = _httpClientFactory.CreateClient();
- return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, httpClient).ConfigureAwait(false);
- }
-
- if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File)
- {
- return BadRequest($"Input protocol {state.InputProtocol} cannot be streamed statically");
- }
-
- var outputPath = state.OutputFilePath;
- var outputPathExists = System.IO.File.Exists(outputPath);
-
- var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
- var isTranscodeCached = outputPathExists && transcodingJob != null;
-
- StreamingHelpers.AddDlnaHeaders(state, Response.Headers, (@static.HasValue && @static.Value) || isTranscodeCached, startTimeTicks, Request, _dlnaManager);
-
- // Static stream
- if (@static.HasValue && @static.Value)
- {
- var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);
-
- if (state.MediaSource.IsInfiniteStream)
- {
- await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
- {
- AllowEndOfFile = false
- }.WriteToAsync(Response.Body, CancellationToken.None)
- .ConfigureAwait(false);
-
- return File(Response.Body, contentType);
- }
-
- return FileStreamResponseHelpers.GetStaticFileResult(
- state.MediaPath,
- contentType,
- isHeadRequest,
- this);
- }
-
- // Need to start ffmpeg (because media can't be returned directly)
- var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
- var encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
- var ffmpegCommandLineArguments = encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
- return await FileStreamResponseHelpers.GetTranscodedFile(
- state,
- isHeadRequest,
- this,
- _transcodingJobHelper,
- ffmpegCommandLineArguments,
- Request,
- _transcodingJobType,
- cancellationTokenSource).ConfigureAwait(false);
+ return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false);
}
}
}
diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
index 23d10e2156..1aed20ade6 100644
--- a/Jellyfin.Api/Controllers/DevicesController.cs
+++ b/Jellyfin.Api/Controllers/DevicesController.cs
@@ -48,7 +48,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery, Required] Guid? userId)
+ public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
{
var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty };
return _deviceManager.GetDevices(deviceQuery);
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index d5cfcdf3f9..c087e0d7b4 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -60,8 +60,8 @@ namespace Jellyfin.Api.Controllers
/// Server UUID.
/// Dlna content directory returned.
/// An containing the dlna content directory xml.
- [HttpGet("{serverId}/ContentDirectory/ContentDirectory")]
- [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_2")]
+ [HttpGet("{serverId}/ContentDirectory")]
+ [HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")]
[Produces(XMLContentType)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -75,8 +75,8 @@ namespace Jellyfin.Api.Controllers
///
/// Server UUID.
/// Dlna media receiver registrar xml.
- [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar")]
- [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
+ [HttpGet("{serverId}/MediaReceiverRegistrar")]
+ [HttpGet("{serverId}/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
[Produces(XMLContentType)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -90,8 +90,8 @@ namespace Jellyfin.Api.Controllers
///
/// Server UUID.
/// Dlna media receiver registrar xml.
- [HttpGet("{serverId}/ConnectionManager/ConnectionManager")]
- [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_2")]
+ [HttpGet("{serverId}/ConnectionManager")]
+ [HttpGet("{serverId}/ConnectionManager.xml", Name = "GetConnectionManager_2")]
[Produces(XMLContentType)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index d581ab8cd2..b4fe3bc8f8 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -13,7 +13,6 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -22,7 +21,6 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Authorization;
@@ -53,9 +51,9 @@ namespace Jellyfin.Api.Controllers
private readonly IConfiguration _configuration;
private readonly IDeviceManager _deviceManager;
private readonly TranscodingJobHelper _transcodingJobHelper;
- private readonly INetworkManager _networkManager;
private readonly ILogger _logger;
private readonly EncodingHelper _encodingHelper;
+ private readonly DynamicHlsHelper _dynamicHlsHelper;
private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Hls;
@@ -74,8 +72,8 @@ namespace Jellyfin.Api.Controllers
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the class.
- /// Instance of the interface.
/// Instance of the interface.
+ /// Instance of .
public DynamicHlsController(
ILibraryManager libraryManager,
IUserManager userManager,
@@ -89,8 +87,8 @@ namespace Jellyfin.Api.Controllers
IConfiguration configuration,
IDeviceManager deviceManager,
TranscodingJobHelper transcodingJobHelper,
- INetworkManager networkManager,
- ILogger logger)
+ ILogger logger,
+ DynamicHlsHelper dynamicHlsHelper)
{
_libraryManager = libraryManager;
_userManager = userManager;
@@ -104,8 +102,8 @@ namespace Jellyfin.Api.Controllers
_configuration = configuration;
_deviceManager = deviceManager;
_transcodingJobHelper = transcodingJobHelper;
- _networkManager = networkManager;
_logger = logger;
+ _dynamicHlsHelper = dynamicHlsHelper;
_encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
}
@@ -220,8 +218,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Dictionary streamOptions,
[FromQuery] bool enableAdaptiveBitrateStreaming = true)
{
- var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
- var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new HlsVideoRequestDto
{
Id = itemId,
@@ -276,8 +272,7 @@ namespace Jellyfin.Api.Controllers
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
};
- return await GetMasterPlaylistInternal(streamingRequest, isHeadRequest, enableAdaptiveBitrateStreaming, cancellationTokenSource)
- .ConfigureAwait(false);
+ return await _dynamicHlsHelper.GetMasterHlsPlaylist(_transcodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
}
///
@@ -390,8 +385,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Dictionary streamOptions,
[FromQuery] bool enableAdaptiveBitrateStreaming = true)
{
- var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
- var cancellationTokenSource = new CancellationTokenSource();
var streamingRequest = new HlsAudioRequestDto
{
Id = itemId,
@@ -446,8 +439,7 @@ namespace Jellyfin.Api.Controllers
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
};
- return await GetMasterPlaylistInternal(streamingRequest, isHeadRequest, enableAdaptiveBitrateStreaming, cancellationTokenSource)
- .ConfigureAwait(false);
+ return await _dynamicHlsHelper.GetMasterHlsPlaylist(_transcodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
}
///
@@ -1118,106 +1110,6 @@ namespace Jellyfin.Api.Controllers
.ConfigureAwait(false);
}
- private async Task GetMasterPlaylistInternal(
- StreamingRequestDto streamingRequest,
- bool isHeadRequest,
- bool enableAdaptiveBitrateStreaming,
- CancellationTokenSource cancellationTokenSource)
- {
- using var state = await StreamingHelpers.GetStreamingState(
- streamingRequest,
- Request,
- _authContext,
- _mediaSourceManager,
- _userManager,
- _libraryManager,
- _serverConfigurationManager,
- _mediaEncoder,
- _fileSystem,
- _subtitleEncoder,
- _configuration,
- _dlnaManager,
- _deviceManager,
- _transcodingJobHelper,
- _transcodingJobType,
- cancellationTokenSource.Token)
- .ConfigureAwait(false);
-
- Response.Headers.Add(HeaderNames.Expires, "0");
- if (isHeadRequest)
- {
- return new FileContentResult(Array.Empty(), MimeTypes.GetMimeType("playlist.m3u8"));
- }
-
- var totalBitrate = state.OutputAudioBitrate ?? 0 + state.OutputVideoBitrate ?? 0;
-
- var builder = new StringBuilder();
-
- builder.AppendLine("#EXTM3U");
-
- var isLiveStream = state.IsSegmentedLiveStream;
-
- var queryString = Request.QueryString.ToString();
-
- // from universal audio service
- if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer))
- {
- queryString += "&SegmentContainer=" + state.Request.SegmentContainer;
- }
-
- // from universal audio service
- if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1)
- {
- queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
- }
-
- // Main stream
- var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8";
-
- playlistUrl += queryString;
-
- var subtitleStreams = state.MediaSource
- .MediaStreams
- .Where(i => i.IsTextSubtitleStream)
- .ToList();
-
- var subtitleGroup = subtitleStreams.Count > 0 && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest!.EnableSubtitlesInManifest)
- ? "subs"
- : null;
-
- // If we're burning in subtitles then don't add additional subs to the manifest
- if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
- {
- subtitleGroup = null;
- }
-
- if (!string.IsNullOrWhiteSpace(subtitleGroup))
- {
- AddSubtitles(state, subtitleStreams, builder);
- }
-
- AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
-
- if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming))
- {
- var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0;
-
- // By default, vary by just 200k
- var variation = GetBitrateVariation(totalBitrate);
-
- var newBitrate = totalBitrate - variation;
- var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
- AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
-
- variation *= 2;
- newBitrate = totalBitrate - variation;
- variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
- AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
- }
-
- return new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8"));
- }
-
private async Task GetVariantPlaylistInternal(StreamingRequestDto streamingRequest, string name, CancellationTokenSource cancellationTokenSource)
{
using var state = await StreamingHelpers.GetStreamingState(
@@ -1411,330 +1303,6 @@ namespace Jellyfin.Api.Controllers
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
}
- private void AddSubtitles(StreamState state, IEnumerable subtitles, StringBuilder builder)
- {
- var selectedIndex = state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Hls ? (int?)null : state.SubtitleStream.Index;
- const string Format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\"";
-
- foreach (var stream in subtitles)
- {
- var name = stream.DisplayTitle;
-
- var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
- var isForced = stream.IsForced;
-
- var url = string.Format(
- CultureInfo.InvariantCulture,
- "{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}",
- state.Request.MediaSourceId,
- stream.Index.ToString(CultureInfo.InvariantCulture),
- 30.ToString(CultureInfo.InvariantCulture),
- ClaimHelpers.GetToken(Request.HttpContext.User));
-
- var line = string.Format(
- CultureInfo.InvariantCulture,
- Format,
- name,
- isDefault ? "YES" : "NO",
- isForced ? "YES" : "NO",
- url,
- stream.Language ?? "Unknown");
-
- builder.AppendLine(line);
- }
- }
-
- private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string? subtitleGroup)
- {
- builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
- .Append(bitrate.ToString(CultureInfo.InvariantCulture))
- .Append(",AVERAGE-BANDWIDTH=")
- .Append(bitrate.ToString(CultureInfo.InvariantCulture));
-
- AppendPlaylistCodecsField(builder, state);
-
- AppendPlaylistResolutionField(builder, state);
-
- AppendPlaylistFramerateField(builder, state);
-
- if (!string.IsNullOrWhiteSpace(subtitleGroup))
- {
- builder.Append(",SUBTITLES=\"")
- .Append(subtitleGroup)
- .Append('"');
- }
-
- builder.Append(Environment.NewLine);
- builder.AppendLine(url);
- }
-
- ///
- /// Appends a CODECS field containing formatted strings of
- /// the active streams output video and audio codecs.
- ///
- ///
- ///
- ///
- /// StringBuilder to append the field to.
- /// StreamState of the current stream.
- private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state)
- {
- // Video
- string videoCodecs = string.Empty;
- int? videoCodecLevel = GetOutputVideoCodecLevel(state);
- if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue)
- {
- videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value);
- }
-
- // Audio
- string audioCodecs = string.Empty;
- if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec))
- {
- audioCodecs = GetPlaylistAudioCodecs(state);
- }
-
- StringBuilder codecs = new StringBuilder();
-
- codecs.Append(videoCodecs);
-
- if (!string.IsNullOrEmpty(videoCodecs) && !string.IsNullOrEmpty(audioCodecs))
- {
- codecs.Append(',');
- }
-
- codecs.Append(audioCodecs);
-
- if (codecs.Length > 1)
- {
- builder.Append(",CODECS=\"")
- .Append(codecs)
- .Append('"');
- }
- }
-
- ///
- /// Appends a RESOLUTION field containing the resolution of the output stream.
- ///
- ///
- /// StringBuilder to append the field to.
- /// StreamState of the current stream.
- private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state)
- {
- if (state.OutputWidth.HasValue && state.OutputHeight.HasValue)
- {
- builder.Append(",RESOLUTION=")
- .Append(state.OutputWidth.GetValueOrDefault())
- .Append('x')
- .Append(state.OutputHeight.GetValueOrDefault());
- }
- }
-
- ///
- /// Appends a FRAME-RATE field containing the framerate of the output stream.
- ///
- ///
- /// StringBuilder to append the field to.
- /// StreamState of the current stream.
- private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state)
- {
- double? framerate = null;
- if (state.TargetFramerate.HasValue)
- {
- framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3);
- }
- else if (state.VideoStream?.RealFrameRate != null)
- {
- framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3);
- }
-
- if (framerate.HasValue)
- {
- builder.Append(",FRAME-RATE=")
- .Append(framerate.Value);
- }
- }
-
- private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream, bool enableAdaptiveBitrateStreaming)
- {
- // Within the local network this will likely do more harm than good.
- var ip = RequestHelpers.NormalizeIp(Request.HttpContext.Connection.RemoteIpAddress).ToString();
- if (_networkManager.IsInLocalNetwork(ip))
- {
- return false;
- }
-
- if (!enableAdaptiveBitrateStreaming)
- {
- return false;
- }
-
- if (isLiveStream || string.IsNullOrWhiteSpace(state.MediaPath))
- {
- // Opening live streams is so slow it's not even worth it
- return false;
- }
-
- if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
- {
- return false;
- }
-
- if (EncodingHelper.IsCopyCodec(state.OutputAudioCodec))
- {
- return false;
- }
-
- if (!state.IsOutputVideo)
- {
- return false;
- }
-
- // Having problems in android
- return false;
- // return state.VideoRequest.VideoBitRate.HasValue;
- }
-
- ///
- /// Get the H.26X level of the output video stream.
- ///
- /// StreamState of the current stream.
- /// H.26X level of the output video stream.
- private int? GetOutputVideoCodecLevel(StreamState state)
- {
- string? levelString;
- if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
- && state.VideoStream.Level.HasValue)
- {
- levelString = state.VideoStream?.Level.ToString();
- }
- else
- {
- levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec);
- }
-
- if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel))
- {
- return parsedLevel;
- }
-
- return null;
- }
-
- ///
- /// Gets a formatted string of the output audio codec, for use in the CODECS field.
- ///
- ///
- ///
- /// StreamState of the current stream.
- /// Formatted audio codec string.
- private string GetPlaylistAudioCodecs(StreamState state)
- {
- if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
- {
- string? profile = state.GetRequestedProfiles("aac").FirstOrDefault();
- return HlsCodecStringHelpers.GetAACString(profile);
- }
-
- if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
- {
- return HlsCodecStringHelpers.GetMP3String();
- }
-
- if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
- {
- return HlsCodecStringHelpers.GetAC3String();
- }
-
- if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
- {
- return HlsCodecStringHelpers.GetEAC3String();
- }
-
- return string.Empty;
- }
-
- ///
- /// Gets a formatted string of the output video codec, for use in the CODECS field.
- ///
- ///
- ///
- /// StreamState of the current stream.
- /// Video codec.
- /// Video level.
- /// Formatted video codec string.
- private string GetPlaylistVideoCodecs(StreamState state, string codec, int level)
- {
- if (level == 0)
- {
- // This is 0 when there's no requested H.26X level in the device profile
- // and the source is not encoded in H.26X
- _logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist");
- return string.Empty;
- }
-
- if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
- {
- string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
- return HlsCodecStringHelpers.GetH264String(profile, level);
- }
-
- if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
- {
- string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
-
- return HlsCodecStringHelpers.GetH265String(profile, level);
- }
-
- return string.Empty;
- }
-
- private int GetBitrateVariation(int bitrate)
- {
- // By default, vary by just 50k
- var variation = 50000;
-
- if (bitrate >= 10000000)
- {
- variation = 2000000;
- }
- else if (bitrate >= 5000000)
- {
- variation = 1500000;
- }
- else if (bitrate >= 3000000)
- {
- variation = 1000000;
- }
- else if (bitrate >= 2000000)
- {
- variation = 500000;
- }
- else if (bitrate >= 1000000)
- {
- variation = 300000;
- }
- else if (bitrate >= 600000)
- {
- variation = 200000;
- }
- else if (bitrate >= 400000)
- {
- variation = 100000;
- }
-
- return variation;
- }
-
- private string ReplaceBitrate(string url, int oldValue, int newValue)
- {
- return url.Replace(
- "videobitrate=" + oldValue.ToString(CultureInfo.InvariantCulture),
- "videobitrate=" + newValue.ToString(CultureInfo.InvariantCulture),
- StringComparison.OrdinalIgnoreCase);
- }
-
private double[] GetSegmentLengths(StreamState state)
{
var result = new List();
@@ -2089,7 +1657,7 @@ namespace Jellyfin.Api.Controllers
return Task.CompletedTask;
});
- return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath)!, false, this);
+ return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath)!, false, HttpContext);
}
private long GetEndPositionTicks(StreamState state, int requestedIndex)
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index e4a6842bce..816252f80d 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -61,7 +61,7 @@ namespace Jellyfin.Api.Controllers
var file = segmentId + Path.GetExtension(Request.Path);
file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
- return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, this);
+ return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, HttpContext);
}
///
@@ -148,7 +148,7 @@ namespace Jellyfin.Api.Controllers
return Task.CompletedTask;
});
- return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path)!, false, this);
+ return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path)!, false, HttpContext);
}
}
}
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index c9ad15babe..afde4a4336 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -331,12 +331,12 @@ namespace Jellyfin.Api.Controllers
/// Task.
private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
{
- var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
- var ext = result.ContentType.Split('/').Last();
+ using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
+ var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
- await using (var stream = result.Content)
+ using (var stream = result.Content)
{
await using var fileStream = new FileStream(
fullCachePath,
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 89112eea7d..3ccfc826db 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
@@ -15,7 +16,6 @@ using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -39,7 +39,7 @@ namespace Jellyfin.Api.Controllers
{
private readonly ILiveTvManager _liveTvManager;
private readonly IUserManager _userManager;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
private readonly ISessionContext _sessionContext;
@@ -52,7 +52,7 @@ namespace Jellyfin.Api.Controllers
///
/// Instance of the interface.
/// Instance of the interface.
- /// Instance of the interface.
+ /// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
@@ -62,7 +62,7 @@ namespace Jellyfin.Api.Controllers
public LiveTvController(
ILiveTvManager liveTvManager,
IUserManager userManager,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IDtoService dtoService,
ISessionContext sessionContext,
@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers
{
_liveTvManager = liveTvManager;
_userManager = userManager;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
_dtoService = dtoService;
_sessionContext = sessionContext;
@@ -592,11 +592,11 @@ namespace Jellyfin.Api.Controllers
GenreIds = RequestHelpers.GetGuids(genreIds)
};
- if (!librarySeriesId.Equals(Guid.Empty))
+ if (librarySeriesId != null && !librarySeriesId.Equals(Guid.Empty))
{
query.IsSeries = true;
- if (_libraryManager.GetItemById(librarySeriesId ?? Guid.Empty) is Series series)
+ if (_libraryManager.GetItemById(librarySeriesId.Value) is Series series)
{
query.Name = series.Name;
}
@@ -1004,7 +1004,7 @@ namespace Jellyfin.Api.Controllers
/// Validate login.
/// Created listings provider returned.
/// A containing the created listings provider.
- [HttpGet("ListingProviders")]
+ [HttpPost("ListingProviders")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")]
@@ -1069,13 +1069,13 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task GetSchedulesDirectCountries()
{
+ var client = _httpClientFactory.CreateClient();
// https://json.schedulesdirect.org/20141201/available/countries
- var response = await _httpClient.Get(new HttpRequestOptions
- {
- Url = "https://json.schedulesdirect.org/20141201/available/countries",
- BufferContent = false
- }).ConfigureAwait(false);
- return File(response, MediaTypeNames.Application.Json);
+ // 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")
+ .ConfigureAwait(false);
+
+ return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json);
}
///
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index 517113074c..7faf479a5c 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -1,30 +1,18 @@
using System;
using System.Buffers;
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using System.Linq;
using System.Net.Mime;
-using System.Text.Json;
-using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.MediaInfoDtos;
using Jellyfin.Api.Models.VideoDtos;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -42,12 +30,9 @@ namespace Jellyfin.Api.Controllers
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IDeviceManager _deviceManager;
private readonly ILibraryManager _libraryManager;
- private readonly INetworkManager _networkManager;
- private readonly IMediaEncoder _mediaEncoder;
- private readonly IUserManager _userManager;
private readonly IAuthorizationContext _authContext;
private readonly ILogger _logger;
- private readonly IServerConfigurationManager _serverConfigurationManager;
+ private readonly MediaInfoHelper _mediaInfoHelper;
///
/// Initializes a new instance of the class.
@@ -55,32 +40,23 @@ namespace Jellyfin.Api.Controllers
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- /// Instance of the interface.
+ /// Instance of the .
public MediaInfoController(
IMediaSourceManager mediaSourceManager,
IDeviceManager deviceManager,
ILibraryManager libraryManager,
- INetworkManager networkManager,
- IMediaEncoder mediaEncoder,
- IUserManager userManager,
IAuthorizationContext authContext,
ILogger logger,
- IServerConfigurationManager serverConfigurationManager)
+ MediaInfoHelper mediaInfoHelper)
{
_mediaSourceManager = mediaSourceManager;
_deviceManager = deviceManager;
_libraryManager = libraryManager;
- _networkManager = networkManager;
- _mediaEncoder = mediaEncoder;
- _userManager = userManager;
_authContext = authContext;
_logger = logger;
- _serverConfigurationManager = serverConfigurationManager;
+ _mediaInfoHelper = mediaInfoHelper;
}
///
@@ -94,7 +70,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery, Required] Guid? userId)
{
- return await GetPlaybackInfoInternal(itemId, userId).ConfigureAwait(false);
+ return await _mediaInfoHelper.GetPlaybackInfo(
+ itemId,
+ userId)
+ .ConfigureAwait(false);
}
///
@@ -153,7 +132,12 @@ namespace Jellyfin.Api.Controllers
}
}
- var info = await GetPlaybackInfoInternal(itemId, userId, mediaSourceId, liveStreamId).ConfigureAwait(false);
+ var info = await _mediaInfoHelper.GetPlaybackInfo(
+ itemId,
+ userId,
+ mediaSourceId,
+ liveStreamId)
+ .ConfigureAwait(false);
if (profile != null)
{
@@ -162,7 +146,7 @@ namespace Jellyfin.Api.Controllers
foreach (var mediaSource in info.MediaSources)
{
- SetDeviceSpecificData(
+ _mediaInfoHelper.SetDeviceSpecificData(
item,
mediaSource,
profile,
@@ -179,10 +163,11 @@ namespace Jellyfin.Api.Controllers
enableDirectStream,
enableTranscoding,
allowVideoStreamCopy,
- allowAudioStreamCopy);
+ allowAudioStreamCopy,
+ Request.HttpContext.Connection.RemoteIpAddress.ToString());
}
- SortMediaSources(info, maxStreamingBitrate);
+ _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
}
if (autoOpenLiveStream)
@@ -191,21 +176,23 @@ namespace Jellyfin.Api.Controllers
if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
{
- var openStreamResult = await OpenMediaSource(new LiveStreamRequest
- {
- AudioStreamIndex = audioStreamIndex,
- DeviceProfile = deviceProfile?.DeviceProfile,
- EnableDirectPlay = enableDirectPlay,
- EnableDirectStream = enableDirectStream,
- ItemId = itemId,
- MaxAudioChannels = maxAudioChannels,
- MaxStreamingBitrate = maxStreamingBitrate,
- PlaySessionId = info.PlaySessionId,
- StartTimeTicks = startTimeTicks,
- SubtitleStreamIndex = subtitleStreamIndex,
- UserId = userId ?? Guid.Empty,
- OpenToken = mediaSource.OpenToken
- }).ConfigureAwait(false);
+ var openStreamResult = await _mediaInfoHelper.OpenMediaSource(
+ Request,
+ new LiveStreamRequest
+ {
+ AudioStreamIndex = audioStreamIndex,
+ DeviceProfile = deviceProfile?.DeviceProfile,
+ EnableDirectPlay = enableDirectPlay,
+ EnableDirectStream = enableDirectStream,
+ ItemId = itemId,
+ MaxAudioChannels = maxAudioChannels,
+ MaxStreamingBitrate = maxStreamingBitrate,
+ PlaySessionId = info.PlaySessionId,
+ StartTimeTicks = startTimeTicks,
+ SubtitleStreamIndex = subtitleStreamIndex,
+ UserId = userId ?? Guid.Empty,
+ OpenToken = mediaSource.OpenToken
+ }).ConfigureAwait(false);
info.MediaSources = new[] { openStreamResult.MediaSource };
}
@@ -215,7 +202,7 @@ namespace Jellyfin.Api.Controllers
{
foreach (var mediaSource in info.MediaSources)
{
- NormalizeMediaSourceContainer(mediaSource, profile!, DlnaProfileType.Video);
+ _mediaInfoHelper.NormalizeMediaSourceContainer(mediaSource, profile!, DlnaProfileType.Video);
}
}
@@ -271,7 +258,7 @@ namespace Jellyfin.Api.Controllers
EnableDirectStream = enableDirectStream,
DirectPlayProtocols = openLiveStreamDto?.DirectPlayProtocols ?? new[] { MediaProtocol.Http }
};
- return await OpenMediaSource(request).ConfigureAwait(false);
+ return await _mediaInfoHelper.OpenMediaSource(Request, request).ConfigureAwait(false);
}
///
@@ -324,454 +311,5 @@ namespace Jellyfin.Api.Controllers
ArrayPool.Shared.Return(buffer);
}
}
-
- private async Task GetPlaybackInfoInternal(
- Guid id,
- Guid? userId,
- string? mediaSourceId = null,
- string? liveStreamId = null)
- {
- var user = userId.HasValue && !userId.Equals(Guid.Empty)
- ? _userManager.GetUserById(userId.Value)
- : null;
- var item = _libraryManager.GetItemById(id);
- var result = new PlaybackInfoResponse();
-
- MediaSourceInfo[] mediaSources;
- if (string.IsNullOrWhiteSpace(liveStreamId))
- {
- // TODO (moved from MediaBrowser.Api) handle supportedLiveMediaTypes?
- var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false);
-
- if (string.IsNullOrWhiteSpace(mediaSourceId))
- {
- mediaSources = mediaSourcesList.ToArray();
- }
- else
- {
- mediaSources = mediaSourcesList
- .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
- .ToArray();
- }
- }
- else
- {
- var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
-
- mediaSources = new[] { mediaSource };
- }
-
- if (mediaSources.Length == 0)
- {
- result.MediaSources = Array.Empty();
-
- result.ErrorCode ??= PlaybackErrorCode.NoCompatibleStream;
- }
- else
- {
- // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
- // Should we move this directly into MediaSourceManager?
- result.MediaSources = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
-
- result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
- }
-
- return result;
- }
-
- private void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type)
- {
- mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type);
- }
-
- private void SetDeviceSpecificData(
- BaseItem item,
- MediaSourceInfo mediaSource,
- DeviceProfile profile,
- AuthorizationInfo auth,
- long? maxBitrate,
- long startTimeTicks,
- string mediaSourceId,
- int? audioStreamIndex,
- int? subtitleStreamIndex,
- int? maxAudioChannels,
- string playSessionId,
- Guid userId,
- bool enableDirectPlay,
- bool enableDirectStream,
- bool enableTranscoding,
- bool allowVideoStreamCopy,
- bool allowAudioStreamCopy)
- {
- var streamBuilder = new StreamBuilder(_mediaEncoder, _logger);
-
- var options = new VideoOptions
- {
- MediaSources = new[] { mediaSource },
- Context = EncodingContext.Streaming,
- DeviceId = auth.DeviceId,
- ItemId = item.Id,
- Profile = profile,
- MaxAudioChannels = maxAudioChannels
- };
-
- if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
- {
- options.MediaSourceId = mediaSourceId;
- options.AudioStreamIndex = audioStreamIndex;
- options.SubtitleStreamIndex = subtitleStreamIndex;
- }
-
- var user = _userManager.GetUserById(userId);
-
- if (!enableDirectPlay)
- {
- mediaSource.SupportsDirectPlay = false;
- }
-
- if (!enableDirectStream)
- {
- mediaSource.SupportsDirectStream = false;
- }
-
- if (!enableTranscoding)
- {
- mediaSource.SupportsTranscoding = false;
- }
-
- if (item is Audio)
- {
- _logger.LogInformation(
- "User policy for {0}. EnableAudioPlaybackTranscoding: {1}",
- user.Username,
- user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
- }
- else
- {
- _logger.LogInformation(
- "User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}",
- user.Username,
- user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
- user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding),
- user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
- }
-
- // Beginning of Playback Determination: Attempt DirectPlay first
- if (mediaSource.SupportsDirectPlay)
- {
- if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
- {
- mediaSource.SupportsDirectPlay = false;
- }
- else
- {
- var supportsDirectStream = mediaSource.SupportsDirectStream;
-
- // Dummy this up to fool StreamBuilder
- mediaSource.SupportsDirectStream = true;
- options.MaxBitrate = maxBitrate;
-
- if (item is Audio)
- {
- if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
- {
- options.ForceDirectPlay = true;
- }
- }
- else if (item is Video)
- {
- if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
- && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
- && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
- {
- options.ForceDirectPlay = true;
- }
- }
-
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
- ? streamBuilder.BuildAudioItem(options)
- : streamBuilder.BuildVideoItem(options);
-
- if (streamInfo == null || !streamInfo.IsDirectStream)
- {
- mediaSource.SupportsDirectPlay = false;
- }
-
- // Set this back to what it was
- mediaSource.SupportsDirectStream = supportsDirectStream;
-
- if (streamInfo != null)
- {
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
- }
- }
- }
-
- if (mediaSource.SupportsDirectStream)
- {
- if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
- {
- mediaSource.SupportsDirectStream = false;
- }
- else
- {
- options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
-
- if (item is Audio)
- {
- if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
- {
- options.ForceDirectStream = true;
- }
- }
- else if (item is Video)
- {
- if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
- && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
- && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
- {
- options.ForceDirectStream = true;
- }
- }
-
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
- ? streamBuilder.BuildAudioItem(options)
- : streamBuilder.BuildVideoItem(options);
-
- if (streamInfo == null || !streamInfo.IsDirectStream)
- {
- mediaSource.SupportsDirectStream = false;
- }
-
- if (streamInfo != null)
- {
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
- }
- }
- }
-
- if (mediaSource.SupportsTranscoding)
- {
- options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
-
- // The MediaSource supports direct stream, now test to see if the client supports it
- var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
- ? streamBuilder.BuildAudioItem(options)
- : streamBuilder.BuildVideoItem(options);
-
- if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
- {
- if (streamInfo != null)
- {
- streamInfo.PlaySessionId = playSessionId;
- streamInfo.StartPositionTicks = startTimeTicks;
- mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
- mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
- mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
- mediaSource.TranscodingContainer = streamInfo.Container;
- mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
-
- // Do this after the above so that StartPositionTicks is set
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
- }
- }
- else
- {
- if (streamInfo != null)
- {
- streamInfo.PlaySessionId = playSessionId;
-
- if (streamInfo.PlayMethod == PlayMethod.Transcode)
- {
- streamInfo.StartPositionTicks = startTimeTicks;
- mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
-
- if (!allowVideoStreamCopy)
- {
- mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
- }
-
- if (!allowAudioStreamCopy)
- {
- mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
- }
-
- mediaSource.TranscodingContainer = streamInfo.Container;
- mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
- }
-
- if (!allowAudioStreamCopy)
- {
- mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
- }
-
- mediaSource.TranscodingContainer = streamInfo.Container;
- mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
-
- // Do this after the above so that StartPositionTicks is set
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
- }
- }
- }
-
- foreach (var attachment in mediaSource.MediaAttachments)
- {
- attachment.DeliveryUrl = string.Format(
- CultureInfo.InvariantCulture,
- "/Videos/{0}/{1}/Attachments/{2}",
- item.Id,
- mediaSource.Id,
- attachment.Index);
- }
- }
-
- private async Task OpenMediaSource(LiveStreamRequest request)
- {
- var authInfo = _authContext.GetAuthorizationInfo(Request);
-
- var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
-
- var profile = request.DeviceProfile;
- if (profile == null)
- {
- var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
- if (caps != null)
- {
- profile = caps.DeviceProfile;
- }
- }
-
- if (profile != null)
- {
- var item = _libraryManager.GetItemById(request.ItemId);
-
- SetDeviceSpecificData(
- item,
- result.MediaSource,
- profile,
- authInfo,
- request.MaxStreamingBitrate,
- request.StartTimeTicks ?? 0,
- result.MediaSource.Id,
- request.AudioStreamIndex,
- request.SubtitleStreamIndex,
- request.MaxAudioChannels,
- request.PlaySessionId,
- request.UserId,
- request.EnableDirectPlay,
- request.EnableDirectStream,
- true,
- true,
- true);
- }
- else
- {
- if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
- {
- result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
- }
- }
-
- // here was a check if (result.MediaSource != null) but Rider said it will never be null
- NormalizeMediaSourceContainer(result.MediaSource, profile!, DlnaProfileType.Video);
-
- return result;
- }
-
- private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
- {
- var profiles = info.GetSubtitleProfiles(_mediaEncoder, false, "-", accessToken);
- mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
-
- mediaSource.TranscodeReasons = info.TranscodeReasons;
-
- foreach (var profile in profiles)
- {
- foreach (var stream in mediaSource.MediaStreams)
- {
- if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
- {
- stream.DeliveryMethod = profile.DeliveryMethod;
-
- if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
- {
- stream.DeliveryUrl = profile.Url.TrimStart('-');
- stream.IsExternalUrl = profile.IsExternalUrl;
- }
- }
- }
- }
- }
-
- private long? GetMaxBitrate(long? clientMaxBitrate, User user)
- {
- var maxBitrate = clientMaxBitrate;
- var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
-
- if (remoteClientMaxBitrate <= 0)
- {
- remoteClientMaxBitrate = _serverConfigurationManager.Configuration.RemoteClientBitrateLimit;
- }
-
- if (remoteClientMaxBitrate > 0)
- {
- var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.HttpContext.Connection.RemoteIpAddress.ToString());
-
- _logger.LogInformation("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.HttpContext.Connection.RemoteIpAddress.ToString(), isInLocalNetwork);
- if (!isInLocalNetwork)
- {
- maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
- }
- }
-
- return maxBitrate;
- }
-
- private void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate)
- {
- var originalList = result.MediaSources.ToList();
-
- result.MediaSources = result.MediaSources.OrderBy(i =>
- {
- // Nothing beats direct playing a file
- if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
- {
- return 0;
- }
-
- return 1;
- })
- .ThenBy(i =>
- {
- // Let's assume direct streaming a file is just as desirable as direct playing a remote url
- if (i.SupportsDirectPlay || i.SupportsDirectStream)
- {
- return 0;
- }
-
- return 1;
- })
- .ThenBy(i =>
- {
- return i.Protocol switch
- {
- MediaProtocol.File => 0,
- _ => 1,
- };
- })
- .ThenBy(i =>
- {
- if (maxBitrate.HasValue && i.Bitrate.HasValue)
- {
- return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
- }
-
- return 1;
- })
- .ThenBy(originalList.IndexOf)
- .ToArray();
- }
}
}
diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs
index cf3e780b44..0ceda6815c 100644
--- a/Jellyfin.Api/Controllers/NotificationsController.cs
+++ b/Jellyfin.Api/Controllers/NotificationsController.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using Jellyfin.Api.Constants;
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index 0422bfe727..22f2ca5c32 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers
/// Item id.
/// Item marked as unplayed.
/// A containing the .
- [HttpDelete("Users/{userId}/PlayedItem/{itemId}")]
+ [HttpDelete("Users/{userId}/PlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
{
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index baa3d80ac8..a203c50b96 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -3,12 +3,12 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -30,7 +30,7 @@ namespace Jellyfin.Api.Controllers
{
private readonly IProviderManager _providerManager;
private readonly IServerApplicationPaths _applicationPaths;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
///
@@ -38,17 +38,17 @@ namespace Jellyfin.Api.Controllers
///
/// Instance of the interface.
/// Instance of the interface.
- /// Instance of the interface.
+ /// Instance of the interface.
/// Instance of the interface.
public RemoteImageController(
IProviderManager providerManager,
IServerApplicationPaths applicationPaths,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager)
{
_providerManager = providerManager;
_applicationPaths = applicationPaths;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
}
@@ -244,22 +244,14 @@ namespace Jellyfin.Api.Controllers
/// Task.
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
{
- using var result = await _httpClient.GetResponse(new HttpRequestOptions
- {
- Url = url,
- BufferContent = false
- }).ConfigureAwait(false);
- var ext = result.ContentType.Split('/').Last();
-
+ var httpClient = _httpClientFactory.CreateClient();
+ using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
+ var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
- await using (var stream = result.Content)
- {
- await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
- await stream.CopyToAsync(fileStream).ConfigureAwait(false);
- }
-
+ await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None)
.ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index 48b57bdb7f..4795fb0cf7 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -413,7 +413,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostFullCapabilities(
- [FromQuery, Required] string? id,
+ [FromQuery] string? id,
[FromBody, Required] ClientCapabilities capabilities)
{
if (string.IsNullOrWhiteSpace(id))
@@ -480,7 +480,7 @@ namespace Jellyfin.Api.Controllers
///
/// Password reset providers retrieved.
/// An with the password reset providers.
- [HttpGet("Auto/PasswordResetProviders")]
+ [HttpGet("Auth/PasswordResetProviders")]
[ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.RequiresElevation)]
public ActionResult> GetPasswordResetProviders()
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 75df16aa7b..3994fb9958 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -2,17 +2,20 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Net.Http;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
-using Jellyfin.Api.Models.VideoDtos;
+using Jellyfin.Api.Models.StreamingDtos;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
namespace Jellyfin.Api.Controllers
{
@@ -23,27 +26,39 @@ namespace Jellyfin.Api.Controllers
public class UniversalAudioController : BaseJellyfinApiController
{
private readonly IAuthorizationContext _authorizationContext;
- private readonly MediaInfoController _mediaInfoController;
- private readonly DynamicHlsController _dynamicHlsController;
- private readonly AudioController _audioController;
+ private readonly IDeviceManager _deviceManager;
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILogger _logger;
+ private readonly MediaInfoHelper _mediaInfoHelper;
+ private readonly AudioHelper _audioHelper;
+ private readonly DynamicHlsHelper _dynamicHlsHelper;
///
/// Initializes a new instance of the class.
///
/// Instance of the interface.
- /// Instance of the .
- /// Instance of the .
- /// Instance of the .
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of .
+ /// Instance of .
+ /// Instance of .
public UniversalAudioController(
IAuthorizationContext authorizationContext,
- MediaInfoController mediaInfoController,
- DynamicHlsController dynamicHlsController,
- AudioController audioController)
+ IDeviceManager deviceManager,
+ ILibraryManager libraryManager,
+ ILogger logger,
+ MediaInfoHelper mediaInfoHelper,
+ AudioHelper audioHelper,
+ DynamicHlsHelper dynamicHlsHelper)
{
_authorizationContext = authorizationContext;
- _mediaInfoController = mediaInfoController;
- _dynamicHlsController = dynamicHlsController;
- _audioController = audioController;
+ _deviceManager = deviceManager;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _mediaInfoHelper = mediaInfoHelper;
+ _audioHelper = audioHelper;
+ _dynamicHlsHelper = dynamicHlsHelper;
}
///
@@ -95,24 +110,68 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool breakOnNonKeyFrames,
[FromQuery] bool enableRedirection = true)
{
- bool isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
_authorizationContext.GetAuthorizationInfo(Request).DeviceId = deviceId;
- var playbackInfoResult = await _mediaInfoController.GetPostedPlaybackInfo(
- itemId,
- userId,
- maxStreamingBitrate,
- startTimeTicks,
- null,
- null,
- maxAudioChannels,
- mediaSourceId,
- null,
- new DeviceProfileDto { DeviceProfile = deviceProfile })
- .ConfigureAwait(false);
- var mediaSource = playbackInfoResult.Value.MediaSources[0];
+ var authInfo = _authorizationContext.GetAuthorizationInfo(Request);
+ _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
+
+ if (deviceProfile == null)
+ {
+ var clientCapabilities = _deviceManager.GetCapabilities(authInfo.DeviceId);
+ if (clientCapabilities != null)
+ {
+ deviceProfile = clientCapabilities.DeviceProfile;
+ }
+ }
+
+ var info = await _mediaInfoHelper.GetPlaybackInfo(
+ itemId,
+ userId,
+ mediaSourceId)
+ .ConfigureAwait(false);
+
+ if (deviceProfile != null)
+ {
+ // set device specific data
+ var item = _libraryManager.GetItemById(itemId);
+
+ foreach (var sourceInfo in info.MediaSources)
+ {
+ _mediaInfoHelper.SetDeviceSpecificData(
+ item,
+ sourceInfo,
+ deviceProfile,
+ authInfo,
+ maxStreamingBitrate ?? deviceProfile.MaxStreamingBitrate,
+ startTimeTicks ?? 0,
+ mediaSourceId ?? string.Empty,
+ null,
+ null,
+ maxAudioChannels,
+ info!.PlaySessionId!,
+ userId ?? Guid.Empty,
+ true,
+ true,
+ true,
+ true,
+ true,
+ Request.HttpContext.Connection.RemoteIpAddress.ToString());
+ }
+
+ _mediaInfoHelper.SortMediaSources(info, maxStreamingBitrate);
+ }
+
+ if (info.MediaSources != null)
+ {
+ foreach (var source in info.MediaSources)
+ {
+ _mediaInfoHelper.NormalizeMediaSourceContainer(source, deviceProfile!, DlnaProfileType.Video);
+ }
+ }
+
+ var mediaSource = info.MediaSources![0];
if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http)
{
if (enableRedirection)
@@ -127,129 +186,71 @@ namespace Jellyfin.Api.Controllers
var isStatic = mediaSource.SupportsDirectStream;
if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
- var transcodingProfile = deviceProfile.TranscodingProfiles[0];
-
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
// TODO: remove this when we switch back to the segment muxer
var supportedHlsContainers = new[] { "mpegts", "fmp4" };
- if (isHeadRequest)
+ var dynamicHlsRequestDto = new HlsAudioRequestDto
{
- _dynamicHlsController.Request.Method = HttpMethod.Head.Method;
- }
-
- return await _dynamicHlsController.GetMasterHlsAudioPlaylist(
- itemId,
- ".m3u8",
- isStatic,
- null,
- null,
- null,
- playbackInfoResult.Value.PlaySessionId,
+ Id = itemId,
+ Container = ".m3u8",
+ Static = isStatic,
+ PlaySessionId = info.PlaySessionId,
// fallback to mpegts if device reports some weird value unsupported by hls
- Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "mpegts",
- null,
- null,
- mediaSource.Id,
- deviceId,
- transcodingProfile.AudioCodec,
- null,
- null,
- null,
- transcodingProfile.BreakOnNonKeyFrames,
- maxAudioSampleRate,
- maxAudioBitDepth,
- null,
- isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)),
- maxAudioChannels,
- null,
- null,
- null,
- null,
- null,
- startTimeTicks,
- null,
- null,
- null,
- null,
- SubtitleDeliveryMethod.Hls,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
- null,
- null,
- EncodingContext.Static,
- new Dictionary())
- .ConfigureAwait(false);
- }
- else
- {
- if (isHeadRequest)
- {
- _audioController.Request.Method = HttpMethod.Head.Method;
- }
+ SegmentContainer = Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "mpegts",
+ MediaSourceId = mediaSourceId,
+ DeviceId = deviceId,
+ AudioCodec = audioCodec,
+ EnableAutoStreamCopy = true,
+ AllowAudioStreamCopy = true,
+ AllowVideoStreamCopy = true,
+ BreakOnNonKeyFrames = breakOnNonKeyFrames,
+ AudioSampleRate = maxAudioSampleRate,
+ MaxAudioChannels = maxAudioChannels,
+ MaxAudioBitDepth = maxAudioBitDepth,
+ AudioChannels = isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)),
+ StartTimeTicks = startTimeTicks,
+ SubtitleMethod = SubtitleDeliveryMethod.Hls,
+ RequireAvc = true,
+ DeInterlace = true,
+ RequireNonAnamorphic = true,
+ EnableMpegtsM2TsMode = true,
+ TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
+ Context = EncodingContext.Static,
+ StreamOptions = new Dictionary(),
+ EnableAdaptiveBitrateStreaming = true
+ };
- return await _audioController.GetAudioStream(
- itemId,
- isStatic ? null : ("." + mediaSource.TranscodingContainer),
- isStatic,
- null,
- null,
- null,
- playbackInfoResult.Value.PlaySessionId,
- null,
- null,
- null,
- mediaSource.Id,
- deviceId,
- audioCodec,
- null,
- null,
- null,
- breakOnNonKeyFrames,
- maxAudioSampleRate,
- maxAudioBitDepth,
- isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)),
- null,
- maxAudioChannels,
- null,
- null,
- null,
- null,
- null,
- startTimeTicks,
- null,
- null,
- null,
- null,
- SubtitleDeliveryMethod.Embed,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
- null,
- null,
- null,
- null)
+ return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType.Hls, dynamicHlsRequestDto, true)
.ConfigureAwait(false);
}
+
+ var audioStreamingDto = new StreamingRequestDto
+ {
+ Id = itemId,
+ Container = isStatic ? null : ("." + mediaSource.TranscodingContainer),
+ Static = isStatic,
+ PlaySessionId = info.PlaySessionId,
+ MediaSourceId = mediaSourceId,
+ DeviceId = deviceId,
+ AudioCodec = audioCodec,
+ EnableAutoStreamCopy = true,
+ AllowAudioStreamCopy = true,
+ AllowVideoStreamCopy = true,
+ BreakOnNonKeyFrames = breakOnNonKeyFrames,
+ AudioSampleRate = maxAudioSampleRate,
+ MaxAudioChannels = maxAudioChannels,
+ AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)),
+ MaxAudioBitDepth = maxAudioBitDepth,
+ AudioChannels = maxAudioChannels,
+ CopyTimestamps = true,
+ StartTimeTicks = startTimeTicks,
+ SubtitleMethod = SubtitleDeliveryMethod.Embed,
+ TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
+ Context = EncodingContext.Static
+ };
+
+ return await _audioHelper.GetAudioStream(TranscodingJobType.Progressive, audioStreamingDto).ConfigureAwait(false);
}
private DeviceProfile GetDeviceProfile(
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index fafa722c57..14d3f24607 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -470,8 +470,8 @@ namespace Jellyfin.Api.Controllers
{
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
- using var httpClient = _httpClientFactory.CreateClient();
- return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, httpClient).ConfigureAwait(false);
+ var httpClient = _httpClientFactory.CreateClient();
+ return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false);
}
if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File)
@@ -507,7 +507,7 @@ namespace Jellyfin.Api.Controllers
state.MediaPath,
contentType,
isHeadRequest,
- this);
+ HttpContext);
}
// Need to start ffmpeg (because media can't be returned directly)
@@ -517,10 +517,9 @@ namespace Jellyfin.Api.Controllers
return await FileStreamResponseHelpers.GetTranscodedFile(
state,
isHeadRequest,
- this,
+ HttpContext,
_transcodingJobHelper,
ffmpegCommandLineArguments,
- Request,
_transcodingJobType,
cancellationTokenSource).ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs
new file mode 100644
index 0000000000..eae2be05ba
--- /dev/null
+++ b/Jellyfin.Api/Helpers/AudioHelper.cs
@@ -0,0 +1,195 @@
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.StreamingDtos;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+
+namespace Jellyfin.Api.Helpers
+{
+ ///
+ /// Audio helper.
+ ///
+ public class AudioHelper
+ {
+ private readonly IDlnaManager _dlnaManager;
+ private readonly IAuthorizationContext _authContext;
+ private readonly IUserManager _userManager;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IFileSystem _fileSystem;
+ private readonly ISubtitleEncoder _subtitleEncoder;
+ private readonly IConfiguration _configuration;
+ private readonly IDeviceManager _deviceManager;
+ private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly IHttpClientFactory _httpClientFactory;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of .
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public AudioHelper(
+ IDlnaManager dlnaManager,
+ IAuthorizationContext authContext,
+ IUserManager userManager,
+ ILibraryManager libraryManager,
+ IMediaSourceManager mediaSourceManager,
+ IServerConfigurationManager serverConfigurationManager,
+ IMediaEncoder mediaEncoder,
+ IFileSystem fileSystem,
+ ISubtitleEncoder subtitleEncoder,
+ IConfiguration configuration,
+ IDeviceManager deviceManager,
+ TranscodingJobHelper transcodingJobHelper,
+ IHttpClientFactory httpClientFactory,
+ IHttpContextAccessor httpContextAccessor)
+ {
+ _dlnaManager = dlnaManager;
+ _authContext = authContext;
+ _userManager = userManager;
+ _libraryManager = libraryManager;
+ _mediaSourceManager = mediaSourceManager;
+ _serverConfigurationManager = serverConfigurationManager;
+ _mediaEncoder = mediaEncoder;
+ _fileSystem = fileSystem;
+ _subtitleEncoder = subtitleEncoder;
+ _configuration = configuration;
+ _deviceManager = deviceManager;
+ _transcodingJobHelper = transcodingJobHelper;
+ _httpClientFactory = httpClientFactory;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ ///
+ /// Get audio stream.
+ ///
+ /// Transcoding job type.
+ /// Streaming controller.Request dto.
+ /// A containing the resulting .
+ public async Task GetAudioStream(
+ TranscodingJobType transcodingJobType,
+ StreamingRequestDto streamingRequest)
+ {
+ bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head;
+ var cancellationTokenSource = new CancellationTokenSource();
+
+ using var state = await StreamingHelpers.GetStreamingState(
+ streamingRequest,
+ _httpContextAccessor.HttpContext.Request,
+ _authContext,
+ _mediaSourceManager,
+ _userManager,
+ _libraryManager,
+ _serverConfigurationManager,
+ _mediaEncoder,
+ _fileSystem,
+ _subtitleEncoder,
+ _configuration,
+ _dlnaManager,
+ _deviceManager,
+ _transcodingJobHelper,
+ transcodingJobType,
+ cancellationTokenSource.Token)
+ .ConfigureAwait(false);
+
+ if (streamingRequest.Static && state.DirectStreamProvider != null)
+ {
+ StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);
+
+ await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
+ {
+ AllowEndOfFile = false
+ }.WriteToAsync(_httpContextAccessor.HttpContext.Response.Body, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ // TODO (moved from MediaBrowser.Api): Don't hardcode contentType
+ return new FileStreamResult(_httpContextAccessor.HttpContext.Response.Body, MimeTypes.GetMimeType("file.ts")!);
+ }
+
+ // Static remote stream
+ if (streamingRequest.Static && state.InputProtocol == MediaProtocol.Http)
+ {
+ StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);
+
+ var httpClient = _httpClientFactory.CreateClient();
+ return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false);
+ }
+
+ if (streamingRequest.Static && state.InputProtocol != MediaProtocol.File)
+ {
+ return new BadRequestObjectResult($"Input protocol {state.InputProtocol} cannot be streamed statically");
+ }
+
+ var outputPath = state.OutputFilePath;
+ var outputPathExists = System.IO.File.Exists(outputPath);
+
+ var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
+ var isTranscodeCached = outputPathExists && transcodingJob != null;
+
+ StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, streamingRequest.Static || isTranscodeCached, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);
+
+ // Static stream
+ if (streamingRequest.Static)
+ {
+ var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);
+
+ if (state.MediaSource.IsInfiniteStream)
+ {
+ await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
+ {
+ AllowEndOfFile = false
+ }.WriteToAsync(_httpContextAccessor.HttpContext.Response.Body, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ return new FileStreamResult(_httpContextAccessor.HttpContext.Response.Body, contentType);
+ }
+
+ return FileStreamResponseHelpers.GetStaticFileResult(
+ state.MediaPath,
+ contentType,
+ isHeadRequest,
+ _httpContextAccessor.HttpContext);
+ }
+
+ // Need to start ffmpeg (because media can't be returned directly)
+ var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
+ var encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
+ var ffmpegCommandLineArguments = encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
+ return await FileStreamResponseHelpers.GetTranscodedFile(
+ state,
+ isHeadRequest,
+ _httpContextAccessor.HttpContext,
+ _transcodingJobHelper,
+ ffmpegCommandLineArguments,
+ transcodingJobType,
+ cancellationTokenSource).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
new file mode 100644
index 0000000000..6a8829d462
--- /dev/null
+++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs
@@ -0,0 +1,550 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net;
+using System.Security.Claims;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Api.Models.StreamingDtos;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Net;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Jellyfin.Api.Helpers
+{
+ ///
+ /// Dynamic hls helper.
+ ///
+ public class DynamicHlsHelper
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly IUserManager _userManager;
+ private readonly IDlnaManager _dlnaManager;
+ private readonly IAuthorizationContext _authContext;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IFileSystem _fileSystem;
+ private readonly ISubtitleEncoder _subtitleEncoder;
+ private readonly IConfiguration _configuration;
+ private readonly IDeviceManager _deviceManager;
+ private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly INetworkManager _networkManager;
+ private readonly ILogger _logger;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of .
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public DynamicHlsHelper(
+ ILibraryManager libraryManager,
+ IUserManager userManager,
+ IDlnaManager dlnaManager,
+ IAuthorizationContext authContext,
+ IMediaSourceManager mediaSourceManager,
+ IServerConfigurationManager serverConfigurationManager,
+ IMediaEncoder mediaEncoder,
+ IFileSystem fileSystem,
+ ISubtitleEncoder subtitleEncoder,
+ IConfiguration configuration,
+ IDeviceManager deviceManager,
+ TranscodingJobHelper transcodingJobHelper,
+ INetworkManager networkManager,
+ ILogger logger,
+ IHttpContextAccessor httpContextAccessor)
+ {
+ _libraryManager = libraryManager;
+ _userManager = userManager;
+ _dlnaManager = dlnaManager;
+ _authContext = authContext;
+ _mediaSourceManager = mediaSourceManager;
+ _serverConfigurationManager = serverConfigurationManager;
+ _mediaEncoder = mediaEncoder;
+ _fileSystem = fileSystem;
+ _subtitleEncoder = subtitleEncoder;
+ _configuration = configuration;
+ _deviceManager = deviceManager;
+ _transcodingJobHelper = transcodingJobHelper;
+ _networkManager = networkManager;
+ _logger = logger;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ ///
+ /// Get master hls playlist.
+ ///
+ /// Transcoding job type.
+ /// Streaming request dto.
+ /// Enable adaptive bitrate streaming.
+ /// A containing the resulting .
+ public async Task GetMasterHlsPlaylist(
+ TranscodingJobType transcodingJobType,
+ StreamingRequestDto streamingRequest,
+ bool enableAdaptiveBitrateStreaming)
+ {
+ var isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == WebRequestMethods.Http.Head;
+ var cancellationTokenSource = new CancellationTokenSource();
+ return await GetMasterPlaylistInternal(
+ streamingRequest,
+ isHeadRequest,
+ enableAdaptiveBitrateStreaming,
+ transcodingJobType,
+ cancellationTokenSource).ConfigureAwait(false);
+ }
+
+ private async Task GetMasterPlaylistInternal(
+ StreamingRequestDto streamingRequest,
+ bool isHeadRequest,
+ bool enableAdaptiveBitrateStreaming,
+ TranscodingJobType transcodingJobType,
+ CancellationTokenSource cancellationTokenSource)
+ {
+ using var state = await StreamingHelpers.GetStreamingState(
+ streamingRequest,
+ _httpContextAccessor.HttpContext.Request,
+ _authContext,
+ _mediaSourceManager,
+ _userManager,
+ _libraryManager,
+ _serverConfigurationManager,
+ _mediaEncoder,
+ _fileSystem,
+ _subtitleEncoder,
+ _configuration,
+ _dlnaManager,
+ _deviceManager,
+ _transcodingJobHelper,
+ transcodingJobType,
+ cancellationTokenSource.Token)
+ .ConfigureAwait(false);
+
+ _httpContextAccessor.HttpContext.Response.Headers.Add(HeaderNames.Expires, "0");
+ if (isHeadRequest)
+ {
+ return new FileContentResult(Array.Empty(), MimeTypes.GetMimeType("playlist.m3u8"));
+ }
+
+ var totalBitrate = state.OutputAudioBitrate ?? 0 + state.OutputVideoBitrate ?? 0;
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("#EXTM3U");
+
+ var isLiveStream = state.IsSegmentedLiveStream;
+
+ var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString();
+
+ // from universal audio service
+ if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer))
+ {
+ queryString += "&SegmentContainer=" + state.Request.SegmentContainer;
+ }
+
+ // from universal audio service
+ if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1)
+ {
+ queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
+ }
+
+ // Main stream
+ var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8";
+
+ playlistUrl += queryString;
+
+ var subtitleStreams = state.MediaSource
+ .MediaStreams
+ .Where(i => i.IsTextSubtitleStream)
+ .ToList();
+
+ var subtitleGroup = subtitleStreams.Count > 0 && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest!.EnableSubtitlesInManifest)
+ ? "subs"
+ : null;
+
+ // If we're burning in subtitles then don't add additional subs to the manifest
+ if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
+ {
+ subtitleGroup = null;
+ }
+
+ if (!string.IsNullOrWhiteSpace(subtitleGroup))
+ {
+ AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.Request.HttpContext.User);
+ }
+
+ AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
+
+ if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming, _httpContextAccessor.HttpContext.Request.HttpContext.Connection.RemoteIpAddress))
+ {
+ var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0;
+
+ // By default, vary by just 200k
+ var variation = GetBitrateVariation(totalBitrate);
+
+ var newBitrate = totalBitrate - variation;
+ var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
+ AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
+
+ variation *= 2;
+ newBitrate = totalBitrate - variation;
+ variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
+ AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
+ }
+
+ return new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8"));
+ }
+
+ private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string? subtitleGroup)
+ {
+ builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
+ .Append(bitrate.ToString(CultureInfo.InvariantCulture))
+ .Append(",AVERAGE-BANDWIDTH=")
+ .Append(bitrate.ToString(CultureInfo.InvariantCulture));
+
+ AppendPlaylistCodecsField(builder, state);
+
+ AppendPlaylistResolutionField(builder, state);
+
+ AppendPlaylistFramerateField(builder, state);
+
+ if (!string.IsNullOrWhiteSpace(subtitleGroup))
+ {
+ builder.Append(",SUBTITLES=\"")
+ .Append(subtitleGroup)
+ .Append('"');
+ }
+
+ builder.Append(Environment.NewLine);
+ builder.AppendLine(url);
+ }
+
+ ///
+ /// Appends a CODECS field containing formatted strings of
+ /// the active streams output video and audio codecs.
+ ///
+ ///
+ ///
+ ///
+ /// StringBuilder to append the field to.
+ /// StreamState of the current stream.
+ private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state)
+ {
+ // Video
+ string videoCodecs = string.Empty;
+ int? videoCodecLevel = GetOutputVideoCodecLevel(state);
+ if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue)
+ {
+ videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value);
+ }
+
+ // Audio
+ string audioCodecs = string.Empty;
+ if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec))
+ {
+ audioCodecs = GetPlaylistAudioCodecs(state);
+ }
+
+ StringBuilder codecs = new StringBuilder();
+
+ codecs.Append(videoCodecs);
+
+ if (!string.IsNullOrEmpty(videoCodecs) && !string.IsNullOrEmpty(audioCodecs))
+ {
+ codecs.Append(',');
+ }
+
+ codecs.Append(audioCodecs);
+
+ if (codecs.Length > 1)
+ {
+ builder.Append(",CODECS=\"")
+ .Append(codecs)
+ .Append('"');
+ }
+ }
+
+ ///
+ /// Appends a RESOLUTION field containing the resolution of the output stream.
+ ///
+ ///
+ /// StringBuilder to append the field to.
+ /// StreamState of the current stream.
+ private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state)
+ {
+ if (state.OutputWidth.HasValue && state.OutputHeight.HasValue)
+ {
+ builder.Append(",RESOLUTION=")
+ .Append(state.OutputWidth.GetValueOrDefault())
+ .Append('x')
+ .Append(state.OutputHeight.GetValueOrDefault());
+ }
+ }
+
+ ///
+ /// Appends a FRAME-RATE field containing the framerate of the output stream.
+ ///
+ ///
+ /// StringBuilder to append the field to.
+ /// StreamState of the current stream.
+ private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state)
+ {
+ double? framerate = null;
+ if (state.TargetFramerate.HasValue)
+ {
+ framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3);
+ }
+ else if (state.VideoStream?.RealFrameRate != null)
+ {
+ framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3);
+ }
+
+ if (framerate.HasValue)
+ {
+ builder.Append(",FRAME-RATE=")
+ .Append(framerate.Value);
+ }
+ }
+
+ private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream, bool enableAdaptiveBitrateStreaming, IPAddress ipAddress)
+ {
+ // Within the local network this will likely do more harm than good.
+ var ip = RequestHelpers.NormalizeIp(ipAddress).ToString();
+ if (_networkManager.IsInLocalNetwork(ip))
+ {
+ return false;
+ }
+
+ if (!enableAdaptiveBitrateStreaming)
+ {
+ return false;
+ }
+
+ if (isLiveStream || string.IsNullOrWhiteSpace(state.MediaPath))
+ {
+ // Opening live streams is so slow it's not even worth it
+ return false;
+ }
+
+ if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
+ {
+ return false;
+ }
+
+ if (EncodingHelper.IsCopyCodec(state.OutputAudioCodec))
+ {
+ return false;
+ }
+
+ if (!state.IsOutputVideo)
+ {
+ return false;
+ }
+
+ // Having problems in android
+ return false;
+ // return state.VideoRequest.VideoBitRate.HasValue;
+ }
+
+ private void AddSubtitles(StreamState state, IEnumerable subtitles, StringBuilder builder, ClaimsPrincipal user)
+ {
+ var selectedIndex = state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Hls ? (int?)null : state.SubtitleStream.Index;
+ const string Format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\"";
+
+ foreach (var stream in subtitles)
+ {
+ var name = stream.DisplayTitle;
+
+ var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
+ var isForced = stream.IsForced;
+
+ var url = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}",
+ state.Request.MediaSourceId,
+ stream.Index.ToString(CultureInfo.InvariantCulture),
+ 30.ToString(CultureInfo.InvariantCulture),
+ ClaimHelpers.GetToken(user));
+
+ var line = string.Format(
+ CultureInfo.InvariantCulture,
+ Format,
+ name,
+ isDefault ? "YES" : "NO",
+ isForced ? "YES" : "NO",
+ url,
+ stream.Language ?? "Unknown");
+
+ builder.AppendLine(line);
+ }
+ }
+
+ ///
+ /// Get the H.26X level of the output video stream.
+ ///
+ /// StreamState of the current stream.
+ /// H.26X level of the output video stream.
+ private int? GetOutputVideoCodecLevel(StreamState state)
+ {
+ string? levelString;
+ if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
+ && state.VideoStream.Level.HasValue)
+ {
+ levelString = state.VideoStream?.Level.ToString();
+ }
+ else
+ {
+ levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec);
+ }
+
+ if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel))
+ {
+ return parsedLevel;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets a formatted string of the output audio codec, for use in the CODECS field.
+ ///
+ ///
+ ///
+ /// StreamState of the current stream.
+ /// Formatted audio codec string.
+ private string GetPlaylistAudioCodecs(StreamState state)
+ {
+ if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
+ string? profile = state.GetRequestedProfiles("aac").FirstOrDefault();
+ return HlsCodecStringHelpers.GetAACString(profile);
+ }
+
+ if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
+ return HlsCodecStringHelpers.GetMP3String();
+ }
+
+ if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
+ {
+ return HlsCodecStringHelpers.GetAC3String();
+ }
+
+ if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
+ {
+ return HlsCodecStringHelpers.GetEAC3String();
+ }
+
+ return string.Empty;
+ }
+
+ ///
+ /// Gets a formatted string of the output video codec, for use in the CODECS field.
+ ///
+ ///
+ ///
+ /// StreamState of the current stream.
+ /// Video codec.
+ /// Video level.
+ /// Formatted video codec string.
+ private string GetPlaylistVideoCodecs(StreamState state, string codec, int level)
+ {
+ if (level == 0)
+ {
+ // This is 0 when there's no requested H.26X level in the device profile
+ // and the source is not encoded in H.26X
+ _logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist");
+ return string.Empty;
+ }
+
+ if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
+ return HlsCodecStringHelpers.GetH264String(profile, level);
+ }
+
+ if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
+ {
+ string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
+
+ return HlsCodecStringHelpers.GetH265String(profile, level);
+ }
+
+ return string.Empty;
+ }
+
+ private int GetBitrateVariation(int bitrate)
+ {
+ // By default, vary by just 50k
+ var variation = 50000;
+
+ if (bitrate >= 10000000)
+ {
+ variation = 2000000;
+ }
+ else if (bitrate >= 5000000)
+ {
+ variation = 1500000;
+ }
+ else if (bitrate >= 3000000)
+ {
+ variation = 1000000;
+ }
+ else if (bitrate >= 2000000)
+ {
+ variation = 500000;
+ }
+ else if (bitrate >= 1000000)
+ {
+ variation = 300000;
+ }
+ else if (bitrate >= 600000)
+ {
+ variation = 200000;
+ }
+ else if (bitrate >= 400000)
+ {
+ variation = 100000;
+ }
+
+ return variation;
+ }
+
+ private string ReplaceBitrate(string url, int oldValue, int newValue)
+ {
+ return url.Replace(
+ "videobitrate=" + oldValue.ToString(CultureInfo.InvariantCulture),
+ "videobitrate=" + newValue.ToString(CultureInfo.InvariantCulture),
+ StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
index a463783e00..deb54dbe61 100644
--- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
+++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
@@ -22,31 +22,32 @@ namespace Jellyfin.Api.Helpers
///
/// The current .
/// Whether the current request is a HTTP HEAD request so only the headers get returned.
- /// The managing the response.
/// The making the remote request.
+ /// The current http context.
/// A containing the API response.
public static async Task GetStaticRemoteStreamResult(
StreamState state,
bool isHeadRequest,
- ControllerBase controller,
- HttpClient httpClient)
+ HttpClient httpClient,
+ HttpContext httpContext)
{
if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent))
{
httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, useragent);
}
- using var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false);
+ // Can't dispose the response as it's required up the call chain.
+ var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false);
var contentType = response.Content.Headers.ContentType.ToString();
- controller.Response.Headers[HeaderNames.AcceptRanges] = "none";
+ httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";
if (isHeadRequest)
{
- return controller.File(Array.Empty(), contentType);
+ return new FileContentResult(Array.Empty(), contentType);
}
- return controller.File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType);
+ return new FileStreamResult(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType);
}
///
@@ -55,23 +56,23 @@ namespace Jellyfin.Api.Helpers
/// The path to the file.
/// The content type of the file.
/// Whether the current request is a HTTP HEAD request so only the headers get returned.
- /// The managing the response.
+ /// The current http context.
/// An the file.
public static ActionResult GetStaticFileResult(
string path,
string contentType,
bool isHeadRequest,
- ControllerBase controller)
+ HttpContext httpContext)
{
- controller.Response.ContentType = contentType;
+ httpContext.Response.ContentType = contentType;
// if the request is a head request, return a NoContent result with the same headers as it would with a GET request
if (isHeadRequest)
{
- return controller.NoContent();
+ return new NoContentResult();
}
- return controller.PhysicalFile(path, contentType);
+ return new PhysicalFileResult(path, contentType);
}
///
@@ -79,34 +80,32 @@ namespace Jellyfin.Api.Helpers
///
/// The current .
/// Whether the current request is a HTTP HEAD request so only the headers get returned.
- /// The managing the response.
+ /// The current http context.
/// The singleton.
/// The command line arguments to start ffmpeg.
- /// The starting the transcoding.
/// The .
/// The .
/// A containing the transcoded file.
public static async Task GetTranscodedFile(
StreamState state,
bool isHeadRequest,
- ControllerBase controller,
+ HttpContext httpContext,
TranscodingJobHelper transcodingJobHelper,
string ffmpegCommandLineArguments,
- HttpRequest request,
TranscodingJobType transcodingJobType,
CancellationTokenSource cancellationTokenSource)
{
// Use the command line args with a dummy playlist path
var outputPath = state.OutputFilePath;
- controller.Response.Headers[HeaderNames.AcceptRanges] = "none";
+ httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";
var contentType = state.GetMimeType(outputPath);
// Headers only
if (isHeadRequest)
{
- return controller.File(Array.Empty(), contentType);
+ return new FileContentResult(Array.Empty(), contentType);
}
var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath);
@@ -116,7 +115,7 @@ namespace Jellyfin.Api.Helpers
TranscodingJobDto? job;
if (!File.Exists(outputPath))
{
- job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false);
+ job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, httpContext.Request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false);
}
else
{
@@ -127,7 +126,7 @@ namespace Jellyfin.Api.Helpers
var memoryStream = new MemoryStream();
await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
memoryStream.Position = 0;
- return controller.File(memoryStream, contentType);
+ return new FileStreamResult(memoryStream, contentType);
}
finally
{
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
new file mode 100644
index 0000000000..d2d1855a4c
--- /dev/null
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -0,0 +1,573 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Session;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Api.Helpers
+{
+ ///
+ /// Media info helper.
+ ///
+ public class MediaInfoHelper
+ {
+ private readonly IUserManager _userManager;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+ private readonly ILogger _logger;
+ private readonly INetworkManager _networkManager;
+ private readonly IDeviceManager _deviceManager;
+ private readonly IAuthorizationContext _authContext;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public MediaInfoHelper(
+ IUserManager userManager,
+ ILibraryManager libraryManager,
+ IMediaSourceManager mediaSourceManager,
+ IMediaEncoder mediaEncoder,
+ IServerConfigurationManager serverConfigurationManager,
+ ILogger logger,
+ INetworkManager networkManager,
+ IDeviceManager deviceManager,
+ IAuthorizationContext authContext)
+ {
+ _userManager = userManager;
+ _libraryManager = libraryManager;
+ _mediaSourceManager = mediaSourceManager;
+ _mediaEncoder = mediaEncoder;
+ _serverConfigurationManager = serverConfigurationManager;
+ _logger = logger;
+ _networkManager = networkManager;
+ _deviceManager = deviceManager;
+ _authContext = authContext;
+ }
+
+ ///
+ /// Get playback info.
+ ///
+ /// Item id.
+ /// User Id.
+ /// Media source id.
+ /// Live stream id.
+ /// A containing the .
+ public async Task GetPlaybackInfo(
+ Guid id,
+ Guid? userId,
+ string? mediaSourceId = null,
+ string? liveStreamId = null)
+ {
+ var user = userId.HasValue && !userId.Equals(Guid.Empty)
+ ? _userManager.GetUserById(userId.Value)
+ : null;
+ var item = _libraryManager.GetItemById(id);
+ var result = new PlaybackInfoResponse();
+
+ MediaSourceInfo[] mediaSources;
+ if (string.IsNullOrWhiteSpace(liveStreamId))
+ {
+ // TODO (moved from MediaBrowser.Api) handle supportedLiveMediaTypes?
+ var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false);
+
+ if (string.IsNullOrWhiteSpace(mediaSourceId))
+ {
+ mediaSources = mediaSourcesList.ToArray();
+ }
+ else
+ {
+ mediaSources = mediaSourcesList
+ .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
+ .ToArray();
+ }
+ }
+ else
+ {
+ var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
+
+ mediaSources = new[] { mediaSource };
+ }
+
+ if (mediaSources.Length == 0)
+ {
+ result.MediaSources = Array.Empty();
+
+ result.ErrorCode ??= PlaybackErrorCode.NoCompatibleStream;
+ }
+ else
+ {
+ // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
+ // Should we move this directly into MediaSourceManager?
+ result.MediaSources = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
+
+ result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ }
+
+ return result;
+ }
+
+ ///
+ /// SetDeviceSpecificData.
+ ///
+ /// Item to set data for.
+ /// Media source info.
+ /// Device profile.
+ /// Authorization info.
+ /// Max bitrate.
+ /// Start time ticks.
+ /// Media source id.
+ /// Audio stream index.
+ /// Subtitle stream index.
+ /// Max audio channels.
+ /// Play session id.
+ /// User id.
+ /// Enable direct play.
+ /// Enable direct stream.
+ /// Enable transcoding.
+ /// Allow video stream copy.
+ /// Allow audio stream copy.
+ /// Requesting IP address.
+ public void SetDeviceSpecificData(
+ BaseItem item,
+ MediaSourceInfo mediaSource,
+ DeviceProfile profile,
+ AuthorizationInfo auth,
+ long? maxBitrate,
+ long startTimeTicks,
+ string mediaSourceId,
+ int? audioStreamIndex,
+ int? subtitleStreamIndex,
+ int? maxAudioChannels,
+ string playSessionId,
+ Guid userId,
+ bool enableDirectPlay,
+ bool enableDirectStream,
+ bool enableTranscoding,
+ bool allowVideoStreamCopy,
+ bool allowAudioStreamCopy,
+ string ipAddress)
+ {
+ var streamBuilder = new StreamBuilder(_mediaEncoder, _logger);
+
+ var options = new VideoOptions
+ {
+ MediaSources = new[] { mediaSource },
+ Context = EncodingContext.Streaming,
+ DeviceId = auth.DeviceId,
+ ItemId = item.Id,
+ Profile = profile,
+ MaxAudioChannels = maxAudioChannels
+ };
+
+ if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
+ {
+ options.MediaSourceId = mediaSourceId;
+ options.AudioStreamIndex = audioStreamIndex;
+ options.SubtitleStreamIndex = subtitleStreamIndex;
+ }
+
+ var user = _userManager.GetUserById(userId);
+
+ if (!enableDirectPlay)
+ {
+ mediaSource.SupportsDirectPlay = false;
+ }
+
+ if (!enableDirectStream)
+ {
+ mediaSource.SupportsDirectStream = false;
+ }
+
+ if (!enableTranscoding)
+ {
+ mediaSource.SupportsTranscoding = false;
+ }
+
+ if (item is Audio)
+ {
+ _logger.LogInformation(
+ "User policy for {0}. EnableAudioPlaybackTranscoding: {1}",
+ user.Username,
+ user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
+ }
+ else
+ {
+ _logger.LogInformation(
+ "User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}",
+ user.Username,
+ user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
+ user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding),
+ user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
+ }
+
+ // Beginning of Playback Determination: Attempt DirectPlay first
+ if (mediaSource.SupportsDirectPlay)
+ {
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
+ {
+ mediaSource.SupportsDirectPlay = false;
+ }
+ else
+ {
+ var supportsDirectStream = mediaSource.SupportsDirectStream;
+
+ // Dummy this up to fool StreamBuilder
+ mediaSource.SupportsDirectStream = true;
+ options.MaxBitrate = maxBitrate;
+
+ if (item is Audio)
+ {
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
+ {
+ options.ForceDirectPlay = true;
+ }
+ }
+ else if (item is Video)
+ {
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
+ {
+ options.ForceDirectPlay = true;
+ }
+ }
+
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
+
+ if (streamInfo == null || !streamInfo.IsDirectStream)
+ {
+ mediaSource.SupportsDirectPlay = false;
+ }
+
+ // Set this back to what it was
+ mediaSource.SupportsDirectStream = supportsDirectStream;
+
+ if (streamInfo != null)
+ {
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ }
+
+ if (mediaSource.SupportsDirectStream)
+ {
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
+ {
+ mediaSource.SupportsDirectStream = false;
+ }
+ else
+ {
+ options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress);
+
+ if (item is Audio)
+ {
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
+ {
+ options.ForceDirectStream = true;
+ }
+ }
+ else if (item is Video)
+ {
+ if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
+ && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
+ {
+ options.ForceDirectStream = true;
+ }
+ }
+
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
+
+ if (streamInfo == null || !streamInfo.IsDirectStream)
+ {
+ mediaSource.SupportsDirectStream = false;
+ }
+
+ if (streamInfo != null)
+ {
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ }
+
+ if (mediaSource.SupportsTranscoding)
+ {
+ options.MaxBitrate = GetMaxBitrate(maxBitrate, user, ipAddress);
+
+ // The MediaSource supports direct stream, now test to see if the client supports it
+ var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
+ ? streamBuilder.BuildAudioItem(options)
+ : streamBuilder.BuildVideoItem(options);
+
+ if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
+ {
+ if (streamInfo != null)
+ {
+ streamInfo.PlaySessionId = playSessionId;
+ streamInfo.StartPositionTicks = startTimeTicks;
+ mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+ mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+
+ // Do this after the above so that StartPositionTicks is set
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ else
+ {
+ if (streamInfo != null)
+ {
+ streamInfo.PlaySessionId = playSessionId;
+
+ if (streamInfo.PlayMethod == PlayMethod.Transcode)
+ {
+ streamInfo.StartPositionTicks = startTimeTicks;
+ mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+
+ if (!allowVideoStreamCopy)
+ {
+ mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
+ }
+
+ if (!allowAudioStreamCopy)
+ {
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+ }
+
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+ }
+
+ if (!allowAudioStreamCopy)
+ {
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+ }
+
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+
+ // Do this after the above so that StartPositionTicks is set
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ }
+
+ foreach (var attachment in mediaSource.MediaAttachments)
+ {
+ attachment.DeliveryUrl = string.Format(
+ CultureInfo.InvariantCulture,
+ "/Videos/{0}/{1}/Attachments/{2}",
+ item.Id,
+ mediaSource.Id,
+ attachment.Index);
+ }
+ }
+
+ ///
+ /// Sort media source.
+ ///
+ /// Playback info response.
+ /// Max bitrate.
+ public void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate)
+ {
+ var originalList = result.MediaSources.ToList();
+
+ result.MediaSources = result.MediaSources.OrderBy(i =>
+ {
+ // Nothing beats direct playing a file
+ if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
+ {
+ return 0;
+ }
+
+ return 1;
+ })
+ .ThenBy(i =>
+ {
+ // Let's assume direct streaming a file is just as desirable as direct playing a remote url
+ if (i.SupportsDirectPlay || i.SupportsDirectStream)
+ {
+ return 0;
+ }
+
+ return 1;
+ })
+ .ThenBy(i =>
+ {
+ return i.Protocol switch
+ {
+ MediaProtocol.File => 0,
+ _ => 1,
+ };
+ })
+ .ThenBy(i =>
+ {
+ if (maxBitrate.HasValue && i.Bitrate.HasValue)
+ {
+ return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
+ }
+
+ return 1;
+ })
+ .ThenBy(originalList.IndexOf)
+ .ToArray();
+ }
+
+ ///
+ /// Open media source.
+ ///
+ /// Http Request.
+ /// Live stream request.
+ /// A containing the .
+ public async Task OpenMediaSource(HttpRequest httpRequest, LiveStreamRequest request)
+ {
+ var authInfo = _authContext.GetAuthorizationInfo(httpRequest);
+
+ var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
+
+ var profile = request.DeviceProfile;
+ if (profile == null)
+ {
+ var clientCapabilities = _deviceManager.GetCapabilities(authInfo.DeviceId);
+ if (clientCapabilities != null)
+ {
+ profile = clientCapabilities.DeviceProfile;
+ }
+ }
+
+ if (profile != null)
+ {
+ var item = _libraryManager.GetItemById(request.ItemId);
+
+ SetDeviceSpecificData(
+ item,
+ result.MediaSource,
+ profile,
+ authInfo,
+ request.MaxStreamingBitrate,
+ request.StartTimeTicks ?? 0,
+ result.MediaSource.Id,
+ request.AudioStreamIndex,
+ request.SubtitleStreamIndex,
+ request.MaxAudioChannels,
+ request.PlaySessionId,
+ request.UserId,
+ request.EnableDirectPlay,
+ request.EnableDirectStream,
+ true,
+ true,
+ true,
+ httpRequest.HttpContext.Connection.RemoteIpAddress.ToString());
+ }
+ else
+ {
+ if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
+ {
+ result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
+ }
+ }
+
+ // here was a check if (result.MediaSource != null) but Rider said it will never be null
+ NormalizeMediaSourceContainer(result.MediaSource, profile!, DlnaProfileType.Video);
+
+ return result;
+ }
+
+ ///
+ /// Normalize media source container.
+ ///
+ /// Media source.
+ /// Device profile.
+ /// Dlna profile type.
+ public void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type)
+ {
+ mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type);
+ }
+
+ private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
+ {
+ var profiles = info.GetSubtitleProfiles(_mediaEncoder, false, "-", accessToken);
+ mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
+
+ mediaSource.TranscodeReasons = info.TranscodeReasons;
+
+ foreach (var profile in profiles)
+ {
+ foreach (var stream in mediaSource.MediaStreams)
+ {
+ if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
+ {
+ stream.DeliveryMethod = profile.DeliveryMethod;
+
+ if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
+ {
+ stream.DeliveryUrl = profile.Url.TrimStart('-');
+ stream.IsExternalUrl = profile.IsExternalUrl;
+ }
+ }
+ }
+ }
+ }
+
+ private long? GetMaxBitrate(long? clientMaxBitrate, User user, string ipAddress)
+ {
+ var maxBitrate = clientMaxBitrate;
+ var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
+
+ if (remoteClientMaxBitrate <= 0)
+ {
+ remoteClientMaxBitrate = _serverConfigurationManager.Configuration.RemoteClientBitrateLimit;
+ }
+
+ if (remoteClientMaxBitrate > 0)
+ {
+ var isInLocalNetwork = _networkManager.IsInLocalNetwork(ipAddress);
+
+ _logger.LogInformation("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, ipAddress, isInLocalNetwork);
+ if (!isInLocalNetwork)
+ {
+ maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
+ }
+ }
+
+ return maxBitrate;
+ }
+ }
+}
diff --git a/Jellyfin.Api/TypeConverters/DateTimeTypeConverter.cs b/Jellyfin.Api/TypeConverters/DateTimeTypeConverter.cs
new file mode 100644
index 0000000000..315b473293
--- /dev/null
+++ b/Jellyfin.Api/TypeConverters/DateTimeTypeConverter.cs
@@ -0,0 +1,44 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Jellyfin.Api.TypeConverters
+{
+ ///
+ /// Custom datetime parser.
+ ///
+ public class DateTimeTypeConverter : TypeConverter
+ {
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == typeof(string))
+ {
+ return true;
+ }
+
+ return base.CanConvertFrom(context, sourceType);
+ }
+
+ ///
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if (value is string dateString)
+ {
+ // Mark Played Item.
+ if (DateTime.TryParseExact(dateString, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var dateTime))
+ {
+ return dateTime;
+ }
+
+ // Get Activity Logs.
+ if (DateTime.TryParse(dateString, null, DateTimeStyles.RoundtripKind, out dateTime))
+ {
+ return dateTime;
+ }
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+ }
+}
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 83d8fac5b5..2e2bfea684 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -154,6 +154,7 @@ namespace Jellyfin.Server.Extensions
opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());
opts.OutputFormatters.Add(new CssOutputFormatter());
+ opts.OutputFormatters.Add(new XmlOutputFormatter());
})
// Clear app parts to avoid other assemblies being picked up
diff --git a/Jellyfin.Server/Formatters/XmlOutputFormatter.cs b/Jellyfin.Server/Formatters/XmlOutputFormatter.cs
new file mode 100644
index 0000000000..58319657d6
--- /dev/null
+++ b/Jellyfin.Server/Formatters/XmlOutputFormatter.cs
@@ -0,0 +1,31 @@
+using System.Net.Mime;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Formatters;
+
+namespace Jellyfin.Server.Formatters
+{
+ ///
+ /// Xml output formatter.
+ ///
+ public class XmlOutputFormatter : TextOutputFormatter
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public XmlOutputFormatter()
+ {
+ SupportedMediaTypes.Add(MediaTypeNames.Text.Xml);
+ SupportedMediaTypes.Add("text/xml;charset=UTF-8");
+ SupportedEncodings.Add(Encoding.UTF8);
+ SupportedEncodings.Add(Encoding.Unicode);
+ }
+
+ ///
+ public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
+ {
+ return context.HttpContext.Response.WriteAsync(context.Object?.ToString());
+ }
+ }
+}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 108d8f881e..d0dd183c68 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -1,4 +1,6 @@
-using System.Net.Http;
+using System;
+using System.ComponentModel;
+using Jellyfin.Api.TypeConverters;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.Middleware;
using Jellyfin.Server.Models;
@@ -94,6 +96,9 @@ namespace Jellyfin.Server
});
app.Use(serverApplicationHost.ExecuteHttpHandlerAsync);
+
+ // Add type descriptor for legacy datetime parsing.
+ TypeDescriptor.AddAttributes(typeof(DateTime?), new TypeConverterAttribute(typeof(DateTimeTypeConverter)));
}
}
}
diff --git a/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
index d18fd95d5f..427f1fa7e0 100644
--- a/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
+++ b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs
@@ -8,7 +8,7 @@ using System.Text.Json.Serialization;
namespace MediaBrowser.Common.Json.Converters
{
///
- /// Long to String JSON converter.
+ /// Parse JSON string as long.
/// Javascript does not support 64-bit integers.
///
public class JsonInt64Converter : JsonConverter
@@ -43,14 +43,14 @@ namespace MediaBrowser.Common.Json.Converters
}
///
- /// Write long to JSON string.
+ /// Write long to JSON long.
///
/// .
/// Value to write.
/// Options.
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
{
- writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo));
+ writer.WriteNumberValue(value);
}
}
}
diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs
index 0dadc283ee..988557f42c 100644
--- a/MediaBrowser.Controller/Dto/IDtoService.cs
+++ b/MediaBrowser.Controller/Dto/IDtoService.cs
@@ -2,7 +2,6 @@ using System.Collections.Generic;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Dto
{
@@ -11,20 +10,6 @@ namespace MediaBrowser.Controller.Dto
///
public interface IDtoService
{
- ///
- /// Gets the dto id.
- ///
- /// The item.
- /// System.String.
- string GetDtoId(BaseItem item);
-
- ///
- /// Attaches the primary image aspect ratio.
- ///
- /// The dto.
- /// The item.
- void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item);
-
///
/// Gets the primary image aspect ratio.
///
@@ -32,15 +17,6 @@ namespace MediaBrowser.Controller.Dto
/// System.Nullable<System.Double>.
double? GetPrimaryImageAspectRatio(BaseItem item);
- ///
- /// Gets the base item dto.
- ///
- /// The item.
- /// The fields.
- /// The user.
- /// The owner.
- BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null);
-
///
/// Gets the base item dto.
///
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index ef744ee3c1..c70886e9cb 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
@@ -157,7 +158,7 @@ namespace MediaBrowser.Controller.Providers
/// The URL.
/// The cancellation token.
/// Task{HttpResponseInfo}.
- Task GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
+ Task GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
Dictionary GetRefreshQueue();
diff --git a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs
index 68a968f901..ee8f5b860a 100644
--- a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs
+++ b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
@@ -34,6 +35,6 @@ namespace MediaBrowser.Controller.Providers
/// The URL.
/// The cancellation token.
/// Task{HttpResponseInfo}.
- Task GetImageResponse(string url, CancellationToken cancellationToken);
+ Task GetImageResponse(string url, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs b/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs
index fdb0c8eb56..17ad9e4a3a 100644
--- a/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs
+++ b/MediaBrowser.Controller/Providers/IRemoteSearchProvider.cs
@@ -1,3 +1,4 @@
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
@@ -12,6 +13,6 @@ namespace MediaBrowser.Controller.Providers
/// The URL.
/// The cancellation token.
/// Task{HttpResponseInfo}.
- Task GetImageResponse(string url, CancellationToken cancellationToken);
+ Task GetImageResponse(string url, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Model/Extensions/ListHelper.cs b/MediaBrowser.Model/Extensions/ListHelper.cs
deleted file mode 100644
index b893a3509a..0000000000
--- a/MediaBrowser.Model/Extensions/ListHelper.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Model.Extensions
-{
- // TODO: @bond remove
- public static class ListHelper
- {
- public static bool ContainsIgnoreCase(string[] list, string value)
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- foreach (var item in list)
- {
- if (string.Equals(item, value, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
-
- return false;
- }
- }
-}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 6cc3ca3691..a5eb095c46 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -465,9 +465,16 @@ namespace MediaBrowser.Providers.Manager
try
{
- var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
+ using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
- await _providerManager.SaveImage(item, response.Content, response.ContentType, type, null, cancellationToken).ConfigureAwait(false);
+ await _providerManager.SaveImage(
+ item,
+ stream,
+ response.Content.Headers.ContentType.MediaType,
+ type,
+ null,
+ cancellationToken).ConfigureAwait(false);
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
return true;
@@ -565,14 +572,14 @@ namespace MediaBrowser.Providers.Manager
try
{
- var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
+ using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
// If there's already an image of the same size, skip it
- if (response.ContentLength.HasValue)
+ if (response.Content.Headers.ContentLength.HasValue)
{
try
{
- if (item.GetImages(imageType).Any(i => _fileSystem.GetFileInfo(i.Path).Length == response.ContentLength.Value))
+ if (item.GetImages(imageType).Any(i => _fileSystem.GetFileInfo(i.Path).Length == response.Content.Headers.ContentLength.Value))
{
response.Content.Dispose();
continue;
@@ -584,7 +591,14 @@ namespace MediaBrowser.Providers.Manager
}
}
- await _providerManager.SaveImage(item, response.Content, response.ContentType, imageType, null, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ await _providerManager.SaveImage(
+ item,
+ stream,
+ response.Content.Headers.ContentType.MediaType,
+ imageType,
+ null,
+ cancellationToken).ConfigureAwait(false);
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
}
catch (HttpException ex)
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 9f63c60465..de8d389c5a 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
@@ -44,7 +45,7 @@ namespace MediaBrowser.Providers.Manager
{
private readonly object _refreshQueueLock = new object();
private readonly ILogger _logger;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
@@ -66,7 +67,7 @@ namespace MediaBrowser.Providers.Manager
///
/// Initializes a new instance of the class.
///
- /// The Http client.
+ /// The Http client factory.
/// The subtitle manager.
/// The configuration manager.
/// The library monitor.
@@ -75,7 +76,7 @@ namespace MediaBrowser.Providers.Manager
/// The server application paths.
/// The library manager.
public ProviderManager(
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ISubtitleManager subtitleManager,
IServerConfigurationManager configurationManager,
ILibraryMonitor libraryMonitor,
@@ -85,7 +86,7 @@ namespace MediaBrowser.Providers.Manager
ILibraryManager libraryManager)
{
_logger = logger;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_configurationManager = configurationManager;
_libraryMonitor = libraryMonitor;
_fileSystem = fileSystem;
@@ -155,25 +156,23 @@ namespace MediaBrowser.Providers.Manager
///
public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
- using var response = await _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url,
- BufferContent = false
- }).ConfigureAwait(false);
+ var httpClient = _httpClientFactory.CreateClient();
+ using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
+
+ var contentType = response.Content.Headers.ContentType.MediaType;
// Workaround for tvheadend channel icons
// TODO: Isolate this hack into the tvh plugin
- if (string.IsNullOrEmpty(response.ContentType))
+ if (string.IsNullOrEmpty(contentType))
{
if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
{
- response.ContentType = "image/png";
+ contentType = "image/png";
}
}
// thetvdb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons...
- if (response.ContentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase))
+ if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase))
{
throw new HttpException("Invalid image received.")
{
@@ -181,7 +180,14 @@ namespace MediaBrowser.Providers.Manager
};
}
- await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ await SaveImage(
+ item,
+ stream,
+ contentType,
+ type,
+ imageIndex,
+ cancellationToken).ConfigureAwait(false);
}
///
@@ -888,7 +894,7 @@ namespace MediaBrowser.Providers.Manager
}
///
- public Task GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
+ public Task GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
{
var provider = _metadataProviders.OfType().FirstOrDefault(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 7c0b542509..85966b3bf5 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -18,6 +18,7 @@
+
@@ -27,7 +28,7 @@
netstandard2.1
false
true
- true
+ true
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index 64a5e7c8eb..43659b68cc 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
namespace MediaBrowser.Providers.MediaInfo
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
index b211ed8b77..c9dac9ecd8 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
@@ -1,9 +1,9 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -17,13 +17,13 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public class AudioDbAlbumImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IJsonSerializer _json;
- public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IJsonSerializer json)
+ public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IJsonSerializer json)
{
_config = config;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_json = json;
}
@@ -94,13 +94,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
}
///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ var httpClient = _httpClientFactory.CreateClient();
+ return httpClient.GetAsync(url, cancellationToken);
}
///
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
index 7e54fcbdda..321144edf9 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
@@ -10,7 +10,6 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
@@ -26,16 +25,16 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IJsonSerializer _json;
public static AudioDbAlbumProvider Current;
- public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClient httpClient, IJsonSerializer json)
+ public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json)
{
_config = config;
_fileSystem = fileSystem;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_json = json;
Current = this;
@@ -174,18 +173,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
Directory.CreateDirectory(Path.GetDirectoryName(path));
- using (var httpResponse = await _httpClient.SendAsync(
- new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (var response = httpResponse.Content)
- using (var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
- {
- await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
- }
+ using var response = await _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ 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 stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
private static string GetAlbumDataPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId)
@@ -294,7 +285,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
}
///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
index 243b62f7bb..6512668681 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
@@ -1,9 +1,9 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -17,14 +17,14 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public class AudioDbArtistImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IJsonSerializer _json;
- public AudioDbArtistImageProvider(IServerConfigurationManager config, IJsonSerializer json, IHttpClient httpClient)
+ public AudioDbArtistImageProvider(IServerConfigurationManager config, IJsonSerializer json, IHttpClientFactory httpClientFactory)
{
_config = config;
_json = json;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
}
///
@@ -135,13 +135,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
return list;
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ var httpClient = _httpClientFactory.CreateClient();
+ return httpClient.GetAsync(url, cancellationToken);
}
///
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
index 892f734228..708426500b 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
@@ -9,7 +9,6 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
@@ -25,7 +24,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IJsonSerializer _json;
public static AudioDbArtistProvider Current;
@@ -33,11 +32,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private const string ApiKey = "195003";
public const string BaseUrl = "https://www.theaudiodb.com/api/v1/json/" + ApiKey;
- public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClient httpClient, IJsonSerializer json)
+ public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json)
{
_config = config;
_fileSystem = fileSystem;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_json = json;
Current = this;
}
@@ -155,23 +154,13 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId);
- using (var httpResponse = await _httpClient.SendAsync(
- new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = true
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (var response = httpResponse.Content)
- {
- Directory.CreateDirectory(Path.GetDirectoryName(path));
+ using var response = await _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
- using (var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
- {
- await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
- }
- }
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+
+ await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
///
@@ -289,7 +278,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
}
///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
index 9f36a03f92..23acb7fd6a 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
@@ -8,12 +8,12 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Common;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.Music
internal static MusicBrainzAlbumProvider Current;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IApplicationHost _appHost;
private readonly ILogger _logger;
@@ -51,11 +51,11 @@ namespace MediaBrowser.Providers.Music
private Stopwatch _stopWatchMusicBrainz = new Stopwatch();
public MusicBrainzAlbumProvider(
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IApplicationHost appHost,
ILogger logger)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_appHost = appHost;
_logger = logger;
@@ -123,11 +123,9 @@ namespace MediaBrowser.Providers.Music
if (!string.IsNullOrWhiteSpace(url))
{
- using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- using (var stream = response.Content)
- {
- return GetResultsFromResponse(stream);
- }
+ using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ return GetResultsFromResponse(stream);
}
return Enumerable.Empty();
@@ -282,23 +280,19 @@ namespace MediaBrowser.Providers.Music
WebUtility.UrlEncode(albumName),
artistId);
- using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- using (var stream = response.Content)
- using (var oReader = new StreamReader(stream, Encoding.UTF8))
+ using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using var oReader = new StreamReader(stream, Encoding.UTF8);
+ var settings = new XmlReaderSettings
{
- var settings = new XmlReaderSettings()
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
+ ValidationType = ValidationType.None,
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true
+ };
- using (var reader = XmlReader.Create(oReader, settings))
- {
- return ReleaseResult.Parse(reader).FirstOrDefault();
- }
- }
+ using var reader = XmlReader.Create(oReader, settings);
+ return ReleaseResult.Parse(reader).FirstOrDefault();
}
private async Task GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
@@ -309,23 +303,19 @@ namespace MediaBrowser.Providers.Music
WebUtility.UrlEncode(albumName),
WebUtility.UrlEncode(artistName));
- using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- using (var stream = response.Content)
- using (var oReader = new StreamReader(stream, Encoding.UTF8))
+ using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using var oReader = new StreamReader(stream, Encoding.UTF8);
+ var settings = new XmlReaderSettings()
{
- var settings = new XmlReaderSettings()
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
+ ValidationType = ValidationType.None,
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true
+ };
- using (var reader = XmlReader.Create(oReader, settings))
- {
- return ReleaseResult.Parse(reader).FirstOrDefault();
- }
- }
+ using var reader = XmlReader.Create(oReader, settings);
+ return ReleaseResult.Parse(reader).FirstOrDefault();
}
private class ReleaseResult
@@ -624,30 +614,21 @@ namespace MediaBrowser.Providers.Music
{
var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture);
- using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- using (var stream = response.Content)
- using (var oReader = new StreamReader(stream, Encoding.UTF8))
+ using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using var oReader = new StreamReader(stream, Encoding.UTF8);
+ var settings = new XmlReaderSettings
{
- var settings = new XmlReaderSettings()
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
+ ValidationType = ValidationType.None,
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true
+ };
- using (var reader = XmlReader.Create(oReader, settings))
- {
- var result = ReleaseResult.Parse(reader).FirstOrDefault();
+ using var reader = XmlReader.Create(oReader, settings);
+ var result = ReleaseResult.Parse(reader).FirstOrDefault();
- if (result != null)
- {
- return result.ReleaseId;
- }
- }
- }
-
- return null;
+ return result?.ReleaseId;
}
///
@@ -660,59 +641,57 @@ namespace MediaBrowser.Providers.Music
{
var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture);
- using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- using (var stream = response.Content)
- using (var oReader = new StreamReader(stream, Encoding.UTF8))
+ using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ using var oReader = new StreamReader(stream, Encoding.UTF8);
+ var settings = new XmlReaderSettings
{
- var settings = new XmlReaderSettings()
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
+ ValidationType = ValidationType.None,
+ CheckCharacters = false,
+ IgnoreProcessingInstructions = true,
+ IgnoreComments = true
+ };
- using (var reader = XmlReader.Create(oReader, settings))
- {
- reader.MoveToContent();
- reader.Read();
+ using (var reader = XmlReader.Create(oReader, settings))
+ {
+ reader.MoveToContent();
+ reader.Read();
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ // Loop through each element
+ while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ {
+ if (reader.NodeType == XmlNodeType.Element)
{
- if (reader.NodeType == XmlNodeType.Element)
+ switch (reader.Name)
{
- switch (reader.Name)
+ case "release-group-list":
{
- case "release-group-list":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
+ if (reader.IsEmptyElement)
+ {
+ reader.Read();
+ continue;
+ }
- using (var subReader = reader.ReadSubtree())
- {
- return GetFirstReleaseGroupId(subReader);
- }
- }
+ using (var subReader = reader.ReadSubtree())
+ {
+ return GetFirstReleaseGroupId(subReader);
+ }
+ }
- default:
- {
- reader.Skip();
- break;
- }
+ default:
+ {
+ reader.Skip();
+ break;
}
}
- else
- {
- reader.Read();
- }
}
-
- return null;
+ else
+ {
+ reader.Read();
+ }
}
+
+ return null;
}
}
@@ -755,23 +734,19 @@ namespace MediaBrowser.Providers.Music
/// A number of retries shall be made in order to try and satisfy the request before
/// giving up and returning null.
///
- internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
+ internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
{
- var options = new HttpRequestOptions
- {
- Url = _musicBrainzBaseUrl.TrimEnd('/') + url,
- CancellationToken = cancellationToken,
- // MusicBrainz request a contact email address is supplied, as comment, in user agent field:
- // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent
- UserAgent = string.Format(
- CultureInfo.InvariantCulture,
- "{0} ( {1} )",
- _appHost.ApplicationUserAgent,
- _appHost.ApplicationUserAgentAddress),
- BufferContent = false
- };
+ using var options = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url);
- HttpResponseInfo response;
+ // MusicBrainz request a contact email address is supplied, as comment, in user agent field:
+ // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent
+ options.Headers.UserAgent.Add(new ProductInfoHeaderValue(string.Format(
+ CultureInfo.InvariantCulture,
+ "{0} ( {1} )",
+ _appHost.ApplicationUserAgent,
+ _appHost.ApplicationUserAgentAddress)));
+
+ HttpResponseMessage response;
var attempts = 0u;
do
@@ -790,7 +765,7 @@ namespace MediaBrowser.Providers.Music
_logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
_stopWatchMusicBrainz.Restart();
- response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
+ response = await _httpClientFactory.CreateClient().SendAsync(options).ConfigureAwait(false);
// We retry a finite number of times, and only whilst MB is indicating 503 (throttling)
}
@@ -799,14 +774,14 @@ namespace MediaBrowser.Providers.Music
// Log error if unable to query MB database due to throttling
if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable)
{
- _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.Url);
+ _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.RequestUri);
}
return response;
}
///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
index 9557664039..b829ed3780 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
@@ -6,11 +6,11 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Providers;
@@ -37,11 +37,9 @@ namespace MediaBrowser.Providers.Music
{
var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture);
- using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- using (var stream = response.Content)
- {
- return GetResultsFromResponse(stream);
- }
+ using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ return GetResultsFromResponse(stream);
}
else
{
@@ -51,7 +49,7 @@ namespace MediaBrowser.Providers.Music
var url = string.Format("/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- using (var stream = response.Content)
+ await using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
var results = GetResultsFromResponse(stream).ToList();
@@ -66,13 +64,9 @@ namespace MediaBrowser.Providers.Music
// Try again using the search with accent characters url
url = string.Format("/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
- using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
- {
- using (var stream = response.Content)
- {
- return GetResultsFromResponse(stream);
- }
- }
+ using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ return GetResultsFromResponse(stream);
}
}
@@ -298,7 +292,7 @@ namespace MediaBrowser.Providers.Music
public string Name => "MusicBrainz";
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index 50d6b78ae9..bfc840ea59 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -1,10 +1,10 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
@@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public class OmdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly OmdbItemProvider _itemProvider;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
@@ -28,17 +28,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public OmdbEpisodeProvider(
IJsonSerializer jsonSerializer,
IApplicationHost appHost,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
- _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClient, libraryManager, fileSystem, configurationManager);
+ _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClientFactory, libraryManager, fileSystem, configurationManager);
}
// After TheTvDb
@@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
{
if (info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue)
{
- result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager)
+ result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager)
.FetchEpisodeData(result, info.IndexNumber.Value, info.ParentIndexNumber.Value, info.GetProviderId(MetadataProvider.Imdb), seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
}
@@ -77,7 +77,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return result;
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
return _itemProvider.GetImageResponse(url, cancellationToken);
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
index 2d09a66c35..41e664aace 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
@@ -1,10 +1,10 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -19,16 +19,16 @@ namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbImageProvider : IRemoteImageProvider, IHasOrder
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IApplicationHost _appHost;
- public OmdbImageProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
+ public OmdbImageProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var list = new List();
- var provider = new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager);
+ var provider = new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager);
if (!string.IsNullOrWhiteSpace(imdbId))
{
@@ -79,13 +79,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return list;
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
public string Name => "The Open Movie Database";
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 12aecba849..d2823a08c3 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -5,10 +5,10 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -26,7 +26,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
IRemoteMetadataProvider, IRemoteMetadataProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
@@ -35,13 +35,13 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public OmdbItemProvider(
IJsonSerializer jsonSerializer,
IApplicationHost appHost,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
@@ -129,67 +129,63 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var url = OmdbProvider.GetOmdbUrl(urlQuery, _appHost, cancellationToken);
- using (var response = await OmdbProvider.GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
+ using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var resultList = new List();
+
+ if (isSearch)
{
- using (var stream = response.Content)
+ var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+ if (searchResultList != null && searchResultList.Search != null)
{
- var resultList = new List();
-
- if (isSearch)
- {
- var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
- if (searchResultList != null && searchResultList.Search != null)
- {
- resultList.AddRange(searchResultList.Search);
- }
- }
- else
- {
- var result = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
- if (string.Equals(result.Response, "true", StringComparison.OrdinalIgnoreCase))
- {
- resultList.Add(result);
- }
- }
-
- return resultList.Select(result =>
- {
- var item = new RemoteSearchResult
- {
- IndexNumber = searchInfo.IndexNumber,
- Name = result.Title,
- ParentIndexNumber = searchInfo.ParentIndexNumber,
- SearchProviderName = Name
- };
-
- if (episodeSearchInfo != null && episodeSearchInfo.IndexNumberEnd.HasValue)
- {
- item.IndexNumberEnd = episodeSearchInfo.IndexNumberEnd.Value;
- }
-
- item.SetProviderId(MetadataProvider.Imdb, result.imdbID);
-
- if (result.Year.Length > 0
- && int.TryParse(result.Year.AsSpan().Slice(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear))
- {
- item.ProductionYear = parsedYear;
- }
-
- if (!string.IsNullOrEmpty(result.Released)
- && DateTime.TryParse(result.Released, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var released))
- {
- item.PremiereDate = released;
- }
-
- if (!string.IsNullOrWhiteSpace(result.Poster) && !string.Equals(result.Poster, "N/A", StringComparison.OrdinalIgnoreCase))
- {
- item.ImageUrl = result.Poster;
- }
-
- return item;
- });
+ resultList.AddRange(searchResultList.Search);
}
}
+ else
+ {
+ var result = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+ if (string.Equals(result.Response, "true", StringComparison.OrdinalIgnoreCase))
+ {
+ resultList.Add(result);
+ }
+ }
+
+ return resultList.Select(result =>
+ {
+ var item = new RemoteSearchResult
+ {
+ IndexNumber = searchInfo.IndexNumber,
+ Name = result.Title,
+ ParentIndexNumber = searchInfo.ParentIndexNumber,
+ SearchProviderName = Name
+ };
+
+ if (episodeSearchInfo != null && episodeSearchInfo.IndexNumberEnd.HasValue)
+ {
+ item.IndexNumberEnd = episodeSearchInfo.IndexNumberEnd.Value;
+ }
+
+ item.SetProviderId(MetadataProvider.Imdb, result.imdbID);
+
+ if (result.Year.Length > 0
+ && int.TryParse(result.Year.AsSpan().Slice(0, Math.Min(result.Year.Length, 4)), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear))
+ {
+ item.ProductionYear = parsedYear;
+ }
+
+ if (!string.IsNullOrEmpty(result.Released)
+ && DateTime.TryParse(result.Released, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out var released))
+ {
+ item.PremiereDate = released;
+ }
+
+ if (!string.IsNullOrWhiteSpace(result.Poster) && !string.Equals(result.Poster, "N/A", StringComparison.OrdinalIgnoreCase))
+ {
+ item.ImageUrl = result.Poster;
+ }
+
+ return item;
+ });
}
public Task> GetMetadata(TrailerInfo info, CancellationToken cancellationToken)
@@ -224,7 +220,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
result.HasMetadata = true;
- await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
@@ -256,7 +252,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
result.HasMetadata = true;
- await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
@@ -276,13 +272,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return first == null ? null : first.GetProviderId(MetadataProvider.Imdb);
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
class SearchResult
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index 13098d1405..6ad5298deb 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -10,7 +10,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -25,14 +24,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IApplicationHost _appHost;
- public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
+ public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
@@ -293,15 +292,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var url = GetOmdbUrl(string.Format("i={0}&plot=short&tomatoes=true&r=json", imdbParam), _appHost, cancellationToken);
- using (var response = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
- {
- using (var stream = response.Content)
- {
- var rootObject = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(rootObject, path);
- }
- }
+ using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var rootObject = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+ _jsonSerializer.SerializeToFile(rootObject, path);
return path;
}
@@ -330,28 +325,18 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var url = GetOmdbUrl(string.Format("i={0}&season={1}&detail=full", imdbParam, seasonId), _appHost, cancellationToken);
- using (var response = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
- {
- using (var stream = response.Content)
- {
- var rootObject = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(rootObject, path);
- }
- }
+ using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var rootObject = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+ _jsonSerializer.SerializeToFile(rootObject, path);
return path;
}
- public static Task GetOmdbResponse(IHttpClient httpClient, string url, CancellationToken cancellationToken)
+ public static Task GetOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken)
{
- return httpClient.SendAsync(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- BufferContent = true,
- EnableDefaultUserAgent = true
- }, HttpMethod.Get);
+ return httpClient.GetAsync(url, cancellationToken);
}
internal string GetDataFilePath(string imdbId)
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
index 9b87e36176..4d38d38dca 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
@@ -2,9 +2,9 @@
using System;
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -18,13 +18,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbEpisodeImageProvider : IRemoteImageProvider
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
private readonly TvdbClientManager _tvdbClientManager;
- public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager)
+ public TvdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
_tvdbClientManager = tvdbClientManager;
}
@@ -113,13 +113,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
index 52fc538728..90436c7c99 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
@@ -2,9 +2,9 @@
using System;
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -21,13 +21,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
///
public class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
private readonly TvdbClientManager _tvdbClientManager;
- public TvdbEpisodeProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager)
+ public TvdbEpisodeProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
_tvdbClientManager = tvdbClientManager;
}
@@ -242,13 +242,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
return result;
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
public int Order => 0;
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
index 9db21f0128..388a4e3e72 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
@@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -20,15 +20,15 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly TvdbClientManager _tvdbClientManager;
- public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager)
+ public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
{
_libraryManager = libraryManager;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
_tvdbClientManager = tvdbClientManager;
}
@@ -104,13 +104,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
}
///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
index e9ba204754..ff8c3455fa 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
@@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -20,13 +20,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
private readonly TvdbClientManager _tvdbClientManager;
- public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager)
+ public TvdbSeasonImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
_tvdbClientManager = tvdbClientManager;
}
@@ -146,13 +146,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
index c33aefbc19..d287828eb5 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
@@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -20,13 +20,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
private readonly TvdbClientManager _tvdbClientManager;
- public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager)
+ public TvdbSeriesImageProvider(IHttpClientFactory httpClientFactory, ILogger logger, TvdbClientManager tvdbClientManager)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
_tvdbClientManager = tvdbClientManager;
}
@@ -144,13 +144,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
index df48629d03..c6dd8a5f3d 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
@@ -3,11 +3,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -25,15 +25,15 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
internal static TvdbSeriesProvider Current { get; private set; }
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
private readonly TvdbClientManager _tvdbClientManager;
- public TvdbSeriesProvider(IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvdbClientManager tvdbClientManager)
+ public TvdbSeriesProvider(IHttpClientFactory httpClientFactory, ILogger logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvdbClientManager tvdbClientManager)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
_libraryManager = libraryManager;
_localizationManager = localizationManager;
@@ -408,14 +408,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url,
- BufferContent = false
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
index c41bd925e9..f434440283 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
@@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
@@ -20,11 +20,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
- public TmdbBoxSetImageProvider(IHttpClient httpClient)
+ public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
}
public string Name => ProviderName;
@@ -153,13 +153,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index 20b6cd505c..4f86a0293a 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -5,10 +5,11 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
@@ -36,7 +37,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
public TmdbBoxSetProvider(
@@ -45,7 +46,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
IServerConfigurationManager config,
IFileSystem fileSystem,
ILocalizationManager localization,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager)
{
_logger = logger;
@@ -53,7 +54,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
_config = config;
_fileSystem = fileSystem;
_localization = localization;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
Current = this;
}
@@ -187,21 +188,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
cancellationToken.ThrowIfCancellationRequested();
- CollectionResult mainResult;
-
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- mainResult = await _json.DeserializeFromStreamAsync(json).ConfigureAwait(false);
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+ using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage);
+ await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var mainResult = await _json.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+
cancellationToken.ThrowIfCancellationRequested();
if (mainResult != null && string.IsNullOrEmpty(mainResult.Name))
@@ -216,18 +212,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
}
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var langRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- mainResult = await _json.DeserializeFromStreamAsync(json).ConfigureAwait(false);
- }
+ langRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ await using var langStream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ mainResult = await _json.DeserializeFromStreamAsync(langStream).ConfigureAwait(false);
}
}
@@ -277,13 +269,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
return dataPath;
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
index 36a06fba79..60d7a599ea 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs
@@ -4,9 +4,9 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
@@ -23,13 +23,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public class TmdbImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
- public TmdbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem)
+ public TmdbImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
{
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
}
@@ -202,13 +202,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 27ab6756f1..685561e2dc 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -6,11 +6,11 @@ using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -18,7 +18,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Plugins.Tmdb.Models.Movies;
@@ -34,7 +33,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
internal static TmdbMovieProvider Current { get; private set; }
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger _logger;
@@ -45,7 +44,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public TmdbMovieProvider(
IJsonSerializer jsonSerializer,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
ILogger logger,
@@ -53,7 +52,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
IApplicationHost appHost)
{
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_logger = logger;
@@ -146,20 +145,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return _tmdbSettings;
}
- using (HttpResponseInfo response = await GetMovieDbResponse(new HttpRequestOptions
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, string.Format(CultureInfo.InvariantCulture, TmdbConfigUrl, TmdbUtils.ApiKey));
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = string.Format(CultureInfo.InvariantCulture, TmdbConfigUrl, TmdbUtils.ApiKey),
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (Stream json = response.Content)
- {
- _tmdbSettings = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false);
-
- return _tmdbSettings;
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ using var response = await GetMovieDbResponse(requestMessage).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ _tmdbSettings = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+ return _tmdbSettings;
}
private const string TmdbConfigUrl = TmdbUtils.BaseTmdbApiUrl + "3/configuration?api_key={0}";
@@ -331,42 +326,23 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
url += "&include_image_language=" + GetImageLanguagesParam(language);
}
- MovieResult mainResult;
-
cancellationToken.ThrowIfCancellationRequested();
- // Cache if not using a tmdbId because we won't have the tmdb cache directory structure. So use the lower level cache.
- var cacheMode = isTmdbId ? CacheMode.None : CacheMode.Unconditional;
- var cacheLength = TimeSpan.FromDays(3);
-
- try
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- using (var response = await GetMovieDbResponse(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader,
- CacheMode = cacheMode,
- CacheLength = cacheLength
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- mainResult = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false);
- }
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
- catch (HttpException ex)
- {
- // Return null so that callers know there is no metadata for this id
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
- {
- return null;
- }
- throw;
+ using var mainResponse = await GetMovieDbResponse(requestMessage);
+ if (mainResponse.StatusCode == HttpStatusCode.NotFound)
+ {
+ return null;
}
+ await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var mainResult = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+
cancellationToken.ThrowIfCancellationRequested();
// If the language preference isn't english, then have the overview fallback to english if it's blank
@@ -385,22 +361,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
url += "&include_image_language=" + GetImageLanguagesParam(language);
}
- using (var response = await GetMovieDbResponse(new HttpRequestOptions
+ using var langRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader,
- CacheMode = cacheMode,
- CacheLength = cacheLength
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- var englishResult = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false);
-
- mainResult.Overview = englishResult.Overview;
- }
+ langRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ using var langResponse = await GetMovieDbResponse(langRequestMessage);
+
+ await using var langStream = await langResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var langResult = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+ mainResult.Overview = langResult.Overview;
}
return mainResult;
@@ -409,25 +380,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
///
/// Gets the movie db response.
///
- internal async Task GetMovieDbResponse(HttpRequestOptions options)
+ internal async Task GetMovieDbResponse(HttpRequestMessage message)
{
- options.BufferContent = true;
- options.UserAgent = _appHost.ApplicationUserAgent;
-
- return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
+ message.Headers.UserAgent.Add(new ProductInfoHeaderValue(_appHost.ApplicationUserAgent));
+ return await _httpClientFactory.CreateClient().SendAsync(message);
}
///
public int Order => 1;
///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs
index 10935c6555..48f2a68a63 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs
@@ -5,10 +5,11 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -168,47 +169,38 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
var url3 = string.Format(_searchURL, WebUtility.UrlEncode(name), TmdbUtils.ApiKey, language, type);
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, url3);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url3,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
-
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- var searchResults = await _json.DeserializeFromStreamAsync>(json).ConfigureAwait(false);
-
- var results = searchResults.Results ?? new List();
-
- return results
- .Select(i =>
- {
- var remoteResult = new RemoteSearchResult
- {
- SearchProviderName = TmdbMovieProvider.Current.Name,
- Name = i.Title ?? i.Name ?? i.Original_Title,
- ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path
- };
-
- if (!string.IsNullOrWhiteSpace(i.Release_Date))
- {
- // These dates are always in this exact format
- if (DateTime.TryParseExact(i.Release_Date, "yyyy-MM-dd", _usCulture, DateTimeStyles.None, out var r))
- {
- remoteResult.PremiereDate = r.ToUniversalTime();
- remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year;
- }
- }
-
- remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
-
- return remoteResult;
- })
- .ToList();
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var searchResults = await _json.DeserializeFromStreamAsync>(stream).ConfigureAwait(false);
+
+ var results = searchResults.Results ?? new List();
+
+ return results
+ .Select(i =>
+ {
+ var remoteResult = new RemoteSearchResult {SearchProviderName = TmdbMovieProvider.Current.Name, Name = i.Title ?? i.Name ?? i.Original_Title, ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path};
+
+ if (!string.IsNullOrWhiteSpace(i.Release_Date))
+ {
+ // These dates are always in this exact format
+ if (DateTime.TryParseExact(i.Release_Date, "yyyy-MM-dd", _usCulture, DateTimeStyles.None, out var r))
+ {
+ remoteResult.PremiereDate = r.ToUniversalTime();
+ remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year;
+ }
+ }
+
+ remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
+
+ return remoteResult;
+ })
+ .ToList();
}
private async Task> GetSearchResultsTv(string name, int? year, string language, string baseImageUrl, CancellationToken cancellationToken)
@@ -220,46 +212,38 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
var url3 = string.Format(_searchURL, WebUtility.UrlEncode(name), TmdbUtils.ApiKey, language, "tv");
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, url3);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url3,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- var searchResults = await _json.DeserializeFromStreamAsync>(json).ConfigureAwait(false);
-
- var results = searchResults.Results ?? new List();
-
- return results
- .Select(i =>
- {
- var remoteResult = new RemoteSearchResult
- {
- SearchProviderName = TmdbMovieProvider.Current.Name,
- Name = i.Name ?? i.Original_Name,
- ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path
- };
-
- if (!string.IsNullOrWhiteSpace(i.First_Air_Date))
- {
- // These dates are always in this exact format
- if (DateTime.TryParseExact(i.First_Air_Date, "yyyy-MM-dd", _usCulture, DateTimeStyles.None, out var r))
- {
- remoteResult.PremiereDate = r.ToUniversalTime();
- remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year;
- }
- }
-
- remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
-
- return remoteResult;
- })
- .ToList();
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var searchResults = await _json.DeserializeFromStreamAsync>(stream).ConfigureAwait(false);
+
+ var results = searchResults.Results ?? new List();
+
+ return results
+ .Select(i =>
+ {
+ var remoteResult = new RemoteSearchResult {SearchProviderName = TmdbMovieProvider.Current.Name, Name = i.Name ?? i.Original_Name, ImageUrl = string.IsNullOrWhiteSpace(i.Poster_Path) ? null : baseImageUrl + i.Poster_Path};
+
+ if (!string.IsNullOrWhiteSpace(i.First_Air_Date))
+ {
+ // These dates are always in this exact format
+ if (DateTime.TryParseExact(i.First_Air_Date, "yyyy-MM-dd", _usCulture, DateTimeStyles.None, out var r))
+ {
+ remoteResult.PremiereDate = r.ToUniversalTime();
+ remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year;
+ }
+ }
+
+ remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture));
+
+ return remoteResult;
+ })
+ .ToList();
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs
index d4264dd4ea..73e49ba5bf 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs
@@ -2,9 +2,9 @@
using System;
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Providers;
@@ -26,7 +26,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Music
public string Name => TmdbMovieProvider.Current.Name;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
index 2faa9f835b..f31a7faea2 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
@@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -22,13 +22,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
{
private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
- public TmdbPersonImageProvider(IServerConfigurationManager config, IJsonSerializer jsonSerializer, IHttpClient httpClient)
+ public TmdbPersonImageProvider(IServerConfigurationManager config, IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory)
{
_config = config;
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
}
public string Name => ProviderName;
@@ -127,13 +127,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
index 58cbf9eef6..e9fb5c7034 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
@@ -6,11 +6,12 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -36,20 +37,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
public TmdbPersonProvider(
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
IJsonSerializer jsonSerializer,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILogger logger)
{
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_logger = logger;
Current = this;
}
@@ -96,22 +97,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), TmdbUtils.ApiKey);
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
-
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- var result = await _jsonSerializer.DeserializeFromStreamAsync>(json).ConfigureAwait(false) ??
- new TmdbSearchResult();
-
- return result.Results.Select(i => GetSearchResult(i, tmdbImageUrl));
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+
+ var result2 = await _jsonSerializer.DeserializeFromStreamAsync>(stream).ConfigureAwait(false)
+ ?? new TmdbSearchResult();
+
+ return result2.Results.Select(i => GetSearchResult(i, tmdbImageUrl));
}
private RemoteSearchResult GetSearchResult(PersonSearchResult i, string baseImageUrl)
@@ -230,23 +228,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", TmdbUtils.ApiKey, id);
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
-
- using (var fs = new FileStream(dataFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
- {
- await json.CopyToAsync(fs).ConfigureAwait(false);
- }
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false);
+ Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
+ await using var fs = new FileStream(dataFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ await response.Content.CopyToAsync(fs).ConfigureAwait(false);
}
private static string GetPersonDataPath(IApplicationPaths appPaths, string tmdbId)
@@ -266,13 +257,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
return Path.Combine(appPaths.CachePath, "tmdb-people");
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
index 77e4b2c568..eebecdac67 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -26,8 +26,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
IRemoteImageProvider,
IHasOrder
{
- public TmdbEpisodeImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
- : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
+ public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
+ : base(httpClientFactory, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
{ }
public IEnumerable GetSupportedImages(BaseItem item)
@@ -115,7 +115,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return images.Stills ?? new List();
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
return GetResponse(url, cancellationToken);
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
index a07ceb40ec..b4aef4542d 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
@@ -5,9 +5,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -27,8 +27,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
IRemoteMetadataProvider,
IHasOrder
{
- public TmdbEpisodeProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
- : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
+ public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
+ : base(httpClientFactory, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory)
{ }
public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
@@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return result;
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
return GetResponse(url, cancellationToken);
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
index 846e6095b5..154664321f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs
@@ -3,9 +3,10 @@
using System;
using System.Globalization;
using System.IO;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
@@ -19,16 +20,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public abstract class TmdbEpisodeProviderBase
{
private const string EpisodeUrlPattern = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos";
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
private readonly ILogger _logger;
- protected TmdbEpisodeProviderBase(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
+ protected TmdbEpisodeProviderBase(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_configurationManager = configurationManager;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
@@ -124,27 +125,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
cancellationToken.ThrowIfCancellationRequested();
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- return await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false);
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ return await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
}
- protected Task GetResponse(string url, CancellationToken cancellationToken)
+ protected Task GetResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
index 56b6e44833..787514d05d 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
@@ -4,9 +4,9 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -22,12 +22,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public class TmdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
- public TmdbSeasonImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
+ public TmdbSeasonImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory)
{
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
}
public int Order => 1;
@@ -36,13 +36,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public static string ProviderName => TmdbUtils.ProviderName;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
public async Task> GetImages(BaseItem item, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
index 822dc4317f..2b9077f557 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
@@ -5,9 +5,10 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -26,7 +27,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public class TmdbSeasonProvider : IRemoteMetadataProvider
{
private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,external_ids,credits,videos";
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
@@ -35,9 +36,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
internal static TmdbSeasonProvider Current { get; private set; }
- public TmdbSeasonProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILogger logger)
+ public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILogger logger)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_localization = localization;
@@ -121,13 +122,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return Task.FromResult>(new List());
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
private async Task GetSeasonInfo(string seriesTmdbId, int season, string preferredMetadataLanguage,
@@ -215,18 +212,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
cancellationToken.ThrowIfCancellationRequested();
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- return await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false);
- }
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage).ConfigureAwait(false);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ return await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
index 95c451493c..f11eeb15b3 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
@@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
@@ -23,13 +23,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public class TmdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
- public TmdbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem)
+ public TmdbSeriesImageProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
{
_jsonSerializer = jsonSerializer;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
}
@@ -180,13 +180,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// After tvdb and fanart
public int Order => 2;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index 3480a15f64..ac577b125a 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -5,10 +5,11 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -35,7 +36,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger _logger;
private readonly ILocalizationManager _localization;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
@@ -48,7 +49,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
IServerConfigurationManager configurationManager,
ILogger logger,
ILocalizationManager localization,
- IHttpClient httpClient,
+ IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager)
{
_jsonSerializer = jsonSerializer;
@@ -56,7 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
_configurationManager = configurationManager;
_logger = logger;
_localization = localization;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
Current = this;
}
@@ -413,24 +414,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
cancellationToken.ThrowIfCancellationRequested();
- SeriesResult mainResult;
-
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var mainRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- mainResult = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false);
+ mainRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
+ }
- if (!string.IsNullOrEmpty(language))
- {
- mainResult.ResultLanguage = language;
- }
- }
+ using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(mainRequestMessage);
+ await using var mainStream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var mainResult = await _jsonSerializer.DeserializeFromStreamAsync(mainStream).ConfigureAwait(false);
+
+ if (!string.IsNullOrEmpty(language))
+ {
+ mainResult.ResultLanguage = language;
}
cancellationToken.ThrowIfCancellationRequested();
@@ -451,21 +447,18 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
}
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
- {
- using (var json = response.Content)
- {
- var englishResult = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false);
-
- mainResult.Overview = englishResult.Overview;
- mainResult.ResultLanguage = "en";
- }
+ mainRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
}
+
+ using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ var englishResult = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+
+ mainResult.Overview = englishResult.Overview;
+ mainResult.ResultLanguage = "en";
}
return mainResult;
@@ -515,38 +508,38 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
TmdbUtils.ApiKey,
externalSource);
- using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
+ foreach (var header in TmdbUtils.AcceptHeaders)
{
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = TmdbUtils.AcceptHeader
- }).ConfigureAwait(false))
+ requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header));
+ }
+
+ using var response = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage);
+ await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+
+ var result = await _jsonSerializer.DeserializeFromStreamAsync(stream).ConfigureAwait(false);
+
+ if (result != null && result.Tv_Results != null)
{
- using (var json = response.Content)
+ var tv = result.Tv_Results.FirstOrDefault();
+
+ if (tv != null)
{
- var result = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false);
+ var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+ var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
- if (result != null && result.Tv_Results != null)
+ var remoteResult = new RemoteSearchResult
{
- var tv = result.Tv_Results.FirstOrDefault();
+ Name = tv.Name,
+ SearchProviderName = Name,
+ ImageUrl = string.IsNullOrWhiteSpace(tv.Poster_Path)
+ ? null
+ : tmdbImageUrl + tv.Poster_Path
+ };
- if (tv != null)
- {
- var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original");
+ remoteResult.SetProviderId(MetadataProvider.Tmdb, tv.Id.ToString(_usCulture));
- var remoteResult = new RemoteSearchResult
- {
- Name = tv.Name,
- SearchProviderName = Name,
- ImageUrl = string.IsNullOrWhiteSpace(tv.Poster_Path) ? null : tmdbImageUrl + tv.Poster_Path
- };
-
- remoteResult.SetProviderId(MetadataProvider.Tmdb, tv.Id.ToString(_usCulture));
-
- return remoteResult;
- }
- }
+ return remoteResult;
}
}
@@ -556,13 +549,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// After TheTVDB
public int Order => 1;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index 2f1e8b791a..1415d69761 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
@@ -1,4 +1,5 @@
using System;
+using System.Net.Mime;
using MediaBrowser.Model.Entities;
using MediaBrowser.Providers.Plugins.Tmdb.Models.General;
@@ -32,7 +33,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
///
/// Value of the Accept header for requests to the provider.
///
- public const string AcceptHeader = "application/json,image/*";
+ public static readonly string[] AcceptHeaders = { MediaTypeNames.Application.Json, "image/*" };
///
/// Maps the TMDB provided roles for crew members to Jellyfin roles.
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
index 7e2b062574..10374bde9e 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs
@@ -1,9 +1,9 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Providers;
@@ -13,11 +13,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
{
public class TmdbTrailerProvider : IHasOrder, IRemoteMetadataProvider
{
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
- public TmdbTrailerProvider(IHttpClient httpClient)
+ public TmdbTrailerProvider(IHttpClientFactory httpClientFactory)
{
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
}
public Task> GetSearchResults(TrailerInfo searchInfo, CancellationToken cancellationToken)
@@ -34,13 +34,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Trailers
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- });
+ return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
index 25f8beb402..dad155c815 100644
--- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
@@ -7,7 +7,6 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -20,13 +19,13 @@ namespace MediaBrowser.Providers.Studios
public class StudiosImageProvider : IRemoteImageProvider
{
private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
+ private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
- public StudiosImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
+ public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
{
_config = config;
- _httpClient = httpClient;
+ _httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
}
@@ -108,26 +107,22 @@ namespace MediaBrowser.Providers.Studios
{
const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studiothumbs.txt";
- return EnsureList(url, file, _httpClient, _fileSystem, cancellationToken);
+ return EnsureList(url, file, _fileSystem, cancellationToken);
}
private Task EnsurePosterList(string file, CancellationToken cancellationToken)
{
const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studioposters.txt";
- return EnsureList(url, file, _httpClient, _fileSystem, cancellationToken);
+ return EnsureList(url, file, _fileSystem, cancellationToken);
}
public int Order => 0;
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
{
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url,
- BufferContent = false
- });
+ var httpClient = _httpClientFactory.CreateClient();
+ return httpClient.GetAsync(url, cancellationToken);
}
///
@@ -135,30 +130,21 @@ namespace MediaBrowser.Providers.Studios
///
/// The URL.
/// The file.
- /// The HTTP client.
/// The file system.
/// The cancellation token.
/// Task.
- public async Task EnsureList(string url, string file, IHttpClient httpClient, IFileSystem fileSystem, CancellationToken cancellationToken)
+ public async Task EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken)
{
var fileInfo = fileSystem.GetFileInfo(file);
if (!fileInfo.Exists || (DateTime.UtcNow - fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays > 1)
{
- Directory.CreateDirectory(Path.GetDirectoryName(file));
+ var httpClient = _httpClientFactory.CreateClient();
- using (var res = await httpClient.SendAsync(
- new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (var content = res.Content)
- using (var fileStream = new FileStream(file, FileMode.Create))
- {
- await content.CopyToAsync(fileStream).ConfigureAwait(false);
- }
+ Directory.CreateDirectory(Path.GetDirectoryName(file));
+ await using var response = await httpClient.GetStreamAsync(url).ConfigureAwait(false);
+ await using var fileStream = new FileStream(file, FileMode.Create);
+ await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
return file;