Files
2026-07-13 13:13:17 +08:00

77 lines
3.4 KiB
C#

#nullable enable
using System;
using System.IO;
using T3.Core.DataTypes;
namespace T3.Core.Video;
/// <summary>
/// How a video stream trades decode work against seek responsiveness, chosen per operator and passed to
/// <see cref="IVideoPlaybackEngine.RequestFrame"/>.
/// </summary>
public enum VideoPlaybackOptimization
{
/// <summary>
/// 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.
/// </summary>
FastSeeking = 0,
/// <summary>
/// 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.
/// </summary>
PlaybackPerformance = 1,
}
/// <summary>
/// The latest frame and status for one video stream, returned by
/// <see cref="IVideoPlaybackEngine.RequestFrame"/>. <see cref="Produced"/> is true when this call uploaded a
/// new frame; <see cref="IsReady"/> drives the export-gated <c>Playback.OpNotReady</c>.
/// </summary>
public readonly record struct VideoFrameResult(
bool Produced,
Texture2D? Texture,
float Duration,
bool HasCompleted,
bool IsReady,
string? ErrorMessage);
/// <summary>
/// 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 <see cref="VideoPlayback.Engine"/>.
/// </summary>
public interface IVideoPlaybackEngine
{
/// <summary>
/// Drives the stream identified by <paramref name="streamId"/> toward <paramref name="requestedSeconds"/>
/// and returns its latest frame and status. The stream's decoder is created on first request and re-opened
/// if <paramref name="optimization"/> changes.
/// </summary>
VideoFrameResult RequestFrame(Guid streamId, string absolutePath, double requestedSeconds, bool loop,
bool renderingToFile, VideoPlaybackOptimization optimization);
/// <summary>Releases the stream's decoder and cache. Call when the owning operator is disposed.</summary>
void ReleaseStream(Guid streamId);
}
/// <summary>
/// Holds the process-wide <see cref="IVideoPlaybackEngine"/>. 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).
/// </summary>
public static class VideoPlayback
{
public static IVideoPlaybackEngine? Engine { get; set; }
/// <summary>Sibling-file suffix that marks a generated proxy (<c>clip</c> → <c>clip.proxy.mov</c>). Proxy codecs
/// (ProRes/HAP/…) all mux to MOV, so a single suffix identifies every proxy for lookup and cleanup.</summary>
public const string ProxySuffix = ".proxy.mov";
/// <summary>The sibling proxy path for a source video (<c>clip.mp4</c> → <c>clip.proxy.mov</c>). Shared by
/// proxy generation and the engine's substitution so both agree.</summary>
public static string GetProxyPath(string sourcePath)
=> Path.Combine(Path.GetDirectoryName(sourcePath) ?? ".",
Path.GetFileNameWithoutExtension(sourcePath) + ProxySuffix);
}