Icecast relay -- any HTTP/Icecast stream via gopher ============================================================= This applet relays HTTP/Icecast audio streams over a long-lived gopher TCP connection. Every station -- the default one or any URL you paste in -- has a per-station menu page with metadata (name, genre, bitrate, current StreamTitle when available), a Listen link (audio passthrough), a Watch link (144p MPEG-TS showcqt visualization, ~38 kbps total), and a copy-pasteable curl command after each. A "Radio menu URI:" info-line at the foot of every per-station menu is the page's stable permalink. NOTE: the bare URL /applets/icecast/icecast.lhs returns a gophermap landing menu, not audio. To play the default station, pipe the bytes at /applets/icecast/icecast.lhs/default instead: curl gopher://gopher.someodd.zip/9/applets/icecast/icecast.lhs/default | mpv - ------------------------------------------------------------ Endpoints ------------------------------------------------------------ /applets/icecast/icecast.lhs landing menu (gophermap) /applets/icecast/icecast.lhs/menu "paste a URL" prompt; type-7 query lands on the per- station menu for the submitted URL. /applets/icecast/icecast.lhs type-7 query against the landing: same as /menu + URL, produces the per-station menu. /applets/icecast/icecast.lhs/default default station, audio passthrough. /applets/icecast/icecast.lhs/default/audio default station, audio passthrough (alias of /default). /applets/icecast/icecast.lhs/default/with_visualization default station, 144p MPEG-TS showcqt visualization. /applets/icecast/icecast.lhs/default/menu per-station menu for the default station --- metadata, Listen + Watch links with curl recipes, permalink. /applets/icecast/icecast.lhs/url/ /applets/icecast/icecast.lhs/url//audio /applets/icecast/icecast.lhs/url//with_visualization /applets/icecast/icecast.lhs/url//menu arbitrary-URL station: is a base64url-encoded upstream URL with padding stripped. Hit any of these directly to bookmark a station; the encoded URLs are produced automatically when you submit a URL via the type-7 box, and printed inside the per-station menu's "Radio menu URI:" line. ------------------------------------------------------------ From the command line ------------------------------------------------------------ Default station (audio): curl gopher://gopher.someodd.zip/9/applets/icecast/icecast.lhs/default | mpv - Default station (visualization): curl gopher://gopher.someodd.zip/9/applets/icecast/icecast.lhs/default/with_visualization | mpv - Default station's menu page (gophermap with live metadata, curl recipes, permalink): curl gopher://gopher.someodd.zip/1/applets/icecast/icecast.lhs/default/menu Open a per-station menu for an arbitrary URL (selector + TAB + URL): printf '/applets/icecast/icecast.lhs/menu\thttp://ice1.somafm.com/groovesalad-128-mp3\r\n' \ | nc gopher.someodd.zip 70 The returned menu's Listen / Watch rows include base64url-encoded selectors you can curl directly: curl gopher://gopher.someodd.zip/9/applets/icecast/icecast.lhs/url/aHR0cDovL2ljZTEuc29tYWZtLmNvbS9ncm9vdmVzYWxhZC0xMjgtbXAz/audio | mpv - (equivalent to the Listen row in that menu; bookmark the "Radio menu URI" to come back to the menu later.) ------------------------------------------------------------ How this works ------------------------------------------------------------ Audio passthrough opens a TLS/HTTP connection to the upstream and copies the response body straight to stdout. Constant memory; nothing buffered through Haskell. Visualization spawns ffmpeg with the upstream URL as the input argument and showcqt as the filter; ffmpeg's stdout inherits the gopher socket directly. Menu pages do a brief one-shot fetch with `Icy-MetaData: 1` and a 4-second timeout to populate the metadata block. If the upstream is slow or offline the menu still renders --- just without the optional fields. Permalinks: per-station menus carry a "Radio menu URI:" line at the foot. For the default station it points at /applets/icecast/icecast.lhs/default/menu. For URL stations it points at /applets/icecast/icecast.lhs/url//menu, where is base64url(upstream) with padding stripped --- so the same URL always produces the same selector. When you disconnect mid-stream (Ctrl-C the nc), stdout becomes a broken pipe; the exception unwinds the script (or SIGPIPE kills ffmpeg); the upstream connection closes; the daemon reaps the process. No leaks. ------------------------------------------------------------ Limits ------------------------------------------------------------ The server caps concurrent connections at 256, so this is not a CDN. Disconnect when you're done. Invalid URLs in the type-7 boxes (anything that isn't http:// or https://) come back as a type-3 error row plus a "back to landing" link. URLs are length-capped at 2 KiB; that's the cap for the upstream URL before base64url encoding. If the upstream is offline you'll get an immediate EOF or a TLS handshake error on the streaming endpoints --- there's no graceful "the radio is offline" message at the audio-byte level. Menu endpoints emit a type-3 row when something throws, and skip metadata fields that couldn't be fetched.