Files
LlamaCasty/TASKS/task-41-test-stream-mode.md
gramps bb5dcb4ba2 feat(stream): TASK 41 Test Stream mode — private test broadcast + TEST drawer
Test button next to Start runs the real private-only go-live pipeline as a
session variant (IsTestStream): BeginTestStream -> extracted
StartStreamingSession(alsoRecord:false) shared with the dialog path — no Go
Live dialog, no 'last live' stamp, no recording. Gold top bar + TEST badge +
'End Test' button face; TEST drawer (third right-rail pull-out, three-way
exclusivity) auto-opens once the liveChatId resolves.

Chat tooling: Mock Chat Input sends a REAL liveChat/messages.insert
(YouTubeStreamService.InsertChatMessageAsync) that round-trips the real ~2s
poll and renders through the live overlay; simulated Subscriber/New
Member/Super Chat events inject through the poller's MessageReceived seam
(YouTubeChatService.InjectSimulatedMessage, ChatMessage.IsSimulated) — the
insert API only creates textMessageEvent, so non-text events are local-only
by design (ref: developers.google.com/youtube/v3/live/docs/liveChatMessages/insert).
Scopes youtube + youtube.force-ssl already cover insert; no re-consent.

Good Dog: ytLive.Tests/TestStreamTests.cs — coordinator protocol with fake
HTTP + real-window drawer exclusivity/gate. 0 warnings; 314/314 pass (clean
run; intermittent host abort is the pre-existing WinRT-webcam flake).

Docs ride in the same change: TASKS.md row 41, TASKS/task-41-test-stream-mode.md,
ai.md (YouTube Live API constraints), HANDOFF rewrite.
2026-09-22 09:31:00 -07:00

5.5 KiB

TASK 41 — Test Stream mode: private test broadcast + TEST drawer

Status: ✅ Done (2026-09-22) — one scope-checked work unit, one Good Dog integration test, 314/314 suite green, 0 warnings.

Creator ask (2026-09-22)

"YouTube Test Stream & Chat Integration": a Test/Preview button beside Start that runs a private broadcast via the API, monitors stream/ingest, activates the chat endpoint (liveChatId), and provides a chat simulator: mock chat input (liveChatMessages.insert) + a toggle to inject mock chat, with alert/event triggers for subscriber, membership, and Super Chat.

Locked decisions (user answers, 2026-09-22)

  • Chat-overlay events only — simulated events validate the existing chat overlay styling (text / member memberColor / SuperChat superChatColor rows). Dedicated stinger/TTS alert widgets are NOT part of this task — they stay queued under TASK 3 items 16/17/20.
  • TEST drawer in the right rail — third white tab below YPP, three-way one-open-at-a-time exclusivity, auto-opens when a test broadcast boots.
  • Hybrid simulation — text = REAL liveChat/messages.insert (round-trips the real 2s poll and renders through the live overlay path); member/SuperChat/subscriber = local injection at the poller's MessageReceived seam (marked IsSimulated). YouTube's insert API only creates textMessageEvents — non-text events are local-only by design.

Facts the build sits on (verified in code, 2026-09-22 research pass)

  • The app is already private-only go-live — YouTubeStreamService.CreateBroadcast hardcodes privacyStatus="private" + enableAutoStart/Stop + low latency, no monitor stream (enableMonitorStream=false), and the Go Live dialog is locked to Private. "Test" is therefore a session variant over the real pipeline, not a new broadcast mode.
  • The liveChatId flow already exists: GetBroadcastLiveChatIdAsync → _youtubeChat.Start(liveChatId) in PrepareAndStartLiveAsync — the "chat endpoint activation" the ask describes.
  • OAuth scope already includes youtube + youtube.force-ssl (YouTubeAuthService.cs:18) — enough for liveChat/messages.insert; no re-consent.
  • Chat = polling (YouTubeChatService, 2s): no webhook exists or is planned.
  • No alert system exists anywhere in the codebase (grep verified) — hence the scope decision above.
  • Existing mock-chat preview (ChatOverlayLayer.StartMockChatPreview) is static placeholders, not live-injectable — separate from this task.

What shipped (TASK 41, 2026-09-22)

  • Controls/TopBar.xaml — Test button (secondary style) left of Start; visible only when ShowTestButton (offline ∧ not recording ∧ signed-in); TEST amber badge while IsTestStream (next to the elapsed clock).
  • Controls/PreviewPane.xaml — TEST tab (gold text, below YPP on the rail) + TEST drawer: session caption / liveChatId line, Mock Chat Input (TextBox + Send), SIMULATED EVENTS (Subscriber / New Member / Super Chat buttons), live feedback line, footer "End Test" + the "private, same pipeline as a real live" note; drawer exclusivity wired three-way.
  • Services/TestSessionViewModel.cs (NEW) — the TEST drawer VM: BeginTest(broadcastId, liveChatId) (auto-opens the dock once the liveChatId resolves), SendTestMessageAsync → real insert, Simulate{Subscriber,Member,SuperChat}Command → InjectSimulatedMessage, EndTestCommand → hands to MainViewModel.StopStream, EndTestSilently (idempotent, called on any stop/ rollback). Fully testable — takes YouTubeStreamService + YouTubeChatService via ctor.
  • Services/YouTubeStreamService.InsertChatMessageAsync(liveChatId, text) — POST liveChat/messages?part=snippet; returns string error or null; never throws.
  • Services/YouTubeChatService.InjectSimulatedMessage(ChatMessage) — raises MessageReceived, the exact seam the real poll uses, so simulations flow through the whole overlay pipeline; marked ChatMessage.IsSimulated.
  • ViewModels/MainViewModel.* — IsTestStream (internal setter, test seam like IsRecording); BeginTestStream() → the shared StartStreamingSession(alsoRecord:false) extracted from BeginGoLive (no dialog — a Test needs no confirmation, every broadcast is already private; no BroadcastForm.CaptureGoLive "last live" stamp; no recording). StopStream clears the face and forgets the drawer session. EndButtonText = "End Test" while in a test. Gold top bar + glow (#9c6f1c) identify a rehearsal vs the live red.
  • Drawer exclusivity (this task): opening TEST collapses Stream Settings + YPP and vice versa (wired in MainViewModel.Ypp.cs).

Good Dog — ONE integration test

ytLive.Tests/TestStreamTests.cs — one [Fact], two sections:

  1. Coordinator (no network, RecordingHandler-faked HTTP): idle → BeginTest opens the dock
    • enables chat; Mock Chat Input emits the insert (POST body asserted for the liveChatId + messageText); Subscriber/New Member/Super Chat simulations arrive through MessageReceived marked IsSimulated with the right shapes (member level, $5.00 Super Chat, plain subscriber row); End hands back to the app; EndTestSilently clears.
  2. Real window (temp DB): TEST tab/drawer exist, three-way drawer exclusivity, Test button gate follows sign-in deterministically.

Follow-ups (NOT in this task)

  • Real stinger/TTS alert widgets → TASK 3 items 16/17/20.
  • Test-mode health-panel depth (per-broadcast status inside the drawer) if ever wanted.