> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reactor.inc/llms.txt
> Use this file to discover all available pages before exploring further.

# Recordings

> Save clips of a live session, preview them, and download them as MP4 files

## What is a clip?

Once a model has recording enabled, your app can ask Reactor for two types of clip from a live
session:

* **Snap clip**: the last `N` seconds of the session.
* **Full recording**: everything from the start of the session up to now.

Both return a [`Clip`](/sdk-reference/types#clip) object you can hand to the SDK's player to
preview, or to its download helpers to save as a single MP4. The same object works either way.

<Warning>
  Reactor does not host clips. The URL you receive from Reactor expires after 24 hours,
  so it is not suitable for sharing. If you want users to keep a clip, download it
  immediately and host the resulting MP4 yourself.
</Warning>

<Note>
  Reactor holds clips for 24 hours and then deletes them. None of this data is used for training.
</Note>

***

## Capturing a clip

Recording lives on the `Reactor` instance. In React, reach it via `useReactor()` inside a
`ReactorProvider`.

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  const snap = await reactor.requestClip(10);
  const full = await reactor.requestRecording();
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { useReactor } from "@reactor-team/js-sdk";

  function SnapButton() {
    const { status, reactor } = useReactor((s) => ({
      status: s.status,
      reactor: s.internal.reactor,
    }));

    return (
      <button
        disabled={status !== "ready"}
        onClick={async () => {
          const clip = await reactor.requestClip(10);
          console.log("Captured", clip.kind, clip.sessionId);
        }}
      >
        Save last 10 seconds
      </button>
    );
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  snap = await reactor.request_clip(10)
  full = await reactor.request_recording()
  ```
</CodeGroup>

`requestClip` / `request_clip` takes a duration in seconds and grabs the live session up to that
point in the past. It is capped server-side at 5 minutes by default. `requestRecording` /
`request_recording` takes no arguments and grabs the full session from the start of recording up to
now. Both resolve with the same `Clip` object.

<Note>
  Both methods can only be called when the connection status is `"ready"`. They throw a
  [`RecordingError`](/sdk-reference/types#recordingerror) with code `DISCONNECTED` otherwise, and a
  request still in flight when the session disconnects is rejected with the same code.
</Note>

***

## Previewing a clip

Play a captured clip in place. In React, drop a [`ClipPlayer`](/sdk-reference/react-components#clipplayer)
into your UI and pass the clip; it renders a native `<video controls>` element. Imperatively, assemble
the clip into an MP4 with [`downloadClipAsFile()`](/sdk-reference/reactor-class#downloadclipasfile) and
point a `<video>` at the blob.

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  // downloadClipAsFile assembles the clip into a single MP4 and authenticates
  // with the jwt. Point a <video> element at the resulting blob to play it.
  const blob = await reactor.downloadClipAsFile(clip, null, { jwt });

  const video = document.querySelector("video")!;
  video.src = URL.createObjectURL(blob);
  video.controls = true;
  ```

  ```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { ClipPlayer } from "@reactor-team/js-sdk";

  <ClipPlayer clip={clip} getJwt={() => jwt} />;
  ```
</CodeGroup>

`ClipPlayer` does not require a `ReactorProvider` in the tree, so it keeps working after the session
has ended.

<Note>
  `ClipPlayer` streams the clip as [HTTP Live Streaming (HLS)](https://www.cloudflare.com/learning/video/what-is-http-live-streaming/),
  a protocol that breaks video into small chunks served over HTTP. Safari plays it natively, while
  Chrome, Firefox, and Edge need `hls.js` installed as an optional peer dependency. The imperative
  `downloadClipAsFile` route above returns a plain MP4 and needs no extra dependency.

  ```bash theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  pnpm add hls.js
  ```
</Note>

See [`ClipPlayer`](/sdk-reference/react-components#clipplayer) for the full prop list.

***

## Downloading a clip

[`ClipDownloadButton`](/sdk-reference/react-components#clipdownloadbutton) renders a plain `<button>`
that reflects download state and triggers a browser save dialog when clicked. Pass the same token you
use to connect as `getJwt` so the component can authenticate the download.

```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
import { ClipDownloadButton } from "@reactor-team/js-sdk";

<ClipDownloadButton clip={clip} getJwt={() => jwt} />;
```

For non-React apps, or when you need the assembled MP4 directly, call `downloadClipAsFile()` /
`download_clip_as_file()`:

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  await reactor.downloadClipAsFile(clip, "highlight.mp4");

  const blob = await reactor.downloadClipAsFile(clip, null);
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  await reactor.download_clip_as_file(clip, "highlight.mp4", jwt=jwt)

  blob = await reactor.download_clip_as_file(clip, jwt=jwt)
  ```
</CodeGroup>

<Tip>
  Building your own download UI? The headless
  [`useClipDownload`](/sdk-reference/react-hooks#useclipdownload) hook exposes the same state
  machine as `ClipDownloadButton`, so you can render a progress bar, a menu item, or anything else.
</Tip>

***

## Putting it together

A small React app that displays a live model, captures a 10-second snap, and previews it alongside a
download button.

```tsx React theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
"use client";

import { useState } from "react";
import {
  ClipPlayer,
  ClipDownloadButton,
  ReactorProvider,
  ReactorView,
  useReactor,
} from "@reactor-team/js-sdk";
import type { Clip } from "@reactor-team/js-sdk";

export default function App({ token }: { token: string }) {
  return (
    <ReactorProvider
      modelName="your-model-name"
      jwtToken={token}
      connectOptions={{ autoConnect: true }}
    >
      <ReactorView className="w-full aspect-video rounded-xl" />
      <Controls token={token} />
    </ReactorProvider>
  );
}

function Controls({ token }: { token: string }) {
  const { status, reactor } = useReactor((s) => ({
    status: s.status,
    reactor: s.internal.reactor,
  }));
  const [clip, setClip] = useState<Clip | null>(null);

  return (
    <div className="mt-4 flex gap-3 items-center">
      <button
        disabled={status !== "ready"}
        onClick={async () => setClip(await reactor.requestClip(10))}
      >
        Snap last 10s
      </button>

      {clip && (
        <div className="fixed inset-0 bg-black/80 flex items-center justify-center">
          <div className="bg-neutral-900 p-4 rounded-xl w-[640px]">
            <ClipPlayer clip={clip} getJwt={() => token} />
            <div className="mt-3 flex justify-end gap-2">
              <ClipDownloadButton clip={clip} getJwt={() => token} />
              <button onClick={() => setClip(null)}>Close</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
```

***

## Error handling

Any recording-specific failure raises a [`RecordingError`](/sdk-reference/types#recordingerror). The
React components surface it inline already, so you only need to catch it when you call `requestClip`
or `requestRecording` directly.

<CodeGroup>
  ```typescript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { RecordingError } from "@reactor-team/js-sdk";

  try {
    const clip = await reactor.requestClip(10);
  } catch (err) {
    if (err instanceof RecordingError) {
      console.error(err.code, err.reason);
    }
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  from reactor_sdk import RecordingError

  try:
      clip = await reactor.request_clip(10)
  except RecordingError as err:
      print(err.code, err.reason)
  ```
</CodeGroup>

See [`RecordingError`](/sdk-reference/types#recordingerror) for the full list of codes.
