Discord/TS-Teilnehmer als Mitglieder in der Voicechat-Gruppenanzeige (mit Sprech-Symbol), Actionbar entfernt

This commit is contained in:
MineTec committed 2026-10-19 17:34:33 +02:00
1 parent d5c181a224
commit e2546ad982
17 files changed
+250 -150

No files matched your search

@@ -37,8 +37,6 @@ public final class Bridge {
private final ScheduledExecutorService ticker = Executors.newSingleThreadScheduledExecutor(
Thread.ofPlatform().name("bridge-tick").factory());
private final Announcer announcer;
/** Wie lange nach dem letzten Paket jemand noch als "spricht" gilt. */
private static final long SPEAKING_MILLIS = 400;
private volatile List<Message.RoomInfo> roomInfos = List.of();
@@ -63,14 +61,6 @@ public final class Bridge {
this.teamspeak = config.teamspeak().enabled()
? new TeamspeakSide(config.teamspeak(), config.dataDir(), List.copyOf(rooms.values()), updater)
: null;
for (Room room : rooms.values()) {
DiscordRoomBot bot = discordBots.get(room.key());
room.setNameResolver((side, id) -> switch (side) {
case TEAMSPEAK -> teamspeak != null ? teamspeak.nickname(Integer.parseInt(id)) : id;
case DISCORD -> bot != null ? bot.name(Long.parseLong(id)) : id;
case MINECRAFT -> id;
});
}
this.announcer = new Announcer(plugin, teamspeak, ticker);
roomInfos = buildInfos();
}
@@ -82,7 +72,7 @@ public final class Bridge {
if (teamspeak != null) teamspeak.start();
discordBots.values().forEach(DiscordRoomBot::start);
ticker.scheduleAtFixedRate(this::safeTick, 1, 1, TimeUnit.SECONDS);
ticker.scheduleAtFixedRate(this::sendSpeakers, 500, 500, TimeUnit.MILLISECONDS);
ticker.scheduleAtFixedRate(this::sendParticipants, 2, 2, TimeUnit.SECONDS);
}
private void safeTick() {
@@ -154,16 +144,27 @@ public final class Bridge {
return roster;
}
/** Actionbar im Spiel: wer aus Discord/TeamSpeak gerade spricht. */
private void sendSpeakers() {
/**
* Wer aus Discord/TeamSpeak im Raum ist, ans Plugin: Es trägt sie in die Gruppenanzeige des Voicechats ein.
* Alle 2 s komplett, damit ein verlorenes UDP-Paket nichts ausmacht.
*/
private void sendParticipants() {
try {
for (Room room : rooms.values()) {
if (!room.active()) continue;
List<String> speakers = room.externalSpeakers(SPEAKING_MILLIS);
if (!speakers.isEmpty()) plugin.sendSpeakers(room.key(), speakers);
DiscordRoomBot bot = discordBots.get(room.key());
List<Message.Member> members = new ArrayList<>();
if (teamspeak != null && teamspeak.ready()) {
teamspeak.participants(room).forEach((id, name) ->
members.add(new Message.Member(room.sourceUuid("ts:" + id), name + " [TeamSpeak]")));
}
if (bot != null && bot.ready()) {
bot.participants().forEach((id, name) ->
members.add(new Message.Member(room.sourceUuid("dc:" + id), name + " [Discord]")));
}
plugin.sendParticipants(room.key(), members);
}
} catch (RuntimeException e) {
LOG.warn("Sprecher-Anzeige fehlgeschlagen", e);
LOG.warn("Teilnehmer-Abgleich fehlgeschlagen", e);
}
}
@@ -61,9 +61,9 @@ final class PluginLink {
if (target != null && alive()) send(new Message.Notice(room, text), target);
}
void sendSpeakers(String room, List<String> names) {
void sendParticipants(String room, List<Message.Member> members) {
SocketAddress target = plugin;
if (target != null && alive()) send(new Message.Speakers(room, names), target);
if (target != null && alive()) send(new Message.Participants(room, members), target);
}
void sendAudio(String room, UUID source, byte[] opus) {
@@ -121,7 +121,7 @@ final class PluginLink {
}
case Message.Rooms ignored -> {}
case Message.Notice ignored -> {}
case Message.Speakers ignored -> {}
case Message.Participants ignored -> {}
}
}
@@ -14,7 +14,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@@ -36,9 +35,6 @@ public final class Room {
private final Mixer toDiscord = new Mixer();
private final Map<String, Source> sources = new ConcurrentHashMap<>();
/** Anzeigename eines Sprechers (Seite, ID ohne Präfix), für die Actionbar. */
private volatile BiFunction<Side, String, String> names = (side, id) -> id;
/** Wohin Frames für Minecraft gehen (Plugin-Verbindung). */
private volatile MinecraftSink minecraft = (room, source, opus) -> {};
@@ -59,13 +55,12 @@ public final class Room {
final FrameSplitter splitter = new FrameSplitter();
OpusEncoder minecraftEncoder;
volatile long lastSeen;
volatile String name;
int lastSequence = -1;
Source(Side side, String key) {
this.side = side;
this.key = key;
this.uuid = UUID.nameUUIDFromBytes(("voicebridge:" + config.key() + ":" + key).getBytes(StandardCharsets.UTF_8));
this.uuid = sourceUuid(key);
}
}
@@ -83,15 +78,13 @@ public final class Room {
public Mixer toDiscord() { return toDiscord; }
public void setMinecraftSink(MinecraftSink sink) { this.minecraft = sink; }
public void setNameResolver(BiFunction<Side, String, String> names) { this.names = names; }
/** Wer aus Discord/TeamSpeak in den letzten {@code millis} Audio geschickt hat, als "Name|Kürzel". */
public List<String> externalSpeakers(long millis) {
long since = System.currentTimeMillis() - millis;
return sources.values().stream()
.filter(s -> s.side != Side.MINECRAFT && s.lastSeen >= since)
.map(s -> s.name + "|" + (s.side == Side.DISCORD ? "DC" : "TS"))
.sorted().toList();
/**
* Feste UUID eines Sprechers, z. B. {@code ts:5} oder {@code dc:1234}. Unter ihr läuft sein Audio im Spiel,
* und unter ihr erscheint er auch in der Gruppenanzeige des Voicechats.
*/
public UUID sourceUuid(String key) {
return UUID.nameUUIDFromBytes(("voicebridge:" + config.key() + ":" + key).getBytes(StandardCharsets.UTF_8));
}
public static String label(Side side) {
@@ -132,7 +125,6 @@ public final class Room {
if (!active || opus.length == 0) return;
Source source = sources.computeIfAbsent(key, k -> new Source(side, k));
source.lastSeen = System.currentTimeMillis();
if (source.name == null) source.name = names.apply(side, key.substring(3));
List<short[]> frames = new ArrayList<>(2);
try {
@@ -152,13 +152,6 @@ public final class DiscordRoomBot implements AudioSendHandler, AudioReceiveHandl
return result;
}
/** Anzeigename eines Users, für die Sprecher-Anzeige. */
public String name(long userId) {
Guild guild = this.guild;
Member member = guild == null ? null : guild.getMemberById(userId);
return member == null ? "?" : member.getEffectiveName();
}
/** Ob der Bot eingeloggt ist und seinen Channel kennt. */
public boolean ready() {
return guild != null && channelId != 0;
@@ -89,12 +89,6 @@ public final class TeamspeakSide {
return result;
}
/** Nickname eines Clients, für die Sprecher-Anzeige. */
public String nickname(int clientId) {
TsClient client = clients.get(clientId);
return client == null ? "?" : client.name();
}
/** Ob der Raum-Client gerade im Channel ist. */
public boolean roomConnected(Room room) {
TeamspeakRoomClient client = roomClients.get(room.key());