Conversation
| if data.get("flush_completed"): | ||
| output_emitter.end_segment() | ||
| # Start a new segment for the next batch | ||
| segment_id = utils.shortuuid() | ||
| output_emitter.start_segment( | ||
| segment_id=segment_id | ||
| ) |
There was a problem hiding this comment.
π΄ TTS SynthesizeStream calls end_segment() twice, and lacks start_segment for multi-flush
In SynthesizeStream._run, a single start_segment is called at line 279. The recv_task calls output_emitter.end_segment() upon each flush_completed (line 327). After asyncio.gather completes, line 345 unconditionally calls output_emitter.end_segment() again. This causes a double end_segment even in the single-flush case. Furthermore, for multi-flush scenarios (user pushes textβflushβtextβflush), the first flush_completed ends the only segment, but no new start_segment is ever called β subsequent audio chunks arrive with no active segment, and subsequent flush_completed events call end_segment on an already-ended segment.
Was this helpful? React with π or π to provide feedback.
| if data.get("flush_completed"): | ||
| output_emitter.end_segment() | ||
|
|
There was a problem hiding this comment.
π΄ TTS SynthesizeStream missing start_segment for segments after the first flush
start_segment(segment_id=segment_id) is called only once at line 279, before the send/recv tasks start. When recv_task receives a flush_completed at line 326-327, it calls end_segment() β closing the segment. If the user pushes more text followed by another flush, the subsequent audio chunks arrive without a corresponding start_segment() call, meaning audio is pushed outside of an active segment. This breaks the expected segment lifecycle contract defined by the AudioEmitter in livekit-agents/livekit/agents/tts/tts.py.
Was this helpful? React with π or π to provide feedback.
| except websockets.exceptions.ConnectionClosed as e: | ||
| logger.info("60db TTS ChunkedStream: WebSocket closed: %s", e) |
There was a problem hiding this comment.
π΄ TTS ChunkedStream silently swallows WebSocket ConnectionClosed without re-raising
At tts.py:186-188, websockets.exceptions.ConnectionClosed is caught and only logged β the method returns normally. The base class ChunkedStream._main_task (livekit-agents/livekit/agents/tts/tts.py:286-326) then checks whether audio was actually pushed. If the connection closed prematurely (e.g., before flush_completed), output_emitter.flush() at line 172 was never called, so audio may be incomplete or missing. The root cause (connection closure) is silently discarded, and the user gets a confusing downstream error like "no audio frames were pushed" instead of a clear connection error. The same pattern exists in SynthesizeStream at tts.py:307-308.
Was this helpful? React with π or π to provide feedback.
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| import audioop |
There was a problem hiding this comment.
π΄ STT plugin uses audioop module removed in Python 3.13
The STT module (stt.py:4) imports audioop, which was deprecated in Python 3.11 (PEP 594) and removed in Python 3.13 (released Oct 2024). Since pyproject.toml specifies requires-python = ">=3.10.0" with no upper bound, users on Python 3.13+ will get an ImportError at import time. The LiveKit agents framework provides its own audio utilities in livekit.agents.utils (including AudioBuffer and codec helpers) that could be used instead.
Prompt for agents
The stt.py module imports audioop (line 4) and uses it for audio conversion in _convert_audio (lines 291, 295, 304). The audioop module was removed in Python 3.13. Replace audioop usage with a compatible alternative. Options include: (1) use the livekit.agents.utils audio utilities or livekit rtc.AudioResampler already used elsewhere in the codebase, (2) add a dependency on the audioop-lts backport package, or (3) implement the conversions using struct/array directly. The same issue exists in tests/test_services.py line 12 which also imports audioop.
Was this helpful? React with π or π to provide feedback.
| build-backend = "hatchling.build" | ||
|
|
||
| [project] | ||
| name = "livekit-plugins-60db" |
There was a problem hiding this comment.
π‘ Plugin directory uses singular plugin instead of plugins convention
The plugin directory is named livekit-plugin-60db (singular) while all 50+ other plugins in the repository follow the pattern livekit-plugins-<provider> (plural), as documented in AGENTS.md: "Each plugin is a separate package following the pattern livekit-plugins-<provider>." The pyproject.toml correctly uses name = "livekit-plugins-60db" (plural), creating a mismatch between the directory name and the established convention.
Was this helpful? React with π or π to provide feedback.
| except websockets.exceptions.ConnectionClosed as e: | ||
| logger.info("60db TTS SynthesizeStream: WebSocket closed: %s", e) |
There was a problem hiding this comment.
π΄ SynthesizeStream silently swallows ConnectionClosed exception
Same issue as in ChunkedStream β at line 351-352, websockets.exceptions.ConnectionClosed is caught and logged but not re-raised as an APIConnectionError. The _run method returns normally, so the base class _main_task in livekit-agents/livekit/agents/tts/tts.py:479 treats it as a successful completion, preventing retries and error propagation.
Was this helpful? React with π or π to provide feedback.
| except websockets.exceptions.ConnectionClosed as e: | ||
| logger.info("60db STT: WebSocket closed: %s", e) |
There was a problem hiding this comment.
π΄ STT silently swallows unexpected WebSocket disconnections, preventing error propagation and retry
In SpeechStream._run(), websockets.exceptions.ConnectionClosed is caught at line 216 and only logged at INFO level, then _run() returns normally. The base class RecognizeStream._main_task() (livekit-agents/livekit/agents/stt/stt.py:345-383) treats a normal return from _run() as success. This means if the WebSocket connection drops unexpectedly mid-stream (server crash, network issue, etc.), the STT stream silently ends with no transcriptions and no error β the retry mechanism in the base class is never triggered, and the caller has no indication of failure.
| except websockets.exceptions.ConnectionClosed as e: | |
| logger.info("60db STT: WebSocket closed: %s", e) | |
| except websockets.exceptions.ConnectionClosed as e: | |
| raise APIConnectionError(f"60db STT: WebSocket closed unexpectedly: {e}") from e |
Was this helpful? React with π or π to provide feedback.
60db Integration with livekit which contains the STT, TTS and LLM all from single platform.