aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
authorNick <20588554+nicknsy@users.noreply.github.com>2023-10-18 19:27:05 -0700
committerNick <20588554+nicknsy@users.noreply.github.com>2023-10-18 19:27:05 -0700
commitcd662506a1f63f9b20e7f5caa9b671eb3d71ea5a (patch)
treeb58f7158e21e7ed21d77b0f0abfce23d796b3fe3 /MediaBrowser.Controller
parentc7feea27fde8af60984c8fe41444dc245dbde395 (diff)
parentde08d38a6f2a6e773fa1000574e08322605b56d3 (diff)
Merge branch 'master' into trickplay
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs8
-rw-r--r--MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs1
-rw-r--r--MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs1
-rw-r--r--MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs9
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs13
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs3
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs6
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs3
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItemExtensions.cs5
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs42
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs2
-rw-r--r--MediaBrowser.Controller/Entities/ItemImageInfo.cs8
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs2
-rw-r--r--MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs60
-rw-r--r--MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs38
-rw-r--r--MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs193
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs17
-rw-r--r--MediaBrowser.Controller/ISystemManager.cs34
-rw-r--r--MediaBrowser.Controller/Library/ItemResolveArgs.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs2
-rw-r--r--MediaBrowser.Controller/Lyrics/ILyricParser.cs28
-rw-r--r--MediaBrowser.Controller/Lyrics/ILyricProvider.cs36
-rw-r--r--MediaBrowser.Controller/Lyrics/LyricFile.cs28
-rw-r--r--MediaBrowser.Controller/Lyrics/LyricInfo.cs49
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs128
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs4
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs9
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketConnection.cs30
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessage.cs6
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessageInfo.cs4
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessageOfT.cs5
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs10
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs13
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/InboundKeepAliveMessage.cs14
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs9
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs13
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs8
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs12
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs7
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessageOfT.cs26
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/OutboundKeepAliveMessage.cs14
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs5
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs11
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessageOfT.cs33
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Shared/KeepAliveMessage.cs23
-rw-r--r--MediaBrowser.Controller/Playlists/IPlaylistManager.cs6
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs1
-rw-r--r--MediaBrowser.Controller/Resolvers/IItemResolver.cs2
-rw-r--r--MediaBrowser.Controller/Resolvers/ItemResolver.cs6
-rw-r--r--MediaBrowser.Controller/Security/IAuthenticationManager.cs4
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs14
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleResponse.cs2
85 files changed, 697 insertions, 380 deletions
diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
index a56d3c822..81b532fda 100644
--- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
+++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System.Threading.Tasks;
@@ -23,7 +21,7 @@ namespace MediaBrowser.Controller.Authentication
public interface IRequiresResolvedUser
{
- Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser);
+ Task<ProviderAuthenticationResult> Authenticate(string username, string password, User? resolvedUser);
}
public interface IHasNewUserPolicy
@@ -33,8 +31,8 @@ namespace MediaBrowser.Controller.Authentication
public class ProviderAuthenticationResult
{
- public string Username { get; set; }
+ public required string Username { get; set; }
- public string DisplayName { get; set; }
+ public string? DisplayName { get; set; }
}
}
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
index b263c173e..6acab13fe 100644
--- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
@@ -1,5 +1,4 @@
using System;
-using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
index ac20120d9..975218ad7 100644
--- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
@@ -1,4 +1,3 @@
-using System.Threading;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
diff --git a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
index dea1c2f32..2a7e6be0f 100644
--- a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
+++ b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
@@ -23,9 +23,12 @@ namespace MediaBrowser.Controller.ClientEvent
{
var fileName = $"upload_{clientName}_{clientVersion}_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}.log";
var logFilePath = Path.Combine(_applicationPaths.LogDirectoryPath, fileName);
- await using var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
- await fileContents.CopyToAsync(fileStream).ConfigureAwait(false);
- return fileName;
+ var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ await using (fileStream.ConfigureAwait(false))
+ {
+ await fileContents.CopyToAsync(fileStream).ConfigureAwait(false);
+ return fileName;
+ }
}
}
}
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index e5ce0aa21..0d1e2a5a0 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Entities;
@@ -66,7 +65,7 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>Guid.</returns>
string GetImageCacheTag(BaseItem item, ItemImageInfo image);
- string GetImageCacheTag(BaseItem item, ChapterInfo chapter);
+ string? GetImageCacheTag(BaseItem item, ChapterInfo chapter);
string? GetImageCacheTag(User user);
@@ -74,14 +73,6 @@ namespace MediaBrowser.Controller.Drawing
/// Processes the image.
/// </summary>
/// <param name="options">The options.</param>
- /// <param name="toStream">To stream.</param>
- /// <returns>Task.</returns>
- Task ProcessImage(ImageProcessingOptions options, Stream toStream);
-
- /// <summary>
- /// Processes the image.
- /// </summary>
- /// <param name="options">The options.</param>
/// <returns>Task.</returns>
Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options);
@@ -97,7 +88,5 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="options">The options.</param>
/// <param name="libraryName">The library name to draw onto the collage.</param>
void CreateImageCollage(ImageCollageOptions options, string? libraryName);
-
- bool SupportsTransparency(string path);
}
}
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index 7912c5e87..953cfe698 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -119,7 +119,8 @@ namespace MediaBrowser.Controller.Drawing
private bool IsFormatSupported(string originalImagePath)
{
var ext = Path.GetExtension(originalImagePath);
- return SupportedOutputFormats.Any(outputFormat => string.Equals(ext, "." + outputFormat, StringComparison.OrdinalIgnoreCase));
+ ext = ext.Replace(".jpeg", ".jpg", StringComparison.OrdinalIgnoreCase);
+ return SupportedOutputFormats.Any(outputFormat => string.Equals(ext, outputFormat.GetExtension(), StringComparison.OrdinalIgnoreCase));
}
}
}
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
index 62b70ce53..10326363a 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using MediaBrowser.Controller.Entities;
@@ -9,12 +7,12 @@ namespace MediaBrowser.Controller.Drawing
{
public static class ImageProcessorExtensions
{
- public static string GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType)
+ public static string? GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType)
{
return processor.GetImageCacheTag(item, imageType, 0);
}
- public static string GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType, int imageIndex)
+ public static string? GetImageCacheTag(this IImageProcessor processor, BaseItem item, ImageType imageType, int imageIndex)
{
var imageInfo = item.GetImageInfo(imageType, imageIndex);
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 2dbd513a1..237345206 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -183,6 +183,9 @@ namespace MediaBrowser.Controller.Entities.Audio
progress.Report(percent * 95);
}
+ // get album LUFS
+ LUFS = items.OfType<Audio>().Max(item => item.LUFS);
+
var parentRefreshOptions = refreshOptions;
if (childUpdateType > ItemUpdateType.None)
{
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 501811003..9f3e8eec9 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1864,7 +1864,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentException">Backdrops should be accessed using Item.Backdrops.</exception>
public bool HasImage(ImageType type, int imageIndex)
{
- return GetImageInfo(type, imageIndex) != null;
+ return GetImageInfo(type, imageIndex) is not null;
}
public void SetImage(ItemImageInfo image, int index)
diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
index 615d236c7..dcd22a3b4 100644
--- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
+++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
@@ -95,10 +95,7 @@ namespace MediaBrowser.Controller.Entities
}
var p = destProps.Find(x => x.Name == sourceProp.Name);
- if (p is not null)
- {
- p.SetValue(dest, v);
- }
+ p?.SetValue(dest, v);
}
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 095b261c0..f51162f9d 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -3,6 +3,7 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -29,7 +30,7 @@ namespace MediaBrowser.Controller.Entities
public class CollectionFolder : Folder, ICollectionFolder
{
private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
- private static readonly Dictionary<string, LibraryOptions> _libraryOptions = new Dictionary<string, LibraryOptions>();
+ private static readonly ConcurrentDictionary<string, LibraryOptions> _libraryOptions = new ConcurrentDictionary<string, LibraryOptions>();
private bool _requiresRefresh;
/// <summary>
@@ -139,45 +140,26 @@ namespace MediaBrowser.Controller.Entities
}
public static LibraryOptions GetLibraryOptions(string path)
- {
- lock (_libraryOptions)
- {
- if (!_libraryOptions.TryGetValue(path, out var options))
- {
- options = LoadLibraryOptions(path);
- _libraryOptions[path] = options;
- }
-
- return options;
- }
- }
+ => _libraryOptions.GetOrAdd(path, LoadLibraryOptions);
public static void SaveLibraryOptions(string path, LibraryOptions options)
{
- lock (_libraryOptions)
- {
- _libraryOptions[path] = options;
+ _libraryOptions[path] = options;
- var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
- foreach (var mediaPath in clone.PathInfos)
+ var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
+ foreach (var mediaPath in clone.PathInfos)
+ {
+ if (!string.IsNullOrEmpty(mediaPath.Path))
{
- if (!string.IsNullOrEmpty(mediaPath.Path))
- {
- mediaPath.Path = ApplicationHost.ReverseVirtualPath(mediaPath.Path);
- }
+ mediaPath.Path = ApplicationHost.ReverseVirtualPath(mediaPath.Path);
}
-
- XmlSerializer.SerializeToFile(clone, GetLibraryOptionsPath(path));
}
+
+ XmlSerializer.SerializeToFile(clone, GetLibraryOptionsPath(path));
}
public static void OnCollectionFolderChange()
- {
- lock (_libraryOptions)
- {
- _libraryOptions.Clear();
- }
- }
+ => _libraryOptions.Clear();
public override bool IsSaveLocalMetadataEnabled()
{
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 44fe65103..e707eedbf 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -598,7 +598,7 @@ namespace MediaBrowser.Controller.Entities
for (var i = 0; i < childrenCount; i++)
{
- await actionBlock.SendAsync(i).ConfigureAwait(false);
+ await actionBlock.SendAsync(i, cancellationToken).ConfigureAwait(false);
}
actionBlock.Complete();
diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs
index 0171af27c..1d45d4da0 100644
--- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs
+++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -14,7 +12,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
- public string Path { get; set; }
+ public required string Path { get; set; }
/// <summary>
/// Gets or sets the type.
@@ -36,9 +34,9 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the blurhash.
/// </summary>
/// <value>The blurhash.</value>
- public string BlurHash { get; set; }
+ public string? BlurHash { get; set; }
[JsonIgnore]
- public bool IsLocalFile => Path is null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase);
+ public bool IsLocalFile => !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase);
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 597b4cecb..bf31508c1 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -99,7 +99,7 @@ namespace MediaBrowser.Controller.Entities.TV
}
[JsonIgnore]
- public bool IsInSeasonFolder => FindParent<Season>() != null;
+ public bool IsInSeasonFolder => FindParent<Season>() is not null;
[JsonIgnore]
public string SeriesPresentationUniqueKey { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 5b7abea10..9f685b7e2 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -333,7 +333,7 @@ namespace MediaBrowser.Controller.Entities
protected override bool IsActiveRecording()
{
- return LiveTvManager.GetActiveRecordingInfo(Path) != null;
+ return LiveTvManager.GetActiveRecordingInfo(Path) is not null;
}
public override bool CanDelete()
diff --git a/MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs b/MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs
new file mode 100644
index 000000000..2143c6998
--- /dev/null
+++ b/MediaBrowser.Controller/Events/Authentication/AuthenticationRequestEventArgs.cs
@@ -0,0 +1,60 @@
+using System;
+using MediaBrowser.Controller.Session;
+
+namespace MediaBrowser.Controller.Events.Authentication;
+
+/// <summary>
+/// A class representing an authentication result event.
+/// </summary>
+public class AuthenticationRequestEventArgs : EventArgs
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationRequestEventArgs"/> class.
+ /// </summary>
+ /// <param name="request">The <see cref="AuthenticationRequest"/>.</param>
+ public AuthenticationRequestEventArgs(AuthenticationRequest request)
+ {
+ Username = request.Username;
+ UserId = request.UserId;
+ App = request.App;
+ AppVersion = request.AppVersion;
+ DeviceId = request.DeviceId;
+ DeviceName = request.DeviceName;
+ RemoteEndPoint = request.RemoteEndPoint;
+ }
+
+ /// <summary>
+ /// Gets or sets the user name.
+ /// </summary>
+ public string? Username { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user id.
+ /// </summary>
+ public Guid? UserId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the app.
+ /// </summary>
+ public string? App { get; set; }
+
+ /// <summary>
+ /// Gets or sets the app version.
+ /// </summary>
+ public string? AppVersion { get; set; }
+
+ /// <summary>
+ /// Gets or sets the device id.
+ /// </summary>
+ public string? DeviceId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the device name.
+ /// </summary>
+ public string? DeviceName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the remote endpoint.
+ /// </summary>
+ public string? RemoteEndPoint { get; set; }
+}
diff --git a/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs
new file mode 100644
index 000000000..357ef9406
--- /dev/null
+++ b/MediaBrowser.Controller/Events/Authentication/AuthenticationResultEventArgs.cs
@@ -0,0 +1,38 @@
+using System;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Dto;
+
+namespace MediaBrowser.Controller.Events.Authentication;
+
+/// <summary>
+/// A class representing an authentication result event.
+/// </summary>
+public class AuthenticationResultEventArgs : EventArgs
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationResultEventArgs"/> class.
+ /// </summary>
+ /// <param name="result">The <see cref="AuthenticationResult"/>.</param>
+ public AuthenticationResultEventArgs(AuthenticationResult result)
+ {
+ User = result.User;
+ SessionInfo = result.SessionInfo;
+ ServerId = result.ServerId;
+ }
+
+ /// <summary>
+ /// Gets or sets the user.
+ /// </summary>
+ public UserDto User { get; set; }
+
+ /// <summary>
+ /// Gets or sets the session information.
+ /// </summary>
+ public SessionInfo? SessionInfo { get; set; }
+
+ /// <summary>
+ /// Gets or sets the server id.
+ /// </summary>
+ public string? ServerId { get; set; }
+}
diff --git a/MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs b/MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs
new file mode 100644
index 000000000..2742f21e3
--- /dev/null
+++ b/MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Xml;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Entities;
+
+namespace MediaBrowser.Controller.Extensions;
+
+/// <summary>
+/// Provides extension methods for <see cref="XmlReader"/> to parse <see cref="BaseItem"/>'s.
+/// </summary>
+public static class XmlReaderExtensions
+{
+ /// <summary>
+ /// Reads a trimmed string from the current node.
+ /// </summary>
+ /// <param name="reader">The <see cref="XmlReader"/>.</param>
+ /// <returns>The trimmed content.</returns>
+ public static string ReadNormalizedString(this XmlReader reader)
+ {
+ ArgumentNullException.ThrowIfNull(reader);
+
+ return reader.ReadElementContentAsString().Trim();
+ }
+
+ /// <summary>
+ /// Reads an int from the current node.
+ /// </summary>
+ /// <param name="reader">The <see cref="XmlReader"/>.</param>
+ /// <param name="value">The parsed <c>int</c>.</param>
+ /// <returns>A value indicating whether the parsing succeeded.</returns>
+ public static bool TryReadInt(this XmlReader reader, out int value)
+ {
+ ArgumentNullException.ThrowIfNull(reader);
+
+ return int.TryParse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture, out value);
+ }
+
+ /// <summary>
+ /// Parses a <see cref="DateTime"/> from the current node.
+ /// </summary>
+ /// <param name="reader">The <see cref="XmlReader"/>.</param>
+ /// <param name="value">The parsed <see cref="DateTime"/>.</param>
+ /// <returns>A value indicating whether the parsing succeeded.</returns>
+ public static bool TryReadDateTime(this XmlReader reader, out DateTime value)
+ {
+ ArgumentNullException.ThrowIfNull(reader);
+
+ return DateTime.TryParse(
+ reader.ReadElementContentAsString(),
+ CultureInfo.InvariantCulture,
+ DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
+ out value);
+ }
+
+ /// <summary>
+ /// Parses a <see cref="DateTime"/> from the current node.
+ /// </summary>
+ /// <param name="reader">The <see cref="XmlReader"/>.</param>
+ /// <param name="formatString">The date format string.</param>
+ /// <param name="value">The parsed <see cref="DateTime"/>.</param>
+ /// <returns>A value indicating whether the parsing succeeded.</returns>
+ public static bool TryReadDateTimeExact(this XmlReader reader, string formatString, out DateTime value)
+ {
+ ArgumentNullException.ThrowIfNull(reader);
+ ArgumentNullException.ThrowIfNull(formatString);
+
+ return DateTime.TryParseExact(
+ reader.ReadElementContentAsString(),
+ formatString,
+ CultureInfo.InvariantCulture,
+ DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
+ out value);
+ }
+
+ /// <summary>
+ /// Parses a <see cref="PersonInfo"/> from the xml node.
+ /// </summary>
+ /// <param name="reader">The <see cref="XmlReader"/>.</param>
+ /// <returns>A <see cref="PersonInfo"/>, or <c>null</c> if none is found.</returns>
+ public static PersonInfo? GetPersonFromXmlNode(this XmlReader reader)
+ {
+ ArgumentNullException.ThrowIfNull(reader);
+
+ if (reader.IsEmptyElement)
+ {
+ reader.Read();
+ return null;
+ }
+
+ var name = string.Empty;
+ var type = PersonKind.Actor; // If type is not specified assume actor
+ var role = string.Empty;
+ int? sortOrder = null;
+ string? imageUrl = null;
+
+ using var subtree = reader.ReadSubtree();
+ subtree.MoveToContent();
+ subtree.Read();
+
+ while (subtree is { EOF: false, ReadState: ReadState.Interactive })
+ {
+ if (subtree.NodeType != XmlNodeType.Element)
+ {
+ subtree.Read();
+ continue;
+ }
+
+ switch (subtree.Name)
+ {
+ case "name":
+ case "Name":
+ name = subtree.ReadNormalizedString();
+ break;
+ case "role":
+ case "Role":
+ role = subtree.ReadNormalizedString();
+ break;
+ case "type":
+ case "Type":
+ Enum.TryParse(subtree.ReadElementContentAsString(), true, out type);
+ break;
+ case "order":
+ case "sortorder":
+ case "SortOrder":
+ if (subtree.TryReadInt(out var sortOrderVal))
+ {
+ sortOrder = sortOrderVal;
+ }
+
+ break;
+ case "thumb":
+ imageUrl = subtree.ReadNormalizedString();
+ break;
+ default:
+ subtree.Skip();
+ break;
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return null;
+ }
+
+ return new PersonInfo
+ {
+ Name = name,
+ Role = role,
+ Type = type,
+ SortOrder = sortOrder,
+ ImageUrl = imageUrl
+ };
+ }
+
+ /// <summary>
+ /// Used to split names of comma or pipe delimited genres and people.
+ /// </summary>
+ /// <param name="reader">The <see cref="XmlReader"/>.</param>
+ /// <returns>IEnumerable{System.String}.</returns>
+ public static IEnumerable<string> GetStringArray(this XmlReader reader)
+ {
+ ArgumentNullException.ThrowIfNull(reader);
+ var value = reader.ReadElementContentAsString();
+
+ // Only split by comma if there is no pipe in the string
+ // We have to be careful to not split names like Matthew, Jr.
+ var separator = !value.Contains('|', StringComparison.Ordinal)
+ && !value.Contains(';', StringComparison.Ordinal)
+ ? new[] { ',' }
+ : new[] { '|', ';' };
+
+ foreach (var part in value.Trim().Trim(separator).Split(separator))
+ {
+ if (!string.IsNullOrWhiteSpace(part))
+ {
+ yield return part.Trim();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Parses a <see cref="PersonInfo"/> array from the xml node.
+ /// </summary>
+ /// <param name="reader">The <see cref="XmlReader"/>.</param>
+ /// <param name="personKind">The <see cref="PersonKind"/>.</param>
+ /// <returns>The <see cref="IEnumerable{PersonInfo}"/>.</returns>
+ public static IEnumerable<PersonInfo> GetPersonArray(this XmlReader reader, PersonKind personKind)
+ => reader.GetStringArray()
+ .Select(part => new PersonInfo { Name = part, Type = personKind });
+}
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 11afdc4ae..e9c4d9e19 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -4,8 +4,6 @@
using System.Net;
using MediaBrowser.Common;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.System;
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller
@@ -17,8 +15,6 @@ namespace MediaBrowser.Controller
{
bool CoreStartupHasCompleted { get; }
- bool CanLaunchWebBrowser { get; }
-
/// <summary>
/// Gets the HTTP server port.
/// </summary>
@@ -43,15 +39,6 @@ namespace MediaBrowser.Controller
string FriendlyName { get; }
/// <summary>
- /// Gets the system info.
- /// </summary>
- /// <param name="request">The HTTP request.</param>
- /// <returns>SystemInfo.</returns>
- SystemInfo GetSystemInfo(HttpRequest request);
-
- PublicSystemInfo GetPublicSystemInfo(HttpRequest request);
-
- /// <summary>
/// Gets a URL specific for the request.
/// </summary>
/// <param name="request">The <see cref="HttpRequest"/> instance.</param>
@@ -75,10 +62,10 @@ namespace MediaBrowser.Controller
/// <summary>
/// Gets an URL that can be used to access the API over LAN.
/// </summary>
- /// <param name="hostname">An optional hostname to use.</param>
+ /// <param name="ipAddress">An optional IP address to use.</param>
/// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
/// <returns>The API URL.</returns>
- string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true);
+ string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true);
/// <summary>
/// Gets a local (LAN) URL that can be used to access the API.
diff --git a/MediaBrowser.Controller/ISystemManager.cs b/MediaBrowser.Controller/ISystemManager.cs
new file mode 100644
index 000000000..ef3034d2f
--- /dev/null
+++ b/MediaBrowser.Controller/ISystemManager.cs
@@ -0,0 +1,34 @@
+using MediaBrowser.Model.System;
+using Microsoft.AspNetCore.Http;
+
+namespace MediaBrowser.Controller;
+
+/// <summary>
+/// A service for managing the application instance.
+/// </summary>
+public interface ISystemManager
+{
+ /// <summary>
+ /// Gets the system info.
+ /// </summary>
+ /// <param name="request">The HTTP request.</param>
+ /// <returns>The <see cref="SystemInfo"/>.</returns>
+ SystemInfo GetSystemInfo(HttpRequest request);
+
+ /// <summary>
+ /// Gets the public system info.
+ /// </summary>
+ /// <param name="request">The HTTP request.</param>
+ /// <returns>The <see cref="PublicSystemInfo"/>.</returns>
+ PublicSystemInfo GetPublicSystemInfo(HttpRequest request);
+
+ /// <summary>
+ /// Starts the application restart process.
+ /// </summary>
+ void Restart();
+
+ /// <summary>
+ /// Starts the application shutdown process.
+ /// </summary>
+ void Shutdown();
+}
diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
index c70102167..dcd0110fb 100644
--- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs
+++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
@@ -217,7 +217,7 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if [contains file system entry by name] [the specified name]; otherwise, <c>false</c>.</returns>
public bool ContainsFileSystemEntryByName(string name)
{
- return GetFileSystemEntryByName(name) != null;
+ return GetFileSystemEntryByName(name) is not null;
}
public string GetCollectionType()
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index c721fb778..05540d490 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.LiveTv
{
public class LiveTvProgram : BaseItem, IHasLookupInfo<ItemLookupInfo>, IHasStartDate, IHasProgramAttributes
{
- private static string EmbyServiceName = "Emby";
+ private const string EmbyServiceName = "Emby";
public LiveTvProgram()
{
diff --git a/MediaBrowser.Controller/Lyrics/ILyricParser.cs b/MediaBrowser.Controller/Lyrics/ILyricParser.cs
new file mode 100644
index 000000000..65a9471a3
--- /dev/null
+++ b/MediaBrowser.Controller/Lyrics/ILyricParser.cs
@@ -0,0 +1,28 @@
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Providers.Lyric;
+
+namespace MediaBrowser.Controller.Lyrics;
+
+/// <summary>
+/// Interface ILyricParser.
+/// </summary>
+public interface ILyricParser
+{
+ /// <summary>
+ /// Gets a value indicating the provider name.
+ /// </summary>
+ string Name { get; }
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ ResolverPriority Priority { get; }
+
+ /// <summary>
+ /// Parses the raw lyrics into a response.
+ /// </summary>
+ /// <param name="lyrics">The raw lyrics content.</param>
+ /// <returns>The parsed lyrics or null if invalid.</returns>
+ LyricResponse? ParseLyrics(LyricFile lyrics);
+}
diff --git a/MediaBrowser.Controller/Lyrics/ILyricProvider.cs b/MediaBrowser.Controller/Lyrics/ILyricProvider.cs
deleted file mode 100644
index 2a04c6152..000000000
--- a/MediaBrowser.Controller/Lyrics/ILyricProvider.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Resolvers;
-
-namespace MediaBrowser.Controller.Lyrics;
-
-/// <summary>
-/// Interface ILyricsProvider.
-/// </summary>
-public interface ILyricProvider
-{
- /// <summary>
- /// Gets a value indicating the provider name.
- /// </summary>
- string Name { get; }
-
- /// <summary>
- /// Gets the priority.
- /// </summary>
- /// <value>The priority.</value>
- ResolverPriority Priority { get; }
-
- /// <summary>
- /// Gets the supported media types for this provider.
- /// </summary>
- /// <value>The supported media types.</value>
- IReadOnlyCollection<string> SupportedMediaTypes { get; }
-
- /// <summary>
- /// Gets the lyrics.
- /// </summary>
- /// <param name="item">The media item.</param>
- /// <returns>A task representing found lyrics.</returns>
- Task<LyricResponse?> GetLyrics(BaseItem item);
-}
diff --git a/MediaBrowser.Controller/Lyrics/LyricFile.cs b/MediaBrowser.Controller/Lyrics/LyricFile.cs
new file mode 100644
index 000000000..ede89403c
--- /dev/null
+++ b/MediaBrowser.Controller/Lyrics/LyricFile.cs
@@ -0,0 +1,28 @@
+namespace MediaBrowser.Providers.Lyric;
+
+/// <summary>
+/// The information for a raw lyrics file before parsing.
+/// </summary>
+public class LyricFile
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LyricFile"/> class.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="content">The content, must not be empty.</param>
+ public LyricFile(string name, string content)
+ {
+ Name = name;
+ Content = content;
+ }
+
+ /// <summary>
+ /// Gets or sets the name of the lyrics file. This must include the file extension.
+ /// </summary>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the contents of the file.
+ /// </summary>
+ public string Content { get; set; }
+}
diff --git a/MediaBrowser.Controller/Lyrics/LyricInfo.cs b/MediaBrowser.Controller/Lyrics/LyricInfo.cs
deleted file mode 100644
index 6ec6df582..000000000
--- a/MediaBrowser.Controller/Lyrics/LyricInfo.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System;
-using System.IO;
-using Jellyfin.Extensions;
-
-namespace MediaBrowser.Controller.Lyrics;
-
-/// <summary>
-/// Lyric helper methods.
-/// </summary>
-public static class LyricInfo
-{
- /// <summary>
- /// Gets matching lyric file for a requested item.
- /// </summary>
- /// <param name="lyricProvider">The lyricProvider interface to use.</param>
- /// <param name="itemPath">Path of requested item.</param>
- /// <returns>Lyric file path if passed lyric provider's supported media type is found; otherwise, null.</returns>
- public static string? GetLyricFilePath(this ILyricProvider lyricProvider, string itemPath)
- {
- // Ensure we have a provider
- if (lyricProvider is null)
- {
- return null;
- }
-
- // Ensure the path to the item is not null
- string? itemDirectoryPath = Path.GetDirectoryName(itemPath);
- if (itemDirectoryPath is null)
- {
- return null;
- }
-
- // Ensure the directory path exists
- if (!Directory.Exists(itemDirectoryPath))
- {
- return null;
- }
-
- foreach (var lyricFilePath in Directory.GetFiles(itemDirectoryPath, $"{Path.GetFileNameWithoutExtension(itemPath)}.*"))
- {
- if (lyricProvider.SupportedMediaTypes.Contains(Path.GetExtension(lyricFilePath.AsSpan())[1..], StringComparison.OrdinalIgnoreCase))
- {
- return lyricFilePath;
- }
- }
-
- return null;
- }
-}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 0c2a85a02..c3a20cdb4 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -23,7 +23,7 @@ using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
- public class EncodingHelper
+ public partial class EncodingHelper
{
private const string QsvAlias = "qs";
private const string VaapiAlias = "va";
@@ -37,7 +37,8 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config;
- private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
+ private readonly IConfigurationManager _configurationManager;
+
// i915 hang was fixed by linux 6.2 (3f882f2)
private readonly Version _minKerneli915Hang = new Version(5, 18);
private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
@@ -47,6 +48,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
+ private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
private static readonly string[] _videoProfilesH264 = new[]
{
@@ -119,14 +121,19 @@ namespace MediaBrowser.Controller.MediaEncoding
IApplicationPaths appPaths,
IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder,
- IConfiguration config)
+ IConfiguration config,
+ IConfigurationManager configurationManager)
{
_appPaths = appPaths;
_mediaEncoder = mediaEncoder;
_subtitleEncoder = subtitleEncoder;
_config = config;
+ _configurationManager = configurationManager;
}
+ [GeneratedRegex(@"\s+")]
+ private static partial Regex WhiteSpaceRegex();
+
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
=> GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
@@ -571,25 +578,25 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.Nullable{VideoCodecs}.</returns>
public string InferVideoCodec(string url)
{
- var ext = Path.GetExtension(url);
+ var ext = Path.GetExtension(url.AsSpan());
- if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
+ if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase))
{
return "wmv";
}
- if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
+ if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase))
{
// TODO: this may not always mean VP8, as the codec ages
return "vp8";
}
- if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
+ if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgnoreCase))
{
return "theora";
}
- if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
+ if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgnoreCase))
{
return "h264";
}
@@ -918,9 +925,11 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (_mediaEncoder.IsVaapiDeviceAmd)
{
+ // Disable AMD EFC feature since it's still unstable in upstream Mesa.
+ Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
+
if (IsVulkanFullSupported()
- && _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
- && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
+ && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop)
{
args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias));
@@ -1083,7 +1092,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
{
- var tmpConcatPath = Path.Join(options.TranscodingTempPath, state.MediaSource.Id + ".concat");
+ var tmpConcatPath = Path.Join(_configurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat");
_mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath);
arg.Append(" -f concat -safe 0 -i ")
.Append(tmpConcatPath);
@@ -1101,10 +1110,10 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.SubtitleStream.IsExternal)
{
var subtitlePath = state.SubtitleStream.Path;
- var subtitleExtension = Path.GetExtension(subtitlePath);
+ var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan());
- if (string.Equals(subtitleExtension, ".sub", StringComparison.OrdinalIgnoreCase)
- || string.Equals(subtitleExtension, ".sup", StringComparison.OrdinalIgnoreCase))
+ if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase)
+ || subtitleExtension.Equals(".sup", StringComparison.OrdinalIgnoreCase))
{
var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
if (File.Exists(idxFile))
@@ -1238,6 +1247,12 @@ namespace MediaBrowser.Controller.MediaEncoding
int bitrate = state.OutputVideoBitrate.Value;
+ // Bit rate under 1000k is not allowed in h264_qsv
+ if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ bitrate = Math.Max(bitrate, 1000);
+ }
+
// Currently use the same buffer size for all encoders
int bufsize = bitrate * 2;
@@ -1484,6 +1499,13 @@ namespace MediaBrowser.Controller.MediaEncoding
args += keyFrameArg + gopArg;
}
+ // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
+ if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.IsVaapiDeviceAmd)
+ {
+ args += " -flags:v -global_header";
+ }
+
return args;
}
@@ -1753,11 +1775,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// Values 0-3, 0 being highest quality but slower
var profileScore = 0;
- string crf;
var qmin = "0";
var qmax = "50";
-
- crf = "10";
+ var crf = "10";
if (isVc1)
{
@@ -1854,7 +1874,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
- profile = Regex.Replace(profile, @"\s+", string.Empty);
+ profile = WhiteSpaceRegex().Replace(profile, string.Empty);
// We only transcode to HEVC 8-bit for now, force Main Profile.
if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
@@ -1925,7 +1945,9 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(profile))
{
- if (!string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
+ // Currently there's no profile option in av1_nvenc encoder
+ if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
{
param += " -profile:v:0 " + profile;
}
@@ -2013,6 +2035,14 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
}
+ /* Access unit too large: 8192 < 20880 error */
+ if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
+ _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
+ {
+ param += " -sei -a53_cc";
+ }
+
return param;
}
@@ -2710,7 +2740,7 @@ namespace MediaBrowser.Controller.MediaEncoding
string args = string.Empty;
// http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
- if (state.VideoStream != null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordinal))
+ if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordinal))
{
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
@@ -2945,7 +2975,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
+ @"scale=trunc(min(max(iw\,ih*a)\,min({0}\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\,ih)\,min({0}/a\,{1}))/2)*2",
maxWidthParam,
maxHeightParam,
scaleVal);
@@ -2987,7 +3017,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
+ @"scale=trunc(min(max(iw\,ih*a)\,{0})/{1})*{1}:trunc(ow/a/2)*2",
maxWidthParam,
scaleVal);
}
@@ -2999,7 +3029,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
+ @"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\,ih)\,{0})",
maxHeightParam,
scaleVal);
}
@@ -3019,19 +3049,19 @@ namespace MediaBrowser.Controller.MediaEncoding
switch (threedFormat.Value)
{
case Video3DFormat.HalfSideBySide:
- filter = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
+ filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
// hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
break;
case Video3DFormat.FullSideBySide:
- filter = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
+ filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
// fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to requestedWidth.
break;
case Video3DFormat.HalfTopAndBottom:
- filter = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
+ filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
// htab crop height in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth
break;
case Video3DFormat.FullTopAndBottom:
- filter = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
+ filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2";
// ftab crop height in half, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth
break;
default:
@@ -3829,12 +3859,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// map from d3d11va to qsv.
mainFilters.Add("hwmap=derive_device=qsv");
}
- else
- {
- // Insert a qsv scaler to sync the decoder surface,
- // msdk will passthrough this internally.
- mainFilters.Add("hwmap=derive_device=qsv,scale_qsv");
- }
}
// hw deint
@@ -4225,14 +4249,13 @@ namespace MediaBrowser.Controller.MediaEncoding
// prefered vaapi + vulkan filters pipeline
if (_mediaEncoder.IsVaapiDeviceAmd
&& isVaapiVkSupported
- && _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
- && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
+ && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop)
{
- // AMD radeonsi path(Vega/gfx9+, kernel>=5.15), with extra vulkan tonemap and overlay support.
+ // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
- // Intel i965 and Amd radeonsi/r600 path(Polaris/gfx8-), only featuring scale and deinterlace support.
+ // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
@@ -4504,7 +4527,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// INPUT vaapi surface(vram)
if (doVkTonemap || hasSubs)
{
- // map from vaapi to vulkan/drm via interop (Vega/gfx9+).
+ // map from vaapi to vulkan/drm via interop (Polaris/gfx8+).
mainFilters.Add("hwmap=derive_device=vulkan");
mainFilters.Add("format=vulkan");
}
@@ -4533,9 +4556,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (doVkTonemap && !hasSubs)
{
// OUTPUT vaapi(nv12) surface(vram)
- // map from vulkan/drm to vaapi via interop (Vega/gfx9+).
- mainFilters.Add("hwmap=derive_device=drm");
- mainFilters.Add("format=drm_prime");
+ // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
mainFilters.Add("hwmap=derive_device=vaapi");
mainFilters.Add("format=vaapi");
@@ -4601,9 +4622,7 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (isVaapiEncoder)
{
// OUTPUT vaapi(nv12) surface(vram)
- // map from vulkan/drm to vaapi via interop (Vega/gfx9+).
- overlayFilters.Add("hwmap=derive_device=drm");
- overlayFilters.Add("format=drm_prime");
+ // map from vulkan/drm to vaapi via interop (Polaris/gfx8+).
overlayFilters.Add("hwmap=derive_device=vaapi");
overlayFilters.Add("format=vaapi");
@@ -5273,10 +5292,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (isD3d11Supported && isCodecAvailable)
{
- // set -threads 3 to intel d3d11va decoder explicitly. Lower threads may result in dead lock.
- // on newer devices such as Xe, the larger the init_pool_size, the longer the initialization time for opencl to derive from d3d11.
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty)
- + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" + (isAv1 ? " -c:v av1" : string.Empty);
}
}
else
@@ -5710,7 +5727,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// Apply -analyzeduration as per the environment variable,
// otherwise ffmpeg will break on certain files due to default value is 0.
- // The default value of -probesize is more than enough, so leave it as is.
var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (state.MediaSource.AnalyzeDurationMs > 0)
@@ -5729,6 +5745,14 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier = inputModifier.Trim();
+ // Apply -probesize if configured
+ var ffmpegProbeSize = _config.GetFFmpegProbeSize();
+
+ if (!string.IsNullOrEmpty(ffmpegProbeSize))
+ {
+ inputModifier += $" -probesize {ffmpegProbeSize}";
+ }
+
var userAgentParam = GetUserAgentParam(state);
if (!string.IsNullOrEmpty(userAgentParam))
@@ -6053,7 +6077,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var format = string.Empty;
var keyFrame = string.Empty;
- if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase)
+ if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase)
&& state.BaseRequest.Context == EncodingContext.Streaming)
{
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
@@ -6262,6 +6286,12 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
}
+ if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
+ {
+ audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
+ audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
+ }
+
if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
// opus only supports specific sampling rates
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index f5e3d03cb..c2cef4978 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -66,8 +66,8 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets a value indicating whether the configured Vaapi device supports vulkan drm format modifier.
/// </summary>
- /// <value><c>true</c> if the Vaapi device supports vulkan drm format modifier, <c>false</c> otherwise.</value>
- bool IsVaapiDeviceSupportVulkanFmtModifier { get; }
+ /// <value><c>true</c> if the Vaapi device supports vulkan drm interop, <c>false</c> otherwise.</value>
+ bool IsVaapiDeviceSupportVulkanDrmInterop { get; }
/// <summary>
/// Whether given encoder codec is supported.
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index 3b34af4e9..3d288b9f8 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -20,12 +20,12 @@ namespace MediaBrowser.Controller.MediaEncoding
_logger = logger;
}
- public async Task StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
+ public async Task StartStreamingLog(EncodingJobInfo state, StreamReader reader, Stream target)
{
try
{
using (target)
- using (var reader = new StreamReader(source))
+ using (reader)
{
while (!reader.EndOfStream && reader.BaseStream.CanRead)
{
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index 0524999c7..0a706c307 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -9,7 +9,7 @@ using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.Net;
+using MediaBrowser.Controller.Net.WebSocketMessages;
using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// The logger.
/// </summary>
- protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
+ protected readonly ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger;
protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger)
{
@@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Net
/// Starts sending messages over a web socket.
/// </summary>
/// <param name="message">The message.</param>
- private void Start(WebSocketMessageInfo message)
+ protected virtual void Start(WebSocketMessageInfo message)
{
var vals = message.Data.Split(',');
@@ -169,9 +169,8 @@ namespace MediaBrowser.Controller.Net
if (data is not null)
{
await connection.SendAsync(
- new WebSocketMessage<TReturnDataType>
+ new OutboundWebSocketMessage<TReturnDataType>
{
- MessageId = Guid.NewGuid(),
MessageType = Type,
Data = data
},
diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
index 4f2492b89..bba5a6b85 100644
--- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
@@ -1,14 +1,15 @@
-#pragma warning disable CS1591
-
using System;
using System.Net;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.Net;
+using MediaBrowser.Controller.Net.WebSocketMessages;
namespace MediaBrowser.Controller.Net
{
+ /// <summary>
+ /// Interface for WebSocket connections.
+ /// </summary>
public interface IWebSocketConnection : IAsyncDisposable, IDisposable
{
/// <summary>
@@ -41,6 +42,11 @@ namespace MediaBrowser.Controller.Net
WebSocketState State { get; }
/// <summary>
+ /// Gets the authorization information.
+ /// </summary>
+ public AuthorizationInfo AuthorizationInfo { get; }
+
+ /// <summary>
/// Gets the remote end point.
/// </summary>
/// <value>The remote end point.</value>
@@ -49,13 +55,27 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// Sends a message asynchronously.
/// </summary>
+ /// <param name="message">The message.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ /// <exception cref="ArgumentNullException">The message is null.</exception>
+ Task SendAsync(OutboundWebSocketMessage message, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Sends a message asynchronously.
+ /// </summary>
/// <typeparam name="T">The type of websocket message data.</typeparam>
/// <param name="message">The message.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">The message is null.</exception>
- Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken);
+ Task SendAsync<T>(OutboundWebSocketMessage<T> message, CancellationToken cancellationToken);
- Task ProcessAsync(CancellationToken cancellationToken = default);
+ /// <summary>
+ /// Receives a message asynchronously.
+ /// </summary>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task ReceiveAsync(CancellationToken cancellationToken = default);
}
}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessage.cs
index c02bcd70b..92183e792 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessage.cs
@@ -1,4 +1,3 @@
-using System;
using System.Text.Json.Serialization;
using MediaBrowser.Model.Session;
@@ -16,11 +15,6 @@ public abstract class WebSocketMessage
public virtual SessionMessageType MessageType { get; set; }
/// <summary>
- /// Gets or sets the message id.
- /// </summary>
- public Guid MessageId { get; set; }
-
- /// <summary>
/// Gets or sets the server id.
/// </summary>
[JsonIgnore]
diff --git a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
index 6f7ebf156..f7a9ccc44 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
@@ -1,13 +1,13 @@
#nullable disable
-using MediaBrowser.Model.Net;
+using MediaBrowser.Controller.Net.WebSocketMessages;
namespace MediaBrowser.Controller.Net
{
/// <summary>
/// Class WebSocketMessageInfo.
/// </summary>
- public class WebSocketMessageInfo : WebSocketMessage<string>
+ public class WebSocketMessageInfo : InboundWebSocketMessage<string>
{
/// <summary>
/// Gets or sets the connection.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessageOfT.cs b/MediaBrowser.Controller/Net/WebSocketMessageOfT.cs
index 7c35c8010..11e5a6bb2 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessageOfT.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessageOfT.cs
@@ -6,13 +6,12 @@ namespace MediaBrowser.Controller.Net;
/// Class WebSocketMessage.
/// </summary>
/// <typeparam name="T">The type of the data.</typeparam>
-// TODO make this abstract, remove empty ctor.
-public class WebSocketMessage<T> : WebSocketMessage
+public abstract class WebSocketMessage<T> : WebSocketMessage
{
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketMessage{T}"/> class.
/// </summary>
- public WebSocketMessage()
+ protected WebSocketMessage()
{
}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs
index b9f71b922..b3a60199a 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs
@@ -1,20 +1,20 @@
-using System.Collections.Generic;
using System.ComponentModel;
-using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
/// <summary>
/// Activity log entry start message.
+/// Data is the timing data encoded as "$initialDelay,$interval" in ms.
/// </summary>
-public class ActivityLogEntryStartMessage : WebSocketMessage<IReadOnlyCollection<ActivityLogEntry>>, IInboundWebSocketMessage
+public class ActivityLogEntryStartMessage : InboundWebSocketMessage<string>
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryStartMessage"/> class.
+ /// Data is the timing data encoded as "$initialDelay,$interval" in ms.
/// </summary>
- /// <param name="data">Collection of activity log entries.</param>
- public ActivityLogEntryStartMessage(IReadOnlyCollection<ActivityLogEntry> data)
+ /// <param name="data">The timing data encoded as "$initialDelay,$interval".</param>
+ public ActivityLogEntryStartMessage(string data)
: base(data)
{
}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs
index eac129b20..6f65cb2c7 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs
@@ -1,6 +1,4 @@
-using System.Collections.Generic;
using System.ComponentModel;
-using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
@@ -8,17 +6,8 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
/// <summary>
/// Activity log entry stop message.
/// </summary>
-public class ActivityLogEntryStopMessage : WebSocketMessage<IReadOnlyCollection<ActivityLogEntry>>, IInboundWebSocketMessage
+public class ActivityLogEntryStopMessage : InboundWebSocketMessage
{
- /// <summary>
- /// Initializes a new instance of the <see cref="ActivityLogEntryStopMessage"/> class.
- /// </summary>
- /// <param name="data">Collection of activity log entries.</param>
- public ActivityLogEntryStopMessage(IReadOnlyCollection<ActivityLogEntry> data)
- : base(data)
- {
- }
-
/// <inheritdoc />
[DefaultValue(SessionMessageType.ActivityLogEntryStop)]
public override SessionMessageType MessageType => SessionMessageType.ActivityLogEntryStop;
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/InboundKeepAliveMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/InboundKeepAliveMessage.cs
new file mode 100644
index 000000000..fec7cb4e4
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/InboundKeepAliveMessage.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
+
+/// <summary>
+/// Keep alive websocket messages.
+/// </summary>
+public class InboundKeepAliveMessage : InboundWebSocketMessage
+{
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.KeepAlive)]
+ public override SessionMessageType MessageType => SessionMessageType.KeepAlive;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs
index dd2a7145e..bf98470bf 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs
@@ -1,20 +1,19 @@
-using System.Collections.Generic;
using System.ComponentModel;
using MediaBrowser.Model.Session;
-using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
/// <summary>
/// Scheduled tasks info start message.
+/// Data is the timing data encoded as "$initialDelay,$interval" in ms.
/// </summary>
-public class ScheduledTasksInfoStartMessage : WebSocketMessage<IReadOnlyCollection<TaskInfo>>, IInboundWebSocketMessage
+public class ScheduledTasksInfoStartMessage : InboundWebSocketMessage<string>
{
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledTasksInfoStartMessage"/> class.
/// </summary>
- /// <param name="data">Collection of task info.</param>
- public ScheduledTasksInfoStartMessage(IReadOnlyCollection<TaskInfo> data)
+ /// <param name="data">The timing data encoded as $initialDelay,$interval.</param>
+ public ScheduledTasksInfoStartMessage(string data)
: base(data)
{
}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs
index 84e1f0166..f36739c70 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs
@@ -1,24 +1,13 @@
-using System.Collections.Generic;
using System.ComponentModel;
using MediaBrowser.Model.Session;
-using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
/// <summary>
/// Scheduled tasks info stop message.
/// </summary>
-public class ScheduledTasksInfoStopMessage : WebSocketMessage<IReadOnlyCollection<TaskInfo>>, IInboundWebSocketMessage
+public class ScheduledTasksInfoStopMessage : InboundWebSocketMessage
{
- /// <summary>
- /// Initializes a new instance of the <see cref="ScheduledTasksInfoStopMessage"/> class.
- /// </summary>
- /// <param name="data">Collection of task info.</param>
- public ScheduledTasksInfoStopMessage(IReadOnlyCollection<TaskInfo> data)
- : base(data)
- {
- }
-
/// <inheritdoc />
[DefaultValue(SessionMessageType.ScheduledTasksInfoStop)]
public override SessionMessageType MessageType => SessionMessageType.ScheduledTasksInfoStop;
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs
index e35a5dc3a..a40a0c79e 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs
@@ -1,19 +1,19 @@
using System.ComponentModel;
-using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
/// <summary>
/// Sessions start message.
+/// Data is the timing data encoded as "$initialDelay,$interval" in ms.
/// </summary>
-public class SessionsStartMessage : WebSocketMessage<SessionInfo>, IInboundWebSocketMessage
+public class SessionsStartMessage : InboundWebSocketMessage<string>
{
/// <summary>
/// Initializes a new instance of the <see cref="SessionsStartMessage"/> class.
/// </summary>
- /// <param name="data">Session info.</param>
- public SessionsStartMessage(SessionInfo data)
+ /// <param name="data">The timing data encoded as $initialDelay,$interval.</param>
+ public SessionsStartMessage(string data)
: base(data)
{
}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs
index 7e3582d64..288d111c5 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs
@@ -1,5 +1,4 @@
using System.ComponentModel;
-using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Session;
namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
@@ -7,17 +6,8 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
/// <summary>
/// Sessions stop message.
/// </summary>
-public class SessionsStopMessage : WebSocketMessage<SessionInfo>, IInboundWebSocketMessage
+public class SessionsStopMessage : InboundWebSocketMessage
{
- /// <summary>
- /// Initializes a new instance of the <see cref="SessionsStopMessage"/> class.
- /// </summary>
- /// <param name="data">Session info.</param>
- public SessionsStopMessage(SessionInfo data)
- : base(data)
- {
- }
-
/// <inheritdoc />
[DefaultValue(SessionMessageType.SessionsStop)]
public override SessionMessageType MessageType => SessionMessageType.SessionsStop;
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs
index 20ca888e1..8d6e821df 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs
@@ -1,9 +1,8 @@
-namespace MediaBrowser.Controller.Net.WebSocketMessages;
+namespace MediaBrowser.Controller.Net.WebSocketMessages;
/// <summary>
-/// Class representing the list of outbound websocket message types.
-/// Only used in openapi generation.
+/// Inbound websocket message.
/// </summary>
-public class InboundWebSocketMessage : WebSocketMessage
+public class InboundWebSocketMessage : WebSocketMessage, IInboundWebSocketMessage
{
}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessageOfT.cs b/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessageOfT.cs
new file mode 100644
index 000000000..4da5e7d31
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessageOfT.cs
@@ -0,0 +1,26 @@
+#pragma warning disable SA1649 // File name must equal class name.
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages;
+
+/// <summary>
+/// Inbound websocket message with data.
+/// </summary>
+/// <typeparam name="T">The data type.</typeparam>
+public class InboundWebSocketMessage<T> : WebSocketMessage<T>, IInboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InboundWebSocketMessage{T}"/> class.
+ /// </summary>
+ public InboundWebSocketMessage()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InboundWebSocketMessage{T}"/> class.
+ /// </summary>
+ /// <param name="data">The data to send.</param>
+ protected InboundWebSocketMessage(T data)
+ {
+ Data = data;
+ }
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs
index 5650ee4bb..2a098615d 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Activity log created message.
/// </summary>
-public class ActivityLogEntryMessage : WebSocketMessage<IReadOnlyList<ActivityLogEntry>>, IOutboundWebSocketMessage
+public class ActivityLogEntryMessage : OutboundWebSocketMessage<IReadOnlyList<ActivityLogEntry>>
{
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs
index 94ade5e81..ca55340a0 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Force keep alive websocket messages.
/// </summary>
-public class ForceKeepAliveMessage : WebSocketMessage<int>, IOutboundWebSocketMessage
+public class ForceKeepAliveMessage : OutboundWebSocketMessage<int>
{
/// <summary>
/// Initializes a new instance of the <see cref="ForceKeepAliveMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs
index 6c71e73f9..5fbbb0624 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// General command websocket message.
/// </summary>
-public class GeneralCommandMessage : WebSocketMessage<GeneralCommand>, IOutboundWebSocketMessage
+public class GeneralCommandMessage : OutboundWebSocketMessage<GeneralCommand>
{
/// <summary>
/// Initializes a new instance of the <see cref="GeneralCommandMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs
index 6432ae8ef..47417c405 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Library changed message.
/// </summary>
-public class LibraryChangedMessage : WebSocketMessage<LibraryUpdateInfo>, IOutboundWebSocketMessage
+public class LibraryChangedMessage : OutboundWebSocketMessage<LibraryUpdateInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="LibraryChangedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/OutboundKeepAliveMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/OutboundKeepAliveMessage.cs
new file mode 100644
index 000000000..d907dcff9
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/OutboundKeepAliveMessage.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Keep alive websocket messages.
+/// </summary>
+public class OutboundKeepAliveMessage : OutboundWebSocketMessage
+{
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.KeepAlive)]
+ public override SessionMessageType MessageType => SessionMessageType.KeepAlive;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs
index 7f943bda1..86ee2ff90 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Play command websocket message.
/// </summary>
-public class PlayMessage : WebSocketMessage<PlayRequest>, IOutboundWebSocketMessage
+public class PlayMessage : OutboundWebSocketMessage<PlayRequest>
{
/// <summary>
/// Initializes a new instance of the <see cref="PlayMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs
index 804ccb37d..cd6d28cb3 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Playstate message.
/// </summary>
-public class PlaystateMessage : WebSocketMessage<PlaystateRequest>, IOutboundWebSocketMessage
+public class PlaystateMessage : OutboundWebSocketMessage<PlaystateRequest>
{
/// <summary>
/// Initializes a new instance of the <see cref="PlaystateMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs
index 3d7dc5c93..17fd25938 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Plugin installation cancelled message.
/// </summary>
-public class PluginInstallationCancelledMessage : WebSocketMessage<InstallationInfo>, IOutboundWebSocketMessage
+public class PluginInstallationCancelledMessage : OutboundWebSocketMessage<InstallationInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginInstallationCancelledMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs
index 81268007f..3e60198ba 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Plugin installation completed message.
/// </summary>
-public class PluginInstallationCompletedMessage : WebSocketMessage<InstallationInfo>, IOutboundWebSocketMessage
+public class PluginInstallationCompletedMessage : OutboundWebSocketMessage<InstallationInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginInstallationCompletedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs
index 9177f1293..40032f16e 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Plugin installation failed message.
/// </summary>
-public class PluginInstallationFailedMessage : WebSocketMessage<InstallationInfo>, IOutboundWebSocketMessage
+public class PluginInstallationFailedMessage : OutboundWebSocketMessage<InstallationInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginInstallationFailedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs
index e371440a0..28861896f 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Package installing message.
/// </summary>
-public class PluginInstallingMessage : WebSocketMessage<InstallationInfo>, IOutboundWebSocketMessage
+public class PluginInstallingMessage : OutboundWebSocketMessage<InstallationInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginInstallingMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs
index b2994fc95..ca4959119 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Plugin uninstalled message.
/// </summary>
-public class PluginUninstalledMessage : WebSocketMessage<PluginInfo>, IOutboundWebSocketMessage
+public class PluginUninstalledMessage : OutboundWebSocketMessage<PluginInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginUninstalledMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs
index 42dbc3029..41b3cd46a 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Refresh progress message.
/// </summary>
-public class RefreshProgressMessage : WebSocketMessage<Dictionary<string, string>>, IOutboundWebSocketMessage
+public class RefreshProgressMessage : OutboundWebSocketMessage<Dictionary<string, string>>
{
/// <summary>
/// Initializes a new instance of the <see cref="RefreshProgressMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs
index 3f3d9e4c8..a89f19b61 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Restart required.
/// </summary>
-public class RestartRequiredMessage : WebSocketMessage, IOutboundWebSocketMessage
+public class RestartRequiredMessage : OutboundWebSocketMessage
{
/// <inheritdoc />
[DefaultValue(SessionMessageType.RestartRequired)]
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs
index d69662b00..afa36fb72 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Scheduled task ended message.
/// </summary>
-public class ScheduledTaskEndedMessage : WebSocketMessage<TaskResult>, IOutboundWebSocketMessage
+public class ScheduledTaskEndedMessage : OutboundWebSocketMessage<TaskResult>
{
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledTaskEndedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs
index 41a05b0de..c7360779f 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Scheduled tasks info message.
/// </summary>
-public class ScheduledTasksInfoMessage : WebSocketMessage<IReadOnlyList<TaskInfo>>, IOutboundWebSocketMessage
+public class ScheduledTasksInfoMessage : OutboundWebSocketMessage<IReadOnlyList<TaskInfo>>
{
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledTasksInfoMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs
index d4950b8b6..f832c8935 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Series timer cancelled message.
/// </summary>
-public class SeriesTimerCancelledMessage : WebSocketMessage<TimerEventInfo>, IOutboundWebSocketMessage
+public class SeriesTimerCancelledMessage : OutboundWebSocketMessage<TimerEventInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="SeriesTimerCancelledMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs
index 091c10be6..450b4c799 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Series timer created message.
/// </summary>
-public class SeriesTimerCreatedMessage : WebSocketMessage<TimerEventInfo>, IOutboundWebSocketMessage
+public class SeriesTimerCreatedMessage : OutboundWebSocketMessage<TimerEventInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="SeriesTimerCreatedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs
index a465d8b00..8f09c802f 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Server restarting down message.
/// </summary>
-public class ServerRestartingMessage : WebSocketMessage, IOutboundWebSocketMessage
+public class ServerRestartingMessage : OutboundWebSocketMessage
{
/// <inheritdoc />
[DefaultValue(SessionMessageType.ServerRestarting)]
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs
index 0b998a523..485e71b6e 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Server shutting down message.
/// </summary>
-public class ServerShuttingDownMessage : WebSocketMessage, IOutboundWebSocketMessage
+public class ServerShuttingDownMessage : OutboundWebSocketMessage
{
/// <inheritdoc />
[DefaultValue(SessionMessageType.ServerShuttingDown)]
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs
index 4c91e0bca..3504831b8 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using System.ComponentModel;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Session;
@@ -7,13 +8,13 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Sessions message.
/// </summary>
-public class SessionsMessage : WebSocketMessage<SessionInfo>, IOutboundWebSocketMessage
+public class SessionsMessage : OutboundWebSocketMessage<IReadOnlyList<SessionInfo>>
{
/// <summary>
/// Initializes a new instance of the <see cref="SessionsMessage"/> class.
/// </summary>
/// <param name="data">Session info.</param>
- public SessionsMessage(SessionInfo data)
+ public SessionsMessage(IReadOnlyList<SessionInfo> data)
: base(data)
{
}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs
index 17a0fc66e..d0624ec01 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Sync play command.
/// </summary>
-public class SyncPlayCommandMessage : WebSocketMessage<SendCommand>, IOutboundWebSocketMessage
+public class SyncPlayCommandMessage : OutboundWebSocketMessage<SendCommand>
{
/// <summary>
/// Initializes a new instance of the <see cref="SyncPlayCommandMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs
index d145d0e01..6a501aa7e 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Untyped sync play command.
/// </summary>
-public class SyncPlayGroupUpdateCommandMessage : WebSocketMessage<GroupUpdate>, IOutboundWebSocketMessage
+public class SyncPlayGroupUpdateCommandMessage : OutboundWebSocketMessage<GroupUpdate>
{
/// <summary>
/// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs
index 668392c66..47f706e2a 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// Sync play group update command with group info.
/// GroupUpdateTypes: GroupJoined.
/// </summary>
-public class SyncPlayGroupUpdateCommandOfGroupInfoMessage : WebSocketMessage<GroupUpdate<GroupInfoDto>>, IOutboundWebSocketMessage
+public class SyncPlayGroupUpdateCommandOfGroupInfoMessage : OutboundWebSocketMessage<GroupUpdate<GroupInfoDto>>
{
/// <summary>
/// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfGroupInfoMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs
index ec8c3344f..11ddb1e25 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// Sync play group update command with group state update.
/// GroupUpdateTypes: StateUpdate.
/// </summary>
-public class SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage : WebSocketMessage<GroupUpdate<GroupStateUpdate>>, IOutboundWebSocketMessage
+public class SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage : OutboundWebSocketMessage<GroupUpdate<GroupStateUpdate>>
{
/// <summary>
/// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs
index 465363f14..7e73399b1 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// Sync play group update command with play queue update.
/// GroupUpdateTypes: PlayQueue.
/// </summary>
-public class SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage : WebSocketMessage<GroupUpdate<PlayQueueUpdate>>, IOutboundWebSocketMessage
+public class SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage : OutboundWebSocketMessage<GroupUpdate<PlayQueueUpdate>>
{
/// <summary>
/// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs
index b87e9bf71..5b5ccd3ed 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// Sync play group update command with string.
/// GroupUpdateTypes: GroupDoesNotExist (error), LibraryAccessDenied (error), NotInGroup (error), GroupLeft (groupId), UserJoined (username), UserLeft (username).
/// </summary>
-public class SyncPlayGroupUpdateCommandOfStringMessage : WebSocketMessage<GroupUpdate<string>>, IOutboundWebSocketMessage
+public class SyncPlayGroupUpdateCommandOfStringMessage : OutboundWebSocketMessage<GroupUpdate<string>>
{
/// <summary>
/// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfStringMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs
index 0e70549ef..f44fd126b 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Timer cancelled message.
/// </summary>
-public class TimerCancelledMessage : WebSocketMessage<TimerEventInfo>, IOutboundWebSocketMessage
+public class TimerCancelledMessage : OutboundWebSocketMessage<TimerEventInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="TimerCancelledMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs
index 295b3081c..8c1e102eb 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// Timer created message.
/// </summary>
-public class TimerCreatedMessage : WebSocketMessage<TimerEventInfo>, IOutboundWebSocketMessage
+public class TimerCreatedMessage : OutboundWebSocketMessage<TimerEventInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="TimerCreatedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs
index b60769540..6a053643d 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs
@@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// User data changed message.
/// </summary>
-public class UserDataChangedMessage : WebSocketMessage<UserDataChangeInfo>, IOutboundWebSocketMessage
+public class UserDataChangedMessage : OutboundWebSocketMessage<UserDataChangeInfo>
{
/// <summary>
/// Initializes a new instance of the <see cref="UserDataChangedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs
index 6d527be7f..add3f7771 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// User deleted message.
/// </summary>
-public class UserDeletedMessage : WebSocketMessage<Guid>, IOutboundWebSocketMessage
+public class UserDeletedMessage : OutboundWebSocketMessage<Guid>
{
/// <summary>
/// Initializes a new instance of the <see cref="UserDeletedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs
index 99e9a1f91..9a72deae1 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
/// <summary>
/// User updated message.
/// </summary>
-public class UserUpdatedMessage : WebSocketMessage<UserDto>, IOutboundWebSocketMessage
+public class UserUpdatedMessage : OutboundWebSocketMessage<UserDto>
{
/// <summary>
/// Initializes a new instance of the <see cref="UserUpdatedMessage"/> class.
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs
index dba3c8392..178245851 100644
--- a/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs
@@ -1,9 +1,14 @@
+using System;
+
namespace MediaBrowser.Controller.Net.WebSocketMessages;
/// <summary>
-/// Class representing the list of outbound websocket message types.
-/// Only used in openapi generation.
+/// Outbound websocket message.
/// </summary>
-public class OutboundWebSocketMessage : WebSocketMessage
+public class OutboundWebSocketMessage : WebSocketMessage, IOutboundWebSocketMessage
{
+ /// <summary>
+ /// Gets or sets the message id.
+ /// </summary>
+ public Guid MessageId { get; set; } = Guid.NewGuid();
}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessageOfT.cs b/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessageOfT.cs
new file mode 100644
index 000000000..cce331805
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessageOfT.cs
@@ -0,0 +1,33 @@
+#pragma warning disable SA1649 // File name must equal class name.
+
+using System;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages;
+
+/// <summary>
+/// Outbound websocket message with data.
+/// </summary>
+/// <typeparam name="T">The data type.</typeparam>
+public class OutboundWebSocketMessage<T> : WebSocketMessage<T>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OutboundWebSocketMessage{T}"/> class.
+ /// </summary>
+ public OutboundWebSocketMessage()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OutboundWebSocketMessage{T}"/> class.
+ /// </summary>
+ /// <param name="data">The data to send.</param>
+ protected OutboundWebSocketMessage(T data)
+ {
+ Data = data;
+ }
+
+ /// <summary>
+ /// Gets or sets the message id.
+ /// </summary>
+ public Guid MessageId { get; set; } = Guid.NewGuid();
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Shared/KeepAliveMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Shared/KeepAliveMessage.cs
deleted file mode 100644
index 7f636212c..000000000
--- a/MediaBrowser.Controller/Net/WebSocketMessages/Shared/KeepAliveMessage.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.ComponentModel;
-using MediaBrowser.Model.Session;
-
-namespace MediaBrowser.Controller.Net.WebSocketMessages.Shared;
-
-/// <summary>
-/// Keep alive websocket messages.
-/// </summary>
-public class KeepAliveMessage : WebSocketMessage<int>, IInboundWebSocketMessage, IOutboundWebSocketMessage
-{
- /// <summary>
- /// Initializes a new instance of the <see cref="KeepAliveMessage"/> class.
- /// </summary>
- /// <param name="data">The seconds to keep alive for.</param>
- public KeepAliveMessage(int data)
- : base(data)
- {
- }
-
- /// <inheritdoc />
- [DefaultValue(SessionMessageType.KeepAlive)]
- public override SessionMessageType MessageType => SessionMessageType.KeepAlive;
-}
diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
index d1a51c2cf..bb68a3b6d 100644
--- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
+++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
@@ -44,6 +44,12 @@ namespace MediaBrowser.Controller.Playlists
/// <summary>
/// Gets the playlists folder.
/// </summary>
+ /// <returns>Folder.</returns>
+ Folder GetPlaylistsFolder();
+
+ /// <summary>
+ /// Gets the playlists folder for a user.
+ /// </summary>
/// <param name="userId">The user identifier.</param>
/// <returns>Folder.</returns>
Folder GetPlaylistsFolder(Guid userId);
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 16943f6aa..eb5069b06 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs
index b95d00aa3..282aa721e 100644
--- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs
@@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary>
/// <param name="args">The args.</param>
/// <returns>BaseItem.</returns>
- BaseItem ResolvePath(ItemResolveArgs args);
+ BaseItem? ResolvePath(ItemResolveArgs args);
}
public interface IMultiItemResolver
diff --git a/MediaBrowser.Controller/Resolvers/ItemResolver.cs b/MediaBrowser.Controller/Resolvers/ItemResolver.cs
index a6da8384e..5c9dd6f07 100644
--- a/MediaBrowser.Controller/Resolvers/ItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/ItemResolver.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -23,7 +21,7 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary>
/// <param name="args">The args.</param>
/// <returns>`0.</returns>
- protected internal virtual T Resolve(ItemResolveArgs args)
+ protected internal virtual T? Resolve(ItemResolveArgs args)
{
return null;
}
@@ -42,7 +40,7 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary>
/// <param name="args">The args.</param>
/// <returns>BaseItem.</returns>
- public BaseItem ResolvePath(ItemResolveArgs args)
+ public BaseItem? ResolvePath(ItemResolveArgs args)
{
var item = Resolve(args);
diff --git a/MediaBrowser.Controller/Security/IAuthenticationManager.cs b/MediaBrowser.Controller/Security/IAuthenticationManager.cs
index e3d18c8c0..070ab7a85 100644
--- a/MediaBrowser.Controller/Security/IAuthenticationManager.cs
+++ b/MediaBrowser.Controller/Security/IAuthenticationManager.cs
@@ -1,6 +1,4 @@
-#nullable enable
-
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Security
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index 0c4719a0e..53df7133b 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -233,20 +233,6 @@ namespace MediaBrowser.Controller.Session
Task SendRestartRequiredNotification(CancellationToken cancellationToken);
/// <summary>
- /// Sends the server shutdown notification.
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendServerShutdownNotification(CancellationToken cancellationToken);
-
- /// <summary>
- /// Sends the server restart notification.
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendServerRestartNotification(CancellationToken cancellationToken);
-
- /// <summary>
/// Adds the additional user.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
diff --git a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs
index 85b3e6fbd..51c29c7a2 100644
--- a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs
+++ b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs
@@ -14,6 +14,8 @@ namespace MediaBrowser.Controller.Subtitles
public bool IsForced { get; set; }
+ public bool IsHearingImpaired { get; set; }
+
public Stream Stream { get; set; }
}
}