fix(recorder): attach the mic from the first configure - the cameraReady gate flashed every cold open - #150
Merged
Merged
Conversation
…ady 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.
There was a problem hiding this comment.
Pull request overview
Removes the cameraReady gate from the recorder’s mic enablement decision so the capture session can be configured with audio from the very first configure, avoiding a post-preview reconfigure that causes a visible “flash” on cold open.
Changes:
- Update
micWantedto depend only onmutedandcallActive, no longer oncameraReady. - Add an in-code rationale explaining why gating on
cameraReadywas unnecessary and howuseCallState’s synchronous initializer supports the change.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
morepriyam
added a commit
to jlocala1/pulse
that referenced
this pull request
Aug 9, 2026
…264 per connection epoch Two regressions from one root: mieweb#150 removed the accidental session restart a camera flip used to trigger (the flip's cameraReady reset flipped micWanted, rebuilding the video output and restarting the session), and two behaviors were riding it. 1. On iOS, flipCamera()'s setCameraReady(false) never re-armed: onStarted maps to AVCaptureSession.didStartRunningNotification, and an iOS device swap runs inside beginConfiguration/commitConfiguration on a RUNNING session - the notification never fires (start() even early-returns on isRunning). Record gestures (enabled: cameraReady && ...) died permanently; flipping back couldn't recover. The reset exists solely for CameraX (mieweb#133 torch-mid-rebind crash) and CameraX re-fires onStarted per bind, so it is now Android-only - mirroring the existing platform scoping of the zoom/torch prop gates (ba7647b). 2. The H.264 pin is applied per-connection natively (output.setOutputSettings(settings, for: connection)), and a flip forms a new connection - the pin died with the old one and the effect, keyed on output identity, never re-applied: post-flip clips silently recorded HEVC. The pin now re-keys on a connection epoch bumped from <Camera onConfigured> (fires after connections are formed on every reconfigure: cold open, enableAudio rebuild, flip), so it re-lands on the new connection with the existing bounded retry riding any race. Repro'd on TestFlight 2.0.0 (31) = main @ 82e3b95, the first build containing mieweb#150. Fixes mieweb#153
morepriyam
added a commit
that referenced
this pull request
Aug 12, 2026
fix(recorder): attach the mic from the first configure - the cameraReady gate flashed every cold open
morepriyam
added a commit
that referenced
this pull request
Aug 12, 2026
…264 per connection epoch Two regressions from one root: #150 removed the accidental session restart a camera flip used to trigger (the flip's cameraReady reset flipped micWanted, rebuilding the video output and restarting the session), and two behaviors were riding it. 1. On iOS, flipCamera()'s setCameraReady(false) never re-armed: onStarted maps to AVCaptureSession.didStartRunningNotification, and an iOS device swap runs inside beginConfiguration/commitConfiguration on a RUNNING session - the notification never fires (start() even early-returns on isRunning). Record gestures (enabled: cameraReady && ...) died permanently; flipping back couldn't recover. The reset exists solely for CameraX (#133 torch-mid-rebind crash) and CameraX re-fires onStarted per bind, so it is now Android-only - mirroring the existing platform scoping of the zoom/torch prop gates (fb06eea). 2. The H.264 pin is applied per-connection natively (output.setOutputSettings(settings, for: connection)), and a flip forms a new connection - the pin died with the old one and the effect, keyed on output identity, never re-applied: post-flip clips silently recorded HEVC. The pin now re-keys on a connection epoch bumped from <Camera onConfigured> (fires after connections are formed on every reconfigure: cold open, enableAudio rebuild, flip), so it re-lands on the new connection with the existing bounded retry riding any race. Repro'd on TestFlight 2.0.0 (31) = main @ f21fb0b, the first build containing #150. Fixes #153
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every cold open of the recorder flashed a beat after the first preview frame — the session came up, rendered, then visibly renegotiated. Root cause (full analysis in #149):
micWantedwas gated oncameraReady, so the session always configured video-only first, then theonStarted->enableAudioflip rebuilt the whole video output on the live session — a second full configure ~25ms afterpreviewStarted:The same gate also made the H.264 pin (#143) race the post-start reconfigure (first
setOutputSettingsattempt always rejected, riding on the retry), and camera flips paid two extra output rebuilds since flipping resetscameraReady.Change
One line plus documentation — drop the
cameraReadyterm:The gate's stated purpose ("call detection lands before the mic is requested") was already satisfied without it:
useCallStatereadsCallDetector.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 the rare cold open that races an in-progress call the snapshot missed. Mute and in-call gating are unchanged, as is the mid-recording freeze (frozenMicRef).Verification (on-device, iPhone)
Instrumented the full session lifecycle (
onSessionConfigSelected/onConfigured/onPreviewStarted/onStarted+ output-identity and codec-pin tracing), reproduced the double-configure, then re-ran with the fix across multiple cold opens:sessionConfigSelected/configuredpair per open; nothing reconfigures after the first frame — flash gonevideoOutputis created audio-ful from the first render (one instance, no rebuild)h264/aac, 1920x1080 @ 29.97 — the pipeline contract holdsInstrumentation was removed after verification; the shipped diff is the one-line gate change with an explanatory comment.
tsc --noEmitclean, eslint clean, full jest suite green (122 tests).Fixes #149