881addb5b4
- IMediaFrameSource gains bool Looping. - MediaVideoSource ctor takes Func<IDecodeProcess> processFactory instead of a single IDecodeProcess: a System.Diagnostics.Process can't be re-Start()ed, so each loop pass creates a fresh decoder. Decode wrapped in do-while(Looping): restart on natural EOF instead of raising Completed. - Production wiring (MainViewModel media factory): passes the process factory AND FfmpegFrameRateProbe -- closes the slice-2b gap where production had no probe and therefore no pacing. - Tests: loop test (single frame re-emits across passes, Completed only when loop cleared); fakes updated for the new interface member. Media tests 12/12, build 0 warnings. Wiring Source.MediaIsLooping into the flag needs a manager-level per-path loop provider -> lands with the UI-picker (acquisition) slice. Derivative reference: looping media by restarting decode on EOF, standard in playback/overlay tooling (OBS media source repeat).
99 lines
6.0 KiB
Markdown
99 lines
6.0 KiB
Markdown
# 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) — slice 1 step 3 shipped (the session-manager + interface); next open work is the VM wire-in (slice 1 step 4).**
|
|
|
|
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<string, IMediaFrameSource?>` 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<TimeSpan,CancellationToken,Task>? 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<IDecodeProcess>` 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. Commit slice 3 (this unit): loop mechanism + process factory + probe wiring + tests + docs.
|
|
2. UI picker slice: acquire/release media sessions on add/remove AND wire `Source.MediaIsLooping` → `IMediaFrameSource.Looping` (manager-level per-path loop provider).
|
|
3. Slice 3: loop control (restart decode on `Completed` when `MediaIsLooping`).
|
|
4. UI picker slice: acquire/release media sessions on add/remove (the remaining slice-1-step-4 open end).
|
|
5. Await user push checkpoint for the pending TASK 22/21 commits.
|
|
|
|
## 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.
|