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