Files
LlamaCasty/HANDOFF.md
T
gramps c01206fb8a feat: web sources frame-captured via composition (CoreWebView2CompositionController → Windows.Graphics.Capture); PNG poll + CaptureScheduler deleted
The ~30Hz CapturePreviewAsync PNG poll capped real cadence at ~20Hz
(35–165ms full-HD encode+decode), so a 60fps widget still juddered at ~1/6
speed. Replaced polling with frame-driven capture of the composition
controller's root visual — the mechanism WebView2CompositionControl and
Flutter's webview_windows use (graphics_context.cc captures the root
surface_ visual via CreateGraphicsCaptureItemFromVisual; reference:
github.com/microsoft/Windows.UI.Composition.WinUI / flutter-internal
webview_windows). Frames now arrive at the renderer's own pace; capture
memory is epoch'd ring reuse + one crop-sized shared WriteableBitmap.

New Services/WebCaptureFrameSource.cs owns GraphicsCaptureItem + free-
threaded Direct3D11CaptureFramePool + session (Straight alpha readback,
per-frame FindContentBounds → CropBounds). WebView2Manager reworked around
per-session composition controllers + one UI-thread Compositor created via
the CoreMessaging CreateDispatcherQueueController P/Invoke (the 19041
projection lacks CreateOnCurrentThread); internal seam ctor
(Dispatcher, Func<string,IScreenCaptureSource>?) for hermetic tests.
CaptureScheduler.cs deleted; the three SetCaptureInterval cadence hooks
removed; InitWebView2() moved from MainWindow ctor to Loaded (a parent
HWND must exist for the composition controller); the hidden WebViewHostPanel
overlay deleted. TransparentBackgroundScript unchanged.

Tests: WebView2ManagerTests reworked — 4 control-size + scheduler tests
dropped, FindContentBounds tests moved to WebCaptureFrameSource, ONE
integration test (Frames_PublishCroppedPreview_And_CoalesceToLatest_CarryingCropBounds)
drives the seam with a FakeWebSource + background-STA DispatcherPump.
Suite 293/293, 0 warnings.

NOTE: composition path NOT yet verified on a device — the take is the
next step. Web work committed locally only (no push per standing rule).

Docs same-commit: ai.md Slice 14 + supersede marker on Slice 11, HANDOFF,
MyMistakes (CoreMessaging DQ + namespace-landmine recipe), TASK 17,
Controls/ViewModels/Services indexes.
2026-09-14 16:14:00 -07:00

96 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HANDOFF — 2026-09-14 (web capture REDESIGNED: composition-capture, PNG polling + CaptureScheduler deleted — NOT yet verified on device, NOT pushed)
## Branch / Commit State
`main` HEAD = **`b22d08e`** (signed audio-sync, already pushed). Working tree **DIRTY** with the
web-frame-capture redesign (below). Commits for it land **LOCALLY only** — the web work was never
pushed; the user greenlights pushes at checkpoints.
## ⚠️ Branding (2026-09-14, creator-corrected): product = **llamacasty**, internals = ytLive
The product is **llamacasty**; the repo path, csproj `AssemblyName`/`RootNamespace`, DB/log paths
(`%APPDATA%\ytLlive\...`), and most code names are the legacy **ytLive/ytLlive**. User-facing
language must say "llamacasty"; code/assembly/repo names stay ytLive. Full detail in
`ai.md` → Brand → "Product name vs repo/assembly branding".
## 💥 The web-capture take history (why this redesign exists)
The recording was 60fps but web widgets ran at ~1/6 speed: capture was a `CapturePreviewAsync` PNG
poll; every full-HD encode+decode cost **35–165ms** so the session budget capped real cadence at
~20Hz — a 60fps widget still juddered. Slice 11's `CaptureScheduler` (30Hz) was the wrong lever:
the fix is frame-driven, not faster polling.
## 🔬 IN FLIGHT (this dirty tree, NOT committed, NOT pushed) — composition capture
**What:** web sources now render through `CoreWebView2CompositionController` into
`Windows.Graphics.Capture` (the Flutter `webview_windows` / `WebView2CompositionControl`
mechanism) — frame-driven at the renderer's pace instead of polling PNGs.
**Pipeline (per session):** `CreateCoreWebView2CompositionControllerAsync(appHwnd)` →
`RootVisualTarget` = a `RelativeSizeAdjustment=1,1` child under a 1920×1080 root `ContainerVisual`
on one `Compositor` (CoreMessaging DQ P/Invoke recipe — see ai.md Slice 14 / MyMistakes) →
`GraphicsCaptureItem.CreateFromVisual(root)` → free-threaded `Direct3D11CaptureFramePool` (2
buffers) → `SoftwareBitmap.CreateCopyFromSurfaceAsync` (`BitmapAlphaMode.Straight`) →
per-frame `FindContentBounds` bbox → `CropBounds` → epoch'd ring → dispatcher-coalesced crop copy
into ONE shared `WriteableBitmap`.
**Files:** NEW `Services/WebCaptureFrameSource.cs`; reworked `Services/WebView2Manager.cs` (internal
seam ctor `(Dispatcher, Func<string, IScreenCaptureSource>?)` for hermetic tests); DELETED
`Services/CaptureScheduler.cs`; `MainViewModel.Web.cs` (`InitWebView2()` no-arg),
`MainViewModel.Streaming.Operations.cs` (three `SetCaptureInterval` hooks gone — verified zero
remaining), `MainWindow.xaml(.cs)` (`InitWebView2()` moved ctor→**Loaded**; `WebViewHostPanel`
overlay deleted). `TransparentBackgroundScript` unchanged.
**Tests:** reworked `ytLive.Tests/WebView2ManagerTests.cs` — dropped the 4 control-size tests +
`CaptureScheduler_Drops…`; `FindContentBounds` tests moved to `WebCaptureFrameSource`; the ONE
integration test (`Frames_PublishCroppedPreview_And_CoalesceToLatest_CarryingCropBounds`) drives the
seam with `FakeWebSource` + background-STA `DispatcherPump`: crop-sized preview published once →
`CropBounds` + `IsOpaque=false` → back-to-back frames coalesce to latest. `RoundClipInteractionTests`
comment updated (WebViewHostPanel gone). Suite **293/293 green**, app build **0 warnings**. Docs
slant (ai.md Slice 14, HANDOFF, MyMistakes, TASK 17, Controls/ViewModels/Services indexes) updated in
the same working set.
## ⚠️ Open items on this change (before it is PUSHABLE)
- **Device verification:** the composition path has never run against a real widget. Verify ~60fps
web animation in a take (also: the OBS de-throttle flags stay; hidden-page JS throttling is fixed
by them, capture pacing is now renderer-driven).
- **No push yet** — web work is commit-local until the user says push.
## ✅ SHIPPED + PUSHED — signed audio sync, −500..+500 (`b22d08e`)
Positive = delay the mix (audio AHEAD — the `AudioSyncDelay` line, live-reactive). Negative =
**advance** (audio BEHIND): OBS-style "eat the stream head" — `AudioMixer.StartLive` arms
`_advanceSamplesRemaining = |N| ms`, `LiveLoopAsync` skips `min(budget, mixBuffer.Length)` off each
write head while it lasts. Slider relabeled **AUDIO SYNC**, `−500..500`, locked while
live/recording (`IsEditMode`). One regression test:
`StartLive_NegativeOffset_AdvancesAudio_ByDroppingTheStreamHead`.
Take `ty-20260914-1128-0000-2.mp4`: **offset ≈ +0.54s** (collapsed from +2.11/2.22s; residual was
the baked-in `SyncOffsetMs=300`, now 0 — confirmed via sqlite3). Both streams `start_time=0`.
## Open threads
- **Verify signed sync negative direction on device** (clap take with a negative offset).
- **Audio-silence verification** — fixed (`724af14`); creator heard real audio.
- **Webcam MJPG missing / ~10–14Hz**, layer SortOrder, truncation-with-dynamic-scenes — queued.
- **Sync control user-doc tutorial — REQUIRED before 1.0** (creator directive; TASK 22 file).
- **Device verify the composition web capture (this change).**
## Landmines
- testhost shares startup.log — filter by time.
- `cmd.exe /c "taskkill /F /IM ytLive.exe"` (WSL double-slashes mangle) before rebuilds.
- Build/tests: **Windows dotnet host** (`/mnt/c/Program Files/dotnet/dotnet.exe`). 0 warnings —
only `./scripts/verify.sh "<files>"`'s clean build counts.
- ffmpeg/ffprobe: `/mnt/c/Program Files/Krita (x64)/bin/` with Windows paths.
- `MyMistakes.md` has the **audio/video sync measurement recipe** AND the **composition-capture
CoreMessaging DQ recipe (2026-09-14)** — grep before re-deriving.
- sqlite3 lives at `/home/gramps/android-sdk/platform-tools/sqlite3` (WSL) for the DB at
`/mnt/c/Users/gramp/AppData/Roaming/ytLlive/ytLlive.db`.
## Next step
From this dirty state: run `./scripts/verify.sh "<all changed files>"`, review the diff, commit
locally (web work — NO push, per standing directive). Then a device take to verify ~60fps web
animation, then present for push review.