Files
LlamaCasty/TASKS/task-41-test-stream-mode.md
T

5.4 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)

  • Test mode is always Private — it remains a session variant over the real pipeline, not a new broadcast mode. The creator clarified 2026-11-04 that normal Release Go Live offers a visibility choice; development/test runs stay forced Private.
  • 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.