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 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.
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.
Reactor holds clips for 24 hours and then deletes them. None of this data is used for training.
Capturing a clip
Recording lives on the Reactor instance. In React, reach it via useReactor() inside a
ReactorProvider.
const snap = await reactor.requestClip(10);
const full = await reactor.requestRecording();
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>
);
}
snap = await reactor.request_clip(10)
full = await reactor.request_recording()
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.
Both methods can only be called when the connection status is "ready". They throw a
RecordingError with code DISCONNECTED otherwise, and a
request still in flight when the session disconnects is rejected with the same code.
Previewing a clip
Play a captured clip in place. In React, drop a ClipPlayer
into your UI and pass the clip; it renders a native <video controls> element. Imperatively, assemble
the clip into an MP4 with downloadClipAsFile() and
point a <video> at the blob.
// 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;
import { ClipPlayer } from "@reactor-team/js-sdk";
<ClipPlayer clip={clip} getJwt={() => jwt} />;
ClipPlayer does not require a ReactorProvider in the tree, so it keeps working after the session
has ended.
ClipPlayer streams the clip as HTTP Live Streaming (HLS),
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.
See ClipPlayer for the full prop list.
Downloading a clip
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.
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():
await reactor.downloadClipAsFile(clip, "highlight.mp4");
const blob = await reactor.downloadClipAsFile(clip, null);
await reactor.download_clip_as_file(clip, "highlight.mp4", jwt=jwt)
blob = await reactor.download_clip_as_file(clip, jwt=jwt)
Building your own download UI? The headless
useClipDownload hook exposes the same state
machine as ClipDownloadButton, so you can render a progress bar, a menu item, or anything else.
Putting it together
A small React app that displays a live model, captures a 10-second snap, and previews it alongside a
download button.
"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. The
React components surface it inline already, so you only need to catch it when you call requestClip
or requestRecording directly.
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);
}
}
from reactor_sdk import RecordingError
try:
clip = await reactor.request_clip(10)
except RecordingError as err:
print(err.code, err.reason)
See RecordingError for the full list of codes.