From 566756c03752ae99a1cf4e388504c5152498d6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 10 Nov 2026 21:05:28 -0800 Subject: [PATCH 1/2] fix(rtc): clear_queue stops a capture in flight Bumps rust-sdks to livekit/rust-sdks#1665. --- .changeset/audio-source-clear-in-flight.md | 5 ++ livekit-rtc/rust-sdks | 2 +- livekit-rtc/tests/test_audio_source.py | 60 ++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 .changeset/audio-source-clear-in-flight.md create mode 100644 livekit-rtc/tests/test_audio_source.py diff --git a/.changeset/audio-source-clear-in-flight.md b/.changeset/audio-source-clear-in-flight.md new file mode 100644 index 00000000..7fab6402 --- /dev/null +++ b/.changeset/audio-source-clear-in-flight.md @@ -0,0 +1,5 @@ +--- +"livekit-rtc": patch +--- + +`AudioSource.clear_queue` also stops a `capture_frame` in flight: a frame longer than the queue no longer keeps playing after a clear. diff --git a/livekit-rtc/rust-sdks b/livekit-rtc/rust-sdks index 7f3ddc08..8bb99151 160000 --- a/livekit-rtc/rust-sdks +++ b/livekit-rtc/rust-sdks @@ -1 +1 @@ -Subproject commit 7f3ddc08bda60e318d4acdd1625b0723ac6c009f +Subproject commit 8bb9915115c9708af449d41987dae7412c820a69 diff --git a/livekit-rtc/tests/test_audio_source.py b/livekit-rtc/tests/test_audio_source.py new file mode 100644 index 00000000..9580768f --- /dev/null +++ b/livekit-rtc/tests/test_audio_source.py @@ -0,0 +1,60 @@ +# Copyright 2026 LiveKit, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Regression tests for AudioSource.clear_queue.""" + +from __future__ import annotations + +import asyncio +import time + +from livekit import rtc + +SAMPLE_RATE = 24000 + + +async def test_clear_queue_cuts_a_capture_longer_than_the_queue() -> None: + """Audio captured before clear_queue stops right after it. + + A frame longer than the queue is fed to it chunk by chunk; clear_queue must also drop the + chunks not queued yet, or they keep playing after an interruption. + """ + source = rtc.AudioSource(SAMPLE_RATE, 1, queue_size_ms=50) + track = rtc.LocalAudioTrack.create_audio_track("clear", source) + stream = rtc.AudioStream(track, sample_rate=SAMPLE_RATE, num_channels=1) + heard: list[float] = [] + + async def listen() -> None: + async for event in stream: + if any(event.frame.data): + heard.append(time.monotonic()) + + listener = asyncio.create_task(listen()) + samples = SAMPLE_RATE // 2 # 500 ms, ten times the queue + frame = rtc.AudioFrame(b"\xe8\x03" * samples, SAMPLE_RATE, 1, samples) + try: + capture = asyncio.create_task(source.capture_frame(frame)) + await asyncio.sleep(0.025) + cleared = time.monotonic() + source.clear_queue() + await asyncio.wait_for(capture, 0.1) + await asyncio.sleep(0.6) + finally: + listener.cancel() + await asyncio.gather(listener, return_exceptions=True) + await stream.aclose() + await source.aclose() + + after_clear = max((t - cleared for t in heard), default=0.0) + assert after_clear < 0.02, f"audio played {after_clear * 1000:.0f} ms after clear_queue" From 1e181e3f6352ee279799c4b6a84a1463697cca97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Monnom?= Date: Tue, 10 Nov 2026 21:20:15 -0800 Subject: [PATCH 2/2] test(rtc): a capture right after clear_queue is taken; bump rust-sdks --- .changeset/audio-source-clear-in-flight.md | 2 +- livekit-rtc/rust-sdks | 2 +- livekit-rtc/tests/test_audio_source.py | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.changeset/audio-source-clear-in-flight.md b/.changeset/audio-source-clear-in-flight.md index 7fab6402..9daccbfe 100644 --- a/.changeset/audio-source-clear-in-flight.md +++ b/.changeset/audio-source-clear-in-flight.md @@ -2,4 +2,4 @@ "livekit-rtc": patch --- -`AudioSource.clear_queue` also stops a `capture_frame` in flight: a frame longer than the queue no longer keeps playing after a clear. +`AudioSource.clear_queue` stops a `capture_frame` in flight: a frame longer than the queue no longer keeps playing after a clear, and a capture right after a clear no longer fails. diff --git a/livekit-rtc/rust-sdks b/livekit-rtc/rust-sdks index 8bb99151..54508c56 160000 --- a/livekit-rtc/rust-sdks +++ b/livekit-rtc/rust-sdks @@ -1 +1 @@ -Subproject commit 8bb9915115c9708af449d41987dae7412c820a69 +Subproject commit 54508c56f5875c1d84106bd843c481647e637d2e diff --git a/livekit-rtc/tests/test_audio_source.py b/livekit-rtc/tests/test_audio_source.py index 9580768f..09a50184 100644 --- a/livekit-rtc/tests/test_audio_source.py +++ b/livekit-rtc/tests/test_audio_source.py @@ -48,6 +48,9 @@ async def listen() -> None: await asyncio.sleep(0.025) cleared = time.monotonic() source.clear_queue() + # the next reply starts at once, while the cut capture winds down + silence = SAMPLE_RATE // 100 + await source.capture_frame(rtc.AudioFrame(b"\x00\x00" * silence, SAMPLE_RATE, 1, silence)) await asyncio.wait_for(capture, 0.1) await asyncio.sleep(0.6) finally: