#nullable enable
using System;
using System.IO;
using T3.Core.DataTypes;
namespace T3.Core.Video;
///
/// How a video stream trades decode work against seek responsiveness, chosen per operator and passed to
/// .
///
public enum VideoPlaybackOptimization
{
///
/// Software decode with a RAM frame cache: instant scrub-back / loop re-seeks and smooth playback (no
/// per-frame GPU read-back), keeping the GPU free for the editor. Heavier CPU cost on large frames.
///
FastSeeking = 0,
///
/// Zero-copy GPU (hardware) decode, no cache: smoothest continuous playback — best for large/4K frames — but
/// random seeks re-decode the GOP. Falls back to software when the codec/GPU profile is unsupported.
///
PlaybackPerformance = 1,
}
///
/// The latest frame and status for one video stream, returned by
/// . is true when this call uploaded a
/// new frame; drives the export-gated Playback.OpNotReady.
///
public readonly record struct VideoFrameResult(
bool Produced,
Texture2D? Texture,
float Duration,
bool HasCompleted,
bool IsReady,
string? ErrorMessage);
///
/// Process-wide video-playback service. The implementation lives in the operator-loaded video assembly; this
/// Core interface lets operators (and, later, the editor) reach it without depending on FFmpeg. One instance
/// owns every decode stream and its frame cache. Obtain it via .
///
public interface IVideoPlaybackEngine
{
///
/// Drives the stream identified by toward
/// and returns its latest frame and status. The stream's decoder is created on first request and re-opened
/// if changes.
///
VideoFrameResult RequestFrame(Guid streamId, string absolutePath, double requestedSeconds, bool loop,
bool renderingToFile, VideoPlaybackOptimization optimization);
/// Releases the stream's decoder and cache. Call when the owning operator is disposed.
void ReleaseStream(Guid streamId);
}
///
/// Holds the process-wide . The video assembly sets it when it first loads;
/// it stays null in a project that never uses video (the video assembly is never loaded).
///
public static class VideoPlayback
{
public static IVideoPlaybackEngine? Engine { get; set; }
/// Sibling-file suffix that marks a generated proxy (clip → clip.proxy.mov). Proxy codecs
/// (ProRes/HAP/…) all mux to MOV, so a single suffix identifies every proxy for lookup and cleanup.
public const string ProxySuffix = ".proxy.mov";
/// The sibling proxy path for a source video (clip.mp4 → clip.proxy.mov). Shared by
/// proxy generation and the engine's substitution so both agree.
public static string GetProxyPath(string sourcePath)
=> Path.Combine(Path.GetDirectoryName(sourcePath) ?? ".",
Path.GetFileNameWithoutExtension(sourcePath) + ProxySuffix);
}