Important
This is a very early proof of concept of the Sendspin protocol. The protocol will likely change. This does work today (10/26), but may not work tomorrow.
A complete Sendspin Protocol implementation in Go, featuring both server and player components for synchronized multi-room audio streaming.
Key Highlights:
- Library-first design: Use as a Go library or standalone CLI tools
- Hi-res audio support: Up to 192kHz/24-bit streaming
- Multi-codec: Opus, FLAC, MP3, PCM
- Precise synchronization: Microsecond-level multi-room sync
- Easy to use: Simple high-level APIs for common use cases
- Flexible: Low-level component APIs for custom implementations
Install the library:
go get github.com/Sendspin/sendspin-gopackage main
import (
"log"
"github.com/Sendspin/sendspin-go/pkg/sendspin"
)
func main() {
// Create and configure player
player, err := sendspin.NewPlayer(sendspin.PlayerConfig{
ServerAddr: "localhost:8927",
PlayerName: "Living Room",
Volume: 80,
OnMetadata: func(meta sendspin.Metadata) {
log.Printf("Playing: %s - %s", meta.Artist, meta.Title)
},
})
if err != nil {
log.Fatal(err)
}
// Connect and play
if err := player.Connect(); err != nil {
log.Fatal(err)
}
if err := player.Play(); err != nil {
log.Fatal(err)
}
// Keep running
select {}
}package main
import (
"log"
"github.com/Sendspin/sendspin-go/pkg/sendspin"
)
func main() {
// Create test tone source (or use NewFileSource)
source := sendspin.NewTestTone(192000, 2)
// Create and start server
server, err := sendspin.NewServer(sendspin.ServerConfig{
Port: 8927,
Name: "My Server",
Source: source,
})
if err != nil {
log.Fatal(err)
}
if err := server.Start(); err != nil {
log.Fatal(err)
}
// Keep running
select {}
}See the examples/ directory for more complete examples:
- basic-player/ - Simple player with status monitoring
- basic-server/ - Simple server with test tone
- custom-source/ - Custom audio source implementation
- High-level API:
pkg/sendspin- Player and Server with simple configuration - Audio processing:
pkg/audio- Format types, codecs, resampling, output - Protocol:
pkg/protocol- WebSocket client and message types - Clock sync:
pkg/sync- Precise timing synchronization - Discovery:
pkg/discovery- mDNS service discovery
Full API documentation: https://pkg.go.dev/github.com/Sendspin/sendspin-go
- Stream audio from multiple sources:
- Local files (MP3, FLAC)
- HTTP/HTTPS streams (direct MP3)
- HLS streams (.m3u8 live radio)
- Test tone generator (440Hz)
- Automatic resampling to 48kHz for Opus compatibility
- Multi-codec support (Opus @ 256kbps, PCM fallback)
- mDNS service advertisement for automatic discovery
- Real-time terminal UI showing connected clients
- WebSocket-based streaming with precise timestamps
- Automatic server discovery via mDNS
- Multi-codec support (Opus, FLAC, PCM)
- Precise clock synchronization for multi-room audio
- Interactive terminal UI with volume control
- Jitter buffer for smooth playback
You'll need pkg-config, Opus libraries, and optionally ffmpeg for HLS streaming:
# macOS
brew install pkg-config opus opusfile ffmpeg
# Ubuntu/Debian
sudo apt-get install pkg-config libopus-dev libopusfile-dev ffmpeg
# Fedora
sudo dnf install pkg-config opus-devel opusfile-devel ffmpegNote: ffmpeg is only required for HLS/m3u8 stream support. Local files and direct HTTP MP3 streams work without it.
Build both server and player:
makeOr build individually:
make server # Builds sendspin-server
make player # Builds sendspin-playerStart a server with the interactive TUI (default, plays 440Hz test tone):
./sendspin-serverStream a local audio file:
./sendspin-server --audio /path/to/music.mp3
./sendspin-server --audio /path/to/album.flacStream from HTTP/HTTPS:
./sendspin-server --audio http://example.com/stream.mp3Stream HLS/m3u8 (live radio):
./sendspin-server --audio "https://stream.radiofrance.fr/fip/fip.m3u8?id=radiofrance"Run without TUI (streaming logs to stdout):
./sendspin-server --no-tui--port- WebSocket server port (default: 8927)--name- Server friendly name (default: hostname-sendspin-server)--audio- Audio source to stream:- Local file path:
/path/to/music.mp3,/path/to/audio.flac - HTTP stream:
http://example.com/stream.mp3 - HLS stream:
https://example.com/live.m3u8 - If not specified, plays 440Hz test tone
- Local file path:
--log-file- Log file path (default: sendspin-server.log)--debug- Enable debug logging--no-mdns- Disable mDNS advertisement (clients must connect manually)--no-tui- Disable TUI, use streaming logs instead
The server TUI shows:
- Server name and port
- Uptime
- Currently playing audio
- Connected clients with codec and state
- Press
qorCtrl+Cto quit
Start a player (auto-discovers servers via mDNS):
./sendspin-player --name "Living Room"Connect to a specific server manually:
./sendspin-player --server ws://192.168.1.100:8927 --name "Kitchen"--server- Manual server WebSocket address (skips mDNS discovery)--port- Port for mDNS advertisement (default: 8927)--name- Player friendly name (default: hostname-sendspin-player)--buffer-ms- Jitter buffer size in milliseconds (default: 150)--log-file- Log file path (default: sendspin-player.log)--debug- Enable debug logging
The player TUI shows:
- Player name
- Server connection status
- Current audio title/artist
- Codec and sample rate
- Buffer depth
- Clock sync statistics (offset, RTT, drift)
- Playback statistics (received, played, dropped)
- Volume control (Up/Down arrows or +/- keys)
- Press
mto mute/unmute - Press
qorCtrl+Cto quit
Sendspin Go is built with a library-first architecture, providing three layers of APIs:
Simple Player and Server types for common use cases:
- Player: Connect, play, control volume, get stats
- Server: Stream from AudioSource, manage clients
- AudioSource: Interface for custom audio sources
Lower-level building blocks for custom implementations:
pkg/audio: Format types, sample conversions, Bufferpkg/audio/decode: PCM, Opus, FLAC, MP3 decoderspkg/audio/encode: PCM, Opus encoderspkg/audio/resample: Sample rate conversionpkg/audio/output: PortAudio playbackpkg/protocol: WebSocket client, message typespkg/sync: Clock synchronization with drift compensationpkg/discovery: mDNS service discovery
Thin wrappers around the library APIs:
cmd/sendspin-server: Full-featured server with TUIcmd/sendspin-player: Full-featured player with TUI (main.go at root)
The server streams audio in 20ms chunks with microsecond timestamps. Audio is buffered 500ms ahead to allow for network jitter and clock synchronization.
Processing flow:
- Audio source (file decoder or test tone generator)
- Per-client codec negotiation (Opus or PCM)
- Timestamp generation using monotonic clock
- WebSocket binary message streaming
The player uses a sophisticated scheduling system to ensure perfectly synchronized playback across multiple rooms.
Processing flow:
- WebSocket client receives timestamped audio chunks
- Clock sync system converts server timestamps to local time
- Priority queue scheduler with startup buffering (200ms)
- Persistent audio player with streaming I/O pipe
- Software volume control and mixing
The player uses a simple, robust clock synchronization system:
- Calculates server loop origin on first sync
- Direct time base matching (no drift prediction)
- Continuous RTT measurement for quality monitoring
- Microsecond precision timestamps
- 500ms startup buffer matches server's lead time
Terminal 1 - Start the server:
./sendspin-server --audio ~/Music/favorite-album.mp3Terminal 2 - Living room player:
./sendspin-player --name "Living Room"Terminal 3 - Kitchen player:
./sendspin-player --name "Kitchen"Both players will discover the server via mDNS and start playing in perfect sync.
Run tests:
make testClean binaries:
make cleanInstall to GOPATH/bin:
make installFound a bug or have a feature request? Please check existing issues or create a new one:
High Priority:
- Verify hi-res audio (96kHz/192kHz) compatibility with Music Assistant and other Sendspin servers
- Test multi-room synchronization accuracy with 5+ players
- Audit protocol implementation for spec compliance as official spec evolves
- Performance profiling and optimization for CPU/memory usage
- Add comprehensive integration tests with real audio files
Protocol & Compatibility:
- Validate all message types match latest Sendspin Protocol spec
- Test with Music Assistant server
- Test with other Sendspin protocol implementations
- Document any protocol extensions or deviations
- Add protocol version negotiation
Audio Quality:
- Verify 24-bit audio pipeline maintains full bit depth
- Test sample rate conversion quality (FLAC 96kHz → Opus 48kHz)
- Add audio quality metrics and testing
- Support for gapless playback
- Volume curve optimization (currently linear)
Features:
- FLAC and MP3 decoder implementation (currently stubs)
- Visualizer role support (FFT spectrum data)
- Album artwork support (already in protocol)
- Player groups and zones
- Playlist/queue management
- Cross-fade between tracks
Stability:
- Reconnection handling and automatic retry
- Network error recovery
- Graceful degradation on clock sync loss
- Memory leak testing for long-running sessions
- Stress testing with many clients
Developer Experience:
- Add godoc examples for all public APIs
- CI/CD pipeline for automated testing
- Cross-platform testing (Linux/macOS/Windows)
- Docker containers for easy deployment
- Benchmarking suite
v0.9.x (Current - Library Stabilization)
- Fix bugs discovered during testing
- Improve protocol compliance
- Add integration tests
v1.0.0 (Stable Release)
- Complete FLAC/MP3 decoder implementation
- Full Music Assistant compatibility verified
- 100% protocol spec compliance
- Production-ready stability
v1.1.0 (Enhanced Features)
- Album artwork support
- Gapless playback
- Advanced volume controls
v2.0.0 (Advanced Multi-Room)
- Player groups and zones
- Synchronized playback controls
- Playlist management
Implements the Sendspin Protocol specification.
Implementation Status:
- ✅ WebSocket transport
- ✅ Client/Server handshake
- ✅ Clock synchronization (NTP-style)
- ✅ Audio streaming (binary frames)
- ✅ Metadata messages
- ✅ Control commands
- ✅ Multi-codec support (Opus, PCM)
⚠️ Visualizer role (planned)⚠️ Album artwork (protocol support exists, not implemented)