diff --git a/lib/SonosSystem.js b/lib/SonosSystem.js index 4f38311a..2a5f90b5 100644 --- a/lib/SonosSystem.js +++ b/lib/SonosSystem.js @@ -48,13 +48,18 @@ function SonosSystem(settings) { } cache[member.uuid] = new Type(member, listener, _this); + + const channelMapSet = member.channelmapset || member.htsatchanmapset; + cache[member.uuid].hasSub = /:SW/.test(channelMapSet); + return cache[member.uuid]; } function getPlayers(members) { if (members instanceof Array === false) { // single item - return [tryGet(playerCache, Player, members)]; + const player = tryGet(playerCache, Player, members); + return [player]; } // Find normal players @@ -66,21 +71,6 @@ function SonosSystem(settings) { return tryGet(playerCache, Player, member); }); - // fix sub and pairs - const invisible = members.filter((member) => { - return !isVisible(member); - }) - .forEach((member) => { - if (!member.channelmapset) { - return; - } - - const hasSub = /SW,SW/.test(member.channelmapset); - const primaryMatch = /(RINCON_[0-9ABCDEF]+):LF/.exec(member.channelmapset); - let primaryUuid = primaryMatch[1]; - playerCache[primaryUuid].hasSub = hasSub; - }); - return players; } diff --git a/lib/prototypes/Player/nightMode.js b/lib/prototypes/Player/nightMode.js new file mode 100644 index 00000000..70bd5175 --- /dev/null +++ b/lib/prototypes/Player/nightMode.js @@ -0,0 +1,13 @@ +'use strict'; +const soap = require('../../helpers/soap'); +const TYPE = soap.TYPE; + +function nightMode(enable) { + const value = enable ? '1' : '0'; + return soap.invoke( + `${this.baseUrl}/MediaRenderer/RenderingControl/Control`, + TYPE.SetEQ, + { eqType: 'NightMode', value }); +}; + +module.exports = nightMode; diff --git a/lib/prototypes/Player/speechEnhancement.js b/lib/prototypes/Player/speechEnhancement.js new file mode 100644 index 00000000..835a16a9 --- /dev/null +++ b/lib/prototypes/Player/speechEnhancement.js @@ -0,0 +1,13 @@ +'use strict'; +const soap = require('../../helpers/soap'); +const TYPE = soap.TYPE; + +function speechEnhancement(enable) { + const value = enable ? '1' : '0'; + return soap.invoke( + `${this.baseUrl}/MediaRenderer/RenderingControl/Control`, + TYPE.SetEQ, + { eqType: 'DialogLevel', value }); +}; + +module.exports = speechEnhancement; diff --git a/package.json b/package.json index 42ac64d5..55031d9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sonos-discovery", - "version": "1.3.2", + "version": "1.4.0", "description": "A simple node library for managing your Sonos system", "author": "Jimmy Shimizu ", "repository": { diff --git a/test/data/topology.json b/test/data/topology.json index 738df80e..bc7fdb4f 100644 --- a/test/data/topology.json +++ b/test/data/topology.json @@ -204,5 +204,23 @@ "channelmapset": "RINCON_30000000000101400:LF,LF;RINCON_30000000000201400:RF,RF;RINCON_30000000000301400:SW,SW" } ] + }, + { + "$name": "zonegroup", + "$attrs": { + "coordinator": "RINCON_40000000000101400", + "id": "RINCON_40000000000101400:49" + }, + "zonegroupmember": { + "bootseq": "8", + "configuration": "1", + "htsatchanmapset": "RINCON_40000000000101400:LF,RF;RINCON_40000000000201400:RR;RINCON_40000000000301400:LR;RINCON_40000000000401400:SW", + "icon": "x-rincon-roomicon:living", + "location": "http://192.168.1.103:1400/xml/device_description.xml", + "mincompatibleversion": "22.0-00000", + "softwareversion": "24.0-71060", + "uuid": "RINCON_40000000000101400", + "zonename": "Home Theatre" + } } ] \ No newline at end of file diff --git a/test/unit/SonosSystem.js b/test/unit/SonosSystem.js index 90b70a5c..25e98041 100644 --- a/test/unit/SonosSystem.js +++ b/test/unit/SonosSystem.js @@ -170,33 +170,33 @@ describe('SonosSystem', () => { }); it('Flags primary player if SUB is connected', () => { - sonos.zones.forEach((zone) => { - let tvRoom = zone.members.find((member) => member.roomName === 'TV Room'); - expect(tvRoom).not.undefined; - expect(tvRoom.hasSub).to.be.true; - }); + const tvRoom = sonos.getPlayer('TV Room'); + expect(tvRoom).not.undefined; + expect(tvRoom.hasSub).to.be.true; }); it('should flag stereo pair if SUB is connected', () => { - sonos.zones.forEach((zone) => { - let livingRoom = zone.members.find((member) => member.roomName === 'Living Room'); - expect(livingRoom).not.undefined; - expect(livingRoom.hasSub).to.be.true; - }); + const livingRoom = sonos.getPlayer('Living Room'); + expect(livingRoom).not.undefined; + expect(livingRoom.hasSub).to.be.true; + }); + + it('should flag PLAYBAR if SUB is connected', () => { + const playbar = sonos.getPlayer('Home Theatre'); + expect(playbar).not.undefined; + expect(playbar.hasSub).to.be.true; }); it('should not flag player if SUB is not connected', () => { - sonos.zones.forEach((zone) => { - let livingRoom = zone.members.find((member) => member.roomName === 'Kitchen'); - expect(livingRoom).not.undefined; - expect(livingRoom.hasSub).to.be.false; - }); + const kitchen = sonos.getPlayer('Kitchen'); + expect(kitchen).not.undefined; + expect(kitchen.hasSub).to.be.false; }); it('Only creates player once', () => { let topology = require('../data/topology.json'); listener.on.withArgs('topology').yield('', topology); - expect(Player).callCount(6); + expect(Player).callCount(7); }); it('Links coordinator property on all players', () => { diff --git a/test/unit/prototypes/Player/nightMode.js b/test/unit/prototypes/Player/nightMode.js new file mode 100644 index 00000000..2b92ce65 --- /dev/null +++ b/test/unit/prototypes/Player/nightMode.js @@ -0,0 +1,123 @@ +'use strict'; +const expect = require('chai').expect; +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); +require('chai').use(require('sinon-chai')); + +const soap = require('../../../../lib/helpers/soap'); +const TYPE = soap.TYPE; + +describe('Player.nightMode()', () => { + let zoneMemberData; + let request; + let Player; + let player; + let Subscriber; + let subscriber; + let listener; + let system; + let musicServices; + + beforeEach(() => { + sinon.stub(soap, 'invoke').resolves(); + sinon.stub(soap, 'parse'); + }); + + afterEach(() => { + if (soap.invoke.restore) + soap.invoke.restore(); + if (soap.parse.restore) + soap.parse.restore(); + }); + + beforeEach(() => { + zoneMemberData = { + uuid: 'RINCON_00000000000001400', + location: 'http://192.168.1.151:1400/xml/device_description.xml', + zonename: 'Kitchen', + icon: 'x-rincon-roomicon:kitchen', + configuration: '1', + softwareversion: '31.8-24090', + mincompatibleversion: '29.0-00000', + legacycompatibleversion: '24.0-00000', + bootseq: '114', + wirelessmode: '0', + hasconfiguredssid: '0', + channelfreq: '2412', + behindwifiextender: '0', + wifienabled: '1', + orientation: '0', + sonarstate: '4' + }; + + subscriber = { + dispose: sinon.spy() + }; + + Subscriber = sinon.stub().returns(subscriber); + + musicServices = { + tryGetHighResArt: sinon.stub() + }; + + musicServices.tryGetHighResArt.onCall(0).resolves('http://example.org/image1'); + musicServices.tryGetHighResArt.onCall(1).resolves('http://example.org/image2'); + + Player = proxyquire('../../../../lib/models/Player', { + '../Subscriber': Subscriber, + '../musicservices': musicServices + }); + + listener = { + endpoint: sinon.stub().returns('http://127.0.0.2/'), + on: sinon.spy() + }; + + system = { + zones: [ + { + uuid: zoneMemberData.uuid, + members: [] + } + ], + on: sinon.stub(), + emit: sinon.spy() + }; + + player = new Player(zoneMemberData, listener, system); + player.coordinator = player; + system.zones[0].coordinator = player; + system.zones[0].members.push(player); + }); + + it('should call correct soap call when enabling', () => { + return player.nightMode(true) + .then(() => { + expect(soap.invoke).calledOnce; + expect(soap.invoke.firstCall.args).eql([ + 'http://192.168.1.151:1400/MediaRenderer/RenderingControl/Control', + TYPE.SetEQ, + { + eqType: 'NightMode', + value: '1' + } + ]); + }); + }); + + it('should call correct soap call when disabling', () => { + return player.nightMode(false) + .then(() => { + expect(soap.invoke).calledOnce; + expect(soap.invoke.firstCall.args).eql([ + 'http://192.168.1.151:1400/MediaRenderer/RenderingControl/Control', + TYPE.SetEQ, + { + eqType: 'NightMode', + value: '0' + } + ]); + }); + }); + +}); diff --git a/test/unit/prototypes/Player/speechEnhancement.js b/test/unit/prototypes/Player/speechEnhancement.js new file mode 100644 index 00000000..bd6639fc --- /dev/null +++ b/test/unit/prototypes/Player/speechEnhancement.js @@ -0,0 +1,123 @@ +'use strict'; +const expect = require('chai').expect; +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); +require('chai').use(require('sinon-chai')); + +const soap = require('../../../../lib/helpers/soap'); +const TYPE = soap.TYPE; + +describe('Player.speechEnhancement()', () => { + let zoneMemberData; + let request; + let Player; + let player; + let Subscriber; + let subscriber; + let listener; + let system; + let musicServices; + + beforeEach(() => { + sinon.stub(soap, 'invoke').resolves(); + sinon.stub(soap, 'parse'); + }); + + afterEach(() => { + if (soap.invoke.restore) + soap.invoke.restore(); + if (soap.parse.restore) + soap.parse.restore(); + }); + + beforeEach(() => { + zoneMemberData = { + uuid: 'RINCON_00000000000001400', + location: 'http://192.168.1.151:1400/xml/device_description.xml', + zonename: 'Kitchen', + icon: 'x-rincon-roomicon:kitchen', + configuration: '1', + softwareversion: '31.8-24090', + mincompatibleversion: '29.0-00000', + legacycompatibleversion: '24.0-00000', + bootseq: '114', + wirelessmode: '0', + hasconfiguredssid: '0', + channelfreq: '2412', + behindwifiextender: '0', + wifienabled: '1', + orientation: '0', + sonarstate: '4' + }; + + subscriber = { + dispose: sinon.spy() + }; + + Subscriber = sinon.stub().returns(subscriber); + + musicServices = { + tryGetHighResArt: sinon.stub() + }; + + musicServices.tryGetHighResArt.onCall(0).resolves('http://example.org/image1'); + musicServices.tryGetHighResArt.onCall(1).resolves('http://example.org/image2'); + + Player = proxyquire('../../../../lib/models/Player', { + '../Subscriber': Subscriber, + '../musicservices': musicServices + }); + + listener = { + endpoint: sinon.stub().returns('http://127.0.0.2/'), + on: sinon.spy() + }; + + system = { + zones: [ + { + uuid: zoneMemberData.uuid, + members: [] + } + ], + on: sinon.stub(), + emit: sinon.spy() + }; + + player = new Player(zoneMemberData, listener, system); + player.coordinator = player; + system.zones[0].coordinator = player; + system.zones[0].members.push(player); + }); + + it('should call correct soap call when enabling', () => { + return player.speechEnhancement(true) + .then(() => { + expect(soap.invoke).calledOnce; + expect(soap.invoke.firstCall.args).eql([ + 'http://192.168.1.151:1400/MediaRenderer/RenderingControl/Control', + TYPE.SetEQ, + { + eqType: 'DialogLevel', + value: '1' + } + ]); + }); + }); + + it('should call correct soap call when disabling', () => { + return player.speechEnhancement(false) + .then(() => { + expect(soap.invoke).calledOnce; + expect(soap.invoke.firstCall.args).eql([ + 'http://192.168.1.151:1400/MediaRenderer/RenderingControl/Control', + TYPE.SetEQ, + { + eqType: 'DialogLevel', + value: '0' + } + ]); + }); + }); + +});