From 588086e82ce3cd33574675f1373da424b5a3b312 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Tue, 28 Jul 2026 19:15:51 +0200 Subject: [PATCH] Audio on by default, per-game volume, and fix real navigation + latency bugs - Removed the "Enable Sound" button: AudioContext/worklet/WebSocket now connect eagerly on page load; only ctx.resume() still needs a user gesture, piggybacking on the page's first click/keypress (e.g. clicking Start) instead of a dedicated audio-only button. - Added a per-game volume slider (POST /volume). Despite audio being one shared PulseAudio mix, this is a real independent control: every game is still its own distinct sink-input, found by matching `pactl -f json list sink-inputs`'s application.process.id against the game's own PID, then `pactl set-sink-input-volume`. - Real bug: audio still never played after removing the button, because Install/Start/Stop/Uninstall were still
submits. Every click caused a full page navigation (303 redirect), tearing down whatever AudioContext had just connected; the fresh page after reload creates a new suspended context with no further gesture to unlock it. Tell: no speaker icon ever appeared on the Chrome tab. Fixed by removing s entirely - every button is now onclick="doAction(...)", do_POST returns a plain 204, and client-side doAction()/refresh() fetch() the action and the updated page, then swap only #content's innerHTML. The page itself never navigates, so the audio connection survives every action. setInterval(refresh, 3000) replaces the old for keeping status current without that risk. - Real bug: once audio worked, ~2s of latency that got worse over time plus multi-second delay before volume changes were audible - fixed upstream in docker-common's pcm-worklet.js (uncapped playback queue), propagated here. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01NiNnj78HGx1KWyCCo39HSz --- CLAUDE.md | 10 +-- TODO.md | 53 +++++++++++++- scripts/pcm-worklet.js | 29 +++++++- scripts/setup_server.py | 151 +++++++++++++++++++++++++++++++--------- 4 files changed, 203 insertions(+), 40 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d518041..07eebdd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,14 +51,14 @@ There is **no single shared desktop/display for video** — unlike the other noV **Game installation & lifecycle** (`scripts/setup_server.py`), the key thing that differs from the other noVNC-family projects: - A stdlib-only Python `ThreadingHTTPServer` (no third-party deps, no pip installs) — the *only* thing `server.sh` starts. No Xvnc/fluxbox/websockify run until a game is actually started. - Games are **not** hardcoded anywhere in the image or discovered from raw zip files. Each game is described by its own `.json` manifest living in the `Games\dosbox\` catalog directory on the SMB share, with `name`/`title`/`zip_path`/`release_date`/`publisher`/`start_cmd` fields — see the module docstring in `setup_server.py` for the exact schema. `discover_games()` lists `*.json` under `Games\dosbox\` (`smbclient -N ... -c 'ls Games\dosbox\*.json'`), `smbclient get`s each one into `/tmp/manifests/`, and parses it — so dropping a new `.json` on the share is enough to make a game appear, no image rebuild needed. `zip_path` is a full path relative to the share root (e.g. `Games/Monkey Island/The Secret of Monkey Island.zip`), **not** assumed to live next to its manifest — manifests always live in the `Games\dosbox\` catalog regardless of where the actual game data sits on the share (the SCUMM games' zips live in their own folders, e.g. `Games\Monkey Island\`, `Games\Indiana Jones\`). `release_date`/`publisher` are static declared metadata (historical facts, can't be derived from the share); install size deliberately is **not** a manifest field — `get_zip_sizes()` looks it up live via a batched `smbclient ls` (one `ls ""` per game in a single connection, results split on each command's `N blocks ... available` trailer) so it can't go stale if a zip is replaced. -- `GET /` renders one row per discovered manifest (title/release/publisher/size/status/actions) plus a `"{used}/{MAX_CONCURRENT_GAMES} game slots in use"` line at the top. +- `GET /` renders one row per discovered manifest (title/release/publisher/size/status/actions) plus a `"{used}/{MAX_CONCURRENT_GAMES} game slots in use"` line at the top. **None of the action buttons are ``s** — every one is a bare `