TASK 21 slice 1 step 3: IMediaFrameSource + MediaVideoSourceManager

Refactor MediaVideoSource onto IMediaFrameSource (Key/FrameAvailable/Completed/
StartAsync/StopAsync, renaming Start->StartAsync, FrameReady->FrameAvailable),
behavior preserved. Add MediaVideoSourceManager: app-wide decode-session owner
refcounted by MediaPath with Func<string,IMediaFrameSource?> factory seam,
Acquire/Release/ReleaseAll/GetLatestFrame, coalescing each file's frames onto
the UI dispatcher onto one shared WriteableBitmap; MediaFailed + PreviewBitmapChanged.

Mirrors ScreenCaptureManager (screen-capture session ownership + dispatcher
coalescing), per the codebase precedent and derivative-work rule.

Tests: MediaVideoSourceManagerTests (5 unit + 1 integration: single shared
bitmap, coalesce-to-latest); MediaVideoSourceTests updated for renames.
10/10 media tests pass, build 0 warnings. Docs (TASKS/HANDOFF/ai.md) updated.
This commit is contained in:
2026-08-31 19:22:02 -07:00
parent a1d9751a6f
commit 480ead0fc7
8 changed files with 557 additions and 56 deletions
+21
View File
@@ -0,0 +1,21 @@
namespace ytLive.Services;
/// <summary>
/// TASK 21: a media file decoder exposed to <see cref="MediaVideoSourceManager"/>.
/// Mirrors <c>IScreenCaptureSource</c>/<c>ICameraFrameSource</c> so the manager
/// (and tests) treat a local video file like any other frame source: start/stop
/// and a per-frame <see cref="FrameAvailable"/> event consumers coalesce.
/// </summary>
public interface IMediaFrameSource : IDisposable
{
string Key { get; }
/// <summary>Raised (on a worker thread) for each decoded frame.</summary>
event Action<VideoFrame>? FrameAvailable;
/// <summary>Raised when the file finishes decoding (natural EOF).</summary>
event Action? Completed;
Task StartAsync(CancellationToken cancellationToken = default);
Task StopAsync();
}