docs: record take-two outcomes — arrival-stamping rule, resolver key fix, top-bar model, TASK 18 verification status
Map corrections: ai.md encoded the webcam lookup bug verbatim (GetLatestFrame(WebcamId)) — code wins, line rewritten; frame-pipeline section gains the rawvideo arrival-stamping lesson + stats log; TASK 18 status = running-app verification IN PROGRESS with take-3 pending on the pump starvation; state-model paragraph matches the new always-Start bar.
This commit is contained in:
@@ -973,7 +973,7 @@ click (volume sliders keep their manual `SetSliderValueFromClick`, harmless dupl
|
||||
|
||||
**Goal:** record the stream output to a local file, with or without simultaneously streaming.
|
||||
|
||||
### Status: ✅ Shipped `a9eb360` (2026-08-29) — code done (incl. manual-rename modal), build 0 warnings, 244/246 tests; remaining = running-app verification
|
||||
### Status: ✅ Shipped `a9eb360` (2026-08-29) — code done (incl. manual-rename modal), build 0 warnings, 244/246 tests; **running-app verification IN PROGRESS (2026-09-01 takes 1–2)**: files now actually land (dead ffmpeg pin re-pinned month-end, locator wrap), webcam-in-output + top-bar reachability + audio-loop NREs fixed en route; take 3 pending with pump stage-timing to resolve the ~2fps producer starvation (time-lapse/short-file/garbled-audio = one root cause, `FramePump stats` will name the stage)
|
||||
|
||||
1. ✅ `EncoderOptions` extended with `StreamEnabled` / `RecordEnabled` / `RecordPath` (independent intent flags)
|
||||
2. ✅ `FfmpegArgs.Build` reworked into per-output blocks (stream `-f flv`, record `-f mp4`) via `AddVideoTags`
|
||||
|
||||
@@ -125,8 +125,9 @@ FfmpegEncoderTests, FramePumpTests, the TASK 8/11/12 audio chain (AudioPipelineT
|
||||
the Socials fediverse-heal roundtrip, AboutHubTests, NotificationAreaIntegrationTests (TASK 24),
|
||||
GlobalHotkeyTests + HotkeyConfigTests (TASK 20), WebcamMenuGateTests (TASK 26), ChatLayerGateTests
|
||||
(TASK 27), BroadcastPullOutTests (TASK 29), DefaultRecordFolder fallback (TASK 30), WebView2ManagerTests
|
||||
(TASK 17), RecordingFileTests + OnAirSignTests (TASK 18), SessionTeardownTests (2026-09-01
|
||||
rollback) — **ZERO known failures as of 2026-09-01. `AudioPipelineTests` 25/25 green in 26ms —
|
||||
(TASK 17), RecordingFileTests + OnAirSignTests (TASK 18), SessionTeardownTests +
|
||||
WebcamOutputKeyTests + GameMeterHonestyTests (2026-09-01 recording-verification fixes) — **ZERO
|
||||
known failures as of 2026-09-01. `AudioPipelineTests` 25/25 green in 26ms —
|
||||
the "known failing" `Mix_HonorsProviderGains…` and the class's notorious STANDALONE HANG shared
|
||||
one root cause: TASK 22's `_delayedMix` (nullable, never initialized) was dereferenced
|
||||
(`delayed.Length` on null) every live-mix tick — a swallowed NRE starved the pipe the tests
|
||||
@@ -539,11 +540,17 @@ until `IsConnected`). The pill is intent; `IsRecording`/`IsLive` are reality —
|
||||
green when a session is actually recording, the ON-AIR dot when actually live.
|
||||
|
||||
- **State model (`MainViewModel`):** pills `RecordPillOn`/`OnAirPillOn` (ON-AIR setter no-ops if
|
||||
`!CanToggleOnAir`), derived `PrimaryButtonText` ("Start Streaming" vs "Start Recording"), `ShowPrimaryStartButton`
|
||||
(offline + [connected OR record-only]), `IsStreamingStart`, `ShowEndStreamButton`, `CanStartSession`,
|
||||
`AccountStatusLightToolTip`. `IsEditMode` now also requires `!IsRecording` (lock scrubbing while recording).
|
||||
`StartSession()` routes: ON-AIR on → GoLive dialog then stream (± record); only REC → `BeginRecordOnly()`
|
||||
starts the pipeline directly, no YouTube dialog.
|
||||
`!CanToggleOnAir`), `ShowPrimaryStartButton` = **always the idle face** (`IsOffline && !IsRecording`,
|
||||
2026-09-01 — the old connected/record-only gate blanked the bar after stopping signed-out),
|
||||
`ShowEndStreamButton`, `CanStartSession`, `AccountStatusLightToolTip`. Sign-in is a **context-menu
|
||||
item on Start** ("Sign in to YouTube" → `SignInCommand`, visible while disconnected) — the standalone
|
||||
Sign In button is gone (TASK 30's single-button rule, completed). `StartSession()` routes: ON-AIR on
|
||||
→ GoLive dialog then stream (± record); REC on or **nothing armed** → `BeginRecordOnly()` (unarmed
|
||||
Start lights the REC pill and records — no dead-end no-ops; local recording needs no account).
|
||||
`IsEditMode` also requires `!IsRecording` (lock scrubbing while recording).
|
||||
**Top bar order (creator spec 2026-09-01):** `[sign light] REC [pill] [sign light] ON-AIR [pill]`
|
||||
— each reality lamp sits before its own intent switch. **Stop ends everything:** `StopStream`
|
||||
clears both pills (see "Stop ends everything; failures roll back" below).
|
||||
- **Filenames** (pure `Services/RecordingFile`): auto-name `ty-<yyyyMMdd>-<HHmm start>-0000.mp4` at start;
|
||||
**rename-on-stop** to `ty-…-<hh2mm2 actual length>.mp4` (`FinalizeRecordingAsync` after the pump stops &
|
||||
the file closes), numeric `-2`/`-3` suffix on collision (`UniquePath`). Length comes from `_liveElapsed`,
|
||||
@@ -666,7 +673,10 @@ devices, no timers).
|
||||
|
||||
The **`FramePump`** (`Services/Encoder/`) is the live frame producer: while live it snapshots the active
|
||||
scene each tick, resolves every element to its latest frame, composites it into the tier's output frame,
|
||||
and paces frames into the encoder at the tier's FPS. **Pattern — everything is a constructor-injected
|
||||
and paces frames into the encoder at the tier's FPS. **rawvideo is stamped by ARRIVAL:** ffmpeg assigns
|
||||
pts from frame order at the declared fps — supply rate = output speed. A starved producer (pump < fps)
|
||||
ships a time-lapse, truncated file with NO error (take-2 lesson, 2026-09-01); the pump therefore logs
|
||||
`FramePump stats: n/target frames per 5s, avg render Xms submit Yms` so a slow stage names itself. **Pattern — everything is a constructor-injected
|
||||
seam:** `Func<Scene?>`, `Func<SceneElement, VideoFrame?>` resolver, `Func<CompositorOptions>`,
|
||||
`Func<EncoderOptions?>`, `Func<IFfmpegEncoder>`, `Action<string>` log, and an injectable pacing delay
|
||||
(default `Task.Delay`; tests inject `Task.Yield`). The pump is free of WPF and of the capture managers.
|
||||
@@ -690,7 +700,10 @@ seam:** `Func<Scene?>`, `Func<SceneElement, VideoFrame?>` resolver, `Func<Compos
|
||||
from a previous session; bitrate/FPS stay on the tier's targets (`ApplyStreamQuality`). The bar shows
|
||||
real encoder values since TASK 9 (2026-08-16) wired the reusable stream URL into `_rtmpUrlProvider`.
|
||||
- **`MainViewModel` owns the resolver** (`ResolveOutputFrame`): `WebcamSceneConfig` →
|
||||
`CameraManager.GetLatestFrame(WebcamId)`, `Source { IsLiveCapture, CaptureKey }` →
|
||||
`CameraManager.GetLatestFrame(DeviceKeyForWebcam(WebcamId))` — **device-keyed** (configs carry the
|
||||
identity GUID; sessions key on the Windows device id — the old direct-GUID lookup returned null
|
||||
forever and the webcam was structurally absent from output; take-2 fix 2026-09-01,
|
||||
`WebcamOutputKeyTests`), `Source { IsLiveCapture, CaptureKey }` →
|
||||
`ScreenCaptureManager.GetLatestFrame(CaptureKey)` (the new accessor mirroring `CameraManager`), image/
|
||||
background → `StaticPixelCache.Get(AssetId)`. `BuildCompositorOptions` rounds the VM's `OutputRect*`
|
||||
doubles to ints — the vertical 607.5 half-pixel crop rounds to a perfectly-centered **608** (`Math.Round`,
|
||||
|
||||
Reference in New Issue
Block a user