> 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/events.md).

# Events

React when capture starts, saving finishes, or viewers change playback state.

Reverse exposes Bukkit events so integrations can observe lifecycle without patching command handlers. Register listeners through Bukkit's PluginManager using your integration plugin as the owner.

{% hint style="info" %}
**Choose the event for the outcome you need.** Use `RecordingStopEvent` for capture ending, `RecordingSavedEvent` for persisted data, and `PlaybackStopEvent` for playback cleanup finishing.
{% endhint %}

## Recording events

Imports use `com.marioded.reverse.api.recorder.event`.

| Event                      | When it matters                    | Useful information                                                   |
| -------------------------- | ---------------------------------- | -------------------------------------------------------------------- |
| `RecordingStartEvent`      | A fully seeded session was started | Session and world name.                                              |
| `RecordingStopEvent`       | Capture stopped                    | Session being stopped; not confirmation of persistence.              |
| `RecordingSavedEvent`      | Event data was saved               | Replay ID, count of ticks carrying events, optional terrain failure. |
| `RecordingSaveFailedEvent` | Event persistence failed           | Replay ID and cause.                                                 |

A saved replay can still be degraded. Check `isDegraded()`/`getTerrainFailure()` instead of assuming every `RecordingSavedEvent` has usable terrain. `getTickCount()` counts occupied event ticks, not wall-clock seconds or a guarantee that the timeline is dense.

Save notifications are dispatched directly on the completion thread. Schedule any Bukkit UI, player lookup, or world access onto the main thread. Events may be attempted during shutdown; only listeners still registered receive them. Keep persistent match bookkeeping in the save stage or your own durable state if receiving an event during teardown is not sufficient.

## Playback events

Imports use `com.marioded.reverse.api.player.event`.

| Event                      | What it tells you                                                           |
| -------------------------- | --------------------------------------------------------------------------- |
| `PlaybackStartEvent`       | A new session started, with its loaded replay and accepted viewer snapshot. |
| `PlaybackPauseEvent`       | The session changed to paused.                                              |
| `PlaybackResumeEvent`      | The session resumed.                                                        |
| `PlaybackSeekEvent`        | A seek occurred; includes previous and target ticks.                        |
| `PlaybackSpeedChangeEvent` | Speed changed; includes previous and new speed.                             |
| `PlaybackViewerLeaveEvent` | One viewer left a session.                                                  |
| `PlaybackStopEvent`        | The session ended and was unregistered/cleaned up.                          |

Stop is a post-cleanup notification. The session is no longer in the service's active registry. Its viewer snapshot contains those present when stopping began; earlier leavers are not included, and handles can already be offline. `session.isFinished()` distinguishes reaching the timeline end from an early stop; there is no separate public stop-reason enum.

## Listener pattern

Let's react when a replay finishes saving. In a JavaPlugin that implements Bukkit `Listener`, register the listener during enable:

```java
getServer().getPluginManager().registerEvents(this, this);
```

Then add the handler:

```java
@EventHandler
public void onSaved(RecordingSavedEvent event) {
    UUID replayId = event.getReplayId();
    boolean degraded = event.isDegraded();
    if (!isEnabled()) return;
    getServer().getScheduler().runTask(this, () -> {
        getLogger().info("Saved " + replayId + (degraded ? " (terrain unavailable)" : ""));
    });
}
```

We copy the result values before scheduling the main-thread application callback. Replace the logging hook with the notification your application needs; do not assume the save event itself was synchronous.

The complete [ReplayNotifications example](/api-how-to-guides/examples/replay-notifications.md) also demonstrates a playback stop listener. Keep event handlers short; do not load an entire replay or block on SQL inside a handler.

Events expose `isAsynchronous()` according to the constructing thread. Ordinary player runtime operations should occur on the main thread, but use the event's actual context instead of assuming every event delivered through Bukkit is synchronous.

## Notifications, not cancellation hooks

These events do not implement Bukkit `Cancellable`. They are not a supported way to veto a recording save, deny a viewer after playback starts, or rewrite seek behavior. Enforce access before starting/dispatching, and use service operations for intentional lifecycle changes.

Avoid an unconditional listener that responds to a pause event by immediately setting the opposite pause state: reentrant state changes can create event loops. Likewise, do not retain mutable playback contexts as asynchronous work items.

{% content-ref url="/pages/S5Fu06g4VrSImF7P7dnF" %}
[Handling notifications](/api-how-to-guides/examples/replay-notifications.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/events.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.
