Skip to content

add preconnect audio buffer API to LocalAudioTrack - #648

Open
theomonnom wants to merge 8 commits into
mainfrom
theo/preconnect-buffer-api
Open

add preconnect audio buffer API to LocalAudioTrack#648
theomonnom wants to merge 8 commits into
mainfrom
theo/preconnect-buffer-api

Conversation

@theomonnom

Copy link
Copy Markdown
Member

No description provided.

Adds start_preconnect_buffer/stop_preconnect_buffer/send_preconnect_buffer
to LocalAudioTrack, backed by a zero-allocation circular bytearray ring
buffer. Sends buffered audio via byte stream data channels using the
existing lk.agent.pre-connect-audio-buffer protocol.
devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 8 additional findings in Devin Review.

Open in Devin Review
Comment on lines +121 to +125
if self._preconnect_buffer is None:
raise RuntimeError("preconnect buffer is not active")

async with self._send_lock:
data = self._preconnect_buffer.capture()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 TOCTOU race: _preconnect_buffer checked before lock but accessed inside lock

In send_preconnect_buffer, self._preconnect_buffer is checked for None at line 121 before acquiring self._send_lock at line 124. If the lock is contended (another send_preconnect_buffer call is in-flight), the async with self._send_lock: line yields control. During this yield, stop_preconnect_buffer() (which is synchronous) can be called from another callback—setting self._preconnect_buffer = None at livekit-rtc/livekit/rtc/track.py:116. When the lock is finally acquired, self._preconnect_buffer.capture() at line 125 will raise AttributeError: 'NoneType' object has no attribute 'capture'. The fix is to move the None-check inside the lock.

Suggested change
if self._preconnect_buffer is None:
raise RuntimeError("preconnect buffer is not active")
async with self._send_lock:
data = self._preconnect_buffer.capture()
async with self._send_lock:
if self._preconnect_buffer is None:
raise RuntimeError("preconnect buffer is not active")
data = self._preconnect_buffer.capture()
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 12 additional findings in Devin Review.

Open in Devin Review
Comment on lines +826 to +832
def _on_participant_active(participant: RemoteParticipant) -> None:
if participant.identity != target_identity:
return
if not track.has_preconnect_buffer:
return
room.off("participant_active", _on_participant_active)
_send_buffer(participant)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Event listener leak in _setup_preconnect_auto_send when preconnect buffer is stopped before target becomes active

In _on_participant_active, when the target identity matches but track.has_preconnect_buffer returns False, the handler returns early without calling room.off("participant_active", _on_participant_active) to unregister itself. This means the handler stays registered on the room indefinitely, holding references to track, room, and other closure variables. Worse, if start_preconnect_buffer() is called again later and the same identity fires participant_active again (e.g. after a reconnect), the stale handler will unexpectedly trigger and send the buffer.

The fix is to unregister the handler whenever the target identity matches, regardless of the buffer state — _send_buffer already checks has_preconnect_buffer internally.

Suggested change
def _on_participant_active(participant: RemoteParticipant) -> None:
if participant.identity != target_identity:
return
if not track.has_preconnect_buffer:
return
room.off("participant_active", _on_participant_active)
_send_buffer(participant)
def _on_participant_active(participant: RemoteParticipant) -> None:
if participant.identity != target_identity:
return
room.off("participant_active", _on_participant_active)
_send_buffer(participant)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants