diff --git a/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java b/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java index eec84bc..26875ec 100644 --- a/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java +++ b/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java @@ -90,19 +90,23 @@ public final class TeamspeakConnection implements TS3Listener { final BanAdmin bans = new BanAdmin(this); private final AvatarAdmin avatarAdmin = new AvatarAdmin(this); + /* + * Set up by the connect thread and torn down by whichever thread notices the end; read from + * the UI, event and action threads, so each reader copies the field before using it. + */ /** Package-private: read directly by {@link ConnectionStatsCollector}. */ - LocalTeamspeakClientSocket client; - private VoiceInput microphone; - private VoiceOutput playback; - private LocalIdentity identity; - private FileTransferManager fileTransfers; - private ChatLogStore chatLogs; + volatile LocalTeamspeakClientSocket client; + private volatile VoiceInput microphone; + private volatile VoiceOutput playback; + private volatile LocalIdentity identity; + private volatile FileTransferManager fileTransfers; + private volatile ChatLogStore chatLogs; /** * Runs ts3j's event callbacks. Supplying it ourselves (rather than leaving ts3j to make * its own) is what lets {@link #awaitEventsProcessed()} tell when everything the server * has already sent has been handled. Closed together with the socket. */ - private ExecutorService eventExecutor; + private volatile ExecutorService eventExecutor; /** Runs this connection's requests to the server; created on connect, shut down with the socket. */ private volatile ExecutorService actions; @@ -248,6 +252,9 @@ public final class TeamspeakConnection implements TS3Listener { String channel, String channelPassword) { Object attempt = new Object(); connectAttempt = attempt; + // A socket or audio line an earlier connect left behind must not outlive it. + connected = false; + safeCleanup(); try { identity = withIdentity; serverHost = address; @@ -570,7 +577,8 @@ public final class TeamspeakConnection implements TS3Listener { // ---- self actions ---- public void setMicMuted(boolean muted) { - if (microphone != null) microphone.setMuted(muted); + VoiceInput mic = microphone; + if (mic != null) mic.setMuted(muted); sound(muted ? SoundEvent.SOUND_CAPTURE_MUTED : SoundEvent.SOUND_CAPTURE_UNMUTED); pushSelfFlags(); } @@ -580,22 +588,26 @@ public final class TeamspeakConnection implements TS3Listener { * status the server (and other clients) see, and without a mute/unmute sound. */ public void setMicLocalMuted(boolean muted) { - if (microphone != null) microphone.setLocalMuted(muted); + VoiceInput mic = microphone; + if (mic != null) mic.setLocalMuted(muted); } public void setDeafened(boolean deaf) { // Announce muting while we can still be heard, and unmuting once we can again. if (deaf) sound(SoundEvent.SOUND_PLAYBACK_MUTED); deafened = deaf; - if (playback != null) playback.setDeafened(deaf); + VoiceOutput out = playback; + if (out != null) out.setDeafened(deaf); if (!deaf) sound(SoundEvent.SOUND_PLAYBACK_UNMUTED); pushSelfFlags(); } private void pushSelfFlags() { - if (microphone == null || playback == null) return; - boolean micMuted = microphone.isMuted(); - boolean deaf = playback.isDeafened(); + VoiceInput mic = microphone; + VoiceOutput out = playback; + if (mic == null || out == null) return; + boolean micMuted = mic.isMuted(); + boolean deaf = out.isDeafened(); updateSelf(self -> { self.inputMuted = micMuted; self.outputMuted = deaf; @@ -815,7 +827,8 @@ public final class TeamspeakConnection implements TS3Listener { public double getPingMillis() { try { - return client != null ? client.getPing().getKey() * 1000.0 : -1; + LocalTeamspeakClientSocket sock = client; + return sock != null ? sock.getPing().getKey() * 1000.0 : -1; } catch (Exception e) { return -1; } @@ -972,9 +985,10 @@ public final class TeamspeakConnection implements TS3Listener { private Contact applyContactPlayback(ClientEntry client) { if (client == null || client.self) return null; Contact contact = contacts.find(client.uniqueId); - if (contact != null && playback != null) { - if (contact.automute) playback.setClientMuted(client.id, true); - playback.setClientVolume(client.id, contact.volumeGain()); + VoiceOutput out = playback; + if (contact != null && out != null) { + if (contact.automute) out.setClientMuted(client.id, true); + out.setClientVolume(client.id, contact.volumeGain()); } return contact; } @@ -991,7 +1005,8 @@ public final class TeamspeakConnection implements TS3Listener { /** Remembers a per-client volume, in the contact entry when the client has one. */ public void setClientVolumeModifier(ClientEntry client, double decibels) { - if (playback != null) playback.setClientVolume(client.id, Math.pow(10.0, decibels / 20.0)); + VoiceOutput out = playback; + if (out != null) out.setClientVolume(client.id, Math.pow(10.0, decibels / 20.0)); Contact contact = contacts.find(client.uniqueId); if (contact == null) return; contact.hasVolumeModifier = decibels != 0; @@ -1000,8 +1015,9 @@ public final class TeamspeakConnection implements TS3Listener { } public double getClientVolumeModifier(ClientEntry client) { - if (playback == null) return 0; - double gain = playback.getClientVolume(client.id); + VoiceOutput out = playback; + if (out == null) return 0; + double gain = out.getClientVolume(client.id); return gain <= 0 ? -60 : 20.0 * Math.log10(gain); }