docs: capture tonight's rulings — record-OR-stream (never both), scheduling scope, SYNC provenance, playout declined
- Modes: creator ruling 2026-09-01 — the VOD is the copy; dual encode degrades both
outputs on mediocre hardware ('we're not them'). ai.md 'cheap on NVENC' claim
retired; TASK 18 three-modes -> two; pills-become-radios noted as PENDING code.
- Design Principle gains 'we're not them' (hardware realism) as its fifth bullet.
- TASK 34 scope: countdown+notify live scheduling is the whole story — 'going live is
our schedule'. Scheduled playout discussed & DECLINED; premiere/upload + playout +
simultaneous-record-stream added to the closed out-of-product table.
- TASK 22: SYNC slider provenance restored — creator-requested (OBS delay-filter fix,
native). Placement question stays open; capability does not.
- MyMistakes: the provenance rule (a fact without its who/why becomes a future argument).
Docs only — zero code in this commit, per creator instruction.
This commit is contained in:
@@ -146,3 +146,15 @@ Bonus: the "failing" test encoded the MAP's contract (`loopbackGain = GameAudioV
|
|||||||
code had drifted to unity on a disproven premise (loopback capture does NOT follow endpoint
|
code had drifted to unity on a disproven premise (loopback capture does NOT follow endpoint
|
||||||
volume — creator's 20%-volume/pegged-meter observation killed it). The test was right all
|
volume — creator's 20%-volume/pegged-meter observation killed it). The test was right all
|
||||||
along — failing tests may be the last honest witnesses; interrogate, don't pardon.
|
along — failing tests may be the last honest witnesses; interrogate, don't pardon.
|
||||||
|
|
||||||
|
## Feature provenance: record WHO asked and WHY, in the task entry itself
|
||||||
|
|
||||||
|
(2026-08-31/09-01, the SYNC slider scare) TASK 22's lip-sync slider surfaced on the preview rail and
|
||||||
|
the creator's reaction was "totally don't remember ordering that" — because the queue entry recorded
|
||||||
|
WHAT shipped (a slider, 0-500ms, a converter class) but not WHO asked (the creator, explicitly, for
|
||||||
|
OBS's delay-filter fix built natively). Eight days later his own request read like AI drift and nearly
|
||||||
|
got deleted. Rule: the moment a creator-driven feature is queued or shipped, its entry carries a
|
||||||
|
one-line provenance — *who asked, what triggered it* ("creator: OBS delay-filter lip-sync fix, native").
|
||||||
|
Features without attribution become roadmap orphans that get punted, removed, or re-litigated. Same
|
||||||
|
disease as an unexplained "known failure" label — a fact recorded without its reason is a future
|
||||||
|
argument.
|
||||||
|
|||||||
@@ -977,7 +977,7 @@ click (volume sliders keep their manual `SetSliderValueFromClick`, harmless dupl
|
|||||||
|
|
||||||
1. ✅ `EncoderOptions` extended with `StreamEnabled` / `RecordEnabled` / `RecordPath` (independent intent flags)
|
1. ✅ `EncoderOptions` extended with `StreamEnabled` / `RecordEnabled` / `RecordPath` (independent intent flags)
|
||||||
2. ✅ `FfmpegArgs.Build` reworked into per-output blocks (stream `-f flv`, record `-f mp4`) via `AddVideoTags`
|
2. ✅ `FfmpegArgs.Build` reworked into per-output blocks (stream `-f flv`, record `-f mp4`) via `AddVideoTags`
|
||||||
3. ✅ Three modes via pills: record-only / stream-only / stream+record (single ffmpeg, dual output)
|
3. ✅ Two modes via pills: record-only / stream-only (single ffmpeg, one output). ~~stream+record~~ — **REMOVED by creator ruling 2026-09-01: the VOD is already the copy; dual encode drags mid-range hardware and degrades both outputs ("we're not them"). Pending implementation: pills become mutually exclusive radios + `BeginGoLive(alsoRecord)` param dies.**
|
||||||
4. ✅ Top-bar REC + ON-AIR pill toggles, status lights (REC green when recording, ON-AIR green when live), dynamic primary-button text, account status-light tooltip
|
4. ✅ Top-bar REC + ON-AIR pill toggles, status lights (REC green when recording, ON-AIR green when live), dynamic primary-button text, account status-light tooltip
|
||||||
5. ✅ Record-folder persistence (`LayoutStore` `RecordFolder` key) + picker (`ChooseRecordFolderCommand`)
|
5. ✅ Record-folder persistence (`LayoutStore` `RecordFolder` key) + picker (`ChooseRecordFolderCommand`)
|
||||||
6. 🟡 Branding flash carries into local recordings — same frame path as streaming (verify in the running app)
|
6. 🟡 Branding flash carries into local recordings — same frame path as streaming (verify in the running app)
|
||||||
@@ -987,8 +987,13 @@ Remaining: running-app verification of the rename + dual output.
|
|||||||
|
|
||||||
### Design decisions
|
### Design decisions
|
||||||
|
|
||||||
- FFmpeg supports multiple outputs natively (`-f flv rtmp://... -f mp4 file.mp4`) — no second subprocess needed
|
- **Record OR stream, never both (creator ruling 2026-09-01):** YouTube's VOD already keeps the copy
|
||||||
- MP4 is the default container (widely compatible); MKV as an option (crash-safe, can be remuxed)
|
(downloadable from Studio), and a second concurrent encode degrades BOTH outputs on anything short of
|
||||||
|
a performant chassis. Recording's real jobs: the offline mux-check bench and the no-account session.
|
||||||
|
Pills become radios (arming one disarms the other); the one-stop ruling ("Stop ends everything")
|
||||||
|
already assumes a single session kind.
|
||||||
|
- FFmpeg supports multiple outputs natively (`-f flv rtmp://... -f mp4 file.mp4`) — kept as generic machinery, but only one block is ever enabled now
|
||||||
|
- MP4 is the default container; crash-safety arrives as **fragmented MP4** (TASK 32 slice 4 — in record mode the local file IS the archive), retiring the old "MKV as an option" note
|
||||||
- Record-only mode is useful for pre-recorded content or testing without going live
|
- Record-only mode is useful for pre-recorded content or testing without going live
|
||||||
- The branding flash carries into local recordings (free tier billboard extends to recordings)
|
- The branding flash carries into local recordings (free tier billboard extends to recordings)
|
||||||
|
|
||||||
@@ -1124,7 +1129,7 @@ change. First GUI smoke test: add a short real mp4 on native Windows, confirm it
|
|||||||
|
|
||||||
1. ☑ `AudioSyncOffsetMs` on `MainViewModel` (global, default 0, range 0..500 ms) — positive-only: OBS's documented fix delays the audio so it lands on video when it runs ahead; advancing audio would need a video-side delay (out of the audio layer's scope, v1.1+).
|
1. ☑ `AudioSyncOffsetMs` on `MainViewModel` (global, default 0, range 0..500 ms) — positive-only: OBS's documented fix delays the audio so it lands on video when it runs ahead; advancing audio would need a video-side delay (out of the audio layer's scope, v1.1+).
|
||||||
2. ☑ Applied in `AudioMixer` — post-mix interleaved-stereo delay via the pure `AudioSyncDelay` line (`Services/Audio/AudioSyncDelay.cs`), fed through a `Func<int> syncOffsetMs` seam each mix tick.
|
2. ☑ Applied in `AudioMixer` — post-mix interleaved-stereo delay via the pure `AudioSyncDelay` line (`Services/Audio/AudioSyncDelay.cs`), fed through a `Func<int> syncOffsetMs` seam each mix tick.
|
||||||
3. ☑ UI: compact "SYNC" slider (0..500) on the mic bar with a status dot (green = no-op, amber = offset set) via `IntToSyncBrushConverter`.
|
3. ☑ UI: compact "SYNC" slider (0..500) on the mic bar with a status dot (green = no-op, amber = offset set) via `IntToSyncBrushConverter`. **Provenance (recorded 2026-09-01 after a creator "I never ordered this" scare): the creator explicitly asked for OBS's delay-filter lip-sync fix BUILT NATIVELY — the feature is his, not AI drift. Its placement (preview-rail vs mic-settings/gear) remains an open design question; do not remove the capability.**
|
||||||
4. ☑ Persisted in `LayoutStore.Settings` (`Audio.SyncOffsetMs`) via `LoadAudioSyncOffsetMs`/`SaveAudioSyncOffsetMs`; saved from `SaveLayoutNow`.
|
4. ☑ Persisted in `LayoutStore.Settings` (`Audio.SyncOffsetMs`) via `LoadAudioSyncOffsetMs`/`SaveAudioSyncOffsetMs`; saved from `SaveLayoutNow`.
|
||||||
5. ☑ Tests: `AudioSyncDelayTests` — zero-delay identity, negative→0 clamp, >500 ms clamp to 500 ms, and 10 ms → 960 interleaved-sample shift.
|
5. ☑ Tests: `AudioSyncDelayTests` — zero-delay identity, negative→0 clamp, >500 ms clamp to 500 ms, and 10 ms → 960 interleaved-sample shift.
|
||||||
6. ⚠ **Post-ship regression (found 2026-09-01, first real launch):** this task's sync-delay line `_delayedMix` was declared nullable and never initialized — the first `delayed.Length` deref NRE'd EVERY live-mix tick, silently killing all live/record audio, hanging `AudioPipelineTests`, and being mislabeled a "known failure". Fixed in the recording-verification pass (init + null-check + throttled loop errors logged with stack); `AudioPipelineTests` 25/25 green afterward. Lesson recorded in MyMistakes.
|
6. ⚠ **Post-ship regression (found 2026-09-01, first real launch):** this task's sync-delay line `_delayedMix` was declared nullable and never initialized — the first `delayed.Length` deref NRE'd EVERY live-mix tick, silently killing all live/record audio, hanging `AudioPipelineTests`, and being mislabeled a "known failure". Fixed in the recording-verification pass (init + null-check + throttled loop errors logged with stack); `AudioPipelineTests` 25/25 green afterward. Lesson recorded in MyMistakes.
|
||||||
@@ -1513,6 +1518,11 @@ WHEN is what TASKS.md:25 always claimed existed and was never built.
|
|||||||
|
|
||||||
## TASK 34 — Scheduled streams, Text-drawer version (queued 2026-09-01; option (b) with the creator's placement)
|
## TASK 34 — Scheduled streams, Text-drawer version (queued 2026-09-01; option (b) with the creator's placement)
|
||||||
|
|
||||||
|
**Scope ruling (2026-09-01):** this is the whole scheduling story — announce a LIVE show (countdown
|
||||||
|
watch-page + subscriber notifications), then the creator shows up and pushes. "Going live is our
|
||||||
|
schedule." Scheduled *playout* of pre-recorded files: discussed and DECLINED by the creator.
|
||||||
|
Premiere/upload scheduling: out of product (see closed list).
|
||||||
|
|
||||||
**Goal:** announce "Friday 8pm" from inside the app — YouTube notifies subscribers and runs the
|
**Goal:** announce "Friday 8pm" from inside the app — YouTube notifies subscribers and runs the
|
||||||
channel countdown; come Friday, one click goes live into the scheduled broadcast. Scheduling is a
|
channel countdown; come Friday, one click goes live into the scheduled broadcast. Scheduling is a
|
||||||
"my channel" concern, so it lives in the always-visible **Text drawer**, not the go-live modal.
|
"my channel" concern, so it lives in the always-visible **Text drawer**, not the go-live modal.
|
||||||
@@ -1614,6 +1624,9 @@ flash is preview-only (see ai.md → Monetization; TASK 36 flips it live).
|
|||||||
| Advanced broadcast-form tab (latency/DVR/embed/projection/CC/region) | TASK 9 item 7 rescoped: fixed sane defaults, invisible. Every exposed field is a support ticket. |
|
| Advanced broadcast-form tab (latency/DVR/embed/projection/CC/region) | TASK 9 item 7 rescoped: fixed sane defaults, invisible. Every exposed field is a support ticket. |
|
||||||
| In-app bug-report mechanism | Never built, never will be: support = email + GitHub issues. (ai.md's support line corrected to match, 2026-09-01. Second-place corpse if the list ever reopens: with replay buffer.) |
|
| In-app bug-report mechanism | Never built, never will be: support = email + GitHub issues. (ai.md's support line corrected to match, 2026-09-01. Second-place corpse if the list ever reopens: with replay buffer.) |
|
||||||
| D3DImage/GPU preview compositor | TASK 3 item 16 superseded: XAML preview + software output compositor are the design; a D3D11 swap remains a seam-respecting possibility, not a feature. |
|
| D3DImage/GPU preview compositor | TASK 3 item 16 superseded: XAML preview + software output compositor are the design; a D3D11 swap remains a seam-respecting possibility, not a feature. |
|
||||||
|
| Simultaneous record + stream | Creator ruling 2026-09-01: the VOD is the copy; dual encode drags mediocre hardware and degrades both outputs. "We're not them." Two modes, radio pills, one stop. |
|
||||||
|
| Premiere / video-upload pipeline | Uploading finished videos is Studio's job (quota-brutal, processing-state polling, API premiere support unproven). Watch-parties are fandom behavior, not our persona. "Going live is our schedule." |
|
||||||
|
| Scheduled playout (pre-recorded file airs as live) | Discussed fully 2026-09-01 and declined by the creator — "let's not." If it ever returns, it returns by ruling, with machine-on caveats and the media-audio slice as its price of admission. |
|
||||||
|
|
||||||
*If a future session is tempted by anything on this list, the answer is already written. New ideas
|
*If a future session is tempted by anything on this list, the answer is already written. New ideas
|
||||||
must survive this page before they get a task number.*
|
must survive this page before they get a task number.*
|
||||||
|
|||||||
@@ -523,9 +523,12 @@ anullsrc` silence in the TASK 8 audio milestone) + explicit `-map 0:v -map 1:a`
|
|||||||
(≤4s keyframes, closed GOP, H.264 compliance) + `-c:a aac -ar 48000 -ac 2 -f flv <rtmpUrl>`.
|
(≤4s keyframes, closed GOP, H.264 compliance) + `-c:a aac -ar 48000 -ac 2 -f flv <rtmpUrl>`.
|
||||||
**Record output (TASK 18):** `FfmpegArgs.Build` emits one self-contained block per output, each its own
|
**Record output (TASK 18):** `FfmpegArgs.Build` emits one self-contained block per output, each its own
|
||||||
`-map 0:v -map 1:a` + codec tags (`AddVideoTags` helper). Stream block = `-f flv <rtmpUrl>`; record block =
|
`-map 0:v -map 1:a` + codec tags (`AddVideoTags` helper). Stream block = `-f flv <rtmpUrl>`; record block =
|
||||||
`-f mp4 <RecordPath>`. `EncoderOptions.StreamEnabled`/`RecordEnabled`/`RecordPath` gate each block, so the
|
`-f mp4 <RecordPath>`. `EncoderOptions.StreamEnabled`/`RecordEnabled`/`RecordPath` gate each block, so
|
||||||
engine runs record-only (no RTMP), stream-only, or stream+record (video encoded twice off the one raw input
|
the engine runs record-only (no RTMP) or stream-only — **stream+record simultaneously is OUT by creator
|
||||||
pipe — cheap on NVENC). `FfmpegEncoder.StartAsync` throws unless at least one output is enabled.
|
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.**
|
||||||
|
`FfmpegEncoder.StartAsync` throws unless at least one output is enabled.
|
||||||
**Encoder choice is probed from the binary's `-encoders` listing** (`FfmpegEncoderPicker`, pure):
|
**Encoder choice is probed from the binary's `-encoders` listing** (`FfmpegEncoderPicker`, pure):
|
||||||
NVENC → QSV → AMF → OpenH264 fallback, **never libx264** (GPL; see Licensing). `EncoderOptions.VideoEncoder`
|
NVENC → QSV → AMF → OpenH264 fallback, **never libx264** (GPL; see Licensing). `EncoderOptions.VideoEncoder`
|
||||||
forces one and skips the probe. `EncoderOptions` also carries W×H/FPS/bitrate from the quality tier and
|
forces one and skips the probe. `EncoderOptions` also carries W×H/FPS/bitrate from the quality tier and
|
||||||
@@ -866,6 +869,10 @@ Apply this to every UI decision:
|
|||||||
- Prefilled YouTube defaults (RTMP URL, bitrate, resolution, latency)
|
- Prefilled YouTube defaults (RTMP URL, bitrate, resolution, latency)
|
||||||
- Visual/drag-and-drop scene building over property panels
|
- Visual/drag-and-drop scene building over property panels
|
||||||
- Every action produces a visible outcome — no dead ends
|
- Every action produces a visible outcome — no dead ends
|
||||||
|
- **"We're not them" (2026-09-01):** assume the creator's hardware is mediocre, because it is.
|
||||||
|
Every feature spends the machine's budget once — never twice for the same result (record OR
|
||||||
|
stream, not both; one reusable stream; one webcam; two mixer inputs). If a feature only sings on
|
||||||
|
a high-end chassis, it doesn't ship — the OBS escape hatch is open by design.
|
||||||
|
|
||||||
## Monetization (design decision — the branding flash is the sword)
|
## Monetization (design decision — the branding flash is the sword)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user