SoundTouch `/storePreset` Implementation Guide
SoundTouch /storePreset Implementation Guide
Overview
This document analyzes the feasibility and implementation approach for adding /storePreset functionality to the Bose SoundTouch API client, based on GitHub Issue #14 and endpoints discovered through the comprehensive SoundTouch Plus Wiki.
Current Implementation Status
✅ Already Implemented
GetPresets()- Read presets from deviceSelectPreset()- Select preset by number (1-6)GetNextAvailablePresetSlot()- Find next available preset slotIsCurrentContentPresetable()- Check if current content can be saved as preset- Complete data models (
models.Preset,models.ContentItem) - WebSocket events for preset updates
❌ Missing Functionality
StorePreset()- Save content as presetRemovePreset()- Delete existing preset
API Capabilities
According to the comprehensive SoundTouch Plus Wiki, /storePreset supports:
- Radio Stations (TUNEIN, LOCAL_INTERNET_RADIO)
- Spotify Content (Playlists, Albums, Artists, Tracks)
- Local Music (STORED_MUSIC, LOCAL_MUSIC)
- Maximum 6 Presets per device
- Automatic Timestamps (createdOn, updatedOn)
- WebSocket Events (
presetsUpdated)
Implementation Examples
Core Client Methods
// StorePreset saves content as a preset on the SoundTouch device
func (c *Client) StorePreset(id int, contentItem *models.ContentItem) error {
now := time.Now().Unix()
preset := &models.Preset{
ID: id,
CreatedOn: &now,
UpdatedOn: &now,
ContentItem: contentItem,
}
var response models.Presets
return c.post("/storePreset", preset, &response)
}
// RemovePreset deletes a preset from the SoundTouch device
func (c *Client) RemovePreset(id int) error {
preset := &models.Preset{ID: id}
var response models.Presets
return c.post("/removePreset", preset, &response)
}
// StoreCurrentAsPreset saves currently playing content as preset
func (c *Client) StoreCurrentAsPreset(id int) error {
nowPlaying, err := c.GetNowPlaying()
if err != nil {
return fmt.Errorf("failed to get current content: %w", err)
}
if !nowPlaying.ContentItem.IsPresetable {
return fmt.Errorf("current content is not presetable")
}
return c.StorePreset(id, nowPlaying.ContentItem)
}CLI Commands
# Store currently playing content as preset
soundtouch-cli --host 192.0.2.100 preset store-current --slot 3
# Store specific content as preset
soundtouch-cli --host 192.0.2.100 preset store \
--slot 1 \
--source SPOTIFY \
--location "spotify:playlist:37i9dQZF1DX0XUsuxWHRQd" \
--source-account "yourusername" \
--name "My Worship Mix"
# Store radio station as preset
soundtouch-cli --host 192.0.2.100 preset store \
--slot 2 \
--source TUNEIN \
--location "/v1/playback/station/s33828" \
--name "K-LOVE Radio"
# Store radio station using TuneIn URL (Name and Artwork are automatically fetched)
soundtouch-cli --host 192.0.2.100 preset store \
--slot 6 \
--location "https://tunein.com/radio/WDR-2-Rheinland-1004-s213886/"
# Store Spotify album using URL (Name and Artwork are automatically fetched)
soundtouch-cli --host 192.0.2.100 preset store \
--slot 1 \
--location "https://open.spotify.com/album/6rT8yer84xoh0t17poLsmn?si=XqxdZazpTLC1ceoC8EeCuA" \
--source-account "yourusername"
# Remove preset
soundtouch-cli --host 192.0.2.100 preset remove --slot 3
# Show current content details (including location URI for all sources)
soundtouch-cli --host 192.0.2.100 play now
# Show detailed content information
soundtouch-cli --host 192.0.2.100 play now --verboseSpotify Integration Examples
1. Spotify Playlist
contentItem := &models.ContentItem{
Source: "SPOTIFY",
Type: "uri",
Location: "spotify:playlist:37i9dQZF1DX0XUsuxWHRQd",
SourceAccount: "yourspotifyusername",
IsPresetable: true,
ItemName: "My Worship Mix",
ContainerArt: "https://i.scdn.co/image/ab67706c0000da84820d2514932c9e2ea40f6473",
}2. Spotify Album
contentItem := &models.ContentItem{
Source: "SPOTIFY",
Type: "uri",
Location: "spotify:album:6vc9OTcyd3hyzabCmsdnwE",
SourceAccount: "yourspotifyusername",
IsPresetable: true,
ItemName: "Welcome to the New",
ContainerArt: "https://i.scdn.co/image/ab67616d0000b27316c019c87a927829804caf0b",
}3. Spotify Artist
contentItem := &models.ContentItem{
Source: "SPOTIFY",
Type: "uri",
Location: "spotify:artist:6APm8EjxOHSYM5B4i3vT3q",
SourceAccount: "yourspotifyusername",
IsPresetable: true,
ItemName: "MercyMe",
ContainerArt: "https://i.scdn.co/image/ab6761610000e5eb16c019c87a927829804caf0b",
}Getting Spotify URIs (Location Values)
Method 1: From Spotify App
- Right-click on playlist/album/song in Spotify app
- “Share” → “Copy link to playlist”
- Convert URL to URI:
- URL:
https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd - URI:
spotify:playlist:37i9dQZF1DX0XUsuxWHRQd
- URL:
Method 2: From Currently Playing Content (All Sources)
func getCurrentContentLocation(client *soundtouch.Client) (string, string, error) {
nowPlaying, err := client.GetNowPlaying()
if err != nil {
return "", "", err
}
if nowPlaying.ContentItem == nil || nowPlaying.ContentItem.Location == "" {
return "", "", fmt.Errorf("no content location available")
}
return nowPlaying.ContentItem.Location, nowPlaying.ContentItem.Source, nil
}Method 3: URL to URI Converter
func SpotifyURLToURI(url string) (string, error) {
re := regexp.MustCompile(`https://open\.spotify\.com/(playlist|album|artist|track|episode|show)/([a-zA-Z0-9]+)`)
matches := re.FindStringSubmatch(url)
if len(matches) != 3 {
return "", fmt.Errorf("invalid Spotify URL format")
}
contentType := matches[1]
contentID := matches[2]
return fmt.Sprintf("spotify:%s:%s", contentType, contentID), nil
}XML Request Format
The actual XML request sent to the SoundTouch API:
<preset id="3" createdOn="1701220500" updatedOn="1701220500">
<ContentItem source="SPOTIFY" type="uri" location="spotify:playlist:37i9dQZF1DX0XUsuxWHRQd" sourceAccount="yourusername" isPresetable="true">
<itemName>My Worship Mix</itemName>
<containerArt>https://i.scdn.co/image/ab67706c0000da84820d2514932c9e2ea40f6473</containerArt>
</ContentItem>
</preset>Radio Station Examples
TUNEIN Radio
contentItem := &models.ContentItem{
Source: "TUNEIN",
Type: "stationurl",
Location: "/v1/playback/station/s33828",
SourceAccount: "",
IsPresetable: true,
ItemName: "K-LOVE Radio",
ContainerArt: "http://cdn-profiles.tunein.com/s33828/images/logog.png",
}Local Internet Radio
contentItem := &models.ContentItem{
Source: "LOCAL_INTERNET_RADIO",
Type: "stationurl",
Location: "https://content.api.bose.io/core02/svc-bmx-adapter-orion/prod/orion/station?data=eyJ...",
SourceAccount: "",
IsPresetable: true,
ItemName: "Custom Radio Station",
ContainerArt: "",
}Implementation Roadmap
Phase 1: Core Functionality
- Add
StorePreset()method to client - Add
RemovePreset()method to client - Add basic CLI commands
- Add unit tests
Phase 2: Enhanced CLI
- Add
store-currentcommand - Add Spotify URL-to-URI conversion
- Add content validation
- Add batch import functionality
Phase 3: Advanced Features
- Add preset management utilities
- Add content discovery helpers
- Add preset backup/restore
- Integration with Spotify Web API for search
Technical Requirements
Prerequisites
- Existing HTTP client infrastructure ✅
- XML marshaling/unmarshaling ✅
- WebSocket event system ✅
- CLI framework ✅
- Data models ✅
Implementation Effort
- Client methods: ~50-100 lines of code
- CLI commands: ~100-150 lines of code
- Tests: ~200-300 lines of code
- Documentation: This document + API docs
WebSocket Events
When presets are stored or removed, the device generates presetsUpdated events:
<updates deviceID="1004567890AA">
<presetsUpdated>
<presets>
<preset id="1" createdOn="1700536011" updatedOn="1700536011">
<ContentItem source="SPOTIFY" type="uri" location="spotify:playlist:37i9dQZF1DX0XUsuxWHRQd" sourceAccount="username" isPresetable="true">
<itemName>My Worship Mix</itemName>
<containerArt>https://i.scdn.co/image/ab67706c0000da84820d2514932c9e2ea40f6473</containerArt>
</ContentItem>
</preset>
</presets>
</presetsUpdated>
</updates>CLI Command Updates
The CLI now automatically shows location details for all sources when using play now:
Automatic Location Display
# Location automatically shown for any source with location data
go run ./cmd/soundtouch-cli --host 192.0.2.100 play nowExample outputs:
TUNEIN Radio:
Now Playing:
Source: TUNEIN
Track: K-LOVE Radio
Content Details:
Location: /v1/playback/station/s33828LOCAL_INTERNET_RADIO:
Now Playing:
Source: LOCAL_INTERNET_RADIO
Track: Custom Radio Station
Content Details:
Location: https://stream.example.com/radioSTORED_MUSIC (NAS):
Now Playing:
Source: STORED_MUSIC
Track: Welcome Home
Artist: MercyMe
Content Details:
Location: 6_a2874b5d_4f83d999Verbose Mode for Complete Details
go run ./cmd/soundtouch-cli --host 192.0.2.100 play now --verboseShows additional information:
Content Details:
Location: /v1/playback/station/s33828
Content Type: stationurl
Item Name: K-LOVE Radio
Presetable: trueUse Cases
- Quick Access to Favorite Playlists: Store frequently used Spotify playlists as presets 1-6
- Radio Station Shortcuts: Save favorite TUNEIN and internet radio stations for instant access
- NAS Music Collections: Store favorite albums from your network storage as presets
- Pandora Stations: Save your custom Pandora radio stations for quick access
- Mood-based Presets: Organize content by activity (workout, relaxation, work)
- Family-friendly Setup: Each family member gets their own preset slots
- Smart Home Integration: Trigger specific music for different scenarios
Spotify URI Reference
Location Reference for All Sources
| Source | Location Format | Example |
|---|---|---|
| Spotify Playlist | spotify:playlist:ID | spotify:playlist:37i9dQZF1DX0XUsuxWHRQd |
| Spotify Album | spotify:album:ID | spotify:album:4aawyAB9vmqN3uQ7FjRGTy |
| Spotify Artist | spotify:artist:ID | spotify:artist:6APm8EjxOHSYM5B4i3vT3q |
| Spotify Track | spotify:track:ID | spotify:track:17GmwQ9Q3MTAz05OokmNNB |
| TUNEIN Radio | /v1/playback/station/ID | /v1/playback/station/s33828 |
| Internet Radio | URL or encoded URL | https://stream.example.com/radio |
| STORED_MUSIC | Container ID | 6_a2874b5d_4f83d999 |
| LOCAL_MUSIC | album:ID or track:ID | album:983, track:2579 |
| PANDORA Station | Station ID | 126740707481236361 |
Conclusion
The /storePreset feature is highly feasible and would add significant value to the SoundTouch API client. The existing infrastructure provides a solid foundation, and the implementation would be straightforward.
Key benefits:
- ✅ User-friendly: Simple CLI commands for preset management with automatic location detection
- ✅ Universal: Supports ALL content sources (Spotify, TUNEIN, Internet Radio, NAS Music, Pandora, Local Music)
- ✅ Well-documented: Complete API specification available via SoundTouch Plus Wiki
- ✅ Event-driven: WebSocket integration for real-time updates
- ✅ Low complexity: Leverages existing code patterns and infrastructure
- ✅ Enhanced CLI: Automatic location display makes it easy to capture preset data
This feature would enable SoundTouch users to fully utilize their device’s preset capabilities programmatically, making it easier to manage and access their favorite content from any supported source. Special thanks to the SoundTouch Plus community for documenting these working endpoints that weren’t included in the official API documentation.