SpeakingRateStream blocks event loop 100–200ms on TTS audio bursts (TranscriptSynchronizer)
Maintainers usually reply within 1 day
@ninglinLiu is already working on this.
Since Oct 18, 2026.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 20/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Stale
- Tech stack
- python
- Domain
- audio-video-rtc, backend, performance
Research direction
Start with voice/transcription/_speaking_rate.py, especially SpeakingRateStream._main_task, _stft, and _spectral_flux; trace how buffered audio windows are processed and inspect the event loop blocking reports in the issue. Compare the proposed yielding, thread, and vectorization approaches, then check interrupted-turn transcript behavior with sync_transcription enabled. Done means bursts no longer block the loop while synchronized transcripts remain accurate; two open pull requests are already linked.
Written by the indexing model from the issue text.
Description
Environment
- livekit-agents 1.8.3 (
voice/transcription/_speaking_rate.pyunchanged through 1.8.8) - Python 3.14, Linux container (LiveKit Cloud agent)
sync_transcriptionleft at default (enabled)
What happens
The event loop monitor reports repeated stalls while the agent is speaking, several per session:
event loop blocked for 184ms at ".../livekit/agents/voice/transcription/_speaking_rate.py", line 153, in _main_task
event loop blocked for 134ms at ".../_speaking_rate.py", line 220, in _stft
event loop blocked for 105ms at ".../_speaking_rate.py", line 242, in _spectral_flux
cpu_time roughly equals duration in every report, so this is pure CPU work on the loop thread, not I/O.
Cause
In SpeakingRateStream._main_task, the inner while True loop runs _compute_speaking_rate (STFT + spectral flux over a 1s window) once for every step_size (0.1s) of buffered audio, without yielding to the loop. TTS delivers audio faster than real time, so a single burst queues many windows, and they are all computed back-to-back on the event loop.
Current workaround and its cost
The only option on our side is RoomOptions(text_output=TextOutputOptions(sync_transcription=False)). That removes synchronized_transcript, so an interrupted agent turn is stored in the chat context with its full generated text, including words that were never played (agent_activity.py, around L3319). We rely on accurate transcripts, so this isn't a viable trade.
Suggested fixes
Any one of these would help:
- run the window computation in a thread (
asyncio.to_threador an executor), batching pending windows await asyncio.sleep(0)between windows so other tasks get scheduled- vectorise the STFT across all pending windows in a single numpy call
- Dominant language
- Python
- Stars
- 14.6k
- Forks
- 3.9k
- Avg merge
- 3d 14h
- Merged PRs (30d)
- 169
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from livekit/agents
-
`_ParticipantAudioInputStream` 补静音帧时`data` 长度未乘声道数,多声道配置下抛`ValueError`Possibly taken @Hugo-DDT claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
Google TTS: default ssml_gender NEUTRAL is rejected for Neural2 voicesPossibly taken @sarankumar1325 claimed this 1 day ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Gemini Live: agent stays "speaking" without audio outputPossibly taken @Luke-salinero claimed this 4 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Assistant markup is stripped from chat history when expressive mode is offPossibly taken @ShivamSharma43 claimed this 12 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
docs: openai.LLM vs openai.responses.LLM for HTTP OpenAI-compat hostsMay be free again @AbarnaaSree claimed this 52 days ago, and no pull request is open. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
livekit/agents#7022 · 1 comment ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
changelog investigate
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
ramnes/notion-sdk-py#423 ·
-
type-bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day