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

# Playback integrations

Start a replay for one or more viewers, control its timeline, and clean up correctly.

**Runtime:** player · **Entry point:** `ReversePlayer` · **Controls:** Bukkit main thread

Use this guide when your own command, menu, or game UI needs to open a saved recording. The runtime handles loading, viewer state, and the playback scene.

## Start by replay ID

Discover `ReversePlayer` and use `play(replayId, viewer)` or `play(replayId, viewers)`. The runtime loads asynchronously and schedules the actual start on the main thread.

Let's assume `api` is the discovered player provider, `viewer` is the requesting online player, and the replay ID has passed a fresh catalog access check. Inside our JavaPlugin, we can start playback and handle its result:

```java
api.play(replayId, viewer).whenComplete((session, failure) -> {
    if (failure != null) {
        getLogger().log(Level.WARNING, "Could not start replay " + replayId, failure);
    } else if (session == null) {
        getLogger().info("No playback session was started");
    } else {
        getLogger().info("Started playback session " + session.getSessionId());
    }
});
```

This callback only logs. If we want to send messages, seek, or change the session speed after completion, we schedule that work back to the main thread and recheck the current viewer/request.

The returned future can:

* Complete with a `PlaybackSession`.
* Complete normally with null if the replay is missing or no viewer can be accepted.
* Complete exceptionally if loading or starting fails.

Handle all three. Completion callbacks have no thread guarantee. For a user-selected recording, authorize with the catalog first: `play` is not itself a catalog actor check. See the [PlaybackIntegration example](/api-how-to-guides/examples/playback-integration.md).

## Start an already loaded replay

Call `startPlaying(loadedReplay, viewers)` on the main thread. Offline players and duplicates are dropped. A player already in another session is deliberately switched where their previous state can be restored; a failed restoration can cause that viewer to be skipped. If nobody is accepted, the result is null.

The supplied `/reverseplayer play` command applies its own “already watching” guard. Do not infer the more general service behavior only from that command UX.

## Shared sessions

Start with a list to give several people one synchronized viewing session. Its cursor, pause state, speed, and folded world state are shared. Every viewer sees the same timeline position.

If each player needs independent controls, start separate sessions. The recording's immutable event stream can be shared, while mutable session state and entity mappings remain independent.

{% hint style="info" %}
**One session means one timeline.** Pausing, changing speed, or seeking affects everyone in that session. Start separate sessions when viewers need independent controls.
{% endhint %}

Membership is chosen at session start. There is no public “join this running session” operation. Viewers leave through the service; do not modify the returned viewer list.

For an already loaded replay, start one shared session on the main thread:

```java
PlaybackSession session = api.getPlaybackService().startPlaying(
    loadedReplay, List.of(firstViewer, secondViewer));
```

Check for null before using `session`: neither viewer may have been accepted. To give them independent controls, call `startPlaying` separately for each viewer instead.

## Control a session

```java
PlaybackService playback = api.getPlaybackService();
playback.pause(session);
playback.seekTo(session, 200);
playback.setSpeed(session, 0.5);
playback.resume(session);
```

Use the Bukkit main thread. Speed must be finite and strictly positive; pause with `pause`, not by setting speed to zero.

`seekTo` uses an absolute session-relative tick. `seekBy` uses a signed relative delta. Tick zero is the start of the event timeline. The service reconstructs scene state and resynchronizes viewers. Setting `session.setCurrentTick` yourself does not do that work.

The API seek does not imply the command's extra “resume after seek” behavior. Decide whether your integration should preserve pause or explicitly resume.

## Leave one viewer or stop everyone

```java
playback.leaveSession(session, viewer); // Restore one viewer; stop if last viewer leaves.
playback.stopPlaying(session);         // End the entire session.
```

Use service operations instead of calling `session.removeViewer`: the service also performs restoration, listener-visible lifecycle, scene cleanup, and final-session handling. Handle disconnections through the runtime rather than retaining offline player objects indefinitely.

## Read session information

* `getReplayId()` identifies the recording; `getSessionId()` identifies this viewing instance.
* `getCurrentTick()` and `getLastTick()` describe the playable timeline.
* `getRecordedDurationMillis()` is a separate wall-clock recording duration.
* `getViewers()` returns an immutable membership snapshot.
* `getWorldState()` exposes the accumulated durable scene; treat it as runtime-owned.
* `getRecordablesAt(tick)` gives events for the requested tick, or an empty list.

Use `ReversePlayer.getSession(viewer)` or `PlaybackService.getSessionByViewer` to find the current session. A cached session reference is not proof that it is still active after an asynchronous operation.

For example, a main-thread pause button can look up the current session at click time:

```java
api.getSession(viewer).ifPresent(session -> {
    api.getPlaybackService().pause(session);
});
```

## Entity IDs are session-specific

Recorded entity IDs cannot be sent directly back to every client. Different playback sessions may contain the same original ID, and real server entities also have IDs. Render through `PlaybackContext` and its session entity ID/UUID mappings.

Do not retain a remapped ID indefinitely across resynchronization or use one session's mapping for another session. Let entity-aware renderers and lifecycle operations maintain those mappings.

## Events and custom display

Subscribe to [playback events](/api-how-to-guides/events.md) to respond to session lifecycle. Use the [layout/menu APIs](/api-how-to-guides/ui-extensions.md) to add controls without replacing the player's inventory management. `playTick` is runtime scheduling machinery; an integration should not advance a session separately while Reverse's own tick task is already doing so.

{% content-ref url="/pages/sVo2hzYBpCUo8kAi2nkz" %}
[Starting local playback](/api-how-to-guides/examples/playback-integration.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/playback.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.
