Problem with media player timing in a script between idle and playing

I have a script I’m using to play media through media_players, however I have a timing issue and I can’t seem to figure out how to fix it so that it works correctly.

The issue comes from setting the volume, then playing the media, waiting for the player to finish playing, then set the volume back.

It appears that when the call to play the media goes out, it’s a fire and forget type request and the state doesn’t immediately go to “playing”. To compound issues, the wait_for_trigger using the state platform doesn’t let you template the entity_id (but it doesn’t seem to work for me anyways).

This is the current script:

alias: Play media
icon: mdi:speaker-wireless
fields:
  speaker:
    name: speaker
    selector:
      media: {}
    required: true
  volume:
    name: volume
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
    default: 0.5
    required: false
sequence:
  - alias: Set volume
    service: media_player.volume_set
    data:
      volume_level: "{{ volume | default(0.5) }}"
    target:
      entity_id: "{{ speaker.entity_id }}"
  - alias: Play media
    service: media_player.play_media
    target:
      entity_id: "{{ speaker.entity_id }}"
    data:
      media_content_id: "{{ speaker.media_content_id }}"
      media_content_type: "{{ speaker.media_content_type }}"
  - alias: Wait until media is finished playing
    wait_template: "{{ not is_state(speaker.entity_id, 'playing') }}"
    continue_on_timeout: true
    timeout: "00:00:30"
  - alias: Reset volume
    service: media_player.volume_set
    data:
      volume_level: 0.5
    target:
      entity_id: "{{ speaker.entity_id }}"
mode: parallel

What ends up happening is there’s a delay between sending the play_media command, and when the media player actually enters a ‘playing’ state. Due to that, the wait_template resolves true pretty much instantly and the volume gets reset before the media has a chance to finish playing.

What I’d like to happen is have the play_media command get sent and then wait for the player to go in to ‘playing’, then go to ‘idle’ before issuing the final reset volume command.

Instead of the wait template, have you tried wait for trigger?

I have, in a few different positions within the script, with both from/to, and with just to and have had no luck. The crazy part is that even if I set it to be just waiting for a trigger to ‘idle’, even with a timeout, it still doesn’t get triggered (last action never runs).

Also, even if it did work, you can’t template the entity_id from that one which really sucks.

I changed the script to use 2 wait templates:

  - wait_template: "{{ is_state(speaker.entity_id, 'playing') }}"
    continue_on_timeout: true
    timeout: "00:00:05"
  - wait_template: "{{ is_state(speaker.entity_id, 'idle') }}"
    continue_on_timeout: true
    timeout: "00:00:05"

Just to see if it would work, and in the end, the volume doesn’t reset back to 0.5, but the trace shows that each of those waits did wait about a second or two and then passed… which means it was able to catch it going in to playing, then to idle, and then supposedly ran the volume reset action - but the entity’s volume isn’t showing the right value afterwards (I don’t see it being changed a 2nd time). Something is really strange…

How long is the media you are playing? If it’s longer than 5 seconds, your timeout will kick in before it finished playing.

Yes, aware of that.

Further testing seems to show that the final volume_set is in fact being called, but the volume_level attribute of the device is not changing. It seems that the volume setting seems to queue and doesn’t take effect until the device goes to play media, and then the volume changes at the same time. It seems it may be an issue with ESPHome - When using arduino framework, the volume of a media_player can only be changed while playing · Issue #5877 · esphome/issues (github.com)

To close out the reported issue though, I’ll post the script I have that appears (according to traces) to be working correctly.

Here’s the complete script, working and also includes using scenes to save/restore state (the reset of the volume explicitly is because I have a Chromecast device that doesn’t expose its volume level, so scenes don’t restore it)

alias: Play media
icon: mdi:speaker-wireless
fields:
  speaker:
    name: speaker
    selector:
      media: {}
    required: true
  volume:
    name: volume
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
    default: 0.5
    required: false
sequence:
  - alias: Generate unique ID
    variables:
      scene_id: "{{ slugify(this.context.id) }}"
  - alias: Save media player state
    service: scene.create
    data:
      scene_id: "{{ scene_id }}"
      snapshot_entities: "{{ speaker.entity_id }}"
  - alias: Set volume
    service: media_player.volume_set
    target:
      entity_id: "{{ speaker.entity_id }}"
    data:
      volume_level: "{{ volume | default(0.5) }}"
  - parallel:
      - alias: Play media
        service: media_player.play_media
        target:
          entity_id: "{{ speaker.entity_id }}"
        data:
          announce: true
          media_content_id: "{{ speaker.media_content_id }}"
          media_content_type: "{{ speaker.media_content_type }}"
      - alias: Wait for media to start playing
        wait_template: "{{ is_state(speaker.entity_id, 'playing') }}"
        continue_on_timeout: true
        timeout: "00:00:30"
  - alias: Wait until media is finished playing
    wait_template: "{{ not is_state(speaker.entity_id, 'playing') }}"
    continue_on_timeout: true
    timeout: "00:00:30"
  - alias: Reset volume
    service: media_player.volume_set
    target:
      entity_id: "{{ speaker.entity_id }}"
    data:
      volume_level: 0.5
  - alias: Restore media player state
    service: scene.turn_on
    target:
      entity_id: scene.{{ scene_id }}
  - alias: Garbage collection
    service: scene.delete
    target:
      entity_id: scene.{{ scene_id }}
mode: parallel