aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid <daullmer@gmail.com>2020-07-18 19:21:43 +0200
committerDavid <daullmer@gmail.com>2020-07-18 19:21:43 +0200
commit0e1bf316b5c8da1d1f334fabe4a0abf77185f2e0 (patch)
treedd59ea68b275ec8125ca454422cd027d891de477
parentfa4e0a73d5eb0b33dc85dffb2a2d35033c5ce698 (diff)
parent09c05ff9faff4fd5013e19fad74835bdf796393f (diff)
Merge branch 'master' of github.com:jellyfin/jellyfin into socket-binding
-rw-r--r--.ci/azure-pipelines-package.yml7
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs13
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs19
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ms.json64
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json11
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json4
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs2
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayController.cs32
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs10
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs29
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs5
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj1
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs69
-rw-r--r--Jellyfin.Server/CoreAppHost.cs3
-rw-r--r--MediaBrowser.Api/SyncPlay/SyncPlayService.cs27
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs44
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupInfo.cs22
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupMember.cs2
-rw-r--r--MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs1
-rw-r--r--MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs6
-rw-r--r--MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs40
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs15
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs3
-rwxr-xr-xdeployment/build.windows.amd642
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs12
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj2
34 files changed, 303 insertions, 175 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 090746398..5539b16ab 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -50,6 +50,13 @@ jobs:
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-server-$(BuildConfiguration)'
+ - task: SSH@0
+ displayName: 'Create target directory on repository server'
+ inputs:
+ sshEndpoint: repository
+ runOptions: 'inline'
+ inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
+
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
inputs:
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index b207397bd..9486874d5 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -1,3 +1,4 @@
+using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Udp;
@@ -48,8 +49,16 @@ namespace Emby.Server.Implementations.EntryPoints
/// <inheritdoc />
public Task RunAsync()
{
- _udpServer = new UdpServer(_logger, _appHost, _config);
- _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
+ try
+ {
+ _udpServer = new UdpServer(_logger, _appHost, _config);
+ _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
+ }
+ catch (SocketException ex)
+ {
+ _logger.LogWarning(ex, "Unable to start AutoDiscovery listener on UDP port {PortNumber}", PortNumber);
+ }
+
return Task.CompletedTask;
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 77d44e131..06cfc78b3 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -341,7 +341,7 @@ namespace Emby.Server.Implementations.Library
if (item is LiveTvProgram)
{
_logger.LogDebug(
- "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
@@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.Library
else
{
_logger.LogInformation(
- "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
@@ -368,7 +368,12 @@ namespace Emby.Server.Implementations.Library
continue;
}
- _logger.LogDebug("Deleting path {MetadataPath}", metadataPath);
+ _logger.LogDebug(
+ "Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ metadataPath,
+ item.Id);
try
{
@@ -392,7 +397,13 @@ namespace Emby.Server.Implementations.Library
{
try
{
- _logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
+ _logger.LogInformation(
+ "Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ fileSystemInfo.FullName,
+ item.Id);
+
if (fileSystemInfo.IsDirectory)
{
Directory.Delete(fileSystemInfo.FullName, true);
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 20447347b..e587c37d5 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -19,8 +19,8 @@
"Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies",
- "DeviceOnlineWithName": "{0} is verbind",
- "DeviceOfflineWithName": "{0} het afgesluit",
+ "DeviceOnlineWithName": "{0} gekoppel is",
+ "DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings",
"Inherit": "Ontvang",
"HeaderLiveTV": "Live TV",
@@ -91,5 +91,9 @@
"ChapterNameValue": "Hoofstuk",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
- "Albums": "Albums"
+ "Albums": "Albums",
+ "TasksChannelsCategory": "Internet kanale",
+ "TasksApplicationCategory": "aansoek",
+ "TasksLibraryCategory": "biblioteek",
+ "TasksMaintenanceCategory": "onderhoud"
}
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 82df43be1..a6e9779c9 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -3,7 +3,7 @@
"AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung",
"Artists": "Interpreten",
- "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert",
+ "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
"Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
"Channels": "Kanäle",
diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json
index 79d078d4a..7f8df1289 100644
--- a/Emby.Server.Implementations/Localization/Core/ms.json
+++ b/Emby.Server.Implementations/Localization/Core/ms.json
@@ -5,47 +5,47 @@
"Artists": "Artis",
"AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
"Books": "Buku-buku",
- "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+ "CameraImageUploadedFrom": "Ada gambar dari kamera yang baru dimuat naik melalui {0}",
"Channels": "Saluran",
- "ChapterNameValue": "Chapter {0}",
+ "ChapterNameValue": "Bab {0}",
"Collections": "Koleksi",
- "DeviceOfflineWithName": "{0} has disconnected",
- "DeviceOnlineWithName": "{0} is connected",
+ "DeviceOfflineWithName": "{0} telah diputuskan sambungan",
+ "DeviceOnlineWithName": "{0} telah disambung",
"FailedLoginAttemptWithUserName": "Cubaan log masuk gagal dari {0}",
- "Favorites": "Favorites",
- "Folders": "Folders",
+ "Favorites": "Kegemaran",
+ "Folders": "Fail-fail",
"Genres": "Genre-genre",
- "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbumArtists": "Album Artis-artis",
"HeaderCameraUploads": "Muatnaik Kamera",
"HeaderContinueWatching": "Terus Menonton",
- "HeaderFavoriteAlbums": "Favorite Albums",
- "HeaderFavoriteArtists": "Favorite Artists",
- "HeaderFavoriteEpisodes": "Favorite Episodes",
- "HeaderFavoriteShows": "Favorite Shows",
- "HeaderFavoriteSongs": "Favorite Songs",
- "HeaderLiveTV": "Live TV",
- "HeaderNextUp": "Next Up",
- "HeaderRecordingGroups": "Recording Groups",
- "HomeVideos": "Home videos",
- "Inherit": "Inherit",
- "ItemAddedWithName": "{0} was added to the library",
- "ItemRemovedWithName": "{0} was removed from the library",
+ "HeaderFavoriteAlbums": "Album-album Kegemaran",
+ "HeaderFavoriteArtists": "Artis-artis Kegemaran",
+ "HeaderFavoriteEpisodes": "Episod-episod Kegemaran",
+ "HeaderFavoriteShows": "Rancangan-rancangan Kegemaran",
+ "HeaderFavoriteSongs": "Lagu-lagu Kegemaran",
+ "HeaderLiveTV": "TV Siaran Langsung",
+ "HeaderNextUp": "Seterusnya",
+ "HeaderRecordingGroups": "Kumpulan-kumpulan Rakaman",
+ "HomeVideos": "Video Personal",
+ "Inherit": "Mewarisi",
+ "ItemAddedWithName": "{0} telah ditambahkan ke dalam pustaka",
+ "ItemRemovedWithName": "{0} telah dibuang daripada pustaka",
"LabelIpAddressValue": "Alamat IP: {0}",
- "LabelRunningTimeValue": "Running time: {0}",
- "Latest": "Latest",
- "MessageApplicationUpdated": "Jellyfin Server has been updated",
- "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
- "MixedContent": "Mixed content",
- "Movies": "Movies",
+ "LabelRunningTimeValue": "Masa berjalan: {0}",
+ "Latest": "Terbaru",
+ "MessageApplicationUpdated": "Jellyfin Server telah dikemas kini",
+ "MessageApplicationUpdatedTo": "Jellyfin Server telah dikemas kini ke {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini",
+ "MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini",
+ "MixedContent": "Kandungan campuran",
+ "Movies": "Filem",
"Music": "Muzik",
"MusicVideos": "Video muzik",
- "NameInstallFailed": "{0} installation failed",
- "NameSeasonNumber": "Season {0}",
- "NameSeasonUnknown": "Season Unknown",
- "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
- "NotificationOptionApplicationUpdateAvailable": "Application update available",
+ "NameInstallFailed": "{0} pemasangan gagal",
+ "NameSeasonNumber": "Musim {0}",
+ "NameSeasonUnknown": "Musim Tidak Diketahui",
+ "NewVersionIsAvailable": "Versi terbaru Jellyfin Server bersedia untuk dimuat turunkan.",
+ "NotificationOptionApplicationUpdateAvailable": "Kemas kini aplikasi telah sedia",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionAudioPlayback": "Audio playback started",
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index 5365fff23..b534d0bbe 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -104,5 +104,14 @@
"TaskRefreshChapterImagesDescription": "Cria miniaturas para vídeos que têm capítulos.",
"TaskCleanCacheDescription": "Apaga ficheiros em cache que já não são usados pelo sistema.",
"TasksChannelsCategory": "Canais de Internet",
- "TaskRefreshChapterImages": "Extrair Imagens do Capítulo"
+ "TaskRefreshChapterImages": "Extrair Imagens do Capítulo",
+ "TaskDownloadMissingSubtitlesDescription": "Pesquisa na Internet as legendas em falta com base na configuração de metadados.",
+ "TaskDownloadMissingSubtitles": "Download das legendas em falta",
+ "TaskRefreshChannelsDescription": "Atualiza as informações do canal da Internet.",
+ "TaskCleanTranscodeDescription": "Apagar os ficheiros com mais de um dia, de Transcode.",
+ "TaskCleanTranscode": "Limpar o diretório de Transcode",
+ "TaskUpdatePluginsDescription": "Download e instala as atualizações para plug-ins configurados para atualização automática.",
+ "TaskRefreshPeopleDescription": "Atualiza os metadados para atores e diretores na tua biblioteca de media.",
+ "TaskRefreshPeople": "Atualizar pessoas",
+ "TaskRefreshLibraryDescription": "Pesquisa a tua biblioteca de media por novos ficheiros e atualiza os metadados."
}
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index 32538ac03..576aaeb1b 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -67,5 +67,7 @@
"Artists": "นักแสดง",
"Application": "แอปพลิเคชั่น",
"AppDeviceValues": "App: {0}, อุปกรณ์: {1}",
- "Albums": "อัลบั้ม"
+ "Albums": "อัลบั้ม",
+ "ScheduledTaskStartedWithName": "{0} เริ่มต้น",
+ "ScheduledTaskFailedWithName": "{0} ล้มเหลว"
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index d069d1ada..ca9f95c70 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.Session
}
catch (DbUpdateConcurrencyException e)
{
- _logger.LogWarning(e, "Error updating user's last activity date.");
+ _logger.LogDebug(e, "Error updating user's last activity date.");
}
}
}
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
index b1f8fd330..39d17833f 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
@@ -194,26 +194,24 @@ namespace Emby.Server.Implementations.SyncPlay
}
/// <inheritdoc />
- public void InitGroup(SessionInfo session, CancellationToken cancellationToken)
+ public void CreateGroup(SessionInfo session, CancellationToken cancellationToken)
{
_group.AddSession(session);
_syncPlayManager.AddSessionToGroup(session, this);
_group.PlayingItem = session.FullNowPlayingItem;
- _group.IsPaused = true;
+ _group.IsPaused = session.PlayState.IsPaused;
_group.PositionTicks = session.PlayState.PositionTicks ?? 0;
_group.LastActivity = DateTime.UtcNow;
var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow));
SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
- var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
}
/// <inheritdoc />
public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken)
{
- if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id)
+ if (session.NowPlayingItem?.Id == _group.PlayingItem.Id)
{
_group.AddSession(session);
_syncPlayManager.AddSessionToGroup(session, this);
@@ -224,7 +222,7 @@ namespace Emby.Server.Implementations.SyncPlay
var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
- // Client join and play, syncing will happen client side
+ // Syncing will happen client-side
if (!_group.IsPaused)
{
var playCommand = NewSyncPlayCommand(SendCommandType.Play);
@@ -262,10 +260,9 @@ namespace Emby.Server.Implementations.SyncPlay
/// <inheritdoc />
public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
- // The server's job is to mantain a consistent state to which clients refer to,
- // as also to notify clients of state changes.
- // The actual syncing of media playback happens client side.
- // Clients are aware of the server's time and use it to sync.
+ // The server's job is to maintain a consistent state for clients to reference
+ // and notify clients of state changes. The actual syncing of media playback
+ // happens client side. Clients are aware of the server's time and use it to sync.
switch (request.Type)
{
case PlaybackRequestType.Play:
@@ -277,13 +274,13 @@ namespace Emby.Server.Implementations.SyncPlay
case PlaybackRequestType.Seek:
HandleSeekRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.Buffering:
+ case PlaybackRequestType.Buffer:
HandleBufferingRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.BufferingDone:
+ case PlaybackRequestType.Ready:
HandleBufferingDoneRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.UpdatePing:
+ case PlaybackRequestType.Ping:
HandlePingUpdateRequest(session, request);
break;
}
@@ -301,7 +298,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Pick a suitable time that accounts for latency
var delay = _group.GetHighestPing() * 2;
- delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
+ delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
// Unpause group and set starting point in future
// Clients will start playback at LastActivity (datetime) from PositionTicks (playback position)
@@ -337,8 +334,9 @@ namespace Emby.Server.Implementations.SyncPlay
var currentTime = DateTime.UtcNow;
var elapsedTime = currentTime - _group.LastActivity;
_group.LastActivity = currentTime;
+
// Seek only if playback actually started
- // (a pause request may be issued during the delay added to account for latency)
+ // Pause request may be issued during the delay added to account for latency
_group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0;
var command = NewSyncPlayCommand(SendCommandType.Pause);
@@ -451,7 +449,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Client, that was buffering, resumed playback but did not update others in time
delay = _group.GetHighestPing() * 2;
- delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
+ delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
_group.LastActivity = currentTime.AddMilliseconds(
delay);
@@ -495,7 +493,7 @@ namespace Emby.Server.Implementations.SyncPlay
private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
{
// Collected pings are used to account for network latency when unpausing playback
- _group.UpdatePing(session, request.Ping ?? _group.DefaulPing);
+ _group.UpdatePing(session, request.Ping ?? _group.DefaultPing);
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
index 45a43fd78..966ed5024 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
@@ -170,10 +170,11 @@ namespace Emby.Server.Implementations.SyncPlay
{
_logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id);
- var error = new GroupUpdate<string>()
+ var error = new GroupUpdate<string>
{
Type = GroupUpdateType.CreateGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -188,7 +189,7 @@ namespace Emby.Server.Implementations.SyncPlay
var group = new SyncPlayController(_sessionManager, this);
_groups[group.GetGroupId()] = group;
- group.InitGroup(session, cancellationToken);
+ group.CreateGroup(session, cancellationToken);
}
}
@@ -205,6 +206,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
Type = GroupUpdateType.JoinGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -300,9 +302,9 @@ namespace Emby.Server.Implementations.SyncPlay
group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())).Select(
group => group.GetInfo()).ToList();
}
- // Otherwise show all available groups
else
{
+ // Otherwise show all available groups
return _groups.Values.Where(
group => HasAccessToItem(user, group.GetPlayingItemId())).Select(
group => group.GetInfo()).ToList();
@@ -322,6 +324,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
Type = GroupUpdateType.JoinGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -366,7 +369,6 @@ namespace Emby.Server.Implementations.SyncPlay
}
_sessionToGroupMap.Remove(session.Id, out var tempGroup);
-
if (!tempGroup.GetGroupId().Equals(group.GetGroupId()))
{
throw new InvalidOperationException("Session was in wrong group!");
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 21c12ae79..d1818deff 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -117,23 +117,20 @@ namespace Emby.Server.Implementations.TV
limit = limit.Value + 10;
}
- var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(Episode).Name },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
- SeriesPresentationUniqueKey = presentationUniqueKey,
- Limit = limit,
- DtoOptions = new DtoOptions
- {
- Fields = new ItemFields[]
+ var items = _libraryManager
+ .GetItemList(
+ new InternalItemsQuery(user)
{
- ItemFields.SeriesPresentationUniqueKey
- },
- EnableImages = false
- },
- GroupBySeriesPresentationUniqueKey = true
-
- }, parentsFolders.ToList()).Cast<Episode>().Select(GetUniqueSeriesKey);
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
+ SeriesPresentationUniqueKey = presentationUniqueKey,
+ Limit = limit,
+ DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableImages = false },
+ GroupBySeriesPresentationUniqueKey = true
+ }, parentsFolders.ToList())
+ .Cast<Episode>()
+ .Where(episode => !string.IsNullOrEmpty(episode.SeriesPresentationUniqueKey))
+ .Select(GetUniqueSeriesKey);
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 146ebaf25..4f54c06dd 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -148,6 +148,11 @@ namespace Emby.Server.Implementations.Updates
_logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
return Array.Empty<PackageInfo>();
}
+ catch (HttpRequestException ex)
+ {
+ _logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
+ return Array.Empty<PackageInfo>();
+ }
}
/// <inheritdoc />
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index 282ea511c..58d1ba2f3 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -21,7 +21,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.5" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.5" />
</ItemGroup>
</Project>
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index ace9c4af0..9aff808db 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -23,6 +23,7 @@ using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Users;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations.Users
@@ -86,7 +87,19 @@ namespace Jellyfin.Server.Implementations.Users
public event EventHandler<GenericEventArgs<User>>? OnUserLockedOut;
/// <inheritdoc/>
- public IEnumerable<User> Users => _dbProvider.CreateContext().Users;
+ public IEnumerable<User> Users
+ {
+ get
+ {
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .ToList();
+ }
+ }
/// <inheritdoc/>
public IEnumerable<Guid> UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id);
@@ -99,7 +112,13 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Guid can't be empty", nameof(id));
}
- return _dbProvider.CreateContext().Users.Find(id);
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .FirstOrDefault(user => user.Id == id);
}
/// <inheritdoc/>
@@ -110,9 +129,14 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Invalid username", nameof(name));
}
- // This can't use an overload with StringComparer because that would cause the query to
- // have to be evaluated client-side.
- return _dbProvider.CreateContext().Users.FirstOrDefault(u => string.Equals(u.Username, name));
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .AsEnumerable()
+ .FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
}
/// <inheritdoc/>
@@ -128,7 +152,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Invalid username", nameof(newName));
}
- if (user.Username.Equals(newName, StringComparison.Ordinal))
+ if (user.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("The new and old names must be different.");
}
@@ -150,7 +174,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void UpdateUser(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
dbContext.Users.Update(user);
dbContext.SaveChanges();
}
@@ -158,7 +182,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task UpdateUserAsync(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ await using var dbContext = _dbProvider.CreateContext();
dbContext.Users.Update(user);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
@@ -172,7 +196,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
}
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
// TODO: Remove after user item data is migrated.
var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0;
@@ -195,8 +219,13 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void DeleteUser(Guid userId)
{
- var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users.Find(userId);
+ using var dbContext = _dbProvider.CreateContext();
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id == userId);
if (user == null)
{
throw new ResourceNotFoundException(nameof(userId));
@@ -381,7 +410,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentNullException(nameof(username));
}
- var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
+ var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
bool success;
IAuthenticationProvider? authenticationProvider;
@@ -409,8 +438,7 @@ namespace Jellyfin.Server.Implementations.Users
// Search the database for the user again
// the authentication provider might have created it
- user = Users
- .ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
+ user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
{
@@ -547,7 +575,7 @@ namespace Jellyfin.Server.Implementations.Users
public void Initialize()
{
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
if (dbContext.Users.Any())
{
@@ -610,7 +638,14 @@ namespace Jellyfin.Server.Implementations.Users
public void UpdateConfiguration(Guid userId, UserConfiguration config)
{
var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!");
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id == userId)
+ ?? throw new ArgumentException("No user exists with given Id!");
+
user.SubtitleMode = config.SubtitleMode;
user.HidePlayedInLatest = config.HidePlayedInLatest;
user.EnableLocalPassword = config.EnableLocalPassword;
@@ -699,7 +734,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void ClearProfileImage(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
dbContext.Remove(user.ProfileImage);
dbContext.SaveChanges();
}
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index fe07411a6..207eaa98d 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -66,8 +66,7 @@ namespace Jellyfin.Server
// TODO: Set up scoping and use AddDbContextPool
serviceCollection.AddDbContext<JellyfinDb>(
options => options
- .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")
- .UseLazyLoadingProxies(),
+ .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
ServiceLifetime.Transient);
serviceCollection.AddSingleton<JellyfinDbProvider>();
diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
index 3782ea5f0..daa1b521f 100644
--- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
+++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
@@ -27,13 +27,6 @@ namespace MediaBrowser.Api.SyncPlay
/// <value>The Group id to join.</value>
[ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string GroupId { get; set; }
-
- /// <summary>
- /// Gets or sets the playing item id.
- /// </summary>
- /// <value>The client's currently playing item id.</value>
- [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlayingItemId { get; set; }
}
[Route("/SyncPlay/Leave", "POST", Summary = "Leave joined SyncPlay group")]
@@ -89,7 +82,7 @@ namespace MediaBrowser.Api.SyncPlay
public long PositionTicks { get; set; }
/// <summary>
- /// Gets or sets whether this is a buffering or a buffering-done request.
+ /// Gets or sets whether this is a buffering or a ready request.
/// </summary>
/// <value><c>true</c> if buffering is complete; <c>false</c> otherwise.</value>
[ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
@@ -150,25 +143,15 @@ namespace MediaBrowser.Api.SyncPlay
var currentSession = GetSession(_sessionContext);
Guid groupId;
- Guid playingItemId = Guid.Empty;
-
if (!Guid.TryParse(request.GroupId, out groupId))
{
Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId);
return;
}
- // Both null and empty strings mean that client isn't playing anything
- if (!string.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId))
- {
- Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId);
- return;
- }
-
var joinRequest = new JoinGroupRequest()
{
- GroupId = groupId,
- PlayingItemId = playingItemId
+ GroupId = groupId
};
_syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None);
@@ -189,7 +172,7 @@ namespace MediaBrowser.Api.SyncPlay
/// </summary>
/// <param name="request">The request.</param>
/// <value>The requested list of groups.</value>
- public List<GroupInfoView> Post(SyncPlayList request)
+ public List<GroupInfoView> Get(SyncPlayList request)
{
var currentSession = GetSession(_sessionContext);
var filterItemId = Guid.Empty;
@@ -254,7 +237,7 @@ namespace MediaBrowser.Api.SyncPlay
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
{
- Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering,
+ Type = request.BufferingDone ? PlaybackRequestType.Ready : PlaybackRequestType.Buffer,
When = DateTime.Parse(request.When),
PositionTicks = request.PositionTicks
};
@@ -270,7 +253,7 @@ namespace MediaBrowser.Api.SyncPlay
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
{
- Type = PlaybackRequestType.UpdatePing,
+ Type = PlaybackRequestType.Ping,
Ping = Convert.ToInt64(request.Ping)
};
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index d3fb6a46d..534e0c372 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -483,7 +483,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isQsvDecoder)
{
- arg.Append("-hwaccel qsv ");
+ arg.Append("-hwaccel qsv -init_hw_device qsv=hw ");
}
// While using SW decoder
else
@@ -1351,7 +1351,7 @@ namespace MediaBrowser.Controller.MediaEncoding
transcoderChannelLimit = 6;
}
- var isTranscodingAudio = !EncodingHelper.IsCopyCodec(codec);
+ var isTranscodingAudio = !IsCopyCodec(codec);
int? resultChannels = state.GetRequestedAudioChannels(codec);
if (isTranscodingAudio)
@@ -1757,7 +1757,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// output dimensions. Output dimensions are guaranteed to be even.
var outputWidth = width.Value;
var outputHeight = height.Value;
- var vaapi_or_qsv = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ? "qsv" : "vaapi";
+ var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase);
if (!videoWidth.HasValue
|| outputWidth != videoWidth.Value
@@ -1765,17 +1765,19 @@ namespace MediaBrowser.Controller.MediaEncoding
|| outputHeight != videoHeight.Value)
{
// Force nv12 pixel format to enable 10-bit to 8-bit colour conversion.
+ // use vpp_qsv filter to avoid green bar when the fixed output size is requested.
filters.Add(
string.Format(
CultureInfo.InvariantCulture,
- "scale_{0}=w={1}:h={2}:format=nv12",
- vaapi_or_qsv,
+ "{0}=w={1}:h={2}:format=nv12",
+ qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
outputWidth,
outputHeight));
}
else
{
- filters.Add(string.Format(CultureInfo.InvariantCulture, "scale_{0}=format=nv12", vaapi_or_qsv));
+ // set w=0:h=0 for vpp_qsv to keep the original dimensions, otherwise it will fail.
+ filters.Add(string.Format(CultureInfo.InvariantCulture, "{0}format=nv12", qsv_or_vaapi ? "vpp_qsv=w=0:h=0:" : "scale_vaapi="));
}
}
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
@@ -2262,7 +2264,7 @@ namespace MediaBrowser.Controller.MediaEncoding
flags.Add("+ignidx");
}
- if (state.GenPtsInput || EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
+ if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
{
flags.Add("+genpts");
}
@@ -2521,21 +2523,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets the name of the output video codec.
+ /// Gets the ffmpeg option string for the hardware accelerated video decoder.
/// </summary>
+ /// <param name="state">The encoding job info.</param>
+ /// <param name="encodingOptions">The encoding options.</param>
+ /// <returns>The option string or null if none available.</returns>
protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
- var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
var videoStream = state.VideoStream;
- var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
- || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
-
- if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
+ if (videoStream == null)
{
return null;
}
+ var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
@@ -2544,10 +2546,16 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- if (videoStream != null
- && !string.IsNullOrEmpty(videoStream.Codec)
- && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
+ if (IsCopyCodec(state.OutputVideoCodec))
{
+ return null;
+ }
+
+ if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
+ {
+ var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile)
+ && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
+
// Only hevc and vp9 formats have 10-bit hardware decoder support now.
if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
@@ -3000,7 +3008,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -mpegts_m2ts_mode 1";
}
- if (EncodingHelper.IsCopyCodec(videoCodec))
+ if (IsCopyCodec(videoCodec))
{
if (state.VideoStream != null
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
@@ -3102,7 +3110,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var args = "-codec:a:0 " + codec;
- if (EncodingHelper.IsCopyCodec(codec))
+ if (IsCopyCodec(codec))
{
return args;
}
diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
index d0fac1efa..e742df517 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
@@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <summary>
/// Gets the default ping value used for sessions.
/// </summary>
- public long DefaulPing { get; } = 500;
+ public long DefaultPing { get; } = 500;
/// <summary>
/// Gets or sets the group identifier.
@@ -70,16 +70,16 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public void AddSession(SessionInfo session)
{
- if (ContainsSession(session.Id.ToString()))
+ if (ContainsSession(session.Id))
{
return;
}
var member = new GroupMember();
member.Session = session;
- member.Ping = DefaulPing;
+ member.Ping = DefaultPing;
member.IsBuffering = false;
- Participants[session.Id.ToString()] = member;
+ Participants[session.Id] = member;
}
/// <summary>
@@ -88,12 +88,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public void RemoveSession(SessionInfo session)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants.Remove(session.Id.ToString(), out _);
+ Participants.Remove(session.Id, out _);
}
/// <summary>
@@ -103,12 +103,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="ping">The ping.</param>
public void UpdatePing(SessionInfo session, long ping)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants[session.Id.ToString()].Ping = ping;
+ Participants[session.Id].Ping = ping;
}
/// <summary>
@@ -117,7 +117,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <value name="session">The highest ping in the group.</value>
public long GetHighestPing()
{
- long max = Int64.MinValue;
+ long max = long.MinValue;
foreach (var session in Participants.Values)
{
max = Math.Max(max, session.Ping);
@@ -133,12 +133,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="isBuffering">The state.</param>
public void SetBuffering(SessionInfo session, bool isBuffering)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants[session.Id.ToString()].IsBuffering = isBuffering;
+ Participants[session.Id].IsBuffering = isBuffering;
}
/// <summary>
diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs
index a3975c334..cde6f8e8c 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.SyncPlay
public class GroupMember
{
/// <summary>
- /// Gets or sets whether this member is buffering.
+ /// Gets or sets a value indicating whether this member is buffering.
/// </summary>
/// <value><c>true</c> if member is buffering; <c>false</c> otherwise.</value>
public bool IsBuffering { get; set; }
diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
index de1fcd259..45c543806 100644
--- a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
+++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
@@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- void InitGroup(SessionInfo session, CancellationToken cancellationToken);
+ void CreateGroup(SessionInfo session, CancellationToken cancellationToken);
/// <summary>
/// Adds the session to the group.
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 4250edfb7..0fd0239b4 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -90,6 +90,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
{
+ { "libavutil=56.51,libavcodec=58.91,libavformat=58.45,libavdevice=58.10,libavfilter=7.85,libswscale=5.7,libswresample=3.7,libpostproc=55.7,", new Version(4, 3) },
{ "libavutil=56.31,libavcodec=58.54,libavformat=58.29,libavdevice=58.8,libavfilter=7.57,libswscale=5.5,libswresample=3.5,libpostproc=55.5,", new Version(4, 2) },
{ "libavutil=56.22,libavcodec=58.35,libavformat=58.20,libavdevice=58.5,libavfilter=7.40,libswscale=5.3,libswresample=3.3,libpostproc=55.3,", new Version(4, 1) },
{ "libavutil=56.14,libavcodec=58.18,libavformat=58.12,libavdevice=58.3,libavfilter=7.16,libswscale=5.1,libswresample=3.1,libpostproc=55.1,", new Version(4, 0) },
diff --git a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
index d67b6bd55..0c77a6132 100644
--- a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
+++ b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
@@ -12,11 +12,5 @@ namespace MediaBrowser.Model.SyncPlay
/// </summary>
/// <value>The Group id to join.</value>
public Guid GroupId { get; set; }
-
- /// <summary>
- /// Gets or sets the playing item id.
- /// </summary>
- /// <value>The client's currently playing item id.</value>
- public Guid PlayingItemId { get; set; }
}
}
diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
index 671f4e01f..e89efeed8 100644
--- a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
+++ b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
@@ -23,16 +23,16 @@ namespace MediaBrowser.Model.SyncPlay
/// <summary>
/// A user is signaling that playback is buffering.
/// </summary>
- Buffering = 3,
+ Buffer = 3,
/// <summary>
/// A user is signaling that playback resumed.
/// </summary>
- BufferingDone = 4,
+ Ready = 4,
/// <summary>
/// A user is reporting its ping.
/// </summary>
- UpdatePing = 5
+ Ping = 5
}
}
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
index 2c6682f82..cd2f96f14 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Providers;
@@ -229,6 +230,45 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
}
+ public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeriesAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
+ var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
+
+ if (imagesSummary.Data.Fanart > 0)
+ {
+ yield return KeyType.Fanart;
+ }
+
+ if (imagesSummary.Data.Series > 0)
+ {
+ yield return KeyType.Series;
+ }
+
+ if (imagesSummary.Data.Poster > 0)
+ {
+ yield return KeyType.Poster;
+ }
+ }
+
+ public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeasonAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
+ var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
+
+ if (imagesSummary.Data.Season > 0)
+ {
+ yield return KeyType.Season;
+ }
+
+ if (imagesSummary.Data.Fanart > 0)
+ {
+ yield return KeyType.Fanart;
+ }
+
+ // TODO seasonwide is not supported in TvDbSharper
+ }
+
private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
{
if (_cache.TryGetValue(key, out T cachedValue))
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
index 5af99a573..e9ba20475 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
@@ -65,8 +65,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
var language = item.GetPreferredMetadataLanguage();
var remoteImages = new List<RemoteImageInfo>();
- var keyTypes = new[] { KeyType.Season, KeyType.Seasonwide, KeyType.Fanart };
- foreach (var keyType in keyTypes)
+ var keyTypes = _tvdbClientManager.GetImageKeyTypesForSeasonAsync(tvdbId, language, cancellationToken).ConfigureAwait(false);
+ await foreach (var keyType in keyTypes)
{
var imageQuery = new ImagesQuery
{
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
index 7dd012825..c33aefbc1 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
@@ -59,9 +59,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
var language = item.GetPreferredMetadataLanguage();
var remoteImages = new List<RemoteImageInfo>();
- var keyTypes = new[] { KeyType.Poster, KeyType.Series, KeyType.Fanart };
var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tvdb));
- foreach (KeyType keyType in keyTypes)
+ var allowedKeyTypes = _tvdbClientManager.GetImageKeyTypesForSeriesAsync(tvdbId, language, cancellationToken)
+ .ConfigureAwait(false);
+ await foreach (KeyType keyType in allowedKeyTypes)
{
var imageQuery = new ImagesQuery
{
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
index b3641dc9f..196e801c0 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
@@ -247,10 +247,15 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
Name = tvdbTitles.FirstOrDefault(),
ProductionYear = firstAired.Year,
- SearchProviderName = Name,
- ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner
+ SearchProviderName = Name
};
+ if (!string.IsNullOrEmpty(seriesSearchResult.Banner))
+ {
+ // Results from their Search endpoints already include the /banners/ part in the url, because reasons...
+ remoteSearchResult.ImageUrl = TvdbUtils.TvdbImageBaseUrl + seriesSearchResult.Banner;
+ }
+
try
{
var seriesSesult =
@@ -365,10 +370,14 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
Type = PersonType.Actor,
Name = (actor.Name ?? string.Empty).Trim(),
Role = actor.Role,
- ImageUrl = TvdbUtils.BannerUrl + actor.Image,
SortOrder = actor.SortOrder
};
+ if (!string.IsNullOrEmpty(actor.Image))
+ {
+ personInfo.ImageUrl = TvdbUtils.TvdbImageBaseUrl + actor.Image;
+ }
+
if (!string.IsNullOrWhiteSpace(personInfo.Name))
{
result.AddPerson(personInfo);
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
index 3f71041b2..37a8d04a6 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
@@ -9,7 +9,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
public const string TvdbBaseUrl = "https://www.thetvdb.com/";
- public const string BannerUrl = TvdbBaseUrl + "banners/";
+ public const string TvdbImageBaseUrl = "https://www.thetvdb.com";
+ public const string BannerUrl = TvdbImageBaseUrl + "/banners/";
public static ImageType GetImageTypeFromKeyType(string keyType)
{
diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64
index 3fabc2cac..aae061e9d 100755
--- a/deployment/build.windows.amd64
+++ b/deployment/build.windows.amd64
@@ -8,7 +8,7 @@ set -o xtrace
# Version variables
NSSM_VERSION="nssm-2.24-101-g897c7ad"
NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
-FFMPEG_VERSION="ffmpeg-4.2.1-win64-static"
+FFMPEG_VERSION="ffmpeg-4.3-win64-static"
FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip"
# Move to source directory
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index bdc3fe15c..219020888 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.12.0" />
+ <PackageReference Include="AutoFixture" Version="4.13.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.12.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.5" />
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
index af29fec87..9eb601edf 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
@@ -17,6 +17,7 @@ namespace Jellyfin.MediaEncoding.Tests
}
[Theory]
+ [InlineData(EncoderValidatorTestsData.FFmpegV43Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV421Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV42Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV414Output, true)]
@@ -32,6 +33,7 @@ namespace Jellyfin.MediaEncoding.Tests
{
public IEnumerator<object?[]> GetEnumerator()
{
+ yield return new object?[] { EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
index c46c9578b..f5ff3d723 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
@@ -2,6 +2,18 @@ namespace Jellyfin.MediaEncoding.Tests
{
internal static class EncoderValidatorTestsData
{
+ public const string FFmpegV43Output = @"ffmpeg version 4.3 Copyright (c) 2000-2020 the FFmpeg developers
+built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04)
+configuration: --prefix=/usr/lib/jellyfin-ffmpeg --target-os=linux --disable-doc --disable-ffplay --disable-shared --disable-libxcb --disable-vdpau --disable-sdl2 --disable-xlib --enable-gpl --enable-version3 --enable-static --enable-libfontconfig --enable-fontconfig --enable-gmp --enable-gnutls --enable-libass --enable-libbluray --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libx264 --enable-libx265 --enable-libzvbi --arch=amd64 --enable-amf --enable-nvenc --enable-nvdec --enable-vaapi --enable-opencl
+libavutil 56. 51.100 / 56. 51.100
+libavcodec 58. 91.100 / 58. 91.100
+libavformat 58. 45.100 / 58. 45.100
+libavdevice 58. 10.100 / 58. 10.100
+libavfilter 7. 85.100 / 7. 85.100
+libswscale 5. 7.100 / 5. 7.100
+libswresample 3. 7.100 / 3. 7.100
+libpostproc 55. 7.100 / 55. 7.100";
+
public const string FFmpegV421Output = @"ffmpeg version 4.2.1 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 9.1.1 (GCC) 20190807
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 6610478ab..03187f4b9 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -14,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.12.0" />
+ <PackageReference Include="AutoFixture" Version="4.13.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
<PackageReference Include="Moq" Version="4.14.5" />
<PackageReference Include="xunit" Version="2.4.1" />