Give back a speaker's line after 30 s of silence
Every client that ever spoke kept an open playback line and a thread until it left the server. A speaker silent for 30 s now has both released, and its next talk burst opens them again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,8 @@ final class PerSpeakerPlayout implements Playout {
|
|||||||
|
|
||||||
private static final long FRAME_NANOS =
|
private static final long FRAME_NANOS =
|
||||||
TimeUnit.SECONDS.toNanos(VoiceFormat.FRAME_SIZE) / VoiceFormat.SAMPLE_RATE;
|
TimeUnit.SECONDS.toNanos(VoiceFormat.FRAME_SIZE) / VoiceFormat.SAMPLE_RATE;
|
||||||
|
/** Silence after which a speaker's line and thread are given back; the next burst reopens them. */
|
||||||
|
private static final long IDLE_RELEASE_NANOS = TimeUnit.SECONDS.toNanos(30);
|
||||||
|
|
||||||
private final class Speaker {
|
private final class Speaker {
|
||||||
final VoiceStream stream;
|
final VoiceStream stream;
|
||||||
@@ -32,6 +34,8 @@ final class PerSpeakerPlayout implements Playout {
|
|||||||
final byte[] out;
|
final byte[] out;
|
||||||
/** Guarded by this speaker. */
|
/** Guarded by this speaker. */
|
||||||
ScheduledFuture<?> ticks;
|
ScheduledFuture<?> ticks;
|
||||||
|
/** Guarded by this speaker; a closed speaker is never ticked again. */
|
||||||
|
boolean closed;
|
||||||
|
|
||||||
Speaker(VoiceStream stream) throws Exception {
|
Speaker(VoiceStream stream) throws Exception {
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
@@ -49,6 +53,7 @@ final class PerSpeakerPlayout implements Playout {
|
|||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
closed = true;
|
||||||
if (ticks != null) ticks.cancel(false);
|
if (ticks != null) ticks.cancel(false);
|
||||||
}
|
}
|
||||||
worker.shutdownNow();
|
worker.shutdownNow();
|
||||||
@@ -68,10 +73,12 @@ final class PerSpeakerPlayout implements Playout {
|
|||||||
this.outputDevice = outputDevice;
|
this.outputDevice = outputDevice;
|
||||||
this.gain = gain;
|
this.gain = gain;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
|
clock.scheduleWithFixedDelay(this::releaseIdle, 1, 1, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void started(VoiceStream stream) {
|
public void started(VoiceStream stream) {
|
||||||
|
while (true) {
|
||||||
Speaker speaker = speakers.get(stream);
|
Speaker speaker = speakers.get(stream);
|
||||||
if (speaker == null) {
|
if (speaker == null) {
|
||||||
try {
|
try {
|
||||||
@@ -87,11 +94,31 @@ final class PerSpeakerPlayout implements Playout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
synchronized (speaker) {
|
synchronized (speaker) {
|
||||||
|
if (speaker.closed) {
|
||||||
|
// Released a moment ago; this burst gets a fresh line.
|
||||||
|
speakers.remove(stream, speaker);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (speaker.ticks == null) {
|
if (speaker.ticks == null) {
|
||||||
Speaker s = speaker;
|
Speaker s = speaker;
|
||||||
speaker.ticks = clock.scheduleAtFixedRate(
|
speaker.ticks = clock.scheduleAtFixedRate(
|
||||||
() -> queueTick(s), FRAME_NANOS, FRAME_NANOS, TimeUnit.NANOSECONDS);
|
() -> queueTick(s), FRAME_NANOS, FRAME_NANOS, TimeUnit.NANOSECONDS);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gives back the line and thread of every speaker that has been silent for a while. */
|
||||||
|
private void releaseIdle() {
|
||||||
|
long now = System.nanoTime();
|
||||||
|
for (Speaker speaker : speakers.values()) {
|
||||||
|
synchronized (speaker) {
|
||||||
|
if (speaker.ticks != null || now - speaker.stream.lastPacketNanos() < IDLE_RELEASE_NANOS) continue;
|
||||||
|
speaker.closed = true;
|
||||||
|
}
|
||||||
|
speakers.remove(speaker.stream, speaker);
|
||||||
|
speaker.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user