chore: consolidate all in-progress parallel-session WIP
audit / bun-audit (push) Successful in 27s
windows-drivers / probe-and-proto (push) Successful in 49s
ci / web (push) Successful in 1m1s
ci / docs-site (push) Successful in 1m10s
apple / swift (push) Successful in 1m18s
decky / build-publish (push) Successful in 34s
windows-drivers / driver-build (push) Successful in 1m37s
audit / cargo-audit (push) Successful in 3m1s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 42s
ci / bench (push) Successful in 5m56s
windows-host / package (push) Failing after 5m17s
apple / screenshots (push) Successful in 4m45s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m38s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11m3s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m2s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8m6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11m6s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m15s
arch / build-publish (push) Successful in 16m50s
android / android (push) Successful in 17m7s
docker / deploy-docs (push) Successful in 28s
deb / build-publish (push) Successful in 17m6s
ci / rust (push) Failing after 19m1s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m3s
flatpak / build-publish (push) Successful in 8m0s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m42s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m29s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m14s
release / apple (push) Successful in 5m44s

Wholesale commit of every uncommitted change across the tree, at the user's
explicit request — host refactor-campaign W1 (native.rs facade + native/ dir,
library/ + mgmt/ splits), Android, core. These streams were mid-flight and not
individually built/tested together; this supersedes the per-session HOLD
markers. Consolidating so everything lands on main in one pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 20:08:29 +02:00
parent 07e2836601
commit ecfa71212d
67 changed files with 9456 additions and 8062 deletions
+38 -7
View File
@@ -2,13 +2,13 @@
//!
//! The GameStream media pipeline records its session in `AppState.{launch, stream, streaming}`
//! (consumed by RTSP/media), but the native punktfunk/1 plane never touches `AppState` — by design
//! it is handed only the shared stats recorder ([`crate::punktfunk1::serve`]). So a native session,
//! it is handed only the shared stats recorder ([`crate::native::serve`]). So a native session,
//! which is the DEFAULT plane (GameStream is opt-in, `--gamestream`), was invisible on the Dashboard:
//! `GET /status` reported `video_streaming: false` and no session/stream card while a client was
//! actively streaming (the Stats page worked because it shares the recorder — hence the confusing
//! "stats move but the dashboard says idle").
//!
//! This module is the small shared surface the native video loop ([`crate::punktfunk1::virtual_stream`])
//! This module is the small shared surface the native video loop ([`crate::native::virtual_stream`])
//! publishes a live snapshot to, keyed per session so CONCURRENT native sessions each get an entry
//! (the native server admits up to `max_sessions`, unbounded by default). The loop registers on
//! stream start and the returned [`LiveSessionGuard`] removes the entry on ANY scope exit (return,
@@ -26,7 +26,7 @@ use crate::encode::Codec;
/// second update path — plus the flags the mgmt API flips to control the session.
struct LiveSession {
id: u64,
/// Packed `w:16|h:16|hz:16` ([`crate::punktfunk1::pack_mode`]); updated on a mode switch.
/// Packed `w:16|h:16|hz:16` ([`crate::native::pack_mode`]); updated on a mode switch.
mode: Arc<AtomicU64>,
/// Live encoder target (kbps); updated on an adaptive-bitrate change.
bitrate_kbps: Arc<AtomicU32>,
@@ -36,6 +36,10 @@ struct LiveSession {
/// One-shot force-keyframe flag ([`force_idr_all`] → mgmt `POST /session/idr`); the encode loop
/// drains it alongside a client's decode-recovery keyframe request.
force_idr: Arc<AtomicBool>,
/// Short client label (cert-fingerprint prefix / peer IP) — carried on the lifecycle events.
client: String,
/// Whether the session negotiated HDR — carried on the lifecycle events.
hdr: bool,
}
/// A resolved read of one live session, for the `/status` view.
@@ -58,23 +62,44 @@ fn next_id() -> u64 {
ID.fetch_add(1, Ordering::Relaxed)
}
/// Resolves one session's [`crate::events::SessionRef`] (mode read live) for the lifecycle events.
fn session_ref(s: &LiveSession) -> crate::events::SessionRef {
let (width, height, fps) = crate::native::unpack_mode(s.mode.load(Ordering::Relaxed));
crate::events::SessionRef {
id: s.id,
client: s.client.clone(),
mode: crate::events::mode_str(width, height, fps),
hdr: s.hdr,
}
}
/// Registers a live native session; the returned guard removes it on drop (session end).
/// Emits the `session.started` lifecycle event; the guard's drop emits `session.ended` — RAII,
/// so every exit path (return, `?`, panic-unwind) pairs them.
pub fn register(
mode: Arc<AtomicU64>,
bitrate_kbps: Arc<AtomicU32>,
codec: Codec,
stop: Arc<AtomicBool>,
force_idr: Arc<AtomicBool>,
client: String,
hdr: bool,
) -> LiveSessionGuard {
let id = next_id();
registry().lock().unwrap().push(LiveSession {
let session = LiveSession {
id,
mode,
bitrate_kbps,
codec,
stop,
force_idr,
client,
hdr,
};
crate::events::emit(crate::events::EventKind::SessionStarted {
session: session_ref(&session),
});
registry().lock().unwrap().push(session);
LiveSessionGuard { id }
}
@@ -85,7 +110,14 @@ pub struct LiveSessionGuard {
impl Drop for LiveSessionGuard {
fn drop(&mut self) {
registry().lock().unwrap().retain(|s| s.id != self.id);
let mut reg = registry().lock().unwrap();
if let Some(pos) = reg.iter().position(|s| s.id == self.id) {
let session = reg.remove(pos);
drop(reg); // emit outside the registry lock — the bus takes its own
crate::events::emit(crate::events::EventKind::SessionEnded {
session: session_ref(&session),
});
}
}
}
@@ -101,8 +133,7 @@ pub fn snapshot() -> Vec<SessionSnapshot> {
.unwrap()
.iter()
.map(|s| {
let (width, height, fps) =
crate::punktfunk1::unpack_mode(s.mode.load(Ordering::Relaxed));
let (width, height, fps) = crate::native::unpack_mode(s.mode.load(Ordering::Relaxed));
SessionSnapshot {
width,
height,