Calculating current media position for use in automation

Dear community,

My use case is as follows: I often listen (and fall asleep) to podcasts at night, and at least in my native Sweden, they have an annoying custom of ending with some sort of loud outro. So, I have added a template sensor to calculate what currently remains of the episode in seconds, to use that in an automation, where if the result is less than 90 seconds after 11PM in the evening, the media player is turned off.

My sensor looks like this (it’s rather clumsy, I imagine):

      bedroom_speaker_remaining:
        value_template: "{{ state_attr('media_player.bedroom_speaker', 'media_duration') - (state_attr('media_player.bedroom_speaker', 'media_position') + as_timestamp(now()) - as_timestamp(state_attr('media_player.bedroom_speaker', 'media_position_updated_at'))) + ((as_timestamp((states.sensor.date_time_iso.state)) - as_timestamp((states.sensor.date_time_iso.state)))) }}"

It takes the current media duration and subtracts the media position, as well as the time elapsed between the last update of the media position and now. Then, at the end of it all, I gathered that I couldn’t get it to update based on sensor.time just by adding the entity_id, so instead I added the sensor and a subtraction that nullifies the result of adding it in the first place.

Partly, I wanted to publish this as a suggestion/example for anyone looking to achieve the same thing, it works quite well now, but I also wanted to ask two things:

  1. Is there any less clumsy way of achieving the same thing?
  2. The sensor currently updates once a minute, with the update of sensor.date_time – what would be the easiest way to achieve a more precise timing, i.e. once every 10 seconds or so?

As of v0.117 you can use now() in templates and they will update every minute. So you no longer need to fall back on sensor.time to update the template.

How about a trigger like this:

trigger:
  platform: template
  value_template: "{{ state_attr('media_player.bedroom_speaker', 'media_duration') - (state_attr('media_player.bedroom_speaker', 'media_position')  <= 90 }}"
condition:
  condition: time
  after: '23:00'
action:
etc...

This will trigger as soon as there is only 90 seconds left (or less if the sensor takes a bit to update) and if it is after 11pm it will pass the condition and you can turn off your media player in the actions.

Hi Tom,

Oh, cool, I hadn’t updated to 0.117 yet, thanks for the heads up!

However, unless I am somehow misinformed, media_position does not update continuously, only with other media_player state changes, so I would still need my code to continuously calculate the actual media position, right?

Ah, ok I did not realise that (I’ve never used the attribute).

The only way around that I can think of is by using another automation to update the media player every 10 seconds, if it is playing:

trigger:
  platform: time_pattern
  seconds: '/10'
condition:
  condition: state
  entity_id: media_player.bedroom_speaker
  state: 'playing'
action:
  service: homeassistant.update_entity
  entity_id: media_player.bedroom_speaker

Beautiful, thank you!

No worries, please let me know if it works. It’s all untested. I’ve never used the update entity service to update a media player. Hopefully it updates the media_position attribute.

homeassistant.update_entity does not update the media_position - just tried it. Will have to use the calculation template.

I have a similar use case: in my case I need a sensor that updates with the time left of the currently active Apple TV. I defined it as follows, works like a charm:

- trigger:
    - platform: time_pattern
      seconds: "/1"
  sensor:
    - name: Time left in current player
      device_class: duration
      state_class: measurement
      unit_of_measurement: s
      state: >
        {% set player = ['media_player.appletv_airplay', 'media_player.appletv_living_room'] | select('is_state', 'playing') | first %}
        {% if player is defined %}
        {{ state_attr(player, 'media_duration') - (state_attr(player, 'media_position') + as_timestamp(now()) - as_timestamp(state_attr(player, 'media_position_updated_at'))) }}
        {% else %}
        {{ 0 }}
        {% endif %}
2 Likes

I’m sorry to bring up this old topic again. Unfortunately, it is the only one that follows a reasonable solution. Unfortunately, the last proposed solution (which works fine in itself, by the way) still has a major flaw. It only works reliably if no one makes any change after position_updatet_at. Sure, on play/pause in the music app (Sonos for me) the position is updated, but when an automation triggers a pause, it doesn’t.

In my situation, I have a button that triggers play/pause, which messes up the timestamp quite a bit. The timestamp keeps running because it was triggered by Homeassistant. If I then later want to query the position, only nonsense comes as an answer, because the whole time the media position has fictitiously continued to run.

Is there another way to update the media_position manually?

Hi @Figuhr, this is how I solved that

        {% if is_state(player, 'playing')  %}
          {{ state_attr(player, 'media_duration') - (state_attr(player, 'media_position') + as_timestamp(now()) - as_timestamp(state_attr(player, 'media_position_updated_at'))) }}
        {% else %}
         {{ state_attr(player, 'media_duration') - state_attr(player, 'media_position') }}
        {% endif %}
1 Like

Can you show your full code?

This is my current automation. As I said, works fine as long as both actions are not used in one song.

When the button is pressed once, the song is either paused or played (with a rewind of 0.5 seconds).

If the button is pressed twice, it first checks if the song has been playing for more than 10 seconds. If this is not the case, a rewind of 10 seconds is not possible. Therefore, the title jumps to position 0. However, if the title has already been running for longer than 10 seconds, it is rewound by 10 seconds.

alias: "Radio drama"
description: ""
trigger:
  - device_id: 0a93e541e82a39a045c80d66eeb8ef60
    domain: zha
    platform: device
    type: remote_button_short_press
    subtype: remote_button_short_press
    id: short
  - device_id: 0a93e541e82a39a045c80d66eeb8ef60
    domain: zha
    platform: device
    type: remote_button_double_press
    subtype: remote_button_double_press
    id: double
condition: []
action:
  - choose:
      - conditions:
          - condition: trigger
            id: short
        sequence:
          - choose:
              - conditions:
                  - condition: state
                    entity_id: media_player.sonos_zucho
                    state: playing
                sequence:
                  - service: media_player.media_pause
                    data: {}
                    target:
                      entity_id: media_player.sonos_zucho
              - conditions:
                  - condition: state
                    entity_id: media_player.sonos_zucho
                    state: paused
                sequence:
                  - service: media_player.media_seek
                    data:
                      seek_position: >-
                        {{ state_attr('media_player.sonos_zucho',
                        'media_position') | float -0.5 }} 
                    target:
                      entity_id: media_player.sonos_zucho
                    alias: Back to 'media_position_updated_at' -0.5 sec
                  - service: media_player.media_play
                    data: {}
                    target:
                      entity_id: media_player.sonos_zucho
            alias: Pause or Play (inkl. back-skip)
      - conditions:
          - condition: trigger
            id: double
        sequence:
          - if:
              - condition: template
                value_template: >-
                  {{ as_timestamp(now()) -
                  as_timestamp(state_attr('media_player.sonos_zucho',
                  'media_position_updated_at')) > 10 }}
                alias: if Position > 10 sec
            then:
              - service: media_player.media_seek
                data:
                  seek_position: >-
                    {{ (as_timestamp(now()) -
                    as_timestamp(state_attr('media_player.sonos_zucho',
                    'media_position_updated_at')) +
                    state_attr('media_player.sonos_zucho', 'media_position') ) |
                    float -10 }} 
                target:
                  entity_id: media_player.sonos_zucho
                alias: Rewind 10 sec
            else:
              - service: media_player.media_seek
                data:
                  seek_position: 0
                target:
                  entity_id: media_player.sonos_zucho
                alias: Play at the Beginning
            alias: if position > 10 sec, then rewind, else back to 0
mode: queued
max: 10

hey there, reviving this as I am trying to calculate the time remaining from a video using the media card attributes that are used above. I’m not interested in any automation, more I would like to display the time remaining on an ink screen. I have tried messing with the various codes above (using my specific entity id’s), but do not seem to be able to get it to work. Would there be a way to adopt this a say a simple sensor that just displays the time remaining?

So long as you’re fine with it updating once a minute, this should do the trick (obviously substituting entity names):

    bedroom_speaker_remaining:
      friendly_name: "Bedroom Speaker Remaining"
      icon_template: mdi:stopwatch
      unit_of_measurement: "s"
      value_template: >
        {% if is_state('media_player.bedroom_speaker', 'playing') %}
          {% set duration = state_attr('media_player.bedroom_speaker', 'media_duration') %}
          {% set position = state_attr('media_player.bedroom_speaker', 'media_position') %}
          {% set last_updated = as_timestamp(state_attr('media_player.bedroom_speaker', 'media_position_updated_at')) %}
          {% set current_time = as_timestamp(now()) %}
          {{ (duration - (position + current_time - last_updated)) | round(0) }}
        {% else %}
          0
        {% endif %}

and then wherever you’re displaying this sensor, you can format the output like this:

      {% set seconds = states('sensor.bedroom_speaker_remaining') | int %}
      {% set hours = (seconds // 3600) %}
      {% set minutes = (seconds % 3600) // 60 %}
      {% set remaining_seconds = (seconds % 60) %}
      {% if hours > 0 %}
        {{ '%d:%02d:%02d' % (hours, minutes, remaining_seconds) }}
      {% elif minutes > 0 %}
        {{ '%d:%02d' % (minutes, remaining_seconds) }}
      {% else %}
        {{ '%d' % remaining_seconds }}
      {% endif %}

progress. Entity has been created but it only shows 0, and never updates.