Sources
Sources are used to play audio to the players.
Stereo sources
PV 2.0 introduced the stereo sources. You can find the difference between stereo and mono sources here: Stereo sources.
Creating a source
All sources are attached to source lines, the process of registering a source requires prior registration of a Source Line.
Static source
Static sources are attached to the certain positions in the world.
PlasmoVoiceServer voiceServer = /* */;
ServerSourceLine sourceLine = voiceServer.getSourceLineManager()
.getLineByName("proximity")
.orElseThrow(() -> new IllegalStateException("Proximity source line not found"));
McServerWorld world = voiceServer.getMinecraftServer()
.getWorlds()
.stream()
.filter(w -> w.getName().equals("world"))
.findAny()
.orElseThrow(() -> new IllegalStateException("World not found"));
ServerPos3d position = new ServerPos3d(world, 0.0D, 64.0D, 0.0D);
ServerStaticSource source = sourceLine.createStaticSource(position, false);
// You can also change the source position after it has been created:
ServerPos3d newPosition = new ServerPos3d(world, 10.0D, 64.0D, 0.0D);
source.setPosition(newPosition);
val voiceServer: PlasmoVoiceServer
val sourceLine = voiceServer.sourceLineManager
.getLineByName("proximity")
.orElseThrow { IllegalStateException("Proximity source line not found") }
val world = voiceServer.minecraftServer.worlds
.find { it.name == "world" }
?: throw IllegalStateException("World not found")
val position = ServerPos3d(world, 0.0, 64.0, 0.0)
val source = sourceLine.createStaticSource(position, false)
// You can also change the source position after it has been created:
val newPosition = ServerPos3d(world, 10.0, 64.0, 0.0)
source.position = newPosition
Entity source
Entity sources are attached to the entities.
PlasmoVoiceServer voiceServer = /* */;
ServerSourceLine sourceLine = voiceServer.getSourceLineManager()
.getLineByName("proximity")
.orElseThrow(() -> new IllegalStateException("Proximity source line not found"));
Object platformEntity = /* platform-specific entity instance, e.g org.bukkit.entity.Entity */;
McServerEntity entity = voiceServer.getMinecraftServer().getEntityByInstance(platformEntity);
ServerEntitySource source = sourceLine.createEntitySource(entity, false);
val voiceServer: PlasmoVoiceServer
val sourceLine = voiceServer.sourceLineManager
.getLineByName("proximity")
.orElseThrow { IllegalStateException("Proximity source line not found") }
val platformEntity: Any = /* platform-specific entity instance, e.g org.bukkit.entity.Entity */
val entity = voiceServer.minecraftServer.getEntityByInstance(platformEntity)
val source = sourceLine.createEntitySource(entity, false)
Player source
Player source is attached to the player, audio will be played at a certain distance around the player.
PlasmoVoiceServer voiceServer = /* */;
ServerSourceLine sourceLine = voiceServer.getSourceLineManager()
.getLineByName("proximity")
.orElseThrow(() -> new IllegalStateException("Proximity source line not found"));
VoiceServerPlayer voicePlayer = voiceServer.getPlayerManager()
.getPlayerByName("Apehum")
.orElseThrow(() -> new IllegalStateException("Player not found"));
ServerPlayerSource source = sourceLine.createPlayerSource(voicePlayer, false);
val voiceServer: PlasmoVoiceServer
val sourceLine = voiceServer.sourceLineManager
.getLineByName("proximity")
.orElseThrow { IllegalStateException("proximity source line not found") }
val voicePlayer = voiceServer.playerManager
.getPlayerByName("Apehum")
.orElseThrow { IllegalStateException("player not found") }
val source = sourceLine.createPlayerSource(voicePlayer, false)
By default, the player attached to the source can't hear the audio. If you want to override this behaviour, you need to remove the first source filter:
ServerPlayerSource source = /* */;
source.getFilters()
.stream()
.findFirst()
.ifPresent(source::removeFilter);
val source: ServerPlayerSource = /* */
source.removeFilter(source.filters.first())
You can also remove all filters, but beware that vanish support won't work for this source.
Direct source
Direct sources are attached to a specific player and can only be heard by that player.
You can also set the sender using BaseServerDirectSource#setSender, which will be displayed in the player overlay.
PlasmoVoiceServer voiceServer = /* */;
ServerSourceLine sourceLine = voiceServer.getSourceLineManager()
.getLineByName("proximity")
.orElseThrow(() -> new IllegalStateException("Proximity source line not found"));
VoicePlayer voicePlayer = voiceServer.getPlayerManager()
.getPlayerByName("Apehum")
.orElseThrow(() -> new IllegalStateException("Player not found"));
ServerDirectSource source = sourceLine.createDirectSource(voicePlayer, false);
val voiceServer: PlasmoVoiceServer
val sourceLine = voiceServer.sourceLineManager
.getLineByName("proximity")
.orElseThrow { IllegalStateException("Proximity source line not found") }
val voicePlayer: VoicePlayer = voiceServer.playerManager
.getPlayerByName("Apehum")
.orElseThrow { IllegalStateException("Player not found") }
val source = sourceLine.createDirectSource(voicePlayer, false)
Broadcast source
Broadcast sources are similar to direct sources, but they are attached to a group of players and can only be heard by those specific players.
By default, the group of players is not set, which means the audio will be heard by all players with Plasmo Voice installed. To change a group of players, use ServerBroadcastSource#setPlayers.
PlasmoVoiceServer voiceServer = /* */;
ServerSourceLine sourceLine = voiceServer.getSourceLineManager()
.getLineByName("proximity")
.orElseThrow(() -> new IllegalStateException("Proximity source line not found"));
ServerBroadcastSource source = sourceLine.createBroadcastSource(false);
// Set "listeners" for broadcast source
VoicePlayer player = voiceServer.getPlayerManager()
.getPlayerByName("Apehum")
.orElseThrow(() -> new IllegalStateException("Player not found"));
Set<VoicePlayer> players = new HashSet<>();
players.add(player);
source.setPlayers(players);
val voiceServer: PlasmoVoiceServer
val sourceLine = voiceServer.sourceLineManager
.getLineByName("proximity")
.orElseThrow { IllegalStateException("Proximity source line not found") }
val source = sourceLine.createBroadcastSource(false)
val player = voiceServer.playerManager
.getPlayerByName("Apehum")
.orElseThrow { IllegalStateException("Player not found") }
val players: MutableSet<VoicePlayer> = HashSet()
players.add(player)
source.players = players
Player activation info
By default, the player sending audio to the activation doesn't know the destination source.
So, when handling activation and sending audio to the source, you should add PlayerActivationInfo in BaseServerDirectSource or ServerProximitySource.
With that, addons like pv-addon-replaymod will know to which source the current activation is being sent.
ServerActivation activation = /* ... */;
ServerProximitySource<?> source = /* ... */;
activation.onPlayerActivation((player, packet) -> {
source.sendAudioFrame(
packet.getData(),
packet.getSequenceNumber(),
packet.getDistance(),
new PlayerActivationInfo(player, packet)
);
return ServerActivation.Result.HANDLED;
});
val activation: ServerActivation = /* */
val source: ServerProximitySource<*> = /* */
activation.onPlayerActivation { player, packet ->
source.sendAudioFrame(
packet.data,
packet.sequenceNumber,
packet.distance,
PlayerActivationInfo(player, packet)
)
ServerActivation.Result.HANDLED
}
Audio sender
ArrayAudioFrameProvider
Use the ArrayAudioFrameProvider
when you need to send a fixed set of audio samples.
You can also loop the samples using ArrayAudioFrameProvider#loop.
package com.plasmovoice.testaddon;
import su.plo.voice.api.server.PlasmoVoiceServer;
import su.plo.voice.api.server.audio.provider.ArrayAudioFrameProvider;
import su.plo.voice.api.server.audio.source.AudioSender;
import su.plo.voice.api.server.audio.source.ServerPlayerSource;
public final class TestAudioSender {
/**
* Sends the audio samples to an audio source in specified distance.
*
* @param voiceServer Plasmo Voice Server API.
* @param source The audio source to send audio.
* @param samples 48kHz 16-bit mono audio samples.
* @param distance The distance to send audio.
*/
private void sendPacketsToSource(
PlasmoVoiceServer voiceServer,
ServerPlayerSource source,
short[] samples,
short distance
) {
ArrayAudioFrameProvider frameProvider = new ArrayAudioFrameProvider(voiceServer, false);
AudioSender audioSender = source.createAudioSender(frameProvider, distance);
// or
// AudioSender audioSender = source.createAudioSender(frameProvider);
// if you are using direct or broadcast source
frameProvider.addSamples(samples);
audioSender.start();
audioSender.onStop(() -> {
// you need to close the ArrayAudioFrameProvider,
// because it contains the encoder which should be closed after being used,
// otherwise it will cause a memory leak
frameProvider.close();
// you also need to remove the source from the source line
// if you don't need it after the audio was sent
source.remove();
});
// you can also manually stop the AudioSender:
// audioSender.stop()
// or pause/resume:
// audioSender.pause()
// audioSender.resume()
}
}
package com.plasmovoice.testaddon
import su.plo.voice.api.server.PlasmoVoiceServer
import su.plo.voice.api.server.audio.provider.ArrayAudioFrameProvider
import su.plo.voice.api.server.audio.source.ServerPlayerSource
class TestAudioSender {
/**
* Sends the audio samples to an audio source in specified distance.
*
* @param voiceServer Plasmo Voice Server API.
* @param source The audio source to send audio.
* @param samples 48kHz 16-bit mono audio samples.
* @param distance The distance to send audio.
*/
private fun sendPacketsToSource(
voiceServer: PlasmoVoiceServer,
source: ServerPlayerSource,
samples: ShortArray,
distance: Short
) {
val frameProvider = ArrayAudioFrameProvider(voiceServer, false)
val audioSender = source.createAudioSender(frameProvider, distance)
// or
// AudioSender audioSender = source.createAudioSender(frameProvider);
// if you are using direct or broadcast source
frameProvider.addSamples(samples)
audioSender.start()
audioSender.onStop {
// you need to close the ArrayAudioFrameProvider,
// because it contains the encoder which should be closed after being used,
// otherwise it will cause a memory leak
frameProvider.close()
// you also need to remove the source from the source line
// if you don't need it after the audio was sent
source.remove()
}
// you can also manually stop the AudioSender:
// audioSender.stop()
// or pause/resume:
// audioSender.pause()
// audioSender.resume()
}
}
Custom provider
You can create your own audio frame providers by implementing AudioFrameProvider.
You should return encoded and encrypted 20ms audio frames to provide20ms
.
package com.plasmovoice.testaddon;
import su.plo.voice.api.server.PlasmoVoiceServer;
import su.plo.voice.api.server.audio.provider.AudioFrameProvider;
import su.plo.voice.api.server.audio.provider.AudioFrameResult;
import su.plo.voice.api.server.audio.source.AudioSender;
import su.plo.voice.api.server.audio.source.ServerPlayerSource;
public final class CustomFrameProvider {
/**
* Sends the audio samples to an audio source in specified distance.
*
* @param source The audio source to send audio.
* @param distance The distance to send audio.
*/
private void createCustomFrameProvider(
ServerPlayerSource source,
short distance
) {
TestAudioFrameProvider frameProvider = new TestAudioFrameProvider();
AudioSender audioSender = source.createAudioSender(frameProvider, distance);
// or
// AudioSender audioSender = source.createAudioSender(frameProvider);
// if you are using direct or broadcast source
audioSender.start();
audioSender.onStop(() -> {
// you also need to remove the source from the source line
// if you don't need it after the audio was sent
source.remove();
});
}
public class TestAudioFrameProvider implements AudioFrameProvider {
@Override
public AudioFrameResult provide20ms() {
if (/* end of the audio stream*/) {
// [EndOfStream] means that [AudioFrameProvider] reaches the end of the current stream,
// but not completely closed and frames could be sent later
return AudioFrameResult.EndOfStream.INSTANCE;
} else if (/* audio stream finished */) {
// [Finished] means that [AudioFrameProvider] is completely closed and will not return any frames
return AudioFrameResult.Finished.INSTANCE;
}
// null frame means that frame is not ready yet
// return new AudioFrameResult.Provided(null);
byte[] encryptedFrame = new byte[100];
return new AudioFrameResult.Provided(encryptedFrame);
}
}
}
package com.plasmovoice.testaddon
import su.plo.voice.api.server.audio.provider.AudioFrameProvider
import su.plo.voice.api.server.audio.provider.AudioFrameResult
import su.plo.voice.api.server.audio.provider.AudioFrameResult.Provided
import su.plo.voice.api.server.audio.source.ServerPlayerSource
class CustomFrameProvider {
/**
* Sends the audio samples to an audio source in specified distance.
*
* @param source The audio source to send audio.
* @param distance The distance to send audio.
*/
private fun createCustomFrameProvider(
source: ServerPlayerSource,
distance: Short
) {
val frameProvider = TestAudioFrameProvider()
val audioSender = source.createAudioSender(frameProvider, distance)
// or
// AudioSender audioSender = source.createAudioSender(frameProvider);
// if you are using direct or broadcast source
audioSender.start()
audioSender.onStop {
// you also need to remove the source from the source line
// if you don't need it after the audio was sent
source.remove()
}
}
inner class TestAudioFrameProvider : AudioFrameProvider {
override fun provide20ms(): AudioFrameResult {
if (/* end of the audio stream*/) {
// [EndOfStream] means that [AudioFrameProvider] reaches the end of the current stream,
// but not completely closed and frames could be sent later
return AudioFrameResult.EndOfStream
} else if (/* audio stream finished */) {
// [Finished] means that [AudioFrameProvider] is completely closed and will not return any frames
return AudioFrameResult.Finished
}
// null frame means that frame is not ready yet
// return Provided(null);
val encryptedFrame = ByteArray(100)
return Provided(encryptedFrame)
}
}
}