Possibly at the point where we can send audio into a channel now.
This commit is contained in:
@@ -3,5 +3,6 @@ package com.github.manevolent.ts3j.audio;
|
||||
public enum AudioEncoding {
|
||||
OPUS,
|
||||
SPEEX,
|
||||
PCM
|
||||
PCM,
|
||||
CELT
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.github.manevolent.ts3j.audio;
|
||||
|
||||
import com.github.manevolent.ts3j.enums.CodecType;
|
||||
|
||||
public interface Microphone {
|
||||
default boolean isMuted() {
|
||||
return false;
|
||||
@@ -7,9 +9,7 @@ public interface Microphone {
|
||||
|
||||
boolean isReady();
|
||||
|
||||
default AudioEncoding getEncoding() {
|
||||
return AudioEncoding.PCM;
|
||||
}
|
||||
CodecType getCodec();
|
||||
|
||||
byte[] provide();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class PacketBody0Voice extends PacketBody {
|
||||
if (getRole() == ProtocolRole.SERVER)
|
||||
clientId = buffer.getShort() & 0x0000FFFF;
|
||||
|
||||
codecType = CodecType.fromId((int) (buffer.get() & 0xFF));
|
||||
codecType = CodecType.fromId(buffer.get() & 0xFF);
|
||||
|
||||
codecData = new byte[buffer.remaining()];
|
||||
|
||||
|
||||
+10
@@ -30,6 +30,11 @@ import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Server/client common abstract client socket, used to interact with the UDP connection. Provides most of the protocol
|
||||
* such as fragmentation, compression, encryption, and sliding windows. Handles packet counters and automatically
|
||||
* handles ACK and PING types for the higher layer.
|
||||
*/
|
||||
public abstract class AbstractTeamspeakClientSocket
|
||||
extends AbstractTeamspeakSocket
|
||||
implements TeamspeakClientSocket {
|
||||
@@ -257,6 +262,11 @@ public abstract class AbstractTeamspeakClientSocket
|
||||
case COMMAND_LOW:
|
||||
header.setPacketFlag(HeaderFlag.NEW_PROTOCOL, true);
|
||||
break;
|
||||
case VOICE:
|
||||
// > X is a ushort in H2N order of an own audio packet counter
|
||||
// it seems it can be the same as the packet counter so we will let the packethandler do it.
|
||||
((PacketBody0Voice)body).setPacketId(header.getPacketId());
|
||||
break;
|
||||
}
|
||||
|
||||
packet.setHeader(header);
|
||||
|
||||
+62
@@ -1,10 +1,12 @@
|
||||
package com.github.manevolent.ts3j.protocol.socket.client;
|
||||
|
||||
import com.github.manevolent.ts3j.api.Channel;
|
||||
import com.github.manevolent.ts3j.audio.Microphone;
|
||||
import com.github.manevolent.ts3j.command.*;
|
||||
import com.github.manevolent.ts3j.command.parameter.CommandSingleParameter;
|
||||
import com.github.manevolent.ts3j.command.response.AbstractCommandResponse;
|
||||
import com.github.manevolent.ts3j.command.response.CommandResponse;
|
||||
import com.github.manevolent.ts3j.enums.CodecType;
|
||||
import com.github.manevolent.ts3j.event.TS3Event;
|
||||
import com.github.manevolent.ts3j.event.TS3Listener;
|
||||
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||
@@ -12,7 +14,9 @@ import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody0Voice;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command;
|
||||
import com.github.manevolent.ts3j.util.HighPrecisionRecurrentTask;
|
||||
import com.github.manevolent.ts3j.util.Ts3Debugging;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -41,6 +45,10 @@ public class LocalTeamspeakClientSocket
|
||||
private final Map<String, CommandProcessor> namedProcessors = new HashMap<>();
|
||||
private final Object commandSendLock = new Object();
|
||||
|
||||
|
||||
private Microphone microphone;
|
||||
private Thread microphoneThread;
|
||||
|
||||
private List<TS3Listener> listeners = new LinkedList<>();
|
||||
|
||||
private int acceptingReturnCode = 0;
|
||||
@@ -147,6 +155,14 @@ public class LocalTeamspeakClientSocket
|
||||
setState(ClientConnectionState.CONNECTING);
|
||||
|
||||
waitForState(ClientConnectionState.CONNECTED, timeout);
|
||||
|
||||
microphoneThread = new HighPrecisionRecurrentTask(
|
||||
1000,
|
||||
0.020F,
|
||||
new MicrophoneTask()
|
||||
);
|
||||
|
||||
microphoneThread.start();
|
||||
} catch (TimeoutException e) {
|
||||
setState(ClientConnectionState.DISCONNECTED);
|
||||
|
||||
@@ -271,6 +287,52 @@ public class LocalTeamspeakClientSocket
|
||||
return response;
|
||||
}
|
||||
|
||||
public Microphone getMicrophone() {
|
||||
return microphone;
|
||||
}
|
||||
|
||||
public void setMicrophone(Microphone microphone) {
|
||||
if (this.microphone != microphone) {
|
||||
this.microphone = microphone;
|
||||
|
||||
if (microphone == null && microphoneThread != null) {
|
||||
microphoneThread.interrupt();
|
||||
microphoneThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MicrophoneTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
PacketBody0Voice voice = new PacketBody0Voice(getRole().getOut());
|
||||
Microphone microphone;
|
||||
while (isConnected()) {
|
||||
// Attempt to get the current microphone instance, saving it in a variable to prevent
|
||||
// any race conditions by accessing the parent class' this.microphone.
|
||||
if ((microphone = LocalTeamspeakClientSocket.this.microphone) == null)
|
||||
continue;
|
||||
|
||||
if (microphone.isMuted()) {
|
||||
// TODO
|
||||
// Set a state and send a muted event
|
||||
} else if (microphone.isReady()) {
|
||||
voice.setCodecType(microphone.getCodec());
|
||||
voice.setCodecData(microphone.provide());
|
||||
|
||||
try {
|
||||
writePacket(voice);
|
||||
} catch (IOException e) {
|
||||
// All we are really concerned about here is a disconnection event, in which case the loop
|
||||
// will exit.
|
||||
} catch (TimeoutException e) {
|
||||
// Not really possible, as we might expect, voice doesn't have ACKs.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClientCommandResponse<T>
|
||||
extends AbstractCommandResponse<T>
|
||||
implements CommandProcessor {
|
||||
|
||||
+34
@@ -47,10 +47,44 @@ public interface TeamspeakClientSocket extends TeamspeakSocket {
|
||||
|
||||
PacketHandler getHandler();
|
||||
|
||||
/**
|
||||
* Reads a packet from the remote endpoint.
|
||||
*
|
||||
* Note that this call may fight with any other reading threads; it is advisede to use the PacketHandler instead.
|
||||
*
|
||||
* This method does not return ACKs, such as: ACK, ACK_LOW, PING, or PONG. These are protocol-level packets that
|
||||
* are handled automatically by the protocol code.
|
||||
*
|
||||
* This method does return INIT1, COMMAND, COMMAND_LOW, and possibly VOICE, and VOICE_WHISPER.
|
||||
*
|
||||
* This method does not return fragments or encrypted bodies. This is handled by the underlying protocol
|
||||
* implementation.
|
||||
*
|
||||
* @return Packet.
|
||||
* @throws IOException
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
Packet readPacket() throws IOException, TimeoutException;
|
||||
|
||||
/**
|
||||
* Sends a packet body to the remote endpoint, generating header information as necessary.
|
||||
*
|
||||
* @param body Body to send.
|
||||
* @throws IOException if there is an issue sending the packet (i.e. socket closed)
|
||||
* @throws TimeoutException if the packet is not acknowledged in time
|
||||
*/
|
||||
void writePacket(PacketBody body) throws IOException, TimeoutException;
|
||||
|
||||
/**
|
||||
* Writes a packet to the remote endpoint, not generating header information. This must be done by the caller.
|
||||
* The packet will be checked, split, and encrypted as needed.
|
||||
*
|
||||
* This includes packet ID, client ID, type, and flags.
|
||||
*
|
||||
* @param packet raw packet information to send.
|
||||
* @throws IOException
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
void writePacket(Packet packet) throws IOException, TimeoutException;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user