TASK 4 audio follow-up: game audio bar + mic status dot + always-on capture — the footer's second audio control (desktop/game, a mirror of the mic bar: meter + mute + volume) appears only while a full-screen game is producing sound (IGameAudioDetector seam + GameAudioHysteresis: show ~500ms of fullscreen+sound, hide ~1s after leaving fullscreen, silence never hides an active bar; VM polls on a 250ms timer); capture now runs for the app's lifetime so both meters preview live (started at startup via StartMicCaptureAsync, disposed in Shutdown — no longer go-live driven); MIC label is a button with a status dot (Models/MicStatus: green via the source Started event, yellow = mic problem, red = no device); PickMicrophone swaps the live device immediately via AudioMixer.RestartMic; fixed a latent ?.Invoke(meter.Push(...)) short-circuit that skipped the meter update when nothing subscribed — 167 tests passing, 0 warnings
This commit is contained in:
+41
-25
@@ -7,41 +7,57 @@
|
||||
|
||||
## Session state (last updated: 2026-08-13)
|
||||
|
||||
- **Branch:** `main`. TASK 4 **ship step 5.5** is **committed and pushed** (`ac60a26`,
|
||||
"TASK 4 ship step 5.5: social bar bug fixes + bar on the live output").
|
||||
- **This session (follow-up):** the drag-snap from 5.5 failed in practice — it snapped
|
||||
up but wouldn't come back down (jitter around the deadzone, per the user). **Superseded
|
||||
by a click-toggle** (user decision, KISS): `MainWindow.SocialBar_MouseLeftButtonDown`
|
||||
→ `MainViewModel.ToggleSocialBarPosition()` flips the bar top ⇄ bottom; the bar rides
|
||||
`{Binding SocialBarTop}` alone; `SocialBarSnap` + its 2 tests removed. Build 0 warnings,
|
||||
**153 tests passing** (155 − 2 snap units). **Uncommitted:** this click-toggle change +
|
||||
its memory corrections (TASKS.md / ai.md / HANDOFF). Next action: commit + push.
|
||||
- **Verified:** 0 warnings; 153/153 tests pass.
|
||||
- **Branch:** `main`. The **game audio bar + mic status dot + always-on capture**
|
||||
work is **uncommitted** (see list below). The last committed+pushed baseline is
|
||||
`ea250c0` (social bar click-toggle).
|
||||
- **This session (audio UX follow-up):** per the creator's requests —
|
||||
1. The MIC label is now a **button with a status dot** (`Models/MicStatus`:
|
||||
green = capturing, yellow = requested mic problem, red = no device).
|
||||
2. **Mic capture runs for the app's lifetime** (started at startup via
|
||||
`StartMicCaptureAsync`, disposed in `Shutdown`; `BeginGoLive`/`StopStream`
|
||||
no longer start/stop the mixer) so both meters preview live.
|
||||
3. A **game audio bar** (desktop/game — meter + mute + volume, mirror of the
|
||||
mic bar) sits centered beneath the preview and appears only while a
|
||||
**full-screen game is producing sound** (decision: fullscreen + loopback
|
||||
sound; **silence never hides an active bar** — the creator's final rule).
|
||||
Show after ~500ms of fullscreen+sound, hide ~1s after leaving fullscreen.
|
||||
- **Uncommitted files:** `MainWindow.xaml`/`.cs`, `ViewModels/MainViewModel.cs`,
|
||||
`Services/Audio/{IAudioSource,WasapiMicAudioSource,WasapiLoopbackAudioSource,AudioMixer}.cs`,
|
||||
new `Models/MicStatus.cs`, new `Services/{IGameAudioDetector,GameAudioHysteresis,GameAudioDetector}.cs`,
|
||||
`ytLive.Tests/AudioMixerTests.cs`, new `GameAudioHysteresisTests.cs` +
|
||||
`GameAudioDetectorTests.cs`, memory docs (TASKS.md, ai.md, Services/index.md,
|
||||
ViewModels/index.md, Models/index.md, HANDOFF).
|
||||
- **Verified:** build 0 warnings, 0 errors; **167 tests passing** (full suite,
|
||||
after the final rebuild).
|
||||
- **Landmines:**
|
||||
- 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.
|
||||
- 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. The background thread + video pipeline is the
|
||||
new reality since ship step 5.
|
||||
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.
|
||||
- `FramePump.IsRunning` must be set true before the loop starts (a completed-task
|
||||
delay can run the first iteration synchronously on the caller's thread).
|
||||
- `StartAsync` never throws; the VM fires-and-forgets it. `Failed` while live
|
||||
flips `StreamStatus.Error` (minimal — real health surfacing is ship step 6).
|
||||
- The heal runs off the UI thread and applies via the dispatcher; a config swap
|
||||
mid-heal (dialog Save) can orphan the healed values on the old entries — harmless
|
||||
(best-effort, re-healed next load).
|
||||
- Tests never instantiate `MainViewModel` directly except the round-clip
|
||||
integration test (a real `MainWindow`), which never goes live — keep it that way.
|
||||
- Sandbox can't reach outbound HTTPS — the subdomain-probe logic is verified via
|
||||
stub-handler tests only, not against the real `mastodon.llamachile.tube`.
|
||||
- **Next step:** commit + push the click-toggle follow-up. Then TASK 4 ship step 6 —
|
||||
health stats: bind `FramePump.HealthUpdated` (bitrate/FPS/duration) into the bottom
|
||||
bar. Nothing else queued — do not expand the task queue on your own. Optional, not
|
||||
queued: rewriting the healed entry's `ProfileUrl` to
|
||||
- Sandbox can't reach outbound HTTPS — `HttpSocialValidator` stub-handler tests
|
||||
only, never the real instance.
|
||||
- **Next step:** commit + push the audio UX follow-up (the whole uncommitted set
|
||||
above in one commit). Then TASK 4 ship step 6 — health stats: bind
|
||||
`FramePump.HealthUpdated` (bitrate/FPS/duration) into the bottom bar. Nothing
|
||||
else queued — do not expand the task queue on your own. Optional, not queued:
|
||||
rewriting the healed entry's `ProfileUrl` to
|
||||
`https://mastodon.llamachile.tube/@gramps` (user must say the word).
|
||||
|
||||
- **Secret/DB/port facts live:** OAuth client id/secret in `Helpers/OAuthCredentials.cs`;
|
||||
|
||||
Reference in New Issue
Block a user