An Android Sendspin client that acts as a synchronized network audio player and optional controller. It connects to a Sendspin-compatible server over WebSocket, receives timestamped PCM or Opus audio frames, performs clock synchronisation and jitter buffering, and plays audio in tight sync with other devices.
- Synchronized audio playback
- Server–client clock alignment with drift estimation
- Timestamp-based playout with adjustable real-time offset
- Opus and PCM support
- Opus decoding via Concentus (pure Java)
- 16‑bit PCM output using Android
AudioTrack
- Adaptive jitter buffering
- Late-frame detection and dropping
- Startup and restart catch-up logic to avoid buffer deadlock
- Controller role support
- Play / pause / stop / next / previous
- Group volume and mute
- Metadata role support
- Artwork role support
- Modern Android UI
- Jetpack Compose UI
- Live diagnostics: offset, drift, RTT, buffer depth
- Real-time playout offset slider for sync tuning
- SendspinPcmClient
- WebSocket protocol handling
- Audio stream lifecycle
- Clock sync, playout scheduling, and control commands
- ClockSync
- RTT-based offset estimation
- Drift calculation (ppm)
- AudioJitterBuffer
- Timestamp-ordered queue
- Late-drop and restart recovery logic
- OpusDecoder
- Concentus-based Opus → PCM decoding
- PcmAudioOutput
- Low-level AudioTrack streaming
- PlayerViewModel / MainActivity
- Compose UI state and controls
- Binary audio frames:
- Type
0x04 - 8‑byte big-endian server timestamp (µs)
- Followed by Opus or PCM payload
- Type
- JSON messages handle:
- Handshake (
client/hello,server/hello) - Time sync (
client/time,server/time) - Stream lifecycle (
stream/start,stream/end) - Controller and group state
- Handshake (
- Build and install the app on an Android device.
- Enter the URL of your HomeAssistant (Music Assistant) server:
ws://<host>:<port>/sendspin - Set a unique
client_idand name. - Connect.
- Adjust Playout offset if required to fine-tune sync with other Sendspin players.
- Android API 26+
- Sendspin-compatible server (homeassistant)
This project is functional but still experimental. The UI exposes internal timing and buffering stats to aid debugging and sync tuning.
Issues:
- When a group is playing, and the app is connected, it won't play audio until a track change is forced on music assistant (but I believe this is a server side bug which i've reported to HA)
- .... it hasn't had much testing!
This is vibe coded - so may have unintentional comments and/or code
