alert ticker: draw it inside the Stream Alerts box, not as a top-edge bar
Creator 2026-09-26: "the ticker should appear over the stream alerts video, not over
the entire preview window". It was a GLOBAL 1920x48 bar blitted last at a hardcoded
(0, 0) -- it covered the whole preview width, not the alert video.
The strip is now rendered at the alert box's own size and the box's origin travels on
the frame in a new VideoFrame.Placement ((int X, int Y)?). OriginX/OriginY default to
0 when Placement is null, so every full-canvas overlay (the branding flash) is
unaffected. Every ticker blit site now reads tickerFrame.OriginX/OriginY instead of a
literal 0, 0 -- SceneCompositor x2 and FramePump's static-bake Overlay path -- and
PreviewPane.xaml's AlertTickerElement binds the same rect (AlertTickerLeft/Top/
Width/Height), so the preview cannot drift from the recording the creator is judging.
Why the position rides on the frame rather than in a full-canvas frame: a 1920x1080
overlay is 8.3MB of large-object-heap garbage per tick, ~2.5GB churned over one 10s
alert at 30fps. A box-sized strip is ~370KB. The branding flash does render
full-canvas but RECYCLES one 8MB buffer and bumps Epoch, so it never allocates per
frame; the ticker allocates per call, so it has to stay small.
Also fixed: CopyStrip now clips ROWS to the target height. The pill rasterises at its
natural 48px, so an alert box shorter than that would have written past the end of the
target buffer once the strip became box-sized.
No alert box in the scene now means no ticker at all -- there is no global position
left for it, and this is the guard against the old bar quietly coming back.
Tests (3 new facts, 27 in the file):
ComposedOutput_PutsTheTickerInsideTheAlertBox_NotAtTheTopEdge -- renders through
the real SceneCompositor and asserts the pixels land at (620, 430) and NOT at the
top-left corner. The creator is judging compositing from local recordings, so the
OUTPUT side is the side that needed proving.
ASceneWithNoAlertBox_PublishesNoTicker
ABoxShorterThanTheStrip_ClipsRowsInsteadOfOverrunning
Updated AlertLayer_PublishesARealTickerFrameToThePreviewSink, which asserted the old
1920 width from a box with no geometry (defaulted to 1px); it now uses the product's
own default box (680x200 at 620,430, MainViewModel.Sources.cs:54-57) and pins the
frame size and origin.
Full suite 367/367.
This commit is contained in:
+20
-4
@@ -13,6 +13,21 @@ 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 alert ticker draws inside the Stream Alerts box
|
||||
|
||||
Was a **global 1920px bar pinned to the top edge**; the creator wants it "over the stream alerts
|
||||
video, not over the entire preview window". The strip is now rendered at the alert box's own size
|
||||
and the box's origin travels on the frame in a new `VideoFrame.Placement` (`(int X, int Y)?`, with
|
||||
`OriginX`/`OriginY` defaulting to 0 so full-canvas overlays are unaffected). Every blit site now
|
||||
reads `tickerFrame.OriginX/OriginY` instead of a literal `0, 0` — `SceneCompositor` ×2 plus
|
||||
`FramePump`'s static-bake `Overlay` path — and `PreviewPane.xaml`'s `AlertTickerElement` binds the
|
||||
same rect (`AlertTickerLeft/Top/Width/Height`), so preview and output can't drift.
|
||||
|
||||
Design notes: the position rides on the frame rather than in a full-canvas frame because a
|
||||
1920×1080 overlay is 8.3MB of LOH garbage per tick (~2.5GB over one 10s alert); the strip is
|
||||
~370KB. `CopyStrip` now clips **rows** to the target height — the pill rasterises at 48px, so a
|
||||
shorter box would have written past the buffer. **No alert box in the scene ⇒ no ticker.**
|
||||
|
||||
### 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
|
||||
@@ -115,14 +130,15 @@ The entire implementation is inside `#if DEBUG`. Release compiles to `DataRoot =
|
||||
|
||||
## Next
|
||||
|
||||
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.
|
||||
2. **Multi-instance** (#4) — route `FfmpegLocator._toolsDir` through `InstanceProfile`, and confirm
|
||||
1. **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.
|
||||
3. **Post-session efficacy report** (#5) — roll up `CurrentHealth` (dropped frames, duration, health
|
||||
2. **Post-session efficacy report** (#5) — roll up `CurrentHealth` (dropped frames, duration, health
|
||||
message) when a stream or recording ends.
|
||||
3. **The creator's own eyes on the output** — the ticker-placement and brand-flash changes are proven
|
||||
by unit tests and the compositor, but the final proof is a local recording, since no YouTube
|
||||
private test recordings are being saved.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user