hokiebrian / spotify_plus

Enhanced Spotify Experience for Home Assistant
Apache License 2.0
15 stars 0 forks source link

More detailed examples please #1

Closed matiasoberg closed 8 months ago

matiasoberg commented 9 months ago

First of all.

Fabulous job on this HA Spotify Integration. @hokiebrian

Was contemplating to dive into the Spotify API myself but you have already done all the needed job. It's a shame it doesn't get more exposure in the HA community.

Your examples looks great and I think it would be very valuable if you could share the code on how you have set up those examples. Explanations are great already but getting a bit of push in the right direction would save a lot of us a lot of time I recon.

Cheers!

matiasoberg commented 9 months ago

I really wanted a good way to see the current queue but still struggle with Jinja2 and templates.

Came up with this code as a custom card that leverages on the awesomeness of what's already provide by @hokiebrian. Ugly workaround but does the trick.

Create a .js file in /config/www/ Name it something appropriate. I called mine spotify-queue-card.js Reference it as a Lovelace reference. Restart and create the card manually.


class SpotifyQueueCard extends HTMLElement {
    set hass(hass) {
        if (!this.content) {
            this.innerHTML = `
                <ha-card>
                    <div class="now-playing"></div>
                    <div class="up-next"></div>
                </ha-card>
            `;
            this.nowPlaying = this.querySelector(".now-playing");
            this.upNext = this.querySelector(".up-next");
        }

        const nowPlayingState = hass.states[this.config.now_playing_sensor];
        const queueState = hass.states[this.config.queue_sensor];

        // Now playing section
        if (nowPlayingState && nowPlayingState.attributes.spotify) {
            const nowPlaying = nowPlayingState.attributes.spotify;
            const trackName = nowPlaying.spotify_track_name || 'Unknown Track';
            const artistName = nowPlaying.spotify_artist_name || 'Unknown Artist';
            const artistImage = nowPlaying.spotify_artist_img || '';
            this.nowPlaying.innerHTML = `
                <div>
                    <strong>Now playing:</strong><br>
                    <ul style="margin-top: 10px;">
                        <li style="display: flex; align-items: center; margin-bottom: 5px;">
                            <span style="width: 30px; text-align: right; margin-right: 10px;">1</span>
                            <img src="${artistImage}" height="50px" style="margin-right: 10px;">
                            <span>${trackName}<br>${artistName}</span>
                        </li>
                    </ul>
                </div>
            `;
        } else {
            this.nowPlaying.innerHTML = '<strong>Now playing:</strong> No current track';
        }

        // Up next section
        if (queueState && queueState.attributes && queueState.attributes.queue) {
            const queue = queueState.attributes.queue;
            const playlistName = nowPlayingState && nowPlayingState.attributes.spotify && nowPlayingState.attributes.spotify.spotify_playlist && nowPlayingState.attributes.spotify.spotify_playlist.name ? nowPlayingState.attributes.spotify.spotify_playlist.name : 'Unknown Playlist';
            this.upNext.innerHTML = `
                <strong>Next from: ${playlistName}</strong>
                <ul style="margin-top: 10px;">
                    ${queue.map((track, index) => `
                        <li style="display: flex; align-items: center; margin-bottom: 5px;">
                            <span style="width: 30px; text-align: right; margin-right: 10px;">${index + 2}</span>
                            <img src="${track.image}" height="50px" style="margin-right: 10px;">
                            <span>${track.trackname}<br>${track.trackartist}</span>
                        </li>`
                    ).join('')}
                </ul>
            `;
        } else {
            this.upNext.innerHTML = '<strong>Next from:</strong> No tracks in queue';
        }

        this.applyStyles();
    }

    applyStyles() {
        this.style.cssText = `
            ha-card {
                padding: 16px;
                color: var(--primary-text-color);
                background-color: var(--card-background-color);
            }
            .now-playing, .up-next {
                margin-bottom: 16px;
            }
            ul {
                list-style-type: none;
                padding: 0;
            }
            li {
                display: flex;
                align-items: center;
                margin-bottom: 10px;
            }
            img {
                border-radius: var(--ha-card-border-radius);
            }
            span {
                margin-right: 10px;
            }
        `;
    }

    setConfig(config) {
        if (!config.now_playing_sensor || !config.queue_sensor) {
            throw new Error("You need to define both now_playing_sensor and queue_sensor");
        }
        this.config = config;
    }

    getCardSize() {
        return 4;
    }
}

customElements.define("spotify-queue-card", SpotifyQueueCard);
hokiebrian commented 8 months ago

Here's what I use for my queue. It will show the song info, album art, and have tap actions to add to library, play, etc.

          - type: custom:auto-entities
            card:
              square: false
              type: grid
              columns: 2
            card_param: cards
            filter:
              template: |-
                {%-
                   for item in state_attr('sensor.spotify_extras', 'queue') -%}
                     {{
                       {
                         'type': 'custom:button-card',
                         'layout': 'icon_name_state2nd',
                         'template': ['recentqueue'],
                         'variables': {'itemName':item.trackname, 'itemURI':item.trackuri, 'itemID':item.trackuri, 'itemSaved':item.saved},
                         'label': '',
                         'name': item.trackartist + '<br>' + item.trackname,
                         'entity_picture': item.image,
                         'tap_action': {'service_data':{'media_content_type':'music','media_content_id': item.trackuri} },
                         'double_tap_action': {'service_data':{'libraryinfo':'track','track_id': item.trackuri,'origin':'RecentQueue'} },
                         'hold_action': {'service_data':{'libraryinfo':'untrack','track_id': item.trackuri,'origin':'RecentQueue'} },
                       }
                     }},
                     {%- endfor %}

The referenced template is:

   recentqueue:
    variables:
      itemPlayed: none
    label: |
      [[[
      const timeago = (new Date() - new
      Date(variables.itemPlayed)) / 1000;
      const minutes = Math.floor((timeago / 60));
      const formattedMinutes = String(minutes).padStart(0, '0');
      return `${formattedMinutes} minutes ago`;
       ]]]
    show_label: true
    show_name: true
    aspect_ratio: 4.5/1
    show_entity_picture: true
    styles:
      card:
        - font-size: .6em
        - padding: 0px
        - border: 0px
      entity_picture:
        - scale: 1
        - padding: 0px
      custom_fields:
        logo:
          - position: absolute
          - top: 35%
          - left: 31%
          - color: firebrick
          - opacity: |
              [[[ 
                 if (variables.itemSaved) return "90%";
                   else return "10%";
              ]]] 
    custom_fields:
      logo: |
        [[[
           return `<img src=/local/spotify-icon.png width=15 height=15>`
        ]]]
    tap_action:
      action: call-service
      haptic: light
      service: media_player.play_media
      confirmation:
        text: '[[[ return `Play: ${variables.itemName}` ]]]'
      service_data:
        media_content_type: playlist
        entity_id: media_player.spotify_home
    double_tap_action:
      action: call-service
      haptic: light
      service: script.song_library_tools
Screenshot 2023-12-26 at 8 15 36 PM
matiasoberg commented 8 months ago

Brilliant!

Thanks a lot for sharing @hokiebrian !

Keep up the great work :)

matiasoberg commented 8 months ago

Got it to work. One final piece of the puzzle I'm missing is the "script.song_library_tools" that you reference in the template. Care to share that one too?

Thanks!

hokiebrian commented 8 months ago

Here's the YAML for the script. It basically takes the argument for what you're trying to do (add track, add album, add artists, etc) and then triggers a refresh of the song data to reflect the updated library status. Worth noting - the mode is queued with max of 10. That way you can blast through adding multiple tracks, etc. The delay step ended up needing to be added because there were some cases when the refresh was too fast and the library status wasn't reflected properly upon refresh.

alias: Song Library Tools
fields:
  libraryinfo:
    name: Library Function
    description: Type of function
    required: true
    example: track
    selector:
      text: null
  playlist_id:
    name: Playlist URI
    description: Playlist URI
    required: false
    example: spotify:playlist:ABC123
    selector:
      text: null
  album_id:
    name: Album ID
    description: Album ID
    required: false
    example: ABC123
    selector:
      text: null
  track_id:
    name: Track ID
    description: Track ID
    required: false
    example: ABC123
    selector:
      text: null
  update_card:
    name: Update Card?
    description: Refresh origin card?
    required: false
    example: true
    selector:
      boolean: null
  origin:
    name: Search Origin
    description: Search Origin
    required: false
    example: Artist Profile
    selector:
      select:
        options:
          - label: Artist Profile
            value: Artist Profile
          - label: General Search
            value: General Search
sequence:
  - choose:
      - conditions:
          - condition: template
            value_template: |
              {{ libraryinfo == "playlist" }}
        sequence:
          - service: spotify_plus.spotify_follow_playlist
            data:
              playlist_id: "{{ playlist_id }}"
      - conditions:
          - condition: template
            value_template: |
              {{ libraryinfo == "album" }}
        sequence:
          - service: spotify_plus.spotify_follow_album
            data:
              album_id: "{{ album_id }}"
      - conditions:
          - condition: template
            value_template: |
              {{ libraryinfo == "track" }}
        sequence:
          - service: spotify_plus.spotify_follow_track
            data:
              track_id: "{{ track_id }}"
      - conditions:
          - condition: template
            value_template: |
              {{ libraryinfo == "artists" }}
        sequence:
          - service: spotify_plus.spotify_follow_artist
            data:
              artist_id: "{{ artist_id }}"
      - conditions:
          - condition: template
            value_template: |
              {{ libraryinfo == "unplaylist" }}
        sequence:
          - service: spotify_plus.spotify_unfollow_playlist
            data:
              playlist_id: "{{ playlist_id }}"
      - conditions:
          - condition: template
            value_template: |
              {{ libraryinfo == "unalbum" }}
        sequence:
          - service: spotify_plus.spotify_unfollow_album
            data:
              album_id: "{{ album_id }}"
      - conditions:
          - condition: template
            value_template: |
              {{ libraryinfo == "untrack" }}
        sequence:
          - service: spotify_plus.spotify_unfollow_track
            data:
              track_id: "{{ track_id }}"
      - conditions:
          - condition: template
            value_template: |
              {{ libraryinfo == "unartists" }}
        sequence:
          - service: spotify_plus.spotify_unfollow_artist
            data:
              artist_id: "{{ artist_id }}"
  - delay:
      hours: 0
      minutes: 0
      seconds: 0
      milliseconds: 250
  - if:
      - condition: template
        value_template: "{{ update_card }}"
    then:
      - service: spotify_plus.spotify_search
        data:
          search_term: "{{ states('input_text.spotify_search')}}"
          search_type: "{{ origin }}"
  - if:
      - condition: template
        value_template: "{{ is_state('media_player.spotify_home', ['playing', 'paused']) }}"
    then:
      - service: spotify_plus.get_song_data
        data: {}
      - service: spotify_plus.spotify_extras
        data: {}
mode: queued
max: 10
matiasoberg commented 8 months ago

You are a true gentleman sir. Thank you very much for sharing your vast knowledge on this topic. I'm learning a lot by just going through your code and adopting it to my own needs. Thanks again and Happy New Year :)

gterras commented 8 months ago

Hi,

First of all.

Fabulous job on this HA Spotify Integration. @hokiebrian

Was contemplating to dive into the Spotify API myself but you have already done all the needed job. It's a shame it doesn't get more exposure in the HA community.

I was about to create an issue to say exactly the same thing so better hijack this closed thread instead : this integration is incredibly powerful and well-crafted, thanks a lot @hokiebrian this should have way more exposure.

Currently playing with the possibilities, I second the idea of publishing some ideas of advanced scripts like you did in this thread.