The tuning path calls setTuning() about 20 times a second and each call asked
the rain engine to open. open() only short-circuited once the stream was
already playing, so every tick bumped the generation counter, orphaned the
connection in flight and started another one: 242 connections over 12s,
readyState stuck at 0, and a rainy-mood spinner that never stopped.
open() now also short-circuits while an attempt for the same URL is in flight,
and the rain path goes through needsReload() like the station path already
does, so a pending retry keeps its backoff instead of being cancelled on the
spot.
close() clears `opening` as well. Bumping the generation orphans the in-flight
attempt, which then returns without clearing the flag, so cutting the bed
mid-connection left the engine looking busy forever and switching it back on
never reopened.
test/rain-reopen.html drives the real RadioAudio at the same tick rate and
counts the connections actually opened, for both the steady state and the…
feat: robust streaming, now-playing titles and a dedicated tuning model
Three parts of the app that were tangled into embed.ts now stand on their
own, each testable from a bench page under test/.
- stream-engine.ts: one <audio> element kept alive, HLS via hls.js (light
build, dynamically imported) for .m3u8 and a hardened native path for
everything else. Reconnects only once the buffer has run dry, so a
network blip costs no audible gap.
- now-playing.ts: resolves the track on air from side-channel endpoints
rather than in-band ICY metadata, which no browser can read cross-origin.
- tuning.ts: the antenna's 36° of travel stretched over a 280° arc means
8x the encoder noise and 8x the mechanical sag. A one-euro filter,
station magnetism, capture hysteresis and a bounded UI/antenna offset
turn that into a needle that holds still and a station that does not
flicker. The needle is pinned whenever the antenna is at rest, so
nothing on the dial moves when nothing is moving.
Stations carry tags and country instead of a single genre string.
Co…