@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 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
- All websocket traffic uses protobuf wire framing — see [Wire Protocol](#wire-protocol).
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @stinkycomputing/sesame-api-client
11
- ```
12
-
13
- ## Quick Start
14
-
15
- ```typescript
16
- import { SesameClient, CommandList } from '@stinkycomputing/sesame-api-client';
17
-
18
- const client = new SesameClient(9000);
19
-
20
- const cl = new CommandList();
21
- cl.sourceAdd('my-source', { type: 'file', path: '/path/to/video.mp4' });
22
- cl.compositorAdd('main', 1920, 1080, false);
23
-
24
- await client.execute(cl);
25
- ```
26
-
27
- Works in the browser too — the browser entry point bundles `events` and other Node built-ins so `SesameClient`, `RPCClient`, and `WireProtocol` all work without polyfills.
28
-
29
- ## Protobuf Modules
30
-
31
- Types are generated from `.proto` files under `sesame.v1.*`:
32
-
33
- ```typescript
34
- import { sesame } from '@stinkycomputing/sesame-api-client';
35
- ```
36
-
37
- | Module | Contents |
38
- |--------|----------|
39
- | `sesame.v1.wire` | Wire framing (FrameHeader, FrameType, MediaCodecData) |
40
- | `sesame.v1.common` | Shared types (Empty, Vec4, PropValue, EventTopic) |
41
- | `sesame.v1.sources` | Source config and transport |
42
- | `sesame.v1.outputs` | Output config and encoder settings |
43
- | `sesame.v1.compositor` | Scene graph, nodes, properties, animations |
44
- | `sesame.v1.audio` | Audio mixer and channels |
45
- | `sesame.v1.recorder` | Recorder, clips, playlists |
46
- | `sesame.v1.jobs` | Background export/import jobs |
47
- | `sesame.v1.status` | Status polling and event subscriptions |
48
- | `sesame.v1.commands` | Command list items |
49
- | `sesame.v1.rpc` | RPC message envelope (Request/Response/Event) |
50
-
51
- For the complete type reference with every enum value, message field, and property name, see **[Protocol Reference](docs/protocol-reference.md)**.
52
-
53
- ## Wire Protocol
54
-
55
- Every websocket message is framed as:
56
-
57
- ```
58
- [4-byte LE header_size][FrameHeader protobuf][payload bytes]
59
- ```
60
-
61
- `WireProtocol.serialize` / `WireProtocol.parse` handle this:
62
-
63
- ```typescript
64
- import { WireProtocol, sesame } from '@stinkycomputing/sesame-api-client';
65
-
66
- // serialize
67
- const frame = WireProtocol.serialize(
68
- { type: sesame.v1.wire.FrameType.FRAME_TYPE_RPC },
69
- rpcPayload,
70
- );
71
-
72
- // parse
73
- const parsed = WireProtocol.parse(incoming);
74
- if (parsed.valid) {
75
- // parsed.header.type, parsed.payload
76
- }
77
- ```
78
-
79
- Frame types: `FRAME_TYPE_RPC`, `FRAME_TYPE_VIDEO`, `FRAME_TYPE_AUDIO`, `FRAME_TYPE_MUXED`, `FRAME_TYPE_DECODER_DATA`, `FRAME_TYPE_DATA`.
80
-
81
- ## Command List
82
-
83
- `CommandList` builds a batch of operations to send in one `execute` call:
84
-
85
- ```typescript
86
- const cl = new CommandList();
87
-
88
- cl.sourceAdd('cam1', { type: 'decklink', deviceIndex: 0 });
89
- cl.compositorAdd('main', 1920, 1080, false);
90
- cl.nodeAdd('main', 'cam1-node', 'source', { sourceId: 'cam1' });
91
-
92
- cl.propertySet(
93
- { compositor: 'main', node: 'cam1-node' },
94
- 'transform', 'position',
95
- { vecValue: { r: 100, g: 100 } },
96
- );
97
-
98
- cl.transportCommand('cam1', { type: 'play' });
99
-
100
- await client.execute(cl);
101
- ```
102
-
103
- ### Animations
104
-
105
- ```typescript
106
- import { CommandList, keyframe, EaseKind, sesame } from '@stinkycomputing/sesame-api-client';
107
-
108
- const cl = new CommandList();
109
- cl.propertyAnimate(
110
- { compositor: 'main', node: 'cam1-node' },
111
- 'cam1-node', 'opacity',
112
- sesame.v1.compositor.AnimationChannelEvaluationMode.HOLD,
113
- sesame.v1.compositor.AnimationChannelEvaluationMode.HOLD,
114
- [
115
- keyframe(0, { floatValue: 0.0 }),
116
- keyframe(500000, { floatValue: 1.0 }, EaseKind.QUADRATIC_INOUT),
117
- keyframe(1000000, { floatValue: 0.5 }, EaseKind.CUBIC_IN, EaseKind.CUBIC_OUT),
118
- ],
119
- );
120
- ```
121
-
122
- `keyframe(timeUs, value, easingIn?, easingOut?)` — time in microseconds, value is a `PropValue`, easing uses `EaseKind`.
123
-
124
- ## Bundling
125
-
126
- All dependencies (`events`, `long`, `protobufjs`) are pure JS — no native modules. The package bundles cleanly with esbuild or similar without any special `external` config.
127
-
128
- ## Publishing
129
-
130
- 1. Bump version in `package.json`
131
- 2. `npm run build`
132
- 3. `npm publish --access public` (or `--tag alpha` for prereleases)
133
- 4. `git tag api-client-vX.Y.Z && git push origin api-client-vX.Y.Z`
134
-
135
- ## License
136
-
137
- MIT
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
@@ -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