feat(audio): add AudioManager microphone mute mode API#1124
feat(audio): add AudioManager microphone mute mode API#1124hiroshihorie wants to merge 4 commits into
Conversation
Adds AudioManager.setMicrophoneMuteMode/getMicrophoneMuteMode with a LiveKit-owned MicrophoneMuteMode enum (voiceProcessing, restart, inputMixer), mirroring the Swift SDK. Controls how the AVAudioEngine audio device module mutes mic input on iOS/macOS. No-op elsewhere, including for unknown, so get/set round-trips are safe cross-platform. voiceProcessing (the default) plays the platform mute tone, inputMixer and restart mute silently. The mode applies to LiveKit's own mute path: disabling a published local audio track drives the engine-level microphone mute. Implemented on LiveKit's own plugin and method channel. The audio device module API is public in the WebRTC-SDK pod, so no flutter_webrtc change or release is needed.
6165bde to
f4d7b62
Compare
| static Future<void> setMicrophoneMuteMode(String mode) async { | ||
| await channel.invokeMethod<void>( | ||
| 'setMicrophoneMuteMode', | ||
| <String, dynamic>{'mode': mode}, | ||
| ); | ||
| } |
There was a problem hiding this comment.
📝 Info: Deliberate error-handling asymmetry between set and get native methods
The Native.setMicrophoneMuteMode at lib/src/support/native.dart:240-245 deliberately does NOT wrap the channel call in a try-catch, allowing PlatformExceptions to propagate to the caller. This is explicitly documented and intentional (a failed set means the caller's assumption about muting behavior is wrong). However, this is a departure from the pattern used by nearly every other method in the Native class (e.g. setAndroidSpeakerphoneOn, configureAudio, etc.), which all swallow errors with logger.warning. The Native.getMicrophoneMuteMode at lib/src/support/native.dart:250-257 follows the conventional swallow-and-return-null pattern. This asymmetry is justified by the doc comment but reviewers should be aware that callers of AudioManager.setMicrophoneMuteMode must handle exceptions, unlike most other AudioManager methods.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fair point. 4ec7888 documents the throwing contract on the public AudioManager.setMicrophoneMuteMode, matching the wording used for setEngineAvailability in #1127. The asymmetry itself stays: the setter propagates because a silently failed mode change would leave muting behavior different from what the caller selected, and the getter falls back to unknown since a failed read is harmless.
The setter deliberately propagates native errors, unlike the getter which falls back to unknown. A silently failed mode change would leave muting behavior different from what the caller selected. This mirrors the Swift SDK's throwing API.
| DispatchQueue.global(qos: .userInitiated).async { | ||
| let admResult = adm.setMuteMode(mode) | ||
| DispatchQueue.main.async { | ||
| if admResult == 0 { | ||
| result(nil) | ||
| } else { | ||
| result(FlutterError( | ||
| code: "setMicrophoneMuteMode", | ||
| message: "Audio engine returned error code: \(admResult)", | ||
| details: nil | ||
| )) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🔍 Background thread dispatch for setMuteMode may interact with concurrent calls
In shared_swift/LiveKitPlugin.swift:476-489, setMuteMode is dispatched to DispatchQueue.global(qos: .userInitiated) because it can rebuild the audio engine. If setMicrophoneMuteMode is called rapidly in succession (e.g. user toggling settings), multiple setMuteMode calls could execute concurrently on different global queue threads. The safety of this depends on whether RTCAudioDeviceModule.setMuteMode is internally serialized. This matches the pattern used by handleStartLocalRecording (line 448-461), so it's consistent with existing code, but worth noting if the underlying ADM method isn't thread-safe.
Was this helpful? React with 👍 or 👎 to provide feedback.
Description
Adds
AudioManager.setMicrophoneMuteMode/getMicrophoneMuteModewith a LiveKit-ownedMicrophoneMuteModeenum (voiceProcessing,restart,inputMixer), mirroring the Swift SDK'sAudioManager.microphoneMuteMode.Background
On iOS/macOS the AVAudioEngine-based ADM defaults to
voiceProcessingmuting, which plays the platform's mute/unmute sound effect (see flutter-webrtc/flutter-webrtc#2098). Apps can selectinputMixerorrestartto mute silently.The mode applies to LiveKit's own mute path: disabling a published local audio track drives the engine-level microphone mute in webrtc (
WebRtcVoiceSendChannel::MuteStream->adm->SetMicrophoneMute). For the fastest silent mute toggling, combineinputMixerwithAudioCaptureOptions(stopAudioCaptureOnMute: false)(documented on the API).Implementation notes
LiveKitPlugin.swiftcalls the ADM'smuteMode/setMuteMode:directly (public API in the WebRTC-SDK pod already linked), so this does not depend on any flutter_webrtc change or release. The companion flutter-webrtc PR (feat: expose microphone mute mode and ADM-level microphone mute flutter-webrtc/flutter-webrtc#2105) exposes the same control to plain flutter-webrtc users independently.restart, notrestartEngine).unknown, soset(await get())round-trips safely in cross-platform code.unknown.Testing
dart analyze lib testclean, all 315 tests pass.