Skip to main content

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