@reactor-models/longlive-v2 SDK throughout.
Installation and setup
Get the example running locally before reading further. Every section below points back at code in the repo. You will need:- Node.js 18+.
- pnpm (the example pins lockfiles to pnpm;
npmoryarnwill work but you’ll regenerate the lockfile). - A Reactor API key (starts with
rk_). - Familiarity with the Next.js App Router.
Clone the example
The example lives alongside our other reference apps in
reactor-team/js-sdk under examples/.Add your API key
Your
rk_… key must never reach the browser; the example reads it server-side and mints a
short-lived JWT for the client. We’ll cover the broker pattern below; for now, drop the key
into .env:How LongLive-2.0 works
Building with LongLive-2.0 is different from calling a typical generative API. There’s no prompt-in / video-out request. You open a long-lived connection, set an opening shot, and the model begins producing a continuous stream of 29-frame chunks (~1.2s each). You direct it by sending evolving shots (same scene) and hard cuts (new scene), in real-time or scheduled ahead against the cumulativesession_chunk clock. A single scene can run up to 48 chunks (~58s). A cut resets that
budget and extends the video. See
Chunks, scenes, and length.
Opening the connection isn’t instant. Reactor provisions a GPU for your session, so the client moves
through four states before media starts flowing:
waiting state is when the GPU is being assigned, which takes a few seconds. Once the status
reaches ready, commands take effect and chunks start arriving. See
Sessions for the full breakdown.
Two properties of the API are worth internalizing before you read on:
- Commands are asynchronous; events are the source of truth. Calling
setShotdoesn’t mean the next chunk uses it; the model confirms withshot_set, and everystatesnapshot reflects the authoritative session position. - Errors arrive out-of-band. A broken precondition like
startwith no opening shot surfaces later as acommand_errorevent, not a thrown exception.
state snapshot. While idle you compose a plan
(<Storyboard>); once snapshot.started is true you can direct it live (<NowPlaying> +
<Director>). app/LongLiveApp.tsx wires the provider and the phase-driven layout; each panel
subscribes with useLongliveV2State and returns null when it’s not its phase.
Authentication
LongLive-2.0 opens a long-lived WebRTC connection the server needs to trust for hours. Shipping a rawrk_… key to the browser would hand full account access to anyone with devtools open. Instead,
the SDK presents a JWT minted server-side from your API key,
short-lived (Reactor caps it at 6 hours), and safe to hand to the client. Your rk_… key stays on
the server.
Every LongLive frontend needs one server-side route that mints JWTs. In the example that’s
app/api/reactor/token/route.ts, which exchanges your rk_… key for a JWT and returns it with a
Cache-Control header derived from the token’s real expiry:
LongLiveApp.tsx hands a resolver (not a static string) to <LongliveV2Provider getJwt>. The
SDK calls it on every Coordinator hop (clip manifests, ICE refreshes), and the route’s
Cache-Control header lets the browser serve repeat calls from its HTTP cache until the JWT
expires:
app/LongLiveApp.tsx
Composing a storyboard
LongLive-2.0’s signature is directing a whole sequence up front.Storyboard.tsx is the setup-phase
panel: you set an opening shot, then add shots (soft, same scene) and cuts (hard, new scene)
at chunk positions. The example calls each one a beat, a { kind, prompt, atChunk } entry in the
storyboard store. “Start storyboard” compiles the plan into the wire sequence (the opener with
setShot, each later beat with scheduleShot / scheduleSceneCut, then start):
app/components/Storyboard.tsx
app/lib/storyboard-store.ts),
distinct from the model’s live position, which you read from useLongliveV2State. Presets in
app/lib/prompts.ts load full multi-shot sequences in one click; their header comment is worth
reading, because terse prompts produce weak output: every beat is a dense, cinematic paragraph
on purpose. See the prompt guide.
The chunk timeline
Timeline.tsx is a read-only visual of the plan on a chunk axis: scene dividers at each cut, beats
as ticks, and (once running) a playhead at the model’s cumulative session_chunk. It’s the clearest
way to see the shot-vs-cut grammar and the per-scene budget:
app/components/Timeline.tsx
session_chunk is the cumulative clock that never resets; it’s what scheduled beats fire against
and where the playhead sits. current_chunk (used in Now playing below) is the per-scene counter
that resets to 0 on every cut.
Going live
Once generation starts, the sidebar flips to its live panels.StatusBadge.tsx is the user’s window
into the four-state connection machine. Every state, including the multi-second waiting GPU step,
gets a visible label:
app/components/StatusBadge.tsx
NowPlaying.tsx mirrors the state snapshot: the active prompt, the per-scene budget
(current_chunk / 48) and the cumulative session_chunk, plus pause / resume / reset transport.
Subscribe once and read fields off the snapshot, no event aggregation:
app/components/NowPlaying.tsx
<LongliveV2MainVideoView />, a pre-bound
<ReactorView track="main_video"> that handles the <video> element, srcObject binding, and
autoplay quirks:
app/components/Video.tsx
Directing live
Director.tsx is the live-phase counterpart to the storyboard: fire a soft shot or hard cut at the
next chunk boundary (“now”), or schedule one ahead at a chunk index. Same four methods, applied to a
running session:
app/components/Director.tsx
setShot keeps the scene’s memory and continuity; a hard sceneCut makes a clean break to a
new scene and resets the budget. Choosing between them is the core creative decision. See
Shots vs cuts.
Snapping a clip
The SDK ships recording primitives so you don’t have to wire upMediaRecorder yourself.
SnapClip.tsx captures the last few seconds of the live stream and opens a modal with the SDK’s
built-in preview player and a download button:
app/components/SnapClip.tsx
@reactor-team/js-sdk, not @reactor-models/longlive-v2. Recording
is a base-SDK feature: it works the same for every Reactor model, and the typed model packages
don’t re-export the recording surface, so direct base-SDK imports are idiomatic here. <ClipPlayer>
and <ClipDownloadButton> auto-inherit the JWT resolver from <LongliveV2Provider getJwt={…}> via
React context. Errors come back as a RecordingError with a typed code and reason, distinct
from the command_error events covered next.
Surfacing command_error
Every LongLive-2.0 command can fail a precondition check:start with no opening shot, a beat
scheduled in the past, an empty prompt. CommandError.tsx never lets these fail silently:
app/components/CommandError.tsx
useLongliveV2CommandError is the typed wrapper for the command_error message. The panel renders
nothing until an error arrives and clears itself on the next snapshot so a stale banner can’t pile
up.
What’s intentionally left out
Not every pattern is surfaced in this demo. See below for what’s missing and how to add it.| Feature | How to add it |
|---|---|
| Draggable timeline | Timeline.tsx is read-only here. Make beats draggable to reschedule them: re-emit scheduleShot / scheduleSceneCut at the new chunk. The Reactor webapp playground has the full editor. |
| Removing a scheduled beat | There’s no unschedule command in this release; reset clears everything. Compose the full sequence before start, or fire live beats from <Director> as you go. |
| Reference images | LongLive-2.0 is text-to-video only. There is no image conditioning. Describe the look in words; see the prompt guide. |
skill/SKILL.md in
the example repo.