# audioserver `audioserver` is a Docker Compose based home audio stack for a Raspberry Pi audio server. It combines Mopidy, Snapserver, Icecast, a React control UI, Bandcamp/Spotify credential helpers, and optional native iOS/Android clients. The intended deployment target is an always-on Linux host on the same LAN as Snapcast clients and a HiFiBerry BeoCreate DSP device. ## What This Stack Provides - **Mopidy music server** for local files, Spotify, YouTube, Beets, Iris, MPD, and HTTP JSON-RPC control. - **Snapserver multi-room audio** with streams for Mopidy, turntable input, and several TCP sources. - **Icecast FLAC stream** for browser and mobile playback outside Snapcast. - **audiocontrol web UI** served by Nginx for Mopidy, Snapcast, and BeoCreate DSP control. - **Nginx API proxy** so the browser talks to one origin instead of many backend services. - **Turntable capture helper** that records from a HiFiBerry ALSA device on the host and writes PCM into Snapserver. - **Now-playing helper service** for Snapcast metadata and audio recognition. - **GrooveAudio mobile apps** for iOS and Android streaming/control. ## Architecture ```text LAN / browser / mobile app | v frontend container, Nginx :${FRONTEND_PORT:-8180} |-- React audiocontrol UI |-- /api/mopidy/* -> mopidy:6680 |-- /ws/mopidy -> mopidy:6680 |-- /api/snapcast/rpc -> snapserver:1780/jsonrpc |-- /ws/snapcast -> snapserver:1780/jsonrpc |-- /api/hifiberry/* -> BeoCreate DSP REST API |-- /api/bandcamp/* -> bandcamp-api:8091 |-- /api/spotify/* -> bandcamp-api:8091 |-- /api/now-playing/* -> host now-playing service :8090 |-- /stream.flac -> icecast:8000/stream.flac mopidy container |-- writes 48 kHz / 16-bit / stereo PCM to /audio/mopidy.fifo |-- mirrors PCM to /audio/cava.fifo and /audio/stream.fifo stream-relay container |-- reads /audio/stream.fifo |-- encodes FLAC with ffmpeg |-- publishes to icecast:8000/stream.flac snapserver container |-- reads /audio/mopidy.fifo |-- reads /audio/turntable.fifo |-- connects to configured TCP audio sources |-- serves Snapcast clients on port 1704 host turntable-capture systemd service |-- records from HiFiBerry ALSA capture device |-- writes raw PCM to /audio/turntable.fifo ``` ## Repository Layout ```text . |-- docker-compose.yml # Main service stack |-- .env.example # Environment template |-- frontend/ # Nginx image and proxy template |-- audiocontrol/ # React/Vite control UI |-- mopidy/ # Mopidy image and config |-- snapserver/ # Snapserver image and config |-- icecast/ # Icecast image and config template |-- stream-relay/ # FIFO-to-Icecast FLAC relay |-- bandcamp-api/ # Credential helper microservice |-- scripts/ # Host helper scripts and systemd units |-- GrooveAudio (Android)/ # Native Android client |-- GrooveAudio (iOS)/ # Native iOS client |-- MOBILE_APP_INSTRUCTIONS.md # Extra mobile/networking notes `-- IMPLEMENTATION_SUMMARY.md # Historical implementation notes ``` ## Prerequisites ### Host - Linux host, preferably Raspberry Pi OS or Debian/Ubuntu. - Docker Engine and Docker Compose plugin. - `git`, `curl`, `alsa-utils`, and `systemd` for host helper services. - A reachable BeoCreate/HiFiBerry DSP API if you want DSP control from the UI. - A capture-capable ALSA device if you want turntable input. Install the common packages on Debian/Raspberry Pi OS: ```bash sudo apt update sudo apt install -y docker.io docker-compose-plugin git curl alsa-utils sudo usermod -aG docker "$USER" ``` Log out and back in after adding your user to the `docker` group. ### Optional Host Packages The now-playing helper can use Flask, NumPy, CAVA, and SongRec: ```bash sudo apt install -y python3 python3-pip cava pip3 install -r scripts/requirements.txt ``` SongRec CLI is optional and must be installed separately if you want acoustic recognition through `snapcast-now-playing.py`. ### Development Tools Only needed when developing outside Docker: - Node.js 20+ for `audiocontrol`. - Android Studio for the Android client. - Xcode for the iOS client. ## First-Time Setup ### 1. Clone The Repository Choose a stable location. The systemd units in `scripts/` expect a fixed path, so avoid temporary directories. ```bash git clone /home/admin/appdata/audioserver cd /home/admin/appdata/audioserver ``` Any path is fine, but if you use a different one, update the `WorkingDirectory`, `ExecStart`, and `EnvironmentFile` paths in the systemd service files before installing them. ### 2. Create Your Environment File ```bash cp .env.example .env nano .env ``` At minimum, review these values: ```dotenv BEOCREATE_HOST=beocreate.local BEOCREATE_PORT=13141 AUDIO_PIPES_DIR=/opt/audiocontrol/pipes MUSIC_DIR=/opt/audiocontrol/music FRONTEND_PORT=8180 SNAPSERVER_HOST=192.168.178.100 HIFIBERRY_CARD=sndrpihifiberry ICECAST_LISTENER_PASSWORD=groove_listen ``` Set `AUDIOCONTROL_PASSWORD` if the web UI should require HTTP Basic Auth: ```dotenv AUDIOCONTROL_USERNAME=admin AUDIOCONTROL_PASSWORD=change-me ``` Leave `AUDIOCONTROL_PASSWORD` empty to disable Basic Auth. ### 3. Create Host Directories Create the default music and FIFO directories: ```bash sudo mkdir -p /opt/audiocontrol/music sudo bash scripts/init-pipes.sh ``` If you use a non-default FIFO path, pass it to the script and keep `.env` aligned: ```bash sudo bash scripts/init-pipes.sh /srv/audioserver/pipes ``` The script creates: - `mopidy.fifo` - `turntable.fifo` - `cava.fifo` - `stream.fifo` ### 4. Add Music Put local music in the path configured by `MUSIC_DIR`. By default: ```bash sudo mkdir -p /opt/audiocontrol/music sudo rsync -av /path/to/music/ /opt/audiocontrol/music/ ``` The directory is mounted read-only into the Mopidy container at `/music`. ### 5. Build And Start The Stack ```bash docker compose up -d --build docker compose ps ``` Watch startup logs: ```bash docker compose logs -f ``` Open the web UI: ```text http://:8180 ``` Use the port from `FRONTEND_PORT` if you changed it. ### 6. Scan The Mopidy Local Library After adding files to `MUSIC_DIR`, scan the library: ```bash docker compose exec mopidy mopidy local scan ``` Restart Mopidy if needed: ```bash docker compose restart mopidy ``` ### 7. Install Turntable Capture Turntable capture runs on the host, not in Docker, because it needs ALSA device access. First check the capture card name: ```bash arecord -l ``` Set `HIFIBERRY_CARD` in `.env` to the matching ALSA card name. Then install the service: ```bash sudo cp scripts/turntable-capture.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now turntable-capture ``` Check logs: ```bash journalctl -u turntable-capture -f ``` If the repo is not located at `/home/admin/appdata/audioserver`, edit `/etc/systemd/system/turntable-capture.service` before enabling it. ### 8. Optional: Install Now-Playing Service The frontend proxies `/api/now-playing/` to a host service on port `8090`. Install Python dependencies: ```bash pip3 install -r scripts/requirements.txt ``` Install the service: ```bash sudo cp scripts/snapcast-now-playing.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now snapcast-now-playing ``` Check logs: ```bash journalctl -u snapcast-now-playing -f ``` As with the turntable service, update paths in the unit file if the repo lives somewhere other than `/home/admin/appdata/audioserver`. ## Service Ports | Port | Service | Bind | Purpose | | ---: | --- | --- | --- | | `8180` | frontend | configurable via `FRONTEND_PORT` | Web UI, API proxy, `/stream.flac` proxy | | `1704` | Snapserver | `0.0.0.0` | Snapcast client audio stream | | `1705` | Snapserver TCP RPC | `RPC_BIND`, default `127.0.0.1` | Direct Snapcast JSON-RPC TCP | | `1780` | Snapserver HTTP RPC | `RPC_BIND`, default `127.0.0.1` | Direct Snapcast HTTP/WebSocket API | | `8000` | Icecast | `STREAM_BIND`, default `0.0.0.0` | Direct FLAC stream server | | `6600` | Mopidy MPD | Docker network only by default | MPD access inside stack | | `6680` | Mopidy HTTP | Docker network only by default | Proxied through frontend | | `8090` | now-playing helper | host service | Proxied through frontend | Mopidy HTTP is intentionally not exposed on the host in `docker-compose.yml`. Use the frontend proxy for browser and mobile control. ## Important URLs Replace `` with your audio server hostname or IP. ```text Web UI: http://:8180/ Mopidy JSON-RPC proxy: http://:8180/api/mopidy/rpc Mopidy WebSocket proxy: ws://:8180/ws/mopidy Snapcast RPC proxy: http://:8180/api/snapcast/rpc Snapcast WebSocket: ws://:8180/ws/snapcast Icecast direct stream: http://:8000/stream.flac Proxied stream: http://:8180/stream.flac ``` ## Environment Reference | Variable | Default | Description | | --- | --- | --- | | `BEOCREATE_HOST` | `beocreate.local` | Hostname/IP for the BeoCreate DSP REST API. | | `BEOCREATE_PORT` | `13141` | BeoCreate DSP REST API port. | | `AUDIO_PIPES_DIR` | `/opt/audiocontrol/pipes` | Host directory containing audio FIFOs. | | `MUSIC_DIR` | `/opt/audiocontrol/music` | Host music library mounted into Mopidy. | | `FRONTEND_PORT` | `8180` | Host port for the Nginx/React frontend. | | `AUDIOCONTROL_USERNAME` | `admin` | Basic Auth username when auth is enabled. | | `AUDIOCONTROL_PASSWORD` | empty | Basic Auth password. Empty disables auth. | | `RPC_BIND` | `127.0.0.1` | Host bind address for direct Snapserver RPC ports. | | `STREAM_BIND` | `0.0.0.0` | Host bind address for Icecast port `8000`. | | `TZ` | `Europe/Vienna` | Container timezone. | | `HIFIBERRY_CARD` | `sndrpihifiberry` | ALSA capture card name for turntable input. | | `ICECAST_SOURCE_PASSWORD` | `groove_source` | Source password used by `stream-relay`. | | `ICECAST_RELAY_PASSWORD` | `groove_relay` | Icecast relay password. | | `ICECAST_ADMIN_PASSWORD` | `groove_admin` | Icecast admin password. | | `ICECAST_LISTENER_PASSWORD` | `groove_listen` | Listener password used by mobile apps. | | `SPOTIFY_CLIENT_ID` | empty | Spotify application client ID for Mopidy Spotify. | | `SPOTIFY_CLIENT_SECRET` | empty | Spotify application client secret. | | `SNAPSERVER_HOST` | `192.168.178.100` | LAN address of this audio server, used by Snapserver/Iris. | | `BEETS_HOST` | `192.168.178.100` | Host running Beets web service. | | `TCP_POP_HOST` | `192.168.178.95` | TCP source host for Snapserver stream `pop`. | | `TCP_TP_HOST` | `192.168.178.89` | TCP source host for Snapserver stream `tp`. | | `TCP_FRAMEWORK_HOST` | `192.168.178.183` | TCP source host for Snapserver stream `framework`. | ## Operating The Stack Start or restart everything: ```bash docker compose up -d --build ``` Stop everything: ```bash docker compose down ``` Show service status: ```bash docker compose ps ``` Follow all logs: ```bash docker compose logs -f ``` Follow one service: ```bash docker compose logs -f mopidy docker compose logs -f snapserver docker compose logs -f frontend docker compose logs -f icecast ``` Restart one service: ```bash docker compose restart mopidy docker compose restart snapserver docker compose restart frontend ``` Open a shell in a container: ```bash docker compose exec mopidy bash docker compose exec frontend sh ``` Rebuild after config or source changes: ```bash docker compose build docker compose up -d ``` ## Updating ```bash git pull docker compose pull docker compose up -d --build docker compose ps ``` If `.env.example` changed, compare it with your `.env` and copy over any new variables you need. ## Audio Routing ### Mopidy To Snapcast Mopidy writes raw PCM into `/audio/mopidy.fifo`. Snapserver reads that FIFO as the `mopidy` stream: ```text Mopidy -> /audio/mopidy.fifo -> Snapserver stream "mopidy" -> Snapcast clients ``` Mopidy also mirrors the same audio into: - `/audio/cava.fifo` for visualization. - `/audio/stream.fifo` for Icecast streaming. ### Mopidy To Icecast The `stream-relay` container reads `/audio/stream.fifo`, encodes FLAC with ffmpeg, and publishes to Icecast: ```text Mopidy -> /audio/stream.fifo -> stream-relay -> Icecast /stream.flac ``` The stream is available directly on port `8000` and through the frontend proxy. ### Turntable To Snapcast The host `turntable-capture` service records from ALSA and writes raw PCM to the turntable FIFO: ```text Turntable / HiFiBerry capture -> turntable-capture -> /audio/turntable.fifo -> Snapserver stream "turntable" -> Snapcast clients ``` All primary PCM paths use 48 kHz, 16-bit, stereo audio. ## Web UI Development The Docker stack builds the React app automatically from `audiocontrol/`. For local frontend development: ```bash cd audiocontrol npm install npm run dev ``` The Vite development server runs on: ```text http://localhost:5173 ``` Build manually: ```bash npm run build ``` Preview the production build: ```bash npm run preview ``` ## Mobile Apps The native clients live in: - `GrooveAudio (Android)/` - `GrooveAudio (iOS)/` They are designed to use: - Port `8000` for the Icecast FLAC stream. - Port `8180` for proxied Mopidy and Snapcast control APIs. - `ICECAST_LISTENER_PASSWORD` from `.env` for stream authentication. Default app settings assume: ```text Host: 192.168.178.100 Password: groove_listen ``` Change those in the app settings if your server IP or listener password differs. See `MOBILE_APP_INSTRUCTIONS.md` for additional notes about mobile and remote access. ### Android ```bash cd "GrooveAudio (Android)" ./gradlew assembleDebug ``` Or open the folder in Android Studio and run the app on a device. ### iOS Open `GrooveAudio (iOS)/GrooveAudio.xcodeproj` in Xcode, select a device or simulator, and run the app. ## Spotify And Bandcamp ### Spotify 1. Create a Spotify application in the Spotify Developer Dashboard. 2. Put the credentials in `.env`: ```dotenv SPOTIFY_CLIENT_ID=... SPOTIFY_CLIENT_SECRET=... ``` 3. Recreate Mopidy: ```bash docker compose up -d --build mopidy ``` The frontend also includes a proxied credential store under `/api/spotify/` served by `bandcamp-api`. ### Bandcamp The `bandcamp-api` service stores Bandcamp credentials or cookies in the `bandcamp_config` Docker volume. The frontend proxies it under `/api/bandcamp/`. ## Security Notes - Set `AUDIOCONTROL_PASSWORD` before exposing the UI outside a trusted LAN. - Keep `RPC_BIND=127.0.0.1` unless direct Snapserver RPC access is required. - Prefer the frontend proxy for control APIs instead of exposing backend ports. - Icecast binds to `0.0.0.0` by default through `STREAM_BIND`; restrict it if you do not want direct LAN access to port `8000`. - Do not commit `.env`; it may contain passwords and API credentials. ## Troubleshooting ### Frontend Does Not Load Check the frontend container and logs: ```bash docker compose ps frontend docker compose logs -f frontend ``` Verify the configured port: ```bash docker compose port frontend 80 ``` ### Basic Auth Fails Check `AUDIOCONTROL_USERNAME` and `AUDIOCONTROL_PASSWORD` in `.env`, then recreate the frontend: ```bash docker compose up -d --force-recreate frontend ``` ### BeoCreate DSP Is Unreachable Confirm the host and port in `.env`: ```bash set -a . ./.env set +a curl "http://$BEOCREATE_HOST:$BEOCREATE_PORT/api/volume" ``` If that fails from the host, fix DNS/IP routing before debugging Docker. ### No Mopidy Audio In Snapcast Confirm the FIFOs exist: ```bash ls -l "${AUDIO_PIPES_DIR:-/opt/audiocontrol/pipes}" ``` Then check service logs: ```bash docker compose logs -f mopidy snapserver ``` If Mopidy started before Snapserver was ready, restart Mopidy: ```bash docker compose restart mopidy ``` ### Local Music Is Missing Confirm `MUSIC_DIR` contains music files and rescan: ```bash docker compose exec mopidy mopidy local scan docker compose restart mopidy ``` ### No Turntable Audio Check ALSA devices: ```bash arecord -l ``` Update `HIFIBERRY_CARD` in `.env`, then restart the service: ```bash sudo systemctl restart turntable-capture journalctl -u turntable-capture -f ``` ### Icecast Stream Is Silent Or Missing Check Icecast and relay logs: ```bash docker compose logs -f icecast stream-relay ``` Verify the stream URL: ```bash curl -I http://localhost:8000/stream.flac curl -I http://localhost:8180/stream.flac ``` The stream only carries Mopidy audio. If Mopidy is stopped or idle, the stream may be silent. ### Snapcast Clients Cannot Connect Snapcast clients need access to port `1704` on the audio server: ```bash sudo ufw allow 1704/tcp ``` Confirm Snapserver is listening: ```bash docker compose logs -f snapserver ``` ### Now-Playing API Returns Errors Check the host service: ```bash systemctl status snapcast-now-playing journalctl -u snapcast-now-playing -f ``` Make sure the service unit paths match the actual repository path and that Python dependencies from `scripts/requirements.txt` are installed. ## Backup Notes Important persistent state lives in: - `.env` - `MUSIC_DIR` - Docker volume `audioserver_mopidy_data` - Docker volume `audioserver_bandcamp_config` - Any systemd unit overrides you create List volumes: ```bash docker volume ls | grep audioserver ``` Back up named volumes with your normal Docker volume backup process.