# @stinkycomputing/sesame-api-client

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.

All websocket traffic uses protobuf wire framing — see [Wire Protocol](#wire-protocol).

## Install

```bash
npm install @stinkycomputing/sesame-api-client
```

## Quick Start

```typescript
import { SesameClient, CommandList } from '@stinkycomputing/sesame-api-client';

const client = new SesameClient(9000);

const cl = new CommandList();
cl.sourceAdd('my-source', { type: 'file', path: '/path/to/video.mp4' });
cl.compositorAdd('main', 1920, 1080, false);

await client.execute(cl);
```

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.

## Protobuf Modules

Types are generated from `.proto` files under `sesame.v1.*`:

```typescript
import { sesame } from '@stinkycomputing/sesame-api-client';
```

| Module | Contents |
|--------|----------|
| `sesame.v1.wire` | Wire framing (FrameHeader, FrameType, MediaCodecData) |
| `sesame.v1.common` | Shared types (Empty, Vec4, PropValue, EventTopic) |
| `sesame.v1.sources` | Source config and transport |
| `sesame.v1.outputs` | Output config and encoder settings |
| `sesame.v1.compositor` | Scene graph, nodes, properties, animations |
| `sesame.v1.audio` | Audio mixer and channels |
| `sesame.v1.recorder` | Recorder, clips, playlists |
| `sesame.v1.jobs` | Background export/import jobs |
| `sesame.v1.status` | Status polling and event subscriptions |
| `sesame.v1.commands` | Command list items |
| `sesame.v1.rpc` | RPC message envelope (Request/Response/Event) |

## Wire Protocol

Every websocket message is framed as:

```
[4-byte LE header_size][FrameHeader protobuf][payload bytes]
```

`WireProtocol.serialize` / `WireProtocol.parse` handle this:

```typescript
import { WireProtocol, sesame } from '@stinkycomputing/sesame-api-client';

// serialize
const frame = WireProtocol.serialize(
  { type: sesame.v1.wire.FrameType.FRAME_TYPE_RPC },
  rpcPayload,
);

// parse
const parsed = WireProtocol.parse(incoming);
if (parsed.valid) {
  // parsed.header.type, parsed.payload
}
```

Frame types: `FRAME_TYPE_RPC`, `FRAME_TYPE_VIDEO`, `FRAME_TYPE_AUDIO`, `FRAME_TYPE_MUXED`, `FRAME_TYPE_DECODER_DATA`.

## Command List

`CommandList` builds a batch of operations to send in one `execute` call:

```typescript
const cl = new CommandList();

cl.sourceAdd('cam1', { type: 'decklink', deviceIndex: 0 });
cl.compositorAdd('main', 1920, 1080, false);
cl.nodeAdd('main', 'cam1-node', 'source', { sourceId: 'cam1' });

cl.propertySet(
  { compositor: 'main', node: 'cam1-node' },
  'transform', 'position',
  { vecValue: { r: 100, g: 100 } },
);

cl.transportCommand('cam1', { type: 'play' });

await client.execute(cl);
```

### Animations

```typescript
import { CommandList, keyframe, EaseKind, sesame } from '@stinkycomputing/sesame-api-client';

const cl = new CommandList();
cl.propertyAnimate(
  { compositor: 'main', node: 'cam1-node' },
  'cam1-node', 'opacity',
  sesame.v1.compositor.AnimationChannelEvaluationMode.HOLD,
  sesame.v1.compositor.AnimationChannelEvaluationMode.HOLD,
  [
    keyframe(0,       { floatValue: 0.0 }),
    keyframe(500000,  { floatValue: 1.0 }, EaseKind.QUADRATIC_INOUT),
    keyframe(1000000, { floatValue: 0.5 }, EaseKind.CUBIC_IN, EaseKind.CUBIC_OUT),
  ],
);
```

`keyframe(timeUs, value, easingIn?, easingOut?)` — time in microseconds, value is a `PropValue`, easing uses `EaseKind`.

## Bundling

All dependencies (`events`, `long`, `protobufjs`) are pure JS — no native modules. The package bundles cleanly with esbuild or similar without any special `external` config.

## Publishing

1. Bump version in `package.json`
2. `npm run build`
3. `npm publish --access public` (or `--tag alpha` for prereleases)
4. `git tag api-client-vX.Y.Z && git push origin api-client-vX.Y.Z`

## License

MIT
