> 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-introduction/first-recording.md).

# Creating your first recording

Create a minimal recording integration and connect it to your game's start and end hooks.

Let's record a match from our own plugin. We will use the **recorder runtime** and build a small `FirstRecordingPlugin` that starts capture, stops it, and logs the save result.

Complete [Setting up](/api-introduction/getting-started.md) first. All start/stop calls in this tutorial run on the Bukkit main thread.

## Get the recorder

Start by discovering the active runtime in `onEnable`:

{% code title="FirstRecordingPlugin.java — runtime discovery" %}

```java
private ReverseRecorder recorder;
private RecordSession activeSession;

@Override
public void onEnable() {
    recorder = ReverseRecorder.get().orElseThrow(
        () -> new IllegalStateException("Reverse recorder is not available"));
}
```

{% endcode %}

`recorder` is the service entry point. `activeSession` will hold the recording started by this example. This simple version intentionally manages one active match at a time.

## Start the match recording

Add a method your game's match-start handler can call:

```java
public UUID startMatch(World world, UUID ownerId) {
    if (activeSession != null) {
        throw new IllegalStateException("This integration already has an active recording");
    }
    activeSession = recorder.getRecorderService().startRecording(
        world, List.of(ownerId), new ReplayText("Arena final", "First integration"));
    return activeSession.getUniqueId();
}
```

Let's break down the call:

* `world` is the live Bukkit world to record.
* `List.of(ownerId)` gives that player catalog access to the finished replay.
* `ReplayText` sets the name and description shown to users.
* The returned session ID identifies this recording, not the Bukkit world.

When start succeeds, Reverse has prepared the initial scene and registered the session. If setup fails, the call throws instead of returning a half-started recording.

## Stop capture and wait for saving

At match end, stop the same session:

```java
UUID sessionId = activeSession.getUniqueId();
CompletionStage<StopResult> saving = recorder.getRecorderService().stopSession(sessionId);
activeSession = null;
```

Capture has now stopped, but the save may still be running. Clearing our reference allows another match to start while that I/O finishes. It does not cancel persistence.

Use the stage to decide when the replay is ready:

```java
saving.whenComplete((result, failure) -> {
    if (failure != null) {
        getLogger().log(Level.SEVERE, "Could not save recording " + sessionId, failure);
    } else if (result.replayId() == null) {
        getLogger().warning("Recording was already stopped: " + sessionId);
    } else if (result.degraded()) {
        getLogger().log(Level.WARNING, "Terrain unavailable for replay " + result.replayId(),
            result.terrainFailure());
    } else {
        getLogger().info("Replay ready: " + result.replayId());
    }
});
```

There are four outcomes here: save failure, an already stopped session, saved events with failed terrain, or a usable saved replay. We only log a normal “ready” result in the last case.

{% hint style="info" %}
The completion callback has no main-thread guarantee. Logging is fine here; schedule Bukkit player messages and world operations back to the main thread. See [Threading and failure handling](/api-how-to-guides/threading-and-failures.md).
{% endhint %}

## Connect it to your game

Declare the recorder dependency:

{% code title="plugin.yml" %}

```yaml
name: FirstRecording
version: 1.0.0
main: example.FirstRecordingPlugin
depend: [reverse-recorder]
```

{% endcode %}

Call `startMatch(world, ownerId)` from your main-thread match-start handler and `stopMatch()` from the match-end or cancellation handler. These are integration methods, **not automatically registered commands**. The example's `onDisable` also requests stop for any recording it still owns.

After a successful save, keep the replay UUID with your match. On a player server connected to the same replay and terrain storage, an authorized owner/admin can use `/reverseplayer play <replay-id>` to check the result. The recorder runtime alone does not provide playback.

<details>

<summary>Complete FirstRecordingPlugin.java</summary>

{% code title="FirstRecordingPlugin.java" %}

```java
package example;

import com.marioded.reverse.api.RecordSession;
import com.marioded.reverse.api.ReplayText;
import com.marioded.reverse.api.recorder.spi.ReverseRecorder;
import com.marioded.reverse.api.recorder.spi.service.StopResult;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletionStage;
import java.util.logging.Level;
import org.bukkit.World;
import org.bukkit.plugin.java.JavaPlugin;

public final class FirstRecordingPlugin extends JavaPlugin {
  private ReverseRecorder recorder;
  private RecordSession activeSession;

  @Override
  public void onEnable() {
    recorder = ReverseRecorder.get().orElseThrow(
        () -> new IllegalStateException("Reverse recorder is not available"));
  }

  public UUID startMatch(World world, UUID ownerId) {
    if (activeSession != null) {
      throw new IllegalStateException("This integration already has an active recording");
    }
    activeSession = recorder.getRecorderService().startRecording(
        world, List.of(ownerId), new ReplayText("Arena final", "First integration"));
    return activeSession.getUniqueId();
  }

  public CompletionStage<StopResult> stopMatch() {
    if (activeSession == null) {
      throw new IllegalStateException("This integration has no active recording");
    }
    UUID sessionId = activeSession.getUniqueId();
    CompletionStage<StopResult> saving = recorder.getRecorderService().stopSession(sessionId);
    activeSession = null;
    saving.whenComplete((result, failure) -> {
      if (failure != null) {
        getLogger().log(Level.SEVERE, "Could not save recording " + sessionId, failure);
      } else if (result.replayId() == null) {
        getLogger().warning("Recording was already stopped: " + sessionId);
      } else if (result.degraded()) {
        getLogger().log(Level.WARNING, "Terrain unavailable for replay " + result.replayId(),
            result.terrainFailure());
      } else {
        getLogger().info("Replay ready: " + result.replayId());
      }
    });
    return saving;
  }

  @Override
  public void onDisable() {
    if (recorder != null && activeSession != null) stopMatch();
  }
}
```

{% endcode %}

Copy this complete class into your own integration project and add the `plugin.yml` shown above.

</details>

## What we have built

We can now discover Reverse, start a recording, stop the exact session we created, and distinguish all persistence outcomes. Next, let's limit capture to an arena and choose the recorded players.

{% content-ref url="/pages/0LEWfxDYhFUYnk0ko4Aa" %}
[Improving your recording](/api-introduction/improving-recording.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-introduction/first-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.
