dragon-iso/src/TeamsISO.Engine/Pipeline/IRecorderSink.cs

58 lines
2.8 KiB
C#

namespace TeamsISO.Engine.Pipeline;
/// <summary>
/// Tap point for per-ISO output recording. Each <see cref="IsoPipeline"/> can be
/// wired with one recorder; when present, every <see cref="ProcessedFrame"/> that
/// flows from <see cref="FrameProcessor"/> to <see cref="NdiSender"/> is also fed
/// to the recorder for persistence to disk.
///
/// Lifecycle:
/// 1. <see cref="Open"/> is called once when the pipeline starts (or restarts).
/// 2. <see cref="WriteFrame"/> is called for every processed frame, in order.
/// 3. <see cref="Close"/> is called once when the pipeline stops or fails.
///
/// Implementations must be tolerant of out-of-order calls (Close before Open,
/// double Close, WriteFrame after Close) — the supervisor's restart logic can
/// race in unusual ways. The simplest correct implementation is to track an
/// <c>_isOpen</c> flag and short-circuit when not open.
/// </summary>
public interface IRecorderSink : IAsyncDisposable
{
/// <summary>
/// Open the underlying file/encoder and prepare to receive frames. Width/Height
/// match the pipeline's normalized output resolution; FPS is the target framerate
/// (incoming frames may arrive with timing jitter, but the recorder writes at the
/// nominal rate for downstream playback consistency).
/// </summary>
/// <param name="participantDisplayName">Used to derive the output filename.</param>
/// <param name="outputDirectory">Directory under which the recording is created.</param>
/// <param name="width">Frame width in pixels.</param>
/// <param name="height">Frame height in pixels.</param>
/// <param name="fps">Nominal framerate.</param>
void Open(string participantDisplayName, string outputDirectory, int width, int height, double fps);
/// <summary>
/// Write one processed frame. Implementations should not block — if encoding is
/// expensive, queue the frame to a worker thread and return promptly. Returning
/// false means the recorder dropped the frame (disk full, queue overflow); the
/// pipeline carries on regardless so a recorder failure never kills the live ISO.
/// </summary>
bool WriteFrame(ProcessedFrame frame);
/// <summary>
/// Flush and finalize the output. Idempotent.
/// </summary>
void Close();
/// <summary>True between successful <see cref="Open"/> and <see cref="Close"/>.</summary>
bool IsRecording { get; }
/// <summary>
/// Drop a timestamped marker into the recording. Used by the operator to
/// chapter a recording in real time — "host intro starts here", "guest
/// answer", etc. — so post-production can jump to the right moment without
/// scrubbing through the raw stream. The label is free-form; an empty
/// label means "unnamed marker." No-op when not recording.
/// </summary>
void AddMarker(string label);
}