Broadcast your DAW to a URL. Icecast in, WebRTC out. home
flim.sh is a glom.sh, but for live audio. Point your DAW's broadcast plugin at a flim.sh mount, share the link, and anyone who opens it hears your set live in the browser — no app, no install.
your DAW --icecast (MP3/OGG)--> flim.sh --webrtc--> listeners
transcode (just a
to Opus browser)
Three stages, one per hop above.
Broadcast tools (BUTT, Mixxx, DAW plugins) already know how to push a continuous MP3 or OGG
stream to an Icecast mount point using HTTP Basic auth. flim.sh speaks that protocol, so
any of them can connect to a mount named after your slug — e.g. /midnight-set — with
the username source and your stream's source password.
Browsers don't play Icecast streams over WebRTC directly, so flim.sh re-encodes your incoming audio to Opus (48 kHz, the codec WebRTC speaks) in real time with ffmpeg.
Listeners connect with WHEP (WebRTC-HTTP Egress Protocol): the browser sends one SDP offer, the server returns one answer, and audio flows. A single shared Opus track is added to every listener's connection, so one encode fans out to everyone — and that track outlives any single source connection, so you can drop and reconnect your DAW without kicking listeners off. Latency is typically well under a second.
With flim.sh running (npm start — HTTP on 8000, ingest
on 8001):
Mints a slug, an api_key (keep it — that's how you manage the stream later), and
source credentials for your DAW.
# with the CLI (saves your api_key locally)
node bin/flim.js create --title "Midnight set"
# ...or with curl
curl -X POST https://flim.sh/api/streams \
-H 'content-type: application/json' \
-d '{"title":"Midnight set"}'
Fastest test — a tone straight from the CLI:
node bin/flim.js stream <slug> --tone
# or a file: node bin/flim.js stream <slug> --file set.mp3
For a real set, point your DAW or BUTT at the source credentials from step 1.
Open https://flim.sh/<slug> and hit Tune in. Share that URL with anyone.
Run node bin/flim.js ls to see which streams are live and how many people are listening.
| Term | What it is |
|---|---|
slug | Public name of your stream, e.g. neon-drift-7f2a. Listen URL is https://flim.sh/<slug>; mount point is /<slug>. |
api_key | Owner key, returned when you claim a stream. Pass as Authorization: Bearer <key> to list or delete. Reuse it to group streams under one account. |
sourcePassword | Icecast source password your DAW/BUTT uses to push audio, with username source. |
mount point | The Icecast path your broadcaster connects to — always /<slug>. |
WHEP | The WebRTC signalling standard listeners use. Handled by the player page; you never touch it. |
Keep api_key and sourcePassword secret — anyone with your
source password can broadcast to your slug.
Every method ends the same way: an Icecast source pushing MP3 (or OGG) to your flim.sh mount. You
need these values from flim create (or the API response's ingest block):
| Field | Value |
|---|---|
| Server / host | flim.sh |
| Port | 8001 |
| Mount | /<your-slug> |
| Username | source |
| Password | your sourcePassword |
| Format | MP3 or OGG, stereo |
Secure your source password: if the server exposes a TLS ingest (port 8443
by default), enable your tool's SSL/TLS option and use that port so your password is
encrypted in transit. The CLI uses flim stream <slug> --tls.
Some tools (Mixxx, VirtualDJ) speak Icecast natively — point them straight at flim.sh. Production DAWs (Ableton, Logic, FL Studio) don't broadcast on their own, so you route their audio through a virtual audio cable into BUTT, which does the streaming.
BUTT ("Broadcast Using This Tool") is a free encoder for macOS, Windows and Linux. It streams whatever audio input you give it — the reliable path for any DAW.
Icecast and fill in:
Address : flim.sh
Port : 8001
Password : <your sourcePassword>
Icecast mountpoint : /<your-slug>
Icecast user : source
https://flim.sh/<your-slug> and hit Tune in.The username must be exactly source — a 401 almost always means a wrong password or username.
Mixxx is free DJ software with Icecast broadcasting built in — no virtual cable needed.
Icecast 2 and fill in:
Host : flim.sh
Port : 8001
Mount : /<your-slug>
Login : source
Password : <your sourcePassword>
https://flim.sh/<your-slug> and hit Tune in.These DAWs don't broadcast on their own. Send the master output into a virtual audio device, then
have BUTT capture that device and stream it:
DAW master → virtual cable → BUTT → flim.sh.
https://flim.sh/<your-slug>.flim.sh, port 8001, mount
/<your-slug>, user source, and your source password.https://flim.sh/<your-slug> to listen.The bundled CLI speaks the Icecast source protocol itself, so it's both a test source and a way to broadcast a file without other software.
node bin/flim.js stream <slug> --tone
node bin/flim.js stream <slug> --file ~/sets/live.mp3
node bin/flim.js stream <slug> --url https://example.com/feed.mp3
If you ran flim create, the source password is remembered; otherwise pass --password.
| Command | Does |
|---|---|
flim create [--title "…"] | Claim a slug; prints listen URL + source creds and saves your api_key to ~/.config/flim/config.json. |
flim ls | List your streams with live status and listener count. |
flim rm <slug> | Stop and delete a stream. |
flim stream <slug> [--tone|--file f|--url u] | Broadcast over the Icecast source protocol. |
Global flags: --server <url> (or FLIM_SERVER); --password,
--host, --port for stream.
Provisioning is a plain REST API, so a DJ's own agent can claim a stream and hand the DAW its
credentials with no clicking. Auth, when needed, is Authorization: Bearer <api_key>.
| Method & path | Auth | Does |
|---|---|---|
POST /api/streams | optional | Claim a slug. Returns slug, apiKey, and an ingest block. Pass an existing key as Bearer to add to that account. |
GET /api/streams | Bearer | List your streams with live + listeners. |
DELETE /api/streams/:slug | Bearer | Stop and delete a stream. |
POST /whep/:slug | none | WHEP: SDP offer in, SDP answer out. |
DELETE /whep/:slug/:id | none | End a listener session. |
GET /:slug | none | The listener player page. |
curl -X POST https://flim.sh/api/streams \
-H 'content-type: application/json' \
-d '{"title":"Midnight set"}'
# -> { "slug": "...", "apiKey": "...", "listenUrl": "...",
# "ingest": { "host": "...", "port": 8001, "mount": "/...",
# "username": "source", "password": "..." } }
flim.sh is a small Node app — no database (streams persist to a JSON file), no system ffmpeg (a static build is bundled).
npm install
npm start # HTTP on 8000, Icecast ingest on 8001
| Env var | Default | Meaning |
|---|---|---|
FLIM_HTTP_PORT | 8000 | API + WHEP + player + docs |
FLIM_INGEST_PORT | 8001 | Icecast source ingest |
FLIM_PUBLIC_ORIGIN | — | e.g. https://flim.sh; else derived from the request |
FLIM_OPUS_BITRATE | 128k | Egress Opus bitrate |
FLIM_ICE_SERVERS | — | JSON array of STUN/TURN servers, injected into both the server and the player. Needed to reach listeners across the internet. |
FLIM_ICE_TRANSPORT_POLICY | all | Set to relay to force media through TURN (recommended behind ngrok/home NAT). |
FLIM_ANNOUNCED_IP | — | The VM's public IP, advertised as a WebRTC host candidate (required on 1:1-NAT clouds like Oracle/GCP/AWS). |
FLIM_ICE_PORT_RANGE | — | min-max UDP range for WebRTC media, so it can be firewalled (e.g. 40000-40100). |
FLIM_HTTP_HOST | 0.0.0.0 | Bind address for HTTP. Set 127.0.0.1 when a TLS proxy (Caddy) fronts it. |
FLIM_INGEST_PUBLIC | — | host:port of a separately-tunnelled ingest (e.g. an ngrok TCP endpoint), reported to remote DJs instead of the local address. |
Deploying for real? Run it on a public-IP VM — listeners then connect directly
over UDP and you can skip TURN entirely. There's a full walkthrough (systemd + Caddy TLS + firewall
ports) in deploy/README.md, with a one-shot deploy/setup.sh.
ngrok tunnels TCP/HTTP but not WebRTC's UDP media, so it can't carry the audio by itself — you'll get "Live now" and silence. The fix is a free TURN server to relay the media (both sides send outbound UDP to it, which NAT allows). ngrok then only carries the player page and WHEP signalling.
export FLIM_ICE_SERVERS='[{"urls":"stun:stun.l.google.com:19302"}]'
npm start # or ./scripts/serve-public.sh
For guaranteed connectivity on any network, add a real TURN server and force relay.
Get a free key from a provider like Metered (the old
public Open Relay endpoints are dead), then:
export FLIM_ICE_SERVERS='[
{"urls":"stun:stun.l.google.com:19302"},
{"urls":"turn:YOUR.relay.metered.ca:443","username":"KEY","credential":"SECRET"}
]'
export FLIM_ICE_TRANSPORT_POLICY=relay # only with WORKING turn, else WHEP hangs
npm start
ngrok http 8000 → gives an https://…ngrok…app URL.ngrok tcp 8001 for a remote DJ):
node bin/flim.js create --title "Test"
node bin/flim.js stream <slug> --file set.mp3
https://…ngrok…app/<slug>. On ngrok's free tier they'll click through a one-time warning page, then hit Tune in.For a cleaner setup without TURN, run flim on a cheap/free cloud VM with a public IP and set
FLIM_ANNOUNCED_IP to that IP (open the WebRTC UDP ports in the firewall).
401 — wrong password, or username isn't exactly source. 404 —
the mount must be /<your-slug>, slash included. Also check you're pointing at the
ingest port (8001), not the HTTP port.
Make sure the DAW is actually feeding the device BUTT captures (watch BUTT's level meter), and that the browser tab isn't muted.
flim's ICE candidates are private when it runs behind NAT, so remote listeners can't reach the
media directly. Configure a TURN server via FLIM_ICE_SERVERS — see
Testing over the internet.
Yes — the shared broadcast track persists across source reconnects.