branding credit: run in every scene and in recordings, not only while live
Creator 2026-09-26: "the made with llamacasty flash should appear in all scenes,
not just live" and "should also appear in recordings". The presenter was
Start()/Stop()-ed from UpdateLiveVisuals()'s IsLive branch, so a recording made
WITHOUT ever going live carried no credit at all -- the exact case the creator hit
while judging compositing from local recordings.
It is now Start()ed once in the MainViewModel ctor and never stopped on live-state
churn. One start covers every scene, the preview, the stream and the recording,
because go-live and local recording are the SAME FramePump: both
Streaming.Operations.cs:68 and :199 call StartAsync with the same brandFlash:
delegate. Verified by reading both call sites, not assumed -- there is no second
encoder path that needed a "redirect". The licence gate needs no live branch at
all: IsPremium's setter already pushes BrandFlashPresenter.Enabled from anywhere.
Fixed a trap that app-lifetime exposed: Enabled = false stops the presenter's
DispatcherTimer to cut the advertisement mid-credit. When Start() was per-go-live
the next go-live restarted it; with a single app-lifetime Start() nothing would,
so a key entered mid-session would leave the credit dead until the process was
restarted. The setter now restarts the timer when re-enabling while _running.
Tests (12 facts in BrandFlashOutputTests, +2):
Credit_IsComposited_WithNoLiveSession_AndSoARecordingCarriesIt -- drives a real
never-live VM past the 5s first-flash delay and asks the frame the pump would
composite. Uses a new internal AdvanceBrandFlash seam; because Advance only
advances the cadence while the presenter is running, a credit coming out
proves Start() happened at construction.
ADowngradeMidSession_RestartsTheCadenceTimer -- asserts the premium/downgrade
edge decision directly (IsCadenceTimerEnabled) instead of sleeping through a
30-60s interval, which a synchronous test body cannot observe.
Full suite 364/364.
This commit is contained in:
+25
-10
@@ -13,6 +13,24 @@ compositing from **local recordings**. They assumed the live path needed a separ
|
||||
(go-live) and `:199` (record) call that same `StartAsync`. Record+simulcast is one ffmpeg with two
|
||||
outputs. Five items, tracked in `TASKS.md` → "Creator-reported batch".
|
||||
|
||||
### Done: the branding flash is no longer a go-live-only behaviour
|
||||
|
||||
The presenter was `Start()`/`Stop()`-ed from `UpdateLiveVisuals()`'s `IsLive` branch, so a
|
||||
recording made **without ever going live** carried no credit — exactly the creator's complaint.
|
||||
It is now started **once in the `MainViewModel` ctor** and never stopped on live-state churn, so it
|
||||
runs in every scene, reaches the preview, and rides the same `FramePump` into the stream and the
|
||||
recording. The licence gate needs no live branch: `IsPremium`'s setter already pushes
|
||||
`BrandFlashPresenter.Enabled`.
|
||||
|
||||
**Trap that came with it (fixed):** `Enabled = false` stops the presenter's `DispatcherTimer`.
|
||||
When `Start()` was per-go-live, the next go-live restarted it; with one app-lifetime `Start()`
|
||||
nothing would, so a key entered mid-session would leave the credit dead until the process
|
||||
restarted. The setter now restarts the timer when re-enabling while `_running`.
|
||||
|
||||
Test-arithmetic trap, hit twice now: `Advance(5.1)` in one call can never observe a credit —
|
||||
`Advance` opens *and* ages the presentation by the same delta, so it jumps the 2s window. Step at
|
||||
1/30s like the real timer (the `Step` helpers).
|
||||
|
||||
### Done this unit: recording save dialog — Cancel now discards
|
||||
|
||||
`RenameRecordingDialog` was always correct (Enter → `DialogResult=true`, Cancel/X/Escape → `false`).
|
||||
@@ -97,19 +115,16 @@ The entire implementation is inside `#if DEBUG`. Release compiles to `DataRoot =
|
||||
|
||||
## Next
|
||||
|
||||
1. **Ungate the brand flash from `IsLive`** (TASK batch #2) — it must run in every scene and appear
|
||||
in recordings; today it only starts in `UpdateLiveVisuals()`'s live branch, so a recording made
|
||||
without ever going live carries no credit.
|
||||
2. **Confine the ticker to the alert box** (#3) — it is still a global 1920×48 top overlay
|
||||
1. **Confine the ticker to the alert box** (#3) — it is still a global 1920×48 top overlay
|
||||
(`BlitOverlay(…, 0, 0)`); needs a rect + scale/clip decision in both preview and output.
|
||||
3. **Multi-instance** (#4) — route `FfmpegLocator._toolsDir` through `InstanceProfile`, and confirm
|
||||
2. **Multi-instance** (#4) — route `FfmpegLocator._toolsDir` through `InstanceProfile`, and confirm
|
||||
the launch method: `dotnet run` while the first app holds `bin/…/ytLive.exe` fails with MSB3027
|
||||
*before any instance starts* (a build-output lock, not a log conflict). `$env:YTLIVE_INSTANCE=2`
|
||||
+ `dotnet run --no-build` in a second shell is the known-good path.
|
||||
4. **Post-session efficacy report** (#5) — roll up `CurrentHealth` (dropped frames, duration, health
|
||||
3. **Post-session efficacy report** (#5) — roll up `CurrentHealth` (dropped frames, duration, health
|
||||
message) when a stream or recording ends.
|
||||
5. Push the commits when the creator asks.
|
||||
6. Test-console chat UX (queued): don't clear the chat window, 20px right padding on the pull-out
|
||||
4. Push the commits when the creator asks.
|
||||
5. Test-console chat UX (queued): don't clear the chat window, 20px right padding on the pull-out
|
||||
input, Enter inserts a newline.
|
||||
7. Decide the alert-gating copy question above.
|
||||
8. Ship-checklist items live in `TASKS.md` → "1.0 gates".
|
||||
6. Decide the alert-gating copy question above.
|
||||
7. Ship-checklist items live in `TASKS.md` → "1.0 gates".
|
||||
|
||||
Reference in New Issue
Block a user