using System; using System.Threading; using ytLive.Services.Audio; using ytLive.Services.Encoder; namespace ytLive.Services; /// /// TASK 47: one alert clip's playback — video frames + on-stream audio from the /// same file, both paced to real time. The alert box owns a decoder per event /// (start at play, dispose at drain) — unlike MediaSource's app-wide refcounted /// session, an alert REPLAYS the same clip each event, so a fresh decode per /// play is required. The fade is applied by the alert layer, not here. /// Since TASK 21's audio slice this is a thin wrapper over the shared /// (the same pipe a media layer uses); the alert case /// simply never sets Looping. /// public interface IAlertClipDecoder : IDisposable { /// Starts the decode loop (video + audio); raises the events off the /// calling thread. Paced to real time so playback matches wall clock. void Start(); /// Stops and kills any running decode subprocesses. void Stop(); /// One decoded video frame (box-sized, straight-alpha BGRA). event Action? FrameAvailable; /// One chunk of on-stream audio (48 kHz stereo float). event Action? AudioReady; /// Raised once when both streams have fully played out. event Action? Completed; } /// Default : the shared clip pipe with looping off. public sealed class AlertClipDecoder : IAlertClipDecoder { public event Action? FrameAvailable; public event Action? AudioReady; public event Action? Completed; private readonly VideoClipPipe _clip; public AlertClipDecoder( string path, int width, int height, IFfmpegLocator locator, Func processFactory, IFrameRateProbe? frameRateProbe = null, Func? delay = null, ChromaKeyOptions? chromaKey = null) { _clip = new VideoClipPipe( path, width, height, locator, processFactory, frameRateProbe, delay, audio: true, chromaKey: chromaKey); _clip.FrameAvailable += frame => FrameAvailable?.Invoke(frame); _clip.AudioAvailable += sample => AudioReady?.Invoke(sample); _clip.Completed += () => Completed?.Invoke(); } public void Start() => _ = _clip.StartAsync(); public void Stop() => _ = _clip.StopAsync(); public void Dispose() => _clip.Dispose(); }