Skip to content

Commit

Permalink
Merge branch 'alpha' into feature/soundcloud
Browse files Browse the repository at this point in the history
  • Loading branch information
svrooij authored Jun 19, 2021
2 parents d761eea + 725d663 commit d983762
Show file tree
Hide file tree
Showing 16 changed files with 2,446 additions and 8,141 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ node_modules
typings

# VS
.vscode
.vscode/*.json
!.vscode/launch.json
config/*local.json

# Ignore Mac DS_Store files
Expand Down
21 changes: 21 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch current example",
"program": "${file}",
"skipFiles": [
"<node_internals>/**"
],
"env": {
"DEBUG":"sonos:*",
"SONOS_HOST":"192.168.96.56"
},
}
]
}
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ DeviceDiscovery().once('DeviceAvailable', (device) => {
console.log('found device at ' + device.host)

// get all groups
sonos = new Sonos(device.host)
sonos.getAllGroups().then(groups => {
device.getAllGroups().then(groups => {
groups.forEach(group => {
console.log(group.Name);
})
Expand Down
14 changes: 7 additions & 7 deletions examples/nowplayingwebsite.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
var http = require('http')
var Sonos = require('../').Sonos
const http = require('http')
const Sonos = require('../').Sonos

var sonos = new Sonos(process.env.SONOS_HOST || '192.168.2.11')
const sonos = new Sonos(process.env.SONOS_HOST || '192.168.2.11')

var server = http.createServer(async function (req, res) {
var track = await sonos.currentTrack()
const server = http.createServer(async function (req, res) {
const track = await sonos.currentTrack()
res.writeHead(200, {
'Content-Type': 'text/html'
})

var rows = []
const rows = []

for (var key in track) {
for (const key in track) {
if (key === 'albumArtURL') {
rows.push('<tr><th>' + key + '</th><td><img src="' + track[key] + '"/></td></tr>')
} else {
Expand Down
2 changes: 1 addition & 1 deletion examples/playSpotifyMusic.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sonos.setSpotifyRegion(Regions.EU)
// your Sonos system.

// var spotifyUri = 'spotify:artistTopTracks:72qVrKXRp9GeFQOesj0Pmv'
var spotifyUri = 'spotify:track:6sYJuVcEu4gFHmeTLdHzRz'
const spotifyUri = 'spotify:track:6sYJuVcEu4gFHmeTLdHzRz'

sonos.play(spotifyUri)
.then(success => {
Expand Down
4 changes: 2 additions & 2 deletions examples/searchinfo.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
var sonos = require('../index')
const sonos = require('../index')

console.log('\nSearching for Sonos devices on network...')

sonos.DeviceDiscovery(function (device, model) {
var devInfo = '\n'
let devInfo = '\n'
devInfo += 'Device \t' + JSON.stringify(device) + ' (' + model + ')\n'
device.getZoneAttrs(function (err, attrs) {
if (err) devInfo += '`- failed to retrieve zone attributes\n'
Expand Down
4 changes: 2 additions & 2 deletions examples/switchToLineIn.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ const sonos = new Sonos(process.env.SONOS_HOST || '192.168.96.223')
sonos.getZoneInfo().then(data => {
// console.log('Got zone info %j', data)
// The mac is needed for switch to a different channel
var macCleaned = data.MACAddress.replace(/:/g, '')
const macCleaned = data.MACAddress.replace(/:/g, '')
console.log('Cleaned mac %j', macCleaned)

// To swtich on a playbar do the following
var uri = 'x-sonos-htastream:RINCON_' + macCleaned + '01400:spdif'
const uri = 'x-sonos-htastream:RINCON_' + macCleaned + '01400:spdif'

// To switch on a Play:5 do the following
// var uri = 'x-ricon-stream:RINCON_' + macCleaned + '01400'
Expand Down
22 changes: 11 additions & 11 deletions lib/deviceDiscovery.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ const Sonos = require('./sonos').Sonos
* @class DeviceDiscovery
* @emits 'DeviceAvailable' on a Sonos Component Discovery
*/
var DeviceDiscovery = function DeviceDiscovery (options) {
var self = this
const DeviceDiscovery = function DeviceDiscovery (options) {
const self = this
self.foundSonosDevices = {}
self.onTimeout = function () {
clearTimeout(self.pollTimer)
}
var PLAYER_SEARCH = Buffer.from(['M-SEARCH * HTTP/1.1',
const PLAYER_SEARCH = Buffer.from(['M-SEARCH * HTTP/1.1',
'HOST: 239.255.255.250:1900',
'MAN: ssdp:discover',
'MX: 1',
'ST: urn:schemas-upnp-org:device:ZonePlayer:1'].join('\r\n'))
var sendDiscover = function () {
['239.255.255.250', '255.255.255.255'].map(function (addr) {
const sendDiscover = function () {
['239.255.255.250', '255.255.255.255'].forEach(function (addr) {
self.socket.send(PLAYER_SEARCH, 0, PLAYER_SEARCH.length, 1900, addr)
})
// Periodically send discover packet to find newly added devices
Expand All @@ -40,11 +40,11 @@ var DeviceDiscovery = function DeviceDiscovery (options) {
this.socket = dgram.createSocket('udp4', function (buffer, rinfo) {
buffer = buffer.toString()
if (buffer.match(/.+Sonos.+/)) {
var modelCheck = buffer.match(/SERVER.*\((.*)\)/)
var model = (modelCheck.length > 1 ? modelCheck[1] : null)
var addr = rinfo.address
const modelCheck = buffer.match(/SERVER.*\((.*)\)/)
const model = (modelCheck.length > 1 ? modelCheck[1] : null)
const addr = rinfo.address
if (!(addr in self.foundSonosDevices)) {
var sonos = self.foundSonosDevices[addr] = new Sonos(addr)
const sonos = self.foundSonosDevices[addr] = new Sonos(addr)
self.emit('DeviceAvailable', sonos, model)
}
}
Expand Down Expand Up @@ -92,14 +92,14 @@ DeviceDiscovery.prototype.destroy = function (callback) {
* @param {Function} listener Optional 'DeviceAvailable' listener (sonos)
* @return {DeviceDiscovery}
*/
var deviceDiscovery = function (options, listener) {
const deviceDiscovery = function (options, listener) {
if (typeof options === 'function') {
listener = options
options = null
}
options = options || {}
listener = listener || null
var search = new DeviceDiscovery(options)
const search = new DeviceDiscovery(options)
if (listener !== null) {
search.on('DeviceAvailable', listener)
}
Expand Down
4 changes: 2 additions & 2 deletions lib/events/adv-listener.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class SonosListener extends EventEmitter {
const cancel = function (s) {
return s.cancelAllSubscriptions()
}
var cancelAll = this._deviceSubscriptions.map(cancel)
const cancelAll = this._deviceSubscriptions.map(cancel)
return Promise.all(cancelAll)
} else {
return new Promise((resolve, reject) => { reject(new Error('Not listening')) })
Expand Down Expand Up @@ -342,7 +342,7 @@ class DeviceSubscription {
* @param {String} timeout TimeOut header
*/
headerToDateTime (timeout) {
var seconds
let seconds

if ((!!timeout) && (timeout.indexOf('Second-') === 0)) timeout = timeout.substr(7)
seconds = (((!!timeout) && (!isNaN(timeout))) ? parseInt(timeout, 10) : 3600) - 15
Expand Down
33 changes: 21 additions & 12 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const parseString = require('xml2js').parseString
* Helper class
* @class Helpers
*/
var Helpers = {}
const Helpers = {}

/**
* Wrap in UPnP Envelope
Expand Down Expand Up @@ -87,12 +87,12 @@ Helpers.GenerateCustomMetadata = function (streamUrl, itemId, duration = '00:00:
* @return {Object} { uri: uri, metadata: metadata }
*/
Helpers.GenerateLocalMetadata = function (uri, artUri = '') {
var title = ''
var match = /.*\/(.*)$/g.exec(uri.replace(/\.[a-zA-Z0-9]{3}$/, ''))
let title = ''
const match = /.*\/(.*)$/g.exec(uri.replace(/\.[a-zA-Z0-9]{3}$/, ''))
if (match) {
title = match[1]
}
var meta = '<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="##ITEMID##" parentID="##PARENTID##" restricted="true"><dc:title>##RESOURCETITLE##</dc:title><upnp:class>##UPNPCLASS##</upnp:class><upnp:albumArtURI>##ARTURI##</upnp:albumArtURI><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">##REGION##</desc></item></DIDL-Lite>'
const meta = '<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="##ITEMID##" parentID="##PARENTID##" restricted="true"><dc:title>##RESOURCETITLE##</dc:title><upnp:class>##UPNPCLASS##</upnp:class><upnp:albumArtURI>##ARTURI##</upnp:albumArtURI><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">##REGION##</desc></item></DIDL-Lite>'
if (uri.startsWith('x-file-cifs')) {
return {
uri: uri,
Expand All @@ -105,8 +105,8 @@ Helpers.GenerateLocalMetadata = function (uri, artUri = '') {
}
}
if (uri.startsWith('x-rincon-playlist')) {
var parentMatch = /.*#(.*)\/.*/g.exec(uri)
var parentID = parentMatch[1]
const parentMatch = /.*#(.*)\/.*/g.exec(uri)
const parentID = parentMatch[1]

return {
uri: uri,
Expand All @@ -129,8 +129,7 @@ Helpers.GenerateLocalMetadata = function (uri, artUri = '') {
* @return {Object} options {uri: Spotify uri, metadata: metadata}
*/
Helpers.GenerateMetadata = function (uri, title = '', region = '3079') {
var parts = uri.split(':')
var meta = '<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="##SPOTIFYURI##" ##PARENTID##restricted="true"><dc:title>##RESOURCETITLE##</dc:title><upnp:class>##SPOTIFYTYPE##</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">##REGION##</desc></item></DIDL-Lite>'
let meta = '<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="##SPOTIFYURI##" ##PARENTID##restricted="true"><dc:title>##RESOURCETITLE##</dc:title><upnp:class>##SPOTIFYTYPE##</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">##REGION##</desc></item></DIDL-Lite>'

// Soundcloud track
const match = uri.match(/x-sonos-http:track:(\d+).mp3\?sid=160&flags=8224&sn=4/)
Expand All @@ -145,13 +144,14 @@ Helpers.GenerateMetadata = function (uri, title = '', region = '3079') {
}
}

const parts = uri.split(':')
if (!((parts.length === 2 && (parts[0] === 'radio' || parts[0] === 'x-sonosapi-stream' || parts[0] === 'x-rincon-cpcontainer')) || (parts.length >= 3 && parts[0] === 'spotify'))) {
debug('Returning string because it isn\'t recognized')
return Helpers.GenerateLocalMetadata(uri)
}

if (parts[0] === 'radio' || parts[0] === 'x-sonosapi-stream') {
var radioTitle = title || 'TuneIn Radio'
const radioTitle = title || 'TuneIn Radio'
if (parts[0] === 'radio') {
return {
uri: 'x-sonosapi-stream:' + parts[1] + '?sid=254&flags=8224&sn=0',
Expand All @@ -175,7 +175,7 @@ Helpers.GenerateMetadata = function (uri, title = '', region = '3079') {
} else {
meta = meta.replace('##REGION##', 'SA_RINCON' + region + '_X_#Svc' + region + '-0-Token')
}
var spotifyUri = uri.replace(/:/g, '%3a')
const spotifyUri = uri.replace(/:/g, '%3a')

if (uri.startsWith('spotify:track:')) { // Just one track
return {
Expand All @@ -201,6 +201,14 @@ Helpers.GenerateMetadata = function (uri, title = '', region = '3079') {
.replace('##SPOTIFYTYPE##', 'object.container.playlistContainer')
.replace('##PARENTID##', '')
}
} else if (uri.startsWith('spotify:playlist:')) { // Playlist
return {
uri: 'x-rincon-cpcontainer:1006206c' + spotifyUri,
metadata: meta.replace('##SPOTIFYURI##', '1006206c' + spotifyUri)
.replace('##RESOURCETITLE##', '')
.replace('##SPOTIFYTYPE##', 'object.container.album.playlistContainer')
.replace('##PARENTID##', '')
}
} else if (uri.startsWith('spotify:user:')) {
return {
uri: 'x-rincon-cpcontainer:10062a6c' + spotifyUri + '?sid=9&flags=10860&sn=7',
Expand All @@ -210,8 +218,8 @@ Helpers.GenerateMetadata = function (uri, title = '', region = '3079') {
.replace('##PARENTID##', 'parentID="10082664playlists" ')
}
} else if (uri.startsWith('spotify:artistRadio:')) { // Artist radio
var spotifyTitle = title || 'Artist Radio'
var parentId = spotifyUri.replace('artistRadio', 'artist')
const spotifyTitle = title || 'Artist Radio'
const parentId = spotifyUri.replace('artistRadio', 'artist')
return {
uri: 'x-sonosapi-radio:' + spotifyUri + '?sid=12&flags=8300&sn=5',
metadata: meta.replace('##SPOTIFYURI##', '100c206c' + spotifyUri)
Expand Down Expand Up @@ -300,6 +308,7 @@ Helpers.ParseDIDLItem = function (item, host, port, trackUri) {
title: item['r:streamContent'] || item['dc:title'] || null,
artist: item['dc:creator'] || null,
album: item['upnp:album'] || null,
albumArtist: item['r:albumArtist'] || null,
albumArtURI
}
if (trackUri) track.uri = trackUri
Expand Down
8 changes: 5 additions & 3 deletions lib/services/AVTransport.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,14 @@ class AVTransport extends Service {
const trackUri = result.TrackURI || null
const queuePosition = parseInt(result.Track)
if (result.TrackMetaData && result.TrackMetaData !== 'NOT_IMPLEMENTED') { // There is some metadata, lets parse it.
var metadata = await Helpers.ParseXml(result.TrackMetaData)
const metadata = await Helpers.ParseXml(result.TrackMetaData)
const track = Helpers.ParseDIDL(metadata)
track.position = position
track.duration = duration
track.albumArtURL = !track.albumArtURI ? null
: track.albumArtURI.startsWith('http') ? track.albumArtURI
track.albumArtURL = !track.albumArtURI
? null
: track.albumArtURI.startsWith('http')
? track.albumArtURI
: 'http://' + this.host + ':' + this.port + track.albumArtURI
if (trackUri) track.uri = trackUri
track.queuePosition = queuePosition
Expand Down
2 changes: 1 addition & 1 deletion lib/services/Service.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class Service {
})
}
messageBody += `</u:${action}>`
var responseTag = `u:${action}Response`
const responseTag = `u:${action}Response`
request({
url: 'http://' + this.host + ':' + this.port + this.controlURL,
method: 'POST',
Expand Down
Loading

0 comments on commit d983762

Please sign in to comment.