> For the complete documentation index, see [llms.txt](https://docs.marioded.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.marioded.com/api-how-to-guides/recording.md).

# Recording integrations

Connect recording to your match lifecycle and publish the replay only after saving succeeds.

**Runtime:** recorder · **Entry point:** `ReverseRecorder` · **Lifecycle calls:** Bukkit main thread

Your integration starts a session when the arena is ready and stops that session when the match ends. Keep the returned session ID in your match object so every teardown path stops the correct recording.

If you are new to the API, follow [Creating your first recording](/api-introduction/first-recording.md) and [Improving your recording](/api-introduction/improving-recording.md) first. This page collects the remaining lifecycle, selection, and capture details.

## Connect capture to your match lifecycle

Discover `ReverseRecorder`, obtain its recorder service, and start a session when your arena is ready. Store the returned session ID in your match object. Stop that exact session when the match ends or is cancelled.

The complete [RecordingIntegration example](/api-how-to-guides/examples/recording-integration.md) contains the start/stop flow and persistence error handling. It does not depend on the player runtime.

## Starting a session

The simplest call captures a world with no owners or text:

```java
RecordSession session = recorder.getRecorderService().startRecording(world);
```

For player-facing recordings, specify owners and useful text instead:

```java
RecordSession session = recorder.getRecorderService().startRecording(
    world,
    List.of(ownerId),
    new ReplayText("Arena final", "Best of three"));
```

Start on the Bukkit main thread. Validate user-facing metadata before calling. The start path seeds the initial terrain/entity scene before publishing the session and its start event. Setup failures throw synchronously and do not publish a half-created session.

Owners are deduplicated to an immutable snapshot. Empty is allowed, but such a replay is not accessible to normal nonadmin catalog actors unless your application deliberately uses another policy.

## Spatial and player selection

Use the canonical overload when a world contains several arenas or spectators:

```java
RecordSession session = recorder.getRecorderService().startRecording(
    world,
    null,
    List.of(ownerId),
    new ReplayText("Arena A", "Round 1"),
    new RecordingBounds(-64, 0, -64, 64, 255, 64),
    Set.copyOf(participantIds));
```

There are three independent decisions:

1. **Bounds** limit spatial capture. Null means the whole world selection.
2. **Participant UUIDs** select players. Null means all in-scope players; an empty set selects no players. Nonplayer entities remain spatially selected.
3. **Owners** control the finished replay's catalog access, not capture admission.

Bounds use inclusive block coordinates. Continuous positions use `min <= p < max+1`. Inverted bounds throw. Terrain capture includes full-height payloads of chunk columns intersecting the horizontal box. Bounds and participant selection are session-only, not additional persisted replay fields.

Movement updates only admitted entities. Do not rely on an arbitrary outgoing move of an unknown/outside entity to create membership. The capture system owns admission and exit handling.

## Templates

Supply a template name when the base scene already exists in a terrain provider:

```java
RecordSession session = recorder.getRecorderService().startRecording(
    world, "arena-template", List.of(ownerId), ReplayText.EMPTY);
```

Null/trimmed-empty means no template. Nonempty templates skip runtime terrain capture. The player machine must resolve that template too. Template mode and nonnull bounds are mutually exclusive and throw before capture starts.

## Several sessions in one world

Multiple recordings may observe the same Bukkit world. Each owns independent selection, ticks, terrain identity, and stop lifecycle. Use `getSessionsForWorld(worldId)` to inspect them; a single “session by world” variable is not sufficient to identify a particular match's recording.

`ReverseRecorder.getSession(player)` returns the first session in the player's world, not necessarily the one owned by your match. Use your stored session ID for deterministic lifecycle control.

## Stopping and publishing

```java
CompletionStage<StopResult> saving =
    recorder.getRecorderService().stopSession(session.getUniqueId());
```

Call on the main thread. Capture stops immediately, while persistence proceeds asynchronously.

{% hint style="info" %}
**Stopped is not yet saved.** Publish a Watch link only after the save stage succeeds, and decide how your product should handle a degraded terrain result.
{% endhint %}

| Completion            | What your match system should do                                                                                              |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Exceptional           | Log the replay/session ID; report save failure.                                                                               |
| `replayId() == null`  | Session was not recording; do not publish a new replay link.                                                                  |
| `degraded() == true`  | Events were saved, terrain failed; surface a degraded result or withhold normal Watch links according to your product policy. |
| Normal nondegraded ID | Publish the replay ID.                                                                                                        |

Callbacks have no main-thread guarantee. Schedule Bukkit notifications back to the server thread. `awaitPendingSaves()` is useful for controlled shutdown orchestration, but do not block normal ticks with `join()`/`get()` waiting for backend I/O.

## Adding your own timeline events

Register a matching codec on each runtime that must decode the custom event, and a renderer on the player. Insert through the recorder service for the intended session(s), not by mutating its immutable recordable snapshot.

```java
recorder.getRecorderService().insertRecordable(session, customEvent);
```

Use the iterable-target overload for a chosen set of active sessions, or the world overload deliberately to target recordings in that world. Events are rebased to each session's relative tick. A supplier runs inline on the calling thread, outside the capture gates; do not perform Bukkit reads there if called by a packet thread.

See [custom recordables](/advanced-integrations/recordables.md) for the full end-to-end contract.

## Metadata/profile and packet integrations

The public API also describes metadata/profile provider services and a recorder adapter. Ordinary minigame integrations should use `RecorderService`; they do not need to replace the capture layer or access private plugin internals.

Explicit observation APIs such as `observeRecordable`, `observeWorldSound`, and entity-admission queries are designed for network-thread capture. They impose deduplication, tick sampling, membership, and sound-quorum rules. Use the existing service rather than appending duplicate per-recipient packet events yourself.

{% content-ref url="/pages/Edz9sNPD1CqInThrMtI0" %}
[Recording a match](/api-how-to-guides/examples/recording-integration.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.marioded.com/api-how-to-guides/recording.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
