feat: Audio device module level mute channel method added - #79
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds ADM-level microphone mute APIs, Apple-specific serial ADM queues, and idempotent audio suspend/resume handling. Web stubs expose unsupported APIs, while iOS/macOS handlers validate ADM availability and marshal recording, mute, stereo, and lifecycle operations through each factory’s queue. ChangesMicrophone mute control
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Dart
participant MethodChannel
participant NativePeerConnectionFactory
participant RTCAudioDeviceModule
Dart->>MethodChannel: invoke appleAdmSetMicrophoneMuted with factoryId and muted
MethodChannel->>NativePeerConnectionFactory: resolve factory and enqueue request
NativePeerConnectionFactory->>RTCAudioDeviceModule: set microphone mute state
RTCAudioDeviceModule-->>NativePeerConnectionFactory: return ADM result
NativePeerConnectionFactory-->>Dart: return success or error
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@android/src/main/java/io/getstream/webrtc/flutter/MethodCallHandlerImpl.java`:
- Around line 1585-1597: Update the admSetMicrophoneMuted executor task to use
the same lifecycle serialization/guard as disposePeerConnectionFactoryHandler,
preventing adm.setMicrophoneMute from running after factory removal or racing
with nf.dispose()/adm.release(). Wrap the guarded ADM operation in try/catch and
post result.error with the caught failure on mainHandler; post
result.success(null) only after a successful mute operation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f4b87652-5afc-481a-a19d-b4a805c27b49
📒 Files selected for processing (6)
CHANGELOG.mdandroid/src/main/java/io/getstream/webrtc/flutter/MethodCallHandlerImpl.javaios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.mlib/src/native/native_peer_connection_factory.dartlib/src/web/native_peer_connection_factory.dartmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m
renefloor
left a comment
There was a problem hiding this comment.
Some minor things:
Issues
- iOS/macOS: rapid toggles can be applied out of order � FlutterWebRTCPlugin.m:1970 (ios), :1729 (macos)
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) is a concurrent queue, so two blocks have no ordering guarantee. setMicrophoneMuted(true) immediately followed by setMicrophoneMuted(false) (push-to-talk, a fast UI toggle, a debounce that fires twice) can land in reverse � and the mic then stays in the wrong state indefinitely, with both Dart futures resolving successfully. Android is not affected: executor is Executors.newSingleThreadExecutor() (MethodCallHandlerImpl.java:138), so calls are serialized there.
A mute toggle is much more order-sensitive than the start/stop-recording handlers this was copied from. Worth a dedicated serial queue for ADM mute ops:
// file scope
static dispatch_queue_t kADMMuteQueue; // dispatch_queue_create("�adm.mute", DISPATCH_QUEUE_SERIAL)
- iOS/macOS: a nil ADM reports success � FlutterWebRTCPlugin.m:1968
audioDeviceModule is declared nullable in NativePeerConnectionFactory.h:21, and the plugin nil-checks it elsewhere (FlutterWebRTCPlugin.m:2107, :2120). Here, [nil setMicrophoneMuted:] returns 0, which the handler reads as success � so result(nil) fires and the caller believes the mic is muted while nothing happened. For a privacy-facing mute, silent-success is the wrong failure mode; this should be a FlutterError. Android is fine � nf.adm is @nonnull final.
- Is the mute state re-applied after the audio unit is rebuilt? (worth verifying before merge)
Nothing stores the requested mute state, so any path that tears down and rebuilds the VPIO unit drops it. Concretely, suspendAudioPeerConnectionFactory � resumeAudioPeerConnectionFactory (FlutterWebRTCPlugin.m:1990�2035) stops recording and calls initRecording/startRecording again; audio route changes may do the same internally. If the unit property doesn't survive that, the user ends up with a hot mic after a suspend/resume cycle or a route change � a bad failure direction. Android is safe here, since setMicrophoneMute sets a sticky flag on WebRtcAudioRecord rather than a hardware property.
If it doesn't survive, the fix fits the existing pattern well � NativePeerConnectionFactory already carries wasPlayingBeforeSuspend / wasRecordingBeforeSuspend for exactly this kind of restore, so a microphoneMuted property re-applied after startRecording would slot right in.
- No way to read the state back
There's no isMicrophoneMuted getter, so Dart can't detect drift if #3 turns out to be real.
isVoiceProcessingEnabled / isStereoPlayouta read-side handler. Not a blocker, butcheap to add alongside.
Minor
- muted.boolValue on a nil NSNumber (Fluttently yields NO. Not reachable through theDart API, but the channel is directly callable, and Android is explicit about it (muted != null && muted). An explicit nil � error would match.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m`:
- Around line 2033-2037: Capture suspend/resume state only within the serialized
audio-manager queue blocks. In
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m
lines 2033-2037, read adm.isPlaying/isRecording and assign
nf.wasPlayingBeforeSuspend/nf.wasRecordingBeforeSuspend inside the suspend
block; at lines 2064-2070, read those restoration flags and apply audio
configuration inside the resume block. Apply the equivalent changes in
macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m
lines 1804-1808 and 1835-1837, using the suspend/resume queue blocks and
preserving existing behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5bf7b478-360c-404e-89a6-c9022adad3e5
📒 Files selected for processing (6)
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.mios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.mios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.hmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.mmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.mmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h
There was a problem hiding this comment.
♻️ Duplicate comments (1)
android/src/main/java/io/getstream/webrtc/flutter/MethodCallHandlerImpl.java (1)
1585-1599: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winSerialize mute operations with factory disposal and always complete the result.
The queued task can call
adm.setMicrophoneMuteafterdisposePeerConnectionFactoryHandlerhas removed the factory andnf.dispose()has released its ADM. Also, any exception escapes the executor task, so Dart receives neither success nor an error and the returned Future can hang.Run the mute operation and
nf.dispose()/adm.release()under the same executor or lifecycle lock, and catch failures to postresult.error(...)on the main thread. This is the same unresolved issue raised in the previous review.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@android/src/main/java/io/getstream/webrtc/flutter/MethodCallHandlerImpl.java` around lines 1585 - 1599, Update the admSetMicrophoneMuted handler and the corresponding disposePeerConnectionFactoryHandler lifecycle path to serialize mute changes with factory disposal and ADM release using the same executor or lifecycle lock. Ensure the queued mute task cannot access a disposed NativePeerConnectionFactory or JavaAudioDeviceModule, and catch operation failures so the main-thread callback always completes with result.error(...) instead of leaving the Future unresolved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In
`@android/src/main/java/io/getstream/webrtc/flutter/MethodCallHandlerImpl.java`:
- Around line 1585-1599: Update the admSetMicrophoneMuted handler and the
corresponding disposePeerConnectionFactoryHandler lifecycle path to serialize
mute changes with factory disposal and ADM release using the same executor or
lifecycle lock. Ensure the queued mute task cannot access a disposed
NativePeerConnectionFactory or JavaAudioDeviceModule, and catch operation
failures so the main-thread callback always completes with result.error(...)
instead of leaving the Future unresolved.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7eb569fe-bb0f-4c58-8d15-fbc4e6557693
📒 Files selected for processing (7)
CHANGELOG.mdandroid/src/main/java/io/getstream/webrtc/flutter/MethodCallHandlerImpl.javaandroid/src/main/java/io/getstream/webrtc/flutter/NativePeerConnectionFactory.javaios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.mlib/src/native/native_peer_connection_factory.dartlib/src/web/native_peer_connection_factory.dartmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m
🚧 Files skipped from review as they are similar to previous changes (3)
- lib/src/web/native_peer_connection_factory.dart
- macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m
- ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m`:
- Around line 1753-1758: Update the muted validation in the
appleAdmSetMicrophoneMuted handler to require a non-nil CFBoolean value using
CFBooleanGetTypeID(), rather than accepting any NSNumber. Preserve the existing
appleAdmSetMicrophoneMuted FlutterError response and return for invalid
payloads, while allowing only true/false values to reach muted.boolValue.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4998f8d0-a40b-4193-858f-87fa00b29676
📒 Files selected for processing (7)
CHANGELOG.mdios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.mios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.hlib/src/native/native_peer_connection_factory.dartlib/src/web/native_peer_connection_factory.dartmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.mmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h
🚧 Files skipped from review as they are similar to previous changes (4)
- macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h
- ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h
- lib/src/web/native_peer_connection_factory.dart
- ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h (1)
60-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
audioSuspendedis publicly mutable with no stated threading contract. The suspend/resume flags are only safe when accessed fromadmQueue, but neither header says so.
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h#L60-L63: extend the doc comment to state that this flag must be read/written onadmQueue.macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h#L60-L63: mirror the same doc note.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h` around lines 60 - 63, The audioSuspended property documentation lacks its threading contract. Update the doc comment in ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h lines 60-63 and mirror the same note in macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h lines 60-63, stating that audioSuspended must be read and written on admQueue.lib/src/native/native_peer_connection_factory.dart (1)
234-241: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDoc note about ambiguous
falseno longer matches the native handler.The iOS/macOS handlers (
appleAdmIsMicrophoneMuted) now return aFlutterErrorwhen the ADM is missing rather thanfalse, so an unqueryable ADM surfaces as a thrown error, notfalse. Consider dropping or rewording the "cannot distinguish" paragraph so callers don't over-defend against a case that no longer occurs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/src/native/native_peer_connection_factory.dart` around lines 234 - 241, Update the documentation for the native microphone mute readback method around setMicrophoneMuted/appleAdmIsMicrophoneMuted to remove or reword the paragraph claiming that an unavailable ADM is reported as false. Document that missing or unqueryable ADM state surfaces as a thrown FlutterError, while false represents the native handler’s non-muted result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m`:
- Around line 2112-2115: Move the audioConfigSnapshot handling into the admQueue
block that restores audio state, and guard both setAppleAudioConfiguration: and
the restore operations with nf.audioSuspended. Ensure configuration occurs
immediately before restoration within the same queued block, and does not run
when no audio was suspended.
In
`@ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m`:
- Around line 100-107: Replace the blocking dispatch_sync teardown around adm in
NativePeerConnectionFactory.m with a deadlock-safe approach: either assert the
ADM queue-specific context before synchronous execution or dispatch the
stopRecording/stopPlayout block asynchronously while retaining adm. Apply the
identical change at
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m
lines 100-107 and
macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m
lines 95-102, preserving exception handling.
---
Nitpick comments:
In
`@ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h`:
- Around line 60-63: The audioSuspended property documentation lacks its
threading contract. Update the doc comment in
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h
lines 60-63 and mirror the same note in
macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h
lines 60-63, stating that audioSuspended must be read and written on admQueue.
In `@lib/src/native/native_peer_connection_factory.dart`:
- Around line 234-241: Update the documentation for the native microphone mute
readback method around setMicrophoneMuted/appleAdmIsMicrophoneMuted to remove or
reword the paragraph claiming that an unavailable ADM is reported as false.
Document that missing or unqueryable ADM state surfaces as a thrown
FlutterError, while false represents the native handler’s non-muted result.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3002f542-de63-4bf6-b4f2-c1c30ca29cc2
📒 Files selected for processing (11)
CHANGELOG.mdios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.mios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.mios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.hlib/src/helper.dartlib/src/native/ios/audio_management.dartlib/src/native/native_peer_connection_factory.dartlib/src/web/native_peer_connection_factory.dartmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.mmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.mmacos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/include/stream_webrtc_flutter/NativePeerConnectionFactory.h
| NSDictionary* audioConfigSnapshot = nf.audioConfigSnapshot; | ||
| BOOL restorePlaying = nf.wasPlayingBeforeSuspend; | ||
| BOOL restoreRecording = nf.wasRecordingBeforeSuspend; | ||
| if (audioConfigSnapshot != nil) { | ||
| [AudioUtils setAppleAudioConfiguration:audioConfigSnapshot]; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Audio-session reconfiguration still runs outside admQueue and even when nothing was suspended.
setAppleAudioConfiguration: executes on the calling thread ahead of the queued restore, so it is not ordered against pending ADM work, and it runs unconditionally — including when nf.audioSuspended is NO and the block restores nothing. Move it inside the queued block under the nf.audioSuspended check so configuration and restore stay atomic and idempotent.
🔧 Proposed fix
- NSDictionary* audioConfigSnapshot = nf.audioConfigSnapshot;
- if (audioConfigSnapshot != nil) {
- [AudioUtils setAppleAudioConfiguration:audioConfigSnapshot];
- }
dispatch_async(nf.admQueue, ^{
if (nf.audioSuspended) {
+ NSDictionary* audioConfigSnapshot = nf.audioConfigSnapshot;
+ if (audioConfigSnapshot != nil) {
+ [AudioUtils setAppleAudioConfiguration:audioConfigSnapshot];
+ }
BOOL restorePlaying = nf.wasPlayingBeforeSuspend;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| NSDictionary* audioConfigSnapshot = nf.audioConfigSnapshot; | |
| BOOL restorePlaying = nf.wasPlayingBeforeSuspend; | |
| BOOL restoreRecording = nf.wasRecordingBeforeSuspend; | |
| if (audioConfigSnapshot != nil) { | |
| [AudioUtils setAppleAudioConfiguration:audioConfigSnapshot]; | |
| } | |
| dispatch_async(nf.admQueue, ^{ | |
| if (nf.audioSuspended) { | |
| NSDictionary* audioConfigSnapshot = nf.audioConfigSnapshot; | |
| if (audioConfigSnapshot != nil) { | |
| [AudioUtils setAppleAudioConfiguration:audioConfigSnapshot]; | |
| } | |
| BOOL restorePlaying = nf.wasPlayingBeforeSuspend; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/FlutterWebRTCPlugin.m`
around lines 2112 - 2115, Move the audioConfigSnapshot handling into the
admQueue block that restores audio state, and guard both
setAppleAudioConfiguration: and the restore operations with nf.audioSuspended.
Ensure configuration occurs immediately before restoration within the same
queued block, and does not run when no audio was suspended.
| dispatch_sync(_admQueue, ^{ | ||
| @try { | ||
| [adm stopRecording]; | ||
| [adm stopPlayout]; | ||
| } @catch (NSException* e) { | ||
| NSLog(@"[NativePeerConnectionFactory] stopRecording/stopPlayout failed: %@", e); | ||
| } | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Blocking dispatch_sync on the factory's own serial ADM queue during dispose. Both platform implementations tail the ADM queue synchronously to run the final stops, which self-deadlocks if teardown is ever reached from ADM-queue work (observer callback, completion hop, or a future refactor).
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m#L100-L107: guard with a queue-specific key assertion, or enqueue the stops viadispatch_asyncwhile the block retainsadm.macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m#L95-L102: apply the identical change.
📍 Affects 2 files
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m#L100-L107(this comment)macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m#L95-L102
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m`
around lines 100 - 107, Replace the blocking dispatch_sync teardown around adm
in NativePeerConnectionFactory.m with a deadlock-safe approach: either assert
the ADM queue-specific context before synchronous execution or dispatch the
stopRecording/stopPlayout block asynchronously while retaining adm. Apply the
identical change at
ios/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m
lines 100-107 and
macos/stream_webrtc_flutter/Sources/stream_webrtc_flutter/NativePeerConnectionFactory.m
lines 95-102, preserving exception handling.
Summary by CodeRabbit