using ytLive.Services.Audio; using ytLive.Services.Encoder; namespace ytLive.Services; /// /// TASK 21: decodes a local media file (any format ffmpeg handles) into /// s for a MediaSource. This is the /// shape over the shared — the same pipe an alert clip uses /// (TASK 47), so the two features cannot drift apart again in how they decode. /// Video AND the file's own soundtrack are decoded. The manager scales the audio by /// the layer's level/mute and hands it to the mixer's desktop group. /// public sealed class MediaVideoSource : IMediaFrameSource { public event Action? FrameAvailable; public event Action? AudioAvailable; public event Action? Completed; public string Key => _path; public bool Looping { get => _clip.Looping; set => _clip.Looping = value; } private readonly string _path; private readonly VideoClipPipe _clip; public MediaVideoSource( string path, int width, int height, IFfmpegLocator locator, Func processFactory, IFrameRateProbe? frameRateProbe = null, Func? delay = null) { _path = path; _clip = new VideoClipPipe( path, width, height, locator, processFactory, frameRateProbe, delay, audio: true); _clip.FrameAvailable += frame => FrameAvailable?.Invoke(frame); _clip.AudioAvailable += sample => AudioAvailable?.Invoke(sample); _clip.Completed += () => Completed?.Invoke(); } public Task StartAsync(CancellationToken cancellationToken = default) => _clip.StartAsync(cancellationToken); public Task StopAsync() => _clip.StopAsync(); public void Dispose() => _clip.Dispose(); }