A recording of a live event crashed partway through: when its short Akamai token expired, the proxy's stale-alias check swapped the recording's mapping to the most recently registered deferred stream, of any content. Segment requests were then built against the wrong CDN path, returned 403, and ffmpeg exited, splitting the recording into several files. Proxy: - Never swap URN-backed livestreams; they refresh themselves from the API. Only consider swap candidates with the same URN. Recording lifecycle: - Complete a recording once its stream has been gone from the API for 5 minutes (broadcasts can end before ValidTo). - Fail a recording still waiting for its stream after ValidTo (or 12h after ValidFrom) instead of retrying forever. - Wait for a DVR window whose start= is still in the future instead of starting ffmpeg against a 400/404. - Keep the original start time across restarts; don't mark a recording failed on shutdown cancellation. Growing .ts recordings: - ffmpeg writes MPEG-TS (-c copy -copyts) to stdout and the plugin appends it to one file per recording, so restarts continue the same file and timeline. - The Recordings folder lists any recording with a file on disk, not only completed ones. - In-progress recordings are exposed as infinite streams reading through a new endpoint that follows the growing file (same pattern as Jellyfin's DVR), so they can be watched from the start like a livestream buffer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
3.0 KiB
C#
79 lines
3.0 KiB
C#
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Jellyfin.Plugin.SRFPlay.Api.Models;
|
|
using Jellyfin.Plugin.SRFPlay.Api.Models.PlayV3;
|
|
|
|
namespace Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
|
|
|
/// <summary>
|
|
/// Service for managing sport livestream recordings.
|
|
/// </summary>
|
|
public interface IRecordingService
|
|
{
|
|
/// <summary>
|
|
/// Gets upcoming sport livestreams that can be recorded.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns>List of upcoming sport livestreams.</returns>
|
|
Task<IReadOnlyList<PlayV3TvProgram>> GetUpcomingScheduleAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Schedules a livestream for recording.
|
|
/// </summary>
|
|
/// <param name="urn">The SRF URN to record.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns>The created recording entry.</returns>
|
|
Task<RecordingEntry> ScheduleRecordingAsync(string urn, CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>
|
|
/// Cancels a scheduled recording.
|
|
/// </summary>
|
|
/// <param name="recordingId">The recording ID.</param>
|
|
/// <returns>True if cancelled.</returns>
|
|
bool CancelRecording(string recordingId);
|
|
|
|
/// <summary>
|
|
/// Stops an active recording.
|
|
/// </summary>
|
|
/// <param name="recordingId">The recording ID.</param>
|
|
/// <returns>True if stopped.</returns>
|
|
bool StopRecording(string recordingId);
|
|
|
|
/// <summary>
|
|
/// Gets all recordings by state.
|
|
/// </summary>
|
|
/// <param name="stateFilter">Optional state filter.</param>
|
|
/// <returns>List of matching recording entries.</returns>
|
|
IReadOnlyList<RecordingEntry> GetRecordings(RecordingState? stateFilter = null);
|
|
|
|
/// <summary>
|
|
/// Gets a single recording by ID.
|
|
/// </summary>
|
|
/// <param name="recordingId">The recording ID.</param>
|
|
/// <returns>The recording entry, or null if not found.</returns>
|
|
RecordingEntry? GetRecording(string recordingId);
|
|
|
|
/// <summary>
|
|
/// Gets recordings that have an output file on disk, finished or still in progress.
|
|
/// </summary>
|
|
/// <returns>Playable recordings, newest first.</returns>
|
|
IReadOnlyList<RecordingEntry> GetPlayableRecordings();
|
|
|
|
/// <summary>
|
|
/// Deletes a completed recording (entry and optionally the file).
|
|
/// </summary>
|
|
/// <param name="recordingId">The recording ID.</param>
|
|
/// <param name="deleteFile">Whether to delete the file too.</param>
|
|
/// <returns>True if deleted.</returns>
|
|
bool DeleteRecording(string recordingId, bool deleteFile = true);
|
|
|
|
/// <summary>
|
|
/// Checks scheduled recordings and starts/stops them as needed.
|
|
/// Called periodically by the scheduler task.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <returns>A task representing the async operation.</returns>
|
|
Task ProcessRecordingsAsync(CancellationToken cancellationToken = default);
|
|
}
|