bose-soundtouch 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/LICENSE +21 -0
- package/README.md +326 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +36 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +25 -0
- package/dist/soundtouch.d.ts +134 -0
- package/dist/soundtouch.js +322 -0
- package/dist/types.d.ts +113 -0
- package/dist/types.js +39 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Paul Grant
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# bose-soundtouch
|
|
2
|
+
|
|
3
|
+
A JavaScript/TypeScript library for controlling Bose SoundTouch speakers via the local REST API.
|
|
4
|
+
|
|
5
|
+
## Background
|
|
6
|
+
|
|
7
|
+
On January 7, 2026, Bose announced that cloud support for SoundTouch products will end on May 6, 2026. After this date:
|
|
8
|
+
|
|
9
|
+
**What will continue to work:**
|
|
10
|
+
|
|
11
|
+
- Streaming via Bluetooth, AirPlay, Spotify Connect, and AUX
|
|
12
|
+
- Setting up and configuring your system
|
|
13
|
+
- Remote control features (play, pause, skip, volume)
|
|
14
|
+
- Grouping multiple speakers together
|
|
15
|
+
|
|
16
|
+
**What will stop working:**
|
|
17
|
+
|
|
18
|
+
- Presets (preset buttons and app presets)
|
|
19
|
+
- Browsing music services from the SoundTouch app
|
|
20
|
+
|
|
21
|
+
As part of this transition, Bose released their SoundTouch API Documentation to enable independent developers to create their own SoundTouch-compatible tools.
|
|
22
|
+
|
|
23
|
+
This library provides a clean, TypeScript-friendly interface to control SoundTouch speakers over your local network, ensuring your speakers remain fully functional even after cloud services end.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install bose-soundtouch
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
or
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
yarn add bose-soundtouch
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { SoundTouch } from "bose-soundtouch";
|
|
41
|
+
|
|
42
|
+
// Connect to a speaker by IP address
|
|
43
|
+
const speaker = new SoundTouch("192.168.1.100");
|
|
44
|
+
|
|
45
|
+
// Get device info
|
|
46
|
+
const info = await speaker.getInfo();
|
|
47
|
+
console.log(`Connected to: ${info.name} (${info.type})`);
|
|
48
|
+
|
|
49
|
+
// Control playback
|
|
50
|
+
await speaker.play();
|
|
51
|
+
await speaker.setVolume(30);
|
|
52
|
+
|
|
53
|
+
// Check what's playing
|
|
54
|
+
const nowPlaying = await speaker.getNowPlaying();
|
|
55
|
+
console.log(`Playing: ${nowPlaying.track} by ${nowPlaying.artist}`);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Demo Script
|
|
59
|
+
|
|
60
|
+
A comprehensive demo script is included to test the library against a real device:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run demo 192.168.1.100
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
or
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
ts-node demo.ts 192.168.1.100
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The demo walks through device info, capabilities, sources, presets, volume control, mute, and playback controls.
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- Control playback (play, pause, stop, next/previous track)
|
|
77
|
+
- Adjust volume and mute
|
|
78
|
+
- Select presets (1-6)
|
|
79
|
+
- Select sources (AUX, Bluetooth, etc.)
|
|
80
|
+
- Get now playing information
|
|
81
|
+
- Control bass and tone settings
|
|
82
|
+
- Full TypeScript support with type definitions
|
|
83
|
+
- Error handling with custom error classes
|
|
84
|
+
|
|
85
|
+
## Usage Examples
|
|
86
|
+
|
|
87
|
+
### Playback Control
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { SoundTouch } from "bose-soundtouch";
|
|
91
|
+
|
|
92
|
+
const speaker = new SoundTouch("192.168.1.100");
|
|
93
|
+
|
|
94
|
+
await speaker.play();
|
|
95
|
+
await speaker.pause();
|
|
96
|
+
await speaker.playPause(); // Toggle
|
|
97
|
+
await speaker.stop();
|
|
98
|
+
await speaker.nextTrack();
|
|
99
|
+
await speaker.previousTrack();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Volume Control
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { SoundTouch } from "bose-soundtouch";
|
|
106
|
+
|
|
107
|
+
const speaker = new SoundTouch("192.168.1.100");
|
|
108
|
+
|
|
109
|
+
// Get current volume
|
|
110
|
+
const volume = await speaker.getVolume();
|
|
111
|
+
console.log(`Volume: ${volume.actualvolume}, Muted: ${volume.muteenabled}`);
|
|
112
|
+
|
|
113
|
+
// Set volume (0-100)
|
|
114
|
+
await speaker.setVolume(50);
|
|
115
|
+
|
|
116
|
+
// Mute/unmute
|
|
117
|
+
await speaker.mute();
|
|
118
|
+
await speaker.unmute();
|
|
119
|
+
|
|
120
|
+
// Volume up/down buttons
|
|
121
|
+
await speaker.volumeUp();
|
|
122
|
+
await speaker.volumeDown();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Presets
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { SoundTouch } from "bose-soundtouch";
|
|
129
|
+
|
|
130
|
+
const speaker = new SoundTouch("192.168.1.100");
|
|
131
|
+
|
|
132
|
+
// Get all presets
|
|
133
|
+
const presets = await speaker.getPresets();
|
|
134
|
+
for (const preset of presets.preset) {
|
|
135
|
+
if (preset.contentItem) {
|
|
136
|
+
console.log(`Preset ${preset.id}: ${preset.contentItem.itemName}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Select a preset
|
|
141
|
+
await speaker.selectPreset(1);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Now Playing
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { SoundTouch, PlayStatus } from "bose-soundtouch";
|
|
148
|
+
|
|
149
|
+
const speaker = new SoundTouch("192.168.1.100");
|
|
150
|
+
|
|
151
|
+
const now = await speaker.getNowPlaying();
|
|
152
|
+
|
|
153
|
+
console.log(`Source: ${now.source}`);
|
|
154
|
+
console.log(`Track: ${now.track}`);
|
|
155
|
+
console.log(`Artist: ${now.artist}`);
|
|
156
|
+
console.log(`Album: ${now.album}`);
|
|
157
|
+
|
|
158
|
+
if (now.playStatus === PlayStatus.PLAY_STATE) {
|
|
159
|
+
console.log("Currently playing");
|
|
160
|
+
} else if (now.playStatus === PlayStatus.PAUSE_STATE) {
|
|
161
|
+
console.log("Paused");
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Source Selection
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { SoundTouch } from "bose-soundtouch";
|
|
169
|
+
|
|
170
|
+
const speaker = new SoundTouch("192.168.1.100");
|
|
171
|
+
|
|
172
|
+
// List available sources
|
|
173
|
+
const sources = await speaker.getSources();
|
|
174
|
+
for (const source of sources.sourceItem) {
|
|
175
|
+
console.log(`${source.source}: ${source.status}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Select a source
|
|
179
|
+
await speaker.selectSource("AUX", "AUX");
|
|
180
|
+
await speaker.selectSource("BLUETOOTH");
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Device Information
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { SoundTouch } from "bose-soundtouch";
|
|
187
|
+
|
|
188
|
+
const speaker = new SoundTouch("192.168.1.100");
|
|
189
|
+
|
|
190
|
+
const info = await speaker.getInfo();
|
|
191
|
+
|
|
192
|
+
console.log(`Name: ${info.name}`);
|
|
193
|
+
console.log(`Type: ${info.type}`);
|
|
194
|
+
console.log(`Device ID: ${info.deviceID}`);
|
|
195
|
+
|
|
196
|
+
for (const net of info.networkInfo) {
|
|
197
|
+
console.log(` ${net.type}: ${net.ipAddress}`);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Raw Key Press
|
|
202
|
+
|
|
203
|
+
For advanced use cases, you can send raw key presses:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { SoundTouch, KeyValue } from "bose-soundtouch";
|
|
207
|
+
|
|
208
|
+
const speaker = new SoundTouch("192.168.1.100");
|
|
209
|
+
|
|
210
|
+
// Using enum
|
|
211
|
+
await speaker.sendKey(KeyValue.THUMBS_UP);
|
|
212
|
+
|
|
213
|
+
// Using string
|
|
214
|
+
await speaker.sendKey("POWER");
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Error Handling
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import {
|
|
221
|
+
SoundTouch,
|
|
222
|
+
ConnectionError,
|
|
223
|
+
TimeoutError,
|
|
224
|
+
ApiError,
|
|
225
|
+
} from "bose-soundtouch";
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const speaker = new SoundTouch("192.168.1.100", 8090, 5.0);
|
|
229
|
+
await speaker.setVolume(50);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (error instanceof ConnectionError) {
|
|
232
|
+
console.error("Could not connect to speaker");
|
|
233
|
+
} else if (error instanceof TimeoutError) {
|
|
234
|
+
console.error("Request timed out");
|
|
235
|
+
} else if (error instanceof ApiError) {
|
|
236
|
+
console.error(`API error: ${error.errorName} (code ${error.errorCode})`);
|
|
237
|
+
} else {
|
|
238
|
+
console.error("Unknown error:", error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## API Reference
|
|
244
|
+
|
|
245
|
+
### SoundTouch Class
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
new SoundTouch(
|
|
249
|
+
host: string, // IP address or hostname
|
|
250
|
+
port?: number, // HTTP port (default: 8090)
|
|
251
|
+
timeout?: number // Request timeout in seconds (default: 10.0)
|
|
252
|
+
)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Methods
|
|
256
|
+
|
|
257
|
+
| Method | Description |
|
|
258
|
+
| ---------------------------------- | -------------------------- |
|
|
259
|
+
| `getInfo()` | Get device information |
|
|
260
|
+
| `getCapabilities()` | Get device capabilities |
|
|
261
|
+
| `setName(name: string)` | Set device name |
|
|
262
|
+
| `getNowPlaying()` | Get current playback state |
|
|
263
|
+
| `getSources()` | Get available sources |
|
|
264
|
+
| `selectSource(source, account?)` | Select a source |
|
|
265
|
+
| `getVolume()` | Get volume state |
|
|
266
|
+
| `setVolume(level: number)` | Set volume (0-100) |
|
|
267
|
+
| `mute()` / `unmute()` | Mute/unmute |
|
|
268
|
+
| `volumeUp()` / `volumeDown()` | Adjust volume |
|
|
269
|
+
| `getPresets()` | Get preset slots |
|
|
270
|
+
| `selectPreset(presetId: number)` | Select preset (1-6) |
|
|
271
|
+
| `play()` / `pause()` / `stop()` | Playback control |
|
|
272
|
+
| `playPause()` | Toggle play/pause |
|
|
273
|
+
| `nextTrack()` / `previousTrack()` | Track navigation |
|
|
274
|
+
| `getBass()` / `setBass(level)` | Bass control |
|
|
275
|
+
| `getTone()` | Get tone settings |
|
|
276
|
+
| `sendKey(key: KeyValue \| string)` | Send raw key press |
|
|
277
|
+
|
|
278
|
+
### Types
|
|
279
|
+
|
|
280
|
+
The library exports TypeScript types and enums:
|
|
281
|
+
|
|
282
|
+
- `DeviceInfo` - Device information structure
|
|
283
|
+
- `Capabilities` - Device capabilities
|
|
284
|
+
- `Presets` - Preset slots
|
|
285
|
+
- `Sources` - Available sources
|
|
286
|
+
- `Volume` - Volume state
|
|
287
|
+
- `NowPlaying` - Current playback information
|
|
288
|
+
- `Bass` - Bass settings
|
|
289
|
+
- `Tone` - Tone settings (bass and treble)
|
|
290
|
+
- `PlayStatus` - Playback status enum
|
|
291
|
+
- `KeyValue` - Key press enum
|
|
292
|
+
|
|
293
|
+
### Error Classes
|
|
294
|
+
|
|
295
|
+
- `ConnectionError` - Network connection errors
|
|
296
|
+
- `TimeoutError` - Request timeout errors
|
|
297
|
+
- `ApiError` - API error responses
|
|
298
|
+
|
|
299
|
+
## Requirements
|
|
300
|
+
|
|
301
|
+
- Node.js 18.0.0 or higher
|
|
302
|
+
- TypeScript 5.0+ (for TypeScript projects)
|
|
303
|
+
|
|
304
|
+
## Development
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
# Install dependencies
|
|
308
|
+
npm install
|
|
309
|
+
|
|
310
|
+
# Build the project
|
|
311
|
+
npm run build
|
|
312
|
+
|
|
313
|
+
# Run in watch mode
|
|
314
|
+
npm run dev
|
|
315
|
+
|
|
316
|
+
# Run demo
|
|
317
|
+
npm run demo <ip-address>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
MIT License
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
Not affiliated with, endorsed, sponsored, or approved by Bose. Bose and SoundTouch are trademarks of Bose Corporation.
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for SoundTouch API
|
|
3
|
+
*/
|
|
4
|
+
export declare class ConnectionError extends Error {
|
|
5
|
+
readonly cause?: Error | undefined;
|
|
6
|
+
constructor(message: string, cause?: Error | undefined);
|
|
7
|
+
}
|
|
8
|
+
export declare class TimeoutError extends Error {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
export interface ApiErrorResponse {
|
|
12
|
+
error: {
|
|
13
|
+
name: string;
|
|
14
|
+
code: number;
|
|
15
|
+
message?: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare class ApiError extends Error {
|
|
19
|
+
readonly errorName: string;
|
|
20
|
+
readonly errorCode: number;
|
|
21
|
+
constructor(errorName: string, errorCode: number, message?: string);
|
|
22
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Custom error classes for SoundTouch API
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ApiError = exports.TimeoutError = exports.ConnectionError = void 0;
|
|
7
|
+
class ConnectionError extends Error {
|
|
8
|
+
cause;
|
|
9
|
+
constructor(message, cause) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
this.name = "ConnectionError";
|
|
13
|
+
Object.setPrototypeOf(this, ConnectionError.prototype);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.ConnectionError = ConnectionError;
|
|
17
|
+
class TimeoutError extends Error {
|
|
18
|
+
constructor(message = "Request timed out") {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "TimeoutError";
|
|
21
|
+
Object.setPrototypeOf(this, TimeoutError.prototype);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.TimeoutError = TimeoutError;
|
|
25
|
+
class ApiError extends Error {
|
|
26
|
+
errorName;
|
|
27
|
+
errorCode;
|
|
28
|
+
constructor(errorName, errorCode, message) {
|
|
29
|
+
super(message || `API error: ${errorName} (code ${errorCode})`);
|
|
30
|
+
this.errorName = errorName;
|
|
31
|
+
this.errorCode = errorCode;
|
|
32
|
+
this.name = "ApiError";
|
|
33
|
+
Object.setPrototypeOf(this, ApiError.prototype);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.ApiError = ApiError;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* bose-soundtouch
|
|
4
|
+
* A TypeScript library for controlling Bose SoundTouch speakers
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.SoundTouch = void 0;
|
|
22
|
+
var soundtouch_1 = require("./soundtouch");
|
|
23
|
+
Object.defineProperty(exports, "SoundTouch", { enumerable: true, get: function () { return soundtouch_1.SoundTouch; } });
|
|
24
|
+
__exportStar(require("./types"), exports);
|
|
25
|
+
__exportStar(require("./errors"), exports);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { DeviceInfo, Capabilities, Presets, Sources, Volume, NowPlaying, Bass, Tone, KeyValue } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Main class for controlling Bose SoundTouch speakers
|
|
4
|
+
*/
|
|
5
|
+
export declare class SoundTouch {
|
|
6
|
+
private host;
|
|
7
|
+
private port;
|
|
8
|
+
private timeout;
|
|
9
|
+
private client;
|
|
10
|
+
private baseUrl;
|
|
11
|
+
/**
|
|
12
|
+
* Create a new SoundTouch instance
|
|
13
|
+
* @param host - IP address or hostname of the speaker
|
|
14
|
+
* @param port - HTTP port (default: 8090)
|
|
15
|
+
* @param timeout - Request timeout in seconds (default: 10.0)
|
|
16
|
+
*/
|
|
17
|
+
constructor(host: string, port?: number, timeout?: number);
|
|
18
|
+
/**
|
|
19
|
+
* Handle errors and convert to appropriate error types
|
|
20
|
+
*/
|
|
21
|
+
private handleError;
|
|
22
|
+
/**
|
|
23
|
+
* Parse XML string to JavaScript object
|
|
24
|
+
*/
|
|
25
|
+
private parseXml;
|
|
26
|
+
/**
|
|
27
|
+
* Make a GET request and parse XML response
|
|
28
|
+
*/
|
|
29
|
+
private get;
|
|
30
|
+
/**
|
|
31
|
+
* Make a POST request with XML body
|
|
32
|
+
*/
|
|
33
|
+
private post;
|
|
34
|
+
/**
|
|
35
|
+
* Get device information
|
|
36
|
+
*/
|
|
37
|
+
getInfo(): Promise<DeviceInfo>;
|
|
38
|
+
/**
|
|
39
|
+
* Get device capabilities
|
|
40
|
+
*/
|
|
41
|
+
getCapabilities(): Promise<Capabilities>;
|
|
42
|
+
/**
|
|
43
|
+
* Set device name
|
|
44
|
+
*/
|
|
45
|
+
setName(name: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Get current playback state
|
|
48
|
+
*/
|
|
49
|
+
getNowPlaying(): Promise<NowPlaying>;
|
|
50
|
+
/**
|
|
51
|
+
* Get available sources
|
|
52
|
+
*/
|
|
53
|
+
getSources(): Promise<Sources>;
|
|
54
|
+
/**
|
|
55
|
+
* Select a source
|
|
56
|
+
*/
|
|
57
|
+
selectSource(source: string, sourceAccount?: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Get volume state
|
|
60
|
+
*/
|
|
61
|
+
getVolume(): Promise<Volume>;
|
|
62
|
+
/**
|
|
63
|
+
* Set volume level (0-100)
|
|
64
|
+
*/
|
|
65
|
+
setVolume(level: number): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Mute the speaker
|
|
68
|
+
*/
|
|
69
|
+
mute(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Unmute the speaker
|
|
72
|
+
*/
|
|
73
|
+
unmute(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Increase volume
|
|
76
|
+
*/
|
|
77
|
+
volumeUp(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Decrease volume
|
|
80
|
+
*/
|
|
81
|
+
volumeDown(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Get presets
|
|
84
|
+
*/
|
|
85
|
+
getPresets(): Promise<Presets>;
|
|
86
|
+
/**
|
|
87
|
+
* Select a preset (1-6)
|
|
88
|
+
*/
|
|
89
|
+
selectPreset(presetId: number): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Play
|
|
92
|
+
*/
|
|
93
|
+
play(): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Pause
|
|
96
|
+
*/
|
|
97
|
+
pause(): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Toggle play/pause
|
|
100
|
+
*/
|
|
101
|
+
playPause(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Stop
|
|
104
|
+
*/
|
|
105
|
+
stop(): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Next track
|
|
108
|
+
*/
|
|
109
|
+
nextTrack(): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Previous track
|
|
112
|
+
*/
|
|
113
|
+
previousTrack(): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Get bass level
|
|
116
|
+
*/
|
|
117
|
+
getBass(): Promise<Bass>;
|
|
118
|
+
/**
|
|
119
|
+
* Set bass level (-10 to 10)
|
|
120
|
+
*/
|
|
121
|
+
setBass(level: number): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Get tone settings (bass and treble)
|
|
124
|
+
*/
|
|
125
|
+
getTone(): Promise<Tone>;
|
|
126
|
+
/**
|
|
127
|
+
* Send a raw key press
|
|
128
|
+
*/
|
|
129
|
+
sendKey(key: KeyValue | string): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Escape XML special characters
|
|
132
|
+
*/
|
|
133
|
+
private escapeXml;
|
|
134
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SoundTouch = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const xml2js_1 = require("xml2js");
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const errors_1 = require("./errors");
|
|
11
|
+
/**
|
|
12
|
+
* Main class for controlling Bose SoundTouch speakers
|
|
13
|
+
*/
|
|
14
|
+
class SoundTouch {
|
|
15
|
+
host;
|
|
16
|
+
port;
|
|
17
|
+
timeout;
|
|
18
|
+
client;
|
|
19
|
+
baseUrl;
|
|
20
|
+
/**
|
|
21
|
+
* Create a new SoundTouch instance
|
|
22
|
+
* @param host - IP address or hostname of the speaker
|
|
23
|
+
* @param port - HTTP port (default: 8090)
|
|
24
|
+
* @param timeout - Request timeout in seconds (default: 10.0)
|
|
25
|
+
*/
|
|
26
|
+
constructor(host, port = 8090, timeout = 10.0) {
|
|
27
|
+
this.host = host;
|
|
28
|
+
this.port = port;
|
|
29
|
+
this.timeout = timeout;
|
|
30
|
+
this.baseUrl = `http://${host}:${port}`;
|
|
31
|
+
this.client = axios_1.default.create({
|
|
32
|
+
baseURL: this.baseUrl,
|
|
33
|
+
timeout: timeout * 1000,
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": "application/xml",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Handle errors and convert to appropriate error types
|
|
41
|
+
*/
|
|
42
|
+
handleError(error) {
|
|
43
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
44
|
+
const axiosError = error;
|
|
45
|
+
if (axiosError.code === "ECONNREFUSED" || axiosError.code === "ENOTFOUND") {
|
|
46
|
+
throw new errors_1.ConnectionError(`Could not connect to speaker at ${this.baseUrl}`, axiosError);
|
|
47
|
+
}
|
|
48
|
+
if (axiosError.code === "ETIMEDOUT" || axiosError.code === "ECONNABORTED") {
|
|
49
|
+
throw new errors_1.TimeoutError(`Request to ${this.baseUrl} timed out`);
|
|
50
|
+
}
|
|
51
|
+
// Check for API error response
|
|
52
|
+
if (axiosError.response?.data) {
|
|
53
|
+
const data = axiosError.response.data;
|
|
54
|
+
if (typeof data === "object" && "error" in data) {
|
|
55
|
+
const errorData = data;
|
|
56
|
+
throw new errors_1.ApiError(errorData.error.name, errorData.error.code, errorData.error.message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw new errors_1.ConnectionError(`Request failed: ${axiosError.message}`, axiosError);
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse XML string to JavaScript object
|
|
65
|
+
*/
|
|
66
|
+
async parseXml(xml) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
(0, xml2js_1.parseString)(xml, {
|
|
69
|
+
explicitArray: false,
|
|
70
|
+
mergeAttrs: true,
|
|
71
|
+
explicitCharkey: false,
|
|
72
|
+
trim: true,
|
|
73
|
+
normalize: true,
|
|
74
|
+
ignoreAttrs: false,
|
|
75
|
+
attrNameProcessors: [],
|
|
76
|
+
tagNameProcessors: [],
|
|
77
|
+
}, (err, result) => {
|
|
78
|
+
if (err) {
|
|
79
|
+
reject(new Error(`Failed to parse XML: ${err.message}`));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
resolve(result);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Make a GET request and parse XML response
|
|
89
|
+
*/
|
|
90
|
+
async get(endpoint) {
|
|
91
|
+
try {
|
|
92
|
+
const response = await this.client.get(endpoint, {
|
|
93
|
+
responseType: "text",
|
|
94
|
+
});
|
|
95
|
+
const parsed = await this.parseXml(response.data);
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
this.handleError(error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Make a POST request with XML body
|
|
104
|
+
*/
|
|
105
|
+
async post(endpoint, body) {
|
|
106
|
+
try {
|
|
107
|
+
await this.client.post(endpoint, body);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
this.handleError(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get device information
|
|
115
|
+
*/
|
|
116
|
+
async getInfo() {
|
|
117
|
+
const data = await this.get("/info");
|
|
118
|
+
// Normalize networkInfo to always be an array
|
|
119
|
+
if (data.info.networkInfo && !Array.isArray(data.info.networkInfo)) {
|
|
120
|
+
data.info.networkInfo = [data.info.networkInfo];
|
|
121
|
+
}
|
|
122
|
+
return data.info;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get device capabilities
|
|
126
|
+
*/
|
|
127
|
+
async getCapabilities() {
|
|
128
|
+
const data = await this.get("/capabilities");
|
|
129
|
+
// Normalize capabilities array
|
|
130
|
+
if (data.capabilities.capability && !Array.isArray(data.capabilities.capability)) {
|
|
131
|
+
data.capabilities.capability = [data.capabilities.capability];
|
|
132
|
+
}
|
|
133
|
+
console.log(data.capabilities.capability);
|
|
134
|
+
return data.capabilities;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Set device name
|
|
138
|
+
*/
|
|
139
|
+
async setName(name) {
|
|
140
|
+
const xml = `<name>${this.escapeXml(name)}</name>`;
|
|
141
|
+
await this.post("/name", xml);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get current playback state
|
|
145
|
+
*/
|
|
146
|
+
async getNowPlaying() {
|
|
147
|
+
const data = await this.get("/now_playing");
|
|
148
|
+
return data.nowPlaying;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get available sources
|
|
152
|
+
*/
|
|
153
|
+
async getSources() {
|
|
154
|
+
const data = await this.get("/sources");
|
|
155
|
+
// Normalize sourceItem array
|
|
156
|
+
if (data.sources.sourceItem && !Array.isArray(data.sources.sourceItem)) {
|
|
157
|
+
data.sources.sourceItem = [data.sources.sourceItem];
|
|
158
|
+
}
|
|
159
|
+
return data.sources;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Select a source
|
|
163
|
+
*/
|
|
164
|
+
async selectSource(source, sourceAccount) {
|
|
165
|
+
let xml = `<ContentItem source="${this.escapeXml(source)}"`;
|
|
166
|
+
if (sourceAccount) {
|
|
167
|
+
xml += ` sourceAccount="${this.escapeXml(sourceAccount)}"`;
|
|
168
|
+
}
|
|
169
|
+
xml += ` location=""></ContentItem>`;
|
|
170
|
+
await this.post("/select", xml);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get volume state
|
|
174
|
+
*/
|
|
175
|
+
async getVolume() {
|
|
176
|
+
const data = await this.get("/volume");
|
|
177
|
+
return data.volume;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Set volume level (0-100)
|
|
181
|
+
*/
|
|
182
|
+
async setVolume(level) {
|
|
183
|
+
if (level < 0 || level > 100) {
|
|
184
|
+
throw new Error("Volume level must be between 0 and 100");
|
|
185
|
+
}
|
|
186
|
+
const xml = `<volume>${level}</volume>`;
|
|
187
|
+
await this.post("/volume", xml);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Mute the speaker
|
|
191
|
+
*/
|
|
192
|
+
async mute() {
|
|
193
|
+
const xml = "<volume>mute</volume>";
|
|
194
|
+
await this.post("/volume", xml);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Unmute the speaker
|
|
198
|
+
*/
|
|
199
|
+
async unmute() {
|
|
200
|
+
const xml = "<volume>unmute</volume>";
|
|
201
|
+
await this.post("/volume", xml);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Increase volume
|
|
205
|
+
*/
|
|
206
|
+
async volumeUp() {
|
|
207
|
+
await this.post("/volume", "<volume>volumeUp</volume>");
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Decrease volume
|
|
211
|
+
*/
|
|
212
|
+
async volumeDown() {
|
|
213
|
+
await this.post("/volume", "<volume>volumeDown</volume>");
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get presets
|
|
217
|
+
*/
|
|
218
|
+
async getPresets() {
|
|
219
|
+
const data = await this.get("/presets");
|
|
220
|
+
// Normalize preset array
|
|
221
|
+
if (data.presets.preset && !Array.isArray(data.presets.preset)) {
|
|
222
|
+
data.presets.preset = [data.presets.preset];
|
|
223
|
+
}
|
|
224
|
+
return data.presets;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Select a preset (1-6)
|
|
228
|
+
*/
|
|
229
|
+
async selectPreset(presetId) {
|
|
230
|
+
if (presetId < 1 || presetId > 6) {
|
|
231
|
+
throw new Error("Preset ID must be between 1 and 6");
|
|
232
|
+
}
|
|
233
|
+
const key = `PRESET_${presetId}`;
|
|
234
|
+
await this.sendKey(key);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Play
|
|
238
|
+
*/
|
|
239
|
+
async play() {
|
|
240
|
+
await this.sendKey(types_1.KeyValue.PLAY);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Pause
|
|
244
|
+
*/
|
|
245
|
+
async pause() {
|
|
246
|
+
await this.sendKey(types_1.KeyValue.PAUSE);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Toggle play/pause
|
|
250
|
+
*/
|
|
251
|
+
async playPause() {
|
|
252
|
+
const nowPlaying = await this.getNowPlaying();
|
|
253
|
+
if (nowPlaying.playStatus === types_1.PlayStatus.PLAY_STATE) {
|
|
254
|
+
await this.pause();
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
await this.play();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Stop
|
|
262
|
+
*/
|
|
263
|
+
async stop() {
|
|
264
|
+
await this.sendKey(types_1.KeyValue.PAUSE);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Next track
|
|
268
|
+
*/
|
|
269
|
+
async nextTrack() {
|
|
270
|
+
await this.sendKey(types_1.KeyValue.NEXT_TRACK);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Previous track
|
|
274
|
+
*/
|
|
275
|
+
async previousTrack() {
|
|
276
|
+
await this.sendKey(types_1.KeyValue.PREV_TRACK);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get bass level
|
|
280
|
+
*/
|
|
281
|
+
async getBass() {
|
|
282
|
+
const data = await this.get("/bass");
|
|
283
|
+
return data.bass;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Set bass level (-10 to 10)
|
|
287
|
+
*/
|
|
288
|
+
async setBass(level) {
|
|
289
|
+
if (level < -10 || level > 10) {
|
|
290
|
+
throw new Error("Bass level must be between -10 and 10");
|
|
291
|
+
}
|
|
292
|
+
const xml = `<bass>${level}</bass>`;
|
|
293
|
+
await this.post("/bass", xml);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get tone settings (bass and treble)
|
|
297
|
+
*/
|
|
298
|
+
async getTone() {
|
|
299
|
+
const data = await this.get("/tone");
|
|
300
|
+
return data.tone;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Send a raw key press
|
|
304
|
+
*/
|
|
305
|
+
async sendKey(key) {
|
|
306
|
+
const keyValue = typeof key === "string" ? key : key;
|
|
307
|
+
const xml = `<key state="press" sender="Gabbo">${keyValue}</key>`;
|
|
308
|
+
await this.post("/key", xml);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Escape XML special characters
|
|
312
|
+
*/
|
|
313
|
+
escapeXml(unsafe) {
|
|
314
|
+
return unsafe
|
|
315
|
+
.replace(/&/g, "&")
|
|
316
|
+
.replace(/</g, "<")
|
|
317
|
+
.replace(/>/g, ">")
|
|
318
|
+
.replace(/"/g, """)
|
|
319
|
+
.replace(/'/g, "'");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
exports.SoundTouch = SoundTouch;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Bose SoundTouch API responses
|
|
3
|
+
*/
|
|
4
|
+
export declare enum PlayStatus {
|
|
5
|
+
PLAY_STATE = "PLAY_STATE",
|
|
6
|
+
PAUSE_STATE = "PAUSE_STATE",
|
|
7
|
+
STOP_STATE = "STOP_STATE",
|
|
8
|
+
BUFFERING_STATE = "BUFFERING_STATE"
|
|
9
|
+
}
|
|
10
|
+
export declare enum KeyValue {
|
|
11
|
+
POWER = "POWER",
|
|
12
|
+
PLAY = "PLAY",
|
|
13
|
+
PAUSE = "PAUSE",
|
|
14
|
+
PREV_TRACK = "PREV_TRACK",
|
|
15
|
+
NEXT_TRACK = "NEXT_TRACK",
|
|
16
|
+
THUMBS_UP = "THUMBS_UP",
|
|
17
|
+
THUMBS_DOWN = "THUMBS_DOWN",
|
|
18
|
+
BOOKMARK = "BOOKMARK",
|
|
19
|
+
PRESET_1 = "PRESET_1",
|
|
20
|
+
PRESET_2 = "PRESET_2",
|
|
21
|
+
PRESET_3 = "PRESET_3",
|
|
22
|
+
PRESET_4 = "PRESET_4",
|
|
23
|
+
PRESET_5 = "PRESET_5",
|
|
24
|
+
PRESET_6 = "PRESET_6",
|
|
25
|
+
AUX_INPUT = "AUX_INPUT",
|
|
26
|
+
SHUFFLE_OFF = "SHUFFLE_OFF",
|
|
27
|
+
SHUFFLE_ON = "SHUFFLE_ON",
|
|
28
|
+
REPEAT_OFF = "REPEAT_OFF",
|
|
29
|
+
REPEAT_ONE = "REPEAT_ONE",
|
|
30
|
+
REPEAT_ALL = "REPEAT_ALL",
|
|
31
|
+
ADD_FAVORITE = "ADD_FAVORITE",
|
|
32
|
+
REMOVE_FAVORITE = "REMOVE_FAVORITE",
|
|
33
|
+
INVALID_KEY = "INVALID_KEY"
|
|
34
|
+
}
|
|
35
|
+
export interface NetworkInfo {
|
|
36
|
+
type: string;
|
|
37
|
+
macAddress: string;
|
|
38
|
+
ipAddress: string;
|
|
39
|
+
}
|
|
40
|
+
export interface DeviceInfo {
|
|
41
|
+
deviceID: string;
|
|
42
|
+
name: string;
|
|
43
|
+
type: string;
|
|
44
|
+
networkInfo: NetworkInfo[];
|
|
45
|
+
}
|
|
46
|
+
export interface Capability {
|
|
47
|
+
name: string;
|
|
48
|
+
value: string;
|
|
49
|
+
}
|
|
50
|
+
export interface Capabilities {
|
|
51
|
+
capability: Capability[];
|
|
52
|
+
}
|
|
53
|
+
export interface ContentItem {
|
|
54
|
+
source: string;
|
|
55
|
+
sourceAccount?: string;
|
|
56
|
+
location?: string;
|
|
57
|
+
isPresetable?: boolean;
|
|
58
|
+
itemName?: string;
|
|
59
|
+
containerArt?: string;
|
|
60
|
+
type?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface Preset {
|
|
63
|
+
id: string;
|
|
64
|
+
contentItem?: ContentItem;
|
|
65
|
+
}
|
|
66
|
+
export interface Presets {
|
|
67
|
+
preset: Preset[];
|
|
68
|
+
}
|
|
69
|
+
export interface Source {
|
|
70
|
+
source: string;
|
|
71
|
+
sourceAccount?: string;
|
|
72
|
+
status: string;
|
|
73
|
+
isLocal?: boolean;
|
|
74
|
+
multiroomallowed?: boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface Sources {
|
|
77
|
+
sourceItem: Source[];
|
|
78
|
+
}
|
|
79
|
+
export interface Volume {
|
|
80
|
+
targetvolume: number;
|
|
81
|
+
actualvolume: number;
|
|
82
|
+
muteenabled: boolean;
|
|
83
|
+
}
|
|
84
|
+
export interface NowPlaying {
|
|
85
|
+
source: string;
|
|
86
|
+
ContentItem?: ContentItem;
|
|
87
|
+
track?: string;
|
|
88
|
+
artist?: string;
|
|
89
|
+
album?: string;
|
|
90
|
+
stationName?: string;
|
|
91
|
+
art?: string;
|
|
92
|
+
artImageStatus?: string;
|
|
93
|
+
playStatus?: PlayStatus;
|
|
94
|
+
skipEnabled?: boolean;
|
|
95
|
+
skipPreviousEnabled?: boolean;
|
|
96
|
+
favoriteEnabled?: boolean;
|
|
97
|
+
isFavorite?: boolean;
|
|
98
|
+
stationLocation?: string;
|
|
99
|
+
time?: number;
|
|
100
|
+
totalTime?: number;
|
|
101
|
+
}
|
|
102
|
+
export interface Bass {
|
|
103
|
+
targetbass: number;
|
|
104
|
+
actualbass: number;
|
|
105
|
+
available?: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface Tone {
|
|
108
|
+
targettreble: number;
|
|
109
|
+
actualtreble: number;
|
|
110
|
+
targetbass: number;
|
|
111
|
+
actualbass: number;
|
|
112
|
+
available?: boolean;
|
|
113
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Type definitions for Bose SoundTouch API responses
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KeyValue = exports.PlayStatus = void 0;
|
|
7
|
+
var PlayStatus;
|
|
8
|
+
(function (PlayStatus) {
|
|
9
|
+
PlayStatus["PLAY_STATE"] = "PLAY_STATE";
|
|
10
|
+
PlayStatus["PAUSE_STATE"] = "PAUSE_STATE";
|
|
11
|
+
PlayStatus["STOP_STATE"] = "STOP_STATE";
|
|
12
|
+
PlayStatus["BUFFERING_STATE"] = "BUFFERING_STATE";
|
|
13
|
+
})(PlayStatus || (exports.PlayStatus = PlayStatus = {}));
|
|
14
|
+
var KeyValue;
|
|
15
|
+
(function (KeyValue) {
|
|
16
|
+
KeyValue["POWER"] = "POWER";
|
|
17
|
+
KeyValue["PLAY"] = "PLAY";
|
|
18
|
+
KeyValue["PAUSE"] = "PAUSE";
|
|
19
|
+
KeyValue["PREV_TRACK"] = "PREV_TRACK";
|
|
20
|
+
KeyValue["NEXT_TRACK"] = "NEXT_TRACK";
|
|
21
|
+
KeyValue["THUMBS_UP"] = "THUMBS_UP";
|
|
22
|
+
KeyValue["THUMBS_DOWN"] = "THUMBS_DOWN";
|
|
23
|
+
KeyValue["BOOKMARK"] = "BOOKMARK";
|
|
24
|
+
KeyValue["PRESET_1"] = "PRESET_1";
|
|
25
|
+
KeyValue["PRESET_2"] = "PRESET_2";
|
|
26
|
+
KeyValue["PRESET_3"] = "PRESET_3";
|
|
27
|
+
KeyValue["PRESET_4"] = "PRESET_4";
|
|
28
|
+
KeyValue["PRESET_5"] = "PRESET_5";
|
|
29
|
+
KeyValue["PRESET_6"] = "PRESET_6";
|
|
30
|
+
KeyValue["AUX_INPUT"] = "AUX_INPUT";
|
|
31
|
+
KeyValue["SHUFFLE_OFF"] = "SHUFFLE_OFF";
|
|
32
|
+
KeyValue["SHUFFLE_ON"] = "SHUFFLE_ON";
|
|
33
|
+
KeyValue["REPEAT_OFF"] = "REPEAT_OFF";
|
|
34
|
+
KeyValue["REPEAT_ONE"] = "REPEAT_ONE";
|
|
35
|
+
KeyValue["REPEAT_ALL"] = "REPEAT_ALL";
|
|
36
|
+
KeyValue["ADD_FAVORITE"] = "ADD_FAVORITE";
|
|
37
|
+
KeyValue["REMOVE_FAVORITE"] = "REMOVE_FAVORITE";
|
|
38
|
+
KeyValue["INVALID_KEY"] = "INVALID_KEY";
|
|
39
|
+
})(KeyValue || (exports.KeyValue = KeyValue = {}));
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bose-soundtouch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A JavaScript/TypeScript library for controlling Bose SoundTouch speakers via the local REST API",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsc --watch",
|
|
10
|
+
"demo": "ts-node demo.ts",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"bose",
|
|
15
|
+
"soundtouch",
|
|
16
|
+
"speaker",
|
|
17
|
+
"audio",
|
|
18
|
+
"rest",
|
|
19
|
+
"api"
|
|
20
|
+
],
|
|
21
|
+
"author": "Paul Grant",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/captivus/bose-soundtouch.git"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"axios": "^1.7.7",
|
|
32
|
+
"xml2js": "^0.6.2"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.14.10",
|
|
36
|
+
"@types/xml2js": "^0.4.14",
|
|
37
|
+
"ts-node": "^10.9.2",
|
|
38
|
+
"typescript": "^5.5.4"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
]
|
|
45
|
+
}
|