phase: 06_sharer_quality_presets
Build and Push Containers / test (push) Successful in 1m12s
Build and Push Containers / build-and-push-app (push) Successful in 9s

**Phase 06 — final verification pass: 100% green.**

- **Verified (all 6 tasks complete in working tree):** `#quality` select + `PRESETS` table; preset-driven capture request + `contentHint`; per-leg `legCap()`/`degradation` at both call sites; live `applyQuality()` with Overconstrained fallback + republish; `q` key cycle; `#stat-quality`/`#stat-codec` readouts; full e2e matrix (requests, committed params ×3, zero-cap, mid-share switch, unrescalable device, late-joiner first-offer, readout, `q`); realdisplay delivered-pixels `light` case. `test_sender_fidelity.py` **untouched and green** (regression canary).
- **Gates:** `uv run ruff check . && uv run ruff format --check .` → clean (32 files); `uv run pyright` → 0 errors; `uv run pytest` → **284 passed, 5 skipped (realdisplay-gated), coverage 95%** (93s); `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → **4 passed, 1 skipped** (no audio source on host — documented platform condition).
- **Completion criteria:** all 11 ✓ — sharp default reproduces old request; light ≤960×600@15fps/motion/round(cap×0.35)/maintain-framerate with moving viewer frames; mid-share re-prices with `peers.size` unchanged, no renegotiation, roster matches `getSettings()`; refused-rescale toasts "next share"; `q` cycles + toasts, text-field guard holds; readout exact + codec shape, both reset to `—` idle; realdisplay: light decoded ≤990px (measured 320×200), sharp clears `SHARP_FLOOR_PX`; no new env/wire/Python/asset; suite 100% green, coverage >90%.
- **Docs:** README.md reviewed in full against code — Quality table, `q` key, env-var table, routes (`/share`), kiosk commands, key table, doc paths all consistent; **no fixes needed**. AGENTS.md — verified, no stale statements, no changes. PLAN.md untouched (diff = 0).
- **Reported (per phase, not edited):** PLAN.md §6 line 204 still says "per-leg `maxBitrate` cap (default **12 Mbit/s**)" — code has defaulted to **25 Mbit/s** since phase 02; needs a user-approved one-line amendment.
- **Next pending phase:** none — 06 is the last phase in `todo/`; the roadmap is complete.
This commit is contained in:
2026-11-02 22:07:17 -05:00
parent f51d0fe9ae
commit 3f93481c54
44 changed files with 2107 additions and 54 deletions
+34 -2
View File
@@ -107,6 +107,37 @@ a browser/platform fact, not a mirror one: Chrome on Linux gets it where
PipeWire offers it, Firefox's desktop capture does not offer it at all. See
[SECURITY.md](SECURITY.md) §4 "Audio".
**Quality.** The share page has a Quality control — `Sharp` (default),
`Balanced`, `Light` — because a sharer's machine is a resource the share
spends, and `MIRROR_MAX_BITRATE` alone cannot express "fewer pixels". Each
preset is one request to the capture device and one per-viewer encoder
budget:
| Preset | Requested pixel cap | Frame rate | Per-viewer bitrate | `degradationPreference` (what the encoder may sacrifice) |
| ------------ | ------------------- | ------------ | ------------------------------ | ------------------------------------------------------- |
| `Sharp` | ≤ 3840×2160 | 30 fps (up to 60) | 100% of `MIRROR_MAX_BITRATE` | `maintain-resolution` — frame rate |
| `Balanced` | ≤ 1920×1200 | 24 fps (up to 60) | 60% of `MIRROR_MAX_BITRATE` | `balanced` |
| `Light` | ≤ 960×600 | 15 fps (up to 30) | 35% of `MIRROR_MAX_BITRATE` | `maintain-framerate` — resolution |
`Sharp` is the default and is what the sharp-capture work shipped: its
request, cap and preference are the old hardcoded values verbatim, so nothing
it proved gets worse — the new presets only ever ask for less, and a preset
can never raise a leg above the server's ceiling. `MIRROR_MAX_BITRATE` stays
the ceiling the factor is applied to: a Wi-Fi sharer picks `Light` in the
page, an operator lowers the ceiling for everyone.
Who it is for: the software-encoding sharer. A Firefox/Linux box often has
no hardware video encoder (VA-API exposes no H.264 encode entrypoint on many
machines), where 4K at 30 fps burns roughly a whole core — `Light` trades
pixels and frame rate for a fan that does not spin, and under pressure the
encoder will in fact sacrifice what its `degradationPreference` allows.
Changing the preset applies to the share that is already running — the
per-viewer bitrate and frame rate always, and the capture size where the
browser allows a live rescale; when it does not, the panel says the size
lands on the next share instead of pretending it did. `q` cycles the preset.
The panel also shows the quality budget in force and the codec the legs
actually negotiated — `—` where a browser does not report it, never a guess.
## Deployment
The internal deployment is `https://mirror.reeseapps.com`, described and automated
@@ -180,7 +211,8 @@ muted, unmuting one (the tile's audio button or `m`) mutes the others, and
leaving the stage re-mutes it. Sound never starts without a click or a
keypress.
On `/mirror`: `s` starts a share, `x` stops one, `Esc` stops while live. Keys
On `/mirror`: `s` starts a share, `x` stops one, `q` cycles the Quality
preset, and `Esc` stops while live. Keys
with `Ctrl`/`Cmd`/`Alt` are never intercepted, and a key typed in the display-
name field is text, not a command. `?` is the only key that needs Shift, and it
is matched on the character itself — on a layout where `?` is a dead key the
@@ -189,7 +221,7 @@ list is not reachable, so keep this table to hand.
## Development
```bash
uv run pytest # unit + API + browser E2E (~50s)
uv run pytest # unit + API + browser E2E (~95s)
uv run pytest -m "not e2e" # everything except the browser (~0.5s)
uv run ruff check . && uv run ruff format --check . && uv run pyright
```