fix(host): native sessions on the console + GPU-aware codecs + gamestream capability

The web console Dashboard read AppState.{streaming,launch,stream}, which only the
GameStream path writes, so a native punktfunk/1 session (the DEFAULT plane) showed
"Idle / no session" while actively streaming — only the Stats page (shared recorder)
reflected it. Add a plane-neutral per-session registry (session_status.rs) the native
video loop publishes to; /status now merges both planes, reports active_sessions, and
the Stop / Request-IDR buttons reach native sessions too (so surfacing them doesn't
leave dead buttons). LocalSummary (tray) gets the same fix.

Also on the management API:
- /host codecs derive from Codec::host_wire_caps() instead of a hardcoded
  [H264,H265,AV1], so codecs the GPU can't encode no longer appear.
- ApiCodec serializes HEVC as "hevc" (matching the wire/SDP/stats label) so the same
  codec reads identically across console pages.
- HostInfo.gamestream reports whether the GameStream planes run (--gamestream), so the
  console can hide the Moonlight-only pairing UI on the native-only default host.
- StatsStatus.elapsed_ms (host-monotonic) so the capture timer doesn't mix host/browser
  clocks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 21:06:03 +02:00
parent c4645a8938
commit b8da32e8b6
7 changed files with 343 additions and 40 deletions
@@ -96,6 +96,10 @@ pub struct StatsStatus {
pub sample_count: u32,
/// Unix start time of the in-progress capture (`0` if idle).
pub started_unix_ms: u64,
/// Host-measured elapsed time of the in-progress capture, in ms (`0` if idle). Computed from the
/// host's MONOTONIC clock, so a console can show elapsed time without subtracting `started_unix_ms`
/// from its own (possibly skewed) wall clock.
pub elapsed_ms: u64,
/// Path of the in-progress capture (`""` if idle).
pub kind: String,
}
@@ -376,12 +380,14 @@ fn status_of(live: Option<&Live>) -> StatsStatus {
armed: true,
sample_count: l.samples.len() as u32,
started_unix_ms: l.started_unix_ms,
elapsed_ms: l.started.elapsed().as_millis() as u64,
kind: l.meta.as_ref().map(|m| m.kind.clone()).unwrap_or_default(),
},
None => StatsStatus {
armed: false,
sample_count: 0,
started_unix_ms: 0,
elapsed_ms: 0,
kind: String::new(),
},
}