progress
This commit is contained in:
parent
1637fc2998
commit
0a58308b80
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,9 +1,13 @@
|
|||
.gradle
|
||||
.settings
|
||||
.idea
|
||||
build
|
||||
bin
|
||||
eaglercraftbungee/.idea
|
||||
eaglercraftbungee/bin
|
||||
eaglercraftbungee/rundir
|
||||
eaglercraftbungee/test
|
||||
eaglercraftbungee/minecrafthtml5bungee.iml
|
||||
epkcompiler/bin
|
||||
spigot-server/world*
|
||||
eaglercraftbungee/rundir
|
||||
|
@ -13,4 +17,4 @@ stable-download/java/spigot_command/world_the_end/*
|
|||
stable-download/java/spigot_command/server.log
|
||||
stable-download/java/bungee_command/proxy*
|
||||
stable-download/web_
|
||||
lwjgl-rundir/_eagstorage*
|
||||
lwjgl-rundir/_eagstorage*
|
||||
|
|
|
@ -39,7 +39,6 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.ArrayList;
|
||||
import java.io.IOException;
|
||||
import jline.UnsupportedTerminal;
|
||||
import java.io.OutputStream;
|
||||
import net.md_5.bungee.log.LoggingOutputStream;
|
||||
import java.util.logging.Level;
|
||||
import net.md_5.bungee.log.BungeeLogger;
|
||||
|
@ -78,6 +77,7 @@ import net.md_5.bungee.config.YamlConfig;
|
|||
import net.md_5.bungee.eaglercraft.BanList;
|
||||
import net.md_5.bungee.eaglercraft.DomainBlacklist;
|
||||
import net.md_5.bungee.eaglercraft.PluginEaglerSkins;
|
||||
import net.md_5.bungee.eaglercraft.PluginEaglerVoice;
|
||||
import net.md_5.bungee.eaglercraft.WebSocketListener;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
@ -233,6 +233,7 @@ public class BungeeCord extends ProxyServer {
|
|||
this.config.load();
|
||||
this.pluginManager.detectPlugins(this.pluginsFolder);
|
||||
this.pluginManager.addInternalPlugin(new PluginEaglerSkins());
|
||||
this.pluginManager.addInternalPlugin(new PluginEaglerVoice(this.config.getVoiceEnabled()));
|
||||
//if(this.config.getAuthInfo().isEnabled()) this.pluginManager.addInternalPlugin(new PluginEaglerAuth());
|
||||
if (this.reconnectHandler == null) {
|
||||
this.reconnectHandler = new SQLReconnectHandler();
|
||||
|
|
|
@ -19,7 +19,7 @@ public class Team {
|
|||
private Set<String> players;
|
||||
|
||||
public Collection<String> getPlayers() {
|
||||
return (Collection<String>) Collections.unmodifiableSet((Set<?>) this.players);
|
||||
return (Collection<String>) (Collection) Collections.unmodifiableSet((Set<?>) this.players);
|
||||
}
|
||||
|
||||
public void addPlayer(final String name) {
|
||||
|
|
|
@ -24,6 +24,7 @@ public class Configuration {
|
|||
private TMap<String, ServerInfo> servers;
|
||||
private AuthServiceInfo authInfo;
|
||||
private boolean onlineMode;
|
||||
private boolean voiceEnabled;
|
||||
private int playerLimit;
|
||||
private String name;
|
||||
private boolean showBanType;
|
||||
|
@ -56,6 +57,7 @@ public class Configuration {
|
|||
}
|
||||
this.authInfo = adapter.getAuthSettings();
|
||||
this.onlineMode = false;
|
||||
this.voiceEnabled = adapter.getBoolean("voice_enabled", true);
|
||||
this.playerLimit = adapter.getInt("player_limit", this.playerLimit);
|
||||
this.name = adapter.getString("server_name", EaglercraftBungee.name + " Server");
|
||||
this.showBanType = adapter.getBoolean("display_ban_type_on_kick", false);
|
||||
|
@ -113,6 +115,10 @@ public class Configuration {
|
|||
return authInfo;
|
||||
}
|
||||
|
||||
public boolean getVoiceEnabled() {
|
||||
return voiceEnabled;
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return name;
|
||||
}
|
||||
|
|
|
@ -8,9 +8,8 @@ import net.md_5.bungee.api.plugin.Listener;
|
|||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginDescription;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
@ -19,12 +18,23 @@ import java.util.Collections;
|
|||
|
||||
public class PluginEaglerVoice extends Plugin implements Listener {
|
||||
|
||||
private final boolean voiceEnabled;
|
||||
|
||||
private final Map<String, UserConnection> voicePlayers = new HashMap<>();
|
||||
private final Map<String, ExpiringSet<String>> voiceRequests = new HashMap<>();
|
||||
private final Set<String[]> voicePairs = new HashSet<>();
|
||||
|
||||
public PluginEaglerVoice() {
|
||||
private static final int VOICE_SIGNAL_ALLOWED = 0;
|
||||
private static final int VOICE_SIGNAL_REQUEST = 0;
|
||||
private static final int VOICE_SIGNAL_CONNECT = 1;
|
||||
private static final int VOICE_SIGNAL_DISCONNECT = 2;
|
||||
private static final int VOICE_SIGNAL_ICE = 3;
|
||||
private static final int VOICE_SIGNAL_DESC = 4;
|
||||
private static final int VOICE_SIGNAL_GLOBAL = 5;
|
||||
|
||||
public PluginEaglerVoice(boolean voiceEnabled) {
|
||||
super(new PluginDescription("EaglerVoice", PluginEaglerVoice.class.getName(), "1.0.0", "ayunami2000", Collections.emptySet(), null));
|
||||
this.voiceEnabled = voiceEnabled;
|
||||
}
|
||||
|
||||
public void onLoad() {
|
||||
|
@ -41,94 +51,125 @@ public class PluginEaglerVoice extends Plugin implements Listener {
|
|||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent event) {
|
||||
if(event.getSender() instanceof UserConnection && event.getData().length > 0) {
|
||||
UserConnection connection = (UserConnection) event.getSender();
|
||||
String user = connection.getName();
|
||||
byte[] msg = event.getData();
|
||||
try {
|
||||
if("EAG|VoiceJoin".equals(event.getTag())) {
|
||||
if (voicePlayers.containsKey(user)) return; // user is already using voice chat
|
||||
// send out packet for player joined voice
|
||||
// notice: everyone on the server can see this packet!! however, it doesn't do anything but let clients know that the player has turned on voice chat
|
||||
for (UserConnection conn : voicePlayers.values()) conn.sendData("EAG|VoiceJoin", user.getBytes(StandardCharsets.UTF_8));
|
||||
voicePlayers.put(user, connection);
|
||||
}else if("EAG|VoiceLeave".equals(event.getTag())) {
|
||||
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
|
||||
removeUser(user);
|
||||
}else if("EAG|VoiceReq".equals(event.getTag())) {
|
||||
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
|
||||
String targetUser = new String(msg, StandardCharsets.UTF_8);
|
||||
if (user.equals(targetUser)) return; // prevent duplicates
|
||||
if (checkVoicePair(user, targetUser)) return; // already paired
|
||||
if (!voicePlayers.containsKey(targetUser)) return; // target user is not using voice chat
|
||||
if (!voiceRequests.containsKey(user)) voiceRequests.put(user, new ExpiringSet<>(2000));
|
||||
if (voiceRequests.get(user).contains(targetUser)) return;
|
||||
voiceRequests.get(user).add(targetUser);
|
||||
synchronized (voicePlayers) {
|
||||
if (!voiceEnabled) return;
|
||||
if (event.getSender() instanceof UserConnection && event.getData().length > 0) {
|
||||
UserConnection connection = (UserConnection) event.getSender();
|
||||
String user = connection.getName();
|
||||
byte[] msg = event.getData();
|
||||
try {
|
||||
if (!("EAG|Voice".equals(event.getTag()))) return;
|
||||
DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(msg));
|
||||
int sig = streamIn.read();
|
||||
switch (sig) {
|
||||
case VOICE_SIGNAL_CONNECT:
|
||||
if (voicePlayers.containsKey(user)) return; // user is already using voice chat
|
||||
// send out packet for player joined voice
|
||||
// notice: everyone on the server can see this packet!! however, it doesn't do anything but let clients know that the player has turned on voice chat
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.write(VOICE_SIGNAL_CONNECT);
|
||||
dos.writeUTF(user);
|
||||
byte[] out = baos.toByteArray();
|
||||
for (UserConnection conn : voicePlayers.values()) conn.sendData("EAG|Voice", out);
|
||||
voicePlayers.put(user, connection);
|
||||
for (String username : voicePlayers.keySet()) sendVoicePlayers(username);
|
||||
break;
|
||||
case VOICE_SIGNAL_DISCONNECT:
|
||||
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
|
||||
try {
|
||||
String user2 = streamIn.readUTF();
|
||||
if (!voicePlayers.containsKey(user2)) return;
|
||||
if (voicePairs.removeIf(pair -> (pair[0].equals(user) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user)))) {
|
||||
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||
DataOutputStream dos2 = new DataOutputStream(baos2);
|
||||
dos2.write(VOICE_SIGNAL_DISCONNECT);
|
||||
dos2.writeUTF(user);
|
||||
voicePlayers.get(user2).sendData("EAG|Voice", baos2.toByteArray());
|
||||
baos2 = new ByteArrayOutputStream();
|
||||
dos2 = new DataOutputStream(baos2);
|
||||
dos2.write(VOICE_SIGNAL_DISCONNECT);
|
||||
dos2.writeUTF(user2);
|
||||
connection.sendData("EAG|Voice", baos2.toByteArray());
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
removeUser(user);
|
||||
}
|
||||
break;
|
||||
case VOICE_SIGNAL_REQUEST:
|
||||
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
|
||||
String targetUser = streamIn.readUTF();
|
||||
if (user.equals(targetUser)) return; // prevent duplicates
|
||||
if (checkVoicePair(user, targetUser)) return; // already paired
|
||||
if (!voicePlayers.containsKey(targetUser)) return; // target user is not using voice chat
|
||||
if (!voiceRequests.containsKey(user)) voiceRequests.put(user, new ExpiringSet<>(2000));
|
||||
if (voiceRequests.get(user).contains(targetUser)) return;
|
||||
voiceRequests.get(user).add(targetUser);
|
||||
|
||||
// check if other has requested earlier
|
||||
if (voiceRequests.containsKey(targetUser) && voiceRequests.get(targetUser).contains(user)) {
|
||||
if (voiceRequests.containsKey(targetUser)) {
|
||||
voiceRequests.get(targetUser).remove(user);
|
||||
if (voiceRequests.get(targetUser).isEmpty()) voiceRequests.remove(targetUser);
|
||||
}
|
||||
if (voiceRequests.containsKey(user)) {
|
||||
voiceRequests.get(user).remove(targetUser);
|
||||
if (voiceRequests.get(user).isEmpty()) voiceRequests.remove(user);
|
||||
}
|
||||
// send each other add data
|
||||
voicePairs.add(new String[] { user, targetUser });
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("username", user);
|
||||
json.put("offer", false);
|
||||
voicePlayers.get(targetUser).sendData("EAG|VoiceAdd", json.toString().getBytes(StandardCharsets.UTF_8));
|
||||
json.put("username", targetUser);
|
||||
json.put("offer", true);
|
||||
connection.sendData("EAG|VoiceAdd", json.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
} else if("EAG|VoiceRemove".equals(event.getTag())) {
|
||||
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
|
||||
String targetUser = new String(msg, StandardCharsets.UTF_8);
|
||||
if (voicePairs.removeIf(pair -> (pair[0].equals(user) && pair[1].equals(targetUser)) || (pair[0].equals(targetUser) && pair[1].equals(user)))) voicePlayers.get(targetUser).sendData("EAG|VoiceRemove", user.getBytes(StandardCharsets.UTF_8));
|
||||
}else if("EAG|VoiceIce".equals(event.getTag())) {
|
||||
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
|
||||
JSONObject json = new JSONObject(new String(msg));
|
||||
if (json.has("username") && json.get("username") instanceof String) {
|
||||
String targetUser = json.getString("username");
|
||||
if (checkVoicePair(user, targetUser)) {
|
||||
if (json.has("ice_candidate")) {
|
||||
// todo: limit ice_candidate data length or sanitize it fully
|
||||
json.keySet().removeIf(s -> !s.equals("ice_candidate"));
|
||||
json.put("username", user);
|
||||
voicePlayers.get(targetUser).sendData("EAG|VoiceIce", json.toString().getBytes(StandardCharsets.UTF_8));
|
||||
// check if other has requested earlier
|
||||
if (voiceRequests.containsKey(targetUser) && voiceRequests.get(targetUser).contains(user)) {
|
||||
if (voiceRequests.containsKey(targetUser)) {
|
||||
voiceRequests.get(targetUser).remove(user);
|
||||
if (voiceRequests.get(targetUser).isEmpty()) voiceRequests.remove(targetUser);
|
||||
}
|
||||
if (voiceRequests.containsKey(user)) {
|
||||
voiceRequests.get(user).remove(targetUser);
|
||||
if (voiceRequests.get(user).isEmpty()) voiceRequests.remove(user);
|
||||
}
|
||||
// send each other add data
|
||||
voicePairs.add(new String[]{user, targetUser});
|
||||
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||
DataOutputStream dos2 = new DataOutputStream(baos2);
|
||||
dos2.write(VOICE_SIGNAL_CONNECT);
|
||||
dos2.writeUTF(user);
|
||||
dos2.writeBoolean(false);
|
||||
voicePlayers.get(targetUser).sendData("EAG|Voice", baos2.toByteArray());
|
||||
baos2 = new ByteArrayOutputStream();
|
||||
dos2 = new DataOutputStream(baos2);
|
||||
dos2.write(VOICE_SIGNAL_CONNECT);
|
||||
dos2.writeUTF(targetUser);
|
||||
dos2.writeBoolean(true);
|
||||
connection.sendData("EAG|Voice", baos2.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}else if("EAG|VoiceDesc".equals(event.getTag())) {
|
||||
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
|
||||
JSONObject json = new JSONObject(new String(msg));
|
||||
if (json.has("username") && json.get("username") instanceof String) {
|
||||
String targetUser = json.getString("username");
|
||||
if (checkVoicePair(user, targetUser)) {
|
||||
if (json.has("session_description")) {
|
||||
// todo: limit session_description data length or sanitize it fully
|
||||
json.keySet().removeIf(s -> !s.equals("session_description"));
|
||||
json.put("username", user);
|
||||
voicePlayers.get(targetUser).sendData("EAG|VoiceDesc", json.toString().getBytes(StandardCharsets.UTF_8));
|
||||
break;
|
||||
case VOICE_SIGNAL_ICE:
|
||||
case VOICE_SIGNAL_DESC:
|
||||
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
|
||||
String targetUser2 = streamIn.readUTF();
|
||||
if (checkVoicePair(user, targetUser2)) {
|
||||
String data = streamIn.readUTF();
|
||||
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||
DataOutputStream dos2 = new DataOutputStream(baos2);
|
||||
dos2.write(sig);
|
||||
dos2.writeUTF(user);
|
||||
dos2.writeUTF(data);
|
||||
voicePlayers.get(targetUser2).sendData("EAG|Voice", baos2.toByteArray());
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// hacker
|
||||
// t.printStackTrace(); // todo: remove in production
|
||||
removeUser(user);
|
||||
}
|
||||
}catch(Throwable t) {
|
||||
// hacker
|
||||
t.printStackTrace(); // todo: remove in production
|
||||
removeUser(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPostLogin(PostLoginEvent event) {
|
||||
event.getPlayer().sendData("EAG|Voice", new byte[] { });
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.write(VOICE_SIGNAL_ALLOWED);
|
||||
dos.writeBoolean(voiceEnabled);
|
||||
dos.write(0);
|
||||
//dos.writeUTF("\"stun:stun.l.google.com:19302\""); // todo: add config controls for ICE servers!
|
||||
event.getPlayer().sendData("EAG|Voice", baos.toByteArray());
|
||||
sendVoicePlayers(event.getPlayer().getName());
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
@ -137,18 +178,57 @@ public class PluginEaglerVoice extends Plugin implements Listener {
|
|||
removeUser(nm);
|
||||
}
|
||||
|
||||
public void removeUser(String name) {
|
||||
voicePlayers.remove(name);
|
||||
for (String[] voicePair : voicePairs) {
|
||||
String target = null;
|
||||
if (voicePair[0].equals(name)) {
|
||||
target = voicePair[1];
|
||||
} else if(voicePair[1].equals(name)) {
|
||||
target = voicePair[0];
|
||||
public void sendVoicePlayers(String name) {
|
||||
synchronized (voicePlayers) {
|
||||
if (!voiceEnabled) return;
|
||||
if (!voicePlayers.containsKey(name)) return;
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.write(VOICE_SIGNAL_GLOBAL);
|
||||
Set<String> mostlyGlobalPlayers = new HashSet<>();
|
||||
for (String username : voicePlayers.keySet()) {
|
||||
if (username.equals(name)) continue;
|
||||
if (voicePairs.stream().anyMatch(pair -> (pair[0].equals(name) && pair[1].equals(username)) || (pair[0].equals(username) && pair[1].equals(name))))
|
||||
continue;
|
||||
mostlyGlobalPlayers.add(username);
|
||||
}
|
||||
if (mostlyGlobalPlayers.size() > 0) {
|
||||
dos.writeInt(mostlyGlobalPlayers.size());
|
||||
for (String username : mostlyGlobalPlayers) dos.writeUTF(username);
|
||||
voicePlayers.get(name).sendData("EAG|Voice", baos.toByteArray());
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
if (target != null && voicePlayers.containsKey(target)) voicePlayers.get(target).sendData("EAG|VoiceRemove", name.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
voicePairs.removeIf(pair -> pair[0].equals(name) || pair[1].equals(name));
|
||||
}
|
||||
|
||||
public void removeUser(String name) {
|
||||
synchronized (voicePlayers) {
|
||||
voicePlayers.remove(name);
|
||||
for (String username : voicePlayers.keySet()) {
|
||||
if (!name.equals(username)) sendVoicePlayers(username);
|
||||
}
|
||||
for (String[] voicePair : voicePairs) {
|
||||
String target = null;
|
||||
if (voicePair[0].equals(name)) {
|
||||
target = voicePair[1];
|
||||
} else if (voicePair[1].equals(name)) {
|
||||
target = voicePair[0];
|
||||
}
|
||||
if (target != null && voicePlayers.containsKey(target)) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.write(VOICE_SIGNAL_DISCONNECT);
|
||||
dos.writeUTF(name);
|
||||
voicePlayers.get(target).sendData("EAG|Voice", baos.toByteArray());
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
voicePairs.removeIf(pair -> pair[0].equals(name) || pair[1].equals(name));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkVoicePair(String user1, String user2) {
|
||||
|
|
|
@ -102,7 +102,11 @@ public class WebSocketProxy extends SimpleChannelInboundHandler<ByteBuf> {
|
|||
ByteBuffer toSend = ByteBuffer.allocateDirect(buffer.capacity());
|
||||
toSend.put(buffer.nioBuffer());
|
||||
toSend.flip();
|
||||
client.send(toSend);
|
||||
if (client.isOpen()) {
|
||||
client.send(toSend);
|
||||
} else {
|
||||
killConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Binary file not shown.
113693
javascript/classes.js
113693
javascript/classes.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -4,7 +4,7 @@
|
|||
|
||||
This is the backend for voice channels in eaglercraft, it links with TeaVM EaglerAdapter at runtime
|
||||
|
||||
Copyright 2022 Calder Young. All rights reserved.
|
||||
Copyright 2022 Calder Young & ayunami2000. All rights reserved.
|
||||
|
||||
Based on code written by ayunami2000
|
||||
|
||||
|
@ -23,35 +23,46 @@ window.initializeVoiceClient = (() => {
|
|||
|
||||
class EaglercraftVoicePeer {
|
||||
|
||||
constructor(client, peerId, peerConnection) {
|
||||
constructor(client, peerId, peerConnection, offer) {
|
||||
this.client = client;
|
||||
this.peerId = peerId;
|
||||
this.peerConnection = peerConnection;
|
||||
this.stream = null;
|
||||
|
||||
const self = this;
|
||||
this.peerConnection.addEventListener("icecandidate", (evt) => {
|
||||
if(evt.candidate) {
|
||||
self.client.iceCandidateHandler(self.peerId, evt.candidate.sdpMLineIndex, evt.candidate.candidate.toJSON().stringify());
|
||||
self.client.iceCandidateHandler(self.peerId, JSON.stringify({ sdpMLineIndex: evt.candidate.sdpMLineIndex, candidate: evt.candidate.candidate }));
|
||||
}
|
||||
});
|
||||
|
||||
this.peerConnection.addEventListener("track", (evt) => {
|
||||
self.client.peerTrackHandler(self.peerId, evt.streams[0]);
|
||||
self.rawStream = evt.streams[0];
|
||||
const aud = new Audio();
|
||||
aud.autoplay = true;
|
||||
aud.muted = true;
|
||||
aud.onended = function() {
|
||||
aud.remove();
|
||||
};
|
||||
aud.srcObject = self.rawStream;
|
||||
self.client.peerTrackHandler(self.peerId, self.rawStream);
|
||||
});
|
||||
|
||||
this.peerConnection.addStream(this.client.localMediaStream);
|
||||
this.peerConnection.createOffer((desc) => {
|
||||
const selfDesc = desc;
|
||||
self.peerConnection.setLocalDescription(selfDesc, () => {
|
||||
self.client.descriptionHandler(self.peerId, selfDesc.toJSON().stringify());
|
||||
this.peerConnection.addStream(this.client.localMediaStream.stream);
|
||||
if (offer) {
|
||||
this.peerConnection.createOffer((desc) => {
|
||||
const selfDesc = desc;
|
||||
self.peerConnection.setLocalDescription(selfDesc, () => {
|
||||
self.client.descriptionHandler(self.peerId, JSON.stringify(selfDesc));
|
||||
}, (err) => {
|
||||
console.error("Failed to set local description for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}, (err) => {
|
||||
console.error("Failed to set local description for \"" + self.peerId + "\"! " + err);
|
||||
console.error("Failed to set create offer for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}, (err) => {
|
||||
console.error("Failed to set create offer for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}
|
||||
|
||||
this.peerConnection.addEventListener("connectionstatechange", (evt) => {
|
||||
if(evt.connectionState === 'disconnected') {
|
||||
|
@ -64,33 +75,47 @@ window.initializeVoiceClient = (() => {
|
|||
disconnect() {
|
||||
this.peerConnection.close();
|
||||
}
|
||||
|
||||
mute(muted) {
|
||||
this.rawStream.getAudioTracks()[0].enabled = !muted;
|
||||
}
|
||||
|
||||
setRemoteDescription(descJSON) {
|
||||
const self = this;
|
||||
const remoteDesc = JSON.parse(descJSON);
|
||||
this.peerConnection.setRemoteDescription(remoteDesc, () => {
|
||||
if(remoteDesc.type == 'offer') {
|
||||
self.peerConnection.createAnswer((desc) => {
|
||||
const selfDesc = desc;
|
||||
self.peerConnection.setLocalDescription(selfDesc, () => {
|
||||
self.client.descriptionHandler(self.peerId, selfDesc.toJSON().stringify());
|
||||
try {
|
||||
const remoteDesc = JSON.parse(descJSON);
|
||||
this.peerConnection.setRemoteDescription(remoteDesc, () => {
|
||||
if(remoteDesc.type == 'offer') {
|
||||
self.peerConnection.createAnswer((desc) => {
|
||||
const selfDesc = desc;
|
||||
self.peerConnection.setLocalDescription(selfDesc, () => {
|
||||
self.client.descriptionHandler(self.peerId, JSON.stringify(selfDesc));
|
||||
}, (err) => {
|
||||
console.error("Failed to set local description for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}, (err) => {
|
||||
console.error("Failed to set local description for \"" + self.peerId + "\"! " + err);
|
||||
self.signalDisconnect(peerId);
|
||||
console.error("Failed to create answer for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}, (err) => {
|
||||
console.error("Failed to create answer for \"" + self.peerId + "\"! " + err);
|
||||
self.signalDisconnect(peerId);
|
||||
});
|
||||
}
|
||||
}, (err) => {
|
||||
console.error("Failed to set remote description for \"" + self.peerId + "\"! " + err);
|
||||
self.signalDisconnect(peerId);
|
||||
});
|
||||
}
|
||||
}, (err) => {
|
||||
console.error("Failed to set remote description for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to parse remote description for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
}
|
||||
}
|
||||
|
||||
addICECandidate(candidate) {
|
||||
this.peerConnection.addICECandidate(new RTCIceCandidate(JSON.parse(candidate)));
|
||||
try {
|
||||
this.peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(candidate)));
|
||||
} catch (err) {
|
||||
console.error("Failed to parse ice candidate for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -117,8 +142,12 @@ window.initializeVoiceClient = (() => {
|
|||
|
||||
setICEServers(urls) {
|
||||
this.ICEServers.length = 0;
|
||||
for(var i = 0; i < urls.length; ++i) {
|
||||
this.ICEServers.push({ urls: urls[i] });
|
||||
if (urls.length == 0) {
|
||||
this.ICEServers = [ { urls: "stun:openrelay.metered.ca:80" }, { urls: "turn:openrelay.metered.ca:80", username: "openrelayproject", credential: "openrelayproject" }, { urls: "turn:openrelay.metered.ca:443", username: "openrelayproject", credential: "openrelayproject", }, { urls: "turn:openrelay.metered.ca:443?transport=tcp", username: "openrelayproject", credential: "openrelayproject" } ];
|
||||
} else {
|
||||
for(var i = 0; i < urls.length; ++i) {
|
||||
this.ICEServers.push({ urls: urls[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,10 +168,10 @@ window.initializeVoiceClient = (() => {
|
|||
}
|
||||
|
||||
activateVoice(tk) {
|
||||
this.localRawMediaStream.getAudioTracks()[0].enabled = tk;
|
||||
if(this.hasInit) this.localRawMediaStream.getAudioTracks()[0].enabled = tk;
|
||||
}
|
||||
|
||||
intitializeDevices() {
|
||||
initializeDevices() {
|
||||
if(!this.hasInit) {
|
||||
this.taskState = TASKSTATE_LOADING;
|
||||
const self = this;
|
||||
|
@ -154,11 +183,12 @@ window.initializeVoiceClient = (() => {
|
|||
var localStreamIn = self.microphoneVolumeAudioContext.createMediaStreamSource(stream);
|
||||
localStreamIn.connect(self.localMediaStreamGain);
|
||||
self.localMediaStreamGain.connect(self.localMediaStream);
|
||||
self.localMediaStreamGain.gain = 1.0;
|
||||
self.localMediaStreamGain.gain.value = 1.0;
|
||||
self.readyState = READYSTATE_DEVICE_INITIALIZED;
|
||||
self.taskState = TASKSTATE_COMPLETE;
|
||||
this.hasInit = true;
|
||||
}).catch(() => {
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
self.readyState = READYSTATE_ABORTED;
|
||||
self.taskState = TASKSTATE_FAILED;
|
||||
});
|
||||
|
@ -169,10 +199,12 @@ window.initializeVoiceClient = (() => {
|
|||
}
|
||||
|
||||
setMicVolume(val) {
|
||||
if(val > 0.5) val = 0.5 + (val - 0.5) * 2.0;
|
||||
if(val > 1.5) val = 1.5;
|
||||
if(val < 0.0) val = 0.0;
|
||||
self.localMediaStreamGain.gain = val * 2.0;
|
||||
if(this.hasInit) {
|
||||
if(val > 0.5) val = 0.5 + (val - 0.5) * 2.0;
|
||||
if(val > 1.5) val = 1.5;
|
||||
if(val < 0.0) val = 0.0;
|
||||
this.localMediaStreamGain.gain.value = val * 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
getTaskState() {
|
||||
|
@ -183,9 +215,10 @@ window.initializeVoiceClient = (() => {
|
|||
return this.readyState;
|
||||
}
|
||||
|
||||
signalConnect(peerId) {
|
||||
signalConnect(peerId, offer) {
|
||||
if (!this.hasInit) initializeDevices();
|
||||
const peerConnection = new RTCPeerConnection({ iceServers: this.ICEServers, optional: [ { DtlsSrtpKeyAgreement: true } ] });
|
||||
const peerInstance = new EaglercraftVoicePeer(this, peerId, peerConnection);
|
||||
const peerInstance = new EaglercraftVoicePeer(this, peerId, peerConnection, offer);
|
||||
this.peerList.set(peerId, peerInstance);
|
||||
}
|
||||
|
||||
|
@ -196,14 +229,21 @@ window.initializeVoiceClient = (() => {
|
|||
}
|
||||
}
|
||||
|
||||
signalDisconnect(peerId) {
|
||||
signalDisconnect(peerId, quiet) {
|
||||
var thePeer = this.peerList.get(peerId);
|
||||
if((typeof thePeer !== "undefined") && thePeer !== null) {
|
||||
this.peerList.delete(thePeer);
|
||||
try {
|
||||
thePeer.disconnect();
|
||||
}catch(e) {}
|
||||
this.peerDisconnectHandler(peerId);
|
||||
this.peerDisconnectHandler(peerId, quiet);
|
||||
}
|
||||
}
|
||||
|
||||
mutePeer(peerId, muted) {
|
||||
var thePeer = this.peerList.get(peerId);
|
||||
if((typeof thePeer !== "undefined") && thePeer !== null) {
|
||||
thePeer.mute(muted);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
276
javascript/eagswebrtc.js.bak
Normal file
276
javascript/eagswebrtc.js.bak
Normal file
|
@ -0,0 +1,276 @@
|
|||
"use strict";
|
||||
|
||||
/*
|
||||
|
||||
This is the backend for voice channels in eaglercraft, it links with TeaVM EaglerAdapter at runtime
|
||||
|
||||
Copyright 2022 Calder Young & ayunami2000. All rights reserved.
|
||||
|
||||
Based on code written by ayunami2000
|
||||
|
||||
*/
|
||||
|
||||
window.initializeVoiceClient = (() => {
|
||||
|
||||
const READYSTATE_NONE = 0;
|
||||
const READYSTATE_ABORTED = -1;
|
||||
const READYSTATE_DEVICE_INITIALIZED = 1;
|
||||
|
||||
const TASKSTATE_NONE = -1;
|
||||
const TASKSTATE_LOADING = 0;
|
||||
const TASKSTATE_COMPLETE = 1;
|
||||
const TASKSTATE_FAILED = 2;
|
||||
|
||||
class EaglercraftVoicePeer {
|
||||
|
||||
constructor(client, peerId, peerConnection, offer) {
|
||||
this.client = client;
|
||||
this.peerId = peerId;
|
||||
this.peerConnection = peerConnection;
|
||||
this.stream = null;
|
||||
|
||||
const self = this;
|
||||
this.peerConnection.addEventListener("icecandidate", (evt) => {
|
||||
if(evt.candidate) {
|
||||
self.client.iceCandidateHandler(self.peerId, JSON.stringify({ sdpMLineIndex: evt.candidate.sdpMLineIndex, candidate: evt.candidate.candidate }));
|
||||
}
|
||||
});
|
||||
|
||||
this.peerConnection.addEventListener("track", (evt) => {
|
||||
self.rawStream = evt.streams[0];
|
||||
const aud = new Audio();
|
||||
aud.autoplay = true;
|
||||
aud.muted = true;
|
||||
aud.onended = function() {
|
||||
aud.remove();
|
||||
};
|
||||
aud.srcObject = self.rawStream;
|
||||
self.client.peerTrackHandler(self.peerId, self.rawStream);
|
||||
});
|
||||
|
||||
this.peerConnection.addStream(this.client.localMediaStream.stream);
|
||||
if (offer) {
|
||||
this.peerConnection.createOffer((desc) => {
|
||||
const selfDesc = desc;
|
||||
self.peerConnection.setLocalDescription(selfDesc, () => {
|
||||
self.client.descriptionHandler(self.peerId, JSON.stringify(selfDesc));
|
||||
}, (err) => {
|
||||
console.error("Failed to set local description for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}, (err) => {
|
||||
console.error("Failed to set create offer for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}
|
||||
|
||||
this.peerConnection.addEventListener("connectionstatechange", (evt) => {
|
||||
if(evt.connectionState === 'disconnected') {
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.peerConnection.close();
|
||||
}
|
||||
|
||||
mute(muted) {
|
||||
this.rawStream.getAudioTracks()[0].enabled = !muted;
|
||||
}
|
||||
|
||||
setRemoteDescription(descJSON) {
|
||||
const self = this;
|
||||
try {
|
||||
const remoteDesc = JSON.parse(descJSON);
|
||||
this.peerConnection.setRemoteDescription(remoteDesc, () => {
|
||||
if(remoteDesc.type == 'offer') {
|
||||
self.peerConnection.createAnswer((desc) => {
|
||||
const selfDesc = desc;
|
||||
self.peerConnection.setLocalDescription(selfDesc, () => {
|
||||
self.client.descriptionHandler(self.peerId, JSON.stringify(selfDesc));
|
||||
}, (err) => {
|
||||
console.error("Failed to set local description for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}, (err) => {
|
||||
console.error("Failed to create answer for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
}
|
||||
}, (err) => {
|
||||
console.error("Failed to set remote description for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to parse remote description for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
}
|
||||
}
|
||||
|
||||
addICECandidate(candidate) {
|
||||
try {
|
||||
this.peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(candidate)));
|
||||
} catch (err) {
|
||||
console.error("Failed to parse ice candidate for \"" + self.peerId + "\"! " + err);
|
||||
self.client.signalDisconnect(self.peerId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EaglercraftVoiceClient {
|
||||
|
||||
constructor() {
|
||||
this.ICEServers = [];
|
||||
this.hasInit = false;
|
||||
this.peerList = new Map();
|
||||
this.readyState = READYSTATE_NONE;
|
||||
this.taskState = TASKSTATE_NONE;
|
||||
this.iceCandidateHandler = null;
|
||||
this.descriptionHandler = null;
|
||||
this.peerTrackHandler = null;
|
||||
this.peerDisconnectHandler = null;
|
||||
this.peerDisconnectHandlerQuiet = null;
|
||||
this.microphoneVolumeAudioContext = new AudioContext();
|
||||
}
|
||||
|
||||
voiceClientSupported() {
|
||||
return typeof window.RTCPeerConnection !== "undefined" && typeof navigator.mediaDevices !== "undefined" &&
|
||||
typeof navigator.mediaDevices.getUserMedia !== "undefined";
|
||||
}
|
||||
|
||||
setICEServers(urls) {
|
||||
this.ICEServers.length = 0;
|
||||
if (urls.length == 0) {
|
||||
this.ICEServers = [ { urls: "stun:openrelay.metered.ca:80" }, { urls: "turn:openrelay.metered.ca:80", username: "openrelayproject", credential: "openrelayproject" }, { urls: "turn:openrelay.metered.ca:443", username: "openrelayproject", credential: "openrelayproject", }, { urls: "turn:openrelay.metered.ca:443?transport=tcp", username: "openrelayproject", credential: "openrelayproject" } ];
|
||||
} else {
|
||||
for(var i = 0; i < urls.length; ++i) {
|
||||
this.ICEServers.push({ urls: urls[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setICECandidateHandler(cb) {
|
||||
this.iceCandidateHandler = cb;
|
||||
}
|
||||
|
||||
setDescriptionHandler(cb) {
|
||||
this.descriptionHandler = cb;
|
||||
}
|
||||
|
||||
setPeerTrackHandler(cb) {
|
||||
this.peerTrackHandler = cb;
|
||||
}
|
||||
|
||||
setPeerDisconnectHandler(cb) {
|
||||
this.peerDisconnectHandler = cb;
|
||||
}
|
||||
|
||||
setPeerDisconnectHandlerQuiet(cb) {
|
||||
this.peerDisconnectHandlerQuiet = cb;
|
||||
}
|
||||
|
||||
activateVoice(tk) {
|
||||
if(this.hasInit) this.localRawMediaStream.getAudioTracks()[0].enabled = tk;
|
||||
}
|
||||
|
||||
initializeDevices() {
|
||||
if(!this.hasInit) {
|
||||
this.taskState = TASKSTATE_LOADING;
|
||||
const self = this;
|
||||
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then((stream) => {
|
||||
self.localRawMediaStream = stream;
|
||||
self.localRawMediaStream.getAudioTracks()[0].enabled = false;
|
||||
self.localMediaStream = self.microphoneVolumeAudioContext.createMediaStreamDestination();
|
||||
self.localMediaStreamGain = self.microphoneVolumeAudioContext.createGain();
|
||||
var localStreamIn = self.microphoneVolumeAudioContext.createMediaStreamSource(stream);
|
||||
localStreamIn.connect(self.localMediaStreamGain);
|
||||
self.localMediaStreamGain.connect(self.localMediaStream);
|
||||
self.localMediaStreamGain.gain.value = 1.0;
|
||||
self.readyState = READYSTATE_DEVICE_INITIALIZED;
|
||||
self.taskState = TASKSTATE_COMPLETE;
|
||||
this.hasInit = true;
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
self.readyState = READYSTATE_ABORTED;
|
||||
self.taskState = TASKSTATE_FAILED;
|
||||
});
|
||||
}else {
|
||||
self.readyState = READYSTATE_DEVICE_INITIALIZED;
|
||||
self.taskState = TASKSTATE_COMPLETE;
|
||||
}
|
||||
}
|
||||
|
||||
setMicVolume(val) {
|
||||
if(this.hasInit) {
|
||||
if(val > 0.5) val = 0.5 + (val - 0.5) * 2.0;
|
||||
if(val > 1.5) val = 1.5;
|
||||
if(val < 0.0) val = 0.0;
|
||||
this.localMediaStreamGain.gain.value = val * 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
getTaskState() {
|
||||
return this.taskState;
|
||||
}
|
||||
|
||||
getReadyState() {
|
||||
return this.readyState;
|
||||
}
|
||||
|
||||
signalConnect(peerId, offer) {
|
||||
if (!this.hasInit) initializeDevices();
|
||||
const peerConnection = new RTCPeerConnection({ iceServers: this.ICEServers, optional: [ { DtlsSrtpKeyAgreement: true } ] });
|
||||
const peerInstance = new EaglercraftVoicePeer(this, peerId, peerConnection, offer);
|
||||
this.peerList.set(peerId, peerInstance);
|
||||
}
|
||||
|
||||
signalDescription(peerId, descJSON) {
|
||||
var thePeer = this.peerList.get(peerId);
|
||||
if((typeof thePeer !== "undefined") && thePeer !== null) {
|
||||
thePeer.setRemoteDescription(descJSON);
|
||||
}
|
||||
}
|
||||
|
||||
signalDisconnect(peerId, quiet) {
|
||||
var thePeer = this.peerList.get(peerId);
|
||||
if((typeof thePeer !== "undefined") && thePeer !== null) {
|
||||
this.peerList.delete(thePeer);
|
||||
try {
|
||||
thePeer.disconnect();
|
||||
}catch(e) {}
|
||||
if (quoet) {
|
||||
this.peerDisconnectHandlerQuiet(peerId);
|
||||
} else {
|
||||
this.peerDisconnectHandler(peerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutePeer(peerId, muted) {
|
||||
var thePeer = this.peerList.get(peerId);
|
||||
if((typeof thePeer !== "undefined") && thePeer !== null) {
|
||||
thePeer.mute(muted);
|
||||
}
|
||||
}
|
||||
|
||||
signalICECandidate(peerId, candidate) {
|
||||
var thePeer = this.peerList.get(peerId);
|
||||
if((typeof thePeer !== "undefined") && thePeer !== null) {
|
||||
thePeer.addICECandidate(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window.constructVoiceClient = () => new EaglercraftVoiceClient();
|
||||
});
|
||||
|
||||
window.startVoiceClient = () => {
|
||||
if(typeof window.constructVoiceClient !== "function") {
|
||||
window.initializeVoiceClient();
|
||||
}
|
||||
return window.constructVoiceClient();
|
||||
};
|
|
@ -9,11 +9,17 @@ import java.util.Map;
|
|||
|
||||
public class ExpiringSet<T> extends HashSet<T> {
|
||||
private final long expiration;
|
||||
private final ExpiringEvent<T> event;
|
||||
|
||||
private final Map<T, Long> timestamps = new HashMap<>();
|
||||
|
||||
public ExpiringSet(long expiration) {
|
||||
public ExpiringSet(long expiration, ExpiringEvent<T> event) {
|
||||
this.expiration = expiration;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public interface ExpiringEvent<T> {
|
||||
void onExpiration(T item);
|
||||
}
|
||||
|
||||
public void checkForExpirations() {
|
||||
|
@ -23,6 +29,7 @@ public class ExpiringSet<T> extends HashSet<T> {
|
|||
T element = iterator.next();
|
||||
if (super.contains(element)) {
|
||||
if (this.timestamps.get(element) + this.expiration < now) {
|
||||
this.event.onExpiration(element);
|
||||
iterator.remove();
|
||||
super.remove(element);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public class WebsocketNetworkManager implements INetworkManager {
|
|||
public void setNetHandler(NetHandler netHandler) {
|
||||
this.netHandler = netHandler;
|
||||
}
|
||||
|
||||
|
||||
private ByteArrayOutputStream sendBuffer = new ByteArrayOutputStream();
|
||||
|
||||
public void addToSendQueue(Packet var1) {
|
||||
|
@ -95,7 +95,6 @@ public class WebsocketNetworkManager implements INetworkManager {
|
|||
stream.mark();
|
||||
try {
|
||||
Packet pkt = Packet.readPacket(packetStream, false);
|
||||
//System.out.println(pkt.toString());
|
||||
pkt.processPacket(this.netHandler);
|
||||
} catch (EOFException e) {
|
||||
stream.reset();
|
||||
|
@ -117,7 +116,6 @@ public class WebsocketNetworkManager implements INetworkManager {
|
|||
}
|
||||
|
||||
public void serverShutdown() {
|
||||
EaglerAdapter.setVoiceStatus(Voice.VoiceStatus.DISCONNECTED);
|
||||
if(EaglerAdapter.connectionOpen()) {
|
||||
EaglerAdapter.endConnection();
|
||||
EaglerAdapter.setDebugVar("minecraftServer", "null");
|
||||
|
@ -133,7 +131,6 @@ public class WebsocketNetworkManager implements INetworkManager {
|
|||
}
|
||||
|
||||
public void closeConnections() {
|
||||
EaglerAdapter.setVoiceStatus(Voice.VoiceStatus.DISCONNECTED);
|
||||
if(EaglerAdapter.connectionOpen()) {
|
||||
EaglerAdapter.endConnection();
|
||||
EaglerAdapter.setDebugVar("minecraftServer", "null");
|
||||
|
|
|
@ -4,7 +4,6 @@ import java.text.DecimalFormat;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.src.*;
|
||||
import net.lax1dude.eaglercraft.DefaultSkinRenderer;
|
||||
import net.lax1dude.eaglercraft.EaglerAdapter;
|
||||
import net.lax1dude.eaglercraft.EaglerProfile;
|
||||
|
@ -13,6 +12,9 @@ import net.lax1dude.eaglercraft.GuiScreenEditProfile;
|
|||
import net.lax1dude.eaglercraft.GuiScreenLicense;
|
||||
import net.lax1dude.eaglercraft.GuiVoiceOverlay;
|
||||
import net.lax1dude.eaglercraft.LocalStorageManager;
|
||||
import net.lax1dude.eaglercraft.Voice;
|
||||
import net.minecraft.src.*;
|
||||
|
||||
import net.lax1dude.eaglercraft.adapter.Tessellator;
|
||||
import net.lax1dude.eaglercraft.glemu.EffectPipeline;
|
||||
import net.lax1dude.eaglercraft.glemu.FixedFunctionShader;
|
||||
|
@ -244,6 +246,11 @@ public class Minecraft implements Runnable {
|
|||
this.ingameGUI = new GuiIngame(this);
|
||||
this.voiceOverlay = new GuiVoiceOverlay(this);
|
||||
|
||||
ScaledResolution var2 = new ScaledResolution(this.gameSettings, this.displayWidth, this.displayHeight);
|
||||
int var3 = var2.getScaledWidth();
|
||||
int var4 = var2.getScaledHeight();
|
||||
this.voiceOverlay.setResolution(var3, var4);
|
||||
|
||||
//if (this.serverName != null) {
|
||||
// this.displayGuiScreen(new GuiConnecting(new GuiMainMenu(), this, this.serverName, this.serverPort));
|
||||
//} else {
|
||||
|
@ -1110,6 +1117,25 @@ public class Minecraft implements Runnable {
|
|||
|
||||
GuiMultiplayer.tickRefreshCooldown();
|
||||
EaglerAdapter.tickVoice();
|
||||
EaglerAdapter.activateVoice(EaglerAdapter.isKeyDown(gameSettings.voicePTTKey));
|
||||
if (EaglerAdapter.getVoiceStatus() == Voice.VoiceStatus.CONNECTING || EaglerAdapter.getVoiceStatus() == Voice.VoiceStatus.CONNECTED) {
|
||||
if (EaglerAdapter.getVoiceChannel() == Voice.VoiceChannel.PROXIMITY) {
|
||||
if (this.theWorld != null && this.thePlayer != null) {
|
||||
for (Object playerObject : this.theWorld.playerEntities) {
|
||||
EntityPlayer player = (EntityPlayer) playerObject;
|
||||
if (player == this.thePlayer) continue;
|
||||
EaglerAdapter.updateVoicePosition(player.username, player.posX, player.posY + player.getEyeHeight(), player.posZ);
|
||||
int prox = EaglerAdapter.getVoiceProximity();
|
||||
// cube
|
||||
if (Math.abs(thePlayer.posX - player.posX) < prox && Math.abs(thePlayer.posY - player.posY) < prox && Math.abs(thePlayer.posZ - player.posZ) < prox) {
|
||||
EaglerAdapter.addNearbyPlayer(player.username);
|
||||
} else {
|
||||
EaglerAdapter.removeNearbyPlayer(player.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentScreen == null || this.currentScreen.allowUserInput) {
|
||||
this.mcProfiler.endStartSection("mouse");
|
||||
|
|
|
@ -8,11 +8,9 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.lax1dude.eaglercraft.DefaultSkinRenderer;
|
||||
import net.lax1dude.eaglercraft.EaglerAdapter;
|
||||
import net.lax1dude.eaglercraft.EaglercraftRandom;
|
||||
import net.lax1dude.eaglercraft.WebsocketNetworkManager;
|
||||
import net.lax1dude.eaglercraft.*;
|
||||
import net.lax1dude.eaglercraft.adapter.EaglerAdapterImpl2.RateLimit;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
|
@ -52,6 +50,14 @@ public class NetClientHandler extends NetHandler {
|
|||
public NetClientHandler(Minecraft par1Minecraft, String par2Str, int par3) throws IOException {
|
||||
this.mc = par1Minecraft;
|
||||
this.netManager = new WebsocketNetworkManager(par2Str, null, this);
|
||||
EaglerAdapter.clearVoiceAvailableStatus();
|
||||
EaglerAdapter.setVoiceSignalHandler(new Consumer<byte[]>() {
|
||||
@Override
|
||||
public void accept(byte[] bytes) {
|
||||
NetClientHandler.this.addToSendQueue(new Packet250CustomPayload("EAG|Voice", bytes));
|
||||
}
|
||||
});
|
||||
if (EaglerAdapter.getVoiceChannel() != Voice.VoiceChannel.NONE) EaglerAdapter.sendInitialVoice();
|
||||
}
|
||||
|
||||
//public NetClientHandler(Minecraft par1Minecraft, String par2Str, int par3, GuiScreen par4GuiScreen) throws IOException {
|
||||
|
@ -1166,6 +1172,8 @@ public class NetClientHandler extends NetHandler {
|
|||
} catch (IOException var7) {
|
||||
var7.printStackTrace();
|
||||
}
|
||||
}else if("EAG|Voice".equals(par1Packet250CustomPayload.channel)) {
|
||||
EaglerAdapter.handleVoiceSignal(par1Packet250CustomPayload.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
package net.lax1dude.eaglercraft.adapter;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
|
@ -21,6 +17,7 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.lax1dude.eaglercraft.*;
|
||||
import org.json.JSONObject;
|
||||
import org.teavm.interop.Async;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
|
@ -40,11 +37,7 @@ import org.teavm.jso.dom.events.KeyboardEvent;
|
|||
import org.teavm.jso.dom.events.MessageEvent;
|
||||
import org.teavm.jso.dom.events.MouseEvent;
|
||||
import org.teavm.jso.dom.events.WheelEvent;
|
||||
import org.teavm.jso.dom.html.HTMLCanvasElement;
|
||||
import org.teavm.jso.dom.html.HTMLDocument;
|
||||
import org.teavm.jso.dom.html.HTMLElement;
|
||||
import org.teavm.jso.dom.html.HTMLVideoElement;
|
||||
import org.teavm.jso.dom.html.HTMLImageElement;
|
||||
import org.teavm.jso.dom.html.*;
|
||||
import org.teavm.jso.media.MediaError;
|
||||
import org.teavm.jso.typedarrays.ArrayBuffer;
|
||||
import org.teavm.jso.typedarrays.DataView;
|
||||
|
@ -52,16 +45,7 @@ import org.teavm.jso.typedarrays.Float32Array;
|
|||
import org.teavm.jso.typedarrays.Int32Array;
|
||||
import org.teavm.jso.typedarrays.Uint8Array;
|
||||
import org.teavm.jso.typedarrays.Uint8ClampedArray;
|
||||
import org.teavm.jso.webaudio.AudioBuffer;
|
||||
import org.teavm.jso.webaudio.AudioBufferSourceNode;
|
||||
import org.teavm.jso.webaudio.AudioContext;
|
||||
import org.teavm.jso.webaudio.AudioListener;
|
||||
import org.teavm.jso.webaudio.DecodeErrorCallback;
|
||||
import org.teavm.jso.webaudio.DecodeSuccessCallback;
|
||||
import org.teavm.jso.webaudio.GainNode;
|
||||
import org.teavm.jso.webaudio.MediaElementAudioSourceNode;
|
||||
import org.teavm.jso.webaudio.MediaEvent;
|
||||
import org.teavm.jso.webaudio.PannerNode;
|
||||
import org.teavm.jso.webaudio.*;
|
||||
import org.teavm.jso.webgl.WebGLBuffer;
|
||||
import org.teavm.jso.webgl.WebGLFramebuffer;
|
||||
import org.teavm.jso.webgl.WebGLProgram;
|
||||
|
@ -72,13 +56,6 @@ import org.teavm.jso.webgl.WebGLUniformLocation;
|
|||
import org.teavm.jso.websocket.CloseEvent;
|
||||
import org.teavm.jso.websocket.WebSocket;
|
||||
|
||||
import net.lax1dude.eaglercraft.AssetRepository;
|
||||
import net.lax1dude.eaglercraft.Base64;
|
||||
import net.lax1dude.eaglercraft.EaglerImage;
|
||||
import net.lax1dude.eaglercraft.EarlyLoadScreen;
|
||||
import net.lax1dude.eaglercraft.LocalStorageManager;
|
||||
import net.lax1dude.eaglercraft.ServerQuery;
|
||||
import net.lax1dude.eaglercraft.Voice;
|
||||
import net.lax1dude.eaglercraft.adapter.teavm.WebGLQuery;
|
||||
import net.lax1dude.eaglercraft.adapter.teavm.WebGLVertexArray;
|
||||
import net.minecraft.src.MathHelper;
|
||||
|
@ -1769,13 +1746,15 @@ public class EaglerAdapterImpl2 {
|
|||
|
||||
public static final boolean startConnection(String uri) {
|
||||
String res = connectWebSocket(uri);
|
||||
return "fail".equals(res) ? false : true;
|
||||
return !"fail".equals(res);
|
||||
}
|
||||
public static final void endConnection() {
|
||||
if(sock == null || sock.getReadyState() == 3) {
|
||||
sockIsConnecting = false;
|
||||
}
|
||||
if(sock != null && !sockIsConnecting) sock.close();
|
||||
|
||||
enableVoice(Voice.VoiceChannel.NONE);
|
||||
}
|
||||
public static final boolean connectionOpen() {
|
||||
if(sock == null || sock.getReadyState() == 3) {
|
||||
|
@ -2010,60 +1989,94 @@ public class EaglerAdapterImpl2 {
|
|||
public static final void openConsole() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
//TODO: voice start =======================================================================
|
||||
|
||||
// implementation notes - DO NOT access any net.minecraft.* classes from EaglerAdapterImpl2 this time
|
||||
// implementation notes - Tick all the "for (Object playerObject : Minecraft.getMinecraft().theWorld.playerEntities)" in net.minecraft.client.Minecraft.runTick() or similar
|
||||
|
||||
// implementation notes - try to only connect to client in GLOBAL or LOCAL not both
|
||||
// implementation notes - try to only connect to nearby clients, and disconnect once they've been out of range for more then 5-10 seconds
|
||||
|
||||
// implementation notes - AGAIN, don't access net.minecraft.* classes from this file
|
||||
|
||||
// to ayunami - this is initialized at startup, right before downloadAssetPack
|
||||
|
||||
private static EaglercraftVoiceClient voiceClient = null;
|
||||
|
||||
private static boolean voiceAvailableStat = false;
|
||||
private static boolean voiceSignalHandlersInitialized = false;
|
||||
|
||||
// to ayunami - use this as a callback to send packets on the voice signal channel
|
||||
|
||||
private static Consumer<byte[]> returnSignalHandler = null;
|
||||
|
||||
// to ayunami - call this before joining a new server
|
||||
|
||||
private static final HashMap<String, AnalyserNode> voiceAnalysers = new HashMap<>();
|
||||
private static final HashMap<String, GainNode> voiceGains = new HashMap<>();
|
||||
private static final HashMap<String, PannerNode> voicePanners = new HashMap<>();
|
||||
private static final HashSet<String> nearbyPlayers = new HashSet<>();
|
||||
|
||||
public static void clearVoiceAvailableStatus() {
|
||||
voiceAvailableStat = false;
|
||||
}
|
||||
|
||||
// to ayunami - use this to set returnSignalHandler when a new NetworkManager is created
|
||||
|
||||
public static void setVoiceSignalHandler(Consumer<byte[]> signalHandler) {
|
||||
returnSignalHandler = signalHandler;
|
||||
}
|
||||
|
||||
public static final int VOICE_SIGNAL_ALLOWED_CLIENTBOUND = 0;
|
||||
public static final int VOICE_SIGNAL_ICE_SERVERBOUND = 1;
|
||||
public static final int VOICE_SIGNAL_DESC_SERVERBOUND = 2;
|
||||
|
||||
// to ayunami - use this to pass voice signal packets
|
||||
|
||||
public static final int VOICE_SIGNAL_ALLOWED = 0;
|
||||
public static final int VOICE_SIGNAL_REQUEST = 0;
|
||||
public static final int VOICE_SIGNAL_CONNECT = 1;
|
||||
public static final int VOICE_SIGNAL_DISCONNECT = 2;
|
||||
public static final int VOICE_SIGNAL_ICE = 3;
|
||||
public static final int VOICE_SIGNAL_DESC = 4;
|
||||
public static final int VOICE_SIGNAL_GLOBAL = 5;
|
||||
|
||||
public static void handleVoiceSignal(byte[] data) {
|
||||
try {
|
||||
DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(data));
|
||||
int sig = streamIn.read();
|
||||
switch(sig) {
|
||||
case VOICE_SIGNAL_ALLOWED_CLIENTBOUND:
|
||||
voiceAvailableStat = streamIn.readBoolean();
|
||||
String[] servs = new String[streamIn.read()];
|
||||
for(int i = 0; i < servs.length; ++i) {
|
||||
servs[i] = streamIn.readUTF();
|
||||
}
|
||||
voiceClient.setICEServers(servs);
|
||||
break;
|
||||
default:
|
||||
System.err.println("Unknown voice signal packet '" + sig + "'!");
|
||||
break;
|
||||
case VOICE_SIGNAL_GLOBAL:
|
||||
if (enabledChannel != Voice.VoiceChannel.GLOBAL) return;
|
||||
String[] voicePlayers = new String[streamIn.readInt()];
|
||||
for(int i = 0; i < voicePlayers.length; i++) voicePlayers[i] = streamIn.readUTF();
|
||||
for (String username : voicePlayers) {
|
||||
// notice that literally everyone except for those already connected using voice chat will receive the request; however, ones using proximity will simply ignore it.
|
||||
if (!voiceGains.containsKey(username)) addNearbyPlayer(username);
|
||||
}
|
||||
break;
|
||||
case VOICE_SIGNAL_ALLOWED:
|
||||
voiceAvailableStat = streamIn.readBoolean();
|
||||
String[] servs = new String[streamIn.read()];
|
||||
for(int i = 0; i < servs.length; i++) {
|
||||
servs[i] = streamIn.readUTF();
|
||||
}
|
||||
voiceClient.setICEServers(servs);
|
||||
break;
|
||||
case VOICE_SIGNAL_CONNECT:
|
||||
String peerId = streamIn.readUTF();
|
||||
try {
|
||||
boolean offer = streamIn.readBoolean();
|
||||
voiceClient.signalConnect(peerId, offer);
|
||||
} catch (EOFException e) { // this is actually a connect ANNOUNCE, not an absolute "yes please connect" situation
|
||||
if (enabledChannel == Voice.VoiceChannel.PROXIMITY && !nearbyPlayers.contains(peerId)) return;
|
||||
// send request to peerId
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.write(VOICE_SIGNAL_REQUEST);
|
||||
dos.writeUTF(peerId);
|
||||
returnSignalHandler.accept(baos.toByteArray());
|
||||
}
|
||||
break;
|
||||
case VOICE_SIGNAL_DISCONNECT:
|
||||
String peerId2 = streamIn.readUTF();
|
||||
voiceClient.signalDisconnect(peerId2, true);
|
||||
break;
|
||||
case VOICE_SIGNAL_ICE:
|
||||
String peerId3 = streamIn.readUTF();
|
||||
String candidate = streamIn.readUTF();
|
||||
voiceClient.signalICECandidate(peerId3, candidate);
|
||||
break;
|
||||
case VOICE_SIGNAL_DESC:
|
||||
String peerId4 = streamIn.readUTF();
|
||||
String descJSON = streamIn.readUTF();
|
||||
voiceClient.signalDescription(peerId4, descJSON);
|
||||
break;
|
||||
default:
|
||||
System.err.println("Unknown voice signal packet '" + sig + "'!");
|
||||
break;
|
||||
}
|
||||
}catch(IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2077,24 +2090,75 @@ public class EaglerAdapterImpl2 {
|
|||
return false;
|
||||
}
|
||||
private static Voice.VoiceChannel enabledChannel = Voice.VoiceChannel.NONE;
|
||||
|
||||
// to ayunami - use this to switch channel modes or disable voice
|
||||
|
||||
public static final void addNearbyPlayer(String username) {
|
||||
recentlyNearbyPlayers.remove(username);
|
||||
if (nearbyPlayers.add(username)) {
|
||||
if (getVoiceStatus() == Voice.VoiceStatus.DISCONNECTED || getVoiceStatus() == Voice.VoiceStatus.UNAVAILABLE) return;
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.write(VOICE_SIGNAL_REQUEST);
|
||||
dos.writeUTF(username);
|
||||
returnSignalHandler.accept(baos.toByteArray());
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
private static final ExpiringSet<String> recentlyNearbyPlayers = new ExpiringSet<>(5000, new ExpiringSet.ExpiringEvent<String>() {
|
||||
@Override
|
||||
public void onExpiration(String username) {
|
||||
if (!nearbyPlayers.contains(username)) voiceClient.signalDisconnect(username, false);
|
||||
}
|
||||
});
|
||||
|
||||
public static final void removeNearbyPlayer(String username) {
|
||||
// todo: add 5-10s disconnect delay
|
||||
if (nearbyPlayers.remove(username)) {
|
||||
if (getVoiceStatus() == Voice.VoiceStatus.DISCONNECTED || getVoiceStatus() == Voice.VoiceStatus.UNAVAILABLE) return;
|
||||
recentlyNearbyPlayers.add(username);
|
||||
}
|
||||
}
|
||||
|
||||
public static final void updateVoicePosition(String username, double x, double y, double z) {
|
||||
if (voicePanners.containsKey(username)) voicePanners.get(username).setPosition((float) x, (float) y, (float) z);
|
||||
}
|
||||
|
||||
public static final void sendInitialVoice() {
|
||||
returnSignalHandler.accept(new byte[] { VOICE_SIGNAL_CONNECT });
|
||||
for (String username : nearbyPlayers) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(baos);
|
||||
dos.write(VOICE_SIGNAL_REQUEST);
|
||||
dos.writeUTF(username);
|
||||
returnSignalHandler.accept(baos.toByteArray());
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
public static final void enableVoice(Voice.VoiceChannel enable) {
|
||||
if (enabledChannel == enable) return;
|
||||
if (enabledChannel != Voice.VoiceChannel.NONE) {
|
||||
for (String username : nearbyPlayers) voiceClient.signalDisconnect(username, false);
|
||||
for (String username : recentlyNearbyPlayers) voiceClient.signalDisconnect(username, false);
|
||||
nearbyPlayers.clear();
|
||||
returnSignalHandler.accept(new byte[] { VOICE_SIGNAL_DISCONNECT });
|
||||
}
|
||||
enabledChannel = enable;
|
||||
if(enable == Voice.VoiceChannel.NONE) {
|
||||
if(enable == Voice.VoiceChannel.NONE) {
|
||||
talkStatus = false;
|
||||
}else {
|
||||
if(!voiceSignalHandlersInitialized) {
|
||||
voiceSignalHandlersInitialized = true;
|
||||
voiceClient.setICECandidateHandler(new EaglercraftVoiceClient.ICECandidateHandler() {
|
||||
@Override
|
||||
public void call(String peerId, String sdpMLineIndex, String candidate) {
|
||||
public void call(String peerId, String candidate) {
|
||||
try {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dat = new DataOutputStream(bos);
|
||||
dat.write(VOICE_SIGNAL_ICE_SERVERBOUND);
|
||||
dat.write(VOICE_SIGNAL_ICE);
|
||||
dat.writeUTF(peerId);
|
||||
dat.writeUTF(sdpMLineIndex);
|
||||
dat.writeUTF(candidate);
|
||||
returnSignalHandler.accept(bos.toByteArray());
|
||||
}catch(IOException ex) {
|
||||
|
@ -2107,7 +2171,7 @@ public class EaglerAdapterImpl2 {
|
|||
try {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dat = new DataOutputStream(bos);
|
||||
dat.write(VOICE_SIGNAL_DESC_SERVERBOUND);
|
||||
dat.write(VOICE_SIGNAL_DESC);
|
||||
dat.writeUTF(peerId);
|
||||
dat.writeUTF(candidate);
|
||||
returnSignalHandler.accept(bos.toByteArray());
|
||||
|
@ -2115,8 +2179,74 @@ public class EaglerAdapterImpl2 {
|
|||
}
|
||||
}
|
||||
});
|
||||
voiceClient.setPeerTrackHandler(new EaglercraftVoiceClient.PeerTrackHandler() {
|
||||
@Override
|
||||
public void call(String peerId, MediaStream audioStream) {
|
||||
if (enabledChannel == Voice.VoiceChannel.NONE) return;
|
||||
MediaStreamAudioSourceNode audioNode = audioctx.createMediaStreamSource(audioStream);
|
||||
AnalyserNode analyser = audioctx.createAnalyser();
|
||||
analyser.setSmoothingTimeConstant(0f);
|
||||
analyser.setFftSize(32);
|
||||
audioNode.connect(analyser);
|
||||
voiceAnalysers.put(peerId, analyser);
|
||||
if (enabledChannel == Voice.VoiceChannel.GLOBAL) {
|
||||
GainNode gain = audioctx.createGain();
|
||||
gain.getGain().setValue(getVoiceListenVolume());
|
||||
analyser.connect(gain);
|
||||
gain.connect(audioctx.getDestination());
|
||||
voiceGains.put(peerId, gain);
|
||||
} else if (enabledChannel == Voice.VoiceChannel.PROXIMITY) {
|
||||
PannerNode panner = audioctx.createPanner();
|
||||
panner.setRolloffFactor(1f);
|
||||
panner.setDistanceModel("linear");
|
||||
panner.setPanningModel("HRTF");
|
||||
panner.setConeInnerAngle(360f);
|
||||
panner.setConeOuterAngle(0f);
|
||||
panner.setConeOuterGain(0f);
|
||||
panner.setOrientation(0f, 1f, 0f);
|
||||
panner.setPosition(0, 0, 0);
|
||||
float vol = getVoiceListenVolume();
|
||||
panner.setMaxDistance(vol * getVoiceProximity() + 0.1f);
|
||||
GainNode gain = audioctx.createGain();
|
||||
gain.getGain().setValue(vol);
|
||||
analyser.connect(gain);
|
||||
gain.connect(panner);
|
||||
panner.connect(audioctx.getDestination());
|
||||
voiceGains.put(peerId, gain);
|
||||
voicePanners.put(peerId, panner);
|
||||
}
|
||||
}
|
||||
});
|
||||
voiceClient.setPeerDisconnectHandler(new EaglercraftVoiceClient.PeerDisconnectHandler() {
|
||||
@Override
|
||||
public void call(String peerId, boolean quiet) {
|
||||
if (voiceAnalysers.containsKey(peerId)) {
|
||||
voiceAnalysers.get(peerId).disconnect();
|
||||
voiceAnalysers.remove(peerId);
|
||||
}
|
||||
if (voiceGains.containsKey(peerId)) {
|
||||
voiceGains.get(peerId).disconnect();
|
||||
voiceGains.remove(peerId);
|
||||
}
|
||||
if (voicePanners.containsKey(peerId)) {
|
||||
voicePanners.get(peerId).disconnect();
|
||||
voicePanners.remove(peerId);
|
||||
}
|
||||
if (!quiet) {
|
||||
try {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
DataOutputStream dat = new DataOutputStream(bos);
|
||||
dat.write(VOICE_SIGNAL_DISCONNECT);
|
||||
dat.writeUTF(peerId);
|
||||
returnSignalHandler.accept(bos.toByteArray());
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
voiceClient.initializeDevices();
|
||||
}
|
||||
sendInitialVoice();
|
||||
}
|
||||
}
|
||||
public static final Voice.VoiceChannel getVoiceChannel() {
|
||||
|
@ -2127,8 +2257,7 @@ public class EaglerAdapterImpl2 {
|
|||
(voiceClient.getReadyState() != EaglercraftVoiceClient.READYSTATE_DEVICE_INITIALIZED ?
|
||||
Voice.VoiceStatus.CONNECTING : Voice.VoiceStatus.CONNECTED);
|
||||
}
|
||||
|
||||
// to ayunami - push to talk in the JS works afaik
|
||||
|
||||
private static boolean talkStatus = false;
|
||||
public static final void activateVoice(boolean talk) {
|
||||
if(talkStatus != talk) {
|
||||
|
@ -2136,8 +2265,7 @@ public class EaglerAdapterImpl2 {
|
|||
}
|
||||
talkStatus = talk;
|
||||
}
|
||||
|
||||
// to ayunami - not currently used in the javascript but is used by GUI and gameSettings
|
||||
|
||||
private static int proximity = 16;
|
||||
public static final void setVoiceProximity(int prox) {
|
||||
proximity = prox;
|
||||
|
@ -2145,17 +2273,22 @@ public class EaglerAdapterImpl2 {
|
|||
public static final int getVoiceProximity() {
|
||||
return proximity;
|
||||
}
|
||||
|
||||
// to ayunami - iterate all AudioNodes from PeerTrackHandler players and adjust their gain here
|
||||
|
||||
private static float volumeListen = 0.5f;
|
||||
public static final void setVoiceListenVolume(float f) {
|
||||
for (GainNode gain : voiceGains.values()) {
|
||||
float val = f;
|
||||
if(val > 0.5) val = 0.5f + (val - 0.5f) * 2.0f;
|
||||
if(val > 1.5) val = 1.5f;
|
||||
if(val < 0.0) val = 0.0f;
|
||||
gain.getGain().setValue(val * 3.0f);
|
||||
}
|
||||
volumeListen = f;
|
||||
}
|
||||
public static final float getVoiceListenVolume() {
|
||||
return volumeListen;
|
||||
}
|
||||
|
||||
// to ayunami - this is already implemented
|
||||
|
||||
private static float volumeSpeak = 0.5f;
|
||||
public static final void setVoiceSpeakVolume(float f) {
|
||||
if(volumeSpeak != f) {
|
||||
|
@ -2166,20 +2299,17 @@ public class EaglerAdapterImpl2 {
|
|||
public static final float getVoiceSpeakVolume() {
|
||||
return volumeSpeak;
|
||||
}
|
||||
|
||||
// to ayunami - this is used to make the ingame GUI display who is speaking
|
||||
// I also already programmed a speaker icon above player name tags of players in "getVoiceSpeaking()"
|
||||
|
||||
|
||||
private static final Set<String> mutedSet = new HashSet();
|
||||
private static final Set<String> emptySet = new HashSet();
|
||||
private static final List<String> emptyLst = new ArrayList();
|
||||
private static final Set<String> speakingSet = new HashSet();
|
||||
public static final Set<String> getVoiceListening() {
|
||||
return emptySet;
|
||||
return voiceGains.keySet();
|
||||
}
|
||||
public static final Set<String> getVoiceSpeaking() {
|
||||
return emptySet;
|
||||
return speakingSet;
|
||||
}
|
||||
public static final void setVoiceMuted(String username, boolean mute) {
|
||||
voiceClient.mutePeer(username, mute);
|
||||
if(mute) {
|
||||
mutedSet.add(username);
|
||||
}else {
|
||||
|
@ -2190,18 +2320,27 @@ public class EaglerAdapterImpl2 {
|
|||
return mutedSet;
|
||||
}
|
||||
public static final List<String> getVoiceRecent() {
|
||||
return emptyLst;
|
||||
return new ArrayList<>(voiceGains.keySet());
|
||||
}
|
||||
|
||||
// to ayunami - use this to clean up that ExpiringSet class you made
|
||||
|
||||
public static final void tickVoice() {
|
||||
|
||||
recentlyNearbyPlayers.checkForExpirations();
|
||||
for (String username : voiceAnalysers.keySet()) {
|
||||
AnalyserNode analyser = voiceAnalysers.get(username);
|
||||
Uint8Array array = Uint8Array.create(analyser.getFrequencyBinCount());
|
||||
analyser.getByteFrequencyData(array);
|
||||
int len = array.getLength();
|
||||
speakingSet.remove(username);
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (array.get(i) >= 0.1f) {
|
||||
speakingSet.add(username);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//TODO: voice end ========================================================
|
||||
|
||||
|
||||
public static final void doJavascriptCoroutines() {
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package net.lax1dude.eaglercraft.adapter.teavm;
|
|||
|
||||
import org.teavm.jso.JSFunctor;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.jso.webaudio.MediaStreamAudioSourceNode;
|
||||
import org.teavm.jso.webaudio.MediaStream;
|
||||
|
||||
public interface EaglercraftVoiceClient extends JSObject {
|
||||
|
||||
|
@ -19,40 +19,37 @@ public interface EaglercraftVoiceClient extends JSObject {
|
|||
|
||||
void initializeDevices();
|
||||
|
||||
// to ayunami - allow the server to tell the client what to put here
|
||||
void setICEServers(String[] urls);
|
||||
|
||||
// to ayunami - this is the equivalent of your "EAG|VoiceIce" callback
|
||||
|
||||
void setICECandidateHandler(ICECandidateHandler callback);
|
||||
|
||||
// to ayunami - this is the equivalent of your "EAG|VoiceDesc" callback
|
||||
void setDescriptionHandler(DescriptionHandler callback);
|
||||
|
||||
// to ayunami - this returns a "MediaStreamAudioSourceNode" for new peers
|
||||
|
||||
void setPeerTrackHandler(PeerTrackHandler callback);
|
||||
|
||||
// to ayunami - this is called when a peer disconnects (so you can remove their MediaStreamAudioSourceNode and stuff)
|
||||
|
||||
void setPeerDisconnectHandler(PeerDisconnectHandler callback);
|
||||
|
||||
void activateVoice(boolean active);
|
||||
|
||||
void setMicVolume(float volume);
|
||||
|
||||
void mutePeer(String peerId, boolean muted);
|
||||
|
||||
int getTaskState();
|
||||
|
||||
int getReadyState();
|
||||
|
||||
int signalConnect(String peerId);
|
||||
int signalConnect(String peerId, boolean offer);
|
||||
|
||||
int signalDescription(String peerId, String description);
|
||||
|
||||
int signalDisconnect(String peerId);
|
||||
int signalDisconnect(String peerId, boolean quiet);
|
||||
|
||||
int signalICECandidate(String peerId, String candidate);
|
||||
|
||||
@JSFunctor
|
||||
public static interface ICECandidateHandler extends JSObject {
|
||||
void call(String peerId, String sdpMLineIndex, String candidate);
|
||||
void call(String peerId, String candidate);
|
||||
}
|
||||
|
||||
@JSFunctor
|
||||
|
@ -62,12 +59,12 @@ public interface EaglercraftVoiceClient extends JSObject {
|
|||
|
||||
@JSFunctor
|
||||
public static interface PeerTrackHandler extends JSObject {
|
||||
void call(String peerId, MediaStreamAudioSourceNode candidate);
|
||||
void call(String peerId, MediaStream audioNode);
|
||||
}
|
||||
|
||||
@JSFunctor
|
||||
public static interface PeerDisconnectHandler extends JSObject {
|
||||
void call(String peerId);
|
||||
void call(String peerId, boolean quiet);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user