Skip to content

Conversation

@ayush-ranjan-official
Copy link

@ayush-ranjan-official ayush-ranjan-official commented Jan 23, 2026

Summary

This PR fixes a bug in the LiveAvatar plugin where AudioSegmentEnd messages are ignored in the _forward_audio() function, causing the agent session to never receive playback completion signals.

Problem

When using the LiveAvatar plugin, the agent session's state machine breaks:

  • Session gets stuck cycling between listeningthinkinglistening
  • The speaking state is never reached
  • notify_playback_finished() is never called for normal audio completion
  • This causes the LLM to repeatedly generate responses (step repetition)

Root Cause

The _forward_audio() function in avatar.py iterates over self._audio_buffer which yields two types:

  1. rtc.AudioFrame - actual audio data
  2. AudioSegmentEnd - marker indicating end of audio segment

However, only rtc.AudioFrame is handled. When AudioSegmentEnd is received, the loop continues without any action:

# Current buggy code
async def _forward_audio() -> None:
    async for audio_frame in self._audio_buffer:
        if isinstance(audio_frame, rtc.AudioFrame):  # Only this case handled!
            # ... process audio
            self._playback_position += resampled_frame.duration
        # AudioSegmentEnd is IGNORED - no else clause!

This means notify_playback_finished() is never called when audio naturally ends, only when interrupted (via _on_clear_buffer).

Solution

Add an elif clause to handle AudioSegmentEnd and call notify_playback_finished():

async def _forward_audio() -> None:
    async for audio_frame in self._audio_buffer:
        if isinstance(audio_frame, rtc.AudioFrame):
            # ... existing audio frame handling ...
        elif isinstance(audio_frame, AudioSegmentEnd):
            if self._audio_playing:
                self._audio_buffer.notify_playback_finished(
                    playback_position=self._playback_position,
                    interrupted=False,
                )
                self._playback_position = 0.0
                self._audio_playing = False

Summary by CodeRabbit

  • Bug Fixes
    • Improved audio playback handling to reliably detect when an audio segment finishes, notify playback completion, reset playback position, and stop playback.
    • Results in more accurate end-of-audio behavior and prevents lingering or resumed playback after a segment ends, improving call and media session reliability.

✏️ Tip: You can customize this high-level summary in your review settings.

The _forward_audio() function in AvatarSession only handled rtc.AudioFrame
objects, completely ignoring AudioSegmentEnd markers. This caused
notify_playback_finished() to never be called when audio naturally ended,
breaking the agent session's state machine.

Root cause:
QueueAudioOutput yields both rtc.AudioFrame and AudioSegmentEnd objects.
The existing code only had an `if isinstance(audio_frame, rtc.AudioFrame)`
check with no else/elif clause, so AudioSegmentEnd was silently dropped.

Fix:
Add an elif clause to detect AudioSegmentEnd and call
notify_playback_finished(interrupted=False) to properly signal that
audio playback completed naturally. This mirrors the existing interruption
handling in _on_clear_buffer() which uses interrupted=True.
@CLAassistant
Copy link

CLAassistant commented Jan 23, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

Added handling for AudioSegmentEnd events in _forward_audio: when a segment ends while audio is playing, the code notifies playback finished with the current playback_position, resets playback_position to 0.0, and sets _audio_playing to False.

Changes

Cohort / File(s) Summary
Audio playback event handling
livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py
Added a new branch in _forward_audio to handle AudioSegmentEnd events, emitting playback-finished notification and resetting playback position and playing state.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Suggested reviewers

  • longcw

Poem

🐇 I hopped along the audio stream,
A segment closed — a gentle dream,
I reset my watch, I still my paw,
Playback ends without a flaw,
Onward now, a fresh new beam 🎵

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding handling for AudioSegmentEnd to signal playback completion in the LiveAvatar plugin, which matches the core fix in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0378f0 and 3220e5e.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: unit-tests

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py (1)

25-25: Missing import for AudioSegmentEnd causes NameError at runtime.

The static analysis correctly identifies that AudioSegmentEnd is used on line 219 but never imported. This will cause the code to fail at runtime.

🐛 Proposed fix
-from livekit.agents.voice.avatar import QueueAudioOutput
+from livekit.agents.voice.avatar import AudioSegmentEnd, QueueAudioOutput
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fe642d and b0378f0.

📒 Files selected for processing (1)
  • livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Format code with ruff
Run ruff linter and auto-fix issues
Run mypy type checker in strict mode
Maintain line length of 100 characters maximum
Ensure Python 3.9+ compatibility
Use Google-style docstrings

Files:

  • livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py
🧬 Code graph analysis (1)
livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py (2)
livekit-agents/livekit/agents/voice/avatar/_types.py (1)
  • AudioSegmentEnd (10-11)
livekit-agents/livekit/agents/voice/speech_handle.py (1)
  • interrupted (83-84)
🪛 GitHub Check: ruff
livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py

[failure] 219-219: Ruff (F821)
livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py:219:46: F821 Undefined name AudioSegmentEnd

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: unit-tests
  • GitHub Check: type-check (3.13)
  • GitHub Check: type-check (3.9)
🔇 Additional comments (1)
livekit-plugins/livekit-plugins-liveavatar/livekit/plugins/liveavatar/avatar.py (1)

219-226: LGTM on the logic (once import is fixed).

The handling correctly mirrors _on_clear_buffer but signals normal completion (interrupted=False) instead of interruption. The guard on _audio_playing and the state reset are appropriate.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants