2024.10.x and media_player state detection

Since upgrading to 2024.10, a script I have for doing TTS announcements on my media_player devices has been having issues. Intially it was the TTS problems themselves, but that appears to have been resolved (TTS itself seems to work).

The problem I am currently facing is with detecting the state of my media_player so that I can restore a saved scene after playing TTS. The script used to work just fine; it would save a scene for the media_player, set the required volume, play the TTS, wait for the TTS to finish by detecting the state (no longer “playing”), and then restore the saved scene and delete it.

Now, it seems everything works fine but the scene is restored before TTS is finished playing, causing the TTS to be cut off. When I try to watch the status, it seems to be ok (going from idle, a brief blip of buffering I think, to playing, then to idle), so I don’t know why things have stopped working.

Has anyone else noticed similar issues? I haven’t seen any reports from people complaining, so I don’t know if perhaps it’s something in how my script is written. If I disable the scene restoring step in my script (below), it runs fine, so I know it has to do with the timing of restoring the scene.

(test with a fairly long TTS sentence like “i’m a little tea pot short and stout, here is my handle, here is my spout”; shorter sentences may appear to play ok)

alias: Broadcast TTS notification
icon: mdi:bullhorn
description: ""
fields:
  speaker:
    name: Speaker
    required: true
    selector:
      entity:
        filter:
          - domain: media_player
        multiple: true
  volume:
    name: volume
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
    default: 0.5
    required: false
  message:
    name: Message
    required: true
    selector:
      text: {}
sequence:
  - if:
      - condition: state
        entity_id: input_boolean.broadcasts
        state: "on"
    then:
      - alias: Generate unique ID
        variables:
          scene_id: bn_{{ slugify(this.context.id) }}
      - alias: Ensure media player is ready
        if:
          - alias: If media player is not on
            condition: template
            value_template: >-
              {{ speaker | select('is_state', ['off', 'unavailable']) | list |
              length > 0 }}
        then:
          - alias: Turn on media player
            action: media_player.turn_on
            target:
              entity_id: >-
                {{ speaker | select('is_state', ['off', 'unavailable']) | list
                }}
          - alias: Wait for media player to be ready
            wait_template: >-
              {{ speaker | select('is_state', ['on', 'idle']) | list | length ==
              speaker | length }}
            continue_on_timeout: true
            timeout: "00:00:05"
      - alias: Save media player state
        action: scene.create
        data:
          scene_id: "{{ scene_id }}"
          snapshot_entities: "{{ speaker }}"
      - alias: Set volume
        action: media_player.volume_set
        data:
          volume_level: "{{ volume | default(0.5) }}"
        target:
          entity_id: "{{ speaker }}"
        continue_on_error: true
      - alias: Make announcement
        action: tts.speak
        target:
          entity_id: tts.google_en_ca
        data:
          media_player_entity_id: "{{ speaker }}"
          message: "{{ message }}"
        continue_on_error: true
      - alias: Wait until announcement is finished playing
        wait_template: >-
          {{ speaker | select('is_state', ['playing', 'buffering']) | list |
          length == 0 }}
        continue_on_timeout: true
        timeout: "00:00:30"
      - alias: Restore media player state
        action: scene.turn_on
        target:
          entity_id: scene.{{ scene_id }}
      - alias: Garbage collection
        action: scene.delete
        target:
          entity_id: scene.{{ scene_id }}
mode: parallel

Turns out there is no problem - the TTS method seems to return much quicker now, before the media_player has switched from idle to a buffering or playing state. This caused my condition to eval true right away, completing the script before the TTS had a chance to finish playing. The fix is to wait for a playing state, then wait for an idle state. Below is the fixed script for anyone who cares:

alias: Broadcast TTS notification
icon: mdi:bullhorn
description: ""
fields:
  speaker:
    name: Speaker
    required: true
    selector:
      entity:
        filter:
          - domain: media_player
        multiple: true
  volume:
    name: volume
    selector:
      number:
        min: 0
        max: 1
        step: 0.1
    default: 0.5
    required: false
  message:
    name: Message
    required: true
    selector:
      text: {}
sequence:
  - if:
      - condition: state
        entity_id: input_boolean.broadcasts
        state: "on"
    then:
      - alias: Generate unique ID
        variables:
          scene_id: bn_{{ slugify(this.context.id) }}
      - alias: Ensure media player is ready
        if:
          - alias: If media player is not on
            condition: template
            value_template: >-
              {{ speaker | select('is_state', ['off', 'unavailable']) | list |
              length > 0 }}
        then:
          - alias: Turn on media player
            action: media_player.turn_on
            target:
              entity_id: >-
                {{ speaker | select('is_state', ['off', 'unavailable']) | list
                }}
          - alias: Wait for media player to be ready
            wait_template: >-
              {{ speaker | select('is_state', ['on', 'idle']) | list | length ==
              speaker | length }}
            continue_on_timeout: true
            timeout: "00:00:05"
      - alias: Save media player state
        action: scene.create
        data:
          scene_id: "{{ scene_id }}"
          snapshot_entities: "{{ speaker }}"
      - alias: Set volume
        action: media_player.volume_set
        data:
          volume_level: "{{ volume | default(0.5) }}"
        target:
          entity_id: "{{ speaker }}"
        continue_on_error: true
      - alias: Make announcement
        action: tts.speak
        target:
          entity_id: tts.google_en_ca
        data:
          media_player_entity_id: "{{ speaker }}"
          message: "{{ message }}"
        continue_on_error: true
      - alias: Wait until announcement is finished playing
        sequence:
          - alias: Wait for announcement to start playing
            wait_template: >-
              {{ speaker | select('is_state', 'playing') | list | length ==
              speaker | length }}
            continue_on_timeout: true
            timeout: "00:00:15"
          - alias: Wait for announcement to finish playing
            wait_template: >-
              {{ speaker | select('is_state', 'idle') | list | length == speaker
              | length }}
            continue_on_timeout: true
            timeout: "00:00:30"
      - alias: Restore media player state
        action: scene.turn_on
        target:
          entity_id: scene.{{ scene_id }}
      - alias: Garbage collection
        action: scene.delete
        target:
          entity_id: scene.{{ scene_id }}
mode: parallel