From 12ca585d905a2334e3b0337600879f4284cfe985 Mon Sep 17 00:00:00 2001 From: ericek111 Date: Fri, 25 Sep 2026 10:48:22 +0000 Subject: [PATCH] Open a new line when a speaker's playback line dies Both desktop backends dropped writes to a line whose device had gone away, and the per-speaker playout ignored any failure, so that speaker stayed silent for the rest of the session. A write to a dead line now throws; the playout drops that line and the speaker's next packet opens a fresh one. Sound effects and the microphone test's loopback reopen their line the same way. Co-Authored-By: Claude Opus 5.5 --- .../src/main/java/com/ts3client/audio/AudioPlayback.java | 7 ++++++- .../main/java/com/ts3client/audio/PerSpeakerPlayout.java | 7 ++++++- .../com/ts3client/audio/desktop/JavaSoundPlayback.java | 8 +++++--- .../audio/desktop/pipewire/PipeWirePlayback.java | 4 +++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/ts3-client/core/src/main/java/com/ts3client/audio/AudioPlayback.java b/ts3-client/core/src/main/java/com/ts3client/audio/AudioPlayback.java index 3ac3346..e1e5b95 100644 --- a/ts3-client/core/src/main/java/com/ts3client/audio/AudioPlayback.java +++ b/ts3-client/core/src/main/java/com/ts3client/audio/AudioPlayback.java @@ -15,7 +15,12 @@ public interface AudioPlayback extends AutoCloseable { /** Begins playing whatever is written from now on. */ void start(); - /** Blocks until the audio has been queued for playback. */ + /** + * Blocks until the audio has been queued for playback. + * + * @throws IllegalStateException once the line is gone for good (its device went away, + * say), so the caller can open another + */ void write(byte[] buffer, int offset, int length); /** Blocks until everything already written has been played out. */ diff --git a/ts3-client/core/src/main/java/com/ts3client/audio/PerSpeakerPlayout.java b/ts3-client/core/src/main/java/com/ts3client/audio/PerSpeakerPlayout.java index 82d9b40..9d541c2 100644 --- a/ts3-client/core/src/main/java/com/ts3client/audio/PerSpeakerPlayout.java +++ b/ts3-client/core/src/main/java/com/ts3client/audio/PerSpeakerPlayout.java @@ -158,7 +158,12 @@ final class PerSpeakerPlayout implements Playout { Pcm16.encode(speaker.pcm, samples, speaker.out); try { speaker.line.write(speaker.out, 0, samples * 2); - } catch (RuntimeException ignored) { + } catch (RuntimeException e) { + // The line died (a device went away, say). Drop it rather than keep writing into it: + // the speaker's next packet starts a burst on a fresh one. + speakers.remove(speaker.stream, speaker); + speaker.close(); + speaker.stream.stop(); } } diff --git a/ts3-client/desktop/src/main/java/com/ts3client/audio/desktop/JavaSoundPlayback.java b/ts3-client/desktop/src/main/java/com/ts3client/audio/desktop/JavaSoundPlayback.java index 49eddf1..aa25649 100644 --- a/ts3-client/desktop/src/main/java/com/ts3client/audio/desktop/JavaSoundPlayback.java +++ b/ts3-client/desktop/src/main/java/com/ts3client/audio/desktop/JavaSoundPlayback.java @@ -25,11 +25,13 @@ final class JavaSoundPlayback implements AudioPlayback { @Override public void write(byte[] buffer, int offset, int length) { + int written; try { - line.write(buffer, offset, length); - } catch (Exception ignored) { - // The line went away (device unplugged); the caller sees silence. + written = line.write(buffer, offset, length); + } catch (RuntimeException e) { + throw new IllegalStateException("The playback line failed", e); } + if (written < length && !line.isOpen()) throw new IllegalStateException("The playback line closed"); } @Override diff --git a/ts3-client/desktop/src/main/java/com/ts3client/audio/desktop/pipewire/PipeWirePlayback.java b/ts3-client/desktop/src/main/java/com/ts3client/audio/desktop/pipewire/PipeWirePlayback.java index eaaee05..98c858f 100644 --- a/ts3-client/desktop/src/main/java/com/ts3client/audio/desktop/pipewire/PipeWirePlayback.java +++ b/ts3-client/desktop/src/main/java/com/ts3client/audio/desktop/pipewire/PipeWirePlayback.java @@ -37,7 +37,9 @@ final class PipeWirePlayback extends PipeWireStream implements AudioPlayback { @Override public void write(byte[] buffer, int offset, int length) { - ring.write(buffer, offset, length); + if (ring.write(buffer, offset, length) < length && ring.isClosed()) { + throw new IllegalStateException("The PipeWire stream ended"); + } } @Override