SpotifyPlus Custom Integration

@witno
I know there is extra logic in the code for touch support, versus mouse support. This sounds like BOTH were detected, and it’s recognizing the touch screen (e.g. touch support) controls over the touchpad (e.g. mouse support).

Not sure there is a fix for that scenario, as some users prefer one over the other. I will look into it though - maybe it’s something I can add a configuration option for to prioritize which option it chooses when both are available?

For my needs it’s no problem. I’m mainly setting it up as part of a wall mounted tablet dashboard. And I don’t think it has anything to do with a bug in the card.
However i got me curious, so I tried to plug an external mouse into the MS Surface laptop. It turns out, that worked. But the touchpad dosn’t (not tapping it nor clicking). Suppose it’s different commands being sendt og hw malfunctin on the laptop.
Anyways. Card is SUPER!

1 Like

FYI - just released a new version of the SpotifyPlus integration

[ 1.0.65 ] - 2024/11/15

  • Updated underlying spotifywebapiPython package requirement to version 1.0.119.

I also released the SpotifyPlus Card Dashboard, which is a front-end UI to the SpotifyPlus integration media player. Check it out if you get some time.

2 Likes

Hey there Todd,

thanks for this awesome integration! Did you by chance ever try one of denons heos speakers? I guess they are similar to your bose soundtouchs. They are spotify connect only devices and seem to have a dynamic spotify device id. I just cant get them to respond reliably.
Everytime I leave home and use spotify within my car for example, home assistant completely loses the devices and the spotify integration only registers new spotify device ids, which are not translated to human readable names I gave them (e.g. kitchen, bathroom, etc).
As soon as I start to play some spotify songs on one of the speakers, the home assistant integration shows the correct names again.

I hoped, that SpotifyPlus would solve this and I think it really can.

I build a simple script that does the following:
action: spotifyplus.player_media_play_tracks
metadata: {}
data:
uris: spotify:playlist:4FhyjgN9pPxxxx
entity_id: media_player.spotifyplus_xxx
device_id: Badezimmer

However 9 out of 10 times I’m getting the following error. The 10th time it works flawlessly and the speaker starts playing:
Fehler: SAM0001E - An unhandled exception occured while processing method “GetInformation”. HTTPConnectionPool(host=‘192.168.1.32’, port=51952): Max retries exceeded with url: /zc/0?action=getInfo&version=2.7.1 (Caused by NewConnectionError(‘<urllib3.connection.HTTPConnection object at 0x7f4d98f710>: Failed to establish a new connection: [Errno 111] Connection refused’))

The good thing is: It seems to know, how to address the speaker, even if home assistants spotify integration doesnt. It correctly resolves its ip address.

Do you have any advice maybe?

@z3p337
Sorry, I do not have any experience with Denons heos speakers.

The Connection refused in the error message leads me to believe that it found the speaker at the IP address, but the Spotify Connect web service running on the speaker refused to reply to the request. This could mean a few different things: the speaker is busy servicing another request (cannot handle multiple requests); the speaker is in a deep sleep state; etc.

You might try calling the player_transfer_playback service to force activate the device, just in case it’s in sleep mode / dropped the connection due to inactivity (see example below).

Prior to calling the player_transfer_playback, you will want to make sure you have the following SpotifyPlus integration options set: LoginId, UserName, and Password. These options are required in order to re-activate a Spotify Connect device.

action: spotifyplus.player_transfer_playback
data:
  entity_id: media_player.spotifyplus_xxx
  device_id: Badezimmer
  play: true
  delay: 0.5
  refresh_device_list: true
  force_activate_device: true
2 Likes

Hi,

You meant the “SpotifyPlus: Get track recommendations” service?
Can you pls explain a little bit more how you use it? It sounds very interesting. Thank you!

Thanks so much for the quick support! :slight_smile: Tried it out. It behaves the same. Sometimes it works, sometimes it doesnt and throws the “connection refused” error.
Seems like 50:50 success rate actually.
I tried to open the speakers ip-address with the port stated in the error (eg http://badezimmer:51952) and get the same error. Chrome states “ERR_CONNECTION_REFUSED”.
The port that spotifyplus uses seems to be dynamic. Maybe denon heos only allows some of the used ports or something…
I’ll figure it out somehow! Thanks so much <3

@z3p337
Check out the Spotify Connect Troubleshooting page for detailed instructions on how to find the port that the Denton HEOS speaker is using. This is basically the process that the SpotifyPlus player_transfer_playback uses to reconnect a device.

It could be that the speaker just needs time in its wake up / reconnect process. You might try increasing the “delay” argument from 0.5 to 7.0. This will cause the service to wait for 7 seconds instead of 500 milliseconds.

If that doesn’t work, then you will need to run some network traces (if you’re familiar with how to do that) to see how the Spotify desktop app is waking up the device. It involves tracing the Zeroconf / mDNS calls and responses.

yes, that’s the service i actually meant. I created a script for the creation of playlists with a few input fields/sliders for the different parameters of that service call, like for example the ‘seed_tracks’ or ‘max_speechiness’.

This script then gets called via an automation once a day with different settings for those parameters and automatically creates 4 playlists for different moods, for example a slow instrumenal only playlist based on a certain song.

2 Likes

Wow. Sounds nice! I hope you are happy to share the script. :grin:

sure! i realized afterwards that it would be a better approach to just delete/add tracks to the same playlists instead of recreating the whole playlist every time. Most of the script is also quite specific for my use case, but the parts with 'get track recommendations" and “add songs to playlist” should give you a good starter to create your own little playlist creator.

To make this more usable i also integrated a template which extracts the IDs from the given Spotify Playlists link(s), so it is possible to just paste the full song link into the field.

i used ai to remove some personal data in the script, just let me know if this broke something:

alias: Spotify | Create Playlist
sequence:
  - alias: Create Playlist
    sequence:
      - variables:
          mix_number: |-
            {%- if mix_title == 'Chill Mix 🌿' -%}
             1
            {%- elif mix_title == 'Happy Mix 🔆' -%}
             2
            {%- elif mix_title == 'Work Mix ☕' -%}
             3
            {%- elif mix_title == 'Psy Mix 🕉️' -%}
             4
            {%- endif -%}
        alias: Set mix_number
      - variables:
          ids_extracted: |-
            {%- set ids = '' -%} {%- for track in mix_tracks -%}
              {%- if track is string -%}
                {%- set url_parts = track.split('?')[0] -%}
                {%- if 'playlist/' in url_parts -%}
                  {%- set id = url_parts.split('playlist/')[1] -%}
                {%- elif 'track/' in url_parts -%}
                  {%- set id = url_parts.split('track/')[1] -%}
                {%- endif -%}
                {%- if id is defined -%}
                  {%- if not loop.first -%},{%- endif -%}
                  {{- id -}}
                {%- endif -%}
              {%- endif -%}
            {%- endfor -%}
        alias: Extract Spotify IDs
        enabled: true
      - action: input_text.set_value
        metadata: {}
        data:
          value: "{{ ids_extracted }}"
        target:
          entity_id: input_text.mix_{{ mix_number }}_playlist_tracks
        alias: Save Selected Tracks
      - action: spotifyplus.unfollow_playlist
        metadata: {}
        data:
          entity_id: media_player.your_spotifyplus_player
          playlist_id: >-
            {{ states('input_text.mix_' ~ mix_number ~ '_playlist_url') | 
            regex_replace(find='spotify:playlist:', replace='',
            ignorecase=False) }}
        alias: SpotifyPlus | Delete Playlist
        enabled: true
      - alias: SpotifyPlus | Get Songs
        action: spotifyplus.get_track_recommendations
        data:
          entity_id: media_player.your_spotifyplus_player
          limit: 50
          seed_tracks: |-
            {% if ids_extracted != '' %}
              {{ ids_extracted }}
            {% else %}
              {{ states('input_text.mix_' ~ mix_number ~ '_playlist_tracks') }}
            {% endif %}
          max_speechiness: "{{ max_speechiness | default (false) }}"
          min_popularity: "{{ min_popularity | default (false)  }}"
          min_liveness: "{{ min_liveness | default (false)  }}"
          min_instrumentalness: "{{ min_instrumentalness | default (false)  }}"
          min_energy: "{{ min_energy | default (false)  }}"
          min_acousticness: "{{ min_acousticness | default (false)  }}"
        enabled: true
        response_variable: track_recommendations
      - alias: SpotifyPlus | Create Playlist
        data:
          entity_id: media_player.your_spotifyplus_player
          name: "{{ mix_title }} | {{ now().strftime('%-d.%m.%y') }}"
          description: >-
            #{{ track_recommendations.result.tracks[0].artists[0].name }}, #{{
            track_recommendations.result.tracks[1].artists[0].name }}, #{{
            track_recommendations.result.tracks[2].artists[0].name }}, #{{
            track_recommendations.result.tracks[3].artists[0].name }}, #{{
            track_recommendations.result.tracks[4].artists[0].name }}, #MORE
            🧙‍♂️🪄 50 Songs, specially created for you on {{ now().strftime('%-d.%m.%y') }} at {{
            states('sensor.time') }}!
          public: false
          collaborative: false
          image_path: www/images/Playlists/mix_{{ mix_number }}.jpg
        action: spotifyplus.playlist_create
        response_variable: playlist
      - data:
          entity_id: media_player.your_spotifyplus_player
          playlist_id: >-
            {{ playlist.result.href |
            regex_replace(find='https://api.spotify.com/v1/playlists/',
            replace='', ignorecase=False) }}
          uris: >-
            {{ track_recommendations.result.tracks[0].uri }}, {{
            track_recommendations.result.tracks[1].uri }}, {{
            track_recommendations.result.tracks[2].uri }}, {{
            track_recommendations.result.tracks[3].uri }}, {{
            track_recommendations.result.tracks[4].uri }}, {{
            track_recommendations.result.tracks[5].uri }}, {{
            track_recommendations.result.tracks[6].uri }}, {{
            track_recommendations.result.tracks[7].uri }}, {{
            track_recommendations.result.tracks[8].uri }}, {{
            track_recommendations.result.tracks[9].uri }}, {{
            track_recommendations.result.tracks[10].uri }}, {{
            track_recommendations.result.tracks[11].uri }}, {{
            track_recommendations.result.tracks[12].uri }}, {{
            track_recommendations.result.tracks[13].uri }}, {{
            track_recommendations.result.tracks[14].uri }}, {{
            track_recommendations.result.tracks[15].uri }}, {{
            track_recommendations.result.tracks[16].uri }}, {{
            track_recommendations.result.tracks[17].uri }}, {{
            track_recommendations.result.tracks[18].uri }}, {{
            track_recommendations.result.tracks[19].uri }}, {{
            track_recommendations.result.tracks[20].uri }}, {{
            track_recommendations.result.tracks[21].uri }}, {{
            track_recommendations.result.tracks[22].uri }}, {{
            track_recommendations.result.tracks[23].uri }}, {{
            track_recommendations.result.tracks[24].uri }}, {{
            track_recommendations.result.tracks[25].uri }}, {{
            track_recommendations.result.tracks[26].uri }}, {{
            track_recommendations.result.tracks[27].uri }}, {{
            track_recommendations.result.tracks[28].uri }}, {{
            track_recommendations.result.tracks[29].uri }}, {{
            track_recommendations.result.tracks[30].uri }}, {{
            track_recommendations.result.tracks[31].uri }}, {{
            track_recommendations.result.tracks[32].uri }}, {{
            track_recommendations.result.tracks[33].uri }}, {{
            track_recommendations.result.tracks[34].uri }}, {{
            track_recommendations.result.tracks[35].uri }}, {{
            track_recommendations.result.tracks[36].uri }}, {{
            track_recommendations.result.tracks[37].uri }}, {{
            track_recommendations.result.tracks[38].uri }}, {{
            track_recommendations.result.tracks[39].uri }}, {{
            track_recommendations.result.tracks[40].uri }}, {{
            track_recommendations.result.tracks[41].uri }}, {{
            track_recommendations.result.tracks[42].uri }}, {{
            track_recommendations.result.tracks[43].uri }}, {{
            track_recommendations.result.tracks[44].uri }}, {{
            track_recommendations.result.tracks[45].uri }}, {{
            track_recommendations.result.tracks[46].uri }}, {{
            track_recommendations.result.tracks[47].uri }}, {{
            track_recommendations.result.tracks[48].uri }}, {{
            track_recommendations.result.tracks[49].uri }}
        action: spotifyplus.playlist_items_add
        enabled: true
        alias: SpotifyPlus | Add Songs to Playlist
      - action: input_text.set_value
        metadata: {}
        data:
          value: >-
            {{ playlist.result.href |
            regex_replace(find='https://api.spotify.com/v1/playlists/',
            replace='spotify:playlist:', ignorecase=False) }}
        target:
          entity_id: input_text.mix_{{ mix_number }}_playlist_url
        enabled: true
        alias: Playlist Sensor | Set Playlist URL
      - action: spotifyplus.playlist_cover_image_add
        metadata: {}
        data:
          playlist_id: >-
            {{ playlist.result.href |
            regex_replace(find='https://api.spotify.com/v1/playlists/',
            replace='', ignorecase=False) }}
          image_path: www/images/Playlists/mix_{{ mix_number }}.jpg
          entity_id: media_player.your_spotifyplus_player
        alias: SpotifyPlus | Add Cover Image
description: Create dynamic playlist with SpotifyPlus.
icon: mdi:spotify
fields:
  mix_title:
    selector:
      select:
        options:
          - Chill Mix 🌿
          - Happy Mix 🔆
          - Work Mix ☕
          - Psy Mix 🕉️
    name: Playlist Title
    required: true
    default: Chill Mix 🌿
  mix_tracks:
    selector:
      text:
        multiple: true
        multiline: false
    default: []
    name: "Insert Song Links:"
    required: true
  max_speechiness:
    name: Maximum Speechiness
    description: Maximum value for speech-like characteristics (0.0 to 1.0)
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
  min_popularity:
    name: Minimum Popularity
    description: Minimum popularity rating (0 to 100)
    selector:
      number:
        min: 0
        max: 100
        step: 1
  min_liveness:
    name: Minimum Liveness
    description: Minimum liveness value (0.0 to 1.0)
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
  min_instrumentalness:
    name: Minimum Instrumentalness
    description: Minimum instrumentalness value (0.0 to 1.0)
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
  min_energy:
    name: Minimum Energy
    description: Minimum energy value (0.0 to 1.0)
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
  min_acousticness:
    name: Minimum Acousticness 
    description: Minimum acousticness value (0.0 to 1.0)
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
2 Likes

Cool. I will give it a try! Thank you very much for sharing!

@shpongledsummer
Just wanted to mention that it’s possible for the service to return less than 50 items in the tracks collection depending on the criteria specified. I mention it due to the hard coded array item references you are doing (eg result.tracks[n], etc). Another alternative is to use a loop.

You probably won’t have any issues if your criteria is pretty generic though.

Just wanted you to be aware though.

haha yes i know, my code is not very elegant and I should have mentioned that there will be an error if the search results is less than 50 tracks. which happens quite fast if the search parameters are too specific. thanks for pointing this out!

1 Like