@svrooij/sonos
Advanced tools
Comparing version 1.1.5 to 1.2.0
@@ -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). |
263057
6006
361
Updatedfast-xml-parser@^3.16.0