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

# Handling notifications

> Observe save completion and playback termination while respecting event threading.

# Handling notifications

**Runtime:** shared API plus recorder/player bindings · **Use it for:** save, playback, and catalogue notifications

Follow the [event guide](https://docs.marioded.com/api/events.md) to choose the correct notification and completion thread.

The save event may be asynchronous. Copy the values you need and schedule Bukkit-facing work onto the main thread. A stop event is a post-cleanup playback notification, not a cancellation hook.

This teaching example imports both role APIs. Split it into role-specific listeners for independently deployed recorder and player integrations; do not eagerly resolve an absent role's event classes.

The catalogue handlers use the shared API and only log, so they need no Bukkit thread dispatch. They observe calls through the runtime's catalogue wrapper, not arbitrary raw backend calls. A refused metadata update still emits a result; an exceptional update does **not** emit `ReplayMetadataUpdatedEvent`. Retain stage failure handling for that case. A deletion stage failure emits `ReplayDeletedEvent` with its cause. Synchronous failures before a backend stage is returned do not guarantee either event.

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

import com.marioded.reverse.api.catalog.ReplayCatalog;
import com.marioded.reverse.api.catalog.event.ReplayDeletedEvent;
import com.marioded.reverse.api.catalog.event.ReplayMetadataUpdatedEvent;
import com.marioded.reverse.api.player.event.PlaybackStopEvent;
import com.marioded.reverse.api.recorder.event.RecordingSavedEvent;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;

/** Install this dual-role example only where both event API modules are available. */
public final class ReplayNotifications extends JavaPlugin implements Listener {
  @Override
  public void onEnable() {
    Bukkit.getPluginManager().registerEvents(this, this);
  }

  @EventHandler
  public void onSaved(RecordingSavedEvent event) {
    UUID replayId = event.getReplayId();
    boolean degraded = event.isDegraded();
    if (!isEnabled()) return;
    Bukkit.getScheduler().runTask(this, () -> {
      // Replace this logging hook with your application's main-thread notification.
      getLogger().info("Saved " + replayId + (degraded ? " (terrain unavailable)" : ""));
    });
  }

  @EventHandler
  public void onPlaybackStopped(PlaybackStopEvent event) {
    getLogger().info("Playback ended: " + event.getSession().getSessionId()
        + "; reached final tick: " + event.getSession().isFinished());
  }

  @EventHandler
  public void onDeleted(ReplayDeletedEvent event) {
    if (event.getFailure() != null) {
      getLogger().log(Level.WARNING, "Could not delete " + event.getReplayId(), event.getFailure());
    } else {
      getLogger().info("Deletion " + event.getReplayId() + ": " + event.isDeleted());
    }
  }

  @EventHandler
  public void onMetadataUpdated(ReplayMetadataUpdatedEvent event) {
    if (event.getResult() == ReplayCatalog.UpdateResult.UPDATED) {
      getLogger().info("Text replaced for " + event.getReplayId());
    } else {
      getLogger().info("Text update missing or forbidden for " + event.getReplayId());
    }
    // Backend failures produce no metadata event: handle the update stage as well.
  }
}
```

Copy the handlers needed by your integration and declare the corresponding runtime dependency.

## Observe lobby dispatch separately

In a lobby-only listener, import `com.marioded.reverse.api.lobby.event.DispatchResultEvent` and `com.marioded.reverse.api.lobby.service.DispatchService`. This handler only logs and is safe on the event's completion thread:

```java theme={null}
@EventHandler
public void onDispatch(DispatchResultEvent event) {
    if (event.getFailure() != null) {
        getLogger().log(Level.WARNING, "Dispatch failed for " + event.getReplayId(), event.getFailure());
    } else if (event.getResult() == DispatchService.Result.SUCCESS) {
        getLogger().info("Handoff sent for " + event.getReplayId());
    } else {
        getLogger().info("Dispatch refused: " + event.getResult());
    }
}
```

This snippet belongs in a `JavaPlugin` implementing `Listener`, registered as above with a dependency on `reverse-lobby`. The event can fire inline for an offline/overlapping request or on a backend completion thread; its viewer handle may already be offline. Schedule any player/UI work onto the main thread and recheck the connection. `SUCCESS` means a handoff was sent, not that playback began. See [dispatch event semantics](https://docs.marioded.com/api/events.md#dispatch-events) for exceptional stages and converted failures.


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