Skip to content

Commit

Permalink
Merge pull request #53 from svrooij/development
Browse files Browse the repository at this point in the history
Small tweaks
  • Loading branch information
svrooij committed Apr 29, 2020
2 parents a9c01cb + 78e1afd commit 9612539
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 29 deletions.
10 changes: 5 additions & 5 deletions .eslintrc.js
Expand Up @@ -13,14 +13,14 @@ module.exports = {
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"no-return-await": "off",
"import/no-cycle": "off",
"class-methods-use-this":"off",
"max-len":"off",
"no-console": "off",
"max-classes-per-file":"off",
"import/first": "warn",
"import/no-cycle": "off",
"import/no-duplicates": "warn",
"max-classes-per-file":"off",
"max-len":"off",
"no-console": "off",
"no-return-await": "off",
},
overrides: [
{
Expand Down
14 changes: 14 additions & 0 deletions .gitattributes
@@ -0,0 +1,14 @@
text=auto
text eol=lf

# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.c text
*.h text

# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf

# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
@@ -1,15 +1,14 @@
name: Deploy to github packages
name: Build and test

on:
push:
branches:
- master
- development
tags:
- '*'
pull_request:

jobs:
build-and-deploy:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
@@ -1,3 +1,4 @@
{
"editor.tabSize": 2
"editor.tabSize": 2,
"files.eol": "\n"
}
23 changes: 21 additions & 2 deletions docs/sonos-device/methods.md
Expand Up @@ -9,13 +9,13 @@ nav_order: 1

These methods aren't possible with the generated services.

- **.AddUriToQueue('spotify:track:0GiWi4EkPduFWHQyhiKpRB')** - Add a track to be next track in the queue, metadata is guessed.
- **.AddUriToQueue('spotify:track:0GiWi4EkPduFWHQyhiKpRB')** - Add a track to be next track in the queue, [metadata](#metadata) is guessed.
- **.AlarmClockService.ListAndParseAlarms()** - List all your alarms
- **.AlarmClockService.PatchAlarm({ ID: 1, ... })** - Update some properties of one of your alarms.
- **.JoinGroup('Office')** - Join an other device by it's name. Will lookup the correct coordinator.
- **.PlayNotification({})** - Play a single url and revert back to previous music source (playlist/radiostream). See [notifications](https://svrooij.github.io/node-sonos-ts/sonos-device/notifications-and-tts.html#notifications)
- **.PlayTTS({})** - Generate mp3 based on text, play and revert back to previous music source. See [Text-to-Speech](https://svrooij.github.io/node-sonos-ts/sonos-device/notifications-and-tts.html#text-to-speech)
- **.SetAVTransportURI('spotify:track:0GiWi4EkPduFWHQyhiKpRB')** - Set playback to this url, metadata is guessed. This doens't start playback all the time!
- **.SetAVTransportURI('spotify:track:0GiWi4EkPduFWHQyhiKpRB')** - Set playback to this url, [metadata](#metadata) is guessed. This doens't start playback all the time!
- **.SwitchToLineIn()** - Some devices have a line-in. Use this command to switch to it.
- **.SwitchToQueue()** - Switch to queue (after power-on or when playing a radiostream).
- **.SwitchToTV()** - On your playbar you can use this to switch to TV input.
Expand Down Expand Up @@ -55,3 +55,22 @@ You can also browse content, see [content.js](https://github.com/svrooij/node-so
- **.GetFavoriteRadioStations()** - Get your favorite radio stations
- **.GetFavorites()** - Get your favorite songs
- **.GetQueue()** - Get the current queue.

## Metadata

This library can guess the required metadata for certain track uri's. This is done by the [MetadataHelper](https://github.com/svrooij/node-sonos-ts/blob/master/src/helpers/metadata-helper.ts). The "guessed" metadata is tested [here](https://github.com/svrooij/node-sonos-ts/blob/master/tests/helpers/metadata-helper.test.ts). If you want to extend this you'll need Wireshark. Set it up to monitor `port 1400`, execute the required action in the sonos app (on the same pc as wireshark) and look for `POST /MediaRenderer/AVTransport/Control`.

Currently supported url's for metadata guessing:

- `spotify:track:0GiWi4EkPduFWHQyhiKpRB` - Regular spotify track.
- `spotify:artistRadio:72qVrKXRp9GeFQOesj0Pmv` - Spotify artist radio (has to be added to queue).
- `spotify:artistTopTracks:72qVrKXRp9GeFQOesj0Pmv` - Spotify artist top tracks (has to be added to queue).
- `spotify:album:5c4y5oD0jCAVHJNusKpy4d` - Spotify album (has to be added to queue).
- `spotify:playlist:37i9dQZF1DXcx1szy2g67M` - Spotify playlist (has to be added to queue).
- `radio:s113577` - Tunein radio station.

This library will guess the metadata automatically for the following methods, but you can also use the **MetadataHelper**, yourself.

- `sonosDevice.AddUriToQueue(...)` - Adding track to queue.
- `sonosDevice.SetAVTransportURI(...)` - Switch transport (cannot be used to play 'containers' like playlists, albums, ...)
- `sonosDevice.PlayNotification({...})` - Play a songs as notification (no containers).
36 changes: 24 additions & 12 deletions src/helpers/metadata-helper.ts
Expand Up @@ -185,32 +185,44 @@ export default class MetadataHelper {
return track;
}
if (parts[1] === 'album') {
track.TrackUri = `x-rincon-cpcontainer:0004206c${spotifyUri}`;
track.TrackUri = `x-rincon-cpcontainer:1004206c${spotifyUri}?sid=9&flags=8300&sn=7`;
track.ItemId = `0004206c${spotifyUri}`;
track.UpnpClass = 'object.container.album.musicAlbum';
return track;
}

if (parts[1] === 'artistRadio') {
track.TrackUri = `x-sonosapi-radio:${spotifyUri}?sid=9&flags=8300&sn=7`;
track.ItemId = `100c206c${spotifyUri}`;
track.Title = 'Artist radio';
track.UpnpClass = 'object.item.audioItem.audioBroadcast.#artistRadio';
track.ParentId = `10052064${spotifyUri.replace('artistRadio', 'artist')}`;
return track;
}

if (parts[1] === 'artistTopTracks') {
track.TrackUri = `x-rincon-cpcontainer:000e206c${spotifyUri}`;
track.ItemId = `000e206c${spotifyUri}`;
track.TrackUri = `x-rincon-cpcontainer:100e206c${spotifyUri}?sid=9&flags=8300&sn=7`;
track.ItemId = `100e206c${spotifyUri}`;
track.ParentId = `10052064${spotifyUri.replace('artistTopTracks', 'artist')}`;
track.UpnpClass = 'object.container.playlistContainer';
return track;
}
if (parts[1] === 'user') {
track.TrackUri = `x-rincon-cpcontainer:10062a6c${spotifyUri}?sid=9&flags=10860&sn=7`;

if (parts[1] === 'playlist') {
track.TrackUri = `x-rincon-cpcontainer:1006206${spotifyUri}?sid=9&flags=8300&sn=7`;
track.ItemId = `10062a6c${spotifyUri}`;
track.Title = 'User\'s playlist';
track.Title = 'Spotify playlist';
track.UpnpClass = 'object.container.playlistContainer';
track.ParentId = '10082664playlists';
track.ParentId = '10fe2664playlists';
return track;
}

if (parts[1] === 'artistRadio') {
track.TrackUri = `x-sonosapi-radio:${spotifyUri}?sid=12&flags=8300&sn=5`;
if (parts[1] === 'user') {
track.TrackUri = `x-rincon-cpcontainer:10062a6c${spotifyUri}?sid=9&flags=10860&sn=7`;
track.ItemId = `10062a6c${spotifyUri}`;
track.Title = 'Artist radio';
track.UpnpClass = 'object.item.audioItem.audioBroadcast.#artistRadio';
track.ParentId = `10052064${spotifyUri.replace('artistRadio', 'artist')}`;
track.Title = 'User\'s playlist';
track.UpnpClass = 'object.container.playlistContainer';
track.ParentId = '10082664playlists';
return track;
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/index.ts
@@ -1,14 +1,17 @@
import MetaDataHelper from './helpers/metadata-helper';
import SonosDevice from './sonos-device';
import SonosDeviceDiscovery from './sonos-device-discovery';
import { SonosEvents, ServiceEvents } from './models';
import SonosManager from './sonos-manager';
import SonosDeviceDiscovery from './sonos-device-discovery';

import { SmapiClient } from './musicservices/smapi-client';

export {
MetaDataHelper,
ServiceEvents,
SmapiClient,
SonosDevice,
SonosDeviceDiscovery,
SonosEvents,
ServiceEvents,
SonosManager,
SonosDeviceDiscovery,
SmapiClient,
};
31 changes: 31 additions & 0 deletions tests/helpers/metadata-helper.test.ts
Expand Up @@ -37,6 +37,37 @@ describe('MetadataHelper', () => {
});
});

describe('GuessTrackAndMetadata', () => {
it('Works for Spotify album', () => {
const data = MetadataHelper.GuessMetaDataAndTrackUri('spotify:album:5nD7RkUvn3TRlDcQSABOjo');
expect(data).to.be.an('object');
expect(data).to.have.property('trackUri', 'x-rincon-cpcontainer:1004206cspotify:album:5nD7RkUvn3TRlDcQSABOjo?sid=9&flags=8300&sn=7');
expect(data).to.have.nested.property('metadata.ItemId', '0004206cspotify%3aalbum%3a5nD7RkUvn3TRlDcQSABOjo');
});

it('Works for Spotify artist radio', () => {
const data = MetadataHelper.GuessMetaDataAndTrackUri('spotify:artistRadio:72qVrKXRp9GeFQOesj0Pmv');
expect(data).to.be.an('object');
expect(data).to.have.property('trackUri', 'x-sonosapi-radio:spotify:artistRadio:72qVrKXRp9GeFQOesj0Pmv?sid=9&flags=8300&sn=7');
expect(data).to.have.nested.property('metadata.ItemId', '100c206cspotify%3aartistRadio%3a72qVrKXRp9GeFQOesj0Pmv');
});

it('Works for Spotify artist top tracks', () => {
const data = MetadataHelper.GuessMetaDataAndTrackUri('spotify:artistTopTracks:72qVrKXRp9GeFQOesj0Pmv');
expect(data).to.be.an('object');
expect(data).to.have.property('trackUri', 'x-rincon-cpcontainer:100e206cspotify:artistTopTracks:72qVrKXRp9GeFQOesj0Pmv?sid=9&flags=8300&sn=7');
expect(data).to.have.nested.property('metadata.ItemId', '100e206cspotify%3aartistTopTracks%3a72qVrKXRp9GeFQOesj0Pmv');
expect(data).to.have.nested.property('metadata.ParentId', '10052064spotify%3aartist%3a72qVrKXRp9GeFQOesj0Pmv');
});

it('Works for Spotify playlist', () => {
const data = MetadataHelper.GuessMetaDataAndTrackUri('spotify:playlist:37i9dQZEVXbLoATJ81JYX');
expect(data).to.be.an('object');
expect(data).to.have.property('trackUri', 'x-rincon-cpcontainer:1006206spotify:playlist:37i9dQZEVXbLoATJ81JYX?sid=9&flags=8300&sn=7');
expect(data).to.have.nested.property('metadata.ItemId', '10062a6cspotify%3aplaylist%3a37i9dQZEVXbLoATJ81JYX');
});
})

describe('TrackToMetaData', () => {
it('returns emtpy string when track undefined', () => {
const result = MetadataHelper.TrackToMetaData(undefined);
Expand Down
2 changes: 1 addition & 1 deletion tests/sonos-device.test.ts
Expand Up @@ -186,7 +186,7 @@ describe('SonosDevice', () => {
// SetAVTransportURI Play Artist radio
TestHelpers.mockRequest('/MediaRenderer/AVTransport/Control',
'"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"',
'<u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><CurrentURI>x-sonosapi-radio:spotify%3aartistRadio%3a37i9dQZF1E4lKH7XBCfvaH?sid=12&amp;flags=8300&amp;sn=5</CurrentURI><CurrentURIMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;&lt;item id=&quot;10062a6cspotify%3aartistRadio%3a37i9dQZF1E4lKH7XBCfvaH&quot; restricted=&quot;true&quot; parentID=&quot;10052064spotify%3aartist%3a37i9dQZF1E4lKH7XBCfvaH&quot;&gt;&lt;dc:title&gt;Artist radio&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.audioBroadcast.#artistRadio&lt;/upnp:class&gt;&lt;desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;&gt;SA_RINCON2311_X_#Svc2311-0-Token&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</CurrentURIMetaData></u:SetAVTransportURI>',
'<u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><CurrentURI>x-sonosapi-radio:spotify%3aartistRadio%3a37i9dQZF1E4lKH7XBCfvaH?sid=9&amp;flags=8300&amp;sn=7</CurrentURI><CurrentURIMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;&lt;item id=&quot;100c206cspotify%3aartistRadio%3a37i9dQZF1E4lKH7XBCfvaH&quot; restricted=&quot;true&quot; parentID=&quot;10052064spotify%3aartist%3a37i9dQZF1E4lKH7XBCfvaH&quot;&gt;&lt;dc:title&gt;Artist radio&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.audioBroadcast.#artistRadio&lt;/upnp:class&gt;&lt;desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;&gt;SA_RINCON2311_X_#Svc2311-0-Token&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</CurrentURIMetaData></u:SetAVTransportURI>',
'SetAVTransportURIResponse',
'AVTransport'
);
Expand Down

0 comments on commit 9612539

Please sign in to comment.