# HANDOFF — Session State ## Branch / Commit State **`main`**, working through TASK 21 (media source). Local commits ahead of `origin/main` (not yet pushed — user pushes at agreed checkpoints): - `a1d9751` — docs: ffmpeg decode-contract verification recipe + WSL CLR boundary - `8f49010` — TASK 21 Increment B: FFmpeg rawvideo video decoder -> VideoFrame - `f89b9f9` — docs: default-device audio assumption (README + startup log) - `e8ff4df` — TASK 22: global audio sync offset (positive delay at the mixer out) Prior milestones pushed through `origin/main`: TASK 31 (`3bf053a..670fe3a`), plus the full refactor series (Commit F `3107f92`, Commit G `85893ea`). ## What's In Flight **TASK 21 (media source) — all headless-testable decoder/mechanism slices shipped (slice 1 + 2a + 2b + 3); the remaining UI-picker (acquisition) slice is handed off below — it is a GUI feature to build + verify on native Windows.** Increment A (the `Source` model: `MediaSourceType`, `SourceType.MediaSource`, `MediaPath`/`MediaIsLooping`/`MediaVolume`/`MediaPlaybackState` + LayoutStore persistence) and Increment B (the decoder `MediaVideoSource`) shipped earlier. This session: - Refactored `Services/MediaVideoSource.cs` onto the new `Services/IMediaFrameSource.cs` (`Key`, `FrameAvailable`, `Completed`, `StartAsync`/`StopAsync`; `Start`→`StartAsync`, `FrameReady`→`FrameAvailable`) — behavior preserved, `MediaVideoSourceTests` updated and passing. - Added `Services/MediaVideoSourceManager.cs` — app-wide decode-session ownership refcounted by `MediaPath`, `Func` factory seam, `AcquireAsync`/`ReleaseAsync`/`ReleaseAllAsync`/`GetLatestFrame`, frames coalesced onto the UI dispatcher onto one shared `WriteableBitmap` per file, `MediaFailed` + `PreviewBitmapChanged`. Structural mirror of `ScreenCaptureManager`. - Added `ytLive.Tests/MediaVideoSourceManagerTests.cs` (5 unit + 1 integration: single shared bitmap, coalesce-to-latest). **Verified: all 10 media tests pass (5 manager + 3 reader + 1 decoder integration + 1), build 0 warnings.** Slice 1 step 4 (wire-in) shipped: `_mediaManager` wired in the core ctor (`path => new MediaVideoSource(path, 1920, 1080, new FfmpegLocator(), new FfmpegDecodeProcess())`, dispatched preview), `OnMediaPreviewBitmapChanged` sets `VideoImageSource` on every `IsMediaSource` with a matching `MediaPath`, a `Source { IsMediaSource, MediaPath }` case in `ResolveOutputFrame` feeds `_mediaManager.GetLatestFrame`, `Source.DisplaySource` routes `VideoImageSource` for `MediaSource`, and the manager is disposed on shutdown. Lives in the new `ViewModels/MainViewModel.Media.cs` partial. **Verified: 14/14 media + DisplaySource tests pass, build 0 warnings.** Next (slice 1 step 4's remaining open end): no session acquisition yet — nothing `AcquireAsync`s a media path, so no frames flow until the UI picker (a later slice) starts a session on add and releases on remove. Slice 2a shipped: the native-FPS **probe seam** (not yet wired into pacing). `Services/FfmpegFrameRateParser.cs` (pure — prefers `avg_frame_rate=` then `r_frame_rate=`, rational `N`/`N/M`, unknown→null), `Services/FrameRateProbe.cs` (`IFrameRateProbe` + `FfmpegFrameRateProbe` — derives sibling `ffprobe.exe` from the located ffmpeg dir, reuses the `IDecodeProcess` seam for the ffprobe subprocess text, null if ffprobe absent), and `FfmpegLocator.ProbeFileName` (`ffprobe.exe`) + conditional extraction alongside ffmpeg.exe. Tests: `ytLive.Tests/FfmpegFrameRateParserTests.cs` (6 pure units + 1 probe integration via fake locator/process); `FfmpegLocatorTests` still green — 15/15 pass, build 0 warnings. Slice 2b shipped too: `MediaVideoSource` now takes optional `IFrameRateProbe?` + `Func? delay` seams, probes FPS once in `RunAsync`, and paces each emitted frame by 1/fps (unknown/ absent probe → no pacing). New test `MediaVideoSource_PacesFramesByProbedFps` (fake probe + recording delay, one delay per frame ≈1ms). Media tests 5/5, build 0 warnings. Next (slice 3): loop control — SHIPPED. `MediaVideoSource` takes a `Func` process factory (a single `Process` can't be re-`Start()`ed, so each loop pass creates a fresh decoder) and wraps the decode in a `do…while (Looping)` restart, keyed on the new `IMediaFrameSource.Looping` flag; Production wiring in `MainViewModel` (media factory) now passes the factory AND `FfmpegFrameRateProbe` (closing the slice-2b gap where production had no probe → no pacing). Loop test `MediaVideoSource_LoopsUntilLoopDisabled` (single frame re-emits, `Completed` only when loop cleared) — media tests 12/12 (6 source + 6 manager), build 0 warnings. Open: wiring `Source.MediaIsLooping` into `IMediaFrameSource.Looping` needs a manager-level per-path loop provider — lands with the UI picker (acquisition) slice. **Landmine:** a stale `testhost` can lock `ytLive.Tests`'s `ytLive.dll` and break `dotnet build` of the test project — kill it first (`cmd.exe /c "taskkill /PID N /F"`) on MSB3027. Full-suite vstest hangs headless (RealAppHost/WASAPI) — only filtered pure tests run in WSL; user verifies the GUI suites on native Windows PowerShell (which may also hang if WASAPI startup blocks — pre-existing, not this change). ## Next 1. **(Handed off) UI picker slice** — the last TASK 21 work unit. GUI feature; build + verify natively on Windows. 2. Await user push checkpoint for the pending commits (TASK 22 + TASK 21 slices below). ## UI picker slice — handoff spec (2026-08-31) Nothing `AcquireAsync`s a media path yet, so **no frames flow in a running app** — the decoder, manager, pacing, and loop mechanism are all shipped and unit-tested, but a media Source has no way to start a session. Deliverables, in order: 1. **File picker + add/remove commands** (mirror `MainViewModel.Background.cs`/`Trax.cs` `OpenFileDialog` usage — see `MainViewModel.Trax.cs:68`). A "Media" source action opens an `OpenFileDialog` filtered to video (mp4/mov/avi); on OK set `Source.MediaPath`, add to scene, and **`AcquireAsync(MediaPath)`**; on remove **`ReleaseAllAsync(MediaPath)`**. On `StagedScene` layout loads/teardown, call `ReleaseAllAsync` for every media path no longer present (and acquire new ones) so sessions track the live scene. 2. **Wire `Source.MediaIsLooping` → `IMediaFrameSource.Looping`** — needs a manager-level per-**path** loop provider (see ambiguity: sessions are refcounted per `MediaPath` and shared across scenes, but `MediaIsLooping` is per-`Source`; a scene set → the path's session `Looping` = any live reference has it on, so one scene turning it on while another shares the file is shared behaviour — pick a rule and record it). 3. **Loop toggle + volume slider** in the source context menu / inspector (TASKS 11/10). 4. `MediaVolume`/`MediaPlaybackState` wiring (audio path for media is future work). **Design notes to preserve:** `ResolveOutputFrame` reads `_mediaManager.GetLatestFrame(MediaPath)`; `OnMediaPreviewBitmapChanged` adopts the shared `WriteableBitmap` per path; `MediaFailed` → `OnMediaFailed` (currently `Debug.WriteLine`, surface in UI). Composer scales any frame size. Tmpl: the real-ffmpeg decode/loop/restart path is only exercised natively on Windows — first GUI smoke test should add a short mp4, confirm it plays and previews, then crash further. ## The directive (2026-08-31, user) Rewrite the project into functional components to aid AI retrieval — `Services/ChatOverlayLayer.cs` / `SceneGraph.cs` style (owner-state extraction), not line-count chasing. Audio assumption is now an explicit contract (README "Audio Assumption"): the app uses system defaults, never fights Windows device locking, and does not debug user audio issues — OS's problem, not ours.