Audio capture and streaming app for webOS 5/6

Captures the TV's audio and sends it out over several transports. The
primary one is HyperHDR: RTP/L16 to a host-side loopback device, since
HyperHDR has no network audio input of its own. A second route renders
the spectrum on the TV and sends FlatBuffers images to port 19400
instead, for setups where touching the host's sound config is not an
option.

  native/       the service: capture backends (PulseAudio, ALSA, exec,
                test tone, all dlopen-based), DSP, and one file per sink
  frontend/     D-pad driven UI at a fixed 1920x1080
  servicefiles/ native service manifest plus the boot script
  host/         RTP receiver and the loopback installer for the HyperHDR
                machine
  tools/        build/package, asset generation, Homebrew Channel
                manifest, on-TV probe
  test/         host-side suites: FlatBuffers and RTP verified against
                real decoders, the engine end to end, the page in jsdom

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Rene Kievits
2026-08-26 10:21:00 +02:00
co-authored by Claude Opus 5
commit 7529a60650
73 changed files with 12826 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=1920, initial-scale=1">
<title>Audio Cap</title>
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<div id="app">
<header id="header">
<div class="brand">
<h1>Audio Cap</h1>
<div class="subtitle">TV audio to HyperHDR and friends</div>
</div>
<div class="header-status">
<span id="state-pill" class="pill">Connecting</span>
<span id="uptime" class="uptime"></span>
</div>
<button id="power" type="button" class="focusable btn primary">Start</button>
</header>
<nav id="tabs">
<button type="button" class="focusable tab" data-panel="panel-status">Status</button>
<button type="button" class="focusable tab" data-panel="panel-sinks">Outputs</button>
<button type="button" class="focusable tab" data-panel="panel-capture">Capture</button>
<button type="button" class="focusable tab" data-panel="panel-system">System</button>
</nav>
<main id="content">
<section id="panel-status" class="panel">
<div class="card">
<h2>Level</h2>
<div class="meter">
<div class="meter-row">
<span class="meter-label">Peak</span>
<div id="meter-peak" class="bar peak"><div class="bar-fill"></div></div>
<span id="meter-peak-db" class="meter-db">—</span>
</div>
<div class="meter-row">
<span class="meter-label">RMS</span>
<div id="meter-rms" class="bar rms"><div class="bar-fill"></div></div>
<span id="meter-rms-db" class="meter-db">—</span>
</div>
</div>
<div id="bands" class="bands"></div>
<div id="clip" class="clip hidden">Clipping</div>
</div>
<div class="card">
<h2>Capture</h2>
<div id="capture-info" class="info-grid"></div>
</div>
<div class="card">
<h2>Outputs</h2>
<div id="sink-status" class="sink-status">
<div class="muted">Not running.</div>
</div>
</div>
<div id="error-card" class="card error hidden">
<h2>Error</h2>
<div id="error-text"></div>
</div>
</section>
<section id="panel-sinks" class="panel hidden">
<div id="sink-cards"></div>
</section>
<section id="panel-capture" class="panel hidden">
<div class="card">
<h2>Source</h2>
<div id="capture-fields"></div>
</div>
<div class="card">
<h2>Level follower</h2>
<p class="blurb">
How quickly the reported level rises and falls. Lower is faster.
</p>
<div id="dsp-fields"></div>
</div>
</section>
<section id="panel-system" class="panel hidden">
<div class="card">
<h2>Service</h2>
<div id="system-fields"></div>
</div>
<div class="card">
<h2>Diagnostics</h2>
<p class="blurb">
What the service can see on this TV: audio libraries, capture
devices and whether it is running as root.
</p>
<div class="actions">
<button id="run-diagnostics" type="button" class="focusable btn">Run diagnostics</button>
<button id="load-logs" type="button" class="focusable btn">Show log</button>
<button id="clear-logs" type="button" class="focusable btn">Clear log</button>
</div>
<pre id="output" class="output hidden"></pre>
</div>
<div class="card">
<h2>Settings file</h2>
<div id="config-path" class="info-grid"></div>
<div class="actions">
<button id="reset-config" type="button" class="focusable btn danger">Reset to defaults</button>
</div>
</div>
</section>
</main>
<div id="toast" class="toast hidden"></div>
</div>
<script src="js/mock.js"></script>
<script src="js/luna.js"></script>
<script src="js/nav.js"></script>
<script src="js/ui.js"></script>
<script src="js/app.js"></script>
</body>
</html>