Resolve the audio device module through the registered WebRTC plugin instance - #1212
hiroshihorie wants to merge 2 commits into
Conversation
…instance LiveKitPlugin read the WebRTC plugin's process-wide singleton on every call. That singleton is assigned in each plugin init, so a second Flutter engine in the process replaced the UI engine's instance with one that never runs the initialize channel call and never creates a peer connection factory. From then on startLocalRecording, setEngineAvailability, mute mode and the track lookups all failed with "audio device module is unavailable" for the rest of the process, which is how setMicrophoneEnabled(true) ended in AudioProcessingException(platformUnavailable) after backgrounding. Capture the WebRTC plugin instance at the first registration, as the Android plugin already does, and resolve every lookup through it while it owns the factory. Fall back to the process singleton otherwise. Also correct two comments that claimed accessing peerConnectionFactory creates it; only the initialize channel call does. Fixes #1206, fixes #1176
This comment has been minimized.
This comment has been minimized.
| if let registered, registered.peerConnectionFactory != nil { | ||
| return registered |
There was a problem hiding this comment.
🔴 Destroyed engine remains WebRTC owner
After the first initialized engine is destroyed, webrtcPlugin keeps returning its retained plugin because its factory remains non-null. Replacement-engine audio and track operations target the detached engine.
Learn more
The captured plugin is held by a process-wide strong reference with no reset path. Destroying its Flutter engine does not clear peerConnectionFactory, so the non-null test continues selecting that detached instance. Later registrations update flutter_webrtc's singleton, but this getter never reaches the singleton while the old factory remains allocated. Calls such as audio processing, visualizer lookup, and audio-device control then use the old instance's tracks and factory.
Example: Engine A registers both plugins, initializes WebRTC, and later shuts down. Engine B then registers and initializes WebRTC. A mute-mode call from Engine B resolves Engine A's retained audio device module instead of Engine B's module.
Recommended fix: Make ownership follow plugin lifecycle rather than permanently retaining the first factory-bearing instance. Store a weak reference and clear or replace it when its engine detaches, or use an upstream API that returns the flutter_webrtc instance registered with the same registrar. Ensure replacement occurs atomically and preserves the multi-engine case where a still-live primary engine owns the active factory.
Was this helpful? React with 👍 or 👎 to provide feedback.
Fixes #1206, fixes #1176.
Problem
LiveKitPlugin.swiftresolved the audio device module on every call throughFlutterWebRTCPlugin.sharedSingleton(). flutter_webrtc assigns that singleton in every plugin init, so a second Flutter engine in the process (background isolates, CallKit, add-to-app) replaced the UI engine's instance with one that never creates a peer connection factory. Every audio device module call then failed withaudio device module is unavailablefor the rest of the process, which is howsetMicrophoneEnabled(true)ended inAudioProcessingException(platformUnavailable)after backgrounding.Android is not affected:
LiveKitPlugin.ktcaptures the flutter_webrtc instance once at construction.Fix
peerConnectionFactorycreates it.Works with the currently pinned flutter_webrtc. Upstream half: flutter-webrtc/flutter-webrtc#2186.
Verification
Example app on the iOS 27 simulator with the pinned flutter_webrtc, a second
FlutterEnginespawned from the AppDelegate 3 s after launch, andgetMicrophoneMuteModecalled over thelivekit_clientchannel at 2, 6 and 9 s.PlatformException(getMicrophoneMuteMode, audio device module is unavailable)The flutter_webrtc singleton moved to the second engine's instance in both runs; only the lookup changed.