Files
ytLlive/HANDOFF.md
T
gramps a0a8331569 TASK 24 post-pause polish batch (creator's 8 review issues, 2026-08-15): (1) desktop/game audio volume slider + (2) mute were no-ops — the AudioMixer's gain Func seams defaulted to unity because the VM never passed them; new AudioGainProvider (Services/Audio/) wires MicMuted/MicVolume/GameMuted/GameAudioVolume into the mixer at construction, read live each mix tick, so sliders and mutes are stream-honest; the game bar also scales TRAX locally via new MusicPlayer.LocalGain (UpdateTraxLocalGain on every game volume/mute change + on load) so the creator HEARS the controls work on the music in the headphones. (3) TRAX played once then stopped — OnPlaybackStopped only looped on Position>=Length (unreliable for MediaFoundationReader); now any clean stop rewinds + replays, errors/explicit stops surface via PlaybackEnded; unused using dropped. (4+6) TRAX moved right of Socials in the footer's left cluster (centered under scenes/sources); the mic cluster is now MIC + meter + mute + volume. (5) mic source persists — LayoutStore gains a Settings key/value table (SaveMicSourceName/LoadMicSourceName); the VM saves on pick and restores before the mixer's first Start, so a restart reconnects the same vetted device (green) or reports it missing (yellow). (7) Backdrop's missing trashcan shifted the edit/eye icons right — new HiddenBoolToVisibilityConverter keeps the trash column reserved (Hidden, not Collapsed) so every source row's icons stay in fixed columns. (8) a 1px hairline with top/bottom padding separates scenes from sources in the left panel. Docs in the same commit: ai.md (gains honest + drive TRAX locally, TRAX loops, mic persistence, footer layout), TASKS.md TASK 24, HANDOFF.md shipped state, ViewModels/index.md. ONE integration test: AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback (real mixer + provider gains through the pipe harness: scaled loopback audible, silence after mute). Build 0 warnings, 197 tests passing
2026-08-15 10:23:49 -07:00

166 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HANDOFF — session state
> Current operational state, read right after `TASKS.md`. Trust this file as the
> truth of what is in flight — do not re-derive from git/fs unless it points at
> a problem. Conventions: [`schema.md`](schema.md). Rewrite this file at session
> end, compaction, or any interruption.
## Session state (last updated: 2026-08-15)
- **Branch:** `main`, tracking `origin/main`. Working tree: the **TASK 24 polish batch** below is
committed + pushed. Local branches `social-bar`/`webcam-validation` untouched (no secrets).
- **TASK 24 — POST-PAUSE POLISH BATCH — SHIPPED + COMMITTED + PUSHED 2026-08-15.** All 8 creator
review issues fixed in one branch (details below). Build **0 warnings**, full suite **197 passing**
(ONE integration test for this branch: `AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback`).
- **AUDIO MILESTONE (TASK 9) — SHIPPED + COMMITTED + PUSHED.** Real stream audio + voice filters +
auto-duck + free TRAX background music. Commits `6d71ace` (milestone) + `f2f6401` (secrets cleanup),
force-pushed (`725f5a7...f2f6401`).
- **MONETIZATION LOCKED (2026-08-14, creator) — committed `86fcd86`.** One paid line = **annual
subscription**: early access **$49.99/yr** → **$99/yr list at GA**, **grandfather-while-subscribed**,
lapse → list on renewal. Free tier unchanged (branding flash = the billboard + no Alerts; Alerts is
the paid feature). **Billing NOT itch-locked**; candidates Gumroad (native affiliates =
tiebreaker) / Lemon Squeezy. Support = in-app bug-report → git issues. Full policy in `ai.md`
Monetization; scoped plan in `TASKS.md` **TASK 23**.
- **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).
## TASK 24 — the 8-issue polish batch — SHIPPED 2026-08-15 (what changed)
The creator's review of the TASK 9 build, all fixed in one branch (full record in `TASKS.md` TASK 24).
1.**Desktop/game audio volume slider had no effect** — the `AudioMixer` gain seams defaulted to
unity (the VM never passed them): the slider/mute were decorative, stream AND local. Fixed with a
new **`AudioGainProvider`** (`Services/Audio/`) wired into the mixer at construction, read live
each mix tick.
2.**Desktop/game mute had no effect** — same wiring; `GameMuted` now zeroes the loopback on the
stream AND silences the music locally via new `MusicPlayer.LocalGain` (scaled by the game bar on
every volume/mute change + on TRAX load) — the creator hears the control work in the headphones.
3.**TRAX played once then stopped**`OnPlaybackStopped` only looped when `Position >= Length`
(unreliable for `MediaFoundationReader`); now any clean stop rewinds + replays. Dropped the unused
`using System.Runtime.InteropServices;` too.
4.**TRAX/MIC buttons swapped** — footer mic cluster is now MIC + meter + mute + volume (TRAX is out
of the mic cluster entirely).
5.**Mic source persists across restarts**`LayoutStore` gained a `Settings` key/value table
(`SaveMicSourceName`/`LoadMicSourceName`); the VM saves on pick and restores before the mixer's
first `Start` → same already-vetted device reconnects green on restart, missing → yellow.
6.**TRAX moved right of Socials** — both centered under the scenes/sources listboxes (footer line
1, left cluster).
7.**Backdrop icons shifted right** — new `HiddenBoolToVisibilityConverter` keeps the trash column
reserved (`Hidden`, not `Collapsed`) so edit/eye stay in fixed columns for every source row.
8.**No separation between scenes and sources** — a 1px hairline with top/bottom padding now sits
between the two left-panel listboxes.
**THE ONE integration test:** `Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` — real mixer +
`AudioGainProvider` gains through the pipe harness: scaled loopback audible at 0.5, silence after mute.
## TASK 9 audio milestone — SHIPPED 2026-08-14 (what changed)
The creator's feature review settled this as the single next branch ("all the audio issues done and
tested — a huge milestone"). Final spec and full shipped-state records live in `TASKS.md` (TASK 9)
and `ai.md` ("Live audio capture"). Highlights:
- **Real audio into the encoder.** `FfmpegArgs` now builds `-f f32le -ar 48000 -ac 2 -i \\.\pipe\ytllive_audio`
+ explicit `-map 0:v -map 1:a` (replaces `anullsrc` silence). `MainViewModel.BeginGoLive`
`_audioMixer.StartLive(EncoderOptions.DefaultAudioPipeName)`; `StopStream``_audioMixer.StopLive()`
before `_framePump.StopAsync()`.
- **2-input mix (mic + loopback)**, honest gains: `micGain = MicVolume` (mute = 0),
`loopbackGain = GameMuted ? 0 : GameAudioVolume` × duck. No third music channel.
- **Voice chain on the mic** (TASK 22), before meter AND mix: bass 120 Hz +4 dB → treble 8 kHz +3 dB →
gate (0.005 / hysteresis 0.5) → compressor (0.5, 4:1). Pure TDF2 DSP, per-sample, always on.
- **Auto-duck:** mic RMS > 0.02 → loopback ×0.25 (12 dB), attack 0.05 / release 0.005.
- **TRAX (free BGM):** `MusicPlayer` = MediaFoundationReader → `VolumeWaveProvider16` at fixed **0.20**
`WaveOutEvent`. Plays to the default device → rides the loopback into the stream (ducked with game).
Footer TRAX button (dot red/yellow/green + "TRAX"), left-click toggles/picks, right-click opens the
picker (`OpenFileDialog`), tooltip shows the track name. Track persists via **schema v9** single-row
`Music`. Sound-bar label "Game Audio Capture" → **"Desktop Audio"**;
`IsGameAudioBarVisible = gameDetectorProducingSound || IsMusicPlaying`.
- **Tests:** new `AudioPipelineTests.cs` (DSP/ring-buffer/ducker/resampler units + the ONE integration
test reading real pipe bytes via `NamedPipeClientStream`); `FfmpegEncoderTests` + layout persistence
updated for pipe args / Music roundtrip. Ring-buffer overwrite bug found by the unit test and fixed
(head must NOT advance on eviction — the write itself advances it). Integration test pre-fills the
ring buffers before `StartLive` so the first pipe tick already carries audio (deterministic — a
start-of-stream silence race was seen and eliminated).
**Out of scope this branch:** IP webcam, chat box, alt-key crop, credits, bg removal, music-off-VOD
track (YouTube mutes VODs with copyrighted music — future feature), `PremiumUrl` (TASK 23 seam).
- **Landmines:**
- Never add another test that constructs `new App()` — use `RealAppHost.Run(...)` (shared STA host
for the one WPF App per AppDomain; round-clip + source-naming tests).
- Never set a local `Canvas.SetTop` on the social bar — a local value permanently
overrides `{Binding SocialBarTop}` (the `ClearValue` lesson from 5.5).
- `AudioMixer` meter `Push` is unconditional **by design now**: `OnMicSample`/
`OnLoopbackSample` compute the level first, then raise the event — a
`?.Invoke(meter.Push(...))` short-circuit skipped the meter update when
nothing was subscribed (found by `RestartMic_ResetsLevel`, fixed).
- `MicConnected` comes from the source `Started` event, raised right after
`StartRecording()` succeeds — tests must `MarkStarted()` the fake source
before asserting connection state.
- The mic dot is red until a resource connects (see contract below) — green
only after `Started`, yellow on `Failed`.
- Zero mic devices at startup = red dot AND the mixer is never started, so
loopback + the game bar can't run either (no capture at all) — acceptable.
- The game detector is polled on the UI thread via a 250ms `DispatcherTimer`;
`OnGameAudioPollTick` wraps `Poll()` in try/catch + `AppLog`.
- The pump reads the active scene on a background thread while the UI can still
edit it — a concurrent-mutation exception is contained (logged + `Failed` +
the pump stops), not a crash.
- `StopAsync` must stop the encoder (closes stdin) **before** awaiting the pump
loop — closing stdin unblocks a write stuck on pipe backpressure; the reverse
order deadlocks. Same rule for audio: `StopLive()` (pipe EOF) before the pump
stop, so ffmpeg's two inputs end in order.
- Tests never instantiate `MainViewModel` directly except via a real `MainWindow`
on the `RealAppHost` STA thread (round-clip + naming), which never go live.
- Sandbox can't reach outbound HTTPS — `HttpSocialValidator` stub-handler tests
only, never the real instance.
- **Mic status contract (creator's rule, verified — do NOT "fix"):** the status dot is
**red until a mic resource is actually connected**. `MicStatus` starts `NotConnected`
(red); it goes green ONLY when the mixer's `MicConnected` fires, which comes strictly
from the mic source's `Started` event raised after `StartRecording()` succeeds. Zero
devices at startup → stays red and the mixer is never started; capture failure → yellow.
- **Secret/DB/port facts live:** OAuth client id/secret in `Helpers/OAuthCredentials.cs`;
OAuth session token in `Helpers/TokenStore.cs` (DPAPI → `%APPDATA%\ytLlive\ytLlive.auth`);
layout DB `%APPDATA%\ytLlive\ytLlive.db` (**schema v9** — single-row `Music`; the
`SocialEntry.Software` column is a column-presence migration like the others); OAuth callback
`http://localhost:8765/oauth2/callback`; crash log `%APPDATA%\ytLlive\startup.log`.
## Server recovery (llamachile.tube / YunoHost / DO) — 2026-08-14
**Facts (hunted this session — do not re-hunt):**
- Droplet **llamachile.tube**: DO droplet ID `473190301`, IP `143.244.176.131`, sfo3, 4GB/2vCPU/50GB.
**SSH port = 2214** (matches git remote `ssh://llgit.llamachile.tube:2214/gramps/ytLlive.git`);
22/2222/2200/etc all closed. SSH as `gramps` works with `~/.ssh/id_ed25519` (key auth, no password).
- **Root is YunoHost-locked**: `PermitRootLogin no` in `/etc/ssh/sshd_config` (there's a conflicting
cloud-init override section below it, but DO's "Reset root password" email does NOT work on this
box — cloud-init `set-passwords` only ran once at install). Root is reachable via `sudo -i` or the
DO web Recovery Console (Settings → Recovery console — VNC-based; the droplet page's Console button
is SSH-based and fails with "all configured authentication methods failed" when no account has a
working password).
- **gramps is a YunoHost LDAP account** (local `/etc/passwd` entry has `*`, auth via pam_ldap) — its
password is changed with `sudo yunohost user update gramps -p '<pw>'`, NOT `passwd`.
- **fail2ban bans the whole IP for 10-12 min** after repeated failed SSH/root logins (all ports
refuse; `ping` still works; droplet API still shows `active`). Wait it out — do NOT power-cycle.
- Password reset this session: `sudo yunohost tools rootpw -n '<pw>'` sets root (rootpw takes `-n`).
Verification: `getent shadow root` shows a `$y$` yescrypt hash + `passwd -S root` = `P`.
- Mastodon services run as systemd units `mastodon-web/sidekiq/streaming` (all were `active` after
the reboot); gitea/nginx/mariadb/postgres/redis/yunohost-api also systemd units. Disk was **93% full**
(3.6G free) — keep an eye on it before any big upgrade.
- The original outage was an interrupted Mastodon upgrade + a DO `password_reset` reboot; everything
came back on its own after boot. No code/schema damage observed.
**Secrets (removed 2026-08-14 — see git history if a value is ever needed again):** the DO API token,
the gramps/root passwords and the DO password-reset email password were recorded in this file and in
chat. They are treated as **compromised** — the DO API token and every listed password have been or
should be rotated/revoked, and **no new secrets belong in this repo or in chat**. Secret paths that
stay here are the file locations only (OAuth creds → `Helpers/OAuthCredentials.cs`, session token →
`Helpers/TokenStore.cs`).