docs(client): correct which sources a bare select actually works for

SOURCE-SELECTION.md listed TUNEIN, RADIO_BROWSER and STORED_MUSIC among the
sources selectable with a source and account alone, and showed SelectTuneIn
as an example. That is the trap: a speaker answers 200 for such a select,
parks on a stub now-playing, and carries on playing whatever it was playing,
so callers checking /now_playing see the source they asked for while the
audio is something else.

Splits the list into sources a bare select works for and sources that need a
ContentItem with a Location, replaces the SelectTuneIn example with
SelectContentItem, and carries the same warning into the doc comments on
SelectSource and SelectTuneIn, where a developer reaching for them will
actually see it. Also points the player's README at the new reference.

No behaviour change; comments and documentation only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-09-05 20:36:58 +02:00
co-authored by Claude Opus 5
parent cf2edd526d
commit fee9fa76ad
4 changed files with 75 additions and 32 deletions
+8
View File
@@ -348,6 +348,14 @@ Based on WebSocket interaction analysis, potential future features:
- Advanced preset programming
- Progressive Web App (PWA) features
## Behaviour reference
[Player: Sources and Selection State](../../docs/content/docs/reference/PLAYER-SOURCE-BEHAVIOUR.md)
covers the parts that are not obvious from the code: which advertised sources
can actually be selected and which need a station ContentItem, how a selection
is confirmed against a speaker that answers 200 either way, and the revision
and epoch fields the browser uses to order status updates.
## License
Same as the parent project - see main repository LICENSE file.
@@ -54,13 +54,13 @@ after the fact. The only remedy is not to issue such a select.
## What the player does instead
| Source | Click behaviour | Resumes from Recents |
| --- | --- | --- |
| `RADIO_BROWSER` | resume newest station, else open RadioBrowser | yes |
| `TUNEIN` | resume newest station, else open TuneIn | yes |
| `LOCAL_INTERNET_RADIO` | open Play URL | no |
| `STORED_MUSIC` | open Library | no |
| anything else | `POST /select` as before | n/a |
| Source | Click behaviour | Resumes from Recents |
|------------------------|-----------------------------------------------|----------------------|
| `RADIO_BROWSER` | resume newest station, else open RadioBrowser | yes |
| `TUNEIN` | resume newest station, else open TuneIn | yes |
| `LOCAL_INTERNET_RADIO` | open Play URL | no |
| `STORED_MUSIC` | open Library | no |
| anything else | `POST /select` as before | n/a |
Resuming replays the newest Recents entry for that source, using that entry's
own ContentItem: the real item the speaker was given, `Location` included.
+43 -23
View File
@@ -29,17 +29,34 @@ The Bose SoundTouch Go client provides comprehensive source selection functional
**Response**: HTTP 200 OK (no body) on success
**Supported Sources:**
- `SPOTIFY` - Spotify streaming service
**Sources selectable this way:**
- `SPOTIFY` - Spotify streaming service (with a real `sourceAccount`)
- `BLUETOOTH` - Bluetooth audio input
- `AUX` - Auxiliary input (3.5mm jack)
- `TUNEIN` - TuneIn internet radio
- `PANDORA` - Pandora streaming service
- `AMAZON` - Amazon Music
- `IHEARTRADIO` - iHeartRadio streaming
- `STORED_MUSIC` - Local/network stored music
- `AIRPLAY` - Apple AirPlay (device dependent)
- `PANDORA`, `AMAZON`, `IHEARTRADIO` - streaming services (with an account)
### Sources that need a ContentItem instead
A bare `/select` carrying only a source and account is **not** enough for
every source a speaker advertises. These need a full ContentItem with a
`Location`, built by `stations.ResolveContentItem` with `type="stationurl"`:
- `TUNEIN` - TuneIn internet radio
- `RADIO_BROWSER` - [RadioBrowser](radio-browser.md) internet radio directory
- `LOCAL_INTERNET_RADIO` - stream URLs, including Play URL and TTS output
- `STORED_MUSIC` - one entry per media server; selecting it names no track
Given a bare select for one of these the speaker does **not** report an
error. It answers `200`, parks on a stub now-playing with no `playStatus`,
empty `type` and `location`, and an `itemName` echoing the source name, and
carries on playing whatever it was playing. It then reports that stub
indefinitely, so callers that check `/now_playing` see the source they asked
for while the audio is something else.
Use `SelectContentItem` with a real `Location` for these, and see
[Player: Sources and Selection State](PLAYER-SOURCE-BEHAVIOUR.md) for the
stub's full signature and how the web player avoids it.
## Client Library Usage
@@ -158,20 +175,20 @@ soundtouch-cli -host 192.0.2.100 -aux
### CLI Flags
| Flag | Description | Example |
|------|-------------|---------|
| `-select-source <source>` | Select audio source | `-select-source SPOTIFY` |
| `-source-account <account>` | Account for streaming services | `-source-account "user123"` |
| `-spotify` | Select Spotify source | `-spotify -source-account "user"` |
| `-bluetooth` | Select Bluetooth source | `-bluetooth` |
| `-aux` | Select AUX input | `-aux` |
| Flag | Description | Example |
|-----------------------------|--------------------------------|-----------------------------------|
| `-select-source <source>` | Select audio source | `-select-source SPOTIFY` |
| `-source-account <account>` | Account for streaming services | `-source-account "user123"` |
| `-spotify` | Select Spotify source | `-spotify -source-account "user"` |
| `-bluetooth` | Select Bluetooth source | `-bluetooth` |
| `-aux` | Select AUX input | `-aux` |
## Source Account Information
### When Source Accounts are Required
- **Spotify**: Required for multi-account setups
- **Pandora**: Required for account-based access
- **Pandora**: Required for account-based access
- **TuneIn**: Optional, may improve personalization
- **Amazon Music**: Required for account access
- **Bluetooth/AUX**: Not required (leave empty)
@@ -259,11 +276,11 @@ func selectWithConfig(client *client.Client, config SourceConfig) error {
### Common Error Codes
| Code | Name | Description | Solution |
|------|------|-------------|----------|
| 1005 | UNKNOWN_SOURCE_ERROR | Invalid or unavailable source | Check available sources first |
| 1006 | SOURCE_UNAVAILABLE | Source temporarily unavailable | Try again later |
| 1007 | ACCOUNT_ERROR | Invalid account for source | Check account name format |
| Code | Name | Description | Solution |
|------|----------------------|--------------------------------|-------------------------------|
| 1005 | UNKNOWN_SOURCE_ERROR | Invalid or unavailable source | Check available sources first |
| 1006 | SOURCE_UNAVAILABLE | Source temporarily unavailable | Try again later |
| 1007 | ACCOUNT_ERROR | Invalid account for source | Check account name format |
### Troubleshooting Tips
@@ -347,13 +364,16 @@ The implementation follows the official SoundTouch API:
## Related Documentation
- [Player: Sources and Selection State](PLAYER-SOURCE-BEHAVIOUR.md) - which
sources accept a bare select, and how a selection is confirmed
- **[API Endpoints Overview](API-ENDPOINTS.md)** - Complete API reference
- **[Sources](https://github.com/gesellix/Bose-SoundTouch/blob/main/pkg/models/sources.go)** - Source model implementation
- **[Sources](https://github.com/gesellix/Bose-SoundTouch/blob/main/pkg/models/sources.go)** - Source model implementation
- **[Now Playing](https://github.com/gesellix/Bose-SoundTouch/blob/main/pkg/models/nowplaying.go)** - ContentItem model
- **[Client Usage Examples](https://github.com/gesellix/Bose-SoundTouch/blob/main/cmd/soundtouch-cli/main.go)** - CLI implementation reference
---
**Implementation Date**: 2026-01-09
**Status**: ✅ Complete and tested
**Implementation Date**: 2026-01-09
**Status**: ✅ Complete and tested
**Real Device Validation**: SoundTouch 10, SoundTouch 20
+17 -2
View File
@@ -796,7 +796,14 @@ func (c *Client) DecreaseBalance(amount int) (*models.Balance, error) {
return c.GetBalance()
}
// SelectSource selects an audio source using the /select endpoint
// SelectSource selects an audio source using the /select endpoint.
//
// Only sources that are inputs in their own right can be selected this way:
// AUX, BLUETOOTH, or a streaming service with a real sourceAccount. Provider
// sources (TUNEIN, RADIO_BROWSER, LOCAL_INTERNET_RADIO) and STORED_MUSIC need
// SelectContentItem with a Location instead; a bare select leaves such a
// speaker reporting a source it is not playing. See
// docs/content/docs/reference/PLAYER-SOURCE-BEHAVIOUR.md.
func (c *Client) SelectSource(source, sourceAccount string) error {
// Validate source parameter
if source == "" {
@@ -861,7 +868,15 @@ func (c *Client) SelectAux() error {
return c.SelectSource("AUX", "")
}
// SelectTuneIn is a convenience method to select TuneIn source
// SelectTuneIn is a convenience method to select TuneIn source.
//
// This sends a bare select, with no station. A speaker does not report an
// error for that: it answers 200, parks on a stub now-playing (no playStatus,
// empty type and location, itemName echoing the source name) and carries on
// playing whatever it was playing, then reports that stub indefinitely. To
// actually play something use SelectContentItem with a ContentItem carrying a
// Location, as stations.ResolveContentItem builds. See
// docs/content/docs/reference/PLAYER-SOURCE-BEHAVIOUR.md.
func (c *Client) SelectTuneIn(sourceAccount string) error {
return c.SelectSource("TUNEIN", sourceAccount)
}