> ## Documentation Index
> Fetch the complete documentation index at: https://docs.marioded.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating your first recording

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

# Creating your first recording

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](https://docs.marioded.com/api/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`:

```java theme={null}
private ReverseRecorder recorder;
private RecordSession activeSession;

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

`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 theme={null}
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 theme={null}
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 theme={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());
    }
});
```

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.

> [!NOTE]
>
> 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](https://docs.marioded.com/api/threading-and-failures.md).

## Connect it to your game

Declare the recorder dependency:

```yaml theme={null}
name: FirstRecording
version: 1.0.0
main: example.FirstRecordingPlugin
depend: [reverse-recorder]
```

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.

```java theme={null}
package example;

import com.marioded.reverse.api.RecordSession;
import com.marioded.reverse.api.ReplayText;
import com.marioded.reverse.api.recorder.ReverseRecorder;
import com.marioded.reverse.api.recorder.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();
  }
}
```

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

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

[Improve your recording →](https://docs.marioded.com/api/improving-recording.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.