@stinkycomputing/sesame-api-client 1.5.0-alpha.1 → 1.5.0-alpha.2
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 +422 -137
- package/dist/index.browser.mjs +69 -0
- package/dist/index.browser.mjs.map +2 -2
- package/dist/index.cjs +73 -0
- package/dist/index.cjs.map +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +69 -0
- package/dist/index.mjs.map +2 -2
- package/dist/sesame-wire-protocol.d.ts +70 -0
- package/dist/sesame-wire-protocol.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,137 +1,422 @@
|
|
|
1
|
-
# @stinkycomputing/sesame-api-client
|
|
2
|
-
|
|
3
|
-
TypeScript client library for the Sesame video production server. Provides type-safe protobuf definitions, a WebSocket RPC client with reconnection, and helpers for building command lists.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
cl
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
cl.compositorAdd('main', 1920, 1080, false);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
cl.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
1
|
+
# @stinkycomputing/sesame-api-client
|
|
2
|
+
|
|
3
|
+
TypeScript client library for the Sesame video production server. Provides type-safe protobuf definitions, a WebSocket RPC client with automatic reconnection, and helpers for building command lists, parsing data frames, and sending remote control messages.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @stinkycomputing/sesame-api-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Works in Node.js and the browser. The browser entry point bundles `events` and other Node built-ins — no polyfills needed.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { SesameClient, CommandList } from '@stinkycomputing/sesame-api-client';
|
|
19
|
+
|
|
20
|
+
const client = new SesameClient(9000); // port, or a full ws:// URL
|
|
21
|
+
|
|
22
|
+
const cl = new CommandList();
|
|
23
|
+
cl.sourceAdd('cam1', { file: { url: '/path/to/video.mp4' } });
|
|
24
|
+
cl.compositorAdd('main', 1920, 1080, false);
|
|
25
|
+
cl.nodeAdd('cam1-node', {
|
|
26
|
+
compositorId: 'main',
|
|
27
|
+
parentAddress: '',
|
|
28
|
+
nodeType: 'source',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await client.execute(cl);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## SesameClient
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const client = new SesameClient(9000);
|
|
40
|
+
// or with a custom URL:
|
|
41
|
+
const client = new SesameClient('ws://192.168.1.10:9000/api');
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The client auto-connects and auto-reconnects. Listen for connection state changes:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
client.on('connected', () => console.log('ready'));
|
|
48
|
+
client.on('disconnected', () => console.log('lost connection'));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Member | Description |
|
|
52
|
+
|--------|-------------|
|
|
53
|
+
| `client.execute(cl)` | Send a `CommandList` to the server |
|
|
54
|
+
| `client.isConnected()` | Current connection state |
|
|
55
|
+
| `client.disconnect()` | Close the connection |
|
|
56
|
+
| `client.status` | [`StatusApi`](#statusapi) instance |
|
|
57
|
+
| `client.recorder` | [`RecorderApi`](#recorderapi) instance |
|
|
58
|
+
| `client.jobs` | `JobsApi` instance |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Command List
|
|
63
|
+
|
|
64
|
+
`CommandList` builds a batch of operations sent in a single `execute` call. All methods are chainable and accept an optional `timeOffsetMs` to schedule commands relative to the current frame.
|
|
65
|
+
|
|
66
|
+
### Sources
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const cl = new CommandList();
|
|
70
|
+
|
|
71
|
+
// File source
|
|
72
|
+
cl.sourceAdd('clip1', { file: { url: '/clips/a.mp4' } });
|
|
73
|
+
|
|
74
|
+
// Decklink capture
|
|
75
|
+
cl.sourceAdd('sdi-in', { decklink: { deviceIndex: 0, videoFormat: DecklinkVideoFormat.HD_1080i_50 } });
|
|
76
|
+
|
|
77
|
+
// WebSocket ingest
|
|
78
|
+
cl.sourceAdd('ws-in', { websocket: { channel: 'my-channel' } });
|
|
79
|
+
|
|
80
|
+
// MoQ ingest
|
|
81
|
+
cl.sourceAdd('moq-in', { moq: { url: 'https://relay.example', broadcast: 'feed', key: '' } });
|
|
82
|
+
|
|
83
|
+
cl.sourceRemove('clip1');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Compositor
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
cl.compositorAdd('main', 1920, 1080, false);
|
|
90
|
+
|
|
91
|
+
cl.nodeAdd('bg', { compositorId: 'main', parentAddress: '', nodeType: 'source' });
|
|
92
|
+
cl.nodeAdd('fg', { compositorId: 'main', parentAddress: '', nodeType: 'source' });
|
|
93
|
+
|
|
94
|
+
cl.nodeRemove('main', 'fg');
|
|
95
|
+
cl.compositorRemove('main');
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Outputs
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
cl.outputAdd('stream', {
|
|
102
|
+
encoder: { width: 1920, height: 1080, fps: 50, preset: 0, bitrateKbps: 8000 },
|
|
103
|
+
websocket: { channel: 'live-out' },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
cl.outputUpdate('stream', { /* updated config */ });
|
|
107
|
+
cl.outputRemove('stream');
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Audio Mixers
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { sesame } from '@stinkycomputing/sesame-api-client';
|
|
114
|
+
const { AUDIO_MIXER_CHANNEL_TYPE_STEREO } = sesame.v1.audio.AudioMixerChannelType;
|
|
115
|
+
|
|
116
|
+
cl.audioMixerAdd('mix1', {
|
|
117
|
+
outputType: AUDIO_MIXER_CHANNEL_TYPE_STEREO,
|
|
118
|
+
order: 0,
|
|
119
|
+
channels: [
|
|
120
|
+
{ id: 'ch1', sourceId: 'cam1', channelType: AUDIO_MIXER_CHANNEL_TYPE_STEREO,
|
|
121
|
+
sourceChannels: [0, 1], level: 1.0, pan: 0.0, plugins: [] },
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
cl.audioMixerChannelAdd('mix1', { id: 'ch2', sourceId: 'cam2', /* ... */ });
|
|
126
|
+
cl.audioMixerChannelRemove('mix1', 'ch2');
|
|
127
|
+
cl.audioMixerRemove('mix1');
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Properties
|
|
131
|
+
|
|
132
|
+
Property names are type-checked against a generated registry — TypeScript will autocomplete valid names and value shapes.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Compositor property (typed)
|
|
136
|
+
cl.propertySet({ compositorId: 'main' }, 'cam1-node', 'transform/position', { vecValue: { r: 100, g: 200 } });
|
|
137
|
+
|
|
138
|
+
// Audio mixer property
|
|
139
|
+
cl.propertySet({ audioMixerId: 'mix1' }, 'ch1', 'level', { floatValue: 0.8 });
|
|
140
|
+
|
|
141
|
+
// Source property
|
|
142
|
+
cl.propertySet({ sourceId: 'cam1' }, '', 'decoder/buffer_frames', { intValue: 4 });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Transport Commands
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
cl.transportCommand('cam1', { type: 'play' });
|
|
149
|
+
cl.transportCommand('cam1', { type: 'pause' });
|
|
150
|
+
cl.transportCommand('cam1', { type: 'seek', value: 5_000_000 }); // microseconds
|
|
151
|
+
cl.transportCommand('cam1', { type: 'next_playlist_clip' });
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Available types: `play`, `pause`, `stop`, `seek`, `seek_usertime`, `jog`, `live`, `set_recorder`, `load_playlist`, `eject_playlist`, `load_clip`, `set_metadata`, `take`, `pre_roll`, `post_roll`, `next_playlist_clip`, `skip_next_playlist_clip`, `transitions_disabled`, `set_scrubbing`.
|
|
155
|
+
|
|
156
|
+
### Playlists
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
cl.playlistLoad('cam1', {
|
|
160
|
+
id: 'my-playlist',
|
|
161
|
+
duration: 0,
|
|
162
|
+
clips: [
|
|
163
|
+
{ id: 1, recorderId: 'recorder1', speed: 1, transitionTimeUs: 0,
|
|
164
|
+
startTimeUs: 0, endTimeUs: 10_000_000, audioRouting: [] },
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
cl.playlistEject('cam1');
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Animations
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { CommandList, keyframe, EaseKind, sesame } from '@stinkycomputing/sesame-api-client';
|
|
175
|
+
const { HOLD } = sesame.v1.compositor.AnimationChannelEvaluationMode;
|
|
176
|
+
|
|
177
|
+
cl.propertyAnimate(
|
|
178
|
+
{ compositorId: 'main' }, 'cam1-node', 'opacity', HOLD, HOLD,
|
|
179
|
+
[
|
|
180
|
+
keyframe(0, { floatValue: 0.0 }),
|
|
181
|
+
keyframe(500_000, { floatValue: 1.0 }, EaseKind.QUADRATIC_INOUT),
|
|
182
|
+
keyframe(1_000_000, { floatValue: 0.5 }, EaseKind.CUBIC_IN, EaseKind.CUBIC_OUT),
|
|
183
|
+
],
|
|
184
|
+
);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`keyframe(timeUs, value, easingIn?, easingOut?)` — time in microseconds.
|
|
188
|
+
|
|
189
|
+
### Metadata Bindings
|
|
190
|
+
|
|
191
|
+
Wire output tracks to node capabilities for remote state / control. See [Data Frames](#data-frames) for the other side.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
cl.setMetadataBindings([
|
|
195
|
+
// Push AudioMixerStatus from 'mix1' out on the 'audio-state' track of output 'ws-out'
|
|
196
|
+
{ sourceNodeId: 'mix1', metadataId: 'audio-state', outputId: 'ws-out', trackName: 'audio-state' },
|
|
197
|
+
// Route incoming 'audio-ctrl' track on source 'ws-in' → mixer 'mix1'
|
|
198
|
+
{ sourceId: 'ws-in', trackName: 'audio-ctrl', targetNodeId: 'mix1', metadataId: 'audio-control' },
|
|
199
|
+
]);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Data Frames
|
|
205
|
+
|
|
206
|
+
`FRAME_TYPE_DATA` frames carry typed protobuf payloads over output WebSocket/MoQ tracks. Use `parseDataFrame` to decode incoming frames without manually inspecting `data_type`.
|
|
207
|
+
|
|
208
|
+
### Receiving (output stream → client)
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { WireProtocol, parseDataFrame } from '@stinkycomputing/sesame-api-client';
|
|
212
|
+
|
|
213
|
+
ws.addEventListener('message', async (evt) => {
|
|
214
|
+
const bytes = new Uint8Array(await evt.data.arrayBuffer());
|
|
215
|
+
const frame = WireProtocol.parse(bytes);
|
|
216
|
+
const data = parseDataFrame(frame);
|
|
217
|
+
if (!data) return; // not a DATA frame
|
|
218
|
+
|
|
219
|
+
switch (data.dataType) {
|
|
220
|
+
case 'audio-state':
|
|
221
|
+
// data.status: sesame.v1.audio.AudioMixerStatus
|
|
222
|
+
console.log(data.trackName, data.status.id, data.status.vu);
|
|
223
|
+
break;
|
|
224
|
+
case 'transport-status':
|
|
225
|
+
// data.event: sesame.v1.status.TransportEvent
|
|
226
|
+
console.log(data.event.state, data.event.positionUs);
|
|
227
|
+
break;
|
|
228
|
+
case 'json':
|
|
229
|
+
console.log(data.json); // parsed JS object
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
| `dataType` | Payload type | Direction |
|
|
236
|
+
|------------|-------------|-----------|
|
|
237
|
+
| `'json'` | `unknown` (parsed JSON) | server → client |
|
|
238
|
+
| `'transport-status'` | `sesame.v1.status.TransportEvent` | server → client |
|
|
239
|
+
| `'audio-state'` | `sesame.v1.audio.AudioMixerStatus` | server → client |
|
|
240
|
+
| `'transport-control'` | `sesame.v1.sources.TransportControlRequest` | client → server |
|
|
241
|
+
| `'audio-control'` | `sesame.v1.audio.AudioControlRequest` | client → server |
|
|
242
|
+
|
|
243
|
+
### Sending Remote Control (source stream → server)
|
|
244
|
+
|
|
245
|
+
Send control frames over a WebSocket **source** connection (inbound to the server). The server resolves the target node from the metadata binding — no ID is needed in the payload.
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { buildAudioControlFrame, buildTransportControlFrame, sesame } from '@stinkycomputing/sesame-api-client';
|
|
249
|
+
|
|
250
|
+
// Adjust a mixer channel level and pan
|
|
251
|
+
ws.send(buildAudioControlFrame('audio-ctrl', {
|
|
252
|
+
channels: [
|
|
253
|
+
{ channelId: 'ch1', level: 0.8, pan: -0.2 },
|
|
254
|
+
],
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
// Adjust a plugin parameter on a channel
|
|
258
|
+
ws.send(buildAudioControlFrame('audio-ctrl', {
|
|
259
|
+
channels: [{
|
|
260
|
+
channelId: 'ch1',
|
|
261
|
+
plugins: [{ pluginId: 'eq1', params: [{ id: 3, floatValue: 200.0 }] }],
|
|
262
|
+
}],
|
|
263
|
+
}));
|
|
264
|
+
|
|
265
|
+
// Queue a transport command on a bound source
|
|
266
|
+
ws.send(buildTransportControlFrame('transport-ctrl', {
|
|
267
|
+
cmdType: sesame.v1.sources.SourceTransportCommandType.SOURCE_TRANSPORT_CMD_PLAY,
|
|
268
|
+
}));
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`buildAudioControlFrame(trackName, request)` and `buildTransportControlFrame(trackName, request)` both return a ready-to-send `Uint8Array`.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## StatusApi
|
|
276
|
+
|
|
277
|
+
Accessed via `client.status`.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const status = await client.status.getStatus();
|
|
281
|
+
// status: sesame.v1.status.Status — full server state snapshot
|
|
282
|
+
|
|
283
|
+
const devices = await client.status.getIODevices();
|
|
284
|
+
// devices.devices: list of available capture/output hardware
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## RecorderApi
|
|
290
|
+
|
|
291
|
+
Accessed via `client.recorder`.
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// Add a clip to a recorder
|
|
295
|
+
await client.recorder.addClip('recorder1', {
|
|
296
|
+
id: 1, path: '/recordings/clip1.mp4', durationUs: 30_000_000,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Copy, remove, clear
|
|
300
|
+
await client.recorder.copyClip('recorder1', 1, 2);
|
|
301
|
+
await client.recorder.removeClip('recorder1', 1);
|
|
302
|
+
await client.recorder.removeAllClips('recorder1');
|
|
303
|
+
|
|
304
|
+
// Fetch all clips
|
|
305
|
+
const clips = await client.recorder.getClips('recorder1');
|
|
306
|
+
|
|
307
|
+
// Verify clip files on disk (returns list of error strings)
|
|
308
|
+
const errors = await client.recorder.verifyClips('recorder1');
|
|
309
|
+
|
|
310
|
+
// Set clip metadata
|
|
311
|
+
await client.recorder.setMetadata('recorder1', { /* ITransportMetadata */ });
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Event Subscriptions
|
|
317
|
+
|
|
318
|
+
Subscribe to server-pushed event topics via `addSubscription`:
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { sesame } from '@stinkycomputing/sesame-api-client';
|
|
322
|
+
const { EVENT_TOPIC_TRANSPORT, EVENT_TOPIC_ERROR } = sesame.v1.common.EventTopic;
|
|
323
|
+
|
|
324
|
+
client.addSubscription(EVENT_TOPIC_TRANSPORT, (topic, event) => {
|
|
325
|
+
const transport = event.transportEvent;
|
|
326
|
+
console.log(transport?.state, transport?.positionUs);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
client.addSubscription(EVENT_TOPIC_ERROR, (topic, event) => {
|
|
330
|
+
console.error(event.errorEvent?.message);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Unsubscribe when done
|
|
334
|
+
client.removeSubscription(EVENT_TOPIC_TRANSPORT, myCallback);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Available topics: `EVENT_TOPIC_TRANSPORT`, `EVENT_TOPIC_ERROR`, `EVENT_TOPIC_CALLBACK`, `EVENT_TOPIC_JOB`, `EVENT_TOPIC_RECORDER`, `EVENT_TOPIC_METADATA`.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Wire Protocol
|
|
342
|
+
|
|
343
|
+
Every WebSocket message is framed as:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
[4-byte LE header_size][FrameHeader protobuf][payload bytes]
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`WireProtocol.serialize` / `WireProtocol.parse` handle the framing layer:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import { WireProtocol, FrameType, sesame } from '@stinkycomputing/sesame-api-client';
|
|
353
|
+
|
|
354
|
+
// Build a frame manually
|
|
355
|
+
const bytes = WireProtocol.serialize(
|
|
356
|
+
{ type: FrameType.FRAME_TYPE_RPC },
|
|
357
|
+
rpcPayload,
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
// Parse an incoming frame
|
|
361
|
+
const { valid, header, payload } = WireProtocol.parse(incoming);
|
|
362
|
+
if (valid && header.type === FrameType.FRAME_TYPE_DATA) {
|
|
363
|
+
// use parseDataFrame() instead — see Data Frames above
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Frame types: `FRAME_TYPE_RPC`, `FRAME_TYPE_VIDEO`, `FRAME_TYPE_AUDIO`, `FRAME_TYPE_MUXED`, `FRAME_TYPE_DECODER_DATA`, `FRAME_TYPE_DATA`.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Protobuf Modules
|
|
372
|
+
|
|
373
|
+
All protobuf types are accessible via the `sesame` namespace:
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
import { sesame } from '@stinkycomputing/sesame-api-client';
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
| Module | Key contents |
|
|
380
|
+
|--------|-------------|
|
|
381
|
+
| `sesame.v1.wire` | `FrameHeader`, `FrameType`, `DataType`, `MediaCodecData` |
|
|
382
|
+
| `sesame.v1.common` | `Empty`, `Vec4`, `PropValue`, `EventTopic`, `PropertyDomain` |
|
|
383
|
+
| `sesame.v1.sources` | Source configs, `SourceTransportCommand`, `TransportControlRequest` |
|
|
384
|
+
| `sesame.v1.outputs` | Output configs, `EncoderConfig` |
|
|
385
|
+
| `sesame.v1.compositor` | Scene graph, nodes, properties, animations, `KeyFrame` |
|
|
386
|
+
| `sesame.v1.audio` | `AudioMixerConfig`, `AudioMixerStatus`, `AudioControlRequest` |
|
|
387
|
+
| `sesame.v1.recorder` | `RecorderClip`, `PlaylistItem`, `TransitionType` |
|
|
388
|
+
| `sesame.v1.jobs` | Background export/import jobs |
|
|
389
|
+
| `sesame.v1.status` | `Status`, `Event`, `TransportEvent`, `SubscriptionRequest` |
|
|
390
|
+
| `sesame.v1.commands` | `CommandList`, `CommandListItem`, `MetadataBinding` |
|
|
391
|
+
| `sesame.v1.rpc` | `Message`, `Request`, `Response`, `Event` envelope |
|
|
392
|
+
|
|
393
|
+
For the complete field and enum reference see **[Protocol Reference](docs/protocol-reference.md)**.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Bundling
|
|
398
|
+
|
|
399
|
+
All dependencies (`events`, `long`, `protobufjs`) are pure JS — no native modules. Bundles cleanly with esbuild, Vite, webpack, or similar without any special `external` config.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Publishing
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
# Bump version in package.json, then:
|
|
407
|
+
pnpm run publish:api-client # stable
|
|
408
|
+
pnpm run publish:api-client:alpha # pre-release (--tag alpha)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Or from this package directly:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
npm run build
|
|
415
|
+
npm publish --access public [--tag alpha]
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
MIT
|
package/dist/index.browser.mjs
CHANGED
|
@@ -24301,9 +24301,74 @@ var import_events2 = __toESM(require_events());
|
|
|
24301
24301
|
|
|
24302
24302
|
// src/sesame-wire-protocol.ts
|
|
24303
24303
|
var FrameType = sesame.v1.wire.FrameType;
|
|
24304
|
+
var DataType = sesame.v1.wire.DataType;
|
|
24304
24305
|
var FrameHeader = sesame.v1.wire.FrameHeader;
|
|
24305
24306
|
var MediaCodecData = sesame.v1.wire.MediaCodecData;
|
|
24306
24307
|
var CodecType = sesame.v1.common.CodecType;
|
|
24308
|
+
function parseDataFrame(frame) {
|
|
24309
|
+
if (!frame.valid || !frame.header || !frame.payload) return null;
|
|
24310
|
+
if (frame.header.type !== sesame.v1.wire.FrameType.FRAME_TYPE_DATA) return null;
|
|
24311
|
+
const d = frame.header.data;
|
|
24312
|
+
if (!d) return null;
|
|
24313
|
+
const trackName = d.trackName ?? "";
|
|
24314
|
+
const payload = frame.payload;
|
|
24315
|
+
try {
|
|
24316
|
+
switch (d.dataType) {
|
|
24317
|
+
case sesame.v1.wire.DataType.DATA_TYPE_JSON: {
|
|
24318
|
+
const text = new TextDecoder().decode(payload);
|
|
24319
|
+
return { dataType: "json", trackName, json: JSON.parse(text) };
|
|
24320
|
+
}
|
|
24321
|
+
case sesame.v1.wire.DataType.DATA_TYPE_TRANSPORT_STATUS:
|
|
24322
|
+
return {
|
|
24323
|
+
dataType: "transport-status",
|
|
24324
|
+
trackName,
|
|
24325
|
+
event: sesame.v1.status.TransportEvent.decode(payload)
|
|
24326
|
+
};
|
|
24327
|
+
case sesame.v1.wire.DataType.DATA_TYPE_AUDIO_STATE:
|
|
24328
|
+
return {
|
|
24329
|
+
dataType: "audio-state",
|
|
24330
|
+
trackName,
|
|
24331
|
+
status: sesame.v1.audio.AudioMixerStatus.decode(payload)
|
|
24332
|
+
};
|
|
24333
|
+
case sesame.v1.wire.DataType.DATA_TYPE_TRANSPORT_CONTROL:
|
|
24334
|
+
return {
|
|
24335
|
+
dataType: "transport-control",
|
|
24336
|
+
trackName,
|
|
24337
|
+
request: sesame.v1.sources.TransportControlRequest.decode(payload)
|
|
24338
|
+
};
|
|
24339
|
+
case sesame.v1.wire.DataType.DATA_TYPE_AUDIO_CONTROL:
|
|
24340
|
+
return {
|
|
24341
|
+
dataType: "audio-control",
|
|
24342
|
+
trackName,
|
|
24343
|
+
request: sesame.v1.audio.AudioControlRequest.decode(payload)
|
|
24344
|
+
};
|
|
24345
|
+
default:
|
|
24346
|
+
return null;
|
|
24347
|
+
}
|
|
24348
|
+
} catch {
|
|
24349
|
+
return null;
|
|
24350
|
+
}
|
|
24351
|
+
}
|
|
24352
|
+
function buildAudioControlFrame(trackName, request) {
|
|
24353
|
+
const payload = sesame.v1.audio.AudioControlRequest.encode(request).finish();
|
|
24354
|
+
return WireProtocol.serialize(
|
|
24355
|
+
{
|
|
24356
|
+
type: sesame.v1.wire.FrameType.FRAME_TYPE_DATA,
|
|
24357
|
+
data: { dataType: sesame.v1.wire.DataType.DATA_TYPE_AUDIO_CONTROL, trackName }
|
|
24358
|
+
},
|
|
24359
|
+
payload
|
|
24360
|
+
);
|
|
24361
|
+
}
|
|
24362
|
+
function buildTransportControlFrame(trackName, request) {
|
|
24363
|
+
const payload = sesame.v1.sources.TransportControlRequest.encode(request).finish();
|
|
24364
|
+
return WireProtocol.serialize(
|
|
24365
|
+
{
|
|
24366
|
+
type: sesame.v1.wire.FrameType.FRAME_TYPE_DATA,
|
|
24367
|
+
data: { dataType: sesame.v1.wire.DataType.DATA_TYPE_TRANSPORT_CONTROL, trackName }
|
|
24368
|
+
},
|
|
24369
|
+
payload
|
|
24370
|
+
);
|
|
24371
|
+
}
|
|
24307
24372
|
var PREFIX_SIZE = 4;
|
|
24308
24373
|
var WireProtocol = class {
|
|
24309
24374
|
/**
|
|
@@ -24784,6 +24849,7 @@ export {
|
|
|
24784
24849
|
CodecType,
|
|
24785
24850
|
CommandList,
|
|
24786
24851
|
ConnectionState,
|
|
24852
|
+
DataType,
|
|
24787
24853
|
EaseKind,
|
|
24788
24854
|
Event,
|
|
24789
24855
|
FrameHeader,
|
|
@@ -24798,8 +24864,11 @@ export {
|
|
|
24798
24864
|
SesameConnection,
|
|
24799
24865
|
StatusApi,
|
|
24800
24866
|
WireProtocol,
|
|
24867
|
+
buildAudioControlFrame,
|
|
24868
|
+
buildTransportControlFrame,
|
|
24801
24869
|
getLogger,
|
|
24802
24870
|
log,
|
|
24871
|
+
parseDataFrame,
|
|
24803
24872
|
sesame,
|
|
24804
24873
|
setLogger,
|
|
24805
24874
|
waitForEvent
|