Using HA's universal media player integration

I wanted to share how I’m using HA’s universal media player (UMP) integration to enable better control of the AmpliPi from a couple of tablets I have around the house.

For background, I have 5 sets of ceiling/outdoor speakers that I have wired as Zones on the AmpliPi. I have created 4 sources: 2 streaming radio (CBC Radio 1 and CBC Music), Airplay and Spotify.

My goal was to create a WAF-friendly front-end player. To do this, I have used the mini-media-player from HACS which I think looks nicer.

Now, on to the code…

lovelace.yaml
In here I have specifically disabled the play_pause, next, previous, and power buttons as they don’t directly map to how AmpliPi works.

type: custom:mini-media-player
entity: media_player.test
artwork: cover
hide:
  power: true
  next: true
  prev: true
  play_pause: true
  play_stop: false
  progress: true

input_select.yaml
Here is where you can create family-friendly names for the sources you have defined through the AmpliPi web-interface. This is what will be displayed on the mini-media-player lovelace and when you select the source.

amplipi_sources:
  options:
    - CBC Radio 1
    - CBC Music
    - AirPlay
    - Spotify 

scripts.yaml
I cannot take credit for this, but reading through the forums of the HA community, I was able to piece this together. Essentially, it translates the family-friendly names of the sources defined above to the sources as listed by the AmpliPi HA integration.

amplipi_change_source:
  sequence:
    - service: input_select.select_option
      data_template:
        entity_id: input_select.amplipi_sources
        option: "{{ source }}"
    - service: media_player.select_source
      data_template:
        entity_id: "{{ mediaplayer }}"
        source: >
            {% set mapper = {
                'CBC Radio 1': 'Source 1',
                'CBC Music': 'Source 2',
                'AirPlay': 'Source 3',
                'Spotify': 'Source 4' } %}
            {{ mapper[source] if source in mapper else 'Source 1' }} 

media_player.yaml
Finally, the guts of everything. Here you define how commands are handled and which of the multiple AmpliPi integration media players they are sent to.

- platform: universal
  name: Test
  state_template: >
      {% if is_state_attr('media_player.office', 'source', 'Source 1') %}
        {{ states('media_player.source_1') }}
      {% elif is_state_attr('media_player.office', 'source', 'Source 2') %}
        {{ states('media_player.source_2') }}
      {% elif is_state_attr('media_player.office', 'source', 'Source 3') %}
        {{ states('media_player.source_3') }}
      {% else %}
        {{ states('media_player.source_4') }}
      {% endif %}
  children:
    - media_player.office
    - media_player.source_1    
    - media_player.source_2
    - media_player.source_3
    - media_player.source_4        
  commands:
    media_play:
      service: media_player.media_play
      target:
        entity_id: >
            {% if is_state_attr('media_player.office', 'source', 'Source 1') %}
              media_player.source_1
            {% elif is_state_attr('media_player.office', 'source', 'Source 2') %}
              media_player.source_2
            {% elif is_state_attr('media_player.office', 'source', 'Source 3') %}
              media_player.source_3
            {% else %}
              media_player.source_4
            {% endif %}

    media_stop:
      service: media_player.media_stop
      target:
        entity_id: >
            {% if is_state_attr('media_player.office', 'source', 'Source 1') %}
              media_player.source_1
            {% elif is_state_attr('media_player.office', 'source', 'Source 2') %}
              media_player.source_2
            {% elif is_state_attr('media_player.office', 'source', 'Source 3') %}
              media_player.source_3
            {% else %}
              media_player.source_4
            {% endif %}
        
    select_source:
      service: script.amplipi_change_source
      data:
        source: "{{ source }}"
        mediaplayer: "media_player.office"

    volume_up:
      service: media_player.volume_up
      target:
        entity_id: media_player.office

    volume_down:
      service: media_player.volume_down
      target:
        entity_id: media_player.office

    volume_mute:
      service: media_player.volume_mute
      data:
        is_volume_muted: >
            {% if is_state_attr('media_player.office', 'is_volume_muted', true) %}
              false
            {% else %}
              true
            {% endif %}
      target:
        entity_id: media_player.office

    volume_set:
      service: media_player.volume_set
      data:
        volume_level: "{{ volume_level }}"
      target:
        entity_id: media_player.office    

  attributes:
    is_volume_muted: media_player.office|is_volume_muted
    source: input_select.amplipi_sources
    source_list: input_select.amplipi_sources|options
    volume_level: media_player.office|volume_level

Hopefully this helps someone else…

4 Likes

Hi @Juggler, thanks for sharing this work. I’ve been playing with it and it is working well. I took your script and modified it slightly to dynamically map the source media_player entity based on the source attribute.

alias: AmpliPi Change Source
sequence:
  - service: input_select.select_option
    data_template:
      entity_id: input_select.amplipi_sources
      option: "{{ source }}"
  - service: media_player.select_source
    data_template:
      entity_id: "{{ mediaplayer }}"
      source: |
        {% set mapper = {
            state_attr('media_player.source_1','source'): 'Source 1',
            state_attr('media_player.source_2','source'): 'Source 2',
            state_attr('media_player.source_3','source'): 'Source 3',
            state_attr('media_player.source_4','source'): 'Source 4' } %}
        {{ mapper[source] if source in mapper else '' }} 
mode: single

This works well if the stream you select is already active for one of the four source media_player entities. Of course it throws an error if the stream is not found on any media_player which is not the ideal user experience.

I’ve been trying to find a way automatically select the chosen stream on an inactive (i.e. paused) source media_player. My Home Assistant script knowledge is somewhat basic so not sure if this is feasible to perform within a script or whether it is better handled within the AmpliPi integration itself.

Interested to hear if anyone here has any further thoughts on how this could be achieved. It’s one of the last remaining barriers to me sharing HA media control with the rest of the family.

@Juggler in your media_player.yaml you have;

  children:
    - media_player.office
    - media_player.source_1    
    - media_player.source_2
    - media_player.source_3
    - media_player.source_4  

which I found caused the playing media to be incorrect in a secnario where I had for example selected the source as media_player.source_4 whilst media_player.source_1 was also playing. The universal media player appeared to show the state of the media_player.source_1 source instead (first playing source in the list?).

Have you noticed this behaviour? I’m not clear what the purpose of the additional children media players is, perhaps you know more about what this is used for? I’ve taken them out of my configuration so I’m using simply the below which seems to work ok so far.

  children:
    - media_player.office

Since first posting, I’ve updated my yaml to match what you’ve put (single child). Doing this allowed the universal media player to properly display the album artwork if present.

1 Like

This is great information, but I’m confused by something in your media_player.yaml file. What is media_player.office?

I setup my amplipi in HA and I see 10 entities (Input 1 - 4 and Zone 1 - 6). I look at media_player in the states page and I have media_player.source_1 through 4. I renamed them media_player.amplipi_source_1 through 4 to make it easier to organize. I added the media_player.yaml and created a new universal media player entity called Amplipi.

It seems like you have 5 media players (office, source_1 through 4), plus the universal Test one that you’ve made.

Thanks!

EDIT: I think I realized what was confusing me. When I added the Amplipi integration to HA, the Zones were inactive. I’m guessing media_player.office was one of the Zones added. Once I enabled the zone that I was trying to play with, it made more sense. Still working through details, but that makes more sense now!

1 Like

great work on this!

have you found any way to toggle a zone on/off through the integration? I’ve found that the Amplipi will fritz when you hit the power toggle on a speaker zone.

1 Like

That’s the big thing I would like to be able to do - assign or add different zones within HA for the different sources - it would help keep everything in one place instead of having to go to the WebApp. I would also like to be able to tell Siri to play something “in the office” or “kitchen” and such. Any body have that working?

Do any of you guys expose your Amplipi integration to Homekit by chance? I’m trying to figure out how to best use it with Siri. Right now the default switches seem to only power on a zone and then unmute it, but gives no ability to choose source. Ideally you would be able to choose source and then power that on and then control volume even.

@matchmee I’m part way there using what @Juggler provided plus a couple of tweaks. I’ve not used with Homekit though.

Here’s what I have in place now;

A template universal (virtual) media player for each zone that I want to control from within HA that has a dynamic source list based on what sources are active on the Amplipi.

Through this virtual media player (which is named according to the room associated with the zone e.g. Kitchen Media Player) I can control each zone with;

  • Pause/resume
  • Mute/unmute
  • Change volume
  • Power off (mutes the zone)
  • Skip tracks (next/previous)
  • Select from the available sources

The dynamic source list is generated using a script which is called by an automation that runs at HA startup and each time a source changes.

The script;

alias: AmpliPi Change Source
description: "Script to update Amplipi dynamic source list"
sequence:
  - service: input_select.select_option
    data:
      entity_id: input_select.amplipi_sources
      option: "{{ source }}"
  - service: media_player.select_source
    data:
      entity_id: "{{ mediaplayer }}"
      source: |
        {% set sources = {
          'source_1': 'Source 1',
          'source_2': 'Source 2',
          'source_3': 'Source 3',
          'source_4': 'Source 4'
        } %} {{ sources[source] if source in sources else '' }}
mode: queued
max: 10

The automation;

alias: AmpliPi Source Dynamic Update
description: "Automation to call script to update Amplipi dynamic source list"
trigger:
  - platform: state
    entity_id:
      - media_player.source_1
      - media_player.source_2
      - media_player.source_3
      - media_player.source_4
    attribute: source
    id: SOURCE_CHANGE
  - platform: homeassistant
    event: start
    id: HA_STARTUP
condition: []
action:
  - choose:
      - conditions:
          - condition: or
            conditions:
              - condition: trigger
                id: HA_STARTUP
              - condition: trigger
                id: SOURCE_CHANGE
        sequence:
          - service: rest_command.update_amplipi_active_source_list
            data: {}
mode: queued
max: 10

This is an example of my template universal media player configuration;

- platform: universal
  name: AmpliPi Kitchen
  unique_id: "B4A2A735-E519-4250-B905-4A1C329F3638"
  state_template: >
    {% set sources = {
      'Source 1': 'media_player.source_1',
      'Source 2': 'media_player.source_2',
      'Source 3': 'media_player.source_3',
      'Source 4': 'media_player.source_4'
    } %}
    {{ states(sources[state_attr('media_player.amp_z_kitchen', 'source')]) }}
  children:
    - media_player.amp_z_kitchen
  commands:
    media_play:
      service: media_player.media_play
      target:
        entity_id: >
          {% set sources = {
            'Source 1': 'media_player.source_1',
            'Source 2': 'media_player.source_2',
            'Source 3': 'media_player.source_3',
            'Source 4': 'media_player.source_4'
          } %}
          {{ sources[state_attr('media_player.amp_z_kitchen', 'source')] }}
    media_stop:
      service: media_player.media_stop
      target:
        entity_id: >
          {% set sources = {
            'Source 1': 'media_player.source_1',
            'Source 2': 'media_player.source_2',
            'Source 3': 'media_player.source_3',
            'Source 4': 'media_player.source_4'
          } %}
          {{ sources[state_attr('media_player.amp_z_kitchen', 'source')] }}
    media_pause:
      service: media_player.media_pause
      target:
        entity_id: >
          {% set sources = {
            'Source 1': 'media_player.source_1',
            'Source 2': 'media_player.source_2',
            'Source 3': 'media_player.source_3',
            'Source 4': 'media_player.source_4'
          } %}
          {{ sources[state_attr('media_player.amp_z_kitchen', 'source')] }}
    media_play_pause:
      service: media_player.media_play_pause
      target:
        entity_id: >
          {% set sources = {
            'Source 1': 'media_player.source_1',
            'Source 2': 'media_player.source_2',
            'Source 3': 'media_player.source_3',
            'Source 4': 'media_player.source_4'
          } %}
          {{ sources[state_attr('media_player.amp_z_kitchen', 'source')] }}
    turn_off:
      service: media_player.volume_mute
      target:
        entity_id: media_player.amp_z_kitchen
      data:
        is_volume_muted: true
    turn_on:
      service: media_player.volume_mute
      target:
        entity_id: media_player.amp_z_kitchen
      data:
        is_volume_muted: false
    select_source:
      service: script.amplipi_change_source
      data:
        source: "{{ source }}"
        mediaplayer: "media_player.amp_z_kitchen"
    volume_up:
      service: media_player.volume_up
      target:
        entity_id: media_player.amp_z_kitchen
    volume_down:
      service: media_player.volume_down
      target:
        entity_id: media_player.amp_z_kitchen
    volume_mute:
      service: media_player.volume_mute
      data:
        is_volume_muted: >
            {% if is_state_attr('media_player.amp_z_kitchen', 'is_volume_muted', true) %}
              false
            {% else %}
              true
            {% endif %}
      target:
        entity_id: media_player.amp_z_kitchen
    volume_set:
      service: media_player.volume_set
      data:
        volume_level: "{{ volume_level }}"
      target:
        entity_id: media_player.amp_z_kitchen
    media_next_track:
      service: media_player.media_next_track
      target:
        entity_id: >
          {% set sources = {
            'Source 1': 'media_player.source_1',
            'Source 2': 'media_player.source_2',
            'Source 3': 'media_player.source_3',
            'Source 4': 'media_player.source_4'
          } %}
          {{ sources[state_attr('media_player.amp_z_kitchen', 'source')] }}
    media_previous_track:
      service: media_player.media_previous_track
      target:
        entity_id: >
          {% set sources = {
            'Source 1': 'media_player.source_1',
            'Source 2': 'media_player.source_2',
            'Source 3': 'media_player.source_3',
            'Source 4': 'media_player.source_4'
          } %}
          {{ sources[state_attr('media_player.amp_z_kitchen', 'source')] }}
  attributes:
    is_volume_muted: media_player.amp_z_kitchen|is_volume_muted
    source: input_select.amplipi_sources
    source_list: input_select.amplipi_sources|options
    volume_level: media_player.amp_z_kitchen|volume_level
1 Like

Having just seen the fork of Brians HA integration (https://github.com/micro-nova/hacs_amplipi) it looks like it won’t be necessary to manually knife and fork it for much longer.

2 Likes

Sorry for the late response, been on vacation and so have been behind the ball a bit. I will have to play around with this!!!