From 288dfb45aa7f194bc03cf431c88b16ee0d070d1e Mon Sep 17 00:00:00 2001 From: morepriyam Date: Sun, 9 Aug 2026 02:35:17 +0530 Subject: [PATCH] fix(recorder): attach the mic from the first configure - the cameraReady gate flashed every cold open micWanted was gated on cameraReady so a cold open configured the session video-only, then rebuilt the whole video output audio-ful the moment onStarted fired - a second full session reconfigure ~25ms after the first preview frame, visible as a flash on every open (and two extra output rebuilds on every camera flip, since flipping resets cameraReady). Confirmed via a temporary session-lifecycle trace on-device (iPhone, iOS): the flash timeline was previewStarted -> started -> videoOutput rebuilt (enableAudio:true) -> second sessionConfigSelected. With the gate removed, a cold open shows exactly one configure, the H.264 pin resolves on its first attempt (~2ms - no longer racing the reconfigure), and recorded clips probe as h264/aac 1920x1080@30. The gate's stated purpose - call detection landing before the mic is requested - was already satisfied without it: useCallState reads CallDetector.isCallActive() synchronously in its state initializer, so the render that first configures the session already knows the call state. The '!pri' -11800 recovery path (reportMicPriorityError) remains the backstop for a cold open racing an in-progress call the snapshot missed. --- src/features/recorder/use-recorder.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/features/recorder/use-recorder.ts b/src/features/recorder/use-recorder.ts index bcb3293..699634a 100644 --- a/src/features/recorder/use-recorder.ts +++ b/src/features/recorder/use-recorder.ts @@ -72,10 +72,16 @@ export function useRecorder(initialDraftId?: string) { // the mic into a call that began in the background. const { callActive, appActive, reportMicPriorityError } = useCallState(); - // The mic config the session SHOULD have. `cameraReady` gates it so a cold open comes up - // video-only first (call detection lands before the mic is ever requested); dropped while a call - // holds the mic (`callActive`) or the user muted, so that clip has no audio track. - const micWanted = cameraReady && !muted && !callActive; + // The mic config the session SHOULD have: dropped while a call holds the mic (`callActive`) + // or the user muted, so that clip has no audio track. Deliberately NOT gated on cameraReady: + // call state is read synchronously at first render (useCallState's initializer), so the mic + // can attach from the session's FIRST configure. Gating on cameraReady made every cold open + // come up video-only and then rebuild the output audio-ful at `onStarted` — a second full + // session reconfigure ~25ms after the first preview frame, i.e. a visible flash on every + // open (and two extra rebuilds on every camera flip). The '!pri' -11800 recovery path + // (reportMicPriorityError) remains the backstop for the rare cold open that races an + // in-progress call the synchronous snapshot missed. + const micWanted = !muted && !callActive; // Freeze the mic config for the duration of a recording: an enableAudio change rebuilds the video // output, which tears down the in-flight recorder before it can finalize — dropping the clip. We // hold the value captured while idle and only let it change once recording stops, so a call