New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@svrooij/sonos

Package Overview
Dependencies
Maintainers
1
Versions
85
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@svrooij/sonos - npm Package Compare versions

Comparing version 1.1.5 to 1.2.0

lib/helpers/array-helper.d.ts

26

lib/helpers/tts-helper.js

@@ -20,19 +20,9 @@ "use strict";

});
return node_fetch_1.default(request)
.then(response => {
if (response.ok) {
return response.text();
}
else {
this.debug('handleRequest error %d %s', response.status, response.statusText);
throw new Error(`Http status ${response.status} (${response.statusText})`);
}
})
.then(JSON.parse)
.then(resp => {
return resp;
})
.then(resp => {
return resp.cdnUri || resp.uri;
});
const response = await node_fetch_1.default(request);
if (!response.ok) {
this.debug('handleRequest error %d %s', response.status, response.statusText);
throw new Error(`Http status ${response.status} (${response.statusText})`);
}
const data = JSON.parse(await response.text());
return data.cdnUri || data.uri;
}

@@ -43,3 +33,3 @@ }

// TODO Automaticly get from pacakge on build
TtsHelper.pack = { version: '0.4.2', homepage: 'https://github.com/svrooij/node-sonos-ts' };
TtsHelper.pack = { version: '1.1.5', homepage: 'https://github.com/svrooij/node-sonos-ts' };
//# sourceMappingURL=tts-helper.js.map
import { SonosDevice } from '../sonos-device';
import { ZoneGroup } from '../helpers/zone-helper';
import { ZoneGroup } from '../services/zone-group-topology.service';
export declare class SonosGroup {

@@ -4,0 +4,0 @@ readonly Coordinator: SonosDevice;

@@ -19,10 +19,10 @@ "use strict";

}
GetMediaMetadata(input) {
return this.SoapRequestWithBody('getMediaMetadata', input);
async GetMediaMetadata(input) {
return await this.SoapRequestWithBody('getMediaMetadata', input);
}
GetMediaUri(input) {
return this.SoapRequestWithBody('getMediaURI', input);
async GetMediaUri(input) {
return await this.SoapRequestWithBody('getMediaURI', input);
}
Search(input) {
return this.SoapRequestWithBody('search', input).then((resp) => this.PostProcessSearch(resp.searchResult));
async Search(input) {
return await this.SoapRequestWithBody('search', input).then((resp) => this.PostProcessSearch(resp.searchResult));
}

@@ -51,35 +51,28 @@ PostProcessSearch(input) {

//#region Private server stuff
SoapRequest(action) {
async SoapRequest(action) {
this.debug('%s()', action);
return this.handleRequestAndParseResponse(this.generateRequest(action, undefined), action);
return await this.handleRequestAndParseResponse(this.generateRequest(action, undefined), action);
}
SoapRequestWithBody(action, body) {
async SoapRequestWithBody(action, body) {
this.debug('%s(%o)', action, body);
return this.handleRequestAndParseResponse(this.generateRequest(action, body), action);
return await this.handleRequestAndParseResponse(this.generateRequest(action, body), action);
}
handleRequestAndParseResponse(request, action) {
return node_fetch_1.default(request)
.then(response => {
if (response.ok) {
return response.text();
}
else {
this.debug('handleRequest error %d %s', response.status, response.statusText);
throw new Error(`Http status ${response.status} (${response.statusText})`);
}
})
.then(body => fast_xml_parser_1.parse(body, { ignoreNameSpace: true }))
.then(result => {
if (!result || !result.Envelope || !result.Envelope.Body) {
this.debug('Invalid response for %s %o', action, result);
throw new Error(`Invalid response for ${action}: ${result}`);
}
if (typeof result.Envelope.Body.Fault !== 'undefined') {
const fault = result.Envelope.Body.Fault;
this.debug('Soap error %s %o', action, fault);
throw new Error(`SOAP Fault ${fault}`);
}
this.debug('handleRequest success');
return result.Envelope.Body[`${action}Response`];
});
async handleRequestAndParseResponse(request, action) {
const response = await node_fetch_1.default(request);
if (!response.ok) {
this.debug('handleRequest error %d %s', response.status, response.statusText);
throw new Error(`Http status ${response.status} (${response.statusText})`);
}
const result = fast_xml_parser_1.parse(await response.text(), { ignoreNameSpace: true });
if (!result || !result.Envelope || !result.Envelope.Body) {
this.debug('Invalid response for %s %o', action, result);
throw new Error(`Invalid response for ${action}: ${result}`);
}
if (typeof result.Envelope.Body.Fault !== 'undefined') {
const fault = result.Envelope.Body.Fault;
this.debug('Soap error %s %o', action, fault);
throw new Error(`SOAP Fault ${fault}`);
}
this.debug('handleRequest success');
return result.Envelope.Body[`${action}Response`];
}

@@ -86,0 +79,0 @@ messageAction(action) {

@@ -7,6 +7,6 @@ import { BaseService } from './base-service';

* @export
* @class AlarmClockService
* @class AlarmClockServiceBase
* @extends {BaseService}
*/
export declare class AlarmClockService extends BaseService {
export declare class AlarmClockServiceBase extends BaseService {
readonly serviceNane: string;

@@ -16,2 +16,16 @@ readonly controlUrl: string;

readonly scpUrl: string;
/**
* Create a single alarm, all properties are required
*
* @param {string} input.StartLocalTime - The starttime as hh:mm:ss
* @param {string} input.Duration - The duration as hh:mm:ss
* @param {string} input.Recurrence - Repeat this alarm on [ ONCE,WEEKDAYS,WEEKENDS,DAILY ]
* @param {boolean} input.Enabled - Alarm enabled after creation
* @param {string} input.RoomUUID - The UUID of the speaker you want this alarm for
* @param {string} input.ProgramURI - The sound uri
* @param {string | Track} input.ProgramMetaData - The sound metadata, can be empty string
* @param {string} input.PlayMode - Alarm playmode [ NORMAL,REPEAT_ALL,SHUFFLE_NOREPEAT,SHUFFLE ]
* @param {number} input.Volume - Volume between 0 and 100
* @param {boolean} input.IncludeLinkedZones - Should grouped palyers also play the alarm?
*/
CreateAlarm(input: {

@@ -29,2 +43,7 @@ StartLocalTime: string;

}): Promise<CreateAlarmResponse>;
/**
* Delete an alarm
*
* @param {number} input.ID - The Alarm ID, see ListAndParseAlarms
*/
DestroyAlarm(input: {

@@ -45,2 +64,5 @@ ID: number;

}): Promise<GetTimeZoneRuleResponse>;
/**
* Get the AlarmList as XML, use ListAndParseAlarms for parsed version
*/
ListAlarms(): Promise<ListAlarmsResponse>;

@@ -65,2 +87,17 @@ SetDailyIndexRefreshTime(input: {

}): Promise<boolean>;
/**
* Update an alarm, all parameters are required. Use PatchAlarm where you can update a single parameter
*
* @param {number} input.ID - The ID of the alarm see ListAndParseAlarms
* @param {string} input.StartLocalTime - The starttime as hh:mm:ss
* @param {string} input.Duration - The duration as hh:mm:ss
* @param {string} input.Recurrence - Repeat this alarm on [ ONCE,WEEKDAYS,WEEKENDS,DAILY ]
* @param {boolean} input.Enabled - Alarm enabled after creation
* @param {string} input.RoomUUID - The UUID of the speaker you want this alarm for
* @param {string} input.ProgramURI - The sound uri
* @param {string | Track} input.ProgramMetaData - The sound metadata, can be empty string
* @param {string} input.PlayMode - Alarm playmode [ NORMAL,REPEAT_ALL,SHUFFLE_NOREPEAT,SHUFFLE ]
* @param {number} input.Volume - Volume between 0 and 100
* @param {boolean} input.IncludeLinkedZones - Should grouped palyers also play the alarm?
*/
UpdateAlarm(input: {

@@ -118,2 +155,34 @@ ID: number;

}
import { Alarm, PatchAlarm } from '../models';
/**
* Extended AlarmClockService
*
* @export
* @class AlarmClockService
* @extends {AlarmClockServiceBase}
*/
export declare class AlarmClockService extends AlarmClockServiceBase {
/**
* Get a parsed list of all alarms.
*
* @returns {Promise<Alarm[]>}
* @memberof SonosDevice
*/
ListAndParseAlarms(): Promise<Alarm[]>;
/**
* Patch a single alarm. Only the ID and one property you want to change are required.
*
* @param {PatchAlarm} [options]
* @param {number} options.ID The ID of the alarm to update
* @param {string | undefined} options.StartLocalTime The time the alarm has to start 'hh:mm:ss'
* @param {string | undefined} options.Duration The duration of the alarm 'hh:mm:ss'
* @param {string | undefined} options.Recurrence What should the recurrence be ['DAILY','ONCE','WEEKDAYS']
* @param {boolean | undefined} options.Enabled Should this alarm be enabled
* @param {PlayMode | undefined} options.PlayMode What playmode should be used
* @param {number | undefined} options.Volume The requested alarm volume
* @returns {Promise<boolean>}
* @memberof SonosDevice
*/
PatchAlarm(options: PatchAlarm): Promise<boolean>;
}
//# sourceMappingURL=alarm-clock.service.d.ts.map

@@ -9,6 +9,6 @@ "use strict";

* @export
* @class AlarmClockService
* @class AlarmClockServiceBase
* @extends {BaseService}
*/
class AlarmClockService extends base_service_1.BaseService {
class AlarmClockServiceBase extends base_service_1.BaseService {
constructor() {

@@ -23,3 +23,22 @@ super(...arguments);

//#region methods
/**
* Create a single alarm, all properties are required
*
* @param {string} input.StartLocalTime - The starttime as hh:mm:ss
* @param {string} input.Duration - The duration as hh:mm:ss
* @param {string} input.Recurrence - Repeat this alarm on [ ONCE,WEEKDAYS,WEEKENDS,DAILY ]
* @param {boolean} input.Enabled - Alarm enabled after creation
* @param {string} input.RoomUUID - The UUID of the speaker you want this alarm for
* @param {string} input.ProgramURI - The sound uri
* @param {string | Track} input.ProgramMetaData - The sound metadata, can be empty string
* @param {string} input.PlayMode - Alarm playmode [ NORMAL,REPEAT_ALL,SHUFFLE_NOREPEAT,SHUFFLE ]
* @param {number} input.Volume - Volume between 0 and 100
* @param {boolean} input.IncludeLinkedZones - Should grouped palyers also play the alarm?
*/
async CreateAlarm(input) { return await this.SoapRequestWithBody('CreateAlarm', input); }
/**
* Delete an alarm
*
* @param {number} input.ID - The Alarm ID, see ListAndParseAlarms
*/
async DestroyAlarm(input) { return await this.SoapRequestWithBodyNoResponse('DestroyAlarm', input); }

@@ -34,2 +53,5 @@ async GetDailyIndexRefreshTime() { return await this.SoapRequest('GetDailyIndexRefreshTime'); }

async GetTimeZoneRule(input) { return await this.SoapRequestWithBody('GetTimeZoneRule', input); }
/**
* Get the AlarmList as XML, use ListAndParseAlarms for parsed version
*/
async ListAlarms() { return await this.SoapRequest('ListAlarms'); }

@@ -41,5 +63,91 @@ async SetDailyIndexRefreshTime(input) { return await this.SoapRequestWithBodyNoResponse('SetDailyIndexRefreshTime', input); }

async SetTimeZone(input) { return await this.SoapRequestWithBodyNoResponse('SetTimeZone', input); }
/**
* Update an alarm, all parameters are required. Use PatchAlarm where you can update a single parameter
*
* @param {number} input.ID - The ID of the alarm see ListAndParseAlarms
* @param {string} input.StartLocalTime - The starttime as hh:mm:ss
* @param {string} input.Duration - The duration as hh:mm:ss
* @param {string} input.Recurrence - Repeat this alarm on [ ONCE,WEEKDAYS,WEEKENDS,DAILY ]
* @param {boolean} input.Enabled - Alarm enabled after creation
* @param {string} input.RoomUUID - The UUID of the speaker you want this alarm for
* @param {string} input.ProgramURI - The sound uri
* @param {string | Track} input.ProgramMetaData - The sound metadata, can be empty string
* @param {string} input.PlayMode - Alarm playmode [ NORMAL,REPEAT_ALL,SHUFFLE_NOREPEAT,SHUFFLE ]
* @param {number} input.Volume - Volume between 0 and 100
* @param {boolean} input.IncludeLinkedZones - Should grouped palyers also play the alarm?
*/
async UpdateAlarm(input) { return await this.SoapRequestWithBodyNoResponse('UpdateAlarm', input); }
}
exports.AlarmClockServiceBase = AlarmClockServiceBase;
const xml_helper_1 = require("../helpers/xml-helper");
const metadata_helper_1 = require("../helpers/metadata-helper");
const array_helper_1 = require("../helpers/array-helper");
/**
* Extended AlarmClockService
*
* @export
* @class AlarmClockService
* @extends {AlarmClockServiceBase}
*/
class AlarmClockService extends AlarmClockServiceBase {
/**
* Get a parsed list of all alarms.
*
* @returns {Promise<Alarm[]>}
* @memberof SonosDevice
*/
async ListAndParseAlarms() {
const alarmList = await super.ListAlarms();
const parsedList = xml_helper_1.XmlHelper.DecodeAndParseXml(alarmList.CurrentAlarmList, '');
const alarms = array_helper_1.ArrayHelper.ForceArray(parsedList.Alarms.Alarm);
alarms.forEach(alarm => {
alarm.Enabled = alarm.Enabled === '1';
alarm.ID = parseInt(alarm.ID);
alarm.IncludeLinkedZones = alarm.IncludeLinkedZones === '1';
alarm.Volume = parseInt(alarm.Volume);
// Alarm response has StartTime, but updates expect StartLocalTime, why??
alarm.StartLocalTime = alarm.StartTime;
delete alarm.StartTime;
if (typeof alarm.ProgramMetaData === 'string')
alarm.ProgramMetaData = metadata_helper_1.MetadataHelper.ParseDIDLTrack(xml_helper_1.XmlHelper.DecodeAndParseXml(alarm.ProgramMetaData), this.host, this.port);
alarm.ProgramURI = xml_helper_1.XmlHelper.DecodeTrackUri(alarm.ProgramURI);
});
return alarms;
}
/**
* Patch a single alarm. Only the ID and one property you want to change are required.
*
* @param {PatchAlarm} [options]
* @param {number} options.ID The ID of the alarm to update
* @param {string | undefined} options.StartLocalTime The time the alarm has to start 'hh:mm:ss'
* @param {string | undefined} options.Duration The duration of the alarm 'hh:mm:ss'
* @param {string | undefined} options.Recurrence What should the recurrence be ['DAILY','ONCE','WEEKDAYS']
* @param {boolean | undefined} options.Enabled Should this alarm be enabled
* @param {PlayMode | undefined} options.PlayMode What playmode should be used
* @param {number | undefined} options.Volume The requested alarm volume
* @returns {Promise<boolean>}
* @memberof SonosDevice
*/
async PatchAlarm(options) {
this.debug('AlarmPatch(%o)', options);
const alarms = await this.ListAndParseAlarms();
const alarm = alarms.find(a => a.ID === options.ID);
if (alarm === undefined)
throw new Error(`Alarm with ID ${options.ID} not found`);
if (options.Duration !== undefined)
alarm.Duration = options.Duration;
if (options.Enabled !== undefined)
alarm.Enabled = options.Enabled;
if (options.PlayMode !== undefined)
alarm.PlayMode = options.PlayMode;
if (options.Recurrence !== undefined)
alarm.Recurrence = options.Recurrence;
if (options.StartLocalTime !== undefined)
alarm.StartLocalTime = options.StartLocalTime;
if (options.Volume !== undefined)
alarm.Volume = options.Volume;
return await this.UpdateAlarm(alarm);
}
}
exports.AlarmClockService = AlarmClockService;
//# sourceMappingURL=alarm-clock.service.js.map

@@ -93,2 +93,8 @@ import { BaseService } from './base-service';

}): Promise<boolean>;
/**
* Stop playing after set sleep timer
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {string} input.NewSleepTimerDuration - Time to stop after, as hh:mm:ss
*/
ConfigureSleepTimer(input: {

@@ -121,8 +127,23 @@ InstanceID: number;

}): Promise<GetDeviceCapabilitiesResponse>;
/**
* Get information about the current playing media (queue)
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
GetMediaInfo(input?: {
InstanceID: number;
}): Promise<GetMediaInfoResponse>;
/**
* Get information about current position (position in queue and Time in current song)
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
GetPositionInfo(input?: {
InstanceID: number;
}): Promise<GetPositionInfoResponse>;
/**
* Get Time left on sleeptimer
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
GetRemainingSleepTimerDuration(input?: {

@@ -140,2 +161,7 @@ InstanceID: number;

}): Promise<GetTransportSettingsResponse>;
/**
* Go to next song, not always supported
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
Next(input?: {

@@ -148,5 +174,16 @@ InstanceID: number;

}): Promise<boolean>;
/**
* Pause playback
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
Pause(input?: {
InstanceID: number;
}): Promise<boolean>;
/**
* Start playing the set TransportURI
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {string} input.Speed - [ 1 ]
*/
Play(input: {

@@ -156,2 +193,7 @@ InstanceID: number;

}): Promise<boolean>;
/**
* Go to previous song, not always supported
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
Previous(input?: {

@@ -204,2 +246,9 @@ InstanceID: number;

}): Promise<SaveQueueResponse>;
/**
* Seek track in queue, time delta or absolute time in song
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {string} input.Unit - What to seek [ TRACK_NR,REL_TIME,TIME_DELTA ]
* @param {string} input.Target - number for track in queue, hh:mm:ss for absolute time in track
*/
Seek(input: {

@@ -210,2 +259,9 @@ InstanceID: number;

}): Promise<boolean>;
/**
* Set the transport URI, to a song, a stream, the queue and a lot more
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {string} input.CurrentURI - The track URI
* @param {string | Track} input.CurrentURIMetaData -
*/
SetAVTransportURI(input: {

@@ -225,2 +281,8 @@ InstanceID: number;

}): Promise<boolean>;
/**
* Set the PlayMode
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {PlayMode} input.NewPlayMode - PlayMode [ NORMAL,REPEAT_ALL,REPEAT_ONE,SHUFFLE_NOREPEAT,SHUFFLE,SHUFFLE_REPEAT_ONE ]
*/
SetPlayMode(input: {

@@ -227,0 +289,0 @@ InstanceID: number;

@@ -36,2 +36,8 @@ "use strict";

async ChangeTransportSettings(input) { return await this.SoapRequestWithBodyNoResponse('ChangeTransportSettings', input); }
/**
* Stop playing after set sleep timer
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {string} input.NewSleepTimerDuration - Time to stop after, as hh:mm:ss
*/
async ConfigureSleepTimer(input) { return await this.SoapRequestWithBodyNoResponse('ConfigureSleepTimer', input); }

@@ -44,4 +50,19 @@ async CreateSavedQueue(input) { return await this.SoapRequestWithBody('CreateSavedQueue', input); }

async GetDeviceCapabilities(input = { InstanceID: 0 }) { return await this.SoapRequestWithBody('GetDeviceCapabilities', input); }
/**
* Get information about the current playing media (queue)
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
async GetMediaInfo(input = { InstanceID: 0 }) { return await this.SoapRequestWithBody('GetMediaInfo', input); }
/**
* Get information about current position (position in queue and Time in current song)
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
async GetPositionInfo(input = { InstanceID: 0 }) { return await this.SoapRequestWithBody('GetPositionInfo', input); }
/**
* Get Time left on sleeptimer
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
async GetRemainingSleepTimerDuration(input = { InstanceID: 0 }) { return await this.SoapRequestWithBody('GetRemainingSleepTimerDuration', input); }

@@ -51,6 +72,27 @@ async GetRunningAlarmProperties(input = { InstanceID: 0 }) { return await this.SoapRequestWithBody('GetRunningAlarmProperties', input); }

async GetTransportSettings(input = { InstanceID: 0 }) { return await this.SoapRequestWithBody('GetTransportSettings', input); }
/**
* Go to next song, not always supported
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
async Next(input = { InstanceID: 0 }) { return await this.SoapRequestWithBodyNoResponse('Next', input); }
async NotifyDeletedURI(input) { return await this.SoapRequestWithBodyNoResponse('NotifyDeletedURI', input); }
/**
* Pause playback
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
async Pause(input = { InstanceID: 0 }) { return await this.SoapRequestWithBodyNoResponse('Pause', input); }
/**
* Start playing the set TransportURI
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {string} input.Speed - [ 1 ]
*/
async Play(input) { return await this.SoapRequestWithBodyNoResponse('Play', input); }
/**
* Go to previous song, not always supported
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
async Previous(input = { InstanceID: 0 }) { return await this.SoapRequestWithBodyNoResponse('Previous', input); }

@@ -64,6 +106,26 @@ async RemoveAllTracksFromQueue(input = { InstanceID: 0 }) { return await this.SoapRequestWithBodyNoResponse('RemoveAllTracksFromQueue', input); }

async SaveQueue(input) { return await this.SoapRequestWithBody('SaveQueue', input); }
/**
* Seek track in queue, time delta or absolute time in song
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {string} input.Unit - What to seek [ TRACK_NR,REL_TIME,TIME_DELTA ]
* @param {string} input.Target - number for track in queue, hh:mm:ss for absolute time in track
*/
async Seek(input) { return await this.SoapRequestWithBodyNoResponse('Seek', input); }
/**
* Set the transport URI, to a song, a stream, the queue and a lot more
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {string} input.CurrentURI - The track URI
* @param {string | Track} input.CurrentURIMetaData -
*/
async SetAVTransportURI(input) { return await this.SoapRequestWithBodyNoResponse('SetAVTransportURI', input); }
async SetCrossfadeMode(input) { return await this.SoapRequestWithBodyNoResponse('SetCrossfadeMode', input); }
async SetNextAVTransportURI(input) { return await this.SoapRequestWithBodyNoResponse('SetNextAVTransportURI', input); }
/**
* Set the PlayMode
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {PlayMode} input.NewPlayMode - PlayMode [ NORMAL,REPEAT_ALL,REPEAT_ONE,SHUFFLE_NOREPEAT,SHUFFLE,SHUFFLE_REPEAT_ONE ]
*/
async SetPlayMode(input) { return await this.SoapRequestWithBodyNoResponse('SetPlayMode', input); }

@@ -70,0 +132,0 @@ /**

@@ -245,3 +245,3 @@ "use strict";

this.events = new events_1.EventEmitter();
this.events.on('removeListener', () => {
this.events.on('removeListener', async () => {
this.debug('Listener removed');

@@ -252,11 +252,10 @@ if (this.events === undefined)

if (this.sid !== undefined && events.length === 0)
this.cancelSubscription(this.sid);
await this.cancelSubscription(this.sid);
});
this.events.on('newListener', () => {
this.events.on('newListener', async () => {
this.debug('Listener added');
if (this.sid === undefined) {
this.debug('Subscribing to events');
return this.subscribeForEvents().then(sid => {
sonos_event_listener_1.SonosEventListener.DefaultInstance.RegisterSubscription(sid, this);
});
const sid = await this.subscribeForEvents();
sonos_event_listener_1.SonosEventListener.DefaultInstance.RegisterSubscription(sid, this);
}

@@ -267,5 +266,5 @@ });

}
subscribeForEvents() {
async subscribeForEvents() {
const callback = sonos_event_listener_1.SonosEventListener.DefaultInstance.GetEndpoint(this.uuid, this.serviceNane);
return node_fetch_1.default(new node_fetch_2.Request(`http://${this.host}:${this.port}${this.eventSubUrl}`, {
const resp = await node_fetch_1.default(new node_fetch_2.Request(`http://${this.host}:${this.port}${this.eventSubUrl}`, {
method: 'SUBSCRIBE',

@@ -277,20 +276,17 @@ headers: {

}
}))
.then(resp => {
if (resp.ok)
return resp.headers.get('sid');
}));
const sid = resp.ok ? resp.headers.get('sid') : undefined;
if (sid === undefined || sid === '') {
throw new Error('No subscription id received');
})
.then(sid => {
this.sid = sid;
this.eventRenewTimeout = setTimeout(() => {
if (this.sid !== undefined)
this.renewEventSubscription(this.sid);
}, 3500 * 1000);
return this.sid;
});
}
this.sid = sid;
this.eventRenewTimeout = setTimeout(async () => {
if (this.sid !== undefined)
return await this.renewEventSubscription(this.sid);
}, 3500 * 1000);
return sid;
}
renewEventSubscription(sid) {
async renewEventSubscription(sid) {
this.debug('Renewing event subscription');
return node_fetch_1.default(new node_fetch_2.Request(`http://${this.host}:${this.port}${this.eventSubUrl}`, {
const resp = await node_fetch_1.default(new node_fetch_2.Request(`http://${this.host}:${this.port}${this.eventSubUrl}`, {
method: 'SUBSCRIBE',

@@ -301,16 +297,22 @@ headers: {

}
}))
.then(resp => resp.ok)
.then(success => {
}));
if (resp.ok) {
this.debug('Renewed event subscription');
this.eventRenewTimeout = setTimeout(() => {
this.eventRenewTimeout = setTimeout(async () => {
if (this.sid !== undefined)
this.renewEventSubscription(this.sid);
await this.renewEventSubscription(this.sid);
}, 3500 * 1000);
return success;
});
return true;
}
this.debug('Renew event subscription failed, trying to resubscribe');
const newSid = await this.subscribeForEvents();
sonos_event_listener_1.SonosEventListener.DefaultInstance.RegisterSubscription(newSid, this);
return true;
}
cancelSubscription(sid) {
async cancelSubscription(sid) {
this.debug('Cancelling event subscription');
return node_fetch_1.default(new node_fetch_2.Request(`http://${this.host}:${this.port}${this.eventSubUrl}`, {
if (this.eventRenewTimeout !== undefined)
clearTimeout(this.eventRenewTimeout);
this.sid = undefined;
const resp = await node_fetch_1.default(new node_fetch_2.Request(`http://${this.host}:${this.port}${this.eventSubUrl}`, {
method: 'UNSUBSCRIBE',

@@ -320,11 +322,5 @@ headers: {

}
}))
.then(resp => resp.ok)
.then(success => {
this.debug('Cancelled event subscription');
if (this.eventRenewTimeout !== undefined)
clearTimeout(this.eventRenewTimeout);
this.sid = undefined;
return success;
});
}));
this.debug('Cancelled event subscription success %o', resp.ok);
return resp.ok;
}

@@ -331,0 +327,0 @@ /**

@@ -7,6 +7,6 @@ import { BaseService } from './base-service';

* @export
* @class ContentDirectoryService
* @class ContentDirectoryServiceBase
* @extends {BaseService}
*/
export declare class ContentDirectoryService extends BaseService {
export declare class ContentDirectoryServiceBase extends BaseService {
readonly serviceNane: string;

@@ -16,2 +16,12 @@ readonly controlUrl: string;

readonly scpUrl: string;
/**
* Browse for content, see BrowseParsed for a better experience.
*
* @param {string} input.ObjectID - The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @param {string} input.BrowseFlag - How to browse [ BrowseMetadata,BrowseDirectChildren ]
* @param {string} input.Filter - Which fields should be returned '*' for all.
* @param {number} input.StartingIndex - Paging, where to start
* @param {number} input.RequestedCount - Paging, number of items
* @param {string} input.SortCriteria - Sort the results based on metadata fields. '+upnp:artist,+dc:title' for sorting on artist then on title.
*/
Browse(input: {

@@ -95,2 +105,40 @@ ObjectID: string;

}
/**
* Browse for local content
*
* @export
* @class ContentDirectoryService
* @extends {ContentDirectoryServiceBase}
*/
export declare class ContentDirectoryService extends ContentDirectoryServiceBase {
/**
* Browse or search content directory
*
* @param {string} input.ObjectID The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @param {string} input.BrowseFlag How to browse [ BrowseMetadata,BrowseDirectChildren ]
* @param {string} input.Filter Which fields should be returned '*' for all.
* @param {number} input.StartingIndex Where to start in the results, (could be used for paging)
* @param {number} input.RequestedCount How many items should be returned, 0 for all.
* @param {string} input.SortCriteria Sort the results based on metadata fields. '+upnp:artist,+dc:title' for sorting on artist then on title.
* @returns {Promise<BrowseResponse>}
* @memberof ContentDirectoryService
* @see http://www.upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf
*/
BrowseParsed(input: {
ObjectID: string;
BrowseFlag: string;
Filter: string;
StartingIndex: number;
RequestedCount: number;
SortCriteria: string;
}): Promise<BrowseResponse>;
/**
* Same as BrowseParsed but with all parameters set to default.
*
* @param {string} ObjectID The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @returns {Promise<BrowseResponse>}
* @memberof SonosDevice
*/
BrowseParsedWithDefaults(ObjectID: string): Promise<BrowseResponse>;
}
//# sourceMappingURL=content-directory.service.d.ts.map

@@ -9,6 +9,6 @@ "use strict";

* @export
* @class ContentDirectoryService
* @class ContentDirectoryServiceBase
* @extends {BaseService}
*/
class ContentDirectoryService extends base_service_1.BaseService {
class ContentDirectoryServiceBase extends base_service_1.BaseService {
constructor() {

@@ -23,2 +23,12 @@ super(...arguments);

//#region methods
/**
* Browse for content, see BrowseParsed for a better experience.
*
* @param {string} input.ObjectID - The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @param {string} input.BrowseFlag - How to browse [ BrowseMetadata,BrowseDirectChildren ]
* @param {string} input.Filter - Which fields should be returned '*' for all.
* @param {number} input.StartingIndex - Paging, where to start
* @param {number} input.RequestedCount - Paging, number of items
* @param {string} input.SortCriteria - Sort the results based on metadata fields. '+upnp:artist,+dc:title' for sorting on artist then on title.
*/
async Browse(input) { return await this.SoapRequestWithBody('Browse', input); }

@@ -41,3 +51,49 @@ async CreateObject(input) { return await this.SoapRequestWithBody('CreateObject', input); }

}
exports.ContentDirectoryServiceBase = ContentDirectoryServiceBase;
const array_helper_1 = require("../helpers/array-helper");
const xml_helper_1 = require("../helpers/xml-helper");
const metadata_helper_1 = require("../helpers/metadata-helper");
/**
* Browse for local content
*
* @export
* @class ContentDirectoryService
* @extends {ContentDirectoryServiceBase}
*/
class ContentDirectoryService extends ContentDirectoryServiceBase {
/**
* Browse or search content directory
*
* @param {string} input.ObjectID The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @param {string} input.BrowseFlag How to browse [ BrowseMetadata,BrowseDirectChildren ]
* @param {string} input.Filter Which fields should be returned '*' for all.
* @param {number} input.StartingIndex Where to start in the results, (could be used for paging)
* @param {number} input.RequestedCount How many items should be returned, 0 for all.
* @param {string} input.SortCriteria Sort the results based on metadata fields. '+upnp:artist,+dc:title' for sorting on artist then on title.
* @returns {Promise<BrowseResponse>}
* @memberof ContentDirectoryService
* @see http://www.upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf
*/
async BrowseParsed(input) {
const resp = await this.Browse(input);
if (typeof resp.Result === 'string' && resp.NumberReturned > 0) {
const parsedData = xml_helper_1.XmlHelper.DecodeAndParseXml(resp.Result)['DIDL-Lite'];
const itemObject = parsedData.item || parsedData.container;
const items = array_helper_1.ArrayHelper.ForceArray(itemObject);
resp.Result = items.map(i => metadata_helper_1.MetadataHelper.ParseDIDLTrack(i, this.host, this.port));
}
return resp;
}
/**
* Same as BrowseParsed but with all parameters set to default.
*
* @param {string} ObjectID The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @returns {Promise<BrowseResponse>}
* @memberof SonosDevice
*/
async BrowseParsedWithDefaults(ObjectID) {
return await this.BrowseParsed({ ObjectID: ObjectID, BrowseFlag: 'BrowseDirectChildren', Filter: '*', StartingIndex: 0, RequestedCount: 0, SortCriteria: '' });
}
}
exports.ContentDirectoryService = ContentDirectoryService;
//# sourceMappingURL=content-directory.service.js.map

@@ -14,8 +14,24 @@ import { BaseService } from './base-service';

readonly scpUrl: string;
/**
* Get if the group is muted
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
GetGroupMute(input?: {
InstanceID: number;
}): Promise<GetGroupMuteResponse>;
/**
* Get the average group volume
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
GetGroupVolume(input?: {
InstanceID: number;
}): Promise<GetGroupVolumeResponse>;
/**
* (Un-/)Mute the entire group
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {boolean} input.DesiredMute - True for mute, false for unmute
*/
SetGroupMute(input: {

@@ -25,2 +41,8 @@ InstanceID: number;

}): Promise<boolean>;
/**
* Change group volume, players will be changed proportionally
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {number} input.DesiredVolume - New volume
*/
SetGroupVolume(input: {

@@ -30,2 +52,8 @@ InstanceID: number;

}): Promise<boolean>;
/**
* Relativly change group volume
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {number} input.Adjustment - number between -100 / +100
*/
SetRelativeGroupVolume(input: {

@@ -32,0 +60,0 @@ InstanceID: number;

@@ -22,6 +22,34 @@ "use strict";

//#region methods
/**
* Get if the group is muted
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
async GetGroupMute(input = { InstanceID: 0 }) { return await this.SoapRequestWithBody('GetGroupMute', input); }
/**
* Get the average group volume
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
*/
async GetGroupVolume(input = { InstanceID: 0 }) { return await this.SoapRequestWithBody('GetGroupVolume', input); }
/**
* (Un-/)Mute the entire group
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {boolean} input.DesiredMute - True for mute, false for unmute
*/
async SetGroupMute(input) { return await this.SoapRequestWithBodyNoResponse('SetGroupMute', input); }
/**
* Change group volume, players will be changed proportionally
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {number} input.DesiredVolume - New volume
*/
async SetGroupVolume(input) { return await this.SoapRequestWithBodyNoResponse('SetGroupVolume', input); }
/**
* Relativly change group volume
*
* @param {number} input.InstanceID - InstanceID meaning unknown, just set to 0
* @param {number} input.Adjustment - number between -100 / +100
*/
async SetRelativeGroupVolume(input) { return await this.SoapRequestWithBody('SetRelativeGroupVolume', input); }

@@ -28,0 +56,0 @@ async SnapshotGroupVolume(input = { InstanceID: 0 }) { return await this.SoapRequestWithBodyNoResponse('SnapshotGroupVolume', input); }

@@ -6,6 +6,6 @@ import { BaseService } from './base-service';

* @export
* @class ZoneGroupTopologyService
* @class ZoneGroupTopologyServiceBase
* @extends {BaseService}
*/
export declare class ZoneGroupTopologyService extends BaseService {
export declare class ZoneGroupTopologyServiceBase extends BaseService {
readonly serviceNane: string;

@@ -25,3 +25,9 @@ readonly controlUrl: string;

}): Promise<CheckForUpdateResponse>;
/**
* Get information about the current Zone
*/
GetZoneGroupAttributes(): Promise<GetZoneGroupAttributesResponse>;
/**
* Get all the Sonos groups, (as XML), see GetParsedZoneGroupState
*/
GetZoneGroupState(): Promise<GetZoneGroupStateResponse>;

@@ -58,2 +64,32 @@ RegisterMobileDevice(input: {

}
export interface ZoneGroup {
name: string;
coordinator: ZoneMember;
members: ZoneMember[];
}
interface ZoneMember {
host: string;
port: number;
uuid: string;
name: string;
}
/**
* Zone config stuff, eg getting all the configured sonos zones.
*
* @export
* @class ZoneGroupTopologyService
* @extends {ZoneGroupTopologyServiceBase}
*/
export declare class ZoneGroupTopologyService extends ZoneGroupTopologyServiceBase {
/**
* Get all the sonos groups in your current network. Parsed the GetZoneGroupState output.
*
* @returns {Promise<ZoneGroup[]>}
* @memberof ZoneGroupTopologyService
*/
GetParsedZoneGroupState(): Promise<ZoneGroup[]>;
private ParseMember;
private ParseGroup;
}
export {};
//# sourceMappingURL=zone-group-topology.service.d.ts.map

@@ -9,6 +9,6 @@ "use strict";

* @export
* @class ZoneGroupTopologyService
* @class ZoneGroupTopologyServiceBase
* @extends {BaseService}
*/
class ZoneGroupTopologyService extends base_service_1.BaseService {
class ZoneGroupTopologyServiceBase extends base_service_1.BaseService {
constructor() {

@@ -25,3 +25,9 @@ super(...arguments);

async CheckForUpdate(input) { return await this.SoapRequestWithBody('CheckForUpdate', input); }
/**
* Get information about the current Zone
*/
async GetZoneGroupAttributes() { return await this.SoapRequest('GetZoneGroupAttributes'); }
/**
* Get all the Sonos groups, (as XML), see GetParsedZoneGroupState
*/
async GetZoneGroupState() { return await this.SoapRequest('GetZoneGroupState'); }

@@ -33,3 +39,51 @@ async RegisterMobileDevice(input) { return await this.SoapRequestWithBodyNoResponse('RegisterMobileDevice', input); }

}
exports.ZoneGroupTopologyServiceBase = ZoneGroupTopologyServiceBase;
const xml_helper_1 = require("../helpers/xml-helper");
const array_helper_1 = require("../helpers/array-helper");
const url_1 = require("url");
/**
* Zone config stuff, eg getting all the configured sonos zones.
*
* @export
* @class ZoneGroupTopologyService
* @extends {ZoneGroupTopologyServiceBase}
*/
class ZoneGroupTopologyService extends ZoneGroupTopologyServiceBase {
/**
* Get all the sonos groups in your current network. Parsed the GetZoneGroupState output.
*
* @returns {Promise<ZoneGroup[]>}
* @memberof ZoneGroupTopologyService
*/
async GetParsedZoneGroupState() {
const groupStateResponse = await this.GetZoneGroupState();
const decodedGroupState = xml_helper_1.XmlHelper.DecodeAndParseXml(groupStateResponse.ZoneGroupState);
const groups = array_helper_1.ArrayHelper.ForceArray(decodedGroupState.ZoneGroupState.ZoneGroups.ZoneGroup);
return groups.map(this.ParseGroup);
}
ParseMember(member) {
const uri = new url_1.URL(member._Location);
return {
name: member._ZoneName,
uuid: member._UUID,
host: uri.hostname,
port: parseInt(uri.port)
};
}
ParseGroup(group) {
const members = array_helper_1.ArrayHelper.ForceArray(group.ZoneGroupMember).map(this.ParseMember);
const coordinator = members.find(m => m.uuid === group._Coordinator);
if (coordinator === undefined)
throw new Error('Error parsing ZoneGroup');
let name = coordinator.name;
if (members.length > 1)
name += ` + ${members.length - 1}`;
return {
name: name,
coordinator: coordinator,
members: members
};
}
}
exports.ZoneGroupTopologyService = ZoneGroupTopologyService;
//# sourceMappingURL=zone-group-topology.service.js.map

@@ -53,2 +53,3 @@ /// <reference types="node" />

* @returns {Promise<Alarm[]>}
* @deprecated Will be removed in favor of Extended AlarmClockService
* @memberof SonosDevice

@@ -68,2 +69,3 @@ */

* @param {number | undefined} options.Volume The requested alarm volume
* @deprecated Will be removed in favor of Extended AlarmClockService
* @returns {Promise<boolean>}

@@ -74,32 +76,2 @@ * @memberof SonosDevice

/**
* Browse or search content directory
*
* @param {{ ObjectID: string; BrowseFlag: string; Filter: string; StartingIndex: number; RequestedCount: number; SortCriteria: string }} input
* @param {string} ObjectID The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @param {string} BrowseFlag 'BrowseDirectChildren' is default, could also be 'BrowseMetadata'
* @param {string} Filter Which fields should be returned '*' for all.
* @param {number} StartingIndex Where to start in the results, (could be used for paging)
* @param {number} RequestedCount How many items should be returned, 0 for all.
* @param {string} SortCriteria Sort the results based on metadata fields. '+upnp:artist,+dc:title' for sorting on artist then on title.
* @returns {Promise<BrowseResponse>}
* @memberof SonosDevice
* @see http://www.upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf
*/
Browse(input: {
ObjectID: string;
BrowseFlag: string;
Filter: string;
StartingIndex: number;
RequestedCount: number;
SortCriteria: string;
}): Promise<BrowseResponse>;
/**
* Same as browse but with all parameters set to default.
*
* @param {string} ObjectID The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @returns {Promise<BrowseResponse>}
* @memberof SonosDevice
*/
BrowseWithDefaults(ObjectID: string): Promise<BrowseResponse>;
/**
* Execute any sonos command by name, see examples/commands.js

@@ -106,0 +78,0 @@ *

@@ -6,3 +6,2 @@ "use strict";

const async_helper_1 = require("./helpers/async-helper");
const zone_helper_1 = require("./helpers/zone-helper");
const events_1 = require("events");

@@ -59,4 +58,4 @@ const xml_helper_1 = require("./helpers/xml-helper");

this.name = this.zoneAttributes.CurrentZoneName;
this.volume = await this.RenderingControlService.GetVolume({ InstanceID: 0, Channel: 'Master' }).then(r => r.CurrentVolume);
this.muted = await this.RenderingControlService.GetMute({ InstanceID: 0, Channel: 'Master' }).then(m => m.CurrentMute);
this.volume = (await this.RenderingControlService.GetVolume({ InstanceID: 0, Channel: 'Master' })).CurrentVolume;
this.muted = (await this.RenderingControlService.GetMute({ InstanceID: 0, Channel: 'Master' })).CurrentMute;
return true;

@@ -76,3 +75,3 @@ }

const guessedMetaData = metadata_helper_1.MetadataHelper.GuessMetaDataAndTrackUri(trackUri);
return this.AVTransportService.AddURIToQueue({
return await this.AVTransportService.AddURIToQueue({
InstanceID: 0,

@@ -89,25 +88,7 @@ EnqueuedURI: guessedMetaData.trackUri,

* @returns {Promise<Alarm[]>}
* @deprecated Will be removed in favor of Extended AlarmClockService
* @memberof SonosDevice
*/
async AlarmList() {
return this.AlarmClockService.ListAlarms()
.then(response => xml_helper_1.XmlHelper.DecodeAndParseXml(response.CurrentAlarmList, ''))
.then(parsedList => {
return Array.isArray(parsedList.Alarms.Alarm) ? parsedList.Alarms.Alarm : [parsedList.Alarms.Alarm];
})
.then((alarms) => {
alarms.forEach(alarm => {
alarm.Enabled = alarm.Enabled === '1';
alarm.ID = parseInt(alarm.ID);
alarm.IncludeLinkedZones = alarm.IncludeLinkedZones === '1';
alarm.Volume = parseInt(alarm.Volume);
// Alarm response has StartTime, but updates expect StartLocalTime, why??
alarm.StartLocalTime = alarm.StartTime;
delete alarm.StartTime;
if (typeof alarm.ProgramMetaData === 'string')
alarm.ProgramMetaData = metadata_helper_1.MetadataHelper.ParseDIDLTrack(xml_helper_1.XmlHelper.DecodeAndParseXml(alarm.ProgramMetaData), this.host, this.port);
alarm.ProgramURI = xml_helper_1.XmlHelper.DecodeTrackUri(alarm.ProgramURI);
});
return alarms;
});
return await this.AlarmClockService.ListAndParseAlarms();
}

@@ -125,2 +106,3 @@ /**

* @param {number | undefined} options.Volume The requested alarm volume
* @deprecated Will be removed in favor of Extended AlarmClockService
* @returns {Promise<boolean>}

@@ -130,59 +112,5 @@ * @memberof SonosDevice

async AlarmPatch(options) {
this.debug('AlarmPatch(%o)', options);
return this.AlarmList().then(alarms => {
const alarm = alarms.find(a => a.ID === options.ID);
if (alarm === undefined)
throw new Error(`Alarm with ID ${options.ID} not found`);
if (options.Duration !== undefined)
alarm.Duration = options.Duration;
if (options.Enabled !== undefined)
alarm.Enabled = options.Enabled;
if (options.PlayMode !== undefined)
alarm.PlayMode = options.PlayMode;
if (options.Recurrence !== undefined)
alarm.Recurrence = options.Recurrence;
if (options.StartLocalTime !== undefined)
alarm.StartLocalTime = options.StartLocalTime;
if (options.Volume !== undefined)
alarm.Volume = options.Volume;
return this.AlarmClockService.UpdateAlarm(alarm);
});
return await this.AlarmClockService.PatchAlarm(options);
}
/**
* Browse or search content directory
*
* @param {{ ObjectID: string; BrowseFlag: string; Filter: string; StartingIndex: number; RequestedCount: number; SortCriteria: string }} input
* @param {string} ObjectID The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @param {string} BrowseFlag 'BrowseDirectChildren' is default, could also be 'BrowseMetadata'
* @param {string} Filter Which fields should be returned '*' for all.
* @param {number} StartingIndex Where to start in the results, (could be used for paging)
* @param {number} RequestedCount How many items should be returned, 0 for all.
* @param {string} SortCriteria Sort the results based on metadata fields. '+upnp:artist,+dc:title' for sorting on artist then on title.
* @returns {Promise<BrowseResponse>}
* @memberof SonosDevice
* @see http://www.upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf
*/
async Browse(input) {
return this.ContentDirectoryService.Browse(input)
.then(resp => {
if (typeof resp.Result === 'string' && resp.NumberReturned > 0) {
const parsedData = xml_helper_1.XmlHelper.DecodeAndParseXml(resp.Result)['DIDL-Lite'];
const itemObject = parsedData.item || parsedData.container;
const items = Array.isArray(itemObject) ? itemObject : [itemObject];
resp.Result = items.map(i => metadata_helper_1.MetadataHelper.ParseDIDLTrack(i, this.host, this.port));
}
return resp;
});
}
/**
* Same as browse but with all parameters set to default.
*
* @param {string} ObjectID The search query, ['A:ARTIST','A:ALBUMARTIST','A:ALBUM','A:GENRE','A:COMPOSER','A:TRACKS','A:PLAYLISTS'] with optionally ':search+query' behind it.
* @returns {Promise<BrowseResponse>}
* @memberof SonosDevice
*/
async BrowseWithDefaults(ObjectID) {
return this.Browse({ ObjectID: ObjectID, BrowseFlag: 'BrowseDirectChildren', Filter: '*', StartingIndex: 0, RequestedCount: 0, SortCriteria: '' });
}
/**
* Execute any sonos command by name, see examples/commands.js

@@ -203,2 +131,5 @@ *

const objectToCall = service !== '' ? this.executeCommandGetService(service) || this.executeCommandGetFunctions() : this.executeCommandGetFunctions();
if (objectToCall[command] === undefined) {
command = Object.keys(objectToCall).find(k => k.toLowerCase() === command.toLowerCase()) || command;
}
if (typeof (objectToCall[command]) === 'function') {

@@ -241,3 +172,3 @@ if (options === undefined) {

async GetFavoriteRadioShows() {
return this.BrowseWithDefaults('R:0/1');
return await this.ContentDirectoryService.BrowseParsedWithDefaults('R:0/1');
}

@@ -251,3 +182,3 @@ /**

async GetFavoriteRadioStations() {
return this.BrowseWithDefaults('R:0/0');
return await this.ContentDirectoryService.BrowseParsedWithDefaults('R:0/0');
}

@@ -261,3 +192,3 @@ /**

async GetFavorites() {
return this.BrowseWithDefaults('FV:2');
return await this.ContentDirectoryService.BrowseParsedWithDefaults('FV:2');
}

@@ -271,3 +202,3 @@ /**

async GetQueue() {
return this.BrowseWithDefaults('Q:0');
return await this.ContentDirectoryService.BrowseParsedWithDefaults('Q:0');
}

@@ -283,7 +214,7 @@ /**

this.debug('JoinGroup(%s)', otherDevice);
const zones = await this.ZoneGroupTopologyService.GetZoneGroupState().then(zone_helper_1.ZoneHelper.ParseZoneGroupStateResponse);
const zones = await this.ZoneGroupTopologyService.GetParsedZoneGroupState();
const groupToJoin = zones.find(z => z.members.some(m => m.name.toLowerCase() === otherDevice.toLowerCase()));
if (groupToJoin === undefined)
throw new Error(`Player '${otherDevice}' isn't found!`);
return this.AVTransportService.SetAVTransportURI({ InstanceID: 0, CurrentURI: `x-rincon:${groupToJoin.coordinator.uuid}`, CurrentURIMetaData: '' });
return await this.AVTransportService.SetAVTransportURI({ InstanceID: 0, CurrentURI: `x-rincon:${groupToJoin.coordinator.uuid}`, CurrentURIMetaData: '' });
}

@@ -298,11 +229,9 @@ /**

if (this.allMusicServices === undefined) {
return this.MusicServicesService.ListAvailableServices().then(resp => {
const musicServiceList = xml_helper_1.XmlHelper.DecodeAndParseXml(resp.AvailableServiceDescriptorList, '');
if (musicServiceList.Services && Array.isArray(musicServiceList.Services.Service)) {
this.allMusicServices = musicServiceList.Services.Service;
//this.debug('MusicList loaded %O', this.allMusicServices)
return this.allMusicServices;
}
throw new Error('Music list could not be downloaded');
});
const resp = await this.MusicServicesService.ListAvailableServices();
const musicServiceList = xml_helper_1.XmlHelper.DecodeAndParseXml(resp.AvailableServiceDescriptorList, '');
if (musicServiceList.Services && Array.isArray(musicServiceList.Services.Service)) {
this.allMusicServices = musicServiceList.Services.Service;
return this.allMusicServices;
}
throw new Error('Music list could not be downloaded');
}

@@ -321,13 +250,11 @@ return this.allMusicServices;

this.deviceId = (await this.SystemPropertiesService.GetString({ VariableName: 'R_TrialZPSerial' })).StringValue;
return this.MusicServicesList()
.then(services => {
if (services === undefined)
throw new Error('Music list could not be loaded');
const service = services.find(s => s.Name === name);
if (service === undefined)
throw new Error(`MusicService could not be found`);
if (service.Policy.Auth !== 'Anonymous')
throw new Error('Music service requires authentication, which isn\'t supported (yet)');
return new smapi_client_1.SmapiClient({ name: name, url: service.SecureUri || service.Uri, deviceId: this.deviceId, serviceId: service.Id });
});
const services = await this.MusicServicesList();
if (services === undefined)
throw new Error('Music list could not be loaded');
const service = services.find(s => s.Name === name);
if (service === undefined)
throw new Error(`MusicService could not be found`);
if (service.Policy.Auth !== 'Anonymous')
throw new Error('Music service requires authentication, which isn\'t supported (yet)');
return new smapi_client_1.SmapiClient({ name: name, url: service.SecureUri || service.Uri, deviceId: this.deviceId, serviceId: service.Id });
}

@@ -343,3 +270,3 @@ /**

this.debug('PlayNotification(%o)', options);
const originalState = await this.AVTransportService.GetTransportInfo().then(info => info.CurrentTransportState);
const originalState = (await this.AVTransportService.GetTransportInfo()).CurrentTransportState;
this.debug('Current state is %s', originalState);

@@ -357,3 +284,3 @@ if (options.onlyWhenPlaying === true && !(originalState === models_1.TransportState.Playing || originalState === models_1.TransportState.Transitioning)) {

// Original data to revert to
const originalVolume = options.volume !== undefined ? await this.RenderingControlService.GetVolume({ InstanceID: 0, Channel: 'Master' }).then(resp => resp.CurrentVolume) : undefined;
const originalVolume = options.volume !== undefined ? (await this.RenderingControlService.GetVolume({ InstanceID: 0, Channel: 'Master' })).CurrentVolume : undefined;
const originalMediaInfo = await this.AVTransportService.GetMediaInfo();

@@ -411,3 +338,3 @@ const originalPositionInfo = await this.AVTransportService.GetPositionInfo();

const uri = await tts_helper_1.TtsHelper.GetTtsUriFromEndpoint(options.endpoint, options.text, options.lang, options.gender);
return this.PlayNotification({ trackUri: uri, onlyWhenPlaying: options.onlyWhenPlaying, volume: options.volume, timeout: options.timeout || 120 });
return await this.PlayNotification({ trackUri: uri, onlyWhenPlaying: options.onlyWhenPlaying, volume: options.volume, timeout: options.timeout || 120 });
}

@@ -423,3 +350,3 @@ /**

const guessedMetaData = metadata_helper_1.MetadataHelper.GuessMetaDataAndTrackUri(trackUri);
return this.AVTransportService.SetAVTransportURI({ InstanceID: 0, CurrentURI: guessedMetaData.trackUri, CurrentURIMetaData: guessedMetaData.metedata });
return await this.AVTransportService.SetAVTransportURI({ InstanceID: 0, CurrentURI: guessedMetaData.trackUri, CurrentURIMetaData: guessedMetaData.metedata });
}

@@ -433,3 +360,3 @@ /**

async SwitchToLineIn() {
return this.AVTransportService
return await this.AVTransportService
.SetAVTransportURI({ InstanceID: 0, CurrentURI: `x-rincon-stream:${this.uuid}0${this.port}`, CurrentURIMetaData: '' });

@@ -444,3 +371,3 @@ }

async SwitchToQueue() {
return this.AVTransportService
return await this.AVTransportService
.SetAVTransportURI({ InstanceID: 0, CurrentURI: `x-rincon-queue:${this.uuid}0${this.port}#0`, CurrentURIMetaData: '' });

@@ -455,3 +382,3 @@ }

async SwitchToTV() {
return this.AVTransportService
return await this.AVTransportService
.SetAVTransportURI({ InstanceID: 0, CurrentURI: `x-sonos-htastream:${this.uuid}0${this.port}:spdiff`, CurrentURIMetaData: '' });

@@ -467,15 +394,6 @@ }

// Load the Current state first, if not present (eg. not using events)
if (this.Coordinator.CurrentTransportStateSimple === undefined) {
return this.Coordinator.AVTransportService.GetTransportInfo()
.then(response => response.CurrentTransportState)
.then(currentState => {
return currentState === models_1.TransportState.Playing || currentState === models_1.TransportState.Transitioning ?
this.AVTransportService.Pause() :
this.Coordinator.Play();
});
}
// Return to correct promise based on current state.
return this.Coordinator.CurrentTransportStateSimple === models_1.TransportState.Playing ?
this.Coordinator.Pause() :
this.Coordinator.Play();
const currentState = this.Coordinator.CurrentTransportStateSimple || (await this.Coordinator.AVTransportService.GetTransportInfo()).CurrentTransportState;
return currentState === models_1.TransportState.Playing || currentState === models_1.TransportState.Transitioning ?
await this.Coordinator.Pause() :
await this.Coordinator.Play();
}

@@ -730,5 +648,4 @@ /**

*/
GetNightMode() {
return this.RenderingControlService.GetEQ({ InstanceID: 0, EQType: 'NightMode' })
.then(resp => resp.CurrentValue === 1);
async GetNightMode() {
return (await this.RenderingControlService.GetEQ({ InstanceID: 0, EQType: 'NightMode' })).CurrentValue === 1;
}

@@ -741,5 +658,4 @@ /**

*/
GetSpeechEnhancement() {
return this.RenderingControlService.GetEQ({ InstanceID: 0, EQType: 'DialogLevel' })
.then(resp => resp.CurrentValue === 1);
async GetSpeechEnhancement() {
return (await this.RenderingControlService.GetEQ({ InstanceID: 0, EQType: 'DialogLevel' })).CurrentValue === 1;
}

@@ -752,8 +668,5 @@ /**

*/
GetZoneAttributes() {
return this.DevicePropertiesService.GetZoneAttributes()
.then(attr => {
this.zoneAttributes = attr;
return attr;
});
async GetZoneAttributes() {
this.zoneAttributes = await this.DevicePropertiesService.GetZoneAttributes();
return this.zoneAttributes;
}

@@ -766,3 +679,3 @@ /**

*/
GetZoneGroupState() { return this.ZoneGroupTopologyService.GetZoneGroupState(); }
async GetZoneGroupState() { return await this.ZoneGroupTopologyService.GetZoneGroupState(); }
/**

@@ -774,3 +687,3 @@ * GetZoneInfo shortcut to .DevicePropertiesService.GetZoneInfo()

*/
GetZoneInfo() { return this.DevicePropertiesService.GetZoneInfo(); }
async GetZoneInfo() { return await this.DevicePropertiesService.GetZoneInfo(); }
/**

@@ -782,3 +695,3 @@ * Play next song, shortcut to .Coordinator.AVTransportService.Next()

*/
Next() { return this.Coordinator.AVTransportService.Next(); }
async Next() { return await this.Coordinator.AVTransportService.Next(); }
/**

@@ -790,3 +703,3 @@ * Pause playback, shortcut to .Coordinator.AVTransportService.Pause()

*/
Pause() { return this.Coordinator.AVTransportService.Pause(); }
async Pause() { return await this.Coordinator.AVTransportService.Pause(); }
/**

@@ -798,3 +711,3 @@ * Start playing, shortcut to .Coordinator.AVTransportService.Play({InstanceID: 0, Speed: '1'})

*/
Play() { return this.Coordinator.AVTransportService.Play({ InstanceID: 0, Speed: '1' }); }
async Play() { return await this.Coordinator.AVTransportService.Play({ InstanceID: 0, Speed: '1' }); }
/**

@@ -806,3 +719,3 @@ * Play previous song, shortcut to .Coordinator.AVTransportService.Previous()

*/
Previous() { return this.Coordinator.AVTransportService.Previous(); }
async Previous() { return await this.Coordinator.AVTransportService.Previous(); }
/**

@@ -815,3 +728,3 @@ * Seek position in the current track, shortcut to .Coordinator.AVTransportService.Seek({InstanceID: 0, Unit: 'REL_TIME', Target: trackTime})

*/
SeekPosition(trackTime) { return this.Coordinator.AVTransportService.Seek({ InstanceID: 0, Unit: 'REL_TIME', Target: trackTime }); }
async SeekPosition(trackTime) { return await this.Coordinator.AVTransportService.Seek({ InstanceID: 0, Unit: 'REL_TIME', Target: trackTime }); }
/**

@@ -824,3 +737,3 @@ * Go to other track in queue, shortcut to .Coordinator.AVTransportService.Seek({InstanceID: 0, Unit: 'TRACK_NR', Target: trackNr.toString()})

*/
SeekTrack(trackNr) { return this.Coordinator.AVTransportService.Seek({ InstanceID: 0, Unit: 'TRACK_NR', Target: trackNr.toString() }); }
async SeekTrack(trackNr) { return await this.Coordinator.AVTransportService.Seek({ InstanceID: 0, Unit: 'TRACK_NR', Target: trackNr.toString() }); }
/**

@@ -833,4 +746,4 @@ * Turn on/off night mode, on your playbar.

*/
SetNightMode(nightmode) {
return this.RenderingControlService
async SetNightMode(nightmode) {
return await this.RenderingControlService
.SetEQ({ InstanceID: 0, EQType: 'NightMode', DesiredValue: nightmode === true ? 1 : 0 });

@@ -845,5 +758,4 @@ }

*/
SetRelativeVolume(volumeAdjustment) {
return this.RenderingControlService.SetRelativeVolume({ InstanceID: 0, Channel: 'Master', Adjustment: volumeAdjustment })
.then(resp => resp.NewVolume);
async SetRelativeVolume(volumeAdjustment) {
return (await this.RenderingControlService.SetRelativeVolume({ InstanceID: 0, Channel: 'Master', Adjustment: volumeAdjustment })).NewVolume;
}

@@ -858,4 +770,4 @@ /**

*/
SetSpeechEnhancement(dialogLevel) {
return this.RenderingControlService
async SetSpeechEnhancement(dialogLevel) {
return await this.RenderingControlService
.SetEQ({ InstanceID: 0, EQType: 'DialogLevel', DesiredValue: dialogLevel === true ? 1 : 0 });

@@ -870,6 +782,6 @@ }

*/
SetVolume(volume) {
async SetVolume(volume) {
if (volume < 0 || volume > 100)
throw new Error('Volume should be between 0 and 100');
return this.RenderingControlService.SetVolume({ InstanceID: 0, Channel: 'Master', DesiredVolume: volume });
return await this.RenderingControlService.SetVolume({ InstanceID: 0, Channel: 'Master', DesiredVolume: volume });
}

@@ -882,5 +794,5 @@ /**

*/
Stop() { return this.Coordinator.AVTransportService.Stop(); }
async Stop() { return await this.Coordinator.AVTransportService.Stop(); }
}
exports.SonosDevice = SonosDevice;
//# sourceMappingURL=sonos-device.js.map

@@ -30,2 +30,3 @@ import { SonosDevice } from './sonos-device';

InitializeWithDiscovery(timeoutInSeconds?: number): Promise<boolean>;
private Initialize;
private LoadAllGroups;

@@ -32,0 +33,0 @@ private InitializeDevices;

@@ -8,3 +8,2 @@ "use strict";

const events_1 = require("events");
const zone_helper_1 = require("./helpers/zone-helper");
/**

@@ -30,7 +29,5 @@ * The SonosManager will manage the logical devices for you. It will also manage group updates so be sure to call .Close on exit to remove open listeners.

*/
InitializeFromDevice(host, port = 1400) {
async InitializeFromDevice(host, port = 1400) {
this.zoneService = new services_1.ZoneGroupTopologyService(host, port);
return this.LoadAllGroups()
.then(groups => this.InitializeDevices(groups))
.then(success => this.SubscribeForGroupEvents(success));
return await this.Initialize();
}

@@ -44,20 +41,17 @@ /**

*/
InitializeWithDiscovery(timeoutInSeconds = 10) {
async InitializeWithDiscovery(timeoutInSeconds = 10) {
const deviceDiscovery = new sonos_device_discovery_1.SonosDeviceDiscovery();
return deviceDiscovery
.SearchOne(timeoutInSeconds)
.then(player => {
this.zoneService = new services_1.ZoneGroupTopologyService(player.host, player.port);
return this.LoadAllGroups()
.then(groups => this.InitializeDevices(groups))
.then(success => this.SubscribeForGroupEvents(success));
});
const player = await deviceDiscovery.SearchOne(timeoutInSeconds);
this.zoneService = new services_1.ZoneGroupTopologyService(player.host, player.port);
return await this.Initialize();
}
LoadAllGroups() {
async Initialize() {
const groups = await this.LoadAllGroups();
const success = this.InitializeDevices(groups);
return this.SubscribeForGroupEvents(success);
}
async LoadAllGroups() {
if (this.zoneService === undefined)
throw new Error('Manager is\'t initialized');
return this.zoneService.GetZoneGroupState()
.then(zoneGroupResponse => {
return zone_helper_1.ZoneHelper.ParseZoneGroupStateResponse(zoneGroupResponse);
});
return await this.zoneService.GetParsedZoneGroupState();
}

@@ -64,0 +58,0 @@ InitializeDevices(groups) {

{
"name": "@svrooij/sonos",
"version": "1.1.5",
"version": "1.2.0",
"description": "A node library to control your sonos devices, written in typescript",

@@ -17,3 +17,3 @@ "main": "lib/index.js",

"prepack": "npm run build",
"docs": "typedoc ./src"
"docs": "typedoc --tsconfig tsconfig.json --toc SonosDevice,SonosManager --theme minimal"
},

@@ -37,3 +37,3 @@ "repository": {

"devDependencies": {
"@types/chai": "^4.2.5",
"@types/chai": "^4.2.9",
"@types/debug": "^4.1.5",

@@ -45,14 +45,14 @@ "@types/mocha": "^5.2.7",

"chai": "^4.2.0",
"eslint": "^6.7.0",
"handlebars": "^4.5.3",
"eslint": "^6.8.0",
"handlebars": "^4.7.3",
"mocha": "^6.2.2",
"nyc": "^14.1.1",
"semantic-release": "^15.4.0",
"ts-node": "^8.5.2",
"typedoc": "^0.15.6",
"typescript": "^3.7.2"
"ts-node": "^8.6.2",
"typedoc": "^0.16.10",
"typescript": "^3.7.5"
},
"dependencies": {
"debug": "^4.1.1",
"fast-xml-parser": "^3.15.1",
"fast-xml-parser": "^3.16.0",
"guid-typescript": "^1.0.9",

@@ -59,0 +59,0 @@ "node-fetch": "^2.6.0"

@@ -9,4 +9,2 @@ # Node Sonos (the typescript version)

A node library to control a sonos device, written in Typescript. See [here](#improvements-over-node-sonos) why I've build it while there already is a sonos library written in node.

@@ -18,2 +16,4 @@

[![Documentation](./img/book.png)](https://svrooij.github.io/node-sonos-ts/#sonosmanager-and-logical-devices)
You'll need to get the [**SonosDevice**](https://svrooij.github.io/node-sonos-ts/classes/_sonos_device_.sonosdevice.html) by one of the [methods below](#sonosmanager-and-logical-devices), and start using the extra [functionality](#extra-functionality), the [shortcuts](#shortcuts) or the [exposed services](#exposed-services). There also is an [Eventlistener](#events) that allows you to subscribe to all the events your sonos sends out. This library allows you to do **everything** you can do with the Sonos application (except search external [music services](./src/musicservices) :cry:).

@@ -37,4 +37,4 @@

- **.AddUriToQueue('spotify:track:0GiWi4EkPduFWHQyhiKpRB')** - Add a track to be next track in the queue, metadata is guessed :musical_note:.
- **.AlarmList()** - List all your alarms :alarm_clock:
- **.AlarmPatch({ ID: 1, ... })** - Update some properties of one of your alarms :clock330:.
- **.AlarmClockService.ListAndParseAlarms()** - List all your alarms :alarm_clock:
- **.AlarmClockService.PatchAlarm({ ID: 1, ... })** - Update some properties of one of your alarms :clock330:.
- **.JoinGroup('Office')** - Join an other device by it's name. Will lookup the correct coordinator :speaker:.

@@ -75,4 +75,2 @@ - **.PlayNotification({})** - Play a single url and revert back to previous music source (playlist/radiostream) :bell:. See [play-notification.js](./examples/play-notification.js)

- **.Browse({...})** - Browse local content.
- **.BrowseWithDefaults({...})** - Browse local content by only specifying the ObjectID, the rest will be set to default.
- **.GetFavoriteRadioShows({...})** - Get your favorite radio shows

@@ -126,3 +124,3 @@ - **.GetFavoriteRadioStations({...})** - Get your favorite radio stations

This library has a [**SonosManager**](https://svrooij.github.io/node-sonos-ts/classes/_sonos_manager_.sonosmanager.html) that resolves all your sonos groups for you. It also manages group updates. Every **SonosDevice** created by this manager has some extra properties that can be used by your application. These properties will automatically be updated on changes.
This library has a [**SonosManager**](https://svrooij.github.io/node-sonos-ts/classes/_sonos_manager_.sonosmanager.html) that resolves all your sonos groups for you. It also manages group updates. Every [**SonosDevice**](https://svrooij.github.io/node-sonos-ts/classes/_sonos_device_.sonosdevice.html) created by this manager has some extra properties that can be used by your application. These properties will automatically be updated on changes.

@@ -129,0 +127,0 @@ - **.Coordinator** - Point to the devices' group coordinator (or to itself when it's the coordinator).

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc