diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding')
7 files changed, 283 insertions, 137 deletions
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 299f294b2..ff91a60a7 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using AsyncKeyedLock; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; @@ -22,7 +23,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.MediaEncoding.Attachments { - public sealed class AttachmentExtractor : IAttachmentExtractor + public sealed class AttachmentExtractor : IAttachmentExtractor, IDisposable { private readonly ILogger<AttachmentExtractor> _logger; private readonly IApplicationPaths _appPaths; @@ -30,8 +31,11 @@ namespace MediaBrowser.MediaEncoding.Attachments private readonly IMediaEncoder _mediaEncoder; private readonly IMediaSourceManager _mediaSourceManager; - private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = - new ConcurrentDictionary<string, SemaphoreSlim>(); + private readonly AsyncKeyedLocker<string> _semaphoreLocks = new(o => + { + o.PoolSize = 20; + o.PoolInitialFill = 1; + }); public AttachmentExtractor( ILogger<AttachmentExtractor> logger, @@ -84,11 +88,7 @@ namespace MediaBrowser.MediaEncoding.Attachments string outputPath, CancellationToken cancellationToken) { - var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false)) { if (!Directory.Exists(outputPath)) { @@ -99,10 +99,6 @@ namespace MediaBrowser.MediaEncoding.Attachments cancellationToken).ConfigureAwait(false); } } - finally - { - semaphore.Release(); - } } public async Task ExtractAllAttachmentsExternal( @@ -111,11 +107,7 @@ namespace MediaBrowser.MediaEncoding.Attachments string outputPath, CancellationToken cancellationToken) { - var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false)) { if (!File.Exists(Path.Join(outputPath, id))) { @@ -131,10 +123,6 @@ namespace MediaBrowser.MediaEncoding.Attachments } } } - finally - { - semaphore.Release(); - } } private async Task ExtractAllAttachmentsInternal( @@ -256,11 +244,7 @@ namespace MediaBrowser.MediaEncoding.Attachments string outputPath, CancellationToken cancellationToken) { - var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false)) { if (!File.Exists(outputPath)) { @@ -271,10 +255,6 @@ namespace MediaBrowser.MediaEncoding.Attachments cancellationToken).ConfigureAwait(false); } } - finally - { - semaphore.Release(); - } } private async Task ExtractAttachmentInternal( @@ -379,5 +359,11 @@ namespace MediaBrowser.MediaEncoding.Attachments var prefix = filename.AsSpan(0, 1); return Path.Join(_appPaths.DataPath, "attachments", prefix, filename); } + + /// <inheritdoc /> + public void Dispose() + { + _semaphoreLocks.Dispose(); + } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 0d1d27ae8..fdca28390 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -45,7 +45,15 @@ namespace MediaBrowser.MediaEncoding.Encoder "mpeg4_cuvid", "vp8_cuvid", "vp9_cuvid", - "av1_cuvid" + "av1_cuvid", + "h264_rkmpp", + "hevc_rkmpp", + "mpeg1_rkmpp", + "mpeg2_rkmpp", + "mpeg4_rkmpp", + "vp8_rkmpp", + "vp9_rkmpp", + "av1_rkmpp" }; private static readonly string[] _requiredEncoders = new[] @@ -82,7 +90,9 @@ namespace MediaBrowser.MediaEncoding.Encoder "av1_vaapi", "h264_v4l2m2m", "h264_videotoolbox", - "hevc_videotoolbox" + "hevc_videotoolbox", + "h264_rkmpp", + "hevc_rkmpp" }; private static readonly string[] _requiredFilters = new[] @@ -116,9 +126,12 @@ namespace MediaBrowser.MediaEncoding.Encoder "libplacebo", "scale_vulkan", "overlay_vulkan", - "hwupload_vaapi", // videotoolbox - "yadif_videotoolbox" + "yadif_videotoolbox", + // rkrga + "scale_rkrga", + "vpp_rkrga", + "overlay_rkrga" }; private static readonly Dictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]> diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4dbefca4b..f86d14fc8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -11,6 +11,7 @@ using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using AsyncKeyedLock; using Jellyfin.Extensions; using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json.Converters; @@ -60,7 +61,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly IServerConfigurationManager _serverConfig; private readonly string _startupOptionFFmpegPath; - private readonly SemaphoreSlim _thumbnailResourcePool; + private readonly AsyncNonKeyedLocker _thumbnailResourcePool; private readonly object _runningProcessesLock = new object(); private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); @@ -116,7 +117,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter()); var semaphoreCount = 2 * Environment.ProcessorCount; - _thumbnailResourcePool = new SemaphoreSlim(semaphoreCount, semaphoreCount); + _thumbnailResourcePool = new(semaphoreCount); } /// <inheritdoc /> @@ -754,8 +755,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { bool ranToCompletion; - await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - try + using (await _thumbnailResourcePool.LockAsync(cancellationToken).ConfigureAwait(false)) { StartProcess(processWrapper); @@ -776,10 +776,6 @@ namespace MediaBrowser.MediaEncoding.Encoder ranToCompletion = false; } } - finally - { - _thumbnailResourcePool.Release(); - } var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; var file = _fileSystem.GetFileInfo(tempExtractPath); @@ -908,8 +904,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { bool ranToCompletion = false; - await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - try + using (await _thumbnailResourcePool.LockAsync(cancellationToken).ConfigureAwait(false)) { StartProcess(processWrapper); @@ -963,10 +958,6 @@ namespace MediaBrowser.MediaEncoding.Encoder StopProcess(processWrapper, 1000); } } - finally - { - _thumbnailResourcePool.Release(); - } var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index a4e8194c1..be63513a7 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <!-- ProjectGuid is only included as a requirement for SonarQube analysis --> <PropertyGroup> @@ -22,6 +22,7 @@ </ItemGroup> <ItemGroup> + <PackageReference Include="AsyncKeyedLock" /> <PackageReference Include="BDInfo" /> <PackageReference Include="libse" /> <PackageReference Include="Microsoft.Extensions.Http" /> diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 629c30060..b532f9a7e 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -742,6 +742,10 @@ namespace MediaBrowser.MediaEncoding.Probing stream.LocalizedExternal = _localization.GetLocalizedString("External"); stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired"); + // Graphical subtitle may have width and height info + stream.Width = streamInfo.Width; + stream.Height = streamInfo.Height; + if (string.IsNullOrEmpty(stream.Title)) { // mp4 missing track title workaround: fall back to handler_name if populated and not the default "SubtitleHandler" diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 459d854bf..8f1cc3f64 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -11,6 +11,7 @@ using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; +using AsyncKeyedLock; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -27,7 +28,7 @@ using UtfUnknown; namespace MediaBrowser.MediaEncoding.Subtitles { - public sealed class SubtitleEncoder : ISubtitleEncoder + public sealed class SubtitleEncoder : ISubtitleEncoder, IDisposable { private readonly ILogger<SubtitleEncoder> _logger; private readonly IApplicationPaths _appPaths; @@ -40,8 +41,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// <summary> /// The _semaphoreLocks. /// </summary> - private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = - new ConcurrentDictionary<string, SemaphoreSlim>(); + private readonly AsyncKeyedLocker<string> _semaphoreLocks = new(o => + { + o.PoolSize = 20; + o.PoolInitialFill = 1; + }); public SubtitleEncoder( ILogger<SubtitleEncoder> logger, @@ -194,36 +198,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles { if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase)) { - string outputFormat; - string outputCodec; + await ExtractAllTextSubtitles(mediaSource, cancellationToken).ConfigureAwait(false); - if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) - || string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase) - || string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = subtitleStream.Codec; - } - else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = "srt"; - } - else - { - // Extract - outputCodec = "srt"; - outputFormat = "srt"; - } - - // Extract + var outputFormat = GetTextSubtitleFormat(subtitleStream); var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat); - await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken) - .ConfigureAwait(false); - return new SubtitleInfo() { Path = outputPath, @@ -318,16 +297,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles } /// <summary> - /// Gets the lock. - /// </summary> - /// <param name="filename">The filename.</param> - /// <returns>System.Object.</returns> - private SemaphoreSlim GetLock(string filename) - { - return _semaphoreLocks.GetOrAdd(filename, _ => new SemaphoreSlim(1, 1)); - } - - /// <summary> /// Converts the text subtitle to SRT. /// </summary> /// <param name="subtitleStream">The subtitle stream.</param> @@ -337,21 +306,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles /// <returns>Task.</returns> private async Task ConvertTextSubtitleToSrt(MediaStream subtitleStream, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken) { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false)) { if (!File.Exists(outputPath)) { await ConvertTextSubtitleToSrtInternal(subtitleStream, mediaSource, outputPath, cancellationToken).ConfigureAwait(false); } } - finally - { - semaphore.Release(); - } } /// <summary> @@ -467,6 +428,203 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.LogInformation("ffmpeg subtitle conversion succeeded for {Path}", inputPath); } + private string GetTextSubtitleFormat(MediaStream subtitleStream) + { + if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)) + { + return subtitleStream.Codec; + } + else + { + return "srt"; + } + } + + private bool IsCodecCopyable(string codec) + { + return string.Equals(codec, "ass", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "ssa", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "srt", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "subrip", StringComparison.OrdinalIgnoreCase); + } + + /// <summary> + /// Extracts all text subtitles. + /// </summary> + /// <param name="mediaSource">The mediaSource.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + private async Task ExtractAllTextSubtitles(MediaSourceInfo mediaSource, CancellationToken cancellationToken) + { + var locks = new List<AsyncKeyedLockReleaser<string>>(); + var extractableStreams = new List<MediaStream>(); + + try + { + var subtitleStreams = mediaSource.MediaStreams + .Where(stream => stream.IsTextSubtitleStream && stream.SupportsExternalStream); + + foreach (var subtitleStream in subtitleStreams) + { + var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream)); + + var @lock = _semaphoreLocks.GetOrAdd(outputPath); + await @lock.SemaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false); + + if (File.Exists(outputPath)) + { + @lock.Dispose(); + continue; + } + + locks.Add(@lock); + extractableStreams.Add(subtitleStream); + } + + if (extractableStreams.Count > 0) + { + await ExtractAllTextSubtitlesInternal(mediaSource, extractableStreams, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Unable to get streams for File:{File}", mediaSource.Path); + } + finally + { + foreach (var @lock in locks) + { + @lock.Dispose(); + } + } + } + + private async Task ExtractAllTextSubtitlesInternal( + MediaSourceInfo mediaSource, + List<MediaStream> subtitleStreams, + CancellationToken cancellationToken) + { + var inputPath = mediaSource.Path; + var outputPaths = new List<string>(); + var args = string.Format( + CultureInfo.InvariantCulture, + "-i {0} -copyts", + inputPath); + + foreach (var subtitleStream in subtitleStreams) + { + var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream)); + var outputCodec = IsCodecCopyable(subtitleStream.Codec) ? "copy" : "srt"; + var streamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream); + + if (streamIndex == -1) + { + _logger.LogError("Cannot find subtitle stream index for {InputPath} ({Index}), skipping this stream", inputPath, subtitleStream.Index); + continue; + } + + Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new FileNotFoundException($"Calculated path ({outputPath}) is not valid.")); + + outputPaths.Add(outputPath); + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0} -an -vn -c:s {1} \"{2}\"", + streamIndex, + outputCodec, + outputPath); + } + + int exitCode; + + using (var process = new Process + { + StartInfo = new ProcessStartInfo + { + CreateNoWindow = true, + UseShellExecute = false, + FileName = _mediaEncoder.EncoderPath, + Arguments = args, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false + }, + EnableRaisingEvents = true + }) + { + _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); + + try + { + process.Start(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error starting ffmpeg"); + + throw; + } + + try + { + await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false); + exitCode = process.ExitCode; + } + catch (OperationCanceledException) + { + process.Kill(true); + exitCode = -1; + } + } + + var failed = false; + + if (exitCode == -1) + { + failed = true; + + foreach (var outputPath in outputPaths) + { + try + { + _logger.LogWarning("Deleting extracted subtitle due to failure: {Path}", outputPath); + _fileSystem.DeleteFile(outputPath); + } + catch (FileNotFoundException) + { + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting extracted subtitle {Path}", outputPath); + } + } + } + else + { + foreach (var outputPath in outputPaths) + { + if (!File.Exists(outputPath)) + { + _logger.LogError("ffmpeg subtitle extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); + failed = true; + continue; + } + + if (outputPath.EndsWith("ass", StringComparison.OrdinalIgnoreCase)) + { + await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false); + } + + _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath); + } + } + + if (failed) + { + throw new FfmpegException( + string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0}", inputPath)); + } + } + /// <summary> /// Extracts the text subtitle. /// </summary> @@ -484,16 +642,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles string outputPath, CancellationToken cancellationToken) { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream); - - try + using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false)) { if (!File.Exists(outputPath)) { + var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream); + var args = _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource); if (subtitleStream.IsExternal) @@ -509,10 +663,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles cancellationToken).ConfigureAwait(false); } } - finally - { - semaphore.Release(); - } } private async Task ExtractTextSubtitleInternal( @@ -728,6 +878,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } + /// <inheritdoc /> + public void Dispose() + { + _semaphoreLocks.Dispose(); + } + #pragma warning disable CA1034 // Nested types should not be visible // Only public for the unit tests public readonly record struct SubtitleInfo diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index 483d0a1d8..ab3eb3298 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -4,11 +4,14 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using AsyncKeyedLock; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -42,7 +45,11 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable private readonly IAttachmentExtractor _attachmentExtractor; private readonly List<TranscodingJob> _activeTranscodingJobs = new(); - private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new(); + private readonly AsyncKeyedLocker<string> _transcodingLocks = new(o => + { + o.PoolSize = 20; + o.PoolInitialFill = 1; + }); /// <summary> /// Initializes a new instance of the <see cref="TranscodeManager"/> class. @@ -223,11 +230,6 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable } } - lock (_transcodingLocks) - { - _transcodingLocks.Remove(job.Path!); - } - job.Stop(); if (delete(job.Path!)) @@ -400,7 +402,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { - var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); + var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId); if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); @@ -624,11 +626,6 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable } } - lock (_transcodingLocks) - { - _transcodingLocks.Remove(path); - } - if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) { _sessionManager.ClearTranscodingInfo(state.Request.DeviceId); @@ -704,21 +701,6 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable } } - /// <inheritdoc /> - public SemaphoreSlim GetTranscodingLock(string outputPath) - { - lock (_transcodingLocks) - { - if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim? result)) - { - result = new SemaphoreSlim(1, 1); - _transcodingLocks[outputPath] = result; - } - - return result; - } - } - private void OnPlaybackProgress(object? sender, PlaybackProgressEventArgs e) { if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) @@ -741,10 +723,23 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable } } + /// <summary> + /// Transcoding lock. + /// </summary> + /// <param name="outputPath">The output path of the transcoded file.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>An <see cref="IDisposable"/>.</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask<IDisposable> LockAsync(string outputPath, CancellationToken cancellationToken) + { + return _transcodingLocks.LockAsync(outputPath, cancellationToken); + } + /// <inheritdoc /> public void Dispose() { _sessionManager.PlaybackProgress -= OnPlaybackProgress; _sessionManager.PlaybackStart -= OnPlaybackProgress; + _transcodingLocks.Dispose(); } } |
