Skip to content

feat(extras): add pause()/resume() to MicrophoneStream#219

Open
ccampbell-aai wants to merge 1 commit into
masterfrom
ccampbell/fix/issue-101
Open

feat(extras): add pause()/resume() to MicrophoneStream#219
ccampbell-aai wants to merge 1 commit into
masterfrom
ccampbell/fix/issue-101

Conversation

@ccampbell-aai

Copy link
Copy Markdown
Contributor

Summary

Fixes #101.

Adds pause(), resume(), and a paused property to aai.extras.MicrophoneStream, so voice-agent builders can mute mic input mid-session — e.g. while the agent's TTS is playing — without tearing down the streaming session.

Design: silence-forwarding. While paused, the device is still read (so PyAudio's input buffer keeps draining) but silence of equal length is yielded in place of the captured audio. This keeps the session alive (no 4031 idle timeout) and avoids buffer overflows — the two failure modes of simply skipping reads or stopping iteration. Pause takes effect within one chunk (~100ms).

Thread-safe close(). Since pause/resume invites controlling the mic from another thread, close() is hardened: a lock now serializes device reads against teardown, so close() waits for any in-flight read to finish instead of closing the PortAudio stream mid-read() — which deadlocks the reading thread (verified live on macOS/CoreAudio). close() is also idempotent now (was a double-free before).

Asyncio guidance. New class docstring documents that AsyncStreamingClient users should feed the mic through asyncio.to_thread rather than passing the iterator directly (blocking reads stall the event loop — same class of problem as the existing stream_file gotcha). pause()/resume() are safe to call from the event loop in that pattern.

Testing

  • pytest tests/unit/test_extras.py — 6 passed. New tests:
    • pause/resume yields silence while paused, keeps draining the device, forwards live audio after resume (fails pre-fix)
    • cross-thread close() during an in-flight read: no deadlock, teardown runs exactly once after the read completes, second close() is a no-op (fails pre-fix)
  • End-to-end verified against a live streaming session with a real mic: transcripts stop during pause, resume works, session survives the pause, clean termination
  • Mirrored CI locally: ruff==0.3.5 format + check clean, mypy==1.5.1 clean

🤖 Generated with Claude Code

Lets voice-agent builders mute the mic while the agent speaks (breaking
the TTS->mic->transcribe feedback loop) without tearing down the session.
While paused the device is still drained but silence is yielded, keeping
the streaming session alive.

Since pause/resume invites controlling the mic from another thread, also
make close() thread-safe: teardown now waits (via a lock) for any
in-flight device read to finish — closing the PortAudio stream mid-read
deadlocks the reading thread — and is idempotent. Document that
AsyncStreamingClient users should wrap the blocking reads via
asyncio.to_thread rather than passing the iterator directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Please provide a method to pause streaming input.

2 participants