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

# Recording a match

> Start a bounded match recording and handle asynchronous persistence outcomes.

# Recording a match

**Runtime:** recorder · **Use it for:** match start/end hooks

Follow the [recording guide](https://docs.marioded.com/api/recording.md) for the lifecycle and selection rules behind this example.

Declare `depend: [reverse-recorder]` in this integration's plugin descriptor. Call `startMatch` and `finishMatch` from your own main-thread match lifecycle, and store the returned session UUID with your match. Adjust the bounds, owners, and participants to your arena.

This example only publishes a normal replay-ready notification after event persistence and terrain have succeeded. A degraded save retains events but needs separate handling.

`exportRecentClip(sessionId)` saves the trailing ten seconds without stopping capture. `exportClip(sessionId, from, to)` uses inclusive offsets from the recording's start: pass `Duration.ZERO` to start at the beginning, or a null `to` to end at the accepted request's current tick. For example, seconds 30–45 use `Duration.ofSeconds(30)` and `Duration.ofSeconds(45)`. The exported replay has a fresh UUID, rebased events and copied terrain. All three methods require the main thread; each returns the same save-outcome contract. Handle synchronous invalid-duration failures in your command layer too.

This example requires a nonnull participant set and snapshots it with `Set.copyOf`: empty means no players. To capture all in-scope players through the service, pass null instead. Owners still control access independently. Packet-recipient identity is not a participant filter.

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

import com.marioded.reverse.api.RecordSession;
import com.marioded.reverse.api.RecordingBounds;
import com.marioded.reverse.api.ReplayText;
import com.marioded.reverse.api.catalog.ReplayCatalog;
import com.marioded.reverse.api.recorder.ReverseRecorder;
import com.marioded.reverse.api.recorder.service.StopResult;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletionStage;
import java.util.logging.Level;
import org.bukkit.World;
import org.bukkit.plugin.java.JavaPlugin;

/** Example match hooks; invoke start/finish from your own main-thread match lifecycle. */
public final class RecordingIntegration extends JavaPlugin {
  private ReverseRecorder recorder;

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

  public UUID startMatch(World world, UUID ownerId, Set<UUID> participants) {
    ReplayCatalog.validateMetadata("Arena final", "Best of three");
    RecordSession session = recorder.getRecorderService().startRecording(
        world, null, List.of(ownerId), new ReplayText("Arena final", "Best of three"),
        new RecordingBounds(-64, 0, -64, 64, 255, 64), Set.copyOf(participants));
    return session.getUniqueId(); // Store this in your match object.
  }

  public CompletionStage<StopResult> finishMatch(UUID sessionId) {
    return reportSave(sessionId, recorder.getRecorderService().stopSession(sessionId));
  }

  /** Main thread: export the last ten seconds while the source keeps recording. */
  public CompletionStage<StopResult> exportRecentClip(UUID sessionId) {
    return reportSave(sessionId, recorder.getRecorderService().saveRecentClip(
        sessionId, Duration.ofSeconds(10), new ReplayText("Recent round", "Last ten seconds")));
  }

  /** Main thread: export inclusive offsets; null to means the current tick. */
  public CompletionStage<StopResult> exportClip(UUID sessionId, Duration from, Duration to) {
    return reportSave(sessionId, recorder.getRecorderService().saveClip(
        sessionId, from, to, new ReplayText("Round clip", "Selected interval")));
  }

  private CompletionStage<StopResult> reportSave(
      UUID sessionId, CompletionStage<StopResult> saving) {
    saving.whenComplete((result, failure) -> {
      if (failure != null) {
        getLogger().log(Level.SEVERE, "Could not save recording " + sessionId, failure);
        return;
      }
      if (result.replayId() == null) {
        getLogger().warning("Source session was not recording: " + sessionId);
        return;
      }
      if (result.degraded()) {
        getLogger().log(Level.WARNING, "Events saved without usable terrain: " + result.replayId(),
            result.terrainFailure());
        return;
      }
      // Publish the UUID to your match database or application here.
      // Schedule onto the main thread before touching Bukkit player/world state.
      getLogger().info("Replay ready: " + result.replayId());
    });
    return saving;
  }
}
```

Copy this class into your integration and connect its methods to your match lifecycle.


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