Bose SoundTouch Toolkit

Documentation for controlling and preserving Bose SoundTouch devices

View the Project on GitHub gesellix/Bose-SoundTouch

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

❌ Missing Functionality

API Capabilities

According to the comprehensive SoundTouch Plus Wiki, /storePreset supports:

  1. Radio Stations (TUNEIN, LOCAL_INTERNET_RADIO)
  2. Spotify Content (Playlists, Albums, Artists, Tracks)
  3. Local Music (STORED_MUSIC, LOCAL_MUSIC)
  4. Maximum 6 Presets per device
  5. Automatic Timestamps (createdOn, updatedOn)
  6. 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.168.1.100 preset store-current --slot 3

# Store specific content as preset
soundtouch-cli --host 192.168.1.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.168.1.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.168.1.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.168.1.100 preset store \
  --slot 1 \
  --location "https://open.spotify.com/album/6rT8yer84xoh0t17poLsmn?si=XqxdZazpTLC1ceoC8EeCuA" \
  --source-account "yourusername"

# Remove preset
soundtouch-cli --host 192.168.1.100 preset remove --slot 3

# Show current content details (including location URI for all sources)
soundtouch-cli --host 192.168.1.100 play now

# Show detailed content information 
soundtouch-cli --host 192.168.1.100 play now --verbose

Spotify 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

  1. Right-click on playlist/album/song in Spotify app
  2. “Share” → “Copy link to playlist”
  3. Convert URL to URI:
    • URL: https://open.spotify.com/playlist/37i9dQZF1DX0XUsuxWHRQd
    • URI: spotify:playlist:37i9dQZF1DX0XUsuxWHRQd

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

  1. Add StorePreset() method to client
  2. Add RemovePreset() method to client
  3. Add basic CLI commands
  4. Add unit tests

Phase 2: Enhanced CLI

  1. Add store-current command
  2. Add Spotify URL-to-URI conversion
  3. Add content validation
  4. Add batch import functionality

Phase 3: Advanced Features

  1. Add preset management utilities
  2. Add content discovery helpers
  3. Add preset backup/restore
  4. Integration with Spotify Web API for search

Technical Requirements

Prerequisites

Implementation Effort

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.168.1.100 play now

Example outputs:

TUNEIN Radio:

Now Playing:
  Source: TUNEIN
  Track: K-LOVE Radio
  
Content Details:
  Location: /v1/playbook/station/s33828

LOCAL_INTERNET_RADIO:

Now Playing:
  Source: LOCAL_INTERNET_RADIO  
  Track: Custom Radio Station
  
Content Details:
  Location: https://stream.example.com/radio

STORED_MUSIC (NAS):

Now Playing:
  Source: STORED_MUSIC
  Track: Welcome Home
  Artist: MercyMe
  
Content Details:
  Location: 6_a2874b5d_4f83d999

Verbose Mode for Complete Details

go run ./cmd/soundtouch-cli --host 192.168.1.100 play now --verbose

Shows additional information:

Content Details:
  Location: /v1/playbook/station/s33828
  Content Type: stationurl
  Item Name: K-LOVE Radio
  Presetable: true

Use Cases

  1. Quick Access to Favorite Playlists: Store frequently used Spotify playlists as presets 1-6
  2. Radio Station Shortcuts: Save favorite TUNEIN and internet radio stations for instant access
  3. NAS Music Collections: Store favorite albums from your network storage as presets
  4. Pandora Stations: Save your custom Pandora radio stations for quick access
  5. Mood-based Presets: Organize content by activity (workout, relaxation, work)
  6. Family-friendly Setup: Each family member gets their own preset slots
  7. 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/playbook/station/ID /v1/playbook/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:

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.