Skip to content

livekit-plugins-fishaudio: pooled websocket reuse + prebuffer to fix cold-start crackle#6368

Open
cshape wants to merge 2 commits into
livekit:mainfrom
cshape:cale/fishaudio-pool-prebuffer
Open

livekit-plugins-fishaudio: pooled websocket reuse + prebuffer to fix cold-start crackle#6368
cshape wants to merge 2 commits into
livekit:mainfrom
cshape:cale/fishaudio-pool-prebuffer

Conversation

@cshape

@cshape cshape commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

two changes to the fish plugin:

  1. reuse/pre-warm the /v1/tts/live websocket via utils.ConnectionPool (same
    pattern as cartesia/rime). the plugin used to open a fresh WSS per utterance,
    paying ~330ms of TLS+WS handshake on every reply. now one socket is reused per
    session and pre-warmed via prewarm(), so the first reply skips the handshake.
    general latency win, independent of the crackle fix.

  2. prebuffer the first two audio chunks before starting playout. fish streams a
    small first chunk (~460ms) then, ~250ms later, a much larger second chunk.
    starting webrtc's real-time playout at the first chunk leaves only ~190ms of
    buffer headroom; app/network jitter tips it into an underrun the receiver
    conceals as an audible click/gap ~0.5s into the first utterance (repro'd ~1 in
    5). waiting for the second chunk starts playout with ~1.3s buffered and
    eliminates it.

the prebuffer is an internal constant (_PREBUFFER_CHUNKS = 2), not a public
option. it costs a little TTFB and is a stopgap for fish's cold-start chunk
pacing; once the inference side streams the opening chunks smoothly we'll drop it
to 1 to start on the first chunk. working with the fish inference team on that now.

verified no crackles on livekit-demo-web.onrender.com; chunk-2 headroom measured
before/after and the negative tail is gone.

@cshape cshape requested a review from a team as a code owner July 9, 2026 17:19
devin-ai-integration[bot]

This comment was marked as resolved.

…cold-start crackle

- reuse/pre-warm the /v1/tts/live websocket via utils.ConnectionPool (like
  cartesia/rime) instead of opening a fresh WSS per utterance, so replies skip the
  ~330ms TLS+WS handshake after the first.
- prebuffer the first two audio chunks before starting playout, so webrtc's
  real-time playout doesn't underrun in the ~250ms gap after fish's small first
  chunk (the cold-start crackle). internal constant _PREBUFFER_CHUNKS=2, a stopgap
  until fish inference streams the opening chunks smoothly (then -> 1).
@cshape cshape force-pushed the cale/fishaudio-pool-prebuffer branch from 4a2b264 to c5a9a0b Compare July 9, 2026 18:21
@cshape cshape changed the base branch from tina/expressive-fish to main July 9, 2026 18:21
model is sent as a ws connection header (not the per-request body), so a pooled
socket keeps the old model after update_options(model=...). invalidate the pool on
model change so the next stream reconnects with the new model. other options ride in
the request body and need no reconnect. addresses PR review feedback.
cshape added a commit to cshape/agents-expressive that referenced this pull request Jul 9, 2026
…ol on model change

- prebuffer_chunks is now an internal constant (_PREBUFFER_CHUNKS=2), not a public
  option, matching the upstream PR (livekit#6368).
- shorten the pool/prebuffer comments.
- invalidate the connection pool when update_options(model=...) changes the model,
  since the model rides as a ws connection header (other opts are per-request body).
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