feat(topbar): REC/ON-AIR pills are radio-exclusive (record-OR-live)
The record-OR-live ruling (2026-09-01) was documented in ai.md but the 'pills = radios' UI constraint never landed — both pills could be armed and StartSession/BuildEncoderOptions would dual-encode to the YouTube ingest AND a local file at once, streaming a concurrent disk write off the same ingested Kbps (cripples the stream on mid-range chassis; 'the VOD is already the copy'). The pill setters now clear one another (silent mutual clear); the dual-block ffmpeg machinery stays generic and unreachable from the UI by design.
This commit is contained in:
@@ -596,7 +596,8 @@ anullsrc` silence in the TASK 8 audio milestone) + explicit `-map 0:v -map 1:a`
|
||||
the engine runs record-only (no RTMP) or stream-only — **stream+record simultaneously is OUT by creator
|
||||
ruling (2026-09-01): the VOD is already the copy, and dual-encoding drags mid-range chassis and degrades
|
||||
BOTH outputs ("we're not them"). The old "cheap on NVENC" claim was an unverified assumption; the
|
||||
UI constraint (pills = radios) lands as a small change, the dual-block machinery stays generic.**
|
||||
UI constraint is landed (2026-09-20): the REC/ON-AIR pills are radio-exclusive — arming one clears the
|
||||
other — while the dual-block machinery stays generic.**
|
||||
`FfmpegEncoder.StartAsync` throws unless at least one output is enabled.
|
||||
**Encoder choice is probed from the binary's `-encoders` listing** (`FfmpegEncoderPicker`, pure):
|
||||
NVENC → QSV → AMF → OpenH264 fallback, **never libx264** (GPL; see Licensing). `EncoderOptions.VideoEncoder`
|
||||
@@ -611,13 +612,13 @@ declare intent: **REC pill** (local file, works signed-out) and **ON-AIR pill**
|
||||
until `IsConnected`). The pill is intent; `IsRecording`/`IsLive` are reality — the REC status dot only turns
|
||||
green when a session is actually recording, the ON-AIR dot when actually live.
|
||||
|
||||
- **State model (`MainViewModel`):** pills `RecordPillOn`/`OnAirPillOn` (ON-AIR setter no-ops if
|
||||
`!CanToggleOnAir`), `ShowPrimaryStartButton` = **always the idle face** (`IsOffline && !IsRecording`,
|
||||
- **State model (`MainViewModel`):** pills `RecordPillOn`/`OnAirPillOn` (radio-exclusive — arming one
|
||||
clears the other, record-OR-live 2026-09-01; the ON-AIR setter also no-ops if `!CanToggleOnAir`), `ShowPrimaryStartButton` = **always the idle face** (`IsOffline && !IsRecording`,
|
||||
2026-09-01 — the old connected/record-only gate blanked the bar after stopping signed-out),
|
||||
`ShowEndStreamButton`, `CanStartSession`, `AccountStatusLightToolTip`. Sign-in is a **context-menu
|
||||
item on Start** ("Sign in to YouTube" → `SignInCommand`, visible while disconnected) — the standalone
|
||||
Sign In button is gone (TASK 30's single-button rule, completed). `StartSession()` routes: ON-AIR on
|
||||
→ GoLive dialog then stream (± record); REC on or **nothing armed** → `BeginRecordOnly()` (unarmed
|
||||
→ GoLive dialog then stream; REC on or **nothing armed** → `BeginRecordOnly()` (unarmed
|
||||
Start lights the REC pill and records — no dead-end no-ops; local recording needs no account).
|
||||
`IsEditMode` also requires `!IsRecording` (lock scrubbing while recording).
|
||||
**Top bar order (creator spec 2026-09-01):** `[sign light] REC [pill] [sign light] ON-AIR [pill]`
|
||||
@@ -640,8 +641,9 @@ green when a session is actually recording, the ON-AIR dot when actually live.
|
||||
`OnFramePumpFailed` + `IsRecording` setter made internal (test-only, InternalsVisibleTo).
|
||||
Test: `SessionTeardownTests`.
|
||||
- `EncoderOptions.StreamEnabled/RecordEnabled/RecordPath` gate outputs; record+stream is one ffmpeg with two
|
||||
output blocks. Rest of the engine (FramePump/encoder) is unchanged — `BuildEncoderOptions` now always
|
||||
returns an options (with flags from the pills + `_activeRecordPath`), so record-only reaches the pump.
|
||||
output blocks (the dual-block machinery is unreachable from the UI since the pills are radio-exclusive).
|
||||
Rest of the engine (FramePump/encoder) is unchanged — `BuildEncoderOptions` always returns an options
|
||||
(flags from the pills + `_activeRecordPath`), so record-only reaches the pump.
|
||||
|
||||
### Live audio capture (TASK 4 ship step 4 — shipped 2026-08-12; game audio bar 2026-08-13; **TASK 8 audio milestone: real stream audio + filters + duck + TRAX, shipped 2026-08-14**, plan in TASKS.md)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user