Populate track metadata from polled GetInfoEx responses - #20
Populate track metadata from polled GetInfoEx responses#20markfrancisonly wants to merge 8 commits into
Conversation
Media metadata was only ever written by the AVTransport eventing path, keyed on AVTransportURIMetaData or CurrentTrackMetaData. Devices that never emit those in a LastChange event therefore report no title, artist, album or album art at all, even though GetInfoEx already returns the same DIDL-Lite payload under TrackMetaData on every poll. Extract the DIDL-Lite/JSON normalisation out of _update_state_from_av_transport_event_data into _parse_track_metadata so both paths produce identically shaped data, and apply it to the GetInfoEx response already fetched by async_get_transport_capabilities. No extra request is made. The polled path only overwrites _current_track_info when the payload yields at least one usable field, so a device returning empty or unparseable TrackMetaData cannot clobber metadata delivered by eventing. Devices whose eventing works are unaffected. Also fix the DIDL item check, which used Element truthiness. That evaluates to len(el) != 0, so an <item> carrying only attributes was silently parsed as an empty dict; ElementTree also deprecates testing an element's truth value. Use `is not None`, matching the check already applied to the same element when resolving its <res> child.
Event payloads arrive XML-escaped inside LastChange, but the polled GetInfoEx TrackMetaData arrives already decoded. Unconditionally unescaping a decoded payload corrupts any literal & in it -- a URL query string or an ampersand in a title -- and the subsequent parse fails with "not well-formed", leaving the metadata empty. Unescape only when the payload does not already start like markup or JSON, and retry a failed XML parse once with unescaping as a fallback. Found by running the polled path against a live WiiM Pro whose current track carried an ampersand in its metadata.
|
Pushed a follow-up commit after running this against a live WiiM Pro: the polled |
The polled path parses TrackMetaData on every refresh, so what was a rare event-path condition became routine and filled the log with warnings for a successful operation. Parse failures still log at error level.
async_get_transport_capabilities awaits the MEDIA_INFO request, and the AVTransport event handler is synchronous, so an event can replace the track info while that request is still outstanding. The reply then describes the track that was playing when it was issued, and it was applied unconditionally, overwriting newer event metadata with older polled metadata. The poll is itself scheduled from the event path, so the window opens exactly when metadata is changing, and the caller's in-flight guard means an event arriving mid-request queues no follow-up poll to correct it. The stale title, artist and album art therefore stand until the next unrelated event. Read the track info before issuing the request and apply the reply only if it is still the same object. Every write to _current_track_info binds a new dict, so identity is a sufficient change token, and it also covers the disconnect reset without special-casing it.
954039b to
a51e9eb
Compare
The mock fires an AVTransport event from inside the request it is standing in for, which is what makes the interleaving deterministic. Without a note the call reads as arbitrary.
|
@Linkplay2020 I wasn't receiving artwork in my ha setup after buying a wiim pro, so that prompted me to troubleshoot and fix these issues. I went ahead and submitted PR home-assistant/core#177667 Ultimately I was able to workaround the upnp issue by disabling all but one home assistant one network interface from the several exposed to the core home assistant container by my docker config. This workaround isn't desirable and I want to bring my secondary network interfaces back online cross vlan usage |
test_parse_track_metadata_reads_item_without_child_elements only checked the key set of the returned dict, which _parse_track_metadata fills in unconditionally, so the `if item_elem:` it was added for passed it too. Parse a present-but-childless item and a genuinely absent one in the same test and compare the results: the first yields empty fields and no warning, the second leaves them unset and reports the missing item. The old truthiness check collapses the two and fails on the first assertion.
test_polled_media_info_applies_when_no_event_intervenes seeded the later track through the event path and then had the poll return the earlier one, so the behaviour it pins read as a stale polled reply replacing newer event metadata — the opposite of what the identity guard is for. Seed the earlier track and poll the later one, standing in for the track advancing with no event to report it. The guard is exercised the same way and no metadata rollback is implied. _DIDL_LITE_NEXT_TRACK is no longer specific to the interleaving test, so its comment no longer says so.
|
Updated PR body text and handing off |
What this enables: track title, artist, album and artwork show up on devices whose UPnP eventing never delivers — the metadata is in every poll the SDK already makes, it just wasn't read. Devices with working eventing behave exactly as before.
Problem —
_current_track_info, the only source for title/artist/album/art, is written solely by the AVTransport event path, so when events don't arrive metadata stays empty forever. EveryGetInfoExpoll already carries the same DIDL-Lite underTrackMetaData; only its capabilities and queue fields are read. Reported in home-assistant/core#168047 and home-assistant/core#177517; #6 is an open PR against the same event path.Fix
_parse_track_metadata, so both paths produce identical shapes. Event path otherwise unchanged.TrackMetaDataalready in theGetInfoExreply — no extra request.&. Found on a live WiiM Pro._current_track_infoidentity compared across the await).Also:
if item_elem:relied onElement.__bool__, so an attribute-only<item>took the "no item" branch. Nowis not None, matching the check above it.Tests — 9 cases: both paths, four empty-payload flavours, the mid-request race, both escaping variants, present-vs-absent
<item>. 60 passed.