alpha
Login
or
Join now
atradio.fm
/
atradio
Star
13
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
A social internet radio platform built on AT Protocol.
atradio.fm
atproto
radio
Star
13
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
atradio
/
apps
/
media-proxy
/
src
/
media_proxy
/
at
main
7 files
Tsiry Sandratraina
fix(media-proxy): keep radio streams alive past gun's 15s cutoff
1mo ago
61305cb4
cache.gleam
feat(media-proxy): cache TuneIn + ICY responses (ETS, per-node TTL) Add a node-local ETS-backed TTL cache (no Redis — the proxy is stateless and scales horizontally). TuneIn responses cached 300s, ICY now-playing 20s; a hit is served with `x-cache: HIT`. The table is created at startup from the long-lived main process so it outlives request handlers. Verified live: ICY/TuneIn MISS -> HIT, hits served from memory in ~15ms.
2 months ago
config.gleam
feat(media-proxy): per-IP rate limiting on /api routes Fixed-window limiter backed by ETS atomic counters (proxy_ratelimit_ffi.erl), keyed by client IP (X-Forwarded-For / X-Real-IP / socket). /api/* over the budget gets 429 + Retry-After; health + root are exempt. Configurable via RATE_LIMIT (120) / RATE_WINDOW (60s). A background sweep drops expired windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 months ago
icy.gleam
feat(media-proxy): implement the ICY now-playing reader Replace the /api/icy stub with a real reader: read_icy_title/1 in the gun FFI opens the stream with Icy-MetaData:1, follows redirects, and does a bounded walk (metaint*2+4096 bytes) of the interleaved metadata blocks — skipping empty blocks — to parse StreamTitle. Unwraps .pls/.m3u first. Verified live against SomaFM (real titles parsed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 months ago
playlist.gleam
feat(media-proxy): scaffold a Gleam/BEAM reverse-proxy service Extract the media proxy routes (stream, tunein, image, icy) out of the AppView into a new stateless service on the BEAM, so the long-lived, bandwidth-heavy streaming workload scales and fails independently of discovery + Connect. - mist HTTP server; PORT env var (default 7081); blanket CORS + exposed ICY headers - /api/stream: streaming reverse-proxy via gun -> mist.chunked (proxy_gun_ffi.erl), with .pls/.m3u unwrapping, ICY/Range passthrough, and redirect-follow - /api/tunein, /api/image: buffered ports via gleam_httpc - /api/icy: documented stub (needs a bounded streaming read) - request/access + upstream logging; 10 unit tests (port config + playlist) - systemd unit mirroring the existing api/jetstream units Verified live: streams real MP3 audio from a CORS-less SomaFM endpoint. Web wiring (VITE_MEDIA_PROXY) + deleting apps/api/src/proxy is a follow-up.
2 months ago
ratelimit.gleam
feat(media-proxy): per-IP rate limiting on /api routes Fixed-window limiter backed by ETS atomic counters (proxy_ratelimit_ffi.erl), keyed by client IP (X-Forwarded-For / X-Real-IP / socket). /api/* over the budget gets 429 + Retry-After; health + root are exempt. Configurable via RATE_LIMIT (120) / RATE_WINDOW (60s). A background sweep drops expired windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 months ago
simple.gleam
feat(media-proxy): cache TuneIn + ICY responses (ETS, per-node TTL) Add a node-local ETS-backed TTL cache (no Redis — the proxy is stateless and scales horizontally). TuneIn responses cached 300s, ICY now-playing 20s; a hit is served with `x-cache: HIT`. The table is created at startup from the long-lived main process so it outlives request handlers. Verified live: ICY/TuneIn MISS -> HIT, hits served from memory in ~15ms.
2 months ago
stream.gleam
fix(media-proxy): keep radio streams alive past gun's 15s cutoff Icecast/SHOUTcast streams reply with no Content-Length and no chunked encoding, so the body is delimited by connection close. gun classifies that as `body_close`/`connection: close`, enters its `closing` state, and arms `closing_timeout` (default 15s) — then force-closes the upstream after 15s, cutting every web radio stream off shortly after it starts. Pass `http_opts => #{closing_timeout => infinity}` so gun keeps the connection open for the whole listening session. Since that 15s close was also the only thing tearing down abandoned streams, add explicit teardown: the chunked loop signals the pump (proxy_gun_ffi:stop/1) when send_chunk to the client fails, so the upstream is closed promptly when a listener leaves (the pump's DATA_TIMEOUT remains the backstop for upstream stalls).
1 month ago