Skip to content

feat(voice): opt-in audio fade-out on interruption (#6165)#6410

Open
dorukdumlu wants to merge 1 commit into
livekit:mainfrom
dorukdumlu:feat/interruption-fade-out
Open

feat(voice): opt-in audio fade-out on interruption (#6165)#6410
dorukdumlu wants to merge 1 commit into
livekit:mainfrom
dorukdumlu:feat/interruption-fade-out

Conversation

@dorukdumlu

Copy link
Copy Markdown

Closes #6165

What

Adds an opt-in linear fade-out applied to the agent's audio when it gets interrupted, instead of cutting playback abruptly:

await session.start(
    room=room,
    room_options=RoomOptions(
        audio_output=AudioOutputOptions(fade_out_on_interruption=0.08),  # seconds
    ),
)

Default is 0.0 (disabled) — existing behavior is unchanged.

How (and why not the holdback buffer I proposed in the issue)

In my comment on #6165 I proposed holding back a small tail buffer before forwarding frames downstream. I dropped that approach while implementing: it adds first-frame playback latency equal to the tail size, which is the one metric a voice agent shouldn't pay for a cosmetic feature.

Instead, the fade is built inside _ParticipantAudioOutput, which already knows everything needed at interruption time:

  • _forward_audio keeps a small rolling history (~400ms: queue size + fade + margin) of frames already pushed to the rtc.AudioSource.
  • On interruption, AudioSource.queued_duration tells us how much of that history hadn't played yet, so the exact playback position is end_of_history - queued_duration.
  • After clear_queue(), a single frame is re-pushed: the next fade_out_on_interruption seconds of the actual audio from the playback position, with a linear 1 → 0 gain ramp.

This gives zero added latency and no content discontinuity — the fade is the same audio the user was hearing, just ramped down. The history bookkeeping is only active when the option is enabled.

playback_position accounting is untouched: the faded tail replays audio that was already subtracted via queued_duration, so reported position stays correct for chat-history truncation.

Notes

  • The fade duration is clamped to what's actually unplayed (interruption near the end of an utterance fades over the remainder).
  • Known pre-existing race, unchanged by this PR: if _forward_audio is blocked in AudioSource.capture_frame under backpressure when the interruption fires, that in-flight frame can land after clear_queue(). Today it plays at full gain after the cut; with the fade enabled it may play before the faded tail. Fixing that needs coordination between _forward_audio and _wait_for_playout and felt out of scope here.

Testing

  • _build_fade_out_frame is a pure function; added hermetic unit tests (tests/test_interruption_fade_out.py, marked unit): ramp shape/monotonicity, correct offset from playback position, clamping to unplayed duration and to available history, stereo, and the no-op cases.
  • Full --unit suite passes (same result as main baseline on my machine).
  • ruff check / ruff format clean; no new mypy errors in room_io.

@dorukdumlu dorukdumlu requested a review from a team as a code owner July 13, 2026 22:26
@CLAassistant

CLAassistant commented Jul 13, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@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: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

@dorukdumlu

Copy link
Copy Markdown
Author

Ended up implementing this in #6410 went with a zero-latency approach inside _ParticipantAudioOutput instead of the holdback buffer, details in the PR description

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.

Feature request: smooth fade-out when the agent is interrupted

2 participants