asminvoicechat second commit
Build AsminVoicechat / build-and-release (push) Canceled after 12s

This commit is contained in:
asminme committed 2026-10-24 15:42:08 +02:00
1 parent bbccd4a815
commit 71396a86a0
581 files changed
+9084 -38713

No files matched your search

@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api;
public interface BukkitVoicechatService {
/**
* Registers the voice chat plugin on bukkit based servers.
*
* @param plugin the voicechat plugin
*/
void registerPlugin(VoicechatPlugin plugin);
}
@@ -0,0 +1,21 @@
package de.maxhenkel.voicechat.api;
import java.net.SocketAddress;
/**
* A socket used for client side voice chat traffic.
* Can be set using {@link de.maxhenkel.voicechat.api.events.ClientVoicechatInitializationEvent#setSocketImplementation(ClientVoicechatSocket)}.
*/
public interface ClientVoicechatSocket {
void open() throws Exception;
RawUdpPacket read() throws Exception;
void send(byte[] data, SocketAddress address) throws Exception;
void close();
boolean isClosed();
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api;
import java.util.UUID;
public interface Entity {
/**
* @return the UUID of the entity
*/
UUID getUuid();
/**
* @return the actual entity object
*/
Object getEntity();
/**
* @return the current position of the entity
*/
Position getPosition();
}
@@ -0,0 +1,8 @@
package de.maxhenkel.voicechat.api;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ForgeVoicechatPlugin {
}
@@ -0,0 +1,115 @@
package de.maxhenkel.voicechat.api;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* Groups can be created using {@link VoicechatServerApi#groupBuilder()}.
*/
public interface Group {
/**
* @return the visual name of the group
*/
String getName();
/**
* @return if the group has a password
*/
boolean hasPassword();
/**
* @return the ID of the group
*/
UUID getId();
/**
* @return if the group is persistent
*/
boolean isPersistent();
/**
* @return if the group is hidden in the group list UI
*/
boolean isHidden();
/**
* @return the group type
*/
Type getType();
public interface Type {
/**
* Players in a group can hear nearby players that are not in a group
*/
public static final Type NORMAL = new Type() {
};
/**
* Players in a group can hear nearby players and nearby players can hear players in the group
*/
public static final Type OPEN = new Type() {
};
/**
* Players in a group can only hear other players in the group
*/
public static final Type ISOLATED = new Type() {
};
}
public interface Builder {
/**
* Sets the ID of the group.
* If this is not set, the ID will be randomly generated.
* <br/>
* <b>NOTE</b>: If there is already a group with the same ID, the group will be overwritten.
*
* @param id the group ID or <code>null</code> if the ID should be randomly generated
* @return the builder
*/
Builder setId(@Nullable UUID id);
/**
* <b>NOTE</b>: The name might be stripped of special characters and whitespace.
*
* @param name the name of the group
* @return the builder
*/
Builder setName(String name);
/**
* @param password the group password
* @return the builder
*/
Builder setPassword(@Nullable String password);
/**
* @param persistent if the group should be persistent
* @return the builder
*/
Builder setPersistent(boolean persistent);
/**
* @param hidden if the group should be hidden in the group list UI
* @return the builder
*/
Builder setHidden(boolean hidden);
/**
* @param type the group type
* @return the builder
*/
Builder setType(Type type);
/**
* @return the built group
* @throws IllegalStateException if the name is not set or invalid
*/
Group build();
}
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api;
public interface Player extends Entity {
/**
* @return the actual player object
*/
Object getPlayer();
}
@@ -0,0 +1,20 @@
package de.maxhenkel.voicechat.api;
public interface Position {
/**
* @return the X position
*/
double getX();
/**
* @return the Y position
*/
double getY();
/**
* @return the Z position
*/
double getZ();
}
@@ -0,0 +1,13 @@
package de.maxhenkel.voicechat.api;
import java.net.SocketAddress;
public interface RawUdpPacket {
byte[] getData();
long getTimestamp();
SocketAddress getSocketAddress();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api;
public interface ServerLevel {
/**
* @return the actual level object
*/
Object getServerLevel();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api;
public interface ServerPlayer extends Player {
/**
* @return the level of the player
*/
ServerLevel getServerLevel();
}
@@ -0,0 +1,116 @@
package de.maxhenkel.voicechat.api;
import de.maxhenkel.voicechat.api.audio.AudioConverter;
import de.maxhenkel.voicechat.api.mp3.Mp3Decoder;
import de.maxhenkel.voicechat.api.mp3.Mp3Encoder;
import de.maxhenkel.voicechat.api.opus.OpusDecoder;
import de.maxhenkel.voicechat.api.opus.OpusEncoder;
import de.maxhenkel.voicechat.api.opus.OpusEncoderMode;
import javax.annotation.Nullable;
import javax.sound.sampled.AudioFormat;
import java.io.InputStream;
import java.io.OutputStream;
public interface VoicechatApi {
/**
* Creates a new opus encoder.
* Note that the encoder needs to be closed after you are finished using it.
*/
OpusEncoder createEncoder();
/**
* Creates a new opus encoder.
* Note that the encoder needs to be closed after you are finished using it.
*
* @param mode the opus encoder mode
* @return the opus encoder
*/
OpusEncoder createEncoder(OpusEncoderMode mode);
/**
* Creates a new opus decoder.
* Note that the decoder needs to be closed after you are finished using it.
*
* @return the opus decoder
*/
OpusDecoder createDecoder();
/**
* <b>NOTE</b>: This is not available for Bukkit! It will always return null.
*
* @param audioFormat the audio format
* @param bitrate the bitrate in kbps
* @param quality the quality from 0 (highest) to 9 (lowest)
* @param outputStream the output stream to write the mp3 to
* @return the mp3 encoder or null if the encoder could not be initialized or is not available
*/
@Nullable
Mp3Encoder createMp3Encoder(AudioFormat audioFormat, int bitrate, int quality, OutputStream outputStream);
/**
* <b>NOTE</b>: This is not available for Bukkit! It will always return null.
*
* @param inputStream the input stream to read the mp3 from
* @return the mp3 decoder or null if the decoder could not be initialized or is not available
*/
@Nullable
Mp3Decoder createMp3Decoder(InputStream inputStream);
/**
* @return the audio converter
*/
AudioConverter getAudioConverter();
/**
* Creates an entity object from an actual entity.
*
* @param entity the entity implementation of your mod/plugin loader
* @return the entity object
*/
Entity fromEntity(Object entity);
/**
* Creates a level object from an actual level.
*
* @param serverLevel the level implementation of your mod/plugin loader
* @return the level
*/
ServerLevel fromServerLevel(Object serverLevel);
/**
* Creates a player object from an actual player.
*
* @param serverPlayer the player implementation of your mod/plugin loader
* @return the player
*/
ServerPlayer fromServerPlayer(Object serverPlayer);
/**
* Creates a new position object.
*
* @param x the X position
* @param y the Y position
* @param z the Z position
* @return the position with the provided coordinates
*/
Position createPosition(double x, double y, double z);
/**
* Don't forget to register your category with {@link VoicechatServerApi#registerVolumeCategory(VolumeCategory)} or {@link VoicechatClientApi#registerClientVolumeCategory(VolumeCategory)}
*
* @return a builder to build a category
*/
VolumeCategory.Builder volumeCategoryBuilder();
/**
* <b>NOTE</b>: Voice chat plugins can change this.
* This is only the default value.
* This value might not be correct if you are not connected to a server.
*
* @return the default distance, audio from voice chat is heard from
*/
double getVoiceChatDistance();
}
@@ -0,0 +1,166 @@
package de.maxhenkel.voicechat.api;
import de.maxhenkel.voicechat.api.audiochannel.ClientEntityAudioChannel;
import de.maxhenkel.voicechat.api.audiochannel.ClientLocationalAudioChannel;
import de.maxhenkel.voicechat.api.audiochannel.ClientStaticAudioChannel;
import de.maxhenkel.voicechat.api.config.ConfigAccessor;
import de.maxhenkel.voicechat.api.config.VolumeConfigAccessor;
import javax.annotation.Nullable;
import java.util.UUID;
public interface VoicechatClientApi extends VoicechatApi {
/**
* @return if the local voice chat is muted
*/
boolean isMuted();
/**
* Does the same as {@link #isDisabled(UUID)} with <code>null</code> as the player UUID.
*
* @return if the local voice chat is disabled
*/
default boolean isDisabled() {
return isDisabled(null);
}
/**
* @param playerId the UUID of the player or <code>null</code> for the local player
* @return if the player with the provided UUID has the voice chat disabled
*/
boolean isDisabled(@Nullable UUID playerId);
/**
* Does the same as {@link #isDisconnected(UUID)} with <code>null</code> as the player UUID.
*
* @return if the voice chat is disconnected from the server
*/
default boolean isDisconnected() {
return isDisconnected(null);
}
/**
* @param playerId the UUID of the player or <code>null</code> for the local player
* @return if the player with the provided UUID is disconnected from voice chat
*/
boolean isDisconnected(@Nullable UUID playerId);
/**
* Does the same as {@link #isTalking(UUID)} with <code>null</code> as the player UUID.
*
* @return if the local player is talking
*/
default boolean isTalking() {
return isTalking(null);
}
/**
* @param playerId the UUID of the player or <code>null</code> for the local player
* @return if the player with the provided UUID is currently talking
*/
boolean isTalking(@Nullable UUID playerId);
/**
* Does the same as {@link #isWhispering(UUID)} with <code>null</code> as the player UUID.
*
* @return if the local player is whispering
*/
default boolean isWhispering() {
return isWhispering(null);
}
/**
* @param playerId the UUID of the player or <code>null</code> for the local player
* @return if the player with the provided UUID is currently whispering
*/
boolean isWhispering(@Nullable UUID playerId);
/**
* This method returns if the push to talk key is pressed, even when using voice activation.
*
* @return if the push to talk key is pressed
*/
boolean isPushToTalkKeyPressed();
/**
* @return if the whisper key is pressed
*/
boolean isWhisperKeyPressed();
@Nullable
Group getGroup();
/**
* Creates a client side entity audio channel.
*
* @param uuid the UUID of the entity
* @return the audio channel
* @deprecated use {@link #createEntityAudioChannel(UUID, Entity)}
*/
@Deprecated
ClientEntityAudioChannel createEntityAudioChannel(UUID uuid);
/**
* Creates a client side entity audio channel.
*
* @param uuid the ID of the channel
* @param entity the entity to attach the channel to
* @return the audio channel
*/
ClientEntityAudioChannel createEntityAudioChannel(UUID uuid, Entity entity);
/**
* Creates a client side locational audio channel.
*
* @param uuid the ID of the channel
* @return the audio channel
*/
ClientLocationalAudioChannel createLocationalAudioChannel(UUID uuid, Position position);
/**
* Creates a client side static audio channel.
*
* @param uuid the ID of the channel
* @return the audio channel
*/
ClientStaticAudioChannel createStaticAudioChannel(UUID uuid);
/**
* Registers a volume category just for this client.
* A category can be created with {@link VoicechatApi#volumeCategoryBuilder()}.
* The category can be unregistered with {@link #unregisterClientVolumeCategory}.
*
* @param category the category to register
*/
void registerClientVolumeCategory(VolumeCategory category);
/**
* Unregisters a category on this client.
* This will release the texture ID for the icon if one exists.
*
* @param category the category to remove
*/
default void unregisterClientVolumeCategory(VolumeCategory category) {
unregisterClientVolumeCategory(category.getId());
}
/**
* Unregisters a category on this client.
* This will release the texture ID for the icon if one exists.
*
* @param categoryId the category ID to remove
*/
void unregisterClientVolumeCategory(String categoryId);
/**
* @return a read-only config accessor for the mods client config
*/
ConfigAccessor getClientConfig();
/**
* @return a read-only accessor for player and category volumes
*/
VolumeConfigAccessor getVolumeConfig();
}
@@ -0,0 +1,80 @@
package de.maxhenkel.voicechat.api;
import javax.annotation.Nullable;
/**
* <b>Note</b>: It is not guaranteed that the state of this connection object is up to date.
* Please re-fetch the connection object to get the latest state.
*/
public interface VoicechatConnection {
/**
* <b>Note</b>: This only returns the group the player was in when fetching this connection object.
* Calling {@link #setGroup(Group)} won't update the return value of this method.
*
* @return the group of the player - <code>null</code> if the player is not in a group
*/
@Nullable
Group getGroup();
/**
* <b>Note</b>: This only returns if the player was in a group when fetching this connection object.
* Calling {@link #setGroup(Group)} won't update the return value of this method.
*
* @return if the player is in a group
*/
boolean isInGroup();
/**
* Joins this player to the provided group.
*
* @param group the group to join or <code>null</code> to leave the current group
*/
void setGroup(@Nullable Group group);
/**
* This might not represent the actual state of the voice chat connection, since other voice chat plugins can fake having a player connected.
* <br/>
* <b>Note</b>: This only returns if the player was connected fetching this connection object.
* Calling {@link #setConnected(boolean)} won't update the return value of this method.
*
* @return if the player is connected to voice chat
*/
boolean isConnected();
/**
* The players disconnected state will reset if its actual disconnected state changes or if the player reconnects.
* <br/>
* <b>NOTE</b>: This method will only work for players that don't have the mod installed. See {@link #isInstalled()}.
*
* @param connected if the player should be shown as connected to voice chat
*/
void setConnected(boolean connected);
/**
* <b>Note</b>: This only returns if the player has the voice chat disabled when fetching this connection object.
* Calling {@link #setDisabled(boolean)} won't update the return value of this method.
*
* @return If the player muted the sound
*/
boolean isDisabled();
/**
* Sets the players disabled state.
* <b>NOTE</b>: This method will only work for players that don't have the mod installed. See {@link #isInstalled()}.
*
* @param disabled if the player should have the voice chat disabled icon
*/
void setDisabled(boolean disabled);
/**
* @return if the player has a version of voice chat installed that is compatible with the server
*/
boolean isInstalled();
/**
* @return the player
*/
ServerPlayer getPlayer();
}
@@ -0,0 +1,26 @@
package de.maxhenkel.voicechat.api;
import de.maxhenkel.voicechat.api.events.EventRegistration;
public interface VoicechatPlugin {
/**
* @return the ID of this plugin - Has to be unique
*/
String getPluginId();
/**
* Called after loading the plugin.
*/
default void initialize(VoicechatApi api) {
}
/**
* Register your events here - Only here!
*
* @param registration the event registration object, used to register events
*/
default void registerEvents(EventRegistration registration) {
}
}
@@ -0,0 +1,343 @@
package de.maxhenkel.voicechat.api;
import de.maxhenkel.voicechat.api.audiochannel.*;
import de.maxhenkel.voicechat.api.audiolistener.AudioListener;
import de.maxhenkel.voicechat.api.audiolistener.PlayerAudioListener;
import de.maxhenkel.voicechat.api.audiosender.AudioSender;
import de.maxhenkel.voicechat.api.config.ConfigAccessor;
import de.maxhenkel.voicechat.api.opus.OpusEncoder;
import de.maxhenkel.voicechat.api.packets.ConvertablePacket;
import de.maxhenkel.voicechat.api.packets.EntitySoundPacket;
import de.maxhenkel.voicechat.api.packets.LocationalSoundPacket;
import de.maxhenkel.voicechat.api.packets.StaticSoundPacket;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.function.Supplier;
public interface VoicechatServerApi extends VoicechatApi {
/**
* Sends the sound packet to the provided connection.
*
* @param connection the connection to send the packet to
* @param packet the packet to send
*/
void sendEntitySoundPacketTo(VoicechatConnection connection, EntitySoundPacket packet);
/**
* Sends the sound packet to the provided connection.
*
* @param connection the connection to send the packet to
* @param packet the packet to send
*/
void sendLocationalSoundPacketTo(VoicechatConnection connection, LocationalSoundPacket packet);
/**
* Sends the sound packet to the provided connection.
*
* @param connection the connection to send the packet to
* @param packet the packet to send
*/
void sendStaticSoundPacketTo(VoicechatConnection connection, StaticSoundPacket packet);
/**
* Creates a sound packet that can be converted to any type.
* <b>NOTE</b>: When creating a packet using this method, the sequence number must be set manually using {@link de.maxhenkel.voicechat.api.packets.SoundPacket.Builder#sequenceNumber(long)}.
*
* @return a convertable packet without any data
*/
ConvertablePacket createPacket();
/**
* Creates a sound channel for the specified entity.
*
* @param channelId the ID of the channel - Has to be unique
* @param entity the entity to attach the channel to
* @return the channel
*/
@Nullable
EntityAudioChannel createEntityAudioChannel(UUID channelId, Entity entity);
/**
* Creates a sound channel at the provided location.
*
* @param channelId the ID of the channel - Has to be unique
* @param level the world
* @param initialPosition the position where the sound should be played
* @return the channel
*/
@Nullable
LocationalAudioChannel createLocationalAudioChannel(UUID channelId, ServerLevel level, Position initialPosition);
/**
* Creates a static audio channel.
*
* @param channelId the ID of the channel - Has to be unique
* @param level the level
* @param connection the connection that should hear the audio
* @return the channel
* @deprecated use {@link #createStaticAudioChannel(UUID)} and add a target using {@link StaticAudioChannel#addTarget(VoicechatConnection)}
*/
@Deprecated
@Nullable
StaticAudioChannel createStaticAudioChannel(UUID channelId, ServerLevel level, VoicechatConnection connection);
/**
* Creates a static audio channel.
*
* @param channelId the ID of the channel - Has to be unique
* @return the channel
*/
@Nullable
StaticAudioChannel createStaticAudioChannel(UUID channelId);
/**
* Creates a new audio player.
* <br/>
* <br/>
* <b>NOTE</b>: Never use more than one audio player for every audio channel.
*
* @param audioChannel the channel where the audio player should send the audio to
* @param encoder the opus encoder used to encode the audio data
* @param audioSupplier this gets called whenever a new audio frame needs to be sent. The size of the array always needs to be 960. To end the playback, return <code>null</code>
* @return the audio player
*/
AudioPlayer createAudioPlayer(AudioChannel audioChannel, OpusEncoder encoder, Supplier<short[]> audioSupplier);
/**
* Creates a new audio player.
* <br/>
* <br/>
* <b>NOTE</b>: Never use more than one audio player for every audio channel.
*
* @param audioChannel the channel where the audio player should send the audio to
* @param encoder the opus encoder used to encode the audio data
* @param audio the audio data
* @return the audio player
*/
AudioPlayer createAudioPlayer(AudioChannel audioChannel, OpusEncoder encoder, short[] audio);
/**
* Creates a new audio sender.
* <br/>
* This can be used to simulate a player sending microphone packets.
* This needs to be registered using {@link #registerAudioSender(AudioSender)} and unregistered using {@link #unregisterAudioSender(AudioSender)}.
*
* @param connection the connection of the player
* @return the audio sender
*/
AudioSender createAudioSender(VoicechatConnection connection);
/**
* <b>NOTE</b>: Only one instance of this can exist per player. This will return <code>false</code> if an audio sender for this player already exists.
* <br/>
* <b>NOTE</b>: The audio sender will only work for players that are connected to the server and don't have the mod installed. Otherwise, it will return <code>false</code>.
*
* @param sender the sender to register
* @return if the sender was registered
*/
boolean registerAudioSender(AudioSender sender);
/**
* Unregisters an audio sender.
*
* @param sender the sender to unregister
* @return <code>false</code> if the audio sender was not registered.
*/
boolean unregisterAudioSender(AudioSender sender);
/**
* @return a {@link PlayerAudioListener} builder
*/
PlayerAudioListener.Builder playerAudioListenerBuilder();
/**
* Registers a new {@link AudioListener}.
* <br/>
* Returns false if the listener is already registered.
*
* @param listener the listener to register
* @return if the listener was registered
*/
boolean registerAudioListener(AudioListener listener);
/**
* Unregisters an {@link AudioListener}.
* <br/>
* Returns false if the listener is already unregistered.
*
* @param listener the listener to unregister
* @return if the listener was unregistered
*/
boolean unregisterAudioListener(AudioListener listener);
/**
* Unregisters an {@link AudioListener}.
* <br/>
* Returns false if the listener is already unregistered.
*
* @param listenerId the {@link AudioListener#getListenerId()} of the listener to unregister
* @return if the listener was unregistered
*/
boolean unregisterAudioListener(UUID listenerId);
/**
* Gets the connection of the player with this UUID.
*
* @param playerUuid the players UUID
* @return the connection or <code>null</code> if the player is not connected
*/
@Nullable
VoicechatConnection getConnectionOf(UUID playerUuid);
/**
* Gets the connection of the player.
*
* @param player the player
* @return the connection or <code>null</code> if the player is not connected
*/
@Nullable
default VoicechatConnection getConnectionOf(ServerPlayer player) {
return getConnectionOf(player.getUuid());
}
/**
* Creates a new group.
*
* @param name the name of the group
* @param password the password of the group - <code>null</code> for no password
* @return the group
* @deprecated use {@link #groupBuilder()} instead
*/
@Deprecated
Group createGroup(String name, @Nullable String password);
/**
* Creates a new group.
*
* @param name the name of the group
* @param password the password of the group - <code>null</code> for no password
* @param persistent if the group should be persistent
* @return the group
* @deprecated use {@link #groupBuilder()} instead
*/
@Deprecated
Group createGroup(String name, @Nullable String password, boolean persistent);
/**
* @return a new group builder
*/
Group.Builder groupBuilder();
/**
* Removes a persistent group.
*
* <b>NOTE</b>: You can't remove a group that is not persistent or has players in it.
*
* @param groupId the ID of the group
* @return if the group was removed
*/
boolean removeGroup(UUID groupId);
/**
* Gets a group by its ID.
*
* @param groupId the ID of the group
* @return the group or <code>null</code> if the group doesn't exist
*/
@Nullable
Group getGroup(UUID groupId);
/**
* @return all groups
*/
Collection<Group> getGroups();
/**
* Gets the secret for the user with this <code>userId</code>.
* Calling this function with a players UUID, that is already connected, this will return the secret of that player.
* Calling it with a new random UUID this will return a new secret.
* Use this with caution - Keep this secret confidential, as this allows connecting to the voice chat as this person.
* Note that if this <code>userId</code> is a players UUID and this player disconnects, the secret will get removed and thus is no longer valid to connect with.
*
* @param userId the user ID
* @return the secret or <code>null</code> if no server is running
* @deprecated this will just return <code>null</code>
*/
@Nullable
@Deprecated
UUID getSecret(UUID userId);
/**
* A convenience method to get all players in the range of a specific location.
*
* @param level the world
* @param pos the location
* @param range the range
* @param filter the filter to exclude specific players
* @return all players in the provided location
*/
Collection<ServerPlayer> getPlayersInRange(ServerLevel level, Position pos, double range, Predicate<ServerPlayer> filter);
/**
* The distance at which audio packets are broadcast.
* <b>NOTE</b>: This is not the distance it can be heard.
*
* @return the maximum distance, voice chat audio packets can be received
*/
double getBroadcastRange();
/**
* A convenience method to get all players in the range of a specific location.
*
* @param level the world
* @param pos the location
* @param range the range
* @return all players in the provided location
*/
default Collection<ServerPlayer> getPlayersInRange(ServerLevel level, Position pos, double range) {
return getPlayersInRange(level, pos, range, null);
}
/**
* Registers a volume category.
* This will get synchronized to all connected clients and all future clients that will connect.
* A category can be created with {@link VoicechatApi#volumeCategoryBuilder()}.
* The category can be unregistered with {@link #unregisterVolumeCategory}.
*
* @param category the category to register
*/
void registerVolumeCategory(VolumeCategory category);
/**
* Unregisters a category for all connected players.
* This will release the texture ID for the icon if one exists on all clients.
*
* @param category the category to remove
*/
default void unregisterVolumeCategory(VolumeCategory category) {
unregisterVolumeCategory(category.getId());
}
/**
* Unregisters a category for all connected players.
* This will release the texture ID for the icon if one exists on all clients.
*
* @param categoryId the category ID to remove
*/
void unregisterVolumeCategory(String categoryId);
/**
* @return all registered volume categories
*/
Collection<VolumeCategory> getVolumeCategories();
/**
* @return a read-only config accessor for the mods server config
*/
ConfigAccessor getServerConfig();
}
@@ -0,0 +1,23 @@
package de.maxhenkel.voicechat.api;
import java.net.SocketAddress;
/**
* A socket used for server side voice chat traffic.
* Can be set using {@link de.maxhenkel.voicechat.api.events.VoicechatServerStartingEvent#setSocketImplementation(VoicechatSocket)}.
*/
public interface VoicechatSocket {
void open(int port, String bindAddress) throws Exception;
RawUdpPacket read() throws Exception;
void send(byte[] data, SocketAddress address) throws Exception;
int getLocalPort();
void close();
boolean isClosed();
}
@@ -0,0 +1,97 @@
package de.maxhenkel.voicechat.api;
import javax.annotation.Nullable;
/**
* A custom volume category for the "adjust volumes screen".
* Create it with {@link VoicechatApi#volumeCategoryBuilder()}.
* Register it with {@link VoicechatServerApi#registerVolumeCategory(VolumeCategory)} or {@link VoicechatClientApi#registerClientVolumeCategory(VolumeCategory)}.
* Unregister it with {@link VoicechatServerApi#unregisterVolumeCategory} or {@link VoicechatClientApi#unregisterClientVolumeCategory}.
*/
public interface VolumeCategory {
/**
* @return the unique ID of the category
*/
String getId();
/**
* @return the full name of the category, that is displayed for the end user
*/
String getName();
/**
* @return the translation key for the full name of the category
*/
@Nullable
String getNameTranslationKey();
/**
* @return the hover tooltip that is shown for this category
*/
@Nullable
String getDescription();
/**
* @return the translation key for the hover tooltip of the category
*/
@Nullable
String getDescriptionTranslationKey();
/**
* @return the icon that is shown for this category
*/
@Nullable
int[][] getIcon();
public interface Builder {
/**
* This ID has to be between 1 and 16 characters and can only contain lowercase <code>a-z</code> and <code>_</code>.
*
* @param id the unique ID of the category
* @return the builder
*/
Builder setId(String id);
/**
* @param name the full name of the category, that is displayed for the end user if no translation is present
* @return the builder
*/
Builder setName(String name);
/**
* @param translationKey the translation key for the full name of the category
* @return the builder
*/
Builder setNameTranslationKey(@Nullable String translationKey);
/**
* @param description the hover tooltip that is shown for this category if no translation is present
* @return the builder
*/
Builder setDescription(@Nullable String description);
/**
* @param translationKey the translation key for the hover tooltip
* @return the builder
*/
Builder setDescriptionTranslationKey(@Nullable String translationKey);
/**
* The array has to be 16x16.
* Each integer represents a pixel in the RGBA format.
*
* @param icon the icon that is shown for this category
* @return the builder
*/
Builder setIcon(@Nullable int[][] icon);
/**
* @return the built category
*/
VolumeCategory build();
}
}
@@ -0,0 +1,27 @@
package de.maxhenkel.voicechat.api.audio;
import de.maxhenkel.voicechat.api.VoicechatApi;
/**
* A utility class to convert audio between different representations.
* <br/>
* Can be obtained by calling {@link VoicechatApi#getAudioConverter()}.
* <br/>
* <br/>
* <b>NOTE</b>: Everything is assumed to be 16 bit PCM audio.
*/
public interface AudioConverter {
short[] bytesToShorts(byte[] bytes);
byte[] shortsToBytes(short[] shorts);
short[] floatsToShorts(float[] floats);
float[] shortsToFloats(short[] shorts);
byte[] floatsToBytes(float[] floats);
float[] bytesToFloats(byte[] bytes);
}
@@ -0,0 +1,4 @@
/**
* Everything related to audio conversion and manipulation.
*/
package de.maxhenkel.voicechat.api.audio;
@@ -0,0 +1,65 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.ServerPlayer;
import de.maxhenkel.voicechat.api.VolumeCategory;
import de.maxhenkel.voicechat.api.packets.MicrophonePacket;
import javax.annotation.Nullable;
import java.util.UUID;
import java.util.function.Predicate;
public interface AudioChannel {
/**
* Sends the audio data to this audio channel.
*
* @param opusData Opus encoded audio data
*/
void send(byte[] opusData);
/**
* Forwards the provided microphone packet to this audio channel.
*
* @param packet the microphone packet containing the audio data
*/
void send(MicrophonePacket packet);
/**
* Applies a filter to the audio channel.
*
* @param filter only sends the data to players where the filter applies
*/
void setFilter(Predicate<ServerPlayer> filter);
/**
* Call this if you are finished sending data.
* You can still use this audio channel after calling this function,
* just don't call it after every packet, only after you are finished sending data.
*/
void flush();
/**
* @return if the audio channel is closed
*/
boolean isClosed();
/**
* @return the ID of the audio channel
*/
UUID getId();
/**
* @return the category ID of the audio channel
*/
@Nullable
String getCategory();
/**
* Make sure you registered your category before using it.
* See {@link de.maxhenkel.voicechat.api.VoicechatServerApi#registerVolumeCategory(VolumeCategory)}.
*
* @param category the category ID of the audio channel
*/
void setCategory(@Nullable String category);
}
@@ -0,0 +1,39 @@
package de.maxhenkel.voicechat.api.audiochannel;
/**
* Streams audio data from the server to clients.
* A player instance can be obtained by calling {@link de.maxhenkel.voicechat.api.VoicechatServerApi#createAudioPlayer}.
*/
public interface AudioPlayer {
/**
* Starts playing/streaming the audio.
*/
void startPlaying();
/**
* Stops playing/streaming the audio.
*/
void stopPlaying();
/**
* @return if the player has been started
*/
boolean isStarted();
/**
* @return if the audio is still playing
*/
boolean isPlaying();
/**
* @return if the player stopped playing
*/
boolean isStopped();
/**
* @param onStopped a runnable that's called when the player is finished playing
*/
void setOnStopped(Runnable onStopped);
}
@@ -0,0 +1,38 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.VolumeCategory;
import javax.annotation.Nullable;
import java.util.UUID;
public interface ClientAudioChannel {
/**
* @return the ID of the channel
*/
UUID getId();
/**
* Plays this audio data on this audio channel.
* <br>
* Providing audio that's not 960 samples will be ignored.
*
* @param rawAudio the raw 16 bit PCM audio frame
*/
void play(short[] rawAudio);
/**
* @return the category ID of the audio channel
*/
@Nullable
String getCategory();
/**
* Make sure you registered your category before using it.
* See {@link de.maxhenkel.voicechat.api.VoicechatServerApi#registerVolumeCategory(VolumeCategory)}.
*
* @param category the category of the audio channel
*/
void setCategory(@Nullable String category);
}
@@ -0,0 +1,32 @@
package de.maxhenkel.voicechat.api.audiochannel;
import java.util.UUID;
public interface ClientEntityAudioChannel extends ClientAudioChannel {
/**
* @return the UUID of the entity
*/
UUID getEntityId();
/**
* @param whispering if the entity should whisper
*/
void setWhispering(boolean whispering);
/**
* @return if the entity is whispering
*/
boolean isWhispering();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* @param distance the distance, the audio can be heard
*/
void setDistance(float distance);
}
@@ -0,0 +1,29 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.Position;
public interface ClientLocationalAudioChannel extends ClientAudioChannel {
/**
* @return the current location of the audio channel
*/
Position getLocation();
/**
* Updates the location of the audio channel.
*
* @param position the location of the audio channel
*/
void setLocation(Position position);
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* @param distance the distance, the audio can be heard
*/
void setDistance(float distance);
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.audiochannel;
public interface ClientStaticAudioChannel extends ClientAudioChannel {
}
@@ -0,0 +1,43 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.Entity;
/**
* An audio channel that is bound to an entity.
* <b>NOTE</b>: If you are using this for a player, you need to use {@link #updateEntity(Entity)} to update the player after it died.
*/
public interface EntityAudioChannel extends AudioChannel {
/**
* @return if the entity is whispering
*/
boolean isWhispering();
/**
* @param whispering if the entity should whisper
*/
void setWhispering(boolean whispering);
/**
* Sets a new entity where this channel is attached to.
*
* @param entity the entity to attach the channel to
*/
void updateEntity(Entity entity);
/**
* @return the entity where the channel is attached to
*/
Entity getEntity();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* @param distance the distance, the audio can be heard
*/
void setDistance(float distance);
}
@@ -0,0 +1,29 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.Position;
public interface LocationalAudioChannel extends AudioChannel {
/**
* Updates the location of the audio.
*
* @param position the audio location
*/
void updateLocation(Position position);
/**
* @return the current location of this channel
*/
Position getLocation();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* @param distance the distance, the audio can be heard
*/
void setDistance(float distance);
}
@@ -0,0 +1,38 @@
package de.maxhenkel.voicechat.api.audiochannel;
import de.maxhenkel.voicechat.api.VoicechatConnection;
public interface StaticAudioChannel extends AudioChannel {
/**
* Sets whether this audio channel should be received by targets in isolated groups.
*
* @param bypassGroupIsolation if group isolation should be bypassed
*/
void setBypassGroupIsolation(boolean bypassGroupIsolation);
/**
* @return if this audio channel is received by targets in isolated groups
*/
boolean bypassesGroupIsolation();
/**
* Adds a target to the audio channel.
*
* @param target the target to add
*/
void addTarget(VoicechatConnection target);
/**
* Removes a target from the audio channel.
*
* @param target the target to remove
*/
void removeTarget(VoicechatConnection target);
/**
* Removes all targets from the audio channel.
*/
void clearTargets();
}
@@ -0,0 +1,4 @@
/**
* Everything related to audio channels and serverside audio.
*/
package de.maxhenkel.voicechat.api.audiochannel;
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.audiolistener;
import java.util.UUID;
public interface AudioListener {
/**
* @return the ID of the listener
*/
UUID getListenerId();
}
@@ -0,0 +1,47 @@
package de.maxhenkel.voicechat.api.audiolistener;
import de.maxhenkel.voicechat.api.ServerPlayer;
import de.maxhenkel.voicechat.api.packets.SoundPacket;
import java.util.UUID;
import java.util.function.Consumer;
public interface PlayerAudioListener extends AudioListener {
/**
* @return the {@link UUID} of the player that is listened to
*/
UUID getPlayerUuid();
public interface Builder {
/**
* <b>Note</b>: It is required to either set a player with this method or with {@link #setPlayer(UUID)}
*
* @param player the player that should be listened to
*/
Builder setPlayer(ServerPlayer player);
/**
* <b>Note</b>: It is required to either set a player with this method or with {@link #setPlayer(ServerPlayer)}
*
* @param playerUuid the player UUID that should be listened to
*/
Builder setPlayer(UUID playerUuid);
/**
* <b>Note</b>: It is required to set a listener
*
* @param listener the listener
*/
Builder setPacketListener(Consumer<SoundPacket> listener);
/**
* @return the built listener
* @throws IllegalStateException if the player or the listener is not set
*/
PlayerAudioListener build();
}
}
@@ -0,0 +1,6 @@
/**
* Utilities for listening to all audio a player would theoretically receive.
* <p>
* Can be obtained by calling {@link de.maxhenkel.voicechat.api.VoicechatServerApi#playerAudioListenerBuilder()}.
*/
package de.maxhenkel.voicechat.api.audiolistener;
@@ -0,0 +1,52 @@
package de.maxhenkel.voicechat.api.audiosender;
public interface AudioSender {
/**
* @param whispering if the player should whisper
* @return the audio sender itself
*/
AudioSender whispering(boolean whispering);
/**
* @return if the player is whispering
*/
boolean isWhispering();
/**
* Sets the sequence number of the packet.
* <br/>
* Setting this will override the automatic sequence numbering.
* <br/>
* If you set this value, consequent calls to {@link AudioSender#send(byte[])} will increase this number by 1.
* If you don't intend this, you need to set the sequence number every time before calling {@link AudioSender#send(byte[])}.
* <br/>
* Calling {@link AudioSender#reset()} will also reset the sequence number to start with <code>0</code> again.
*
* @param sequenceNumber the sequence number (Must be >= 0)
* @return the audio sender itself
*/
AudioSender sequenceNumber(long sequenceNumber);
/**
* @return if a microphone packet can be sent. This will return false if the player has the mod installed or the {@link AudioSender} is not registered.
*/
boolean canSend();
/**
* Acts as if the player has sent a microphone packet.
*
* @param opusEncodedAudioData the opus encoded audio data
* @return <code>true</code> if the packet was sent, <code>false</code> if the sender does have the mod installed or the {@link AudioSender} is not registered
*/
boolean send(byte[] opusEncodedAudioData);
/**
* Resets the sequence number and indicates to clients that the current audio stream is paused/stopped.
* <br/>
* This method should be called when the sending of audio is paused or stopped.
* You can still send audio after this method is called.
*/
boolean reset();
}
@@ -0,0 +1,6 @@
/**
* Utilities for simulating players sending audio without the mod.
* <p>
* Can be obtained by calling {@link de.maxhenkel.voicechat.api.VoicechatServerApi#createAudioSender(de.maxhenkel.voicechat.api.VoicechatConnection)}.
*/
package de.maxhenkel.voicechat.api.audiosender;
@@ -0,0 +1,100 @@
package de.maxhenkel.voicechat.api.config;
import de.maxhenkel.voicechat.api.VoicechatClientApi;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
import javax.annotation.Nullable;
/**
* An interface to access a config.
* This is kept very generic, so it can be used for any config type.
* <br/>
* This contains a few default methods to get primitive values from the config.
* <br/>
* <b>NOTE</b>: You need to implement more complex values yourself (e.g. enums).
* <br/>
* Can be obtained by calling {@link VoicechatServerApi#getServerConfig()} or {@link VoicechatClientApi#getClientConfig()}.
*/
public interface ConfigAccessor {
/**
* Checks if the config has the given key.
*
* @param key the key
* @return <code>true</code> if the config has the key, <code>false</code> otherwise
*/
boolean hasKey(String key);
/**
* Gets the raw value of the given key.
* <b>NOTE</b>: This gets the <i>raw</i> value from the config, so it will be a {@link String} even if the value is a number, a boolean or an enum.
*
* @param key the key
* @return the value or <code>null</code> if the key doesn't exist
*/
@Nullable
String getValue(String key);
/**
* Gets the string value of the given key.
*
* @param key the key
* @param def the default value
* @return the value or the default value if the key doesn't exist
*/
default String getString(String key, String def) {
String value = getValue(key);
if (value == null) {
return def;
}
return value;
}
/**
* Gets the boolean value of the given key.
*
* @param key the key
* @param def the default value
* @return the boolean value, <code>false</code> if the value is not <code>"true"</code> or the default value if the key doesn't exist
*/
default boolean getBoolean(String key, boolean def) {
String value = getValue(key);
if (value == null) {
return def;
}
return Boolean.parseBoolean(value);
}
/**
* Gets the integer value of the given key.
*
* @param key the key
* @param def the default value
* @return the value or the default value if the key doesn't exist
* @throws NumberFormatException if the value is not an integer
*/
default int getInt(String key, int def) {
String value = getValue(key);
if (value == null) {
return def;
}
return Integer.parseInt(value);
}
/**
* Gets the double value of the given key.
*
* @param key the key
* @param def the default value
* @return the value or the default value if the key doesn't exist
* @throws NumberFormatException if the value is not a double
*/
default double getDouble(String key, double def) {
String value = getValue(key);
if (value == null) {
return def;
}
return Double.parseDouble(value);
}
}
@@ -0,0 +1,51 @@
package de.maxhenkel.voicechat.api.config;
import de.maxhenkel.voicechat.api.VoicechatClientApi;
import java.util.UUID;
/**
* An interface to access client side adjusted volumes.
* <br/>
* Can be obtained by calling {@link VoicechatClientApi#getVolumeConfig()}.
*/
public interface VolumeConfigAccessor {
/**
* Gets the volume of a player by its UUID.
* <br/>
* <br/>
* This will always return a value, even if the player is not in the volume config.
* <br/>
* By default, the volume will be <code>1.0</code>.
* A volume of <code>0.0</code> means this player is muted.
* Can be larger than <code>1.0</code>.
* Also works for players that are not online.
* <br/>
* <br/>
* If you want the <code>Other</code> volume (All volumes that are not covered by categories or players), use the UUID <code>00000000-0000-0000-0000-000000000000</code>.
*
* @param uuid the UUID of the player
* @return the volume of the player
* @throws NullPointerException if the UUID is null
*/
double getVolume(UUID uuid);
/**
* Gets the volume of a category by its UUID.
* <br/>
* <br/>
* This will always return a value, even if the category doesn't exist.
* <br/>
* By default, the volume will be <code>1.0</code>.
* A volume of <code>0.0</code> means this category is muted.
* Can be larger than <code>1.0</code>.
* Also works for categories that are currently not registered.
*
* @param category the category
* @return the volume of the category
* @throws NullPointerException if the category is null
*/
double getCategoryVolume(String category);
}
@@ -0,0 +1,4 @@
/**
* Everything configuration related.
*/
package de.maxhenkel.voicechat.api.config;
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatClientApi;
public interface ClientEvent extends Event {
/**
* @return the voice chat client API
*/
VoicechatClientApi getVoicechat();
}
@@ -0,0 +1,73 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Position;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* This event is emitted before the sound is played on the client.
*/
public interface ClientReceiveSoundEvent extends ClientEvent {
/**
* @return the ID of the channel
*/
UUID getId();
/**
* The unencoded audio data.
* <br/>
* Returns an empty array in case of the end of transmission
*
* @return the raw 16 bit PCM audio frame
*/
short[] getRawAudio();
/**
* Overrides the actual audio data that is played.
* <p>
* This is ignored, if the {@link #getRawAudio} array is empty
* <br>
* Providing audio that's not 960 samples will be ignored.
* An empty array skips this frame, which adds latency. To mute the audio, use an array filled with zeros instead.
*
* @param rawAudio the raw 16 bit PCM audio frame
*/
void setRawAudio(@Nullable short[] rawAudio);
public static interface EntitySound extends ClientReceiveSoundEvent {
/**
* @return the UUID of the entity
*/
UUID getEntityId();
/**
* @return if the player is whispering
*/
boolean isWhispering();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
}
public static interface LocationalSound extends ClientReceiveSoundEvent {
/**
* @return the position of the sound
*/
Position getPosition();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
}
public static interface StaticSound extends ClientReceiveSoundEvent {
}
}
@@ -0,0 +1,29 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted before the client encodes the audio and sends it to the server.
*/
public interface ClientSoundEvent extends ClientEvent {
/**
* The unencoded audio data.
* <br/>
* <s>Returns an empty array in case of the end of transmission</s>
*
* @return the raw 16 bit PCM audio frame
*/
short[] getRawAudio();
/**
* Overrides the actual audio data that's sent to the server.
*
* @param rawAudio the raw 16 bit PCM audio frame
*/
void setRawAudio(short[] rawAudio);
/**
* @return if the player is whispering
*/
boolean isWhispering();
}
@@ -0,0 +1,13 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted on the client when the voice chat connects/disconnects.
*/
public interface ClientVoicechatConnectionEvent extends ClientEvent {
/**
* @return if the voice chat is connected to the server
*/
boolean isConnected();
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.ClientVoicechatSocket;
import javax.annotation.Nullable;
public interface ClientVoicechatInitializationEvent extends ClientEvent {
/**
* Sets a custom implementation of the socket used for client side voice chat traffic.
*
* @param socket the custom socket implementation
*/
void setSocketImplementation(ClientVoicechatSocket socket);
/**
* @return the custom socket implementation or <code>null</code> to use voice chats default one
*/
@Nullable
ClientVoicechatSocket getSocketImplementation();
}
@@ -0,0 +1,21 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
public interface CreateGroupEvent extends GroupEvent {
/**
* @return the group that was created
*/
Group getGroup();
/**
* @return the connection of the player that created the group or <code>null</code> if the group was not created by a player
*/
@Nullable
VoicechatConnection getConnection();
}
@@ -0,0 +1,18 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted when the voice chats OpenAL context is created.
*/
public interface CreateOpenALContextEvent extends ClientEvent {
/**
* @return the OpenAL context
*/
long getContext();
/**
* @return the OpenAL device
*/
long getDevice();
}
@@ -0,0 +1,18 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted when the voice chats OpenAL context is destroyed.
*/
public interface DestroyOpenALContextEvent extends ClientEvent {
/**
* @return the OpenAL context - Might be <code>0L</code> in some cases
*/
long getContext();
/**
* @return the OpenAL device - Might be <code>0L</code> in some cases
*/
long getDevice();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.EntitySoundPacket;
/**
* This event is emitted when an entity sound packet is about to get sent to a client.
*/
public interface EntitySoundPacketEvent extends SoundPacketEvent<EntitySoundPacket> {
}
@@ -0,0 +1,23 @@
package de.maxhenkel.voicechat.api.events;
public interface Event {
/**
* @return if this event can be cancelled
*/
boolean isCancellable();
/**
* Cancels this event.
* Does nothing if the event isn't cancellable.
*
* @return if the event was actually cancelled
*/
boolean cancel();
/**
* @return if the event was cancelled
*/
boolean isCancelled();
}
@@ -0,0 +1,28 @@
package de.maxhenkel.voicechat.api.events;
import java.util.function.Consumer;
public interface EventRegistration {
/**
* Registers an event.
*
* @param eventClass the class of the event you want to receive
* @param onPacket the consumer that is called when the event was dispatched
* @param priority the event priority - Higher values mean a higher priority
* @param <T> the event type
*/
<T extends Event> void registerEvent(Class<T> eventClass, Consumer<T> onPacket, int priority);
/**
* Registers an event.
*
* @param eventClass the class of the event you want to receive
* @param onPacket the consumer that is called when the event was dispatched
* @param <T> the event type
*/
default <T extends Event> void registerEvent(Class<T> eventClass, Consumer<T> onPacket) {
registerEvent(eventClass, onPacket, 0);
}
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
public interface GroupEvent extends ServerEvent {
/**
* @return the group - <code>null</code> if there is no group
*/
@Nullable
Group getGroup();
/**
* @return the connection of the player
*/
@Nullable
VoicechatConnection getConnection();
}
@@ -0,0 +1,18 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
public interface JoinGroupEvent extends GroupEvent {
/**
* @return the group that was joined
*/
Group getGroup();
/**
* @return the connection of the player
*/
VoicechatConnection getConnection();
}
@@ -0,0 +1,21 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
public interface LeaveGroupEvent extends GroupEvent {
/**
* @return the group that was left or <code>null</code> if the player was not in a group
*/
@Nullable
Group getGroup();
/**
* @return the connection of the player
*/
VoicechatConnection getConnection();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.LocationalSoundPacket;
/**
* This event is emitted when a locational sound packet is about to get sent to a client.
*/
public interface LocationalSoundPacketEvent extends SoundPacketEvent<LocationalSoundPacket> {
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted before the {@link ClientSoundEvent} is getting called.
* It allows you to merge audio into the audio that is captured from the microphone.
* <br/>
* This is called even when the player is muted or not talking.
* <br/>
* When the voice chat it disabled, this event is not called.
*/
public interface MergeClientSoundEvent extends ClientEvent {
/**
* Merges the audio into the audio that is captured from the microphone.
* <br/>
* Calling this multiple times will merge the audio multiple times into the same audio frame.
*
* @param audio the audio that should get merged
*/
void mergeAudio(short[] audio);
}
@@ -0,0 +1,13 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted on the client when the state of the microphone changes.
*/
public interface MicrophoneMuteEvent extends ClientEvent {
/**
* @return if the microphone is muted
*/
boolean isDisabled();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.MicrophonePacket;
/**
* This event is emitted when a microphone packet arrives at the server.
*/
public interface MicrophonePacketEvent extends PacketEvent<MicrophonePacket> {
}
@@ -0,0 +1,30 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatClientApi;
import java.util.UUID;
/**
* This event is emitted when an icon is getting rendered.
* <br/>
* This event should not be used to do any actual rendering.
* Just use this event to cancel the rendering of the icon.
* <br/>
* <b>Warning: </b> Don't do heavy operations in this event, as it will block the render thread!
* <br/>
* <br/>
* <b>Note: </b> Don't keep references to this event. It will be reused for every icon render to avoid allocations.
* <br/>
* <br/>
* Use {@link VoicechatClientApi#isTalking(UUID)}, {@link VoicechatClientApi#isWhispering(UUID)},
* {@link VoicechatClientApi#isDisabled(UUID)} and {@link VoicechatClientApi#isDisconnected(UUID)}
* to get the status of the player and render it using your mod loaders render event system.
*/
public interface NameTagIconRenderEvent extends ClientEvent {
/**
* @return the UUID of the entity the icon is being rendered for
*/
UUID getEntityId();
}
@@ -0,0 +1,59 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Position;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* This event is emitted for every audio chunk for every audio channel.
* These events are meant to be used to modify OpenAL effects.
* <br/>
* This specific event is emitted after the position of the sound is set, but before the any other OpenAL calls.
* Use {@link Pre} or {@link Post} get earlier or later events respectively.
*/
public interface OpenALSoundEvent extends ClientEvent {
/**
* Returns the position of the sound.
* This returns <code>null</code> for static sounds.
*
* @return the position of the sound
*/
@Nullable
Position getPosition();
/**
* This returns <code>null</code> for non audio channel sounds, like microphone testing.
*
* @return the unique ID of the audio channel
*/
@Nullable
UUID getChannelId();
/**
* @return the OpenAL source
*/
int getSource();
/**
* @return the category of the sound
*/
@Nullable
String getCategory();
/**
* This event is emitted before any OpenAL calls were made by the voice chat.
*/
public interface Pre extends OpenALSoundEvent {
}
/**
* This event is emitted after all OpenAL calls by the voice chat and the audio is added to the buffer queue.
*/
public interface Post extends OpenALSoundEvent {
}
}
@@ -0,0 +1,27 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import de.maxhenkel.voicechat.api.packets.Packet;
import javax.annotation.Nullable;
public interface PacketEvent<T extends Packet> extends ServerEvent {
/**
* @return the packet
*/
T getPacket();
/**
* @return the connection of the player that should receive this packet
*/
@Nullable
VoicechatConnection getReceiverConnection();
/**
* @return the connection of the player that sent this packet or <code>null</code> if it wasn't sent by a player
*/
@Nullable
VoicechatConnection getSenderConnection();
}
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatConnection;
public interface PlayerConnectedEvent extends ServerEvent {
/**
* @return the connection of the player
*/
VoicechatConnection getConnection();
}
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import java.util.UUID;
public interface PlayerDisconnectedEvent extends ServerEvent {
/**
* @return the UUID of the player
*/
UUID getPlayerUuid();
}
@@ -0,0 +1,35 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
import java.util.UUID;
/**
* Called when a player changes its state.
* For example if voice chat connects/disconnects, the player joins/leaves a group or disables voice chat.
*/
public interface PlayerStateChangedEvent extends ServerEvent {
/**
* @return if the player disabled voice chat
*/
boolean isDisabled();
/**
* @return if the player is disconnected from voice chat
*/
boolean isDisconnected();
/**
* @return the uuid of the player
*/
UUID getPlayerUuid();
/**
* @return the connection of the player
*/
@Nullable
VoicechatConnection getConnection();
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.events;
public interface RegisterVolumeCategoryEvent extends VolumeCategoryEvent {
}
@@ -0,0 +1,25 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.Group;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import javax.annotation.Nullable;
/**
* This event is only cancellable if the group is persistent
*/
public interface RemoveGroupEvent extends GroupEvent {
/**
* @return the group that was removed
*/
Group getGroup();
/**
* @return <code>null</code>
*/
@Nullable
@Deprecated
VoicechatConnection getConnection();
}
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
public interface ServerEvent extends Event {
/**
* @return the voice chat server API
*/
VoicechatServerApi getVoicechat();
}
@@ -0,0 +1,23 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.Packet;
public interface SoundPacketEvent<T extends Packet> extends PacketEvent<T> {
String SOURCE_GROUP = "group";
String SOURCE_PROXIMITY = "proximity";
String SOURCE_SPECTATOR = "spectator";
String SOURCE_PLUGIN = "plugin";
/**
* Where the packet originated from.
* It can be either {@value SOURCE_GROUP} for group chats,
* {@value SOURCE_PROXIMITY} for proximity sound,
* {@value SOURCE_SPECTATOR} for spectator sound
* or {@value SOURCE_PLUGIN} if the packet was sent from a plugin.
*
* @return the source of the packet
*/
String getSource();
}
@@ -0,0 +1,10 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.packets.StaticSoundPacket;
/**
* This event is emitted when a static sound packet is about to get sent to a client.
*/
public interface StaticSoundPacketEvent extends SoundPacketEvent<StaticSoundPacket> {
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.events;
public interface UnregisterVolumeCategoryEvent extends VolumeCategoryEvent {
}
@@ -0,0 +1,39 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatConnection;
import de.maxhenkel.voicechat.api.packets.MicrophonePacket;
/**
* This event is emitted after a microphone packet arrives at the server and the distance is processed.
* This can be used to modify the distance at which other players can hear this packet.
*/
public interface VoiceDistanceEvent extends ServerEvent {
/**
* @return the microphone packet
*/
MicrophonePacket getPacket();
/**
* @return the connection of the player
*/
VoicechatConnection getSenderConnection();
/**
* This returns either the default distance that's set on the server or the modified distance if another subscriber changed it already.
* In case a player is whispering, this will return the whisper distance.
* The whisper state can be obtained by calling {@link #getPacket()} and getting {@link MicrophonePacket#isWhispering()}.
*
* @return the distance in blocks
*/
float getDistance();
/**
* Sets the distance this player sends audio at.
* Note that this also includes the whisper distance, if the player is whispering.
*
* @param distance the distance in blocks
*/
void setDistance(float distance);
}
@@ -0,0 +1,24 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.ServerPlayer;
public interface VoiceHostEvent extends ServerEvent {
/**
* @return the voice host string
*/
String getVoiceHost();
/**
* Overwrites voice chats voice host - This is sent to the client and used by it to connect to the server.
*
* @param voiceHost the voice host string
*/
void setVoiceHost(String voiceHost);
/**
* @return the player that is connecting
*/
ServerPlayer getPlayer();
}
@@ -0,0 +1,13 @@
package de.maxhenkel.voicechat.api.events;
/**
* This event is emitted on the client when the voice chat is getting enabled/disabled.
*/
public interface VoicechatDisableEvent extends ClientEvent {
/**
* @return if the voice chat is disabled
*/
boolean isDisabled();
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.events;
public interface VoicechatServerStartedEvent extends ServerEvent {
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VoicechatSocket;
import javax.annotation.Nullable;
public interface VoicechatServerStartingEvent extends ServerEvent {
/**
* Sets a custom implementation of the socket used for voice chat traffic.
*
* @param socket the custom socket implementation
*/
void setSocketImplementation(VoicechatSocket socket);
/**
* @return the custom socket implementation or <code>null</code> to use voice chats default one
*/
@Nullable
VoicechatSocket getSocketImplementation();
}
@@ -0,0 +1,5 @@
package de.maxhenkel.voicechat.api.events;
public interface VoicechatServerStoppedEvent extends ServerEvent {
}
@@ -0,0 +1,12 @@
package de.maxhenkel.voicechat.api.events;
import de.maxhenkel.voicechat.api.VolumeCategory;
public interface VolumeCategoryEvent extends ServerEvent {
/**
* @return the volume category
*/
VolumeCategory getVolumeCategory();
}
@@ -0,0 +1,6 @@
/**
* All registerable events.
* <p>
* Events can be registered by calling {@link de.maxhenkel.voicechat.api.events.EventRegistration#registerEvent}.
*/
package de.maxhenkel.voicechat.api.events;
@@ -0,0 +1,37 @@
package de.maxhenkel.voicechat.api.mp3;
import de.maxhenkel.voicechat.api.VoicechatApi;
import javax.sound.sampled.AudioFormat;
import java.io.IOException;
/**
* You can obtain an instance of this class by calling {@link VoicechatApi#createMp3Decoder}.
*/
public interface Mp3Decoder {
/**
* Decodes the MP3 file and returns the decoded audio data as PCM samples.
*
* @return the decoded audio data as PCM samples
* @throws IOException if an I/O error occurs
*/
short[] decode() throws IOException;
/**
* Decodes the MP3 file if {@link #decode()} has not been called before.
*
* @return the audio format of the decoded audio data
* @throws IOException if an I/O error occurs
*/
AudioFormat getAudioFormat() throws IOException;
/**
* Decodes the MP3 file if {@link #decode()} has not been called before.
*
* @return the bitrate of the mp3 file
* @throws IOException if an I/O error occurs
*/
int getBitrate() throws IOException;
}
@@ -0,0 +1,30 @@
package de.maxhenkel.voicechat.api.mp3;
import de.maxhenkel.voicechat.api.VoicechatApi;
import java.io.IOException;
/**
* You can obtain an instance of this class by calling {@link VoicechatApi#createMp3Encoder}.
*/
public interface Mp3Encoder {
/**
* Encodes the given PCM samples and writes it to the provided output stream.
*
* @param samples the PCM samples to encode
* @throws IOException if an I/O error occurs
*/
void encode(short[] samples) throws IOException;
/**
* Closes the encoder and flushes the output stream.
* Also writes leftover mp3 data to the output stream.
*
* <b>NOTE</b>: Not closing encoders will cause a memory leak in voice chat versions <code>&lt;2.6.3</code>!
*
* @throws IOException if an I/O error occurs
*/
void close() throws IOException;
}
@@ -0,0 +1,4 @@
/**
* Wrappers for mp3 encoding and decoding.
*/
package de.maxhenkel.voicechat.api.mp3;
@@ -0,0 +1,45 @@
package de.maxhenkel.voicechat.api.opus;
import de.maxhenkel.voicechat.api.VoicechatApi;
import javax.annotation.Nullable;
/**
* Instances can be obtained by calling {@link VoicechatApi#createDecoder()}.
*/
public interface OpusDecoder {
/**
* Decodes opus encoded audio data to 16 bit PCM audio.
*
* @param data the opus encoded data or <code>null</code> to do PLC
* @return the 16 bit PCM audio data
*/
short[] decode(@Nullable byte[] data);
/**
* Decodes the provided packet and recovers previous lost frames using FEC.
*
* @param input the input packet
* @param frames the number of frames to return (min 1 for just the current frame)
* @return an array containing the decoded frames - the length of the array is equal to {@param frames}
*/
short[][] decode(byte[] input, int frames);
/**
* Resets the decoders state.
*/
void resetState();
/**
* @return if the decoder is closed
*/
boolean isClosed();
/**
* Closes the decoder.
* <b>NOTE</b>: Not closing encoders will cause a memory leak in voice chat versions <code>&lt;2.6.3</code>!
*/
void close();
}
@@ -0,0 +1,34 @@
package de.maxhenkel.voicechat.api.opus;
import de.maxhenkel.voicechat.api.VoicechatApi;
/**
* Instances can be obtained by calling {@link VoicechatApi#createEncoder()}.
*/
public interface OpusEncoder {
/**
* Encodes 16 bit PCM audio with opus.
*
* @param rawAudio the raw 16 bit PCM audio
* @return the opus encoded data
*/
byte[] encode(short[] rawAudio);
/**
* Resets the encoders state.
*/
void resetState();
/**
* @return if the encoder is closed
*/
boolean isClosed();
/**
* Closes the encoder.
* <b>NOTE</b>: Not closing encoders will cause a memory leak in voice chat versions <code>&lt;2.6.3</code>!
*/
void close();
}
@@ -0,0 +1,8 @@
package de.maxhenkel.voicechat.api.opus;
/**
* The different Opus encoder modes
*/
public enum OpusEncoderMode {
VOIP, AUDIO, RESTRICTED_LOWDELAY;
}
@@ -0,0 +1,4 @@
/**
* Wrappers for the Opus codec.
*/
package de.maxhenkel.voicechat.api.opus;
@@ -0,0 +1,4 @@
/**
* The main Simple Voice Chat API package.
*/
package de.maxhenkel.voicechat.api;
@@ -0,0 +1,64 @@
package de.maxhenkel.voicechat.api.packets;
import de.maxhenkel.voicechat.api.Position;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
import java.util.UUID;
public interface ConvertablePacket {
/**
* <b>NOTE</b>: Make sure to set {@link SoundPacket.Builder#channelId(UUID)} to avoid conflicts with other channels.
*
* @return a builder to build an entity sound packet based on this packet
*/
EntitySoundPacket.Builder<?> entitySoundPacketBuilder();
/**
* <b>NOTE</b>: Make sure to set {@link SoundPacket.Builder#channelId(UUID)} to avoid conflicts with other channels.
*
* @return a builder to build a locational sound packet based on this packet
*/
LocationalSoundPacket.Builder<?> locationalSoundPacketBuilder();
/**
* <b>NOTE</b>: Make sure to set {@link SoundPacket.Builder#channelId(UUID)} to avoid conflicts with other channels.
*
* @return a builder to build a static sound packet based on this packet
*/
StaticSoundPacket.Builder<?> staticSoundPacketBuilder();
/**
* Converts this packet to an entity sound packet.
*
* @param entityUuid the UUID of the entity
* @param whispering if the entity is whispering
* @return the entity sound packet
* @throws UnsupportedOperationException if this is called on a packet created by {@link VoicechatServerApi#createPacket()}
* @deprecated use {@link #entitySoundPacketBuilder()}
*/
@Deprecated
EntitySoundPacket toEntitySoundPacket(UUID entityUuid, boolean whispering);
/**
* Converts this packet to a locational sound packet.
*
* @param position the position of the audio
* @return the locational sound packet
* @throws UnsupportedOperationException if this is called on a packet created by {@link VoicechatServerApi#createPacket()}
* @deprecated use {@link #locationalSoundPacketBuilder()}
*/
@Deprecated
LocationalSoundPacket toLocationalSoundPacket(Position position);
/**
* Converts this packet to a static sound packet.
*
* @return the static sound packet
* @throws UnsupportedOperationException if this is called on a packet created by {@link VoicechatServerApi#createPacket()}
* @deprecated use {@link #staticSoundPacketBuilder()}
*/
@Deprecated
StaticSoundPacket toStaticSoundPacket();
}
@@ -0,0 +1,60 @@
package de.maxhenkel.voicechat.api.packets;
import java.util.UUID;
/**
* The receiver of this event will hear the sound from the specified entity.
* This can be done for all entities, but only players will show the speaker icon.
* If the entity is further away than the maximum voice distance, the receiving player won't actually hear the sound.
*/
public interface EntitySoundPacket extends SoundPacket {
/**
* @return the UUID of the entity
*/
UUID getEntityUuid();
/**
* @return if the entity is whispering
*/
boolean isWhispering();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* A builder to build an entity sound packet.
*
* <b>NOTE</b>: Some values are required to be set.
*
* @param <T> the builder itself
*/
public interface Builder<T extends Builder<T>> extends SoundPacket.Builder<T, EntitySoundPacket> {
/**
* This is required to be set!
* <br>
* The entity UUID is also used as the sender of the packet.
*
* @param entityUuid the UUID of the entity
* @return the builder
*/
T entityUuid(UUID entityUuid);
/**
* @param whispering if the entity should be whispering
* @return the builder
*/
T whispering(boolean whispering);
/**
* @param distance the distance, this packet can be heard
* @return the builder
*/
T distance(float distance);
}
}
@@ -0,0 +1,46 @@
package de.maxhenkel.voicechat.api.packets;
import de.maxhenkel.voicechat.api.Position;
/**
* The receiver of this event will hear the sound from the specified location.
* If the location is further away than the maximum voice distance, the receiving player won't actually hear the sound.
*/
public interface LocationalSoundPacket extends SoundPacket {
/**
* @return the audio location
*/
Position getPosition();
/**
* @return the distance, the audio can be heard
*/
float getDistance();
/**
* A builder to build a locational sound packet.
*
* <b>NOTE</b>: Some values are required to be set.
*
* @param <T> the builder itself
*/
public interface Builder<T extends Builder<T>> extends SoundPacket.Builder<T, LocationalSoundPacket> {
/**
* This is required to be set!
*
* @param position the position of the sound
* @return the builder
*/
T position(Position position);
/**
* @param distance the distance, this packet can be heard
* @return the builder
*/
T distance(float distance);
}
}
@@ -0,0 +1,22 @@
package de.maxhenkel.voicechat.api.packets;
public interface MicrophonePacket extends Packet, ConvertablePacket {
/**
* @return if the player is whispering
*/
boolean isWhispering();
/**
* @return the opus encoded audio data from the player
*/
byte[] getOpusEncodedData();
/**
* Allows you to modify or replace the opus encoded audio data.
*
* @param data the opus encoded audio data to replace
*/
void setOpusEncodedData(byte[] data);
}
@@ -0,0 +1,4 @@
package de.maxhenkel.voicechat.api.packets;
public interface Packet {
}
@@ -0,0 +1,100 @@
package de.maxhenkel.voicechat.api.packets;
import de.maxhenkel.voicechat.api.VoicechatServerApi;
import de.maxhenkel.voicechat.api.VolumeCategory;
import javax.annotation.Nullable;
import java.util.UUID;
public interface SoundPacket extends Packet, ConvertablePacket {
/**
* @return the channel ID of the sound packet
*/
UUID getChannelId();
/**
* @return the sender of this packet - doesn't necessarily need to be a player's UUID
*/
UUID getSender();
/**
* @return the opus encoded audio data
*/
byte[] getOpusEncodedData();
/**
* @return the sequence number of the packet
*/
long getSequenceNumber();
/**
* @return the category ID of the sound packet
*/
@Nullable
String getCategory();
/**
* A builder to build a sound packet.
*
* @param <T> the builder itself
* @param <P> the packet, the builder builds
*/
public interface Builder<T extends Builder<T, P>, P extends SoundPacket> {
/**
* <b>NOTE</b>: Make sure to set this to a unique value to avoid conflicts with other channels.
* This value needs to be the same when sending multiple packets of the same channel.
*
* @param channelId the channel ID of the sound packet
* @return the builder
*/
T channelId(UUID channelId);
/**
* Sets the sender of the sound packet.
* This must be set if this is created using {@link VoicechatServerApi#createPacket()}.
*
* @param sender the sender of the sound packet
* @return the builder
*/
T sender(UUID sender);
/**
* @param data the opus encoded audio data
* @return the builder
*/
T opusEncodedData(byte[] data);
/**
* Sets the sequence number of the sound packet.
* The sequence number is set automatically for everything except when using {@link VoicechatServerApi#createPacket()}.
* <br/>
* <br/>
* <b>NOTE</b>: You should only change this when using {@link VoicechatServerApi#createPacket()}.
*
* @param sequenceNumber the sequence number of the sound packet
* @return the builder
*/
T sequenceNumber(long sequenceNumber);
/**
* Make sure you registered your category before using it.
* See {@link de.maxhenkel.voicechat.api.VoicechatServerApi#registerVolumeCategory(VolumeCategory)}.
*
* @param category the category ID of the sound packet
* @return the builder
*/
T category(@Nullable String category);
/**
* Builds the packet.
* <b>NOTE</b>: If you are missing required values, this will throw an {@link IllegalStateException}.
*
* @return the packet
*/
P build();
}
}
@@ -0,0 +1,18 @@
package de.maxhenkel.voicechat.api.packets;
/**
* The receiver of this event will hear the sound non-directional.
* This is what is basically used for group chats.
*/
public interface StaticSoundPacket extends SoundPacket {
/**
* A builder to build a static sound packet.
*
* @param <T> the builder itself
*/
public interface Builder<T extends StaticSoundPacket.Builder<T>> extends SoundPacket.Builder<T, StaticSoundPacket> {
}
}
@@ -0,0 +1,4 @@
/**
* All voice chat UDP packets.
*/
package de.maxhenkel.voicechat.api.packets;