> ## 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.

> ## Agent Instructions
> To build and serve your own model, start at /deploy/development/quickstart and /deploy/development/overview. Deploying is the default path: reactor init scaffolds a workspace, reactor auth login authenticates, and reactor model deploy registers the model, publishes the release with the weights/ folder, and activates it on Reactor's GPUs, in one command from that workspace. Docker must be running, because the publish step builds the image locally. Bump model.version in reactor.yaml before redeploying a change, because a release that already has an image is reactivated as it is. Deployment access is granted per account, so contact team@reactor.inc if a deploy is refused. Every key in reactor.yaml is documented at /deploy/platform/reactor-yaml. Model code imports reactor_runtime; Python client code imports reactor_sdk. The runtime overview explains the model interface. Running the model on your own machine with reactor run is optional and needs a GPU you attach with --gpus; /deploy/development/local-testing covers that loop and pairs a complete brightness model with a Python client test in a separate brightness-test workspace.
> Reactor hosts multiple models, each with its own connect slug (modelName) and command/event schema. The catalog of every model — slug, typed SDK package, and links to its schema — is at /model-api-reference/overview. Some models expose one slug per experience (e.g. HappyOyster); always take the slug from the model's own pages, never guess it.
> Fastest path to a working app: `npx create-reactor-app my-app --model=<slug>` scaffolds a complete app with secure auth wired up. Typed TypeScript SDKs are published as @reactor-models/<model>; Python uses the base reactor-sdk package.
> Auth: exchange an API key (rk_...) for a JWT via POST https://api.reactor.inc/tokens from your server. Never put the API key in client-side code.
> Append .md to any docs URL for clean Markdown. Search these docs via the MCP server at https://docs.reactor.inc/mcp.

# Track

> Receive and send named video and audio tracks with the Java SDK

A `Track` is a named media stream declared by the model. Direction is from the client's perspective:
`RECVONLY` receives from the model, and `SENDONLY` sends to it. `TrackKind` is `VIDEO` or `AUDIO`.
The
[Track Javadoc](https://javadoc.io/doc/inc.reactor/reactor-sdk/1.0.0/inc.reactor.sdk/inc/reactor/sdk/Track.html)
lists every operation.

The snippets below assume a connected `Reactor reactor` and imports from `inc.reactor.sdk`.

## Find a track

Look up a known name after connecting, or filter the declared tracks:

```java theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
Track video = reactor.track("main_video");
Track output = reactor.tracks()
        .withKind(TrackKind.VIDEO)
        .withDirection(TrackDirection.RECVONLY)
        .one();
```

`tracks()` is empty before the session declares its tracks and after disconnecting. `track(name)`
throws if that name has not been declared, including before connecting. `TrackList.byName(name)`
instead returns an `Optional<Track>`. `one()` requires exactly one match and throws if there are
zero or multiple matches. You can also iterate, call `asList()`, or use `stream()`.

A track exposes `name()`, `kind()`, `direction()`, and an optional `mid()`.

## Receive frames

Register a handler on a receiving track. Specify the lambda parameter type: `onFrame` is overloaded
for video and audio, so an untyped lambda is ambiguous.

```java theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
Subscription frames = reactor.track("main_video").onFrame((VideoFrame frame) -> {
    System.out.println(frame.width() + "x" + frame.height());
});
```

Call `frames.close()` when you no longer need the handler. Registering a video handler on an audio
track, or any frame handler on a sending track, throws `ReactorException`.

| Frame        | Data                                                              | Metadata                                                          |
| ------------ | ----------------------------------------------------------------- | ----------------------------------------------------------------- |
| `VideoFrame` | `pixels()`: read-only BGRA memory, `width() * height() * 4` bytes | `width()`, `height()`, `frameId()`, `timestampUs()`, `userData()` |
| `AudioFrame` | `samples()`: interleaved signed 16-bit PCM memory                 | `sampleCount()`, `sampleRate()`, `channels()`                     |

<Warning>
  Frame memory is valid only while the callback runs. To keep pixels or samples, call
  `VideoFrame.toByteArray()` or `AudioFrame.toShortArray()` inside the callback. Reading the memory
  after the callback returns throws `IllegalStateException`.
</Warning>

Frame handlers run on a media delivery thread, independently of the control-event dispatcher. Copy
media before handing it to another thread or a UI. Keep any application queue bounded: a slow video
handler causes intermediate frames to be dropped in favor of the newest one.

A video's `timestampUs()` uses the sender's clock, so compare it only with timestamps from that same
sender. It is not a local wall-clock time. `userData()` returns an optional copy of the sender's tag
that can outlive the callback.

## Send frames

Choose a sending track and await `publish()` before pushing data. Track names depend on the model;
this example finds its single video input:

```java theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
Track input = reactor.tracks()
        .withKind(TrackKind.VIDEO)
        .withDirection(TrackDirection.SENDONLY)
        .one();
input.publish().join();

int width = 640;
int height = 480;
byte[] bgra = new byte[width * height * 4];
for (int i = 3; i < bgra.length; i += 4) {
    bgra[i] = (byte) 255;
}
input.pushFrame(bgra, width, height);
input.unpublish();
```

Video input must contain exactly `width * height * 4` bytes in B, G, R, A order. An overload accepts
`userData` and `captureTimeUs` after the dimensions. Use `reactor.timeMicros()` for an explicit
capture time, or `null` for now.

For audio, call `pushFrame(short[] pcm, int sampleRate, int channels)` on a published audio input.
Samples are interleaved signed 16-bit PCM. Supported rates are 8,000, 16,000, 24,000, 32,000,
44,100, and 48,000 Hz, with one or two channels. The sample count must divide evenly by the channel
count.

`isPublished()` reports whether a sender is ready. `publishState()` returns `UNPUBLISHED`,
`PUBLISHING`, or `PUBLISHED`. Pushing before publication completes, after unpublishing, or to a
receiving track throws. Publication does not survive reconnect: await `publish()` again before
sending more frames.

## Pause and bitrate

`pause()` and `resume()` return `CompletableFuture<Void>`. `isPaused()` reports the current state.
`setBitrate(minBps, maxBps)` sets track bitrate bounds in bits per second; negative values leave the
corresponding bound unset.

The base SDK does not capture or play device audio. Add the optional `inc.reactor:reactor-sdk-audio`
module for microphone and speaker helpers; see
[Installation](/sdk-reference/java/installation#optional-modules).
