Skip to content

feat: expose r:resMD as Track.ResMD when parsing favorites - #226

Merged
svrooij merged 1 commit into
svrooij:mainfrom
rocklobster42195:feat/favorite-resmd
Jul 8, 2026
Merged

feat: expose r:resMD as Track.ResMD when parsing favorites#226
svrooij merged 1 commit into
svrooij:mainfrom
rocklobster42195:feat/favorite-resmd

Conversation

@rocklobster42195

Copy link
Copy Markdown
Contributor

Problem

Sonos favorites report their own container UpnpClass (often the well-known malformed value object.itemobject.item.sonos-favorite), which is not a valid CurrentURIMetaData class for playback - passing a favorite Track straight into SetAVTransportURI gets rejected by the speaker with UPnP error 402.

The actual playable resource's metadata (with the correct upnp:class for the underlying track/stream) is carried separately in the r:resMD attribute of the favorite's DIDL-Lite item, but MetadataHelper.ParseDIDLTrack silently discarded it, forcing consumers to re-parse the raw ContentDirectoryService.Browse response themselves to get at it.

Fix

ParseDIDLTrack now decodes r:resMD (when present) into Track.ResMD - a ready-to-use, already-decoded DIDL-Lite XML string. Consumers can pass it directly as a raw CurrentURIMetaData string to AVTransportService.SetAVTransportURI to play a favorite without hitting the 402.

Testing

  • Unit tests on MetadataHelper.ParseDIDLTrack covering decoding of r:resMD and the absent case.
  • Extended the existing GetFavorites() end-to-end test with an assertion on the decoded ResMD value using a real captured Sonos favorites response.

Purely additive (new optional field) - not a breaking change.

Sonos favorites report their own container UpnpClass (often the
malformed 'object.itemobject.item.sonos-favorite'), which is not a
valid CurrentURIMetaData class for playback and gets rejected by the
speaker with UPnP error 402. The actual playable resource's metadata
(with the correct upnp:class) is carried separately in the r:resMD
attribute, which ParseDIDLTrack silently discarded.

ResMD now surfaces that pre-encoded DIDL-Lite metadata (decoded, ready
to pass as a raw CurrentURIMetaData string) so favorites can be played
back directly via AVTransportService.SetAVTransportURI without callers
having to re-parse the raw Browse response themselves.
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 28782200864

Coverage remained the same at 73.387%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 2171
Covered Lines: 1615
Line Coverage: 74.39%
Relevant Branches: 1207
Covered Branches: 864
Branch Coverage: 71.58%
Branches in Coverage %: Yes
Coverage Strength: 67.99 hits per line

💛 - Coveralls

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves favorites playback interoperability by preserving the playable resource’s DIDL-Lite metadata carried in favorites’ r:resMD, exposing it on parsed Track objects so consumers can use it as CurrentURIMetaData instead of the favorite entry’s often-invalid upnp:class.

Changes:

  • Parse and decode r:resMD in MetadataHelper.ParseDIDLTrack and expose it as Track.ResMD.
  • Add unit tests covering presence/absence of r:resMD.
  • Extend the GetFavorites() end-to-end test to assert the decoded ResMD is surfaced.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/helpers/metadata-helper.ts Decodes r:resMD during DIDL parsing and attaches it to the returned Track.
src/models/track.ts Adds optional ResMD field to the Track model with JSDoc explaining intended usage.
src/tests/helpers/metadata-helper.test.ts Adds unit tests verifying r:resMD decoding and undefined behavior when absent.
src/tests/sonos-device.test.ts Extends GetFavorites() test to assert decoded ResMD is present in parsed results.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/models/track.ts
Comment on lines +13 to +17
/**
* Pre-encoded DIDL-Lite metadata (`r:resMD`) for the underlying playable resource, only
* present on some favorites. Use this as CurrentURIMetaData instead of the favorite's own
* UpnpClass, which describes the favorite entry itself and isn't valid for playback.
*/
expect(result).toHaveProperty('NumberReturned', 6);
expect(result).toHaveProperty('TotalMatches', 6);
expect(result).toHaveProperty('UpdateID', 3);
expect(result.Result[0]).toHaveProperty('ResMD', '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="10032020spotify%3atrack%3a7nDoBWDvf02SyD8kEQuuPO" parentID="100e206cspotify%3aartistTopTracks%3a5q8HGNo0BjLWaTAhRtbwxa" restricted="true"><dc:title>Bottoms Up</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON2311_X_#Svc2311-0-Token</desc></item></DIDL-Lite>');
@svrooij
svrooij merged commit 8548ab5 into svrooij:main Jul 8, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants