Add shuffle support for artist radio/dynamic playlists

- Add shuffle_on and shuffle_off actions to control_playback function
- Update prompt to automatically enable shuffle when playing artists
- Add artist radio mode feature to README
- Update all example configurations with shuffle support
- Simplify control_playback YAML syntax (if/then vs choose/conditions)
- Add shuffle examples to voice command demonstrations
This commit is contained in:
Chad Auld
2025-11-16 12:28:20 -07:00
parent 547281a69e
commit ec0bcd174d
3 changed files with 179 additions and 89 deletions
+157 -86
View File
@@ -9,17 +9,31 @@ Before using this integration, you need:
1. **Home Assistant** - Obviously!
2. **Spotify Premium account** - Required for Spotify Connect API
3. **[Spotify integration](https://www.home-assistant.io/integrations/spotify/)** - Home Assistant's official Spotify integration (provides OAuth authentication)
4. **[Extended OpenAI Conversation](https://github.com/jekalmin/extended_openai_conversation)** - Or another LLM-based conversation agent (Ollama, Google Generative AI, etc.)
4. **A conversation agent with function calling support** - Examples: [Extended OpenAI Conversation](https://github.com/jekalmin/extended_openai_conversation) (works with Ollama, OpenAI, LocalAI), OpenAI Conversation, Google Generative AI Conversation
5. **At least one Spotify Connect device** - Any speaker/device that appears in Spotify (see [Compatible Speakers](#compatible-speakers) for examples)
**Note:** This integration is specifically designed for custom voice assistants (Extended OpenAI Conversation, etc.). If you're using Home Assistant's built-in voice pipeline without a custom LLM, Music Assistant's native voice support may be sufficient.
## Why Use This?
**Perfect for you if:**
- You want voice-controlled Spotify without running a full music server
- You use conversation agents with function calling (Extended OpenAI Conversation, OpenAI, Gemini, etc.)
- You want direct Spotify Connect integration with minimal dependencies
- You prefer simple, maintainable code (<120 lines)
**Consider alternatives if:**
- You need multi-provider music aggregation (Spotify + local files + streaming services) → Use Music Assistant
- You want rich UI-based queue management and multi-room features → Use Music Assistant
- You use Home Assistant Assist voice pipeline → Music Assistant has [native voice support](https://github.com/music-assistant/voice-support)
- You need advanced Spotify API features → Use SpotifyPlus
**Use both together:** Many users run Music Assistant for UI/management and this integration for voice control via custom conversation agents.
## The Problem
Want to control Spotify with your voice in Home Assistant? You'll quickly hit these issues:
- **Spotcast** requires cookie authentication (`sp_dc`, `sp_key`) which breaks frequently with "serverTime" errors
- **Music Assistant** is excellent for UI-based music control, but voice commands require specific sentence patterns that break when using custom voice assistants (Extended OpenAI Conversation, etc.)
- **Music Assistant** is a full-featured music server - excellent for complete music management, but requires running a separate server and aggregates multiple providers
- **SpotifyPlus** is feature-rich but complex to set up for simple voice commands
- **Custom Sentences** require rigid YAML patterns - "Play {artist} on {speaker}" instead of natural language
- **Built-in Spotify integration** has no search service for voice assistants to use
@@ -27,26 +41,28 @@ Want to control Spotify with your voice in Home Assistant? You'll quickly hit th
## The Solution
A lightweight integration designed specifically for voice assistants that:
A lightweight integration designed specifically for voice assistants with function calling:
✅ **Direct Spotify Connect integration** - No music server required, uses Home Assistant's official Spotify integration
✅ **Natural language from day one** - "Play Coldplay" not "play artist equals Coldplay"
✅ **Zero additional authentication** - Reuses your existing Home Assistant Spotify OAuth
✅ **Exact match artist search** - Finds Coldplay when you ask for Coldplay, not recommendations
✅ **Works with any Spotify Connect device** - WiiM, Sonos, Google Cast, Echo, whatever you have
✅ **Under 120 lines of code** - Simple, maintainable, easy to understand
✅ **Voice assistant ready** - Complete Extended OpenAI Conversation examples included
✅ **Voice assistant ready** - Complete conversation agent examples included
## Comparison with Alternatives
| Feature | Spotify Voice Assistant | Spotcast | Music Assistant | SpotifyPlus |
|---------|------------------------|----------|-----------------|-------------|
| **Primary Focus** | Voice control | Casting | Full music server | Spotify API wrapper |
| **Primary Focus** | Voice search for conversation agents | Casting | Complete music server | Spotify API wrapper |
| **Architecture** | Direct Spotify Connect | Spotify API | Music server (aggregates providers) | Spotify API |
| **Lines of Code** | <120 | 1000+ | 10,000+ | 5000+ |
| **Authentication** | Reuses HA OAuth | Cookies (breaks often) | Own server auth | Own OAuth flow |
| **Setup Complexity** | 1 YAML line | Cookies + config | Full server install | Complex config |
| **Exact Artist Match** | ✅ Yes | ❌ No | ✅ Yes | ✅ Yes |
| **Voice Assistant Ready** | ✅ Yes (built-in) | ⚠️ Partial | ✅ Yes | ⚠️ Partial |
| **Natural Language** | ✅ LLM-based | ❌ Rigid patterns | ✅ LLM-based | ❌ Manual calls |
| **Voice Support** | ✅ Function calling | ⚠️ Manual scripts | ✅ HA Assist integration | ⚠️ Manual scripts |
| **Natural Language** | ✅ Via conversation agent | ❌ Rigid patterns | ✅ Via HA Assist | ❌ Manual calls |
| **External Dependencies** | None | None | Separate server | None |
| **Hardware Support** | Any Spotify Connect | Chromecast focused | Universal | Any Spotify Connect |
@@ -57,7 +73,8 @@ A lightweight integration designed specifically for voice assistants that:
- ✅ **Search by type** - Artists, albums, tracks, playlists
- ✅ **Exact match preference** - Finds what you ask for, not recommendations
- ✅ **Complete examples** - Extended OpenAI Conversation config included
- ✅ **Playback control** - Pause, play, skip, volume - all via voice
- ✅ **Playback control** - Pause, play, skip, volume, shuffle - all via voice
- ✅ **Artist radio mode** - Automatically shuffles when playing artists for dynamic playlists
- ✅ **Zero config authentication** - Leverages existing Spotify integration
- ✅ **Detailed logging** - Debug mode shows exactly what's happening
- ✅ **Service response support** - Returns data to automations/scripts
@@ -152,13 +169,13 @@ Copy these functions to Extended OpenAI Conversation settings:
- spec:
name: control_playback
description: Control music playback (pause, resume, stop, next track, previous track, set volume)
description: Control music playback (pause, resume, stop, next track, previous track, set volume, shuffle)
parameters:
type: object
properties:
action:
type: string
enum: [pause, play, stop, next_track, previous_track, volume_set]
enum: [pause, play, stop, next_track, previous_track, volume_set, shuffle_on, shuffle_off]
description: Playback control action
media_player:
type: string
@@ -172,51 +189,52 @@ Copy these functions to Extended OpenAI Conversation settings:
function:
type: script
sequence:
- choose:
- conditions:
- condition: template
value_template: "{{ action == 'pause' }}"
sequence:
- service: media_player.media_pause
target:
entity_id: "{{ media_player }}"
- conditions:
- condition: template
value_template: "{{ action == 'play' }}"
sequence:
- service: media_player.media_play
target:
entity_id: "{{ media_player }}"
- conditions:
- condition: template
value_template: "{{ action == 'stop' }}"
sequence:
- service: media_player.media_stop
target:
entity_id: "{{ media_player }}"
- conditions:
- condition: template
value_template: "{{ action == 'next_track' }}"
sequence:
- service: media_player.media_next_track
target:
entity_id: "{{ media_player }}"
- conditions:
- condition: template
value_template: "{{ action == 'previous_track' }}"
sequence:
- service: media_player.media_previous_track
target:
entity_id: "{{ media_player }}"
- conditions:
- condition: template
value_template: "{{ action == 'volume_set' }}"
sequence:
- service: media_player.volume_set
target:
entity_id: "{{ media_player }}"
data:
volume_level: "{{ volume_level / 100 }}"
- if: "{{ action == 'pause' }}"
then:
- service: media_player.media_pause
target:
entity_id: "{{ media_player }}"
- if: "{{ action == 'play' }}"
then:
- service: media_player.media_play
target:
entity_id: "{{ media_player }}"
- if: "{{ action == 'stop' }}"
then:
- service: media_player.media_stop
target:
entity_id: "{{ media_player }}"
- if: "{{ action == 'next_track' }}"
then:
- service: media_player.media_next_track
target:
entity_id: "{{ media_player }}"
- if: "{{ action == 'previous_track' }}"
then:
- service: media_player.media_previous_track
target:
entity_id: "{{ media_player }}"
- if: "{{ action == 'volume_set' }}"
then:
- service: media_player.volume_set
target:
entity_id: "{{ media_player }}"
data:
volume_level: "{{ volume_level / 100 }}"
- if: "{{ action == 'shuffle_on' }}"
then:
- service: media_player.shuffle_set
target:
entity_id: "{{ media_player }}"
data:
shuffle: true
- if: "{{ action == 'shuffle_off' }}"
then:
- service: media_player.shuffle_set
target:
entity_id: "{{ media_player }}"
data:
shuffle: false
```
</details>
@@ -225,13 +243,20 @@ Add this to your Extended OpenAI Conversation system prompt:
```
Music Playback:
- When asked to play music, follow this two-step process: 1) Call search_spotify to get the Spotify URI, 2) Call play_music with the URI and media player entity
- Available media players: [list your Spotify Connect devices here, e.g., "media_player.kitchen_speaker, media_player.living_room_sonos"]
- Default speaker: If no speaker is specified, use media_player.kitchen_speaker (change this to your preferred default)
- Available media players: media_player.kitchen_speaker, media_player.gaming_room_speaker, media_player.office_speaker
- Default speaker: If no speaker is specified, use media_player.kitchen_speaker
- Parse commands like "Play {Artist/Album/Track}" and determine the media type automatically
- For playback control (pause, skip, volume), use the control_playback function
- IMPORTANT: When playing an artist, always enable shuffle after starting playback by calling control_playback with action: shuffle_on to create a dynamic playlist experience
- For playback control (pause, skip, volume, shuffle), use the control_playback function
```
**Customize the default speaker:** Replace `media_player.kitchen_speaker` with your preferred media player entity ID. The LLM will use this when you say "Play Coldplay" without specifying a location.
**Customization tips:**
- **Entity IDs are descriptive** (like `media_player.kitchen_speaker`): Just list the entity IDs as shown above
- **Entity IDs are cryptic** (like `media_player.wiim_12345`): Include friendly names for better LLM understanding:
```
- Available speakers: Kitchen Speaker (media_player.wiim_12345), Gaming Room (media_player.sonos_abc), Office (media_player.cast_xyz)
```
- **Default speaker:** Change `media_player.kitchen_speaker` to your preferred default location
### Done! Try It
@@ -242,11 +267,12 @@ Music Playback:
- "Pause the music"
- "Skip to the next track"
- "Set volume to 50%"
- "Shuffle on"
**Natural Language (LLM advantage):**
- "I'm in the mood for some Coldplay"
- "Put on that song Yellow"
- "Start playing Coldplay" (uses default speaker from prompt)
- "Start playing Coldplay" (uses default speaker from prompt, automatically shuffles artist)
- "Can you play the Parachutes album?"
- "Play me something by Coldplay"
- "I want to hear some chill music from Coldplay"
@@ -258,6 +284,7 @@ Music Playback:
- "Turn it down a bit" (instead of "Set volume to 30")
- "Next song please" (instead of "Skip to next track")
- "Stop the music" (instead of "Pause playback")
- "Shuffle this" (instead of "Set shuffle to on")
> **Note:** When no speaker is specified, the LLM will use the default media player defined in your Extended OpenAI Conversation prompt. Add instructions like "If no speaker specified, use media_player.kitchen_speaker" to your system prompt.
@@ -329,31 +356,39 @@ This integration works with **any Spotify Connect-compatible device**:
**Note:** If your device runs Spotify and shows up in Home Assistant's integrations, it will work with this integration.
## Use With Music Assistant
## Works Alongside Music Assistant
**You can (and should!) use both Music Assistant and this integration together.** They complement each other perfectly:
Music Assistant and this integration serve different purposes and work well together:
### Music Assistant
- **Best for:** Rich UI-based music control in dashboards and automations
- **Strengths:** Multi-room audio, queue management, playlist management, beautiful UI
- **Voice support:** Built-in voice commands work with Home Assistant's default voice pipeline
- **Limitation:** Voice commands require specific sentence patterns and don't work with custom voice assistants (Extended OpenAI Conversation, custom LLMs, etc.)
- **Purpose:** Full-featured music server with rich UI, multi-room audio, queue management
- **Architecture:** Music server that aggregates multiple providers (Spotify, local files, streaming services)
- **Voice support:** [Built-in voice integration](https://github.com/music-assistant/voice-support) works with Home Assistant Assist (with or without LLM enhancement)
- **Best for:** Complete music management solution with visual control
### This Integration (Spotify Voice Assistant)
- **Best for:** Natural language voice control with custom voice assistants
- **Strengths:** Works with Extended OpenAI Conversation, fully natural language, exact artist matching
- **Use case:** "Play some Coldplay in the kitchen" → LLM interprets naturally and plays music
- **Limitation:** Not a full music server, just search + playback control
- **Purpose:** Lightweight Spotify search service for conversation agents with function calling support
- **Architecture:** Direct Spotify Connect integration via Home Assistant's official Spotify integration (no music server required)
- **Works with:** Any conversation agent that supports function calling (Extended OpenAI Conversation, OpenAI Conversation, Google Generative AI Conversation, etc.)
- **Best for:** Adding Spotify voice control without running a full music server
### Why Use Both?
Many users (including the author) run both together:
- **Music Assistant** provides the UI for manual control, queue management, and automations
- **This integration** handles natural language voice commands via custom LLM assistants
- **They work together seamlessly:** Music started via voice appears in Music Assistant's UI and remains fully controllable there
### Using Both Together
Example workflow: Say "Play Coldplay on office speaker" → music starts → open Music Assistant UI → see what's playing, adjust volume, skip tracks, manage queue - all through MA's interface.
Many users run both simultaneously:
- **Music Assistant:** Provides UI for manual control, queue management, and automations (skip voice setup)
- **This integration:** Handles voice control via custom conversation agents
- **They work together:** Music started via voice appears in Music Assistant UI and remains fully controllable there
They don't conflict because they access the same underlying Spotify Connect devices. This integration just provides the search service that custom voice assistants need to find music.
**Example workflow:** Say "Play Coldplay on office speaker" → music starts → open Music Assistant UI → see what's playing, adjust volume, skip tracks, manage queue.
They don't directly integrate but work together because both control the same Spotify Connect devices independently.
### Choose Your Setup
- **Voice only:** Use this integration alone
- **Voice + rich UI:** Use both (Music Assistant without voice + this integration)
- **Home Assistant Assist voice:** Use Music Assistant's [built-in voice support](https://github.com/music-assistant/voice-support)
- **Custom conversation agent voice:** Use this integration
## How It Works
@@ -535,16 +570,52 @@ This shows search queries, match types (exact vs. first result), and any errors.
3. Test playing Spotify directly to device from Spotify app
4. Verify Spotify Premium account is active
## Performance
The integration itself is highly optimized with client caching (15-50x faster after first search). However, overall voice command response time is dominated by your conversation agent's LLM processing.
### Response Time Breakdown
Typical "Play Coldplay" command:
- **Spotify search:** ~500ms first time, ~10ms cached ✅ Fast
- **LLM processing:** 2-10+ seconds per function call ⚠️ Bottleneck
- **Total:** Depends almost entirely on your LLM
### Optimization Tips
**1. Choose a faster LLM**
- **Slow (2-5 min):** Large models on CPU (qwen3:8b, llama3:8b)
- **Medium (5-15 sec):** Small local models (qwen3:1.8b, phi3:mini)
- **Fast (2-5 sec):** Cloud APIs (OpenAI GPT-4o-mini, Anthropic Claude Haiku)
**2. Reduce exposed devices**
Each device in your conversation agent adds tokens to every LLM call, slowing processing:
- **38 devices:** ~1,600 tokens = slower responses
- **25 devices:** ~1,000 tokens = 20-30% faster
**Safe to remove from Extended OpenAI Conversation:**
- Voice satellite media players (if not directly controlled)
- Unavailable/unused devices
- Status sensors not needed for voice commands
**3. Enable GPU acceleration**
If using Ollama locally, GPU acceleration provides 5-10x speedup over CPU.
### Expected Performance
| Configuration | Response Time |
|---------------|---------------|
| Cloud API (GPT-4o-mini) + 25 devices | 2-5 seconds ✅ |
| Local GPU (qwen3:8b) + 25 devices | 10-30 seconds |
| Local CPU (qwen3:8b) + 38 devices | 2-5 minutes ❌ |
**Bottom line:** This integration adds <1 second overhead. LLM choice determines user experience.
## Roadmap
- [ ] Playlist search support
- [ ] Configuration options (search limits, match behavior)
- [ ] Search result caching for faster responses
- [ ] Support for multiple search result types
- [ ] Album/track exact name matching
- [ ] Integration with HA Assist (native conversation)
- [ ] Favorite/saved content quick access
- [ ] Multi-room playback examples
See [TODO.md](TODO.md) for detailed enhancement proposals and implementation plans.
## Contributing
+20 -2
View File
@@ -53,13 +53,13 @@
- spec:
name: control_playback
description: Control music playback (pause, resume, stop, next track, previous track, set volume)
description: Control music playback (pause, resume, stop, next track, previous track, set volume, shuffle)
parameters:
type: object
properties:
action:
type: string
enum: [pause, play, stop, next_track, previous_track, volume_set]
enum: [pause, play, stop, next_track, previous_track, volume_set, shuffle_on, shuffle_off]
description: Playback control action
media_player:
type: string
@@ -118,3 +118,21 @@
entity_id: "{{ media_player }}"
data:
volume_level: "{{ volume_level / 100 }}"
- conditions:
- condition: template
value_template: "{{ action == 'shuffle_on' }}"
sequence:
- service: media_player.shuffle_set
target:
entity_id: "{{ media_player }}"
data:
shuffle: true
- conditions:
- condition: template
value_template: "{{ action == 'shuffle_off' }}"
sequence:
- service: media_player.shuffle_set
target:
entity_id: "{{ media_player }}"
data:
shuffle: false
+2 -1
View File
@@ -2,5 +2,6 @@ Music Playback:
- When asked to play music, follow this two-step process: 1) Call search_spotify to get the Spotify URI, 2) Call play_music with the URI and media player entity
- Available media players: media_player.kitchen_speaker, media_player.living_room_speaker (customize with your actual entity IDs)
- Parse commands like "Play {Artist/Album/Track}" and determine the media type automatically (artist, album, or track)
- For playback control (pause, skip, volume), use the control_playback function
- IMPORTANT: When playing an artist, always enable shuffle after starting playback by calling control_playback with action: shuffle_on to create a dynamic playlist experience
- For playback control (pause, skip, volume, shuffle), use the control_playback function
- Always use natural language interpretation - users will say things like "play Coldplay" not "search for artist Coldplay"