Addon API - SocketSVC Docs
SocketSVC Docs

Addon API

Route and observe relayed voice connections from your own Velocity or BungeeCord plugin

The relay plugin exposes a small Java API for other proxy plugins. It is built as socketsvc-proxy-<version>-api.jar by buildAllProxy, alongside the plugin jar itself.

Routing and observation only

The relay is a byte-level pipe — it never terminates the WebSocket handshake and never decodes audio. So this API covers which backend a player is relayed to and what the relay is doing, and nothing else. Anything about the voice itself (speakers, positions, per-player volume) belongs to the backend's own Spatial audio API. No such API can exist on the relay.

Generated reference for every type: Proxy API.

Depending on it

Compile against the API jar and never shade it — the same rule the Paper plugin's API jar follows. Shading it would put a second copy of the interfaces on the classpath, and the relay's own registration would no longer be the one your plugin sees.

Every release publishes the API jar to the update service's Maven repository, so you do not have to download it or commit a copy:

build.gradle.kts
repositories {
    maven("https://socketsvc-updater.vercel.app/maven") {
        name = "SocketSVC"
        // Nothing else is published there, so no other lookup is slowed down by it.
        content { includeGroup("com.socketsvc") }
    }
}

dependencies {
    compileOnly("com.socketsvc:socketsvc-proxy-api:0.2.0")
}
pom.xml
<repositories>
  <repository>
    <id>socketsvc</id>
    <url>https://socketsvc-updater.vercel.app/maven</url>
  </repository>
</repositories>

<dependencies>
  <dependency>
    <groupId>com.socketsvc</groupId>
    <artifactId>socketsvc-proxy-api</artifactId>
    <version>0.2.0</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

The version is the relay's own proxy.version, and every published build has one — including dev builds, as 0.2.1-dev.7. Resolve the version your server actually runs: the API a release serves is the API that release's relay implements. compileOnly(files("libs/socketsvc-proxy-<version>-api.jar")) still works if you would rather vendor the jar.

Getting the API

Neither Velocity nor BungeeCord has a Bukkit-style services manager, so the API is published statically. It is null before the relay plugin has started and null again after the proxy shuts down, so look it up when you need it rather than caching it in a field.

SocketSvcProxyApi api = SocketSvcProxyApi.get();
if (api == null) return;   // relay plugin not started

The same API and the same jar work on both platforms.

Routing overrides

A ProxyBackendResolver decides which backend voice listener a connecting player reaches, overriding the servers: map in the relay's config.

api.setBackendResolver("my-addon", (playerId, serverName) -> {
    if (isInLobbyQueue(playerId)) return Optional.of(LOBBY_VOICE);
    return Optional.empty();   // defer: next resolver, then servers:
});

Resolvers are consulted in registration order, before the configured map. Returning Optional.empty() passes the decision on, and if every resolver passes, routing behaves exactly as it does with no addon installed.

Runs on the relay's connection thread

A resolver is called once per incoming connection, before any bytes are forwarded, on the relay's own per-connection thread. Keep it fast and non-blocking — a slow resolver delays that player's voice connection — and make it safe to call from several threads at once.

A resolver is the only way to relay a player the proxy itself cannot place. When the serverName argument is empty, the proxy could not resolve the player to a backend — a race right after a server switch, or a backend missing from servers:. Without a resolver those connections are rejected; see Failure behaviour.

Lifecycle events

A ProxyRelayListener observes connections opening, closing and being rejected. Every method has a default no-op, so override only what you need.

api.addListener("my-addon", new ProxyRelayListener() {
    @Override public void onRelayOpened(RelaySession session) { }
    @Override public void onRelayClosed(RelaySession session) { }
    @Override public void onRelayRejected(UUID playerId, String reason) { }
});

Callbacks run on the relay's threads, never the proxy's main thread. onRelayOpened fires before the connection is piped and onRelayClosed as it is torn down, so do not block in them. In onRelayRejected, playerId is null when the connection never supplied a usable uuid parameter.

Each open and close carries a RelaySession: the player, the backend's name (or null when a resolver supplied an address without naming a server), the backend address, and a nanoTime stamp.

Status and control

MethodReports
isRunning()Whether the listener is accepting connections
listenAddress()The single public address, or null when not running
activeRelays()Connections currently being relayed
rejectedConnections()Connections closed without being relayed since start
backendVoiceAddresses()Snapshot of the configured servers: map
isRelaying(UUID)Whether a player is currently being relayed
reload()Re-reads config.yml and restarts the listener

These are the same numbers /ssvc-proxy status prints, and reload() does what /ssvc-proxy reload does.

Failure isolation and lifetime

An addon callback that throws is logged — naming the ownerId you registered with — and skipped. A resolver that throws falls through to the next one and then to the configured map; a listener that throws does not affect the connection. A broken addon degrades to default routing rather than breaking voice for the whole network.

Registrations are held by the plugin, not by the relay listener, so they survive both /ssvc-proxy reload and reload(). Call clearOwner("my-addon") from your own plugin's shutdown.

Versioning

SocketSvcProxyApi.API_VERSION is incremented only for incompatible changes. Additions arrive as interface default methods, so an addon built against an older API jar keeps working.