Skip to content

fix(participant): guard chunk restart so a dead mic ends as an interruption - #1033

Draft
posthog-eu[bot] wants to merge 1 commit into
mainfrom
posthog-self-driving/fixparticipant-guard-mediarecorder-4072c4
Draft

fix(participant): guard chunk restart so a dead mic ends as an interruption#1033
posthog-eu[bot] wants to merge 1 commit into
mainfrom
posthog-self-driving/fixparticipant-guard-mediarecorder-4072c4

Conversation

@posthog-eu

@posthog-eu posthog-eu Bot commented Sep 3, 2026

Copy link
Copy Markdown

Problem

  • Participants silently lose the rest of a recording when the mic drops mid-conversation: the recorder stops, but the UI keeps saying it is recording.
  • useChunkedAudioRecorder restarts each chunk from the onstop handler, ending in an unguarded recorder.start(). When the mic track dies between chunks (the participant takes a call, or another app grabs the device), start() throws NotSupportedError and the throw escapes onstop.
  • Result: isRecording stays true, no further ondataavailable or onstop fires, the suspicious-chunk detection never runs, and onRecordingInterrupted is never called. The participant gets no alert and every second of audio after that point is lost.

Changes

  • Wrap the chunk-restart start() in a try/catch that mirrors the existing startRecording failure path: release the mic, drop the recorder, clear isRecording, and raise the same interruption the participant already gets for a dead mic (alert sound + interruption modal via onRecordingInterrupted).
  • Only restart calls (from onstop and the restart timer) are handled locally; the initial start() from startRecording still propagates so its own caller resets state and returns false. A new isRestart flag draws that line.
  • Guard the previous-recorder stop() on state === "recording" (as stopRecording and pauseRecording do). A restart runs from onstop, where that recorder is already inactive, and stop() on an inactive recorder throws too.
  • Factor the alert playback into a small playInterruptionAlert helper shared by both interruption paths.

Testing

  • New unit test drives the restart-failure path (mic dies between chunks) and asserts the failure surfaces as an interruption, not a stuck "still recording" state: onRecordingInterrupted fires, isRecording drops to false, and the dead mic is released.
  • useChunkedAudioRecorder.test.tsx: 7 passed.

Agent context

  • The reported symptom named the start() throw. Reproducing the restart path in the test uncovered a second unguarded throw in the same loop (stop() on the already-inactive previous recorder), fixed the same way.

Created with PostHog Desktop from this inbox report.

…uption

The chunk recorder restarts each chunk from onstop by calling
startRecordingChunk again, which ends in an unguarded recorder.start().
When the mic track dies between chunks (the participant takes a call, or
another app grabs the device), start() throws NotSupportedError and the
throw escapes onstop: isRecording stays true, no more chunks upload, and
the participant loses the rest of the recording with no alert.

Wrap the restart start() in a try/catch that mirrors the startRecording
failure path: release the mic, drop the recorder, clear isRecording, and
raise the same interruption the participant already gets for a dead mic.
The initial start (from startRecording) still propagates so its own
caller handles it.

Also guard the previous-recorder stop() on state === "recording", the
way stopRecording and pauseRecording do. A restart runs from onstop where
that recorder is already inactive, and stop() on an inactive recorder
throws too.

Generated-By: PostHog Desktop
Task-Id: 0a01a33f-1277-42a8-8087-d93f2358e066
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.

0 participants