Merge branch 'master' into trickplay

This commit is contained in:
Nick
2023-10-18 19:27:05 -07:00
462 changed files with 8749 additions and 10207 deletions

View File

@@ -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; }
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Linq;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;

View File

@@ -1,4 +1,3 @@
using System.Threading;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;

View File

@@ -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;
}
}
}
}

View File

@@ -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,18 +65,10 @@ 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);
/// <summary>
/// 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>
@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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;
var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
foreach (var mediaPath in clone.PathInfos)
{
_libraryOptions[path] = options;
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()
{

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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; }

View File

@@ -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()

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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 });
}

View File

@@ -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>
@@ -42,15 +38,6 @@ namespace MediaBrowser.Controller
/// <value>The name of the friendly.</value>
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>
@@ -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.

View File

@@ -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();
}

View File

@@ -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()

View File

@@ -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()
{

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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; }
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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)
{

View File

@@ -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
},

View File

@@ -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>
@@ -40,12 +41,26 @@ namespace MediaBrowser.Controller.Net
/// <value>The state.</value>
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>
IPAddress? RemoteEndPoint { get; }
/// <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>
@@ -54,8 +69,13 @@ namespace MediaBrowser.Controller.Net
/// <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);
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Text.Json.Serialization;
using MediaBrowser.Model.Session;
@@ -15,11 +14,6 @@ public abstract class WebSocketMessage
/// </summary>
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>

View File

@@ -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.

View File

@@ -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()
{
}

View File

@@ -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)
{
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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)
{
}

View File

@@ -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;

View File

@@ -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)
{
}

View File

@@ -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;

View File

@@ -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
{
}

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)]

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)]

View File

@@ -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)]

View File

@@ -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)
{
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -1,6 +1,4 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Security

View File

@@ -232,20 +232,6 @@ namespace MediaBrowser.Controller.Session
/// <returns>Task.</returns>
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>

View File

@@ -14,6 +14,8 @@ namespace MediaBrowser.Controller.Subtitles
public bool IsForced { get; set; }
public bool IsHearingImpaired { get; set; }
public Stream Stream { get; set; }
}
}