TASK 5 items 1-3: reusable stream, health banner, docs refresh
- Reusable stream: GetOrCreateReusableStreamAsync, cached via LayoutStore, bound at broadcast insert - Health monitoring: GetStreamHealthAsync polls liveStreams.status, report-by-exception banner - Go-live order: ensure stream → create broadcast → then start pump (URL must exist first) - Full broadcast form scoped (TASK 5 items 6-7) in TASKS.md + ai.md - TASK 23 trimmed to technical scope, TASK 26 added (marcom/launch kit) - Confidential files: MONETIZATION.md + MARCOM.md (gitignored, not committed) - .gitignore updated for confidential business files - HANDOFF.md updated with session state and next steps
This commit is contained in:
+94
-11
@@ -5,11 +5,44 @@
|
||||
> a problem. Conventions: [`schema.md`](schema.md). Rewrite this file at session
|
||||
> end, compaction, or any interruption.
|
||||
|
||||
## Session state (last updated: 2026-08-15)
|
||||
## Session state (last updated: 2026-08-16)
|
||||
|
||||
- **Branch:** `main`, tracking `origin/main`. Working tree: the **TASK 24 polish batch** + the **game
|
||||
audio bar always-visible change** + **TASK 25 (master limiter)** are committed + pushed. Local
|
||||
branches `social-bar`/`webcam-validation` untouched (no secrets).
|
||||
- **Branch:** `main`, tracking `origin/main`. Working tree: the **TASK 5 reusable-stream slice** is
|
||||
committed + pushed (details below). Local branches `social-bar`/`webcam-validation` untouched (no secrets).
|
||||
- **TASK 5 — REUSABLE STREAM + HEALTH BANNER — SHIPPED 2026-08-16 (items 1–3).**
|
||||
- **Items 1–2 (reusable stream):** `_rtmpUrlProvider` now yields a real RTMP URL.
|
||||
`YouTubeStreamService` gains `GetOrCreateReusableStreamAsync` (lists `liveStreams?mine=true`,
|
||||
reuses the existing `cdn.isReusable` stream, inserts once on first use with
|
||||
`resolution=variable`/`frameRate=variable`); `CreateBroadcast(..., streamId)` binds at insert via
|
||||
`boundStreamId` + the one-click v3 flags (`enableMonitorStream=false`, `latencyPreference=low`).
|
||||
Cached via `LayoutStore` Settings (`SaveReusableStream`/`LoadReusableStream`). **Go-live order
|
||||
changed** (the pump reads the URL once at start — `FramePump.StartAsync`): `BeginGoLive` →
|
||||
`PrepareAndStartLiveAsync` = ensure stream → create+bind broadcast → THEN start the pump.
|
||||
- **Item 3 (report-by-exception health):** `GetStreamHealthAsync(streamId)` polls
|
||||
`liveStreams?part=status`; the pure `Services/StreamHealthReporter.BannerFor` decides (null text on
|
||||
good/ok/noData/info-only; warning/error issue → its type text, error beats warning). The VM polls
|
||||
every **30s while live** (`_healthPollTimer`, first poll right after go-live, stopped on End/Error
|
||||
via `UpdateLiveVisuals`; failures log-only). UI = full-width banner strip under the top bar,
|
||||
`HealthIssueBanner` + `HealthIssueBackground` (amber `#b8860b` warning / dark red `#8f1f1f` error),
|
||||
hidden by `NotNullToVis`; cleared in `ResetHealth`.
|
||||
- **Tests (8 new this branch, 207 total, 0 warnings):** 3 service units (parse, good→no issues,
|
||||
no-session→null) + 4 `StreamHealthReporterTests` + the ONE integration test
|
||||
`GetStreamHealthAsync_Report_By_Exception_Banner_Only_On_Warning_Or_Error` (real service JSON parse
|
||||
→ real reporter: good → no banner, error issue → banner text + error color).
|
||||
- **CONFIDENTIAL files created (gitignored):** `MONETIZATION.md` (pricing, billing research, unlock
|
||||
mechanics) and `MARCOM.md` (launch marketing strategy, positioning, platform strategy). These are
|
||||
NOT committed to the public repo. `Helpers/OAuthCredentials.cs` was already gitignored.
|
||||
- **TASK 5 items 6-7 scoped (not built):** visibility unlock (remove temporary "always Private" enforcement)
|
||||
and full broadcast form (Core + Advanced tabs with all YouTube API-supported fields). These are the
|
||||
next technical tasks after live chat (item 4) and error handling (item 5).
|
||||
- **TASK 23 trimmed** to technical scope only (billing decision, unlock mechanism, bug report, alerts gating).
|
||||
Business details moved to `MONETIZATION.md`.
|
||||
- **TASK 26 added** — marcom/launch kit. Business details moved to `MARCOM.md`. Queued after all v1 features.
|
||||
- **Backlog updated** — removed v0.3 (stream scheduling, not needed for casual streamers). Multi-destination
|
||||
restreaming noted as "congrats, you're ready for OBS" moment.
|
||||
- **Resume point (next branch):** **TASK 5 item 4 — live chat** (`liveChat/messages` poll, right-panel
|
||||
render, Super Chat + membership badges). Then item 5 (error handling), item 6 (visibility unlock),
|
||||
item 7 (full broadcast form), then TASK 23 (billing + unlock + support).
|
||||
- **TASK 25 — MASTER LIMITER — COMMITTED + PUSHED 2026-08-15.** Queued from the TRAX discussion: the
|
||||
live mix summed mic + loopback with no ceiling, so hot gains could pass 0 dBFS and clip the AAC
|
||||
encode. New pure `Services/Audio/MasterLimiter.cs` (−1 dBFS ceiling, instant attack per frame,
|
||||
@@ -38,13 +71,63 @@
|
||||
- **Secrets scrubbed from git history.** The DO token + passwords were purged via `git filter-branch`
|
||||
+ `git gc --prune=now --aggressive` (all-refs scan = 0 hits); values still exist in chat — keep
|
||||
treating as **compromised**; rotate the DO API token.
|
||||
- **Shipped, all pushed:** TASK 24 polish batch, TASK 21 (creator-hub About, `3d92bd0`), TASK 8
|
||||
(meter +10 dB), TASK 4 ship step 7 (one-click go-live + private-only), TASK 9 audio milestone
|
||||
(`6d71ace`), secrets cleanup (`f2f6401`), monetization docs (`86fcd86`).
|
||||
- **Resume point (next branch):** **TASK 5** — reusable stream → `_rtmpUrlProvider` (the last blocker
|
||||
before go-live actually encodes + pushes). Then TASK 23 (billing + unlock + support).
|
||||
Optional, not queued: rewriting the healed entry's `ProfileUrl` to
|
||||
`https://mastodon.llamachile.tube/@gramps` (user must say the word).
|
||||
- **Shipped, all pushed:** TASK 5 items 1–3 (reusable stream + health banner, 2026-08-16), TASK 25
|
||||
master limiter, TASK 24 polish batch, TASK 21 (creator-hub About, `3d92bd0`), TASK 8 (meter +10 dB),
|
||||
TASK 4 ship step 7 (one-click go-live + private-only), TASK 9 audio milestone (`6d71ace`), secrets
|
||||
cleanup (`f2f6401`), monetization docs (`86fcd86`).
|
||||
|
||||
## TASK 5 — reusable stream + health banner 2026-08-16 (what changed, items 1–3)
|
||||
|
||||
The recorded resume point: the "last blocker" was that go-live ran the visual flow but never pushed —
|
||||
`_rtmpUrlProvider` returned null, so `FramePump.StartAsync` skipped the encoder entirely.
|
||||
|
||||
- **`Services/YouTubeStreamService.cs`:** new `GetOrCreateReusableStreamAsync()` lists
|
||||
`liveStreams?mine=true` and reuses the existing `cdn.isReusable` stream, inserting once per channel
|
||||
only on first use (`cdn.resolution=variable`, `cdn.frameRate=variable`, `isReusable=true`) and
|
||||
returning a `ReusableStream(Id, IngestionAddress, StreamName)` record (`RtmpUrl` =
|
||||
`ingestionAddress/streamName`). `CreateBroadcast` takes an optional `streamId` and binds at insert
|
||||
via `contentDetails.boundStreamId` (no second bind round-trip) and now always sends the full
|
||||
one-click v3 flag set (`enableAutoStart/Stop`, `enableMonitorStream=false`, `latencyPreference=low`).
|
||||
The old per-broadcast `BindStream` (throwaway 1080p/60fps stream + `contentDetails.streamId`) is
|
||||
**gone**.
|
||||
- **`Services/LayoutStore.cs`:** `SaveReusableStream`/`LoadReusableStream` — the Settings key/value
|
||||
table caches the stream id/address/name so the pump has its RTMP URL at startup, no round-trip.
|
||||
- **`ViewModels/MainViewModel.cs`:** `_rtmpUrlProvider` returns `_reusableStreamUrl` (loaded from the
|
||||
cache in the ctor, set fresh on go-live). `BeginGoLive` → `PrepareAndStartLiveAsync`: ensure the
|
||||
stream → cache it → create the broadcast bound to it → **then** `_framePump.StartAsync()`. The
|
||||
ordering matters because the pump reads `_encoderOptions()` once at startup.
|
||||
- **Tests (6 new, 199 total, 0 warnings):** `GetOrCreateReusableStreamAsync_Reuses_Existing_Reusable_Stream`
|
||||
(list path, no POST), `..._Creates_When_None_Exists` (insert path with variable/isReusable),
|
||||
`CreateBroadcast_With_StreamId_Binds_Reusable_Stream_At_Insert` (boundStreamId + new v3 flags),
|
||||
`..._Without_Session_Returns_Null`, `ReusableStream_Cache_RoundTrips`, and the ONE integration test
|
||||
`FramePumpTests.ReusableStream_Url_From_Service_Feeds_Encoder_Startup` (real service + real pump +
|
||||
hermetic HTTP: the reusable stream's URL lands in `EncoderOptions.RtmpUrl` and the encoder starts).
|
||||
|
||||
**Out of scope this branch (next branches):** TASK 5 item 4 live chat, item 5 the YouTube error-code
|
||||
mappings, and the design's bottom-strip YouTube logo/green-red dot (clickable → dialog).
|
||||
|
||||
### Item 3 — report-by-exception health banner (same session, second branch-worth of scope)
|
||||
|
||||
- **`Services/YouTubeStreamService.cs`:** `GetStreamHealthAsync(streamId)` polls
|
||||
`liveStreams?part=status&id={id}` → `StreamHealth` with parsed `healthStatus` (`good|ok|bad|noData`)
|
||||
+ `configurationIssues[]` (severity `info|warning|error` + type). The old `GetStreamHealth(broadcastId)`
|
||||
(wrong endpoint — `liveBroadcasts.lifeCycleStatus` — zero callers) is **gone**.
|
||||
- **`Services/StreamHealthReporter.cs` (new, pure):** `BannerFor(issues)` → `HealthIssueReport(Text?, IsError)` —
|
||||
null text on good/ok/noData/info-only; the first warning/error issue produces its type text
|
||||
(comma-joined, blank types dropped); error beats warning for color.
|
||||
- **`ViewModels/MainViewModel.cs`:** `_reusableStream` (the record, not just the URL) is stashed by
|
||||
`PrepareAndStartLiveAsync` + loaded from the cache in the ctor; a 30s `DispatcherTimer`
|
||||
(`_healthPollTimer`) polls while live — first poll fires right after the pump starts, the tick
|
||||
handler fire-and-forgets `PollHealthAsync()` (fully try/caught, failures log-only), and
|
||||
`UpdateLiveVisuals`' offline/error branch stops the timer. `ApplyHealthIssue` sets
|
||||
`HealthIssueBanner` + `HealthIssueBackground` from the reporter; `ResetHealth` clears both.
|
||||
- **`MainWindow.xaml`:** a full-width banner strip in its own window-grid row (below the top bar,
|
||||
above the content) bound via `NotNullToVis` to `HealthIssueBanner`, background to
|
||||
`HealthIssueBackground` (amber `#b8860b` warning / dark red `#8f1f1f` error); rows shifted
|
||||
(content → row 2, footer → row 3).
|
||||
- **Tests (8 new this branch, 207 total, 0 warnings):** 3 service units + 4 `StreamHealthReporterTests`
|
||||
+ the ONE integration test `GetStreamHealthAsync_Report_By_Exception_Banner_Only_On_Warning_Or_Error`
|
||||
(real service JSON parse → real reporter: good → no banner, error issue → banner text + error color).
|
||||
|
||||
## TASK 24 — the 8-issue polish batch — SHIPPED 2026-08-15 (what changed)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user