Skip to content

fix: stop the microphone when a local audio track is stopped before joining a room - #1216

Open
Venomexeno wants to merge 3 commits into
livekit:mainfrom
Venomexeno:fix/stop-local-audio-recording
Open

Venomexeno wants to merge 3 commits into
livekit:mainfrom
Venomexeno:fix/stop-local-audio-recording

Conversation

@Venomexeno

@Venomexeno Venomexeno commented Sep 22, 2026 •

Copy link
Copy Markdown

Summary

  • startCapture() opens the native audio device module directly. Stopping the MediaStreamTrack does not close it, so a pre-join microphone keeps recording after stop() / dispose().
  • Native recording is process-wide, so it now stops only when the last holder releases it.

Test plan

  • flutter test test/track/native_recording_holders_test.dart
  • Pre-join mic: start a local audio track, stop it without joining, confirm the microphone indicator turns off
  • Two holders (pre-join mic and room mic): stopping one does not stop recording until the last one is released
Untitled.mp4

- Introduced a mechanism to hold native audio recording sessions, allowing multiple tracks to share the same recording instance.
- Updated startCapture and stopCapture methods to manage the recording state and ensure proper resource release.
- Added NativeRecordingHolders class to track active recording holders and control when to stop recording.
- Improved error handling during audio recording initialization.

This change optimizes audio capture processes and prevents unnecessary recording sessions from being initiated.
@CLAassistant

CLAassistant commented Sep 22, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +89 to +92
/// Process-wide because [Native.startLocalRecording] starts one audio device
/// module, shared by every local audio track (pre-join mic, room mic,
/// pre-connect buffer).
static final NativeRecordingHolders _nativeRecordingHolders = NativeRecordingHolders();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Failed pre-connect leaves permanent holder

When pre-connect fails after capture starts, _nativeRecordingHolders retains the abandoned track. reset drops it without calling stop(), so later microphones never stop native recording.

Learn more

A successfully started pre-connect track adds itself to the process-wide holder set. If the operation wrapped by withPreConnectAudio then throws, stopRecording stops the native recorder directly but never stops the track. reset then clears _localTrack, while the static set keeps the object reachable forever. Every later track can add and remove itself, but the abandoned entry prevents the set from becoming empty.

Example: A pre-connect track starts, then Room.connect fails. A later pre-join microphone starts and stops normally, but removing it leaves the abandoned pre-connect holder, so Native.stopLocalRecording() is never called.

Recommended fix: Route pre-connect failure cleanup through the owned LocalAudioTrack.stop() before clearing _localTrack. Remove the direct native stop from that path so holder removal and native shutdown remain one atomic lifecycle.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@Venomexeno
Venomexeno requested a review from changt as a code owner September 24, 2026 14:45

This branch has not been deployed

No deployments
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.

2 participants