TASK 4 ship step 3: encoder + RTMP push — FFmpeg subprocess with probed H.264 picker, BGRA stdin feed, stderr health parsing, graceful stop — 122 tests passing, 0 warnings
This commit is contained in:
+31
-60
@@ -7,69 +7,40 @@
|
||||
|
||||
## Session state (last updated: 2026-08-12)
|
||||
|
||||
- **Branch:** `main` — the social bar v2 push is **committed + pushed** (`9873eed`,
|
||||
18 files, +1878/−321, 112 tests passing, 0 warnings). Working tree is **dirty
|
||||
with the post-push docs backfill only** (ai.md, README.md, TASKS.md schema
|
||||
section, THIRD-PARTY-NOTICES.txt) — ready to commit when the user says so.
|
||||
- **Finished this session (round 3):** user's live-test report said the mastodon
|
||||
icon still didn't show for `@gramps@llamachile.tube`. Root cause: the identity
|
||||
domain `llamachile.tube` is YunoHost-SSO-gated — `/.well-known/nodeinfo`,
|
||||
`/@gramps`, and webfinger all answer with the SSO login page, so nodeinfo
|
||||
returned nothing and the icon fell back to the honeycomb glyph. The real
|
||||
instance lives at `mastodon.llamachile.tube`, and only the bare root
|
||||
`https://llamachile.tube/` 302s to it ("default app" redirect). Fix:
|
||||
`HttpSocialValidator.TryFetchFediverseSoftwareAsync` now, when nodeinfo on the
|
||||
identity domain fails, follows the root redirect (`ResolveInstanceHostAsync`,
|
||||
reads `resp.RequestMessage.RequestUri.Host`) and re-runs the nodeinfo lookup
|
||||
on the resolved host. New test `FediverseHandle_RootRedirectToSubdomain_ResolvesSoftware`.
|
||||
Build: **0 warnings**. Tests: **112 passing**.
|
||||
- **What landed (all rounds this session):**
|
||||
1. **Fediverse icon resolution** — `DetectService` maps `@user@domain` to the
|
||||
new `SocialService.Fediverse` (was `Link`/chain icon). On validate,
|
||||
`HttpSocialValidator` best-effort GETs `https://{domain}/.well-known/nodeinfo`,
|
||||
follows the first nodeinfo `links[].href`, reads `software.name`, and returns
|
||||
it in `SocialLookupResult.FediverseSoftware`. `SocialEntry`/`SocialSlotViewModel`
|
||||
carry `FediverseSoftware`; `SocialServiceIcons.LogoDataForFediverse(software)`
|
||||
maps it to a bundled logo (mastodon/peertube/pixelfed/misskey/lemmy/pleroma/
|
||||
firefish — Simple Icons CC0), falling back to the `FediverseIconData`
|
||||
honeycomb glyph for unknown software (GoToSocial/Sharkey/Akkoma aren't in
|
||||
Simple Icons). Nodeinfo failure still validates — the glyph falls back.
|
||||
2. **Redirect resolution (round 3)** — identity domains that 302 their root
|
||||
to the real instance (YunoHost default-app subdomains) resolve software via
|
||||
the root redirect when identity-domain nodeinfo is SSO-blocked.
|
||||
3. **Persistence** — new `SocialEntry.Software TEXT` column via
|
||||
`MigrateSocialEntryTable()` (column-presence pattern, same as the others);
|
||||
saved/loaded alongside Service/Handle/ProfileUrl.
|
||||
4. **Icon colors** — `IconButton` style gains `Foreground="#d0d0d0"`
|
||||
(`Themes/Controls.xaml`); the dialog's trash button overrides
|
||||
`Foreground="#e94560"` (`SocialsDialog.xaml`). All other `IconButton`
|
||||
usages are `Path` content with explicit `Fill`, so unaffected.
|
||||
- **Branch:** `main`. TASK 4 ship step 3 (encoder + RTMP push) is **built and
|
||||
tested** but **NOT committed** — the working tree is dirty with the encoder
|
||||
(10 new files in `Services/Encoder/` + `ytLive.Tests/FfmpegEncoderTests.cs`) and
|
||||
its memory updates (TASKS.md, ai.md, Services/index.md). Ready to commit when
|
||||
the user says so.
|
||||
- **Finished this session:** TASK 4 ship step 3 — the FFmpeg subprocess encoder:
|
||||
`EncoderOptions` + `IFfmpegEncoder`/`FfmpegEncoder` + `IEncoderProcess`/
|
||||
`FfmpegEncoderProcess` + pure `FfmpegArgs`/`FfmpegProgressParser`/
|
||||
`FfmpegEncoderPicker` in `Services/Encoder/`. StartAsync (locate → probe
|
||||
`-encoders` → spawn → stderr loop), SubmitFrameAsync (serialized BGRA stdin),
|
||||
StopAsync (stdin EOF → ffmpeg finalizes; 10s kill watchdog), ProcessFailed on
|
||||
unexpected non-zero exit. Not yet constructed by the app (ship step 5 wiring).
|
||||
Build: **0 warnings**. Tests: **122 passing** (was 112; +10 new).
|
||||
- **Landmines:**
|
||||
- `LayoutStore.Socials` is only populated by `Load()` — tests must call
|
||||
`store.Load()` before asserting it.
|
||||
- `DetectService("justaname")` → **Website** with empty handle; only
|
||||
unparseable input or `@user@domain` yields Link/Fediverse.
|
||||
- Dialog sign-in provider is `Func<Task<YouTubeChannel?>>`; test fakes must
|
||||
return `Task.FromResult` (sync-completed) so the fire-and-forget command
|
||||
settles before the next assert.
|
||||
- `SocialBarBottomTop = 1040` literal lives in `MainWindow.xaml.cs` (the VM's
|
||||
`MasterFrameHeight` is private).
|
||||
- `Cancel_AbortsInFlightValidation_WithoutMutatingSlot` relies on the
|
||||
BlockingValidator's gate completing synchronously (no
|
||||
`RunContinuationsAsynchronously`) — the assertions run after
|
||||
`Gate.TrySetResult` returns because the awaited continuation executes inline.
|
||||
- Nodeinfo cancellation test cancels mid-lookup via a counting stub; the
|
||||
post-fetch `ct.IsCancellationRequested` check is what reports `Canceled`.
|
||||
- Round-3 redirect fallback fires only when identity-domain nodeinfo fails;
|
||||
the root follow happens automatically (HttpClient default auto-redirect),
|
||||
and `RequestMessage.RequestUri.Host` is read from the final response.
|
||||
- **Next step:** TASK 4 ship step 3 — the encoder + RTMP push (FFmpeg subprocess:
|
||||
frames via stdin, stderr health parsing, FLV mux + push to the cached reusable
|
||||
stream's ingestion URL). Nothing else queued — do not expand the task queue on
|
||||
your own.
|
||||
- `ChannelReader.ReadAsync` on a completed channel **throws**
|
||||
`ChannelClosedException` — it does NOT return `null` like a StreamReader EOF.
|
||||
The test fake (`QueuedReader` in `FfmpegEncoderTests.cs`) catches it and
|
||||
returns `null`, or the encoder's stderr loop treats it as a fault and
|
||||
`ProcessFailed` never fires (that's exactly what happened on the first run —
|
||||
see commit history).
|
||||
- The probe process (`FakeEncoderProcess`) must be a *separate* `IEncoderProcess`
|
||||
instance from the encoder process in `Start_*` tests — `StartAsync` calls the
|
||||
factory twice (probe → encoder), and the fake can't simulate both roles at
|
||||
once.
|
||||
- `StopAsync` waits the full `ExitTimeout` if the fake's process doesn't signal
|
||||
exit — fakes must call `SignalExit()` inside `StopAsync`'s stdin-EOF path.
|
||||
- Windows-only: `FfmpegEncoderProcess` sets `UseShellExecute=false` +
|
||||
`RedirectStandardXxx=true` — never spawn with a shell.
|
||||
- **Next step:** TASK 4 ship step 4 — WASAPI audio capture (loopback + mic)
|
||||
feeding `AudioLevel` (req 7). Nothing else queued — do not expand the task
|
||||
queue on your own.
|
||||
|
||||
- **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 v8; the new `SocialEntry.Software`
|
||||
layout DB `%APPDATA%\ytLlive\ytLlive.db` (schema v8; `SocialEntry.Software`
|
||||
column is a column-presence migration like the others, no version bump); OAuth callback
|
||||
`http://localhost:8765/oauth2/callback`; crash log `%APPDATA%\ytLlive\startup.log`.
|
||||
|
||||
Reference in New Issue
Block a user