diff --git a/Jellyfin.Plugin.SRFPlay/Services/RecordingService.cs b/Jellyfin.Plugin.SRFPlay/Services/RecordingService.cs index 66946ce..b0d3b37 100644 --- a/Jellyfin.Plugin.SRFPlay/Services/RecordingService.cs +++ b/Jellyfin.Plugin.SRFPlay/Services/RecordingService.cs @@ -34,6 +34,7 @@ public class RecordingService : IRecordingService, IDisposable private readonly ConcurrentDictionary _activeProcesses = new(); private static readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true }; private readonly SemaphoreSlim _persistLock = new(1, 1); + private readonly SemaphoreSlim _processLock = new(1, 1); private List _recordings = new(); private bool _loaded; private bool _disposed; @@ -305,6 +306,25 @@ public class RecordingService : IRecordingService, IDisposable /// public async Task ProcessRecordingsAsync(CancellationToken cancellationToken) + { + // Prevent overlapping scheduler runs from spawning duplicate ffmpeg processes + if (!await _processLock.WaitAsync(0).ConfigureAwait(false)) + { + _logger.LogDebug("ProcessRecordingsAsync already running, skipping"); + return; + } + + try + { + await ProcessRecordingsCoreAsync(cancellationToken).ConfigureAwait(false); + } + finally + { + _processLock.Release(); + } + } + + private async Task ProcessRecordingsCoreAsync(CancellationToken cancellationToken) { await LoadRecordingsAsync().ConfigureAwait(false); @@ -515,6 +535,7 @@ public class RecordingService : IRecordingService, IDisposable } _persistLock.Dispose(); + _processLock.Dispose(); } _disposed = true;