A basic Android client for Sendspin that provides synchronized network audio playback. It connects to a Sendspin-compatible server (e.g., Home Assistant) over WebSocket, receives timestamped PCM or Opus audio frames, performs clock synchronization and jitter buffering, and plays audio in tight sync with other devices.
This project is specially designed for low memory devices and a local network connection only. Connections via cellular will not be supported. The client is designed to offer only a Sendspin player.
- Synchronized audio playback across network devices
- Server-client clock alignment with drift estimation and real-time correction
- Timestamp-based playout with adjustable real-time offset for fine-tuning sync
- RTT-based network latency measurement
- Adaptive jitter buffering with late-frame detection and dropping
- Startup and restart catch-up logic to prevent buffer deadlock
- Flexible codec configuration
- PCM-only mode by default (optimized for local WiFi)
- Opus support available via intent parameter
- Opus decoding via Concentus (pure Java library)
- Support for 16-bit, 24-bit, and 32-bit PCM output
- Configurable sample rates and channel counts
- Automatic server discovery
- mDNS service discovery (
_sendspin-server._tcp) - Manual URL entry fallback
- Persistence of server URL and connection settings
- mDNS service discovery (
- Real-time diagnostics dashboard
- Sync drift (ppm) and uncertainty measurements
- Network quality and stability assessment
- Connection type and RTT latency
- Buffer depth and late frame drops
- Audio pipeline latency monitoring
- Automatic playback speed adjustment (0.998x-1.002x) for buffer timing
- Memory usage monitoring for low-end devices
- Detailed stream information and state
- Adaptive playback speed adjustment
- Continuous buffer-ahead monitoring
- Proportional control to maintain target latency
- Automatic speed tuning (0.998x to 1.002x) without user intervention
- Dampened deadband to prevent audible artifacts
- Real-time latency and speed display
- Background service support
- Foreground service for persistent playback
- Wake lock management for sustained operation
- Boot completion receiver for auto-start
- Memory-aware operation for low-end devices
- Watchdog monitoring for connection stability
- Privacy-first crash reporting
- Detects hard crashes and App Not Responding (ANR) conditions
- On startup after a crash, the app prompts to send an anonymous report
- Reports are sent to Sentry only when the feature is explicitly enabled (opt-in)
- Toggle is available in the Settings section of the main screen
- No data is ever collected or transmitted unless opted-in
- Sentry is not used for anything other than reports for crashes/ANR or audio issues (triggered manually by pressing the dedicated button)
- Only available in builds where a Sentry DSN has been configured (see Development)
- Android: API 24+ (Android 7.0 and later), in theory this could be dropped to 21, however this app relies on
AudioTrackto play audio streams and a number of required sync features are unavailable prior to Android 7.0. - Permissions:
INTERNET- WebSocket communicationMODIFY_AUDIO_SETTINGS- Audio playback controlWAKE_LOCK- Prevent sleep during playbackFOREGROUND_SERVICE- Background audio servicePOST_NOTIFICATIONS- Playback notifications (Android 13+)NEARBY_WIFI_DEVICES- mDNS service discovery (Android 12+)RECEIVE_BOOT_COMPLETED- Auto-start on device bootACCESS_NETWORK_STATE,CHANGE_NETWORK_STATE- Network monitoring
- Server: Sendspin-compatible server (e.g., Music Assistant)
- Build and install the app on an Android device.
- Grant required permissions when prompted.
- The app will attempt automatic server discovery via mDNS.
- If discovery succeeds, the server URL is populated automatically.
- If discovery fails or times out, manually enter your server URL:
ws://<host>:<port>/sendspin
- Connect to the server.
- All configuration is performed server side.
The app supports launch parameters for programmatic configuration:
- Enables Opus codec support
true: Opus offered as preferred codec (PCM as fallback)false: PCM-only mode (default, optimized for local WiFi)- Persistence: Intent parameter overrides saved value and persists across restarts
- Example:
adb shell am start -n com.sendspinlite/.MainActivity --ez enableOpusCodec true
-
SendspinService
- Background service managing WebSocket connection lifecycle
- Runs as foreground service with media playback notifications
- Handles connection persistence and recovery
-
SendspinPcmClient
- WebSocket protocol implementation
- Audio stream lifecycle management
- Clock synchronization loops
- Playout scheduling and timing control
- Memory monitoring and watchdog systems
-
ClockSync
- RTT-based offset estimation
- Drift calculation and uncertainty tracking
- SNR-based quality assessment
-
AudioJitterBuffer
- Timestamp-ordered queue management
- Late-frame detection and dropping
- Restart recovery logic for buffer deadlock prevention
-
OpusDecoder
- Concentus-based Opus to PCM decoding
- Automatic fallback when unavailable
-
PcmAudioOutput
- AndroidX AudioTrack wrapper
- Multi-bit-depth support (16/24/32-bit)
- Playback speed control for adaptive timing
- Pipeline latency estimation with smoothing
- Real-time latency and speed reporting
- Buffer management for low-latency playback
-
ServiceDiscovery
- mDNS service discovery using Android NSD Manager
- Automatic server detection on local network
-
PlayerViewModel / MainActivity
- Jetpack Compose UI state management
- User preference persistence
- Real-time diagnostics streaming
- Type:
0x04 - 8-byte big-endian server timestamp (microseconds)
- Followed by PCM or Opus audio payload
- Handshake:
client/hello,server/hello - Time Sync:
client/time,server/time - Stream Lifecycle:
stream/start,stream/end
This project is stable. Contributions and bug reports are welcome.
Tap the bug-report icon (🐛) next to the app title to open the audio issue reporter. It collects a privacy-redacted diagnostics snapshot that includes:
- Audio configuration (codec, static delay, playback speed)
- Audio pipeline statistics (latency, drift, RTT, buffer, sync counts)
- Network quality metrics (connection type, stability)
- Recent logcat lines filtered to Sendspin and Android audio subsystem tags only
Personal data is intentionally excluded: server addresses, client/group names, and all track metadata (title, artist, album, etc.) are never collected.
Two reporting options are offered:
| Option | When available | Notes |
|---|---|---|
| Upload to Sentry | Crash & ANR reporting enabled in Settings | Returns a unique event ID to include in GitHub issues |
| Save to File | Always | System file picker lets you choose where to save the .txt report |
Crash and ANR reporting via Sentry is disabled by default and only activates when:
- The build is compiled with a valid Sentry DSN, and
- The user explicitly enables the feature in the app's Settings section.
To enable crash reporting in your own build, set the SENTRY_DSN environment variable before building:
export SENTRY_DSN="https://<key>@<org>.ingest.sentry.io/<project>"
./gradlew assembleReleaseWithout this variable the feature is unavailable (the toggle in Settings will be disabled). Official release builds published by this project include a configured DSN.