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.