@lox-audio-server/sonos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/esm/index.js +27 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/sonos/api/namespace.js +38 -0
- package/dist/esm/sonos/api/namespaces/audioClip.js +25 -0
- package/dist/esm/sonos/api/namespaces/groupVolume.js +25 -0
- package/dist/esm/sonos/api/namespaces/groups.js +31 -0
- package/dist/esm/sonos/api/namespaces/homeTheater.js +13 -0
- package/dist/esm/sonos/api/namespaces/playback.js +56 -0
- package/dist/esm/sonos/api/namespaces/playbackMetadata.js +16 -0
- package/dist/esm/sonos/api/namespaces/playbackSession.js +67 -0
- package/dist/esm/sonos/api/namespaces/playerVolume.js +41 -0
- package/dist/esm/sonos/api/websocket.js +310 -0
- package/dist/esm/sonos/client.js +193 -0
- package/dist/esm/sonos/constants.js +18 -0
- package/dist/esm/sonos/errors.js +49 -0
- package/dist/esm/sonos/group.js +231 -0
- package/dist/esm/sonos/models.js +63 -0
- package/dist/esm/sonos/player.js +112 -0
- package/dist/esm/sonos/types.js +46 -0
- package/dist/esm/sonos/utils.js +18 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +27 -0
- package/dist/sonos/api/namespace.d.ts +16 -0
- package/dist/sonos/api/namespace.js +38 -0
- package/dist/sonos/api/namespaces/audioClip.d.ts +16 -0
- package/dist/sonos/api/namespaces/audioClip.js +25 -0
- package/dist/sonos/api/namespaces/groupVolume.d.ts +10 -0
- package/dist/sonos/api/namespaces/groupVolume.js +25 -0
- package/dist/sonos/api/namespaces/groups.d.ts +10 -0
- package/dist/sonos/api/namespaces/groups.js +31 -0
- package/dist/sonos/api/namespaces/homeTheater.d.ts +5 -0
- package/dist/sonos/api/namespaces/homeTheater.js +13 -0
- package/dist/sonos/api/namespaces/playback.d.ts +17 -0
- package/dist/sonos/api/namespaces/playback.js +56 -0
- package/dist/sonos/api/namespaces/playbackMetadata.d.ts +7 -0
- package/dist/sonos/api/namespaces/playbackMetadata.js +16 -0
- package/dist/sonos/api/namespaces/playbackSession.d.ts +30 -0
- package/dist/sonos/api/namespaces/playbackSession.js +67 -0
- package/dist/sonos/api/namespaces/playerVolume.d.ts +12 -0
- package/dist/sonos/api/namespaces/playerVolume.js +41 -0
- package/dist/sonos/api/websocket.d.ts +51 -0
- package/dist/sonos/api/websocket.js +310 -0
- package/dist/sonos/client.d.ts +44 -0
- package/dist/sonos/client.js +193 -0
- package/dist/sonos/constants.d.ts +14 -0
- package/dist/sonos/constants.js +18 -0
- package/dist/sonos/errors.d.ts +25 -0
- package/dist/sonos/errors.js +49 -0
- package/dist/sonos/group.d.ts +58 -0
- package/dist/sonos/group.js +231 -0
- package/dist/sonos/models.d.ts +20 -0
- package/dist/sonos/models.js +63 -0
- package/dist/sonos/player.d.ts +33 -0
- package/dist/sonos/player.js +112 -0
- package/dist/sonos/types.d.ts +279 -0
- package/dist/sonos/types.js +46 -0
- package/dist/sonos/utils.d.ts +6 -0
- package/dist/sonos/utils.js +18 -0
- package/package.json +49 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SonosGroup = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const models_1 = require("./models");
|
|
8
|
+
class SonosGroup {
|
|
9
|
+
client;
|
|
10
|
+
data;
|
|
11
|
+
activeSessionId = null;
|
|
12
|
+
playbackStatus = null;
|
|
13
|
+
playbackMetadata = null;
|
|
14
|
+
playbackStatusUpdatedAt = 0;
|
|
15
|
+
volumeData = null;
|
|
16
|
+
playModes = new models_1.PlayModesWrapper({});
|
|
17
|
+
playbackActions = new models_1.PlaybackActionsWrapper({});
|
|
18
|
+
unsubscribeCallbacks = [];
|
|
19
|
+
constructor(client, data) {
|
|
20
|
+
this.client = client;
|
|
21
|
+
this.data = data;
|
|
22
|
+
}
|
|
23
|
+
async init() {
|
|
24
|
+
try {
|
|
25
|
+
this.volumeData = await this.client.api.groupVolume.getVolume(this.id);
|
|
26
|
+
this.playbackStatus = await this.client.api.playback.getPlaybackStatus(this.id);
|
|
27
|
+
this.playbackStatusUpdatedAt = Date.now();
|
|
28
|
+
this.playbackActions = new models_1.PlaybackActionsWrapper(this.playbackStatus.availablePlaybackActions ?? {});
|
|
29
|
+
this.playModes = new models_1.PlayModesWrapper(this.playbackStatus.playModes ?? {});
|
|
30
|
+
this.playbackMetadata = await this.client.api.playbackMetadata.getMetadataStatus(this.id);
|
|
31
|
+
this.unsubscribeCallbacks = [
|
|
32
|
+
await this.client.api.playback.subscribe(this.id, (data) => this.handlePlaybackStatusUpdate(data)),
|
|
33
|
+
await this.client.api.groupVolume.subscribe(this.id, (data) => this.handleVolumeUpdate(data)),
|
|
34
|
+
await this.client.api.playbackMetadata.subscribe(this.id, (data) => this.handleMetadataUpdate(data)),
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (err instanceof errors_1.FailedCommand && err.errorCode === 'groupCoordinatorChanged') {
|
|
39
|
+
this.volumeData = null;
|
|
40
|
+
this.playbackStatus = null;
|
|
41
|
+
this.playbackActions = new models_1.PlaybackActionsWrapper({});
|
|
42
|
+
this.playModes = new models_1.PlayModesWrapper({});
|
|
43
|
+
this.playbackMetadata = null;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
this.client.signalEvent({
|
|
49
|
+
eventType: constants_1.EventType.GROUP_ADDED,
|
|
50
|
+
objectId: this.id,
|
|
51
|
+
data: this,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
cleanup() {
|
|
55
|
+
for (const cb of this.unsubscribeCallbacks)
|
|
56
|
+
cb();
|
|
57
|
+
this.unsubscribeCallbacks = [];
|
|
58
|
+
}
|
|
59
|
+
get id() {
|
|
60
|
+
return this.data.id;
|
|
61
|
+
}
|
|
62
|
+
get name() {
|
|
63
|
+
return this.data.name;
|
|
64
|
+
}
|
|
65
|
+
get coordinatorId() {
|
|
66
|
+
return this.data.coordinatorId;
|
|
67
|
+
}
|
|
68
|
+
get playerIds() {
|
|
69
|
+
return this.data.playerIds;
|
|
70
|
+
}
|
|
71
|
+
get areaIds() {
|
|
72
|
+
return this.data.areaIds ?? [];
|
|
73
|
+
}
|
|
74
|
+
get playbackState() {
|
|
75
|
+
return (this.playbackStatus?.playbackState ??
|
|
76
|
+
this.data.playbackState ??
|
|
77
|
+
types_1.PlayBackState.IDLE);
|
|
78
|
+
}
|
|
79
|
+
get playbackMetadataStatus() {
|
|
80
|
+
return this.playbackMetadata;
|
|
81
|
+
}
|
|
82
|
+
get playbackActionsWrapper() {
|
|
83
|
+
return this.playbackActions;
|
|
84
|
+
}
|
|
85
|
+
get playModesWrapper() {
|
|
86
|
+
return this.playModes;
|
|
87
|
+
}
|
|
88
|
+
get positionSeconds() {
|
|
89
|
+
if (!this.playbackStatus)
|
|
90
|
+
return 0;
|
|
91
|
+
if (this.playbackState === types_1.PlayBackState.PLAYING) {
|
|
92
|
+
const elapsed = (Date.now() - this.playbackStatusUpdatedAt) / 1000;
|
|
93
|
+
return (this.playbackStatus.positionMillis ?? 0) / 1000 + elapsed;
|
|
94
|
+
}
|
|
95
|
+
return (this.playbackStatus.positionMillis ?? 0) / 1000;
|
|
96
|
+
}
|
|
97
|
+
get isDucking() {
|
|
98
|
+
return this.playbackStatus?.isDucking ?? false;
|
|
99
|
+
}
|
|
100
|
+
get activeService() {
|
|
101
|
+
return (0, models_1.normalizeMusicService)(this.playbackMetadata?.container?.id?.serviceId);
|
|
102
|
+
}
|
|
103
|
+
get containerType() {
|
|
104
|
+
return (0, models_1.normalizeContainerType)(this.playbackMetadata?.container?.type);
|
|
105
|
+
}
|
|
106
|
+
async play() {
|
|
107
|
+
await this.client.api.playback.play(this.id);
|
|
108
|
+
}
|
|
109
|
+
async pause() {
|
|
110
|
+
await this.client.api.playback.pause(this.id);
|
|
111
|
+
}
|
|
112
|
+
async stop() {
|
|
113
|
+
try {
|
|
114
|
+
if (this.activeSessionId) {
|
|
115
|
+
await this.client.api.playbackSession.suspend(this.activeSessionId);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
if (!(err instanceof errors_1.FailedCommand))
|
|
120
|
+
throw err;
|
|
121
|
+
try {
|
|
122
|
+
await this.playStreamUrl('clear');
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// ignore
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
this.activeSessionId = null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async togglePlayPause() {
|
|
133
|
+
await this.client.api.playback.togglePlayPause(this.id);
|
|
134
|
+
}
|
|
135
|
+
async skipToNextTrack() {
|
|
136
|
+
await this.client.api.playback.skipToNextTrack(this.id);
|
|
137
|
+
}
|
|
138
|
+
async skipToPreviousTrack() {
|
|
139
|
+
await this.client.api.playback.skipToPreviousTrack(this.id);
|
|
140
|
+
}
|
|
141
|
+
async setPlayModes(options) {
|
|
142
|
+
await this.client.api.playback.setPlayModes(this.id, options);
|
|
143
|
+
}
|
|
144
|
+
async seek(positionMillis) {
|
|
145
|
+
await this.client.api.playback.seek(this.id, positionMillis);
|
|
146
|
+
}
|
|
147
|
+
async seekRelative(deltaMillis) {
|
|
148
|
+
await this.client.api.playback.seekRelative(this.id, deltaMillis);
|
|
149
|
+
}
|
|
150
|
+
async loadLineIn(deviceId, playOnCompletion = false) {
|
|
151
|
+
await this.client.api.playback.loadLineIn(this.id, deviceId, playOnCompletion);
|
|
152
|
+
}
|
|
153
|
+
async modifyGroupMembers(playerIdsToAdd, playerIdsToRemove) {
|
|
154
|
+
await this.client.api.groups.modifyGroupMembers(this.id, playerIdsToAdd, playerIdsToRemove);
|
|
155
|
+
}
|
|
156
|
+
async setGroupMembers(playerIds, areaIds) {
|
|
157
|
+
await this.client.api.groups.setGroupMembers(this.id, playerIds, areaIds);
|
|
158
|
+
}
|
|
159
|
+
async createPlaybackSession(appId = 'com.lox.sonos.playback', appContext = '1', accountId, customData) {
|
|
160
|
+
const session = await this.client.api.playbackSession.createSession(this.id, appId, appContext, accountId, customData);
|
|
161
|
+
this.activeSessionId = session.sessionId;
|
|
162
|
+
return session;
|
|
163
|
+
}
|
|
164
|
+
async playStreamUrl(url, metadata) {
|
|
165
|
+
await this.ensureSession();
|
|
166
|
+
if (!this.activeSessionId)
|
|
167
|
+
throw new Error('No active session');
|
|
168
|
+
await this.client.api.playbackSession.loadStreamUrl(this.activeSessionId, url, {
|
|
169
|
+
playOnCompletion: true,
|
|
170
|
+
stationMetadata: metadata,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async playCloudQueue(queueBaseUrl, options) {
|
|
174
|
+
await this.ensureSession();
|
|
175
|
+
if (!this.activeSessionId)
|
|
176
|
+
throw new Error('No active session');
|
|
177
|
+
await this.client.api.playbackSession.loadCloudQueue(this.activeSessionId, queueBaseUrl, {
|
|
178
|
+
...options,
|
|
179
|
+
playOnCompletion: true,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
updateData(newData) {
|
|
183
|
+
let changed = false;
|
|
184
|
+
for (const [key, value] of Object.entries(newData)) {
|
|
185
|
+
if (this.data[key] !== value) {
|
|
186
|
+
this.data[key] = value;
|
|
187
|
+
changed = true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (changed) {
|
|
191
|
+
this.client.signalEvent({
|
|
192
|
+
eventType: constants_1.EventType.GROUP_UPDATED,
|
|
193
|
+
objectId: this.id,
|
|
194
|
+
data: this,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async ensureSession() {
|
|
199
|
+
if (!this.activeSessionId) {
|
|
200
|
+
await this.createPlaybackSession();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
handlePlaybackStatusUpdate(data) {
|
|
204
|
+
this.playbackStatus = data;
|
|
205
|
+
this.playbackStatusUpdatedAt = Date.now();
|
|
206
|
+
this.playbackActions = new models_1.PlaybackActionsWrapper(data.availablePlaybackActions ?? {});
|
|
207
|
+
this.playModes = new models_1.PlayModesWrapper(data.playModes ?? {});
|
|
208
|
+
this.client.signalEvent({
|
|
209
|
+
eventType: constants_1.EventType.GROUP_UPDATED,
|
|
210
|
+
objectId: this.id,
|
|
211
|
+
data: this,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
handleMetadataUpdate(data) {
|
|
215
|
+
this.playbackMetadata = data;
|
|
216
|
+
this.client.signalEvent({
|
|
217
|
+
eventType: constants_1.EventType.GROUP_UPDATED,
|
|
218
|
+
objectId: this.id,
|
|
219
|
+
data: this,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
handleVolumeUpdate(data) {
|
|
223
|
+
this.volumeData = data;
|
|
224
|
+
this.client.signalEvent({
|
|
225
|
+
eventType: constants_1.EventType.GROUP_UPDATED,
|
|
226
|
+
objectId: this.id,
|
|
227
|
+
data: this,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.SonosGroup = SonosGroup;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PlayModesWrapper = exports.PlaybackActionsWrapper = void 0;
|
|
4
|
+
exports.normalizeContainerType = normalizeContainerType;
|
|
5
|
+
exports.normalizeMusicService = normalizeMusicService;
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
class PlaybackActionsWrapper {
|
|
8
|
+
raw;
|
|
9
|
+
constructor(raw) {
|
|
10
|
+
this.raw = raw;
|
|
11
|
+
}
|
|
12
|
+
get canSkipForward() {
|
|
13
|
+
return this.raw.canSkipForward ?? this.raw.canSkip ?? false;
|
|
14
|
+
}
|
|
15
|
+
get canSkipBackward() {
|
|
16
|
+
return this.raw.canSkipBack ?? this.raw.canSkipToPrevious ?? false;
|
|
17
|
+
}
|
|
18
|
+
get canPlay() {
|
|
19
|
+
return this.raw.canPlay ?? false;
|
|
20
|
+
}
|
|
21
|
+
get canPause() {
|
|
22
|
+
return this.raw.canPause ?? false;
|
|
23
|
+
}
|
|
24
|
+
get canStop() {
|
|
25
|
+
return this.raw.canStop ?? false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.PlaybackActionsWrapper = PlaybackActionsWrapper;
|
|
29
|
+
class PlayModesWrapper {
|
|
30
|
+
raw;
|
|
31
|
+
constructor(raw) {
|
|
32
|
+
this.raw = raw;
|
|
33
|
+
}
|
|
34
|
+
get crossfade() {
|
|
35
|
+
return this.raw.crossfade;
|
|
36
|
+
}
|
|
37
|
+
get repeat() {
|
|
38
|
+
return this.raw.repeat;
|
|
39
|
+
}
|
|
40
|
+
get repeatOne() {
|
|
41
|
+
return this.raw.repeatOne;
|
|
42
|
+
}
|
|
43
|
+
get shuffle() {
|
|
44
|
+
return this.raw.shuffle;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.PlayModesWrapper = PlayModesWrapper;
|
|
48
|
+
function normalizeContainerType(containerType) {
|
|
49
|
+
if (!containerType)
|
|
50
|
+
return null;
|
|
51
|
+
if (Object.values(types_1.ContainerType).includes(containerType)) {
|
|
52
|
+
return containerType;
|
|
53
|
+
}
|
|
54
|
+
return containerType;
|
|
55
|
+
}
|
|
56
|
+
function normalizeMusicService(serviceId) {
|
|
57
|
+
if (!serviceId)
|
|
58
|
+
return null;
|
|
59
|
+
if (Object.values(types_1.MusicService).includes(serviceId)) {
|
|
60
|
+
return serviceId;
|
|
61
|
+
}
|
|
62
|
+
return serviceId;
|
|
63
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SonosPlayer = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
class SonosPlayer {
|
|
6
|
+
client;
|
|
7
|
+
data;
|
|
8
|
+
volumeData = null;
|
|
9
|
+
activeGroup = null;
|
|
10
|
+
constructor(client, data) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
this.data = data;
|
|
13
|
+
this.activeGroup = this.client.groups.find((g) => g.coordinatorId === this.id || g.playerIds.includes(this.id)) ?? null;
|
|
14
|
+
}
|
|
15
|
+
async init() {
|
|
16
|
+
this.volumeData = await this.client.api.playerVolume.getVolume(this.id);
|
|
17
|
+
await this.client.api.playerVolume.subscribe(this.id, (data) => this.handleVolumeUpdate(data));
|
|
18
|
+
}
|
|
19
|
+
get id() {
|
|
20
|
+
return this.data.id;
|
|
21
|
+
}
|
|
22
|
+
get name() {
|
|
23
|
+
return this.data.name;
|
|
24
|
+
}
|
|
25
|
+
get icon() {
|
|
26
|
+
return this.data.icon ?? '';
|
|
27
|
+
}
|
|
28
|
+
get volumeLevel() {
|
|
29
|
+
return this.volumeData?.volume;
|
|
30
|
+
}
|
|
31
|
+
get volumeMuted() {
|
|
32
|
+
return this.volumeData?.muted;
|
|
33
|
+
}
|
|
34
|
+
get hasFixedVolume() {
|
|
35
|
+
return this.volumeData?.fixed;
|
|
36
|
+
}
|
|
37
|
+
get group() {
|
|
38
|
+
return this.activeGroup;
|
|
39
|
+
}
|
|
40
|
+
get groupMembers() {
|
|
41
|
+
return this.group?.playerIds ?? [];
|
|
42
|
+
}
|
|
43
|
+
get isCoordinator() {
|
|
44
|
+
return Boolean(this.group && this.group.coordinatorId === this.id);
|
|
45
|
+
}
|
|
46
|
+
get isPassive() {
|
|
47
|
+
return Boolean(this.group && this.group.coordinatorId !== this.id);
|
|
48
|
+
}
|
|
49
|
+
async setVolume(volume, muted) {
|
|
50
|
+
await this.client.api.playerVolume.setVolume(this.id, volume, muted);
|
|
51
|
+
}
|
|
52
|
+
async duck(durationMillis) {
|
|
53
|
+
await this.client.api.playerVolume.duck(this.id, durationMillis);
|
|
54
|
+
}
|
|
55
|
+
async leaveGroup() {
|
|
56
|
+
if (!this.group)
|
|
57
|
+
return;
|
|
58
|
+
await this.client.api.groups.modifyGroupMembers(this.group.id, [], [this.id]);
|
|
59
|
+
}
|
|
60
|
+
async joinGroup(groupId) {
|
|
61
|
+
await this.client.api.groups.modifyGroupMembers(groupId, [this.id], []);
|
|
62
|
+
}
|
|
63
|
+
async playAudioClip(url, options = {}) {
|
|
64
|
+
await this.client.api.audioClip.loadAudioClip(this.id, {
|
|
65
|
+
name: options.name ?? 'lox-sonos',
|
|
66
|
+
appId: 'lox-sonos',
|
|
67
|
+
streamUrl: url,
|
|
68
|
+
volume: options.volume,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async loadHomeTheaterPlayback() {
|
|
72
|
+
await this.client.api.homeTheater.loadHomeTheaterPlayback(this.id);
|
|
73
|
+
}
|
|
74
|
+
updateData(newData) {
|
|
75
|
+
this.checkActiveGroup();
|
|
76
|
+
let changed = false;
|
|
77
|
+
for (const [key, value] of Object.entries(newData)) {
|
|
78
|
+
if (this.data[key] !== value) {
|
|
79
|
+
this.data[key] = value;
|
|
80
|
+
changed = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (changed) {
|
|
84
|
+
this.client.signalEvent({
|
|
85
|
+
eventType: constants_1.EventType.PLAYER_UPDATED,
|
|
86
|
+
objectId: this.id,
|
|
87
|
+
data: this,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
checkActiveGroup() {
|
|
92
|
+
const prevGroupId = this.activeGroup?.id;
|
|
93
|
+
this.activeGroup =
|
|
94
|
+
this.client.groups.find((g) => g.coordinatorId === this.id || g.playerIds.includes(this.id)) ?? null;
|
|
95
|
+
if (prevGroupId !== this.activeGroup?.id) {
|
|
96
|
+
this.client.signalEvent({
|
|
97
|
+
eventType: constants_1.EventType.PLAYER_UPDATED,
|
|
98
|
+
objectId: this.id,
|
|
99
|
+
data: this,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
handleVolumeUpdate(data) {
|
|
104
|
+
this.volumeData = data;
|
|
105
|
+
this.client.signalEvent({
|
|
106
|
+
eventType: constants_1.EventType.PLAYER_UPDATED,
|
|
107
|
+
objectId: this.id,
|
|
108
|
+
data: this,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.SonosPlayer = SonosPlayer;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContainerType = exports.MusicService = exports.SonosCapability = exports.PlayBackState = void 0;
|
|
4
|
+
var PlayBackState;
|
|
5
|
+
(function (PlayBackState) {
|
|
6
|
+
PlayBackState["IDLE"] = "PLAYBACK_STATE_IDLE";
|
|
7
|
+
PlayBackState["BUFFERING"] = "PLAYBACK_STATE_BUFFERING";
|
|
8
|
+
PlayBackState["PAUSED"] = "PLAYBACK_STATE_PAUSED";
|
|
9
|
+
PlayBackState["PLAYING"] = "PLAYBACK_STATE_PLAYING";
|
|
10
|
+
})(PlayBackState || (exports.PlayBackState = PlayBackState = {}));
|
|
11
|
+
var SonosCapability;
|
|
12
|
+
(function (SonosCapability) {
|
|
13
|
+
SonosCapability["CLOUD"] = "CLOUD";
|
|
14
|
+
SonosCapability["PLAYBACK"] = "PLAYBACK";
|
|
15
|
+
SonosCapability["AIRPLAY"] = "AIRPLAY";
|
|
16
|
+
SonosCapability["LINE_IN"] = "LINE_IN";
|
|
17
|
+
SonosCapability["VOICE"] = "VOICE";
|
|
18
|
+
SonosCapability["AUDIO_CLIP"] = "AUDIO_CLIP";
|
|
19
|
+
SonosCapability["MICROPHONE_SWITCH"] = "MICROPHONE_SWITCH";
|
|
20
|
+
SonosCapability["HT_PLAYBACK"] = "HT_PLAYBACK";
|
|
21
|
+
})(SonosCapability || (exports.SonosCapability = SonosCapability = {}));
|
|
22
|
+
var MusicService;
|
|
23
|
+
(function (MusicService) {
|
|
24
|
+
MusicService["SPOTIFY"] = "9";
|
|
25
|
+
MusicService["MUSIC_ASSISTANT"] = "mass";
|
|
26
|
+
MusicService["TUNEIN"] = "303";
|
|
27
|
+
MusicService["QOBUZ"] = "31";
|
|
28
|
+
MusicService["YOUTUBE_MUSIC"] = "284";
|
|
29
|
+
MusicService["LOCAL_LIBRARY"] = "local-library";
|
|
30
|
+
})(MusicService || (exports.MusicService = MusicService = {}));
|
|
31
|
+
var ContainerType;
|
|
32
|
+
(function (ContainerType) {
|
|
33
|
+
ContainerType["LINEIN"] = "linein";
|
|
34
|
+
ContainerType["STATION"] = "station";
|
|
35
|
+
ContainerType["PLAYLIST"] = "playlist";
|
|
36
|
+
ContainerType["AIRPLAY"] = "linein.airplay";
|
|
37
|
+
ContainerType["PODCAST"] = "podcast";
|
|
38
|
+
ContainerType["BOOK"] = "book";
|
|
39
|
+
ContainerType["ARTIST"] = "artist";
|
|
40
|
+
ContainerType["ALBUM"] = "album";
|
|
41
|
+
ContainerType["ARTIST_LOCAL"] = "artist.local";
|
|
42
|
+
ContainerType["ALBUM_LOCAL"] = "album.local";
|
|
43
|
+
ContainerType["HOME_THEATER_SPDIF"] = "linein.homeTheater.spdif";
|
|
44
|
+
ContainerType["HOME_THEATER_HDMI"] = "linein.homeTheater.hdmi";
|
|
45
|
+
// fallbacks for unknown types will be treated as strings
|
|
46
|
+
})(ContainerType || (exports.ContainerType = ContainerType = {}));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDiscoveryInfo = getDiscoveryInfo;
|
|
4
|
+
const undici_1 = require("undici");
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
async function getDiscoveryInfo(playerIp, options) {
|
|
7
|
+
const dispatcher = options?.dispatcher ?? new undici_1.Agent({ connect: { rejectUnauthorized: false } });
|
|
8
|
+
const resp = await (0, undici_1.fetch)(`https://${playerIp}:1443/api/v1/players/local/info`, {
|
|
9
|
+
method: 'GET',
|
|
10
|
+
headers: { 'X-Sonos-Api-Key': constants_1.LOCAL_API_TOKEN },
|
|
11
|
+
dispatcher,
|
|
12
|
+
});
|
|
13
|
+
if (!resp.ok) {
|
|
14
|
+
throw new Error(`Sonos discovery failed (${resp.status} ${resp.statusText})`);
|
|
15
|
+
}
|
|
16
|
+
const data = (await resp.json());
|
|
17
|
+
return data;
|
|
18
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { SonosClient, SonosClientOptions } from './sonos/client';
|
|
2
|
+
export { SonosGroup } from './sonos/group';
|
|
3
|
+
export { SonosPlayer } from './sonos/player';
|
|
4
|
+
export * from './sonos/models';
|
|
5
|
+
export * from './sonos/types';
|
|
6
|
+
export * from './sonos/constants';
|
|
7
|
+
export * from './sonos/errors';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.SonosPlayer = exports.SonosGroup = exports.SonosClient = void 0;
|
|
18
|
+
var client_1 = require("./sonos/client");
|
|
19
|
+
Object.defineProperty(exports, "SonosClient", { enumerable: true, get: function () { return client_1.SonosClient; } });
|
|
20
|
+
var group_1 = require("./sonos/group");
|
|
21
|
+
Object.defineProperty(exports, "SonosGroup", { enumerable: true, get: function () { return group_1.SonosGroup; } });
|
|
22
|
+
var player_1 = require("./sonos/player");
|
|
23
|
+
Object.defineProperty(exports, "SonosPlayer", { enumerable: true, get: function () { return player_1.SonosPlayer; } });
|
|
24
|
+
__exportStar(require("./sonos/models"), exports);
|
|
25
|
+
__exportStar(require("./sonos/types"), exports);
|
|
26
|
+
__exportStar(require("./sonos/constants"), exports);
|
|
27
|
+
__exportStar(require("./sonos/errors"), exports);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SonosWebSocketApi } from './websocket';
|
|
2
|
+
export type SubscribeCallback<T> = (payload: T) => void;
|
|
3
|
+
export type UnsubscribeCallback = () => void;
|
|
4
|
+
/**
|
|
5
|
+
* Base helper for namespace handlers.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SonosNamespace<TEventPayload> {
|
|
8
|
+
protected api: SonosWebSocketApi;
|
|
9
|
+
protected namespace: string;
|
|
10
|
+
protected eventKey: string;
|
|
11
|
+
protected listeners: Map<string, SubscribeCallback<TEventPayload>>;
|
|
12
|
+
readonly eventType: string;
|
|
13
|
+
constructor(api: SonosWebSocketApi, namespace: string, eventType: string, eventKey: string);
|
|
14
|
+
protected handleSubscribe(id: string, callback: SubscribeCallback<TEventPayload>): Promise<UnsubscribeCallback>;
|
|
15
|
+
handleEvent(event: Record<string, unknown>, eventData: TEventPayload): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SonosNamespace = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base helper for namespace handlers.
|
|
6
|
+
*/
|
|
7
|
+
class SonosNamespace {
|
|
8
|
+
api;
|
|
9
|
+
namespace;
|
|
10
|
+
eventKey;
|
|
11
|
+
listeners = new Map();
|
|
12
|
+
eventType;
|
|
13
|
+
constructor(api, namespace, eventType, eventKey) {
|
|
14
|
+
this.api = api;
|
|
15
|
+
this.namespace = namespace;
|
|
16
|
+
this.eventKey = eventKey;
|
|
17
|
+
this.eventType = eventType;
|
|
18
|
+
}
|
|
19
|
+
async handleSubscribe(id, callback) {
|
|
20
|
+
if (this.listeners.has(id)) {
|
|
21
|
+
this.api.logger.error(`Duplicate subscription detected for ${id}`);
|
|
22
|
+
}
|
|
23
|
+
await this.api.sendCommand(this.namespace, 'subscribe', undefined, { [this.eventKey]: id });
|
|
24
|
+
this.listeners.set(id, callback);
|
|
25
|
+
return () => {
|
|
26
|
+
this.listeners.delete(id);
|
|
27
|
+
this.api.sendCommandNoWait(this.namespace, 'unsubscribe', undefined, { [this.eventKey]: id });
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
handleEvent(event, eventData) {
|
|
31
|
+
const id = event[this.eventKey];
|
|
32
|
+
if (typeof id === 'string') {
|
|
33
|
+
const handler = this.listeners.get(id);
|
|
34
|
+
handler?.(eventData);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.SonosNamespace = SonosNamespace;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SonosNamespace, SubscribeCallback, UnsubscribeCallback } from '../namespace';
|
|
2
|
+
import { AudioClipStatusEvent } from '../../types';
|
|
3
|
+
export declare class AudioClipNamespace extends SonosNamespace<AudioClipStatusEvent> {
|
|
4
|
+
constructor(api: any);
|
|
5
|
+
loadAudioClip(playerId: string, options: {
|
|
6
|
+
name: string;
|
|
7
|
+
appId: string;
|
|
8
|
+
streamUrl?: string;
|
|
9
|
+
volume?: number;
|
|
10
|
+
priority?: string;
|
|
11
|
+
clipType?: string;
|
|
12
|
+
httpAuthorization?: string;
|
|
13
|
+
clipLEDBehavior?: string;
|
|
14
|
+
}): Promise<any>;
|
|
15
|
+
subscribe(playerId: string, callback: SubscribeCallback<AudioClipStatusEvent>): Promise<UnsubscribeCallback>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AudioClipNamespace = void 0;
|
|
4
|
+
const namespace_1 = require("../namespace");
|
|
5
|
+
class AudioClipNamespace extends namespace_1.SonosNamespace {
|
|
6
|
+
constructor(api) {
|
|
7
|
+
super(api, 'audioClip', 'audioClip', 'playerId');
|
|
8
|
+
}
|
|
9
|
+
async loadAudioClip(playerId, options) {
|
|
10
|
+
return this.api.sendCommand(this.namespace, 'loadAudioClip', {
|
|
11
|
+
name: options.name,
|
|
12
|
+
appId: options.appId,
|
|
13
|
+
priority: options.priority,
|
|
14
|
+
clipType: options.clipType,
|
|
15
|
+
streamUrl: options.streamUrl,
|
|
16
|
+
httpAuthorization: options.httpAuthorization,
|
|
17
|
+
volume: options.volume,
|
|
18
|
+
clipLEDBehavior: options.clipLEDBehavior,
|
|
19
|
+
}, { playerId });
|
|
20
|
+
}
|
|
21
|
+
async subscribe(playerId, callback) {
|
|
22
|
+
return this.handleSubscribe(playerId, callback);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.AudioClipNamespace = AudioClipNamespace;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GroupVolume } from '../../types';
|
|
2
|
+
import { SonosNamespace, SubscribeCallback, UnsubscribeCallback } from '../namespace';
|
|
3
|
+
export declare class GroupVolumeNamespace extends SonosNamespace<GroupVolume> {
|
|
4
|
+
constructor(api: any);
|
|
5
|
+
setVolume(groupId: string, volume: number): Promise<void>;
|
|
6
|
+
getVolume(groupId: string): Promise<GroupVolume>;
|
|
7
|
+
setMute(groupId: string, muted: boolean): Promise<void>;
|
|
8
|
+
setRelativeVolume(groupId: string, volumeDelta?: number): Promise<void>;
|
|
9
|
+
subscribe(groupId: string, callback: SubscribeCallback<GroupVolume>): Promise<UnsubscribeCallback>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GroupVolumeNamespace = void 0;
|
|
4
|
+
const namespace_1 = require("../namespace");
|
|
5
|
+
class GroupVolumeNamespace extends namespace_1.SonosNamespace {
|
|
6
|
+
constructor(api) {
|
|
7
|
+
super(api, 'groupVolume', 'groupVolume', 'groupId');
|
|
8
|
+
}
|
|
9
|
+
async setVolume(groupId, volume) {
|
|
10
|
+
await this.api.sendCommand(this.namespace, 'setVolume', { volume }, { groupId });
|
|
11
|
+
}
|
|
12
|
+
async getVolume(groupId) {
|
|
13
|
+
return this.api.sendCommand(this.namespace, 'getVolume', undefined, { groupId });
|
|
14
|
+
}
|
|
15
|
+
async setMute(groupId, muted) {
|
|
16
|
+
await this.api.sendCommand(this.namespace, 'setMute', { muted }, { groupId });
|
|
17
|
+
}
|
|
18
|
+
async setRelativeVolume(groupId, volumeDelta) {
|
|
19
|
+
await this.api.sendCommand(this.namespace, 'setRelativeVolume', { volumeDelta }, { groupId });
|
|
20
|
+
}
|
|
21
|
+
async subscribe(groupId, callback) {
|
|
22
|
+
return this.handleSubscribe(groupId, callback);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.GroupVolumeNamespace = GroupVolumeNamespace;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GroupInfo, Groups } from '../../types';
|
|
2
|
+
import { SonosNamespace, SubscribeCallback, UnsubscribeCallback } from '../namespace';
|
|
3
|
+
export declare class GroupsNamespace extends SonosNamespace<Groups> {
|
|
4
|
+
constructor(api: any);
|
|
5
|
+
modifyGroupMembers(groupId: string, playerIdsToAdd: string[], playerIdsToRemove: string[]): Promise<GroupInfo>;
|
|
6
|
+
setGroupMembers(groupId: string, playerIds: string[], areaIds?: string[]): Promise<GroupInfo>;
|
|
7
|
+
getGroups(householdId: string, includeDeviceInfo?: boolean): Promise<Groups>;
|
|
8
|
+
createGroup(householdId: string, playerIds: string[], musicContextGroupId?: string): Promise<GroupInfo>;
|
|
9
|
+
subscribe(householdId: string, callback: SubscribeCallback<Groups>): Promise<UnsubscribeCallback>;
|
|
10
|
+
}
|