# TypeScript Quickstart

> Mint a playback URL, open a viewer, render fMP4 — under five minutes.

This quickstart walks the **streams** modality end to end: the control plane
creates a live input, mints a signed playback URL, and the data plane opens a
browser viewer that yields CMAF chunks. Replace `streams` with any other
modality (`voice`, `robotics`, `games`, `data`) — they all follow the same
shape.

## 1. Install

```bash
npm install @clutchcall/sdk
```

## 2. Server-side: create a live input

```ts
import { Streams } from "@clutchcall/sdk/streams";

const streams = new Streams({
  baseUrl: "https://app.clutchcall.dev",
  apiKey:  process.env.CLUTCHCALL_API_KEY!,
  orgId:   "org_abc",
});

const { input, streamKey } = await streams.liveInputs.create({
  name: "My First Stream",
});

// streamKey is returned ONCE — store it; the control-plane API only keeps the hash.
console.log({ inputId: input.external_input_id, streamKey });

// Mint a 1-hour signed playback URL to hand to the browser.
const { url } = await streams.liveInputs
  .get({ id: input.external_input_id })
  .then(i => i.signedPlaybackUrl({ ttlSeconds: 3600 }));
console.log("playback URL:", url);
```

## 3. Browser-side: open a viewer

```ts
import { BroadcastViewer } from "@clutchcall/sdk/streams";

const mediaSource = new MediaSource();
videoElement.src  = URL.createObjectURL(mediaSource);
await new Promise(r => mediaSource.addEventListener("sourceopen", r));

const buffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01F,mp4a.40.2"');

const viewer = await BroadcastViewer.open(playbackUrl, {
  onChunk: (init, chunk) => buffer.appendBuffer(chunk.data),
  onClose: reason => console.log("closed:", reason),
});
```

## 4. Push a broadcast (if you're publishing too)

```ts
import { BroadcastPublisher } from "@clutchcall/sdk/streams";

const pub = await BroadcastPublisher.open({
  inputId:   input.external_input_id,
  streamKey,
  codecs:    { video: "avc1.42E01F", audio: "opus" },
});

pub.write(fmp4Init);             // CMAF init segment first
pub.write(fmp4Segment);          // media segments
await pub.close("finished");
```

## What just happened

1. The control-plane API (tRPC) procedure created the live input and returned the cleartext
   stream key once. The control-plane API only keeps a hash.
2. `signedPlaybackUrl` minted a relay token scoped to
   `playback/<input_id>` for an hour.
3. `BroadcastViewer.open` dialled MoQT over WebTransport on
   `relay.clutchcall.dev:443`, presented the token, and started receiving the
   CMAF init + media segments live.
4. `BroadcastPublisher.open` opened a publish track on
   `publish/<input_id>` and the relay fanned it to your viewer.

## Other modalities

The other four follow the same construct → control-plane call → data-plane
publish/subscribe shape:

  - **[Voice](/modalities/voice/details)** — `Voice.calls.originate` + `Voice.audioBridge.attach`
  - **[Robotics](/modalities/robotics/details)** — `Robotics.publishTelemetry` + `subscribeCommand`
  - **[Games](/modalities/games/details)** — `Games.publishState` + `subscribeInputs`
  - **[Data](/modalities/data/details)** — `Data.publish` + `subscribe` with MQTT-style filters
