Simple user interface for Music Assistant?

I'm after a device running Music Assistant and Voice Assist ... but for my 75 year old mum it needs a simple user interface, probably with a dozen radio buttons for playlists and radio stations, suitable for playing music in the background as she's cooking.

All the music players I found a few years ago seem designed for audiophiles who want all the bells and whistles as they select each track individually :frowning:

Is this even possible with MA ? Can you point me in the right direction or suggest appropriate keywords to search for ?


FYI, this is the python program I wrote a few years ago as a front end to mpd and running on a RasPi. My other option is to try adding Linux Voice Assist to this.

I'm using LMS instead of MA, so I don't know the details of MA, but basic control can be achieved using media_player services. Since you're no stranger to programming, you could create the UI yourself, for example, using custom:button-card. Basic JavaScript and CSS knowledge will be sufficient.

If you'd like to try it, to get you started, here's a dashboard/view in single-card mode that mimics part of your program:

Code
views:
  - type: panel
    path: music
    title: Music
    cards:
      - type: custom:button-card
        show_name: false
        variables:
          player: media_player.squeezeesp32
          stateObj: '[[[ return states[variables.player]; ]]]'
          state: '[[[ return variables.stateObj?.state; ]]]'
          attrs: '[[[ return variables.stateObj?.attributes; ]]]'
          volume: '[[[ return variables.attrs?.volume_level; ]]]'
          title: '[[[ return variables.attrs?.media_title; ]]]'
          artist: '[[[ return variables.attrs?.media_artist; ]]]'
          album: '[[[ return variables.attrs?.media_album_name; ]]]'
          contentId: '[[[ return variables.attrs?.media_content_id; ]]]'
        name: '[[[ return variables.state ]]]'
        styles:
          card:
            - cursor: default
            - padding: 16px
          grid:
            - text-align: start
            - font-size: 125%
            - grid-template-areas: |
                "title  title  title title ."
                "album  album  album .     ."
                "up     pause  prev  .     ."
                "down   pause  next  .     ."
                "r1     .      .     .     ."
                "r2     .      .     .     ."
                "r3     .      .     .     ."
            - grid-template-rows: auto
            - grid-template-columns: repeat(3, 150px) auto
          custom_fields:
            title:
              - background-color: rgba(var(--rgb-card-background-color),0.5)
              - padding: 4px
              - margin-bottom: 8px
            album:
              - background-color: rgba(var(--rgb-card-background-color),0.5)
              - padding: 4px
              - margin-bottom: 16px
            pause:
              - margin: 0px 8px
            up:
              - margin-bottom: 8px
            prev:
              - margin-bottom: 8px
            r1:
              - margin-top: 16px
              - margin-bottom: 8px
            r2:
              - margin-bottom: 8px
        custom_fields:
          title: '[[[ return `${variables.title} - ${variables.artist}`; ]]]'
          album: '[[[ return variables.album ?? ''-- no album --''; ]]]'
          up:
            card:
              type: custom:button-card
              name: Vol +
              styles:
                card:
                  - background-color: var(--card-background-color)
                  - font-size: 1em
              tap_action:
                action: |
                  [[[ 
                    const v = variables.volume;
                    return isNaN(v) || v == 1 ? 'none' : 'perform-action'
                  ]]]
                perform_action: media_player.volume_set
                target:
                  entity_id: '[[[ return variables.player; ]]]'
                data:
                  volume_level: '[[[ return Math.min(variables.volume + 0.05, 1); ]]]'
          down:
            card:
              type: custom:button-card
              name: Vol -
              styles:
                card:
                  - background-color: var(--card-background-color)
                  - font-size: 1em
              tap_action:
                action: |
                  [[[ 
                    const v = variables.volume;
                    return isNaN(v) || v == 0 ? 'none' : 'perform-action'
                  ]]]
                perform_action: media_player.volume_set
                target:
                  entity_id: '[[[ return variables.player; ]]]'
                data:
                  volume_level: '[[[ return Math.max(variables.volume - 0.05, 0); ]]]'
          pause:
            card:
              type: custom:button-card
              name: Pause
              styles:
                card:
                  - background-color: var(--card-background-color)
                  - font-size: 1em
                  - min-height: 3em
              tap_action:
                action: perform-action
                perform_action: media_player.media_pause
                target:
                  entity_id: '[[[ return variables.player; ]]]'
          prev:
            card:
              type: custom:button-card
              name: << Prev
              styles:
                card:
                  - background-color: var(--card-background-color)
                  - font-size: 1em
              tap_action:
                action: perform-action
          next:
            card:
              type: custom:button-card
              name: Next >>
              styles:
                card:
                  - background-color: var(--card-background-color)
                  - font-size: 1em
              tap_action:
                action: perform-action
          r1:
            card:
              type: custom:button-card
              name: Radio NŚ
              styles:
                card:
                  - background-color: |
                      [[[
                        const me = 'ypqt40u0x1zuv';
                        const id = variables.contentId;
                        const title = variables.title;
                        return id.includes(me) || title.includes(me) ? 'brown' : 'var(--card-background-color)';
                      ]]]
                  - font-size: 1em
              tap_action:
                action: perform-action
                perform_action: media_player.play_media
                target:
                  entity_id: '[[[ return variables.player; ]]]'
                data:
                  media_content_id: https://n20a-eu.rcs.revma.com/ypqt40u0x1zuv
                  media_content_type: music
          r2:
            card:
              type: custom:button-card
              name: Radio 357
              styles:
                card:
                  - background-color: |
                      [[[
                        const me = 'an1ugyygzk8uv';
                        const id = variables.contentId;
                        const title = variables.title;
                        return id.includes(me) || title.includes(me) ? 'brown' : 'var(--card-background-color)';
                      ]]]
                  - font-size: 1em
              tap_action:
                action: perform-action
                perform_action: media_player.play_media
                target:
                  entity_id: '[[[ return variables.player; ]]]'
                data:
                  media_content_id: https://n31a-eu.rcs.revma.com/an1ugyygzk8uv
                  media_content_type: music
          r3:
            card:
              type: custom:button-card
              name: Other radio
              styles:
                card:
                  - background-color: |
                      [[[
                        const me = 'xyz';
                        const id = variables.contentId;
                        const title = variables.title;
                        return id.includes(me) || title.includes(me) ? 'brown' : 'var(--card-background-color)';
                      ]]]
                  - font-size: 1em


Colors come from my theme; you have full control over colors, font sizes, etc.; you can embed helpers/selectors (e.g. through entities card); you can enhance/declutter the code with custom button templates.

Thanks slimak, I haven't used custom:button-card before, but it does look like what I'm after, and it's been quite a while since I've done much website development - so I have some work ahead :wink: Thank you also for such a large chunk of code to get me started ... most appreciated.

So, if I understand correctly, the idea is to install Linux Voice Assist and a browser (to run full-screen in kiosk mode) on the Kitchen RasPi, with the browser configured to log into HA as a user who only sees the Music panel.

Ha! If you have experience with web development, UI should be easy. For me, the hardest part is always tackling CSS :slight_smile: Ask if you have any problems.

CSS obviously hasn't gotten any better in the past 20 years :cry: Except maybe that the C now stands for "complex" instead of "cascading"