Files
LlamaCasty/HANDOFF.md
T
gramps ec1091a170 feat(stream): probe the ingest before going live — no more ghost broadcasts (TASK 32·5)
"I forgot the VPN" used to create a broadcast on YouTube that nothing ever pushed to: the app looked
live, YouTube held an empty broadcast, and the creator had to work out what happened. Prevention beats
recovery here, so go-live now asks one question first.

- `Services/IngestProbe.cs` — `IIngestProbe` + `TcpIngestProbe`: parse the RTMP URL, connect to the host
  (rtmp → 1935, rtmps → 443) with a 2 s timeout. Any failure answers "not reachable"; the point is a
  fast unambiguous answer, not a diagnosis.
- `MainViewModel.IngestPreflightAsync` runs **before the first API call** whenever a cached ingest URL
  exists; on a first run it probes once the stream is resolved, still before any broadcast is created.
  A false answer logs, tells the creator ("Nothing was created on YouTube…"), and rolls the session
  back — nothing to clean up on YouTube.
- The probe is a test seam (`IngestProbeOverride`) like the camera and chroma ones.

Tests: `IngestPreflightTests` — unreachable aborts, reachable continues, URL parsing (rtmp/rtmps,
garbage), and the probe against **real sockets**: a listening port answers true, the same port closed
answers false and does so fast. FAST gate: 547 passed, 0 failed, 0 warnings.
2026-11-05 14:56:20 -08:00

37 lines
2.4 KiB
Markdown

# HANDOFF — 2026-11-05 (alert stack + media/chroma fixes)
## State
- `main`. TASK 21 (video layer + soundtrack), TASK 64 (green/blue screen) and TASK 32·4 (fragmented MP4)
are complete; TASK 49 is retired (YouTube owns chat moderation — we just don't paint tombstones).
- **Left panel alert stack** shipped: Steam licence indicator pinned at row 1 (`Entitlement 30 fps ·
Maximum 60 fps`, pin → close once owned, permanent while owned), event rows LIFO beneath, click →
About or the Logs tab with that event's lines painted. Detail: `docs/ai/ui-surface.md`.
- Video layer fixes this session: the preview-pane trigger whitelist (a video layer was invisible), a
quadratic frame copy that capped 1080p playback at 2 fps, and deadline pacing for video *and* audio.
- Tests: 575 green, 1m50s; `FAST=1 ./scripts/verify.sh` = 527 in ~62s.
## Next action
- **32·1 + 32·2 shipped**: blip retry (immediate/+1/+2/+3 s), and the RECONNECTING sign driven by the
status poll (4 s cadence while recovering) with **measured** elapsed/attempt only — the countdown was
dropped after the creator pointed out YouTube's cutoff depends on variables we cannot see. The
`chatEndedEvent` in the chat feed now prompts an immediate status check.
- **32·5 shipped**: ingest pre-flight (TCP, 2 s) aborts go-live before any API call, so a dead
network leaves no ghost broadcast.
- Next:
then 32·2/32·3 (countdown + Back on air) which need YOUR measurement take: pull the network on a
private stream and time how long YouTube holds the broadcast.
- Open loop: `32·4` is proven by construction only — kill ffmpeg mid-recording and play the file.
- Do not push absent a checkpoint request.
## Landmines
- **The preview pane draws frame-source layers from a whitelist of DataTriggers.** A layer type with no
trigger is invisible in the preview while its decode looks healthy (alert box 2026-09-26, scoreboard,
video layer 2026-11-05). Add the trigger with the layer type; pin it with `MediaPreviewDisplayTests`.
- Nothing in the suite drives the real cursor any more; the drag arithmetic is pure (`DragReorderMathTests`).
- Integration tests constructing `MainWindow` must set a temporary `LayoutPathOverride` and clear it in `finally`.
- Media sessions are keyed by **path + look** (`MainViewModel.MediaSessionKey`); never acquire by raw path.
- Bundled FFmpeg stays outside the single-file exe (`ExcludeFromSingleFile="true"`).