aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs14
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs7
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs8
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs6
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs15
-rw-r--r--MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs49
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs54
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs15
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs6
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs8
-rw-r--r--MediaBrowser.Controller/Sorting/SortExtensions.cs4
14 files changed, 106 insertions, 88 deletions
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 3c46d53e5..7586b99e7 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1620,12 +1620,17 @@ namespace MediaBrowser.Controller.Entities
return isAllowed;
}
- if (maxAllowedSubRating is not null)
+ if (!maxAllowedRating.HasValue)
{
- return (ratingScore.SubScore ?? 0) <= maxAllowedSubRating && ratingScore.Score <= maxAllowedRating.Value;
+ return true;
}
- return !maxAllowedRating.HasValue || ratingScore.Score <= maxAllowedRating.Value;
+ if (ratingScore.Score != maxAllowedRating.Value)
+ {
+ return ratingScore.Score < maxAllowedRating.Value;
+ }
+
+ return !maxAllowedSubRating.HasValue || (ratingScore.SubScore ?? 0) <= maxAllowedSubRating.Value;
}
public ParentalRatingScore GetParentalRatingScore()
@@ -2048,6 +2053,9 @@ namespace MediaBrowser.Controller.Entities
public virtual async Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken)
=> await LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken).ConfigureAwait(false);
+ public async Task ReattachUserDataAsync(CancellationToken cancellationToken) =>
+ await LibraryManager.ReattachUserDataAsync(this, cancellationToken).ConfigureAwait(false);
+
/// <summary>
/// Validates that images within the item are still on the filesystem.
/// </summary>
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 59a967725..d2a3290c4 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1406,13 +1406,6 @@ namespace MediaBrowser.Controller.Entities
.Where(e => query is null || UserViewBuilder.FilterItem(e, query))
.ToArray();
- if (this is BoxSet && (query.OrderBy is null || query.OrderBy.Count == 0))
- {
- realChildren = realChildren
- .OrderBy(e => e.PremiereDate ?? DateTime.MaxValue)
- .ToArray();
- }
-
var childCount = realChildren.Length;
if (result.Count < limit)
{
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index b32b64f5d..076a59292 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -125,6 +125,8 @@ namespace MediaBrowser.Controller.Entities
public string? Name { get; set; }
+ public bool? UseRawName { get; set; }
+
public string? Person { get; set; }
public Guid[] PersonIds { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 1d1fb2c39..3999c3e07 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -124,7 +124,7 @@ namespace MediaBrowser.Controller.Entities.Movies
if (sortBy == ItemSortBy.Default)
{
- return items;
+ return items;
}
return LibraryManager.Sort(items, user, new[] { sortBy }, SortOrder.Ascending);
@@ -136,6 +136,12 @@ namespace MediaBrowser.Controller.Entities.Movies
return Sort(children, user).ToArray();
}
+ public override IReadOnlyList<BaseItem> GetChildren(User user, bool includeLinkedChildren, out int totalItemCount, InternalItemsQuery query = null)
+ {
+ var children = base.GetChildren(user, includeLinkedChildren, out totalItemCount, query);
+ return Sort(children, user).ToArray();
+ }
+
public override IReadOnlyList<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query, out int totalCount)
{
var children = base.GetRecursiveChildren(user, query, out totalCount);
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 427c2995b..6396631f9 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -214,7 +214,7 @@ namespace MediaBrowser.Controller.Entities.TV
query.AncestorWithPresentationUniqueKey = null;
query.SeriesPresentationUniqueKey = seriesKey;
query.IncludeItemTypes = new[] { BaseItemKind.Season };
- query.OrderBy = new[] { (ItemSortBy.IndexNumber, SortOrder.Ascending) };
+ query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
if (user is not null && !user.DisplayMissingEpisodes)
{
@@ -247,6 +247,10 @@ namespace MediaBrowser.Controller.Entities.TV
query.AncestorWithPresentationUniqueKey = null;
query.SeriesPresentationUniqueKey = seriesKey;
+ if (query.OrderBy.Count == 0)
+ {
+ query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
+ }
if (query.IncludeItemTypes.Length == 0)
{
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index fcc5ed672..df1c98f3f 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -282,6 +282,14 @@ namespace MediaBrowser.Controller.Library
Task UpdateItemAsync(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
/// <summary>
+ /// Reattaches the user data to the item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>A task that represents the asynchronous reattachment operation.</returns>
+ Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken);
+
+ /// <summary>
/// Retrieves the item.
/// </summary>
/// <param name="id">The id.</param>
@@ -652,5 +660,12 @@ namespace MediaBrowser.Controller.Library
/// This exists so plugins can trigger a library scan.
/// </remarks>
void QueueLibraryScan();
+
+ /// <summary>
+ /// Add mblink file for a media path.
+ /// </summary>
+ /// <param name="virtualFolderPath">The path to the virtualfolder.</param>
+ /// <param name="pathInfo">The new virtualfolder.</param>
+ public void CreateShortcut(string virtualFolderPath, MediaPathInfo pathInfo);
}
}
diff --git a/MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs b/MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs
index 0de5f198d..6da398129 100644
--- a/MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs
+++ b/MediaBrowser.Controller/LibraryTaskScheduler/LimitedConcurrencyLibraryScheduler.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
+using System.Threading.Channels;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Hosting;
@@ -29,7 +30,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
/// </summary>
private readonly Lock _taskLock = new();
- private readonly BlockingCollection<TaskQueueItem> _tasks = new();
+ private readonly Channel<TaskQueueItem> _tasks = Channel.CreateUnbounded<TaskQueueItem>();
private volatile int _workCounter;
private Task? _cleanupTask;
@@ -77,7 +78,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
lock (_taskLock)
{
- if (_tasks.Count > 0 || _workCounter > 0)
+ if (_tasks.Reader.Count > 0 || _workCounter > 0)
{
_logger.LogDebug("Delay cleanup task, operations still running.");
// tasks are still there so its still in use. Reschedule cleanup task.
@@ -144,9 +145,9 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
_deadlockDetector.Value = stopToken.TaskStop;
try
{
- foreach (var item in _tasks.GetConsumingEnumerable(stopToken.GlobalStop.Token))
+ while (!stopToken.GlobalStop.Token.IsCancellationRequested)
{
- stopToken.GlobalStop.Token.ThrowIfCancellationRequested();
+ var item = await _tasks.Reader.ReadAsync(stopToken.GlobalStop.Token).ConfigureAwait(false);
try
{
var newWorkerLimit = Interlocked.Increment(ref _workCounter) > 0;
@@ -187,7 +188,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
await item.Worker(item.Data).ConfigureAwait(true);
}
- catch (System.Exception ex)
+ catch (Exception ex)
{
_logger.LogError(ex, "Error while performing a library operation");
}
@@ -242,7 +243,7 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
};
}).ToArray();
- if (ShouldForceSequentialOperation())
+ if (ShouldForceSequentialOperation() || _deadlockDetector.Value is not null)
{
_logger.LogDebug("Process sequentially.");
try
@@ -264,35 +265,14 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
for (var i = 0; i < workItems.Length; i++)
{
var item = workItems[i]!;
- _tasks.Add(item, CancellationToken.None);
+ await _tasks.Writer.WriteAsync(item, CancellationToken.None).ConfigureAwait(false);
}
- if (_deadlockDetector.Value is not null)
- {
- _logger.LogDebug("Nested invocation detected, process in-place.");
- try
- {
- // we are in a nested loop. There is no reason to spawn a task here as that would just lead to deadlocks and no additional concurrency is achieved
- while (workItems.Any(e => !e.Done.Task.IsCompleted) && _tasks.TryTake(out var item, 200, _deadlockDetector.Value.Token))
- {
- await ProcessItem(item).ConfigureAwait(false);
- }
- }
- catch (OperationCanceledException) when (_deadlockDetector.Value.IsCancellationRequested)
- {
- // operation is cancelled. Do nothing.
- }
-
- _logger.LogDebug("process in-place done.");
- }
- else
- {
- Worker();
- _logger.LogDebug("Wait for {NoWorkers} to complete.", workItems.Length);
- await Task.WhenAll([.. workItems.Select(f => f.Done.Task)]).ConfigureAwait(false);
- _logger.LogDebug("{NoWorkers} completed.", workItems.Length);
- ScheduleTaskCleanup();
- }
+ Worker();
+ _logger.LogDebug("Wait for {NoWorkers} to complete.", workItems.Length);
+ await Task.WhenAll([.. workItems.Select(f => f.Done.Task)]).ConfigureAwait(false);
+ _logger.LogDebug("{NoWorkers} completed.", workItems.Length);
+ ScheduleTaskCleanup();
}
/// <inheritdoc/>
@@ -304,13 +284,12 @@ public sealed class LimitedConcurrencyLibraryScheduler : ILimitedConcurrencyLibr
}
_disposed = true;
- _tasks.CompleteAdding();
+ _tasks.Writer.Complete();
foreach (var item in _taskRunners)
{
await item.Key.CancelAsync().ConfigureAwait(false);
}
- _tasks.Dispose();
if (_cleanupTask is not null)
{
await _cleanupTask.ConfigureAwait(false);
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index b5d14e94b..0025080cc 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -19,9 +19,7 @@
<ItemGroup>
<PackageReference Include="BitFaster.Caching" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
- <PackageReference Include="System.Threading.Tasks.Dataflow" />
</ItemGroup>
<ItemGroup>
@@ -36,7 +34,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net9.0</TargetFramework>
+ <TargetFramework>net10.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
index 20f51ddb7..10f2f04af 100644
--- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -43,8 +43,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public bool AllowAudioStreamCopy { get; set; }
- public bool BreakOnNonKeyFrames { get; set; }
-
/// <summary>
/// Gets or sets the audio sample rate.
/// </summary>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 843590a1f..11eee1a37 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -2914,8 +2914,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (time > 0)
{
- // For direct streaming/remuxing, we seek at the exact position of the keyframe
- // However, ffmpeg will seek to previous keyframe when the exact time is the input
+ // For direct streaming/remuxing, HLS segments start at keyframes.
+ // However, ffmpeg will seek to previous keyframe when the exact frame time is the input
// Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos.
// This will help subtitle syncing.
var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCodec(state.OutputVideoCodec);
@@ -2932,17 +2932,16 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.IsVideoRequest)
{
- var outputVideoCodec = GetVideoEncoder(state, options);
- var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
-
- // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
- // Disable -noaccurate_seek on mpegts container due to the timestamps issue on some clients,
- // but it's still required for fMP4 container otherwise the audio can't be synced to the video.
- if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)
- && state.TranscodingType != TranscodingJobType.Progressive
- && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
- && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
+ // If we are remuxing, then the copied stream cannot be seeked accurately (it will seek to the nearest
+ // keyframe). If we are using fMP4, then force all other streams to use the same inaccurate seeking to
+ // avoid A/V sync issues which cause playback issues on some devices.
+ // When remuxing video, the segment start times correspond to key frames in the source stream, so this
+ // option shouldn't change the seeked point that much.
+ // Important: make sure not to use it with wtv because it breaks seeking
+ if (state.TranscodingType is TranscodingJobType.Hls
+ && string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase)
+ && (IsCopyCodec(state.OutputVideoCodec) || IsCopyCodec(state.OutputAudioCodec))
+ && !string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase))
{
seekParam += " -noaccurate_seek";
}
@@ -6359,6 +6358,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ // Block unsupported H.264 Hi422P and Hi444PP profiles, which can be encoded with 4:2:0 pixel format
+ if (string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ if (videoStream.Profile.Contains("4:2:2", StringComparison.OrdinalIgnoreCase)
+ || videoStream.Profile.Contains("4:4:4", StringComparison.OrdinalIgnoreCase))
+ {
+ // VideoToolbox on Apple Silicon has H.264 Hi444PP and theoretically also has Hi422P
+ if (!(hardwareAccelerationType == HardwareAccelerationType.videotoolbox
+ && RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64)))
+ {
+ return null;
+ }
+ }
+ }
+
var decoder = hardwareAccelerationType switch
{
HardwareAccelerationType.vaapi => GetVaapiVidDecoder(state, options, videoStream, bitDepth),
@@ -7039,8 +7053,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
{
- var accelType = GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
- return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
+ // there's an issue about AV1 AFBC on RK3588, disable it for now until it's fixed upstream
+ return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
}
}
@@ -7069,7 +7083,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
#nullable disable
- public void TryStreamCopy(EncodingJobInfo state)
+ public void TryStreamCopy(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
{
@@ -7086,8 +7100,14 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ var preventHlsAudioCopy = state.TranscodingType is TranscodingJobType.Hls
+ && state.VideoStream is not null
+ && !IsCopyCodec(state.OutputVideoCodec)
+ && options.HlsAudioSeekStrategy is HlsAudioSeekStrategy.TranscodeAudio;
+
if (state.AudioStream is not null
- && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
+ && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)
+ && !preventHlsAudioCopy)
{
state.OutputAudioCodec = "copy";
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 43680f5c0..7d0384ef2 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -515,21 +515,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public int HlsListSize => 0;
- public bool EnableBreakOnNonKeyFrames(string videoCodec)
- {
- if (TranscodingType != TranscodingJobType.Progressive)
- {
- if (IsSegmentedLiveStream)
- {
- return false;
- }
-
- return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec);
- }
-
- return false;
- }
-
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
var count = MediaSource.GetStreamCount(type);
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index 3d288b9f8..2702e3bc0 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -27,10 +27,9 @@ namespace MediaBrowser.Controller.MediaEncoding
using (target)
using (reader)
{
- while (!reader.EndOfStream && reader.BaseStream.CanRead)
+ string line = await reader.ReadLineAsync().ConfigureAwait(false);
+ while (line is not null && reader.BaseStream.CanRead)
{
- var line = await reader.ReadLineAsync().ConfigureAwait(false);
-
ParseLogLine(line, state);
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
@@ -50,6 +49,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
await target.FlushAsync().ConfigureAwait(false);
+ line = await reader.ReadLineAsync().ConfigureAwait(false);
}
}
}
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 00c492742..bf80b7d0a 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -36,6 +36,14 @@ public interface IItemRepository
Task SaveImagesAsync(BaseItem item, CancellationToken cancellationToken = default);
/// <summary>
+ /// Reattaches the user data to the item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>A task that represents the asynchronous reattachment operation.</returns>
+ Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken);
+
+ /// <summary>
/// Retrieves the item.
/// </summary>
/// <param name="id">The id.</param>
diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs
index f9c0d39dd..ec8878dcb 100644
--- a/MediaBrowser.Controller/Sorting/SortExtensions.cs
+++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs
@@ -1,7 +1,9 @@
#pragma warning disable CS1591
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using Jellyfin.Extensions;
@@ -9,7 +11,7 @@ namespace MediaBrowser.Controller.Sorting
{
public static class SortExtensions
{
- private static readonly AlphanumericComparator _comparer = new AlphanumericComparator();
+ private static readonly StringComparer _comparer = StringComparer.Create(CultureInfo.InvariantCulture, CompareOptions.NumericOrdering);
public static IEnumerable<T> OrderByString<T>(this IEnumerable<T> list, Func<T, string> getName)
{