Repository navigation
Commit 35fc9f3
authored
fix(examples): handle interruptions and disconnects in push-to-talk (#4192)
Pressing Enter tried to use a button the app never mounted, and
transcripts containing Rich tags could crash the session. This change
displays transcripts and API errors as literal text, reports failed or
incomplete responses, and closes the audio device and SDK client on
exit. Q exits with code 0; an unexpected normal or abnormal disconnect
exits with code 1.
When K interrupts playback, the sample cancels the active response and
truncates unheard audio, including queued items after response.done.
Capture starts before awaiting cancellation and truncation; microphone
sends wait until those writes finish. If audio or its transcript arrives
after interruption, the sample truncates each previously unknown item
part to zero once. It retains the original cutoff for partly played
audio. Manual mode skips commit and response.create if no audio was sent
before release. Manual finalization also reserves the server input
buffer before the next recording can append, so successive recordings
remain separate while capture and interruption can still proceed.
A response.created event that arrives after K, before any new audio is
offered, belongs to the earlier turn. The sample reserves interruption
work before it waits for the send lock. Microphone appends and manual
commits recheck that work after obtaining the lock and yield outside it
when necessary. They cannot overtake a cancellation or truncation that
was queued while they waited. It suppresses late audio and transcripts
and truncates unheard parts, even after a quick release. Once new audio
is offered, these events cannot identify whether a delayed response
belongs to an earlier turn or to a current pause detected by server VAD.
The sample preserves possible current VAD replies; it does not guarantee
interruption of every old response in that interval.
All 48 committed sample regressions pass locally on Python 3.10 and 3.13
against the public SDK, and on Python 3.10 against the internal SDK.
They use fake audio devices and held writes. Contended delayed
cancellations reproduced wrong send ordering in both VAD and manual
modes. Separate tests reproduce a runnable microphone or manual
completion overtaking an interruption worker that has not started. The
new regressions also verify progress when that worker is cancelled
before starting. Four late-audio cases reproduced missing truncations.
The tests also cover frame counts, queued playback, recording while
cancellation is blocked, manual and VAD modes, transcripts and
connection closure. Scoped Ruff and pinned Pyright checks pass. Eight
earlier Textual scenarios exercised SDK connections to a local WebSocket
peer and virtual audio devices.
Run the sample regressions explicitly after installing the sample
dependencies:
```sh
python -m pytest -o addopts= --noconftest -q examples/realtime/test_push_to_talk_app.py
```
The default SDK test run does not collect these example tests.
The first microphone append restores recording state and immediately
re-raises any failure, including asyncio.CancelledError. Narrowing that
cleanup to Exception would skip restoring _awaiting_new_input when
asyncio cancels an append. The committed canceled-append regression
tests that no event is sent, the microphone closes, and cancellation
propagates.
Playback accounting uses PortAudio DAC timestamps to exclude audio still
buffered on the device. Interrupting silences callbacks and aborts
playback before processing the accounting queue. It measures the last
known playing time immediately before abort; PortAudio's clock keeps
advancing after playback stops and does not provide an exact hardware
stop timestamp. No physical-device timing precision was tested;
prolonged backpressure can fill the device buffer. Unsupported audio
content parts are reported without sending an invalid truncate event.
---------
Co-authored-by: Mark Stuart <323302876+markstuart-oai@users.noreply.github.com>1 parent 55fc500 commit 35fc9f3
3 files changed
Lines changed: 1424 additions & 59 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
46 | 50 | | |
47 | 51 | | |
48 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
49 | 56 | | |
50 | 57 | | |
51 | 58 | | |
52 | 59 | | |
53 | | - | |
| 60 | + | |
54 | 61 | | |
55 | | - | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
56 | 69 | | |
57 | | - | |
| 70 | + | |
58 | 71 | | |
59 | 72 | | |
| 73 | + | |
60 | 74 | | |
61 | 75 | | |
62 | 76 | | |
63 | 77 | | |
64 | 78 | | |
65 | 79 | | |
66 | 80 | | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | 81 | | |
71 | 82 | | |
72 | 83 | | |
73 | | - | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
74 | 103 | | |
75 | 104 | | |
76 | 105 | | |
77 | | - | |
| 106 | + | |
78 | 107 | | |
79 | 108 | | |
80 | 109 | | |
81 | 110 | | |
82 | 111 | | |
83 | 112 | | |
84 | 113 | | |
85 | | - | |
86 | | - | |
87 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
88 | 125 | | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
89 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
90 | 140 | | |
91 | 141 | | |
92 | 142 | | |
| |||
0 commit comments