fix(stream): TEST-tab chat — resolve liveChatId from snippet, poll until the broadcast is live
The open creator report ('I still cannot post a chat message in the TEST tab',
feedback 'Chat polling couldn't start — Mock Chat Input is disabled') was a single
cause: the liveChatId never resolved. Two YouTube API facts:
1. The id lives in snippet.liveChatId — contentDetails has no such property
(TASK 44 read part=contentDetails: could never resolve).
https://developers.google.com/youtube/v3/live/docs/liveBroadcasts
2. It only exists once the broadcast is LIVE — the official sample lists
broadcastStatus=active, and our fetch ran before the frame pump pushed RTMP
(enableAutoStart flips ready→live). A ready-state list legitimately returns
no id.
https://github.com/youtube/api-samples/blob/master/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/GetLiveChatId.java
Fix: GetBroadcastLiveChatIdAsync reads part=snippet and polls with a bounded
retry (10x/2s); PrepareAndStartLiveAsync starts the frame pump FIRST, then
resolves the id. Chat stays non-fatal. Docs ai.md/TASKS.md/HANDOFF.md +
MyMistakes recipe updated same commit. TASK 44.
Good Dog: ONE integration test (GetBroadcastLiveChatIdAsync_Polls_Snippet_...
) drives a broadcast that gains its id mid-retry and asserts every request used
part=snippet. Gate: clean build 0 warnings, vstest 318/318.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# TASK 44 — TEST-tab chat fix: resolve the liveChatId (snippet + live-timing)
|
||||
|
||||
> Catalog: [`TASKS.md`](../TASKS.md). Status: ✅ **SHIPPED 2026-09-25** — the open
|
||||
> creator report from HANDOFF ("I still cannot post a chat message in the TEST tab")
|
||||
> diagnosed and fixed as its own scoped unit.
|
||||
|
||||
## Provenance
|
||||
|
||||
- **2026-09-24, HANDOFF** filed the creator report as open: "I still cannot post a chat
|
||||
message in the TEST tab." The TEST drawer's Mock Chat Input posts a real
|
||||
`liveChat/messages.insert`; the drawer only enables it when a liveChatId is resolved.
|
||||
- **2026-09-25 diagnosis:** the reported feedback line "Chat polling couldn't start —
|
||||
Mock Chat Input is disabled" is `TestSessionViewModel.BeginTest` running with a **null
|
||||
liveChatId**. That id comes from `YouTubeStreamService.GetBroadcastLiveChatIdAsync`,
|
||||
and two API facts (from the official liveBroadcasts reference + the
|
||||
`GetLiveChatId.java` sample) pin why it never resolved:
|
||||
1. **It lives under `snippet.liveChatId`** — `contentDetails` has no such property at
|
||||
all. The code requested `part=contentDetails` and read `contentDetails.liveChatId`,
|
||||
a path that can NEVER resolve.
|
||||
2. **YouTube only populates it once the broadcast is `live`** (the official sample
|
||||
lists `broadcastStatus=active`). The fetch ran right after broadcast insert
|
||||
(lifecycleStatus `ready`) AND before `_framePump.StartAsync()` pushed the RTMP
|
||||
stream that `enableAutoStart` needs to flip the broadcast to live — so even the
|
||||
correct property would have been empty at fetch time.
|
||||
|
||||
## What shipped
|
||||
|
||||
- `Services/YouTubeStreamService.cs`:
|
||||
- `GetBroadcastLiveChatIdAsync(broadcastId, maxAttempts = 10, delayMs = 2000)` now
|
||||
requests **`part=snippet`** and reads **`snippet.liveChatId`**, and **polls with a
|
||||
bounded retry** until the id appears or the attempts exhaust (never throws). The
|
||||
default retry (~20s) gives the encoder time to connect and the broadcast to go live.
|
||||
- New private `FetchLiveChatIdAsync` holds the single read; the public method owns
|
||||
the retry loop.
|
||||
- `ViewModels/MainViewModel.Streaming.Operations.cs`:
|
||||
- `PrepareAndStartLiveAsync` **starts the frame pump FIRST**, then resolves the
|
||||
liveChatId — the reorder is required for the id to ever exist. Chat remains
|
||||
non-fatal: failure logs and the stream continues.
|
||||
- TEST drawer wiring preserved: `TestSession.BeginTest(broadcastId, liveChatId)`
|
||||
still opens the dock once the id resolves → Mock Chat Input becomes sendable.
|
||||
- Docs: `ai.md` (Test Stream section), `TASKS.md` (row 44), `HANDOFF.md` rewritten.
|
||||
|
||||
## Validation (Good Dog: ONE integration test)
|
||||
|
||||
`ytLive.Tests/YouTubeStreamServiceTests.cs`:
|
||||
`GetBroadcastLiveChatIdAsync_Polls_Snippet_Until_Id_Appears` — a fake broadcast that
|
||||
starts WITHOUT a liveChatId (not yet live) and gains it on a later poll; asserts the
|
||||
retry eventually resolves `Cg0LIVECHAT42`, that it polled ≥3 times, and that every
|
||||
request asked for **snippet** (never contentDetails) — the exact seam that was broken.
|
||||
|
||||
**Gate: clean build 0 warnings; full vstest 318/318 passed.**
|
||||
|
||||
## Reference
|
||||
|
||||
- https://developers.google.com/youtube/v3/live/docs/liveBroadcasts (snippet.liveChatId;
|
||||
contentDetails has no liveChatId)
|
||||
- https://github.com/youtube/api-samples/blob/master/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/GetLiveChatId.java
|
||||
(sample lists broadcastStatus=active — id only present on live broadcasts)
|
||||
Reference in New Issue
Block a user