Files
ytLlive/HANDOFF.md
T

11 KiB
Raw Blame History

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. Rewrite this file at session end, compaction, or any interruption.

Session state (last updated: 2026-08-14)

  • Branch: main (audio milestone work is uncommitted, see "Uncommitted").
  • AUDIO MILESTONE (TASK 9) — SHIPPED this session. Real stream audio + voice filters + auto-duck
    • free TRAX background music. Build 0 warnings, full suite 196 passing (ONE integration test: AudioPipelineTests.Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe). Everything below in the milestone section is DONE. NOT yet committed — next step is review + commit (with ai.md/ TASKS.md/this file already updated in the same changeset).
  • Two build-time deviations from the settled plan (both recorded in ai.md + TASKS.md):
    1. MusicPlayer uses WaveOutEvent (new sibling package NAudio.WinMM 2.2.1) — not WasapiOut as planned; WaveOutEvent isn't in NAudio.Wasapi/NAudio.Core.
    2. No forced 48 kHz capture — NAudio 2.2.1's WasapiCapture has no overridable GetDefaultMixFormat, so both sources run the device mix format and the mixer's TinyResampler normalizes any rate to 48 kHz (resampler IS the design, not a fallback).
    3. FfmpegEncoder.cs is untouched — the VM closes the audio pipe (_audioMixer.StopLive()) in StopStream BEFORE the frame pump stops (audio EOF then video EOF, in order).
  • SERVER RECOVERY (llamachile.tube / YunoHost / DO droplet) — done earlier session. Side task complete; rotate the DO API token (control-panel only) and any password no longer trusted.
  • Shipped, all pushed before this session: TASK 21 (creator-hub About, 3d92bd0), TASK 8 (meter +10 dB), TASK 4 ship step 7 (one-click go-live + private-only). 174 tests passing, 0 warnings.
  • Uncommitted (whole TASK 9 milestone): Services/Audio/ (new VoiceFilterChain.cs, AudioRingBuffer.cs, AutoDucker.cs, TinyResampler.cs, MusicPlayer.cs, NamedPipeAudioWriter.cs, rewritten AudioMixer.cs, simplified WasapiMicAudioSource.cs), Models/Music.cs (new), FfmpegArgs.cs, EncoderOptions.cs, LayoutStore.cs (v9), MainViewModel.cs, MainWindow.xaml, MainWindow.xaml.cs, ytLive.csproj (NAudio.WinMM), ytLive.Tests/ (AudioPipelineTests.cs new, FfmpegEncoderTests.cs + LayoutStorePersistenceTests.cs updated), and docs ai.md, TASKS.md, HANDOFF.md.
  • Next step: commit the milestone (docs already staged in the same changeset), push, then start TASK 5 (reusable stream → _rtmpUrlProvider — the last blocker before go-live actually encodes). Then itch.io product page setup (premium URL lands in MainViewModel.PremiumUrl, lights up Unlock Premium). Optional, not queued: rewriting the healed entry's ProfileUrl to https://mastodon.llamachile.tube/@gramps (user must say the word).

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.20WaveOutEvent. 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), itch.io PremiumUrl.

  • 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).