Skip to content

Add stopMicrophoneOnMute option to release the microphone while muted - #987

Open
adrian-niculescu wants to merge 1 commit into
livekit:mainfrom
adrian-niculescu:stop-microphone-on-mute
Open

Add stopMicrophoneOnMute option to release the microphone while muted#987
adrian-niculescu wants to merge 1 commit into
livekit:mainfrom
adrian-niculescu:stop-microphone-on-mute

Conversation

@adrian-niculescu

@adrian-niculescu adrian-niculescu commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #375.

Android equivalent of the web SDK's stopMicTrackOnMute, added as LocalAudioTrackOptions.stopMicrophoneOnMute.

Two reasons to want it:

  1. Muting an audio publication only disables the media track locally (mediaTrack.enabled = false). The ADM keeps AudioRecord open, so the OS microphone-in-use indicator stays lit for as long as the track is published. An indicator burning next to a muted mic is a privacy concern.
  2. Advanced call handling needs the device actually released. Putting a call on hold should cleanly hand the microphone back so another app can use it.

Unpublishing and republishing the track does release the mic, but it leaks native memory on the audio path and churns track history on the server.

Leak measurements

Measured on LiveKit Android SDK 2.27.0, on a Pixel 6a (bluejay) running Android 17 beta (build CP41.260701.005, API 37, 2026-07-05 security patch).

On a two-party call with the camera off, toggling the microphone 50 times through unpublish/republish retained about 11.7 MB of native heap, roughly 0.23 MB per cycle, against a baseline noise band of 0.9 MB.

Metric Baseline After 50 cycles, settled Delta
Native Heap Alloc 91.7 - 92.5 MB 103.6 - 104.2 MB +11.7 MB
Native Heap PSS 83.8 - 84.3 MB 94.3 - 94.4 MB +10.4 MB
Total PSS 371.8 - 376.3 MB 388.3 - 391.0 MB +15.5 MB
Graphics 88.3 - 89.9 MB 88.5 - 90.0 MB unchanged

Three details point at retention rather than an artifact. There was no decay: five samples over the following 60 seconds read 104.1, 104.2, 103.6, 104.2, 104.2 MB. Native Heap Size grew from 125.5 to 134.0 MB, so the arena was extended to satisfy live allocations, which fragmentation slack does not do. Graphics is unchanged, so no part of the video pipeline is involved.

The same test driving the camera instead fell back inside its baseline band within 16 seconds, which lines up with #971 fixing the video path by stopping the transceiver and disposing the VideoSource on unpublish. The audio path has no counterpart to that fix, and the measured version already contains it.

That split looks deliberate rather than accidental. Reviewing the libwebrtc side in webrtc-sdk/webrtc#194, the transceiver teardown crash is described as "not even reproducible without audio", and the equivalent SDK-side fix then landed for video only, described as "restricting to video tracks (where it really matters), due to reproducible crashes in the audio stack". So the audio-side leak is known and was left in place rather than risk the audio path.

The web SDK's approach has no direct port here, since capture is owned by the ADM shared across the PeerConnectionFactory rather than by the track. Muting the publication instead stops capture through PeerConnection.setAudioRecording(false), which flips recording_enabled_ on the factory-scoped AudioState and stops the ADM, releasing AudioRecord with no renegotiation and no change to the publication. AudioState::AddSendingStream checks the same flag, so a reconnect or republish won't silently reopen the mic while muted. Capture restarts on unmute.

Recording state is reconciled from the current publications on mute changes (including server-initiated ones), publish, unpublish, and applyOptions changes while published, with the desired state computed on the RTC thread so rapid toggles converge. Capture is shared by every local audio track, so it is only stopped while all published audio tracks are muted. Apps that don't opt in get no behavior change; a test asserts setAudioRecording is never called for them.

Also fixed: prepareForFullReconnect() clears the publications, so the unpublishTrack() calls in republishTracks() could never find and cancel the publication jobs, leaking each old audio publication's feature collector on every full reconnect and leaving it sending feature updates for a stale track sid. The reconciliation added here rides those same jobs, which is how it surfaced. The jobs are now cancelled when preparing for the reconnect.

Tests cover mute/unmute release and restore, zero native calls without opt-in, unpublish while muted, applyOptions toggling the flag in both directions while muted, and the all-tracks-muted rule with two simultaneous audio publications.

@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fe05d1c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
client-sdk-android Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@adrian-niculescu
adrian-niculescu force-pushed the stop-microphone-on-mute branch 2 times, most recently from a3b9bd8 to e60d2f6 Compare July 28, 2026 19:25
Also fixed local track publication jobs leaking on full reconnect.
@adrian-niculescu
adrian-niculescu force-pushed the stop-microphone-on-mute branch from e60d2f6 to fe05d1c Compare July 28, 2026 19:30
@adrian-niculescu
adrian-niculescu marked this pull request as ready for review July 28, 2026 19:38

@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: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

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.

Any way to release android microfone when mute

1 participant