Skip to content

fix(tts): eager audio binding, dynamic terminate wait, and cancel audio purge - #287

Open
pylrn wants to merge 1 commit into
AssemblyAI:masterfrom
pylrn:fix/tts-session-drain-and-binding
Open

pylrn wants to merge 1 commit into
AssemblyAI:masterfrom
pylrn:fix/tts-session-drain-and-binding

Conversation

@pylrn

@pylrn pylrn commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses several session synchronization and lifecycle edge cases identified across the Text-to-Speech WebSocket clients (as highlighted in Node SDK #200):

  • Eager audio() binding: Captures target flush_id and checks session open state immediately at audio() call time rather than deferring evaluation to the first iterator step. This ensures that creating a reader before subsequent generate() and flush() calls does not inadvertently bind to an advanced or incremented flush_id.
  • Prevent re-reading finished flush_ids: Tracks _done_through across consumed requests; calling audio(flush_id) on an id whose FlushDone was already consumed now immediately raises a ValueError rather than hanging indefinitely waiting on a dead queue.
  • Purge and discard stale audio on cancel(): Purges pending audio frames from PullBuffer upon cancel() and drops in-flight audio frames arriving for cancelled flush_ids in _dispatch(), preventing cancelled audio from leaking into subsequent iterations.
  • Activity-based terminate() drain: Replaces the flat cutoff wait in terminate() with an activity-based loop that continues waiting as long as the server is actively sending frames, timing out only if no message has arrived for terminate_timeout seconds.
  • Speak backlog and chunking guard: Validates text_max_buffer_chars before sending text in speak() and chunks text exceeding single Generate frame limits.
  • Version bump: Bumps SDK version to 1.8.3.

Verification

  • Ran full test suite: 134/134 TTS session unit tests passing (tests/unit/test_tts_session.py, tests/unit/test_tts_session_async.py).
  • Added unit tests for:
    • eager audio() request id binding across multiple flushes (sync and async)
    • re-reading finished flush_id error raising
    • speak() buffer backlog limit enforcement and chunking
    • purging and discarding in-flight audio on cancel()
  • Linted with ruff and formatted with black.

…ge cancelled audio

- Eager audio() binding: bind target flush_id and session open state immediately at audio() call time rather than lazily on first iteration, preventing subsequent flush() calls from advancing the request id before playback begins.
- Guard against re-reading finished flush_ids: track _done_through and raise ValueError if audio() is called for an id whose FlushDone was already consumed.
- Purge and discard stale audio on cancel(): remove pending audio frames from PullBuffer on cancel() and discard in-flight frames arriving for cancelled flush_ids in _dispatch().
- Activity-based terminate() wait: dynamically extend terminate wait as long as the server actively sends frames, timing out only after terminate_timeout of complete silence/inactivity rather than cutting off active synthesis at a flat duration.
- Speak backlog guard: enforce text_max_buffer_chars limit before sending text in speak(), and split text into chunked Generate frames within single-frame limits.
- Bump version to 1.8.3.
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.

1 participant