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

# Send viewers to a replay server

> Send an authorized viewer to a replay server and understand what dispatch completion means.

# Send viewers to a replay server

**Runtime:** lobby · **Entry point:** `ReverseLobby` · **Watch calls:** Bukkit main thread

## Prefer the high-level lobby service

If a player selects a replay in your lobby UI, call:

```java theme={null}
ReverseLobby lobby = ReverseLobby.get().orElseThrow();
lobby.getDispatchService().watch(viewer, replayId)
    .whenComplete((result, failure) -> {
        if (failure != null) {
            plugin.getLogger().log(Level.WARNING, "Could not dispatch viewer", failure);
        } else {
            plugin.getLogger().info("Replay dispatch result: " + result);
        }
    });
```

Here `plugin` is our integration's JavaPlugin, `viewer` is the requesting player, and `replayId` is their selection. Invoke this from the main thread. The service performs the lobby-policy access check and handoff sequence; the callback above only logs the result.

Call on the main thread. The returned stage can complete on a backend thread. Handle an exceptional completion in addition to the result enum:

| Result | Meaning |
| - | - |
| `SUCCESS` | Pending playback persisted and proxy transfer message sent. |
| `NOT_AUTHORIZED` | Missing replay or actor not allowed to view it. |
| `NO_REPLAYER` | No fresh player-node heartbeat. |
| `TRANSFER_FAILED` | Request could not complete, including an overlapping request by that viewer. |

The service uses the lobby's current access policy and guards duplicate in-flight requests. Do not bypass that with raw pending-selection writes merely to hide an authorization failure.

`DispatchResultEvent` lets listeners observe the result without originating the request, including immediate refusals. It has no main-thread firing guarantee. An exceptional request is reported by the event as `TRANSFER_FAILED` with a cause; some downstream errors are converted to a normal failure result instead. Keep stage failure handling as well as result handling; see [dispatch events](https://docs.marioded.com/api/events.md#dispatch-events).

> [!NOTE]
>
> **Dispatch success means “handoff sent.”** It does not confirm that the proxy connected the viewer or playback started. Observe the target runtime's start event when you need that acknowledgment.

## What availability stores

`Availability` represents:

* `heartbeat(nodeId)` and `leave(nodeId)` for node liveness.
* `active()` for fresh nodes, ordered oldest heartbeat first.
* `pending(nodeId, viewerId, PendingPlayback)` to store a short-lived selection.
* `take(nodeId, viewerId)` to consume that selection once.

`PendingPlayback` contains the replay ID and origin server. Selections expire after five minutes. This contract does not expose reservations, queue position, capacity, match status, or playback progress.

## SQL, MongoDB and Redis

Factories supply concrete availability backends. `AvailabilityService` registers factories, opens the configured backend, manages node heartbeat/start lifecycle, and closes owned resources. The selected storage backend contributes its availability factory (id `sql` or `mongodb`) and depends on that store opening; Redis is independently configured and lazily loads its runtime libraries.

A facade may return null from `getAvailability()` when networking is disabled. Ordinary plugins should borrow that configured backend rather than closing it or starting a second heartbeat loop over the same runtime instance. Both `ReversePlayer` and `ReverseLobby` also expose the owning `AvailabilityService` through `getAvailabilityService()` for introspection (for example the list of registered backend ids).

The registered backends are the selected storage backend (`sql` or `mongodb`) and `redis`: the composition root does not scan for external `AvailabilityFactory` services, so `network.backend` must name a built-in id. Use the configured backend for normal lobby integrations.

## Transfer is not confirmation

The transfer implementation sends a `Connect` message over the BungeeCord channel. Dispatch success does not confirm arrival. The target player runtime consumes a selection when the viewer joins and then loads the replay. Expiry, proxy errors, disconnection, or storage failure can prevent playback after dispatch was accepted.

If your application needs a guaranteed “viewer is now watching” signal, observe `PlaybackStartEvent` on the target runtime and communicate it through your own application channel. Do not reinterpret `SUCCESS` as that acknowledgment.

## Integrating a custom lobby menu

Use the catalog to display accessible metadata and the dispatch service for Watch. The shared `MenuActionRegistry` lets you attach custom actions without importing Inventory Framework. Obtain it from `ReverseLobby`, not from a new registry instance that the real menu never uses.

For a purely local viewing integration, use `ReversePlayer` instead. Availability is unnecessary when no cross-server transfer is involved.


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