Files
jellyfin-srfPlay/Jellyfin.Plugin.SRFPlay/Api/Models/RecordingEntry.cs
T
dtourolleandClaude Opus 5.5 71a65aab6e
🏗️ Build Plugin / build (push) Successful in 12m44s
Nightly Build / nightly-build (push) Successful in 2m49s
🧪 Test Plugin / test (push) Successful in 11m18s
🚀 Release Plugin / build-and-release (push) Successful in 12m24s
Fix recordings dying mid-stream; record to growing .ts with live buffer playback
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>
2026-09-26 10:14:24 -04:00

115 lines
3.3 KiB
C#

using System;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.SRFPlay.Api.Models;
/// <summary>
/// A scheduled or active recording.
/// </summary>
public class RecordingEntry
{
/// <summary>
/// Gets or sets the unique recording ID.
/// </summary>
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the SRF URN.
/// </summary>
[JsonPropertyName("urn")]
public string Urn { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the business unit this recording belongs to (e.g. "srf", "rts").
/// Used to show recordings under the matching unit's channel.
/// </summary>
[JsonPropertyName("businessUnit")]
public string BusinessUnit { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the title.
/// </summary>
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>
/// Gets or sets the image URL.
/// </summary>
[JsonPropertyName("imageUrl")]
public string? ImageUrl { get; set; }
/// <summary>
/// Gets or sets when the livestream starts.
/// </summary>
[JsonPropertyName("validFrom")]
public DateTime? ValidFrom { get; set; }
/// <summary>
/// Gets or sets when the livestream ends.
/// </summary>
[JsonPropertyName("validTo")]
public DateTime? ValidTo { get; set; }
/// <summary>
/// Gets or sets the recording state.
/// </summary>
[JsonPropertyName("state")]
public RecordingState State { get; set; } = RecordingState.Scheduled;
/// <summary>
/// Gets or sets the output file path.
/// </summary>
[JsonPropertyName("outputPath")]
public string? OutputPath { get; set; }
/// <summary>
/// Gets or sets when the recording actually started.
/// </summary>
[JsonPropertyName("recordingStartedAt")]
public DateTime? RecordingStartedAt { get; set; }
/// <summary>
/// Gets or sets when the recording ended.
/// </summary>
[JsonPropertyName("recordingEndedAt")]
public DateTime? RecordingEndedAt { get; set; }
/// <summary>
/// Gets or sets when the stream disappeared from the API after recording had started.
/// Used to detect that a broadcast ended before its scheduled ValidTo.
/// </summary>
[JsonPropertyName("streamLostAt")]
public DateTime? StreamLostAt { get; set; }
/// <summary>
/// Gets or sets the file size in bytes.
/// </summary>
[JsonPropertyName("fileSizeBytes")]
public long? FileSizeBytes { get; set; }
/// <summary>
/// Gets or sets the error message if recording failed.
/// </summary>
[JsonPropertyName("errorMessage")]
public string? ErrorMessage { get; set; }
/// <summary>
/// Gets or sets when this entry was created.
/// </summary>
[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// Gets a value indicating whether the output file may still grow (recording, or waiting to resume).
/// </summary>
[JsonIgnore]
public bool IsInProgress => State is RecordingState.Recording or RecordingState.WaitingForStream;
}