Spotify Playlist in a Picture Card

I am just after some general guidance, not necessarily the whole code.

I noticed in some themes that people had a picture card that was a picture of a Spotify playlist, and I presumed when clicked, it played the playlist on the current device.

Example:

I am not even sure of what to Google for help. If anyone can point me in the right direction, that would be great.

By the way, I already have Spotify connected and working.

3 Likes

I Have not done this

But From what I understand you can get spotify to play a playlist
could you just get each Picture to play a differance play list

Hello @myle

Are you thinking the images are saved locally, and not actually loaded dynamically via spotify?

yes That my thinking

ok. Well, that’s a start. I was thinking it was more dynamic. I got this card up and running:


It pulls a list of playlists into lovelace. Works well, but I just like the look of the large images more. Your solution may be the only one, but I really hope there is more of an automatic one.
1 Like

The Spotify card also comes with the Spotcast custom component. Did you check out that documentation? I’m curious if we can find a solution, would like this too…

I just installed spotcast, because it talks to my idle chromecast devices.

I did see a few buttons in the documentation that might do the trick. I just need to play around with it. I only started with HA about a week ago, so will take me a bit to understand. Hopefully, it will work. In the meantime, hopefully, one of the experts with Spotify might shed some light :flashlight:

Just gathering puzzle pieces. This creates a sensor containing your most recent playlists, including images:


Perhaps there is a custom card to display this sensor data as clickable pictures?
Edit: maybe this one:

This is getting close. The Spotify Playlist Sensor is just what I need. However, it actually has a couple of issues and wont work.

I just spent some time and figured them out and reported the issues on github.

I now have all of my playlists in a sensor. It is late here, and I am off to bed. I will do the rest tomorrow, and let you know how I go.

1 Like

Ok. Have been working on this half the day. I finally got it to work, but please note. I have only used Home Assistant, and lovelace for about 1 week, so I am no expert.

I used the Spotify Playlist Sensor to get my playlists:


Please note, there is a config error with this plugin. It does not gather the correct permissions for the Spotify API. Please see issue 2 on github for a fix. Hopefully it will be updated soon.

I also have installed Mini Media Player and Spotcast:


I did not know how to display the playlists myself, so I then installed the Spotify Playlist Card:

Now… They say beauty is in the eye of the beholder, and without any offence to dnguyen800, who created this card and for which I am very grateful… I really didn’t like the way it looked. Nothing lined up to existing cards, I didn’t want the Spotify text on the left, when names were displayed they were messy and the font needed a specific background to be seen… Anyway, long story short, I did an absolute hack job of his code (never coded js before) and changed the way it looks.


For Comparison, my design on the left, old version on the right.

Here is what it looks like with my speaker controls:

For the speaker controls, I used the custom:slider-entity-row

Now, the real issue… Clicking on a playlist and have it start playing… Seems easy, but it is all a bit confusing to me. When configuring the playlist card, it says that speaker_name is optional, but I could not get it to play without indicating the speaker. Then, that causes issues because when you press the button, you want it to play where the music is currently playing.

My hack/solution for this was to set the speaker_name to a group I set up called ‘All Speakers’. So, when I click a playlist it turns on all speakers, and you then set the volume using the sliders for each speaker.

The good news, is that the system remembers the volume for each speaker even after being idle. So pressing a playlist button in 1 hour plays on the same speakers at the same volume.

Here is the new code I used for the spotify-playlist-card.js
I am no expert, and have no idea how to interact with github to ‘fork’ or what ever I need to do.

// Examples:
// const entityId = this.config.entity;
// const playlist = hass.states[entityId].attributes;
//    ${playlist['Unorganized']['name']}<br>
// ${playlist['Unorganized']['image']}<br>
// ${playlist['Unorganized']['uri']}<br>


class SpotifyPlaylistCard extends HTMLElement {

    constructor() {
      super();
      this.attachShadow({ mode: 'open' });
    }
  
    setConfig(config) {
      if (!config.entity) {
        throw new Error('Please define an entity.');
      }
      const root = this.shadowRoot;
      if (root.lastChild) root.removeChild(root.lastChild);
  
      const cardConfig = Object.assign({}, config);
      if (!cardConfig.title) {
        cardConfig.title = `Playlists`;
      } 
      
      if (!config.size) {
        config.size = `15vmin`;
      }

      if (!config.columns) {
        config.columns = 3;
      }
        config.columns = config.columns-1;
      if (!config.media_player) {
        config.media_player = `default`;
      }

      if (!config.speaker_name) {
        config.speaker_name = `speaker name`;
      }

      const card = document.createElement('div');
      const content = document.createElement('div');
      const style = document.createElement('style');

      style.textContent = `

            button {
              border: 0px;
              padding: 0;
              color: #FFFFFF;
              border: 0;
              width:100%
              font-size: 14px;
              margin: 0;
              background-color: rgba(0,0,0,0.0);
            }

            button:hover {
            }

            button img {
                display: block;
                width: 100%;
                border: 2px;            
            `;
             
      style.textContent += `    height: `;
      style.textContent += config.size;
      style.textContent += `;
                width: `;
      style.textContent += config.size;
      style.textContent += `;
    }`
            
      style.textContent += `
            .grid-container {
              justify-content: center;
              justify-items: center;
              align-items: center;
              display: grid;
              border: 0;
              grid-gap: 8px; 
              grid-template-columns: auto`;
      var cssColumns = ' auto'.repeat(config.columns);
      style.textContent += cssColumns;
      style.textContent += `;}

      .grid-item {
        border: 0;
        padding: 0;
        position: relative; 
      }
      .grid-item-text {
        border: 0;
        padding: 18px 10px 10px 10px;
        text-align:left;
        position: absolute; 
        bottom: 0; 
        width: 100%;
        box-sizing:border-box;
        background: rgb(0,0,0);
        background: linear-gradient(360deg, rgba(0,0,0,1) 0%, rgba(0,0,0,0.698383577063638) 69%, rgba(0,0,0,0) 100%);
        overflow: hidden;
        text-overflow: ellipsis;
      }
      `; 
      content.innerHTML = `
      <div id='content'>
      </div>
      `;
      
      if (config.show_title) {
        card.header = cardConfig.title;
      }
      card.appendChild(content);
      card.appendChild(style);
      root.appendChild(card);
      this._config = cardConfig;
    }
  
 
    set hass(hass) {
      const config = this._config;
      const root = this.shadowRoot;
      const card = root.lastChild;
      this.myhass = hass;
      let card_content = ''
      card_content += `
      <div class="grid-container">
      `;
       
      if (hass.states[config.entity]) {
        const playlist = hass.states[config.entity].attributes;

        let column_count = 0
        
        for (let entry in playlist) {
          if (entry !== "friendly_name" && entry !== "icon" && entry !== "homebridge_hidden") {
            card_content += `<div class="grid-item"><button raised id ='playlist${playlist[entry]['id']}'><img src="${playlist[entry]['image']}"></button>`;
            if (config.show_name == true) {
              card_content += `<div class="grid-item-text">${playlist[entry]['name']}</div>`
            };
            card_content += `</div>`;
          }
        } 
      };
      card_content += `</div>`;
//      card_content += `
//      <ha-icon icon="mdi:speaker"></ha-icon><select name="device_name">
//      `;    

//      if (hass.states['sensor.chromecast_devices']) {
//        const chromecastSensor = hass.states['sensor.chromecast_devices'];
        
//        if (chromecastSensor) {
//         const chromecastDevices = JSON.parse(chromecastSensor.attributes.devices_json);
//          for (let x in chromecastDevices) {
//            card_content += `
//              <option value ="${chromecastDevices[x]['name']}">${chromecastDevices[x]['name']}</option>
//            `;  
//          }
//        }


//      };
      
//      card_content += `</select>`;


      root.lastChild.hass = hass;
      root.getElementById('content').innerHTML = card_content;

      if (hass.states[config.entity]) {
        const playlist = hass.states[config.entity].attributes;
        const media_player = config.media_player;
        const speaker_name = config.speaker_name;

        for (let entry in playlist) {
          if (entry !== "friendly_name" && entry !== "icon" && entry !== "homebridge_hidden") {
            card.querySelector(`#playlist${playlist[entry]['id']}`).addEventListener('click', event => {
              if (media_player == "echo") {
                const myPlaylist = {"entity_id": speaker_name, "media_content_type": "playlist", "media_content_id": `${playlist[entry]['uri']}`};
                this.myhass.callService('media_player', 'play_media', myPlaylist);                
              }
              else if (media_player == "spotcast") {
                const spotcastPlaylist = {"device_name": speaker_name, "uri": `${playlist[entry]['uri']}`};
                this.myhass.callService('spotcast', 'start', spotcastPlaylist);
              }
            });            
          }  
        }
      }
    }
    getCardSize() {
      return 1;
    }
}
  
customElements.define('spotify-playlist-card', SpotifyPlaylistCard);

I really only changed the styling and added a couple of divs so I could position the name text. Sorry if this doesn’t work for you. If you do use this code, remember you need to get around the cache. You can do this by changing the line in your raw config:
url: /local/spotify-playlist-card.js
Just add a variable to the query string of the url:
/local/spotify-playlist-card.js?v=1

I hope this helps someone. Sorry if my hacking raises a few eyebrows. I will get better over time I am sure.

Lastly, a BIG shout out to the people who make all of the custom cards and other plugins. They are so useful!!! Thank you.

1 Like

Here is an example of my interface while music is playing. All speakers are awake, but most here are off.

I also added a conditional mini-player to show when there is music playing. Seemed pretty useless to show it while nothing was playing.

No offense taken. Your version definitely looks better than mine. This was also my first time playing around with HTML/CSS/Javascript so there was definitely room for improvement. Good job!

I stopped updating the Spotify playlist sensor and card since @fondberg 's card is much better.

It’s better in a way, but it doesn’t give you the pretty UI that @bretteldridge built. You sure you won’t update your card? It looks like a fine companion to @fondberg’s card.

1 Like

I agree. I like quite a few things about this one compared to the other, to be honest.

@dwinnn, I am happy to help you with CSS. I am not too bad at CSS and design. I know nothing about JS and am totally new to HA. I have 2 small boys, so can only work odd hours, but am happy to help if you want. Just let me know.

Hey @bretteldridge, I’m going to make some updates to the card this month and I’ll let you know where I can use some help. I think the biggest help I could use from you regarding CSS is how to format the width of the playlist images to use 100% width of the card. Right now it is defined in the configuration option size but doesn’t change appropriately based on the web browser’s screen width. If you could figure out how to get the value of the card’s width, then I can use that value (or %?), divide by number of columns, and space the images evenly.

Here is my wish list for the card. No promises though.

  • Integrate your changes (like the title display) to the card as a configuration option
  • Add Spotcast, Alexa media player custom components as playback options
  • Add drop-down menu to select available Chromecast speakers
  • (For Spotify playlist sensor) Use existing token created by Spotify component instead of separate token
3 Likes

Has anyone been able to display the content /tracks of playlist chosen in format above ?

Kr,

B

anyone has an idea ?

+1 for this.

Would also be really neat if it could display new podcast episodes.

Hi to all. does anybody know how to set up this “follow music” slider from bretteldridges first post?
nice feature…