Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Examples and Recipes

DatanoiseTV edited this page Jun 18, 2026 · 1 revision

Examples and Recipes

End-to-end setups for real deployments. Each recipe is a tinyice.json fragment (merge the keys into your single config file) plus the commands to drive and verify it. For the meaning of every key, see Configuration; for the mechanics behind each feature, follow the "See also" links.

Apply config changes with ./tinyice reload (or kill -HUP <pid>) — no restart, no dropped listeners.

Stations24/7 automated radio · Live DJ with AutoDJ fallback · Multiple stations on one box · Talk / podcast station VideoOBS to HLS with a website embed · Low-latency WebRTC · Adaptive bitrate ladder Delivery & scaleMobile-friendly transcodes · Edge relay / scale-out IntegrationsNow playing everywhere DeploymentDocker Compose · systemd in production · Behind Cloudflare / a reverse proxy · Prometheus + Grafana · Migrating from Icecast


24/7 automated radio

A station that plays a music library around the clock, shuffles, injects track metadata, lists publicly, and announces each song to a Discord channel.

{
 "page_title": "Nightwave Radio",
 "page_subtitle": "Synthwave, all night",
 "base_url": "https://radio.example.com",
 "autodjs": [
 {
 "name": "Nightwave",
 "mount": "/nightwave",
 "music_dir": "/music/synthwave",
 "format": "mp3",
 "bitrate": 128,
 "enabled": true,
 "loop": true,
 "inject_metadata": true,
 "visible": true
 }
 ],
 "webhooks": [
 {
 "name": "Discord now-playing",
 "url": "https://discord.com/api/webhooks/<id>/<token>",
 "method": "POST",
 "events": ["now_playing"],
 "body_template": "{\"content\": \":notes: **{{.Artist}} – {{.Title}}**{{if .MountURL}} · [Listen]({{.MountURL}}){{end}}\"}",
 "enabled": true
 }
 ]
}

Listen at https://radio.example.com/nightwave or /player/nightwave. Set base_url so the webhook can build the Listen link.

See also: AutoDJ · Webhooks.

Live DJ with AutoDJ fallback

A live mount for a human DJ that automatically falls back to a 24/7 AutoDJ when the DJ disconnects — listeners stay connected through the gap.

{
 "autodjs": [
 { "name": "Auto", "mount": "/auto", "music_dir": "/music/rotation",
 "format": "mp3", "bitrate": 128, "enabled": true, "loop": true,
 "inject_metadata": true, "visible": false }
 ],
 "fallback_mounts": { "/live": "/auto" }
}

The DJ connects an Icecast/BUTT source to /live (mount password = the DJ's source password). While /live has no source, listeners are served /auto; when the DJ goes live, /live takes over. Keep the fallback mount visible: false so only /live is advertised.

See also: Streaming Sources · Configuration → Mounts.

Multiple stations on one box

Several independent stations from one process — each its own mount, library, and format.

{
 "autodjs": [
 { "name": "Chill", "mount": "/chill", "music_dir": "/music/chill",
 "format": "mp3", "bitrate": 128, "enabled": true, "loop": true, "inject_metadata": true, "visible": true },
 { "name": "Jazz", "mount": "/jazz", "music_dir": "/music/jazz",
 "format": "opus", "bitrate": 96, "enabled": true, "loop": true, "inject_metadata": true, "visible": true },
 { "name": "Ambient", "mount": "/ambient", "music_dir": "/music/ambient",
 "format": "mp3", "bitrate": 96, "enabled": true, "loop": true, "inject_metadata": true, "visible": true }
 ]
}

All three appear on /explore. Raise max_listeners to your bandwidth budget (Deployment#sizing).

Talk / podcast station

Speech content benefits from Opus tuned for voice and a lower bitrate. Use a transcoder (or AutoDJ format: opus) with the VoIP application profile:

{
 "transcoders": [
 {
 "name": "talk-voip",
 "input_mount": "/talk-src",
 "output_mount": "/talk",
 "format": "opus",
 "bitrate": 48,
 "opus_application": "voip",
 "channels": 1,
 "enabled": true
 }
 ]
}

Feed /talk-src from your live source; listeners use /talk.

See also: Transcoding#opus-tuning.


OBS to HLS with a website embed

Stream video from OBS and embed the player on any web page.

  1. Enable RTMP and (optionally) set a public URL:
    { "base_url": "https://radio.example.com",
     "ingest": { "rtmp_enabled": true, "rtmp_port": "1935" } }
  2. Create the mount /live in Admin → Streams; its source password is the OBS Stream Key.
  3. OBS → Stream (Custom): Server rtmp://radio.example.com/live, Stream Key = that password. Output: x264 + AAC, 1 s keyframe interval.
  4. Embed it:
    <iframe src="https://radio.example.com/embed/live"
     width="100%" height="360" frameborder="0"
     allow="autoplay; fullscreen"></iframe>

Direct HLS for external players: https://radio.example.com/live/playlist.m3u8.

See also: Streaming Sources#video-from-obs-rtmp · Playback and Output.

Low-latency WebRTC (WHEP)

Sub-second playback for IRL/event streams. The encoder must publish without B-frames.

  • OBS → Output (Advanced) → x264 options: add bf=0 (or set Profile to baseline). Keep RTMP ingest as above.
  • Watch: open https://radio.example.com/player/live?webrtc=1, or POST an SDP offer to https://radio.example.com/live/whep (Content-Type: application/sdp).

HLS remains available on the same mount as the robust fallback.

See also: Playback and Output#webrtc-playback-whep.

Adaptive bitrate ladder

Let players pick a rendition. Run one OBS output per rendition to its own mount, then group them into a multivariant playlist.

{
 "ingest": { "rtmp_enabled": true, "rtmp_port": "1935" },
 "variant_groups": { "/live": ["/live", "/live_720", "/live_480"] }
}

In OBS use the "Multiple Outputs" plugin (or three OBS instances) publishing to /live (source quality), /live_720, and /live_480. Viewers open /player/live; the player loads /live/master.m3u8 and hls.js does ABR. BANDWIDTH / RESOLUTION are derived from each member's live ingest metrics.

See also: Playback and Output#obs-simulcast-abr-ladder.


Mobile-friendly transcodes

Publish one good source, serve cheaper renditions automatically.

{
 "transcoders": [
 { "name": "live-opus-64", "input_mount": "/live", "output_mount": "/live-opus",
 "format": "opus", "bitrate": 64, "enabled": true }
 ],
 "auto_transcode_mp3_bitrates": [128, 64]
}

The transcoder gives you a 64 kbps Opus mount at /live-opus. The auto_transcode_mp3_bitrates list spawns ephemeral MP3 mounts (/live-mp3-128, /live-mp3-64) for any non-MP3 source the moment it connects — handy when you ingest Ogg/Opus but want MP3 fallbacks without declaring each one.

See also: Transcoding.

Edge relay / scale-out

Put a TinyIce node close to listeners and have it pull from your origin. Each edge serves its own listeners; the origin sees one connection per edge.

{
 "relays": [
 { "url": "https://origin.example.com/nightwave", "mount": "/nightwave",
 "password": "", "burst_size": 65536, "enabled": true }
 ]
}

Run this config on each edge box (different region/host). ICY "now playing" metadata is carried through. Relay chaining works; shared cluster state does not — scale each node to its bandwidth ceiling and add edges (Architecture#whats-intentionally-not-here).

See also: Streaming Sources#pulling-a-relay.


Now playing everywhere

Announce track changes to chat platforms and a directory at once: webhooks for HTTP targets, on_play_command for a local script (e.g. TuneIn AIR).

{
 "webhooks": [
 { "name": "Telegram", "url": "https://api.telegram.org/bot<token>/sendMessage",
 "method": "POST", "content_type": "application/json", "events": ["now_playing"],
 "body_template": "{\"chat_id\":\"<chat>\",\"text\":\"Now playing: {{.Artist}} – {{.Title}}\"}",
 "enabled": true }
 ],
 "autodjs": [
 { "name": "Nightwave", "mount": "/nightwave", "music_dir": "/music/synthwave",
 "format": "mp3", "bitrate": 128, "enabled": true, "loop": true,
 "inject_metadata": true, "visible": true,
 "on_play_command": "/opt/tinyice/tunein.sh", "on_play_command_timeout": 10 }
 ]
}

/opt/tinyice/tunein.sh gets the track in environment variables:

#!/bin/bash
curl -s "https://air.radiotime.com/Playing.ashx?partnerId=$P&partnerKey=$K&id=$ID" \
 --data-urlencode "title=${TINYICE_TITLE}" \
 --data-urlencode "artist=${TINYICE_ARTIST}"

Available env vars: TINYICE_ARTIST, TINYICE_TITLE, TINYICE_ALBUM, TINYICE_FILE, TINYICE_MOUNT. The editor ships presets for Discord, Slack, Teams, ntfy, Pushover, TuneIn AIR, and more.

See also: Webhooks · AutoDJ#track-hooks.


Docker Compose

The published image listens on 8080 inside the container and reads its config from /data/config.json, so map the port host:8080 and mount a volume at /data.

services:
 tinyice:
 image: ghcr.io/datanoisetv/tinyice:latest
 restart: unless-stopped
 ports:
 - "8000:8080" # http / Icecast (reach it on http://host:8000)
 - "1935:1935" # RTMP (only if you ingest video)
 - "9000:9000/udp" # SRT (only if you ingest SRT)
 volumes:
 - tinyice-data:/data # config.json, history.db, playlists, ACME certs
volumes:
 tinyice-data:

docker compose up -d, then read the generated admin password from docker compose logs tinyice. Edit /data/config.json (in the volume) for the recipes above and docker compose exec tinyice tinyice reload.

For direct ACME on 80/443, the binary or .deb/.rpm on the host is simpler — see systemd in production. To do it in a container, publish 80:80/443:443, override the command with -port 80 -https-port 443 -config /data/config.json, and set use_https/auto_https/domains in the config.

See also: Installation#docker-ghcr-multi-arch.

systemd in production

Install the package, then bring the service up deliberately.

sudo dpkg -i tinyice_*.deb # or: sudo rpm -i tinyice-*.rpm
sudo systemctl unmask tinyice # the unit ships masked on purpose
sudo systemctl enable --now tinyice
sudo journalctl -u tinyice | grep -A4 "FIRST RUN" # generated credentials

Config lives at /etc/tinyice/tinyice.json, state in /var/lib/tinyice. The binary is granted cap_net_bind_service, so a direct-ACME config can bind 80/443 with no proxy:

{ "use_https": true, "auto_https": true, "port": "80", "https_port": "443",
 "domains": ["radio.example.com"], "acme_email": "admin@example.com" }
sudo systemctl reload tinyice # apply config (SIGHUP)

See also: Deployment#systemd · Deployment#auto-https-acme.

Behind Cloudflare / a reverse proxy

A proxy is optional — TinyIce does its own TLS. If you put one in front anyway (WAF, shared edge, path routing), declare it as trusted and turn off response buffering.

{ "trusted_proxies": ["127.0.0.1", "10.0.0.0/8", "173.245.48.0/20"] }

nginx location for the stream/HLS/SSE paths:

location / {
 proxy_pass http://127.0.0.1:8000;
 proxy_http_version 1.1;
 proxy_buffering off; # streaming + SSE must not be buffered
 proxy_set_header Host $host;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_read_timeout 3600s;
}

With trusted_proxies set, loopback stops being auto-whitelisted and bans/scan detection see real client IPs. Get the list wrong and either bans break or everyone looks trusted.

See also: Security#behind-a-reverse-proxy · Deployment#do-i-need-a-reverse-proxy.

Prometheus + Grafana

Metrics and pprof are served on the internal port :8081 (firewall it).

# prometheus.yml
scrape_configs:
 - job_name: tinyice
 static_configs:
 - targets: ["10.0.0.10:8081"]

Import monitoring/grafana-dashboard.json into Grafana. In Docker, scrape over the compose network or publish 127.0.0.1:8081:8081 — don't expose :8081 publicly.

See also: Observability.

Migrating from Icecast

TinyIce speaks the Icecast 2 source and listener protocols, so existing encoders and players keep working:

  1. Point your encoder (BUTT, Mixxx, ffmpeg, hardware) at TinyIce: same host:port, same mount, the mount's source password. No encoder change.
  2. Listeners keep their http://host:8000/<mount> URLs and .m3u / .pls playlist links.
  3. /status-json.xsl keeps Icecast-compatible stats tooling working.
  4. Then adopt the extras incrementally — HLS at /<mount>/playlist.m3u8, an AutoDJ, transcodes, a built-in player, ACME HTTPS.

See also: Streaming Sources · Playback and Output.


Looking for the reference behind a setting? Configuration. Stuck? Troubleshooting and FAQ.

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /