Five findings from reviewing gesellix/Bose-SoundTouch#644 (Retry configured player devices during discovery): 1. Source-labeling used exact string equality (device.DiscoveryMethod == "Configuration"), which breaks once a configured host is also found via mDNS/UPnP in the same sweep: mergeDeviceData concatenates methods into e.g. "Configuration+mDNS/Bonjour", so the check silently failed and the device got labeled "discovered" instead of "manual". Extracted the decision into classifySource() and switched to a substring match. Added TestClassifySource, which fails against the old exact-equality logic on exactly the composite-string cases (verified) and would have caught this before merge -- the PR's own test disables mDNS/UPnP, so it never exercised this path. 2. Manually configured devices no longer registered immediately at startup -- they now wait for the full mDNS/UPnP sweep (up to the 10s discovery timeout) to complete, since the PR removed the synchronous registration loop and relies entirely on PreferredDevices. Restored the immediate loop alongside (not instead of) folding manualHosts into PreferredDevices, so a currently-online configured device registers immediately as before, while an offline one still gets retried on every subsequent discovery pass -- the actual value this PR adds. 3. The new PreferredDevices-seeding loop didn't dedupe against hosts already loaded from PREFERRED_DEVICES, so setting both for the same host produced duplicate entries. Currently harmless (absorbed by AddDeviceByHost's fast path) but fragile. Added dedup by host. 4. NewDiscoveryService's doc comment didn't mention the new configuredHosts parameter or its retry-on-every-sweep behavior. Documented. 5. The new test's second DiscoverDevices call spawns a one-shot status-update goroutine and a 30s-ticker poll loop with no guaranteed drain before the deferred server.Close(), risking benign but real -race/CI flakiness. Added a bounded settle delay after RemoveDevice. Verified: full build/vet/race test suite/lint clean; the new TestClassifySource fails against the pre-fix logic and passes with it; TestDiscoverDevicesRetriesConfiguredHosts re-run 20x under -race with no flakiness. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SoundTouch Web UI
A modern single-page web application (SPA) for controlling Bose SoundTouch devices. Built with a JSON API backend and client-side JavaScript rendering for superior performance and maintainability.
Architecture
Browser → Static HTML → JavaScript → JSON API → Go Server
↓
Client-Side Rendering
Key Benefits
- Better Performance: No server-side template processing overhead
- Improved Maintainability: Clear separation between frontend (JavaScript) and backend (Go)
- Real-time Experience: Smooth client-side updates without page reloads
- Mobile Ready: The JSON API can power both this web interface and mobile applications
Features
Based on captured WebSocket interactions and device API capabilities, this web UI provides:
Device Management
- Auto-discovery of SoundTouch devices on the network
- Real-time status monitoring via WebSocket connections
- Multi-device support with centralized control
- Connection status indicators and health monitoring
Playback Control
- Play/Pause/Stop/Next/Previous controls
- Now playing information with artwork, track details, and progress
- Real-time updates of playback state changes
- Source selection from available inputs (Spotify, TuneIn, Bluetooth, AUX, etc.)
Audio Controls
- Volume control with real-time slider updates
- Mute/Unmute functionality
- Bass adjustment (on supported models)
- Audio level monitoring and statistics
Preset Management
- 6 preset buttons with visual feedback
- Preset content display showing station/playlist names
- One-click preset selection
Advanced Features
- WebSocket real-time updates for instant state synchronization
- Responsive design optimized for desktop and mobile
- Dark mode support (auto-detects system preference)
- Accessibility features (keyboard navigation, screen reader support)
- Network statistics and device health monitoring
Screenshots
Main Device Overview
The main page shows all discovered devices with their current status, now-playing information, and quick controls.
Detailed Device Control
Individual device pages provide full control over:
- Detailed now-playing information with artwork
- Comprehensive audio controls (volume, bass)
- Full preset and source selection
- Real-time status updates
Installation
Prerequisites
- Go 1.21 or later
- Access to SoundTouch devices on the same network
- Modern web browser with WebSocket support
Building
# From project root
make build
# Or manually
cd cmd/soundtouch-player
go build -o soundtouch-player
Running
# Run with default settings (port 8080)
./soundtouch-player
# Specify custom port
./soundtouch-player -port 8888
# Connect to specific device
./soundtouch-player --devices 192.0.2.100
Command Line Options
--port, -p string HTTP port to listen on (default "8080", env PORT)
--bind string Address for the HTTP listener: host, IP, or interface name (env BIND_ADDR)
--interface string Network interface name for mDNS/UPnP discovery (env DISCOVERY_INTERFACE)
--devices strings SoundTouch device IP(s) to add manually, repeatable (env SOUNDTOUCH_DEVICES)
--service-url string AfterTouch service base URL, e.g. https://soundtouch.local (env SERVICE_URL)
--service-ca string Path to the AfterTouch service CA certificate (PEM) to trust (env SERVICE_CA)
--help, -h Show help information
Text-to-Speech (TTS)
TTS synthesis and the Bose app_key live in the AfterTouch service, not in
soundtouch-player, so the "Speak" feature proxies to the service's
/setup/tts/speak endpoint. To use it, point soundtouch-player at the service
with --service-url.
When the service is served over HTTPS with its own self-signed certificate
(the default), soundtouch-player also needs to trust the service's CA, or the
proxied call fails with x509: certificate signed by unknown authority. Pass
the CA with --service-ca; it is the service's <dataDir>/certs/ca.crt:
soundtouch-player \
--service-url https://soundtouch.fritz.box \
--service-ca /path/to/certs/ca.crt
The CA is appended to the system trust store, so a service URL that uses a publicly trusted certificate keeps working without the flag. The target speaker must be known to the service (it resolves the speaker against its own device datastore).
Usage
Accessing the Interface
- Start the application
- Open your web browser and navigate to
http://localhost:8080 - Click "Discover Devices" to find SoundTouch devices on your network
- Click on any device for detailed control, or use quick controls from the main page
Device Discovery
The application automatically discovers SoundTouch devices using:
- mDNS discovery for local network devices
- UPnP/SSDP discovery as fallback
- Configured devices via
--devices, retried whenever discovery runs
Real-time Updates
The interface maintains WebSocket connections to each device for instant updates of:
- Now playing information and artwork
- Volume and audio settings changes
- Playback status (play/pause/stop)
- Connection status and device health
Responsive Design
- Desktop: Full-featured interface with side-by-side panels
- Tablet: Optimized layout with touch-friendly controls
- Mobile: Stacked interface with gesture support
API Endpoints
The web UI exposes a REST API for programmatic control:
Device Management
GET /api/devices # List all discovered devices
GET /api/device/{id} # Get specific device info
POST /api/discover # Trigger device discovery
Device Control
GET /api/control/{id}/play # Start playback
GET /api/control/{id}/pause # Pause playback
GET /api/control/{id}/stop # Stop playback
GET /api/control/{id}/next # Next track
GET /api/control/{id}/previous # Previous track
POST /api/control/{id}/volume # Set volume (body: {"level": 50})
GET /api/control/{id}/mute # Mute audio
GET /api/control/{id}/unmute # Unmute audio
POST /api/control/{id}/bass # Set bass (body: {"level": 0})
GET /api/control/{id}/preset?id=1 # Select preset
GET /api/control/{id}/source?name=SPOTIFY # Select source
WebSocket Events
Connect to /ws for real-time updates:
const ws = new WebSocket('ws://localhost:8080/ws');
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
// Handle device updates, status changes, etc.
};
Architecture
Single-Page Application Architecture
- JSON API Backend: Go server providing RESTful endpoints
- Client-Side Rendering: JavaScript handles all UI rendering
- WebSocket Real-time: Bi-directional real-time communication
- No Template Dependencies: Eliminates server-side template issues
Backend Components
- Discovery Service: Finds and manages SoundTouch devices
- WebSocket Manager: Maintains real-time connections to devices
- JSON API Server: RESTful interface returning only JSON
- Device Manager: Tracks device state and health
Frontend Components
- Bootstrap 5: Modern responsive UI framework
- Vanilla JavaScript: No framework dependencies, fast loading
- WebSocket Client: Real-time bidirectional communication
- Dynamic Rendering: Client-side HTML generation from JSON
Communication Flow
- SPA Loading: Single HTML file with embedded CSS and JavaScript
- JSON API: Device discovery and control via REST endpoints
- WebSocket (Device): Real-time status updates from SoundTouch devices
- WebSocket (Browser): Real-time UI updates to web clients
- Client Rendering: JavaScript dynamically creates all UI elements
Development
Project Structure
cmd/soundtouch-player/
├── main.go # Application entry point and SPA routing
├── handlers/ # HTTP and WebSocket handlers
│ ├── handlers.go # JSON API endpoints
│ └── websocket.go # WebSocket management
├── webtypes/ # Type definitions
│ └── types.go # Request/response types
├── static/ # Static assets
│ ├── index.html # Single-page application
│ └── js/ # Legacy JS files (reference)
├── templates/ # Legacy templates (unused in SPA)
└── README.md # This file
Adding New Features
- API Endpoints: Add new JSON routes in
setupRoutes()andhandlers.go - WebSocket Events: Extend event handlers in WebSocket client
- UI Components: Add JavaScript rendering functions in
static/index.html - Device Controls: Implement new control commands and update client-side handlers
Testing
# Unit tests
go test ./...
# Manual testing with multiple devices
./soundtouch-player -port 8080
# API testing
curl http://localhost:8080/api/devices
WebSocket Protocol Analysis
This UI is based on extensive analysis of captured SoundTouch WebSocket interactions, including:
Message Types Implemented
- SoundTouchSdkInfo: Initial handshake and version info
- nowPlayingUpdated: Real-time track information
- volumeUpdated: Audio level changes
- recentsUpdated: Recently played items
- userActivityUpdate: User interaction notifications
Request/Response Patterns
- Device Information: System details and capabilities
- Audio Controls: Volume, bass, mute controls
- Playback Control: Play/pause/stop/skip commands
- Source Selection: Input switching (Spotify, TuneIn, etc.)
- Preset Management: Saved station/playlist access
Gabbo Protocol Features
- Persistent Connections: Maintains long-lived WebSocket connections
- Request Correlation: Uses request IDs for response matching
- Real-time Events: Instant updates for all device state changes
- Bi-directional Control: Both status monitoring and device control
Browser Compatibility
Supported Browsers
- Chrome 80+ (recommended)
- Firefox 75+
- Safari 13+
- Edge 80+
Required Features
- WebSocket support
- CSS Grid and Flexbox
- ES6 JavaScript features
- Responsive CSS media queries
Security Considerations
- Local Network Only: Designed for local network device control
- No Authentication: Assumes trusted local network environment
- CORS Policy: Restricted to same-origin requests
- WebSocket Security: Uses same-origin WebSocket connections
Troubleshooting
Common Issues
Devices Not Found
- Ensure devices are on the same network
- Check firewall settings (ports 8090, 8080)
- Click "Discover Devices" button to trigger discovery
WebSocket Connection Failed
- Verify device supports WebSocket connections
- Check browser console for connection errors
- Refresh the page to reconnect WebSocket
Control Commands Not Working
- Check device is powered on and connected
- Verify device is not in exclusive mode (e.g., Spotify Connect active)
- Look for error notifications in the UI
Page Shows Template Errors
- This has been fixed in the SPA implementation
- Ensure you're accessing the correct URL (localhost:8080)
- Clear browser cache if you see old template-based content
Debug Mode
Add verbose logging by setting environment variable:
export DEBUG=true
./soundtouch-player
Contributing
This web UI is part of the larger SoundTouch Go library project. See the main project README for contribution guidelines.
Architecture Benefits
The new SPA approach provides:
- Better Performance: No server-side template rendering
- Easier Development: Clear separation of frontend/backend
- Mobile Ready: Same JSON API can power mobile apps
- Scalable: Single-page app architecture
Feature Requests
Based on WebSocket interaction analysis, potential future features:
- Zone/multi-room management
- Clock display control
- Software update management
- Advanced preset programming
- Progressive Web App (PWA) features
License
Same as the parent project - see main repository LICENSE file.
Acknowledgments
- Built on the comprehensive SoundTouch Go library
- UI design inspired by modern audio control interfaces
- WebSocket protocol reverse-engineered from captured device interactions
- Bootstrap and Bootstrap Icons for responsive design components