Building against the Java API

How to build against the OpenAudioMc Java API to build custom audio and voice chat features

Adding the API to your project

The API module can be consumed in two ways.

  1. You can download the API as a jar file and include it in your project

Loading...

Fetching recent release file openaudiomc-api.jar

2. You can use maven to include the API in your project (using jitpack)
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories> <dependencies> <dependency> <groupId>com.github.Mindgamesnl</groupId> <artifactId>openaudiomc-api</artifactId> <version>6.10.0-pre7</version> <scope>provided</scope> </dependency> </dependencies>

The various components

The API is split into several different components, each having their own purpose and supporting different platforms. The main components are:


  • The general ClientApi
    • Supported on all platforms
    • Used to interface with Clients (a 'client' represents a possible connection, owner by a User which is an Actor)
      • Clients are required for pretty much all interactions with a player or session
      • the Client holds many useful methods for interacting with a session

  • The EventApi
    • This is our event system, which works on all platforms (though not all events are supported on all platforms)
    • Supports single callbacks, or lets you register entire handler classes

  • The MediaApi
    • Supported on all platforms
    • Used to create Media instances, and hook into various media sub-systems (like playback, preloading, syncing, etc)

  • The VoiceApi
    • only supported on Spigot
    • Used to interface with all voice-related services
      • Exposes proximity voice chat filters
      • Lets you link or disconnect specific players with a static voice stream
      • Lets you update voice peers regardless of their type (changing visibility or rendering type)
    • (Some of these features go hand-in-hand with the EventApi)

  • The WorldApi
    • only supported on Spigot
    • Used to interface with the world-related services
      • Lets you get properties for placed speakers, and their settings
      • Exposes a read-only version of the region registry by location

Examples

Here are a fair few examples of how to use the API, to hopefully give you a good idea of how to use it in your own projects. A full breakdown of the API can be found in the Javadocs, which I highly recommend you check out, because there's a lot of useful stuff in there.

Getting a client

Everything begins with Clients, so for these examples, we'll get a client instance of myself

UUID myUuid = UUID.fromString("88243ba3-382f-4eb0-874f-c5831eb3c0a6"); Client mats = ClientApi.getInstance().getClient(myUuid); // the client may be null if they aren't online, or just joined (due to event order) if (mats == null) { System.out.println("Mats is not loaded yet, you may need to wait a tick after joining"); return; } // lets check if they are connected to the web client if (mats.isConnected()) { System.out.println("Mats is connected!"); } else { System.out.println("Mats is not connected to the web client!"); }

Playing a sound

Now that we have a client, we can play a sound for them. Media sources are automatically translated to the correct format for the client, so you can use any supported source you would also use in the play command. Setting an ID (or any of the other properties) is optional, but it can be useful for managing media later on.

// lets construct a media object, the source we pass in here will be automatically translated Media sound = new Media("files:mysong.mp3"); // configure some pr sound.setLoop(true); sound.setVolume(50); sound.setMediaId("example-sound"); // play the sound for mats (but the last argument is an optional array, so you can play the sound for multiple clients at once) MediaApi.getInstance().playFor(sound, mats);

Stopping a sound

If you want to stop a sound, you can use the stopMedia method. This will stop all media with the given ID, or all media if no ID is given. Just like with the playFor method, you can also stop media for multiple clients at once.

MediaApi.getInstance().stopFor("example-sound", mats);

Preloading a sound

We can also forcefully preload media for the client, which works a lot like the preload command but exposes a few more options.

// this makes the client re-add it to the cache if its played, always keeping one instance ready // unless it is explicitly removed from the cache. Great for repetitive sounds, but dangerous for memory leaks boolean keepCopy = true; // request the client to preload the media from "sound" which we defined earlier MediaApi.getInstance().preloadMediaSource(mats, sound, keepCopy);

And we can also clear the cache for a client, which will remove all preloaded media for them. It's not possible to remove specific media from the cache, this may feel like a limitation, but it's a design choice to prevent memory leaks.

MediaApi.getInstance().clearPreloadedMedia(mats);

Using events

The event system is a powerful tool, and it's used extensively in the API. Here's an example of how to use it to listen for a client connecting.

// create a new, single event handler EventApi.getInstance().registerHandler(ClientConnectEvent.class, event -> { System.out.println(event.getClient().getActor().getName() + " just connected!"); }); // or register multiple handlers in a handler object (which does not need to implement anything) EventApi.getInstance().registerHandlers(new Object() { @Handler public void onConnect(ClientConnectEvent event) { System.out.println(event.getClient().getActor().getName() + " just connected!"); } @Handler public void onDisconnect(ClientConnectEvent event) { System.out.println(event.getClient().getActor().getName() + " just disconnected!"); } });

Using the voice API

The voice API is only supported on Spigot, and it's used to interface with all voice-related services. There's a lot we can do with this, so here are a few example features.


This first one will connect two players to each-other using a voice stream, which will make them hear each-other regardless of distance, much like a phone call. This also covers some interesting edge-cases which are automatically handled, for example, if these peers are already in proximity of each-other, then it will re-use the existing proximity stream.
public void connectAsPhone(Client playerA, Client playerB) { // ensure both ready if (!playerA.hasVoicechatEnabled() || !playerB.hasVoicechatEnabled()) { System.out.println("Both players need to be connected to the web client"); return; } // we want the players to see eachother in the UI, setting this to false will hide the player from the UI boolean visible = true; // we want the players to be able to hear eachother, setting this to false will only make playerA hear playerB boolean mutual = true; VoiceApi.getInstance().addStaticPeer(playerA, playerB, visible, mutual); }

And this can easy be undone by calling the removeStaticPeer method, which will disconnect the players from each-other.

public void disconnectAsPhone(Client playerA, Client playerB) { // the true flag here once again indicates that this is a mutual action, and should also remove playerA from playerB VoiceApi.getInstance().removeStaticPeer(playerA, playerB, true); }

This example will hook into proximity voice chat using the Event api, making it so that players in survival will never be able to hear players in spectator, but spectators can still hear survival players.

This example should only be followed for single-sided voice chat, please please please use filters for actual peer filtering. The javadoc for those explains why exactly that is so important.

EventApi.getInstance().registerHandler(ClientPeerAddEvent.class, event -> { // first of, convert both Clients to a Player Player player = Bukkit.getPlayer(event.getClient().getActor().getUniqueId()); Player possiblePeer = Bukkit.getPlayer(event.getPeer().getActor().getUniqueId()); if (player.getGameMode() == GameMode.SURVIVAL && possiblePeer.getGameMode() == GameMode.SPECTATOR) { // cancel the event, as we don't want the players to be able to hear eachother event.setCancelled(true); } }); // this will only handle when the players are initially are being connected to eachother. // If the peer changes gamemode, you can kick player A using client#kickProximityPeer(client)

This example will register a filter, which prevents any players in different game modes from being linked-up together in proximity voice chat. This is far more efficient then using the events, because it will prevent the voice chat from even being created in the first place. Please read the javadoc for the `addFilterFunction` method for more information on when this is and isn't invoked.
VoiceApi.getInstance().addFilterFunction((player, peer) -> { if (player.getGameMode() != peer.getGameMode()) { // this combination resulted in a negative result, so we need to mark this filter as failed return false; } // they are in the same game mode, so we can allow them to hear eachother (given that the other preconditions are met) return true; });

But wait, there's more!

These are all of the main examples, but there's loads more you can do with the API. I highly recommend you check out the Javadocs for a full breakdown of the API, because there's a lot of useful stuff in there. If you have any questions about the api itself, feel free to ask in the Discord, and I'll be happy to help you out.

Javadoc related to this feature (currently based on the dev branch)