Compare commits

...

2 Commits

Author SHA1 Message Date
0a2d6a558c no dupplicate recordings
Some checks failed
🏗️ Build Plugin / build (push) Failing after 28s
🧪 Test Plugin / test (push) Failing after 39s
🚀 Release Plugin / build-and-release (push) Failing after 38s
2026-03-07 16:25:01 +01:00
fb539d6a32 Scheduling fixes 2026-03-07 16:24:29 +01:00
2 changed files with 29 additions and 3 deletions

View File

@ -69,7 +69,7 @@ public class RecordingSchedulerTask : IScheduledTask
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerInterval,
IntervalTicks = TimeSpan.FromMinutes(2).Ticks
IntervalTicks = TimeSpan.FromSeconds(30).Ticks
}
};
}

View File

@ -14,6 +14,7 @@ using Jellyfin.Plugin.SRFPlay.Api.Models;
using Jellyfin.Plugin.SRFPlay.Api.Models.PlayV3;
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
using MediaBrowser.Controller;
using MediaBrowser.Controller.MediaEncoding;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.SRFPlay.Services;
@ -29,9 +30,11 @@ public class RecordingService : IRecordingService, IDisposable
private readonly IStreamUrlResolver _streamUrlResolver;
private readonly IMediaCompositionFetcher _mediaCompositionFetcher;
private readonly IServerApplicationHost _appHost;
private readonly IMediaEncoder _mediaEncoder;
private readonly ConcurrentDictionary<string, Process> _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<RecordingEntry> _recordings = new();
private bool _loaded;
private bool _disposed;
@ -45,13 +48,15 @@ public class RecordingService : IRecordingService, IDisposable
/// <param name="streamUrlResolver">The stream URL resolver.</param>
/// <param name="mediaCompositionFetcher">The media composition fetcher.</param>
/// <param name="appHost">The application host.</param>
/// <param name="mediaEncoder">The media encoder for ffmpeg path.</param>
public RecordingService(
ILogger<RecordingService> logger,
ISRFApiClientFactory apiClientFactory,
IStreamProxyService proxyService,
IStreamUrlResolver streamUrlResolver,
IMediaCompositionFetcher mediaCompositionFetcher,
IServerApplicationHost appHost)
IServerApplicationHost appHost,
IMediaEncoder mediaEncoder)
{
_logger = logger;
_apiClientFactory = apiClientFactory;
@ -59,6 +64,7 @@ public class RecordingService : IRecordingService, IDisposable
_streamUrlResolver = streamUrlResolver;
_mediaCompositionFetcher = mediaCompositionFetcher;
_appHost = appHost;
_mediaEncoder = mediaEncoder;
}
private string GetDataFilePath()
@ -300,6 +306,25 @@ public class RecordingService : IRecordingService, IDisposable
/// <inheritdoc />
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);
@ -416,7 +441,7 @@ public class RecordingService : IRecordingService, IDisposable
{
StartInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
FileName = _mediaEncoder.EncoderPath,
Arguments = $"-y -i \"{inputUrl}\" -c copy -movflags +faststart \"{outputPath}\"",
UseShellExecute = false,
RedirectStandardInput = true,
@ -510,6 +535,7 @@ public class RecordingService : IRecordingService, IDisposable
}
_persistLock.Dispose();
_processLock.Dispose();
}
_disposed = true;