bencevans / node-sonos

🔈 Sonos Media Player Interface/Client
https://www.npmjs.com/package/sonos
MIT License
704 stars 147 forks source link

Question: Send custom metadata #330

Closed Coriou closed 5 years ago

Coriou commented 6 years ago

Loving this library and using it I was able to achieve what I was trying to do. There's however something I can't figure out is how to send custom metadata (title, artist, cover, ...) ?

I understand the play() methods accepts and object with a meta key however I can't figure out the format I need to send this information for Sonos to parse it.

Is it even possible without using an actual "service" ?

Thanks !

PS : I know it could be done using ID3 tags but I can't edit them in my case

svrooij commented 6 years ago

The play command accepts both an string or an object. If it is a string is should be an url that is playable. If it is an object it should have at least an uri property, but you can also specify a metadata property.

So you could try something like:

let playObject = { uri: 'http://.....', metdata: 'YOUR_GENERATED_META'}
sonos.play(playObject).then(....).error(...);

And have a look at GenerateMetadata in helpers to check out how the library generates metadata.

We are very curious to your findings, and would gladly incorperate this into the library (if you got a good sample).

Coriou commented 6 years ago

Yeah well, as often, after spending a couple hours searching/testing how to do that, I finally decided to open this issue and, within the next 15 minutes, I came across the solution ...

Here's what helped me : https://github.com/denysvitali/sonos-web/blob/master/test/didl.xml

Essentially, Sonos will parse correctly the XML if you send metadata as follows :

<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="[SOME-UNIQUE-ID]" parentID="[SOME-UNIQUE-ID]">
    <upnp:class>object.item.audioItem.musicTrack</upnp:class>
    <res protocolInfo="http-get:*:audio/mpeg:*" duration="[DURATION-IN-HUMAN-FORMAT]">[MEDIA-URI]</res>
    <upnp:albumArtURI>[COVER-URI]</upnp:albumArtURI>
    <dc:title>[TRACK-TITLE]</dc:title>
    <dc:creator>[TRACK-ARTIST]</dc:creator>
    <upnp:album>[TRACK-ALBUM]</upnp:album>
  </item>
</DIDL-Lite>

So, you could something like :

Sonos.play( {uri: 'URI', metadata: 'METASTRINGABOVE'} )

The only thing I'm not too happy about, is that it seems that if you omit the duration value in the meta, Sonos will not fallback on reading that info from the media itself. I'm still poking around with this, maybe I'm missing something, but right now if I don't set the duration Sonos will not be able to display the length of the track nor seek through it.

I'll update with more info if I find anything else.

It would be awesome to have this built-in the library indeed, so users could just pass an object with simple keys (artist, title, album_cover, ...) and the library could take care of formatting it.

wwwizzarrdry commented 6 years ago

ReadMe shows a sonos.parseDIDL() method but it's undefined when called.

Anyway, thanks for sharing @Coriou - this helped me figure out how to send metadata.

bencevans commented 6 years ago

@wwwizzarrdry looks like it's moved to sonos.Helpers.parseDIDL() ... https://github.com/bencevans/node-sonos/search?utf8=%E2%9C%93&q=parseDIDL&type=

svrooij commented 5 years ago

Would it help if we would add a helper class like:

Helpers.GenerateCustomMetadata = function (streamUrl, itemId, duration = '00:00:00', title, artist, album, coverUrl, parentId) {
  let metadata = '<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/">'
  metadata += `<item id="${itemId}"` + (parentId) ? ` parentID="${parentId}">` : '>'
  metadata += `<res protocolInfo="http-get:*:audio/mpeg:*" duration="${duration}">${streamUrl}</res>`
  if (coverUrl) metadata += `<upnp:albumArtURI>${coverUrl}</upnp:albumArtURI>`
  if (title) metadata += `<dc:title>${title}</dc:title>`
  if (artist) metadata += `<dc:creator>${artist}</dc:creator>`
  if (album) metadata += `<upnp:album>${album}</upnp:album>`
  metadata += '</item></DIDL-Lite>'
  return metadata
}

Then you could do

sonos.play({uri:'blabla', metadata: sonos.Helpers.GenerateCustomMetadata('url', 'itemId', '00:03:20', 'Track Title',....)})
Coriou commented 5 years ago

I think that would be fantastic yes

bencevans commented 5 years ago

:tada: This issue has been resolved in version 1.11.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket: