home-assistant / core

:house_with_garden: Open source home automation that puts local control and privacy first.
https://www.home-assistant.io
Apache License 2.0
73.93k stars 30.99k forks source link

Sonos: media_position and media_position_updated_at not updating on media_seek #128142

Open Mayhem-SWE opened 1 month ago

Mayhem-SWE commented 1 month ago

The problem

Issue that seems to affect the Sonos integration only, not other/all media players:

When executing a media_seek action, regardless of whether through automation/script or manually by clicking timeline on dashboard, the media_position and media_position_updated_at attributes are never updated. These attributes does not update when manually seeking in the Sonos app either, they only seem to update when switching tracks.

Also, if I manually seek to a position, then switch tracks, the media_position attribute is wrong – it is not 0 as should be, but gets set to wherever I currently was on the last track. But this particular issue may be down to Sonos and/or their Spotify Connect implementation, as lets say I've observed similar behaviour outside of Home Assistant…

I have no idea how the Sonos API works so maybe there is no good way for Home Assistant to be notified if seeking happens elsewhere, and I understand if it is considered wasteful to be constantly polling Sonos. But at least when manual seeking happens within Home Assistant it should be possible to keep these attributes accurately updated!

What version of Home Assistant Core has the issue?

core-2024.10.1

What was the last working version of Home Assistant Core?

No response

What type of installation are you running?

Home Assistant OS

Integration causing the issue

Sonos

Link to integration documentation on our website

No response

Diagnostics information

No response

Example YAML snippet

- action: media_player.media_seek
  target:
    entity_id: "media_player.lounge_sonos_arc"
  data:
    seek_position: 0

Anything in the logs that might be useful for us?

No response

Additional information

No response

home-assistant[bot] commented 1 month ago

Hey there @jjlawren, @peterager, mind taking a look at this issue as it has been labeled with an integration (sonos) you are listed as a code owner for? Thanks!

Code owner commands Code owners of `sonos` can trigger bot actions by commenting: - `@home-assistant close` Closes the issue. - `@home-assistant rename Awesome new title` Renames the issue. - `@home-assistant reopen` Reopen the issue. - `@home-assistant unassign sonos` Removes the current integration label and assignees on the issue, add the integration domain after the command. - `@home-assistant add-label needs-more-information` Add a label (needs-more-information, problem in dependency, problem in custom component) to the issue. - `@home-assistant remove-label needs-more-information` Remove a label (needs-more-information, problem in dependency, problem in custom component) on the issue.

(message by CodeOwnersMention)


sonos documentation sonos source (message by IssueLinks)

Mayhem-SWE commented 1 month ago

Playing around with the code, I think I managed to fix this myself!

In sonos/media_player.py at the end of media_seek method, adding a call to self._update() does the trick.

    @soco_error(UPNP_ERRORS_TO_IGNORE)
    def media_seek(self, position: float) -> None:
        """Send seek command."""
        self.coordinator.soco.seek(str(datetime.timedelta(seconds=int(position))))
        self._update()

Most difficult part was figuring out how to override the standard integration with a custom component.

Now gotta learn how to put this into a pull request I guess…

PeteRager commented 1 month ago

Interesting. I'll take a look at this with you. Would be good if we could get the media position updating when changed from Sonos app or from HA.

Mayhem-SWE commented 1 month ago

Interesting. I'll take a look at this with you. Would be good if we could get the media position updating when changed from Sonos app or from HA.

@PeteRager see my post above, I managed to fix it! I am however a complete noob when it comes to contributing to open source so don't (yet) know how to put together a pull request, but will get to it eventually…

I also tried looking into what self._update() actually does and if it might be pared down to only the necessities. I figured maybe self.media.poll_media() or perhaps even self.media.set_basic_track_info(update_position=True) or only self.update_media_position({POSITION_SECONDS: position}, force_update=True) could be enough, but wasn't able to make any sense of it – none of it worked!

The other parts of self._update() alone (self.speaker.update_groups(), self.speaker.update_volume()or the property self.speaker.is_coordinator) didn't make any difference, only with all of them combined do the media position attributes update as they should. Beginning to suspect that maybe it comes down to some sort of timing issue in the dependency library that talks to Sonos, where the media seek hasn't quite gone through before we try to get/set the updated attributes?

Mayhem-SWE commented 1 month ago

Also I only noticed this behaviour at all because I am trying to put together a better blueprint for an IKEA Symfonisk remote. I want the previous track button to work like it does on any other media player, where if current playback is more than 1-2 seconds into a track it should first restart the current track rather than immediately skip to the previous track. This was impossible to get working when the media position attributes were not reliably updated in realtime.

PeteRager commented 1 month ago

The _update function polls the device and hence it updates the pos. But if the position is changed from the Sonos App it'll still be wrong in HA.

The way it should work is this:

a) the integration subscribes to each speaker b) the speaker sends messages to the integration when state changes.

This appears to be working for track title, transport state (playing / paused) but is not working for track position. Hence we want to understand why that isn't working. It could be that the speaker is not sending the update (this is unlikely since the Sonos app works the same way and does know the track pos), or that the event is not being processed within SoCo and/or the integration.

PeteRager commented 1 month ago

So, it look to me like it is deliberately not always updating the track position. At least when running the integration in polling mode. I believe this is to limit the number of updates to HA. In this section of code it runs a calc to determine if the time has jumped by more than 1.5 seconds, if not it doesn't update. I haven't tried the seek use case yet, as that definitely should trigger an update.

https://github.com/home-assistant/core/blob/6650d32055b915023abc0756d00b2905f80e4aad/homeassistant/components/sonos/media.py#L220-L232

The way to calculate current media position is by using these two piece of information along with current time. So

current_position = media_position + ( now() - media_position_updated_at)

media_position: 389
media_position_updated_at: 2024-10-11T19:32:34.199629+00:00
Mayhem-SWE commented 1 month ago

It is looking more and more like this is down to weird/buggy behaviour on Sonos's side. The reason I am suspecting this is because even though my streaming source is always Spotify, it seems to behave differently depending on whether Spotify Connect is in use or only the Sonos app.

Compare these three cases, assuming you also have Spotify, and see if it is the same on your end:

  1. Start playback by navigating to a track in the Sonos app. Even though my source is still Spotify, this does not trigger Spotify Connect – if I look in the Spotify app it cannot tell that anything is playing, nor act as a remote control. In this case, seeking propagates correctly to/from HASS. Seek in the Sonos app and the position is updated in the HASS entity and dashboard. Seek from the HASS dashboard and the position indicator moves to the updated position.

  2. Start playback from the Spotify application, by way of Spotify Connect to a Sonos speaker. Seeking in either Spotify or Sonos apps will still propagate between the two, but will not propagate to the HASS entity or dashboard. And while seeking from the HASS dashboard or action does change which part of the track that is played, it does not update the position indicator or the values on the entity.

  3. Add my tiny mod of the Sonos integration above; insert a self._update() at the end of media_player.media_seek method. Now seeking in HASS, by either action or dashboard, immediately propagates throughout HASS entity and dashboard, even when using Spotify Connect! (Seeking in Sonos or Spotify apps still does not propagate over to HASS, but I obviously did not expect that to change.)

Now for my own very narrow use case, a remote blueprint capable of deciding whether to seek to the beginning of the current track or skip to the previous track, this fix is sort of sufficient. But in the grand scheme of things it would of course be better if it always behaved correctly regardless of whether Spotify Connect is involved or not…

I have experienced similar issues before with Sonos and Spotify Connect, unrelated to HASS, where seeking and/or track switching doesn't quite propagate /update/reset correctly. If I would seek to an earlier position in a track, the next track may play prematurely (when the original track would've ended without being interrupted by seeking). If I seek to a later position on the track, there may instead be a period of silence (new position minus old position) before the next track played. Also sometimes happened when switching tracks, where the audio would start playing from the beginning, but Spotify/Sonos still kept the position and/or duration metadata of the previous track with similar results as already described. This has gotten better the last couple of months though.

PeteRager commented 1 month ago

Thanks for the update. When playing from Spotify connect Does seeking from HA with your change to call _update causes the track position to be updated correctly?