How to loop play sound files [Solved]

HI,
used to my iPhone loop-playing my ringtones, I tried to achieve the same via media_play, without succes just yet…
I’ve dl all my ringtones, and they play just fine over HA and the appointed googlehome device. I now like to repeat loop that, so my alarm takes longer than 40 seconds the max ringtone is…

please have a look with me?

this is that I have for playing the files in the fist place:

  play_sound_bite:
    alias: Play sound bite
    sequence:
      - service: media_player.volume_set
        data_template:
          entity_id: >
            {{states('sensor.media_player')}}
          volume_level: >
            {{ states('input_number.radio_volume')|float }}
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.media_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'audio/mp4'

I’ve never tried it but I would think you could re-trigger the Automation from within itself, creating a loop.
You’d probably want to add a Condition as well to break the loop.

Maybe something like this…

  set_volume:
    alias: Set volume
    sequence:
      - service: media_player.volume_set
        data_template:
          entity_id: >
            {{states('sensor.media_player')}}
          volume_level: >
            {{ states('input_number.radio_volume')|float }}
      - service: script.play_sound_bite

  play_sound_bite:
    sequence:
    alias: Play sound bite
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.media_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'audio/mp4'
      - condition: state
        entity_id: <input_boolean>, <light>
        state: 'on'
      - service: script.play_sound_bite

hi, thanks!
yes I’ve thought about that, but didn’t yet test it, because suspect some HA lag between stopping and restarting the script (this isn’t an automation btw)

that’s why I didn’t yet try a wait_template for the media_player to not have state ‘playing’ anymore, it never is immediate, so wouldn’t sound as if the file was looped.

also, this script would execute all steps immediately, so when the first play_media might still be on its way, it would already call the next instance, and scripts can’t be called during their own execution. So, I fear this is not what we should try, still one never knows, and Ill try your suggestion anyway and see what gives.

1 Like

I’ve never tried it either (with an Automation or Script). I just thought I’d throw it out there as a possibility.

I had to do something similar for my alarm clock. Basically I ensure each alarm sound MP3 I created is just over 5 minutes. Then I set a 5 minute timer when the alarm goes off. When the timer is finished, I simply replay the MP3 and restart the timer. Sometimes there is a couple second gap when the sound play is restarting, but that doesn’t matter to me for this purpose.

That’s a good point, I didn’t think of that.

Maybe you could re-trigger using the media_title state attribute from your media player…?

yes, hard coding can be done, but is of no value in my use case. I have soundbite (!) of several seconds and some are the max ringtone length. And some are in between…So, no fixed length to take in to account af any trigger or timer.

I could lateron select based on that length maybe, but for now am looking for a way to simply (…) loop the media_player automatically.

Maybe @pnbruckner can think of a way? Please check this thread if you can spare a moment?

wait_template: >
   {{states(states('sensor.sound_bite_player')) != 'playing'}}

would be the template to wait and re-act the script, but as said, I fear lag.

anyway, testing:

  play_sound_bite:
    alias: Play sound bite
    sequence:
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.sound_bite_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'audio/mp4'
      - condition: template
        value_template: >
          {{is_state('input_boolean.loop_sound_bite','on')}}
      - wait_template: >
          {{states(states('sensor.sound_bite_player')) != 'playing'}}
      - service: script.play_sound_bite

update-

I can see the wait_template change from false to true, but the following script isn’t executed again. Which is kind of strange, since it doesn’t require anything extra t be set, can simply be run. Unless it would be a script isn’t finished and can call a second instance of itself.

in which case this could be a hack:

  play_sound_bite:
    alias: Play sound bite
    sequence:
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.sound_bite_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'audio/mp4'
      - condition: template
        value_template: >
          {{is_state('input_boolean.loop_sound_bite','on')}}
      - wait_template: >
          {{states(states('sensor.sound_bite_player')) != 'playing'}}
      - service: script.play_sound_bite_loop

  play_sound_bite_loop:
    alias: Play sound bite
    sequence:
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.sound_bite_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'audio/mp4'
      - condition: template
        value_template: >
          {{is_state('input_boolean.loop_sound_bite','on')}}
      - wait_template: >
          {{states(states('sensor.sound_bite_player')) != 'playing'}}
      - service: script.play_sound_bite

Do you know python? This might help you out.

thanks, but as said, none of my files has the same length so I wouldn’t have much use extracting that for this use case would I?

If it were me, I’d try using some sort of audio editor to turn the short MP3’s into longer versions where the original “sound bite” is replicated over and over until all the MP3’s are long enough to suit your worst case time requirement. Then no overly complicated looping in HA, and no temporary sound gap between loops.

Ha, thanks for chiming in! that would be another and creative approach altogether :wink: Tink I can manage that…

still, form an educative perspective: how do we loop in HA. Trying my above setup seemed to be correct conceptually, but I got several script already running errors. Added the service script.turn_off as an extra safeguard for that, and will see what the gives.

I didn’t look super close at that code I was just googling around to help you out but I kind of assumed that when you wanted to play a file you would be able to use that script each time you played a file to determine the length of the audio file then set your loop timer/ automation whatever to that value.

Maybe I am just misunderstanding what you are trying to do.

No, you’re completely right, that would be a way to do it indeed.

@Mariusthvdb

This is exactly what I suggested to you in my first reply.

For my alarm clock I took the 2-3 second samples I had and used Audacity to create a 5 minute loop of the audio. Now I know how long the files are so I can then set a 5 minute timer just in case I don’t wake up in the first 5 minutes and have to restart the play to keep the alarm sounding.

Here is my entire alarm clock package. Maybe there is something in there you can pick out and use.

First time I’ve done one of these gists, hope it works! (Not ready to make my entire config public yet)

https://gist.github.com/jazzyisj/ff5fda62ac857d93ecb66fef9149defb

1 Like

partial succes!
changed setup to:

  play_sound_bite:
    alias: Play sound bite
    sequence:
      - service: script.turn_off
        entity_id: script.sound_bite_loop
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.sound_bite_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'audio/mp4'
#      - condition: template
#        value_template: >
#          {{is_state('input_boolean.loop_sound_bite','on')}}
#      - wait_template: >
#          {{states(states('sensor.sound_bite_player')) != 'playing'}}
#      - delay:
#          seconds: 5
#      - service: script.play_sound_bite
      - service: script.turn_on
        entity_id: script.sound_bite_loop

  sound_bite_loop:
    alias: Sound bite loop
    sequence:
      - condition: template
        value_template: >
          {{is_state('input_boolean.loop_sound_bite','on')}}
      - delay:
          seconds: 5
      - wait_template: >
         {{states(states('sensor.sound_bite_player')) != 'playing'}}
      - service: script.turn_off
        entity_id: script.play_sound_bite
      - service: script.turn_on
        entity_id: script.play_sound_bite

and all my shorter files are looping nicely. At least the architecture should be fine now, and finetuning for the templates needs to be done.

1 Like

@Mariusthvdb
Side question…

What is the difference (if any) between using

      - service: script.turn_on
        entity_id: script.play_sound_bite

and

      - service: script.play_sound_bite

I have always used the second method but am now wondering if there is something I don’t know about.

(Obviously there is lots I don’t know about but you know what I mean :wink: )

there is none, other than when using variables. see https://www.home-assistant.io/components/script/#passing-variables-to-scripts

I’ve only changed it here, because of the fact I need to turn_off the script, which can only be done using script.turn_off, and I like the scripts being symmetrical.

1 Like

testing:

      - delay:
          seconds: >
           {{1+state_attr(states('sensor.sound_bite_player'),'media_duration')|int}}

which should use the file length in seconds, and add 1 just to be sure.

still hoping a media_player.loop_media service could be made, and filed a feature request for that…

bingo!
all files loop. there’s still some lag, the file won’t append directly, but for an alarm this wil do.

  play_sound_bite:
    alias: Play sound bite
    sequence:
      - service: script.turn_off
        entity_id: script.play_sound_bite_loop
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.sound_bite_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'music'
      - delay:
          seconds: 1
      - delay:
          seconds: >
           {{state_attr(states('sensor.sound_bite_player'),'media_duration')|int}}
      - condition: template
        value_template: >
          {{is_state('input_boolean.loop_sound_bite','on')}}
#      - delay:
#          seconds: 2
#      - wait_template: >
#          {{states(states('sensor.sound_bite_player')) != 'playing'}}
#      - delay:
#          seconds: 1
#      - service: script.play_sound_bite
      - service: script.play_sound_bite_loop

  play_sound_bite_loop:
    alias: Play sound bite loop
    sequence:
      - service: script.turn_off
        entity_id: script.play_sound_bite
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.sound_bite_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'music'
      - delay:
          seconds: 1
      - delay:
          seconds: >
           {{state_attr(states('sensor.sound_bite_player'),'media_duration')|int}}
      - condition: template
        value_template: >
          {{is_state('input_boolean.loop_sound_bite','on')}}
#      - wait_template: >
#          {{states(states('sensor.sound_bite_player')) != 'playing'}}
      - service: script.play_sound_bite

still checking out wether with this new setup, the initial service turn_off is necessary, as this timing ought to be precise and immediate.
the delay of 1 second might also be superfluous, but Ive set it for now to give the media_duration sensor time to check…

edit
delay: 1 second: it is superfluous…
service: turn_off: not necessary

which is kind of wonderful. leading to my final setup for now:

  play_sound_bite:
    alias: Play sound bite
    sequence:
      - service: script.sound_bite
      - delay:
          seconds: >
           {{state_attr(states('sensor.sound_bite_player'),'media_duration')|int}}
      - condition: template
        value_template: >
          {{is_state('input_boolean.loop_sound_bite','on')}}
      - service: script.play_sound_bite_loop

  play_sound_bite_loop:
    alias: Play sound bite loop
    sequence:
      - service: script.sound_bite
      - delay:
          seconds: >
           {{state_attr(states('sensor.sound_bite_player'),'media_duration')|int}}
      - condition: template
        value_template: >
          {{is_state('input_boolean.loop_sound_bite','on')}}
      - service: script.play_sound_bite

  sound_bite:
    alias: Sound bite
    sequence:
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.sound_bite_player')}}
          media_content_id: >
            {{states('sensor.sound_bite')}}
          media_content_type: 'music'