asteroid/docs/DOCKER-STREAMING.org

18 KiB

Asteroid Radio - Docker Streaming Setup

Docker Streaming Overview

This guide covers the complete Docker-based streaming setup for Asteroid Radio using Icecast2 and Liquidsoap containers. This approach provides a containerized, portable streaming infrastructure that's easy to deploy and maintain.

Key Features

  • Stream queue control system for curated playlists
  • ReplayGain audio processing for consistent volume
  • Automatic fallback to random playback
  • Multi-format streaming (AAC, MP3 high/low)
  • Telnet control interface for live DJ operations

Architecture

Container Stack

  • Icecast2 Container: Streaming server handling client connections
  • Liquidsoap Container: Audio processing and stream generation
  • Shared Volumes: Music library and configuration sharing

Stream Formats

  • High Quality MP3: 128kbps MP3 stream at /asteroid.mp3
  • High Quality AAC: 96kbps AAC stream at /asteroid.aac (better efficiency than MP3)
  • Low Quality MP3: 64kbps MP3 stream at /asteroid-low.mp3 (compatibility)

Audio Processing

  • ReplayGain: Consistent volume without pumping artifacts
  • Crossfading: Smooth 5-second transitions between tracks
  • Compression: Dynamic compression to prevent clipping
  • Fallback: Emergency sine wave if all sources fail

Network Configuration

  • Icecast2: Port 8000 (streaming and admin)
  • Liquidsoap Telnet: Port 1234 (remote control)
  • Internal Network: Container-to-container communication

Quick Start

Prerequisites

# Install Docker and Docker Compose
sudo apt update
sudo apt install docker.io docker compose
sudo usermod -a -G docker $USER
# Log out and back in for group changes

One-Command Setup

# Clone and start
git clone <repository-url> asteroid-radio
cd asteroid-radio/docker
docker compose up -d

Verify Setup

# Check container status
docker compose ps

# Test streaming (all three formats)
curl -I http://localhost:8000/asteroid.mp3     # 128kbps MP3
curl -I http://localhost:8000/asteroid.aac     # 96kbps AAC
curl -I http://localhost:8000/asteroid-low.mp3 # 64kbps MP3

Docker Compose Configuration

Complete docker-compose.yml

version: '3.8'

services:
  icecast:
    image: infiniteproject/icecast:latest
    container_name: asteroid-icecast
    ports:
      - "8000:8000"
    volumes:
      - ./icecast.xml:/etc/icecast.xml
    environment:
      - ICECAST_SOURCE_PASSWORD=H1tn31EhsyLrfRmo
      - ICECAST_ADMIN_PASSWORD=asteroid_admin_2024
      - ICECAST_RELAY_PASSWORD=asteroid_relay_2024
    restart: unless-stopped
    networks:
      - asteroid-network

  liquidsoap:
    build:
      context: .
      dockerfile: Dockerfile.liquidsoap
    container_name: asteroid-liquidsoap
    ports:
      - "1234:1234"  # Telnet control port
    depends_on:
      - icecast
    volumes:
      - ./music:/app/music:ro
      - ./asteroid-radio-docker.liq:/app/asteroid-radio.liq:ro
      - ../stream-queue.m3u:/app/stream-queue.m3u:ro  # Stream queue control
    restart: unless-stopped
    networks:
      - asteroid-network

networks:
  asteroid-network:
    driver: bridge

Container Configurations

Icecast2 Container Setup

Custom Icecast Configuration (icecast.xml)

<icecast>
    <location>Asteroid Radio Docker</location>
    <admin>admin@asteroid-radio.docker</admin>
    
    <limits>
        <clients>100</clients>
        <sources>10</sources>
        <queue-size>524288</queue-size>
        <client-timeout>30</client-timeout>
        <header-timeout>15</header-timeout>
        <source-timeout>10</source-timeout>
        <burst-on-connect>1</burst-on-connect>
    </limits>
    
    <authentication>
        <source-password>H1tn31EhsyLrfRmo</source-password>
        <relay-password>asteroid_relay_2024</relay-password>
        <admin-user>admin</admin-user>
        <admin-password>asteroid_admin_2024</admin-password>
    </authentication>
    
    <hostname>icecast</hostname>
    <listen-socket>
        <port>8000</port>
        <bind-address>0.0.0.0</bind-address>
    </listen-socket>
    
    <!-- High Quality Stream -->
    <mount type="normal">
        <mount-name>/asteroid.mp3</mount-name>
        <username>source</username>
        <password>H1tn31EhsyLrfRmo</password>
        <max-listeners>50</max-listeners>
        <public>1</public>
        <stream-name>Asteroid Radio - High Quality</stream-name>
        <stream-url>http://localhost:8080/asteroid/</stream-url>
        <genre>Electronic/Alternative</genre>
        <bitrate>128</bitrate>
    </mount>
    
    <!-- AAC High Quality Stream -->
    <mount type="normal">
        <mount-name>/asteroid.aac</mount-name>
        <username>source</username>
        <password>H1tn31EhsyLrfRmo</password>
        <max-listeners>50</max-listeners>
        <public>1</public>
        <stream-name>Asteroid Radio - AAC</stream-name>
        <stream-description>Music for Hackers - 96kbps AAC</stream-description>
        <stream-url>http://localhost:8080/asteroid/</stream-url>
        <genre>Electronic/Alternative</genre>
        <bitrate>96</bitrate>
    </mount>
    
    <!-- Low Quality Stream -->
    <mount type="normal">
        <mount-name>/asteroid-low.mp3</mount-name>
        <username>source</username>
        <password>H1tn31EhsyLrfRmo</password>
        <max-listeners>100</max-listeners>
        <public>1</public>
        <stream-name>Asteroid Radio - Low Quality</stream-name>
        <stream-description>Music for Hackers - 64kbps</stream-description>
        <stream-url>http://localhost:8080/asteroid/</stream-url>
        <genre>Electronic/Alternative</genre>
        <bitrate>64</bitrate>
    </mount>
    
    <fileserve>1</fileserve>
    <paths>
        <basedir>/usr/share/icecast2</basedir>
        <logdir>/var/log/icecast2</logdir>
        <webroot>/usr/share/icecast2/web</webroot>
        <adminroot>/usr/share/icecast2/admin</adminroot>
        <alias source="/" destination="/status.xsl"/>
    </paths>
    
    <logging>
        <accesslog>access.log</accesslog>
        <errorlog>error.log</errorlog>
        <loglevel>3</loglevel>
        <logsize>10000</logsize>
    </logging>
</icecast>

Liquidsoap Container Setup

Liquidsoap Configuration (asteroid-radio-docker.liq)

#!/usr/bin/liquidsoap

# Asteroid Radio - Docker streaming script
# Streams music library continuously to Icecast2 running in Docker

# Allow running as root in Docker
set("init.allow_root", true)

# Set log level for debugging
log.level.set(4)

# Enable telnet server for remote control
settings.server.telnet.set(true)
settings.server.telnet.port.set(1234)
settings.server.telnet.bind_addr.set("0.0.0.0")

# Create playlist source from managed stream queue
radio = playlist(
  mode="normal",  # Play in order (not randomized)
  reload=5,       # Check for playlist updates every 5 seconds
  reload_mode="watch",  # Watch file for changes
  "/app/stream-queue.m3u"
)

# Fallback to directory scan if queue is empty
radio_fallback = playlist.safe(
  mode="randomize",
  reload=3600,
  "/app/music/"
)

radio = fallback(track_sensitive=false, [radio, radio_fallback])

# Use ReplayGain for consistent volume without pumping
radio = amplify(1.0, override="replaygain", radio)

# Add smooth crossfade between tracks (5 seconds)
radio = crossfade(
  duration=5.0,
  fade_in=3.0,
  fade_out=3.0,
  radio
)

# Add compressor to prevent clipping
radio = compress(
  ratio=3.0,
  threshold=-15.0,
  attack=50.0,
  release=400.0,
  radio
)

# Create a fallback with emergency content
emergency = sine(440.0)
emergency = amplify(0.1, emergency)

# Make source safe with fallback
radio = fallback(track_sensitive=false, [radio, emergency])

# Add metadata
radio = map_metadata(fun(m) -> 
  [("title", m["title"] ?? "Unknown Track"),
   ("artist", m["artist"] ?? "Unknown Artist"),
   ("album", m["album"] ?? "Unknown Album")], radio)

# High Quality MP3 Stream (128kbps)
output.icecast(
  %mp3(bitrate=128),
  host="icecast",  # Docker service name
  port=8000,
  password="H1tn31EhsyLrfRmo",
  mount="asteroid.mp3",
  name="Asteroid Radio",
  description="Music for Hackers - Streaming from the Asteroid",
  genre="Electronic/Alternative",
  url="http://localhost:8080/asteroid/",
  public=true,
  radio
)

# AAC High Quality Stream (96kbps - better quality than 128kbps MP3)
output.icecast(
  %fdkaac(bitrate=96),
  host="icecast",
  port=8000,
  password="H1tn31EhsyLrfRmo",
  mount="asteroid.aac",
  name="Asteroid Radio (AAC)",
  description="Music for Hackers - High efficiency AAC stream",
  genre="Electronic/Alternative",
  url="http://localhost:8080/asteroid/",
  public=true,
  radio
)

# Low Quality MP3 Stream (for compatibility)
output.icecast(
  %mp3(bitrate=64),
  host="icecast",
  port=8000,
  password="H1tn31EhsyLrfRmo",
  mount="asteroid-low.mp3",
  name="Asteroid Radio (Low Quality)",
  description="Music for Hackers - Low bandwidth stream",
  genre="Electronic/Alternative",
  url="http://localhost:8080/asteroid/",
  public=true,
  radio
)

print("🎵 Asteroid Radio Docker streaming started!")
print("High Quality MP3:  http://localhost:8000/asteroid.mp3")
print("High Quality AAC:  http://localhost:8000/asteroid.aac")
print("Low Quality MP3:   http://localhost:8000/asteroid-low.mp3")
print("Icecast Admin:     http://localhost:8000/admin/")
print("Telnet control:    telnet localhost 1234")

Management Scripts

Start Script (start-streaming.sh)

#!/bin/bash

# Asteroid Radio Docker Streaming Startup Script

set -e

echo "🚀 Starting Asteroid Radio Docker Streaming..."

# Check if Docker is running
if ! docker info > /dev/null 2>&1; then
    echo "❌ Docker is not running. Please start Docker first."
    exit 1
fi

# Create required directories
mkdir -p music/incoming music/library logs

# Set permissions
chmod 755 music/incoming music/library
chmod 777 logs

# Pull latest images
echo "📦 Pulling latest Docker images..."
docker compose pull

# Start services
echo "🎵 Starting streaming services..."
docker compose up -d

# Wait for services to be ready
echo "⏳ Waiting for services to start..."
sleep 10

# Check service status
echo "📊 Checking service status..."
docker compose ps

# Test connectivity
echo "🔍 Testing streaming connectivity..."
if curl -s -I http://localhost:8000/asteroid.mp3 | grep -q "200 OK"; then
    echo "✅ High quality stream is working"
else
    echo "⚠️  High quality stream may not be ready yet"
fi

if curl -s -I http://localhost:8000/asteroid-low.mp3 | grep -q "200 OK"; then
    echo "✅ Low quality MP3 stream is working"
else
    echo "⚠️  Low quality MP3 stream may not be ready yet"
fi

if curl -s -I http://localhost:8000/asteroid.aac | grep -q "200 OK"; then
    echo "✅ AAC stream is working"
else
    echo "⚠️  AAC stream may not be ready yet"
fi

echo ""
echo "🎉 Asteroid Radio Docker setup complete!"
echo ""
echo "📻 Stream URLs:"
echo "   High Quality MP3: http://localhost:8000/asteroid.mp3 (128kbps)"
echo "   High Quality AAC: http://localhost:8000/asteroid.aac (96kbps)"
echo "   Low Quality MP3:  http://localhost:8000/asteroid-low.mp3 (64kbps)"
echo ""
echo "🔧 Admin Interfaces:"
echo "   Icecast:      http://localhost:8000/admin/ (admin/asteroid_admin_2024)"
echo "   Telnet:       telnet localhost 1234"
echo ""
echo "📁 Add music files to: ./music/"
echo "   Files are automatically detected and streamed."

Stop Script (stop-streaming.sh)

#!/bin/bash

# Asteroid Radio Docker Streaming Stop Script

echo "🛑 Stopping Asteroid Radio Docker Streaming..."

# Stop all services
docker compose down

# Optional: Remove volumes (uncomment to clean up completely)
# docker compose down -v

echo "✅ All services stopped."

Test Script (test-streaming.sh)

#!/bin/bash

# Asteroid Radio Docker Streaming Test Script

echo "🧪 Testing Asteroid Radio Docker Setup..."

# Test container status
echo "📊 Container Status:"
docker compose ps

echo ""
echo "🔍 Testing Connectivity:"

# Test Icecast2
if curl -s -I http://localhost:8000/ | grep -q "200 OK"; then
    echo "✅ Icecast2 server is responding"
else
    echo "❌ Icecast2 server is not responding"
fi

# Test high quality stream
if curl -s -I http://localhost:8000/asteroid.mp3 | grep -q "200 OK"; then
    echo "✅ High quality stream is available"
else
    echo "❌ High quality stream is not available"
fi

# Test low quality stream
if curl -s -I http://localhost:8000/asteroid-low.mp3 | grep -q "200 OK"; then
    echo "✅ Low quality MP3 stream is available"
else
    echo "❌ Low quality MP3 stream is not available"
fi

# Test AAC stream
if curl -s -I http://localhost:8000/asteroid.aac | grep -q "200 OK"; then
    echo "✅ AAC stream is available"
else
    echo "❌ AAC stream is not available"
fi

echo ""
echo "📋 Service Logs (last 10 lines):"
echo "--- Icecast2 ---"
docker compose logs --tail=10 icecast

echo "--- Liquidsoap ---"
docker compose logs --tail=10 liquidsoap

Stream Queue Control

Overview

The Docker setup integrates with Asteroid's stream queue control system, allowing you to curate exactly what plays on the broadcast stream.

How It Works

  1. Asteroid web app manages stream-queue.m3u file in the project root
  2. File is mounted into Liquidsoap container at /app/stream-queue.m3u
  3. Liquidsoap watches the file and reloads every 5 seconds
  4. When queue is empty, falls back to random playback from music directory

Managing the Queue

Use the Asteroid web API or admin interface to control the stream queue:

# Add track to queue (requires admin authentication)
curl -X POST http://localhost:8080/api/asteroid/stream/queue/add \
  -d "track-id=42" \
  -b cookies.txt

# View current queue
curl http://localhost:8080/api/asteroid/stream/queue -b cookies.txt

# Clear queue (falls back to random)
curl -X POST http://localhost:8080/api/asteroid/stream/queue/clear \
  -b cookies.txt

See docs/STREAM-CONTROL.org for complete queue management documentation.

Volume Management

Music Library Setup

# Music directory already exists in repository
# Copy sample music directly to the music directory
cp ~/path/to/music/*.mp3 docker/music/

# Set permissions
chmod 755 docker/music/
sudo chown -R $USER:$USER docker/music/

Persistent Data

  • Music Library: ./music/ - Mounted as volume
  • Logs: ./logs/ - Container logs and streaming logs
  • Configuration: ./liquidsoap/ and ./icecast.xml - Read-only configs

Networking

Internal Container Network

  • Containers communicate via asteroid-network bridge
  • Liquidsoap connects to Icecast using hostname icecast
  • Telnet control available on port 1234 for Liquidsoap management

External Access

  • Port 8000: Icecast2 streaming and admin interface
  • Port 1234: Liquidsoap telnet control interface
  • All services bind to 0.0.0.0 for external access

WSL Compatibility

# Find WSL IP for external access
ip addr show eth0 | grep inet

# Access from Windows host
# http://[IP-ADDRESS]:8000/asteroid.mp3     # 128kbps MP3
# http://[IP-ADDRESS]:8000/asteroid.aac     # 96kbps AAC  
# http://[IP-ADDRESS]:8000/asteroid-low.mp3 # 64kbps MP3

Production Deployment

Docker Swarm Setup

# docker compose.prod.yml
version: '3.8'

services:
  icecast:
    image: moul/icecast
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
    # ... rest of configuration

  liquidsoap:
    image: savonet/liquidsoap:v2.2.x
    deploy:
      replicas: 1
      restart_policy:
        condition: on-failure
    # ... rest of configuration

Environment Variables

# Production environment
export ASTEROID_ENV=production
export ASTEROID_STREAM_QUALITY=high
export ASTEROID_MAX_LISTENERS=200
export ICECAST_ADMIN_PASSWORD=secure_password_here

SSL/TLS Setup

Use reverse proxy (nginx/traefik) for HTTPS termination:

# Add to docker-compose.yml
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/ssl:ro

Monitoring and Logging

Container Health Checks

# Check container health
docker compose exec icecast curl -f http://localhost:8000/status.xsl
docker compose exec liquidsoap ps aux | grep liquidsoap

# Test telnet control interface
echo "help" | nc localhost 1234

Log Management

# View real-time logs
docker compose logs -f

# View specific service logs
docker compose logs -f icecast
docker compose logs -f liquidsoap

# Log rotation setup
docker run --log-driver=json-file --log-opt max-size=10m --log-opt max-file=3

Troubleshooting

Common Docker Issues

Container Won't Start

# Check container logs
docker compose logs [service-name]

# Check resource usage
docker stats

# Verify configuration files
docker compose config

Streaming Issues

# Test internal connectivity
docker compose exec liquidsoap ping icecast

# Check Liquidsoap connection and logs
docker compose logs liquidsoap

# Test telnet interface
echo "request.queue" | nc localhost 1234

Permission Issues

# Fix music directory permissions
sudo chown -R $USER:$USER docker/music/
chmod 755 docker/music/

Performance Tuning

Resource Limits

# Add to services in docker-compose.yml
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
        reservations:
          memory: 256M
          cpus: '0.25'

Network Optimization

# Optimize network settings
networks:
  asteroid-network:
    driver: bridge
    driver_opts:
      com.docker.network.driver.mtu: 1500

This Docker streaming setup provides a complete containerized solution for Asteroid Radio with professional streaming capabilities and easy deployment.