Skip to content
On this page

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.

java
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);
kotlin
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.

java
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);
kotlin
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 sources are attached to the players.

java
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);
kotlin
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)

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.

java
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);
kotlin
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.

java
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);
kotlin
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.

java
ServerActivation activation = /* ... */;
ServerProximitySource<?> source = /* ... */;

activation.onPlayerActivation((player, packet) -> {
    source.sendAudioFrame(
        packet.getData(),
        packet.getSequenceNumber(),
        packet.getDistance(),
        new PlayerActivationInfo(player, packet)
    );

    return ServerActivation.Result.HANDLED;
});
kotlin
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.

java
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()
    }
}
kotlin
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.

java
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);
        }
    }
}
kotlin
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)
        }
    }
}