How to sync mute status across mutliple media devices (Sonos esp.)

Former SmartThings user, new HA user, first post here. Thanks so much for your help!

I’ve always found it frustrating that when listening to TV with a group of Sonos speakers, and I press the TV mute button, only the Sonos connected to the TV is muted while the rest of the group continues playing. So I’m trying to create an automation that detects when my Sonos Beam speaker (connected to my TV) is muted, and then mute the other Sonos speakers in the group. (This is not surround sound - just basic sonos speaker grouping.) I am immediately thwarted in creating such an automation because there is apparently no such “muted” attribute for the sonos speaker to detect a status change. I can see that there is an “Action” of “is_volume_muted”, but as far as this newb can tell, actions can be set but cannot be used to trigger an automation (?). So I can set the mute status, but I am struggling to create an automation that detects a change in this status to mute the other speakers. I’ve searched the hell out of these forums and the internets for a solution before posting here, to no avail. If someone could point me in the right direction, I would be very greatful.

I think you might be misunderstanding this. Or HA is not explaining itself very well. Or a little of both.

“is_volume_muted” isn’t an action. It’s an attribute of the media player entity.

the action would be “media_player.volume_mute”.

so you can trigger off of the “is_volume_muted” attribute of the TV Sonos speaker to run the action “media_player.volume_mute” on the other desired Sonos speakers.

That should get you pointed in the right direction but if you need more “pointing” then post back here and I or someone else can give you further help.

1 Like

Hi @finity thanks for the response. Let me state my challenge another way…. If I try to create a new automation from scratch, in the “When” section I select my Sonos speaker as the device, but in the following Trigger section, none of the available triggers are related to the speaker being muted.

I don’t use the UI. And when helping people to write automations it’s recommended to post the properly formatted yaml so we can easily edit the text instead of trying to describe what to do in the UI.

But I’ll try to help…

first I don’t think you can use a device trigger for this. Device stuff seems to be more limited. You will probably need to use an entity state trigger.

then in the entity state trigger there will be an attribute portion to fill in. that’s where you will put the “is_volume_muted” attribute.

then in the actions section is where you will also likely need to use an action call (previously it was called a service call) to tell the other speakers to mute using the above action call.

that’s a lot of words to describe (incompletely) what you need to do in the UI. Which is why it’s not recommended.

in yaml it would like like this:

alias: mute one other speaker
triggers:
  - trigger: state
    entity_id: media_player.your_beam_entity
    attribute: is_volume_muted
    to: 'true'
actions:
  - action: media_player.volume_mute
    entity_id: media_player.your_other_speaker

and if you want to mute more than one speaker:

alias: mute more than one other speakers
triggers:
  - trigger: state
    entity_id: media_player.your_beam_entity
    attribute: is_volume_muted
    to: 'true'
actions:
  - action: media_player.volume_mute
    entity_id: 
      - media_player.your_other_speaker
      - media_player.your_some_other_speaker
      - media_player.and_then_another_speaker

you might be able to copy that code snippet into the UI editor but you’ll need to correct the entity id’s with your own correct entities. Those are found in the developers tools states tab list.

Thanks for your patience and super detailed explanation! I’ll give this a try when I’m back at my desktop and let you know how it goes.

1 Like

We have progress, but not a working automation. I was able to create yaml entries that look identical to yours. It just doesn’t do anything - muting the TV has not impact on the other speakers. I think I saw another thread struggling to overcome similar issues, so I will research there.

alias: Sync Sonos Mute
triggers:
  - trigger: state
    entity_id:
      - media_player.tv_room
    attribute: is_volume_muted
    to: "true"
actions:
  - action: media_player.volume_mute
    entity_id:
      - media_player.family_room
      - media_player.kitchen

Can you trigger the automation manually to see if the action part works? as in do the speakers mute when you manually trigger it?

Is the TV room speaker actually going to true for is_volume_muted?

you should add an id to your automation so you can look at the traces to see what’s happening.

so add this:

alias: Sync Sonos Mute
id: sync_sonos_mute
triggers:
  - trigger: state
    entity_id:
      - media_player.tv_room
    attribute: is_volume_muted
    to: "true"
actions:
  - action: media_player.volume_mute
    entity_id:
      - media_player.family_room
      - media_player.kitchen

also you should reply to my post to get my attention. if not then I will only see your reply if I look into my unread threads.

I got it working. The UI adds quotes to to: “true”, but it should be to: true (without quotes). Removing the quotes fixes it. Here’s the full code from my automations.yaml.

- id: "1735595634188"
  alias: Sonos Mute Syncer
  description: ""
  triggers:
    - trigger: state
      entity_id:
        - media_player.tv_room
      attribute: is_volume_muted
      to: true
  conditions: []
  actions:
    - action: media_player.volume_mute
      metadata: {}
      data:
        is_volume_muted: true
      target:
        entity_id:
          - media_player.kitchen
          - media_player.family_room
  mode: single

My one remaining question is, how to efficiently enhance this to handle both mute & unmute triggers. Rather than creating two separate automations, can I remove the ‘to: true’ entirely (so that either state triggers) and then tell the other two speakers to mirror the is_volume_muted value of the TV Sonos?

Edited to add: ^ I’m not asking for help here, just thinking out loud about next steps. I appreciate your help so far and am happy with the current state.

1 Like

Why are you using grouped speakers for TV audio playback? That typically would introduce enough of a delay for lipsync issues to become visible, which I could never live with.

Anyway, my personal very similar annoyance is that there is no way to force Sonos to always use the exact same volume level for all grouped speakers. I made a pretty spiffy blueprint to deal with this which monitors media players (speakers) for changed to group status, volume level and mute and enforces synchronization. Probably should finalize the last bits of documentation and release it…

Yes, I would be very interested in your automation. Syncing volumes was going to be my next project. Ideally as a % rather than an absolute value or in-kind increments.

I use grouped speakers to share TV sound in other nearby rooms, such as the Kitchen where the TV is line-of-sight, but speakers are far away. I’ve never had any serious delay issues with Sonos speakers in a group being out of sync.

As long as your network can keep up speakers should always be in sync with each other, but as soon as you start using groups (and not just a soundbar + surrounds + subs) there will always be a delay to ensure speakers stay in sync. While this is user configurable you cannot go lower than 75 ms, which would be 4-5 frames at 60 Hz or 2 frames at 24 Hz. For many that would be enough to annoy.

I’ve put the current version of my blueprint below. Suggested usage would be to copy and paste everything into a new file at /config/blueprints/automation/synchronize_group_volume.yaml, then reload automations and create a new automation from blueprint.

Not much in the way of documentation aside from the short explanations of the settings, but should be easy enough to figure out. Note that selected speakers are not forcibly synchronized against each other, rather they are synchronized against other members of their respective groups.

While synchronization with different scaling factors would certainly be possible, I don’t see how one could include it into a streamlined blueprint UI. What can be done in UI is just too limited right now and would either involve lots of duplicate UI elements (one set for each anticipated volume factor group) or require the user to enter values as raw YAML in an object field.

Easiest way to deal with it, assuming you always want the proportionality to be the same, would be to set appropriate volume limits on the speakers in the Sonos app. What this does is re-scale how big each volume step is. At work I lowered the volume level down to like 50% on our Play:5 speakers or else the usable volume range was not more than a handful of steps.

blueprint:
  name: "Synchronize Group Volume"
  author: "Magnus Helin <[email protected]>"
  domain: automation
  homeassistant:
    min_version: 2024.10.0
  input:
    player_entities:
      name: "Media player(s)"
      description: "Media player(s) to sync volume levels for (within respective groups)."
      selector:
        entity:
          filter:
            domain: media_player
            supported_features:
            - media_player.MediaPlayerEntityFeature.GROUPING
            - media_player.MediaPlayerEntityFeature.VOLUME_SET
          multiple: true
    sync_mode:
      name: "Automatic progagation"
      description: "Automatically propagate or prevent volume changes."
      selector:
        select:
          mode: list
          options:
          - label: "Any member's volume change propagates to all in group"
            value: "on"
          - label: "Prevent volume changes not originating from group leader"
            value: "first"
          - label: "Only sync on custom or manual trigger"
            value: "off"
      default: "on"

mode: single
max_exceeded: silent
trigger_variables:
  player_entities: !input player_entities
  sync_mode: !input sync_mode
triggers:
- trigger: state
  entity_id: !input player_entities
  attribute: group_members
  enabled: "{{ bool(sync_mode, true) }}"
- trigger: state
  entity_id: !input player_entities
  attribute: volume_level
  enabled: "{{ bool(sync_mode, true) }}"
- trigger: state
  entity_id: !input player_entities
  attribute: is_volume_muted
  enabled: "{{ bool(sync_mode, true) }}"
- trigger: event
  event_type: automation_reloaded
  enabled: "{{ bool(sync_mode, true) }}"

variables:
  sync_attrs: >-
    {% if trigger.platform == 'state' and trigger.to_state.attributes.group_members | count > 1 %}
      {{ dict(trigger[['from', 'to'][bool(sync_mode, false) or trigger.entity_id == trigger.to_state.attributes.group_members | first]
      ~ '_state'].attributes | items | selectattr(0, 'in', ['group_members', 'volume_level', 'is_volume_muted'])) }}
    {% endif %}
conditions:
- or:
  - and:
    - "{{ trigger.platform == 'state' }}"
    - "{{ trigger.attribute == 'group_members' }}"
    - "{{ trigger.to_state.attributes.group_members | first == trigger.entity_id }}"
    - "{{ trigger.to_state.attributes.group_members | count > trigger.from_state.attributes.group_members | count }}"
  - and:
    - "{{ trigger.platform == 'state' }}"
    - "{{ trigger.attribute in ['volume_level', 'is_volume_muted'] }}"
    - "{{ sync_attrs.group_members | count > 1 }}"
    - "{{ sync_attrs.group_members | reject('is_state_attr', trigger.attribute, sync_attrs[trigger.attribute]) | list | count > 0 }}"
  - and:
    - "{{ trigger.platform == 'event' }}"
    - "{{ trigger.event.event_type == 'automation_reloaded' }}"
  - "{{ trigger.platform is none }}"
actions:
- if: "{{ trigger.platform == 'state' }}"
  then:
  - if: "{{ trigger.attribute in ['volume_level', 'group_members'] }}"
    then:
    - action: media_player.volume_set
      target:
        entity_id: "{{ sync_attrs.group_members | reject('is_state_attr', 'volume_level', sync_attrs.volume_level) | list }}"
      data:
        volume_level: "{{ sync_attrs.volume_level }}"
      continue_on_error: true
  - if: "{{ trigger.attribute in ['is_volume_muted', 'group_members'] }}"
    then:
    - action: media_player.volume_mute
      target:
        entity_id: "{{ sync_attrs.group_members | reject('is_state_attr', 'is_volume_muted', sync_attrs.is_volume_muted) | list }}"
      data:
        is_volume_muted: "{{ sync_attrs.is_volume_muted }}"
      continue_on_error: true
  else:
  - repeat:
      for_each: >-
        {% set repeat = namespace(items=[]) %}
        {% for attrs in expand(player_entities | map('state_attr', 'group_members') | map('first')) | map('attr', 'attributes') %}
          {% set repeat.items = repeat.items + [dict(attrs | items | selectattr(0, 'in', ['group_members', 'volume_level', 'is_volume_muted']))] %}
        {% endfor %}
        {{ repeat.items }}
      sequence:
      - action: media_player.volume_set
        target:
          entity_id: "{{ repeat.item.group_members | reject('is_state_attr', 'volume_level', repeat.item.volume_level) | list }}"
        data:
          volume_level: "{{ repeat.item.volume_level }}"
        continue_on_error: true
      - action: media_player.volume_mute
        target:
          entity_id: "{{ repeat.item.group_members | reject('is_state_attr', 'is_volume_muted', repeat.item.is_volume_muted) | list }}"
        data:
          is_volume_muted: "{{ repeat.item.is_volume_muted }}"
        continue_on_error: true

Well, I’m going to help you anyway and you can’t stop me. :laughing:

- id: "1735595634188"
  alias: Sonos Mute Syncer
  description: ""
  triggers:
    - trigger: state
      entity_id:
        - media_player.tv_room
      attribute: is_volume_muted
      to: 
  conditions: []
  actions:
    - choose:
        - conditions:
            - condition: template
              value_template: "{{ trigger.to_state.state == true }}"
          sequence:
            - action: media_player.volume_mute
              target:
                entity_id:
                  - media_player.kitchen
                  - media_player.family_room
        - conditions:
            - condition: template
              value_template: "{{ trigger.to_state.state == false }}"
          sequence:
            - action: media_player.volume_set
              data:
                volume_level: "{{ state_attr('media_player.tv_room', 'volume_level') }}"
              target:
                entity_id:
                  - media_player.kitchen
                  - media_player.family_room     
  mode: single

it’s again untested but I think it should work.

Thanks, finity! I had to do some troubleshooting, but your template helped get me started in the right direction. The main issue was that the condition template was never triggering. I added a default action to message me the value of the template, and saw that trigger.to_state.state was returning a value of “playing” (the state of the speaker, rather than the mute attribute of interest). I also think your true/false logic was backwards in the first condition, and the second condition was changing the volume level rather than the mute state. All together, I got the following working:

alias: Sonos Mute Syncer
description: ""
triggers:
  - trigger: state
    entity_id:
      - media_player.tv_room
    attribute: is_volume_muted
conditions: []
actions:
  - choose:
      - conditions:
          - condition: template
            value_template: >-
              {{ state_attr('media_player.tv_room', 'is_volume_muted') == true
              }}
        sequence:
          - action: media_player.volume_mute
            target:
              entity_id:
                - media_player.kitchen
                - media_player.family_room
            data:
              is_volume_muted: true
      - conditions:
          - condition: template
            value_template: >-
              {{ state_attr('media_player.tv_room', 'is_volume_muted') == false
              }}
        sequence:
          - action: media_player.volume_mute
            target:
              entity_id:
                - media_player.kitchen
                - media_player.family_room
            data:
              is_volume_muted: false
mode: single

I was hoping to clean this up even further by replacing the multiple conditions with a single template to set the mute state of the other 2 speakers to match the mute state of the master TV speaker, regardless of its state. Something like the following. But so far I can’t quite get it to work - HA complains about my yaml syntax and won’t let me save this. I also tried using the “template” syntax here, but still got syntax errors.

actions:
  - action: media_player.volume_mute
    metadata: {}
    data:
      is_volume_muted: {{ state_attr('media_player.tv_room', 'is_volume_muted') }}
    target:
      entity_id:
        - media_player.kitchen
        - media_player.family_room

Got that last bit working - I was just missing double quotes around the template.

alias: Sonos Mute Syncer
description: ""
triggers:
  - trigger: state
    entity_id:
      - media_player.tv_room
    attribute: is_volume_muted
conditions: []
actions:
  - action: media_player.volume_mute
    metadata: {}
    data:
      is_volume_muted: "{{ state_attr('media_player.tv_room', 'is_volume_muted') }}"
    target:
      entity_id:
        - media_player.kitchen
        - media_player.family_room
mode: single

Thanks Mayhem_SWE! I can’t quite figure out how to implement this either. What I was going for is if the main TV volume changed from (for example) 28 to 35, then the other speaker volumes would need to be changed by *35/28. This would require capturing the volume both before and after the trigger. So far I haven’t seen a good way to capture the “before” volume.

If using a state trigger, the “before” volume should be available as part of the trigger variable.

trigger.from_state.attributes.volume_level

I’m fairly sure I tried that, but I can check again. Everything I tried gave me the new volume. Which kinda makes sense - it can’t check the volume until the trigger is fired, and the trigger is the volume changing.

Edit: trigger.from_state.attributes.volume_level gives me the last volume value in the transition as I was changing the volume. So for example, if I change the volume from 15 to 20 by pressing volume+ on the remote 5 times, the from_state will return 19.

Yeah I didn’t realize that “is_volume_muted” was a valid action call. I only thought it was an attribute of the media player entity.

That choice of action data is kind of confusing.

So that’s why I changed the volume to match the controlling media player.

I forgot to add the attribute of interest to the template.

I think it should have been:

{{ trigger.to_state.state.attributes.is_volume_muted == true }}

But I’m glad you got it all working.