Skip to content

fix: handle mute state management along with callkit#24

Open
santhoshvai wants to merge 16 commits intomasterfrom
audio-track-recording-logic
Open

fix: handle mute state management along with callkit#24
santhoshvai wants to merge 16 commits intomasterfrom
audio-track-recording-logic

Conversation

@santhoshvai
Copy link
Member

@santhoshvai santhoshvai commented Jan 28, 2026

Overview

Change muteMode to inputMixer, this plays well along with callkit..

Here is the explanation of the modes

public enum MicrophoneMuteMode {
    /// Uses `AVAudioEngine`'s `isVoiceProcessingInputMuted` internally.
    /// This is fast, and muted speaker detection works. However, iOS will play a sound effect.
    /// Does not reconfigure the audio session on mute/unmute.
    case voiceProcessing

    /// Restarts the internal `AVAudioEngine` without mic input when muted.
    /// This is slower, and muted speaker detection does not work. No sound effect is played.
    /// Deactivates the audio session on mute and reconfigures it on unmute.
    case restart

    /// Simply mutes the output of the input mixer.
    /// The mic indicator remains on, and the internal `AVAudioEngine` continues running
    /// without reconfiguration.
    /// Does not reconfigure the audio session on mute/unmute.
    /// No sound effect is played.
    case inputMixer
}

Why?

voiceProcessing in our test, doesnt work will callkit well.

When we do a track.release() or track.diable() now:
It automatically triggers callkit setMuted to true and then soon it reverts to false..

Additional

I added RN event senders for AudioEngine, but its currently unused in Stream SDK. its for future, where we can listen to speaking while muted and audio levels from native level

Future

We would likely revert the change in the future. When we get the Audio LLC from Stream-Swift-Video-SDK

Summary by CodeRabbit

  • New Features

    • Added audio device module events for monitoring speech activity, engine state transitions, audio processing updates, and device changes.
    • Added frame cryption state change events.
    • Introduced typed event listeners for audio device monitoring on iOS/macOS platforms.
  • Chores

    • Version bump to 137.1.2-alpha.1.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

This PR introduces an audio device module event observation system for iOS/macOS WebRTC. A new AudioDeviceModuleObserver class is created to listen for RTCAudioDeviceModule delegate callbacks and relay them via React Native's event bridge. The AudioDeviceModule is updated to accept and forward events to this observer. A TypeScript event emitter (AudioDeviceModuleEventEmitter) is added to provide type-safe JavaScript listeners for these events. Additionally, new event constants for frame cryption and audio device module lifecycle events are defined, with cryptor-related storage properties added to WebRTCModule.

Changes

Cohort / File(s) Summary
Audio Device Observer Implementation
ios/RCTWebRTC/AudioDeviceModuleObserver.h, ios/RCTWebRTC/AudioDeviceModuleObserver.m
New Objective-C observer class implementing RTCAudioDeviceModuleDelegate protocol. Initializer associates observer with WebRTCModule and relays 9 distinct audio device events (speech activity, engine lifecycle, devices/audio processing state updates) to React Native bridge via predefined event constants.
AudioDeviceModule Integration
ios/RCTWebRTC/Utils/AudioDeviceModule/AudioDeviceModule.swift
Modified to accept delegateObserver parameter and forward all RTCAudioDeviceModuleDelegate callbacks to it, enabling secondary event propagation pathway. Adds delegateObserver property and updates both primary and convenience initializers.
WebRTCModule Core Updates
ios/RCTWebRTC/WebRTCModule.h, ios/RCTWebRTC/WebRTCModule.m
Adds 10 new event constants for audio device module and frame cryption events. Introduces 3 new mutable dictionaries (frameCryptors, keyProviders, dataPacketCryptors) as properties. Initializes AudioDeviceModuleObserver and wires it to AudioDeviceModule. Extends supportedEvents array with new audio device event names.
Media Stream Track Dimension Detection
ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m
Adjusts dimension detector lifecycle: adds dimension detection initialization for cloned local video tracks via addLocalVideoTrackDimensionDetection; consolidates cleanup logic; optimizes disabled-state assignment to only trigger when necessary.
TypeScript Event Emitter
src/AudioDeviceModuleEvents.ts
New module providing typed event emitter wrapper for audio device module events. Defines discriminated union types for event data (SpeechActivityEvent, EngineStateEvent, AudioProcessingStateEvent). Implements idempotent setupListeners and provides typed addListener methods for 9 distinct events. Exports singleton audioDeviceModuleEvents instance.
Module Export
src/index.ts
Initializes audioDeviceModuleEvents at startup via setupListeners() and adds it to public API exports.
Version Update
package.json
Version bumped from 137.1.1 to 137.1.2-alpha.1.

Sequence Diagram

sequenceDiagram
    participant Audio as RTCAudioDeviceModule
    participant Observer as AudioDeviceModuleObserver
    participant Module as AudioDeviceModule
    participant RTC as WebRTCModule
    participant Bridge as React Native Bridge
    participant JS as JavaScript Code

    Audio->>Observer: onSpeechActivity(isSpeaking)
    Observer->>RTC: sendEvent(kEventAudioDeviceModuleSpeechActivity, data)
    RTC->>Bridge: emit event
    Bridge->>JS: audioDeviceModuleEvents.onSpeechActivity()

    Audio->>Observer: onEngineCreated()
    Observer->>RTC: sendEvent(kEventAudioDeviceModuleEngineCreated)
    RTC->>Bridge: emit event
    Bridge->>JS: audioDeviceModuleEvents.onEngineCreated()

    Audio->>Module: configure/state queries
    Module->>Observer: forward delegate calls
    Observer->>RTC: sendEvent(kEventAudioDeviceModule*)
    RTC->>Bridge: emit event
    Bridge->>JS: listener callback
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • GetStream/react-native-webrtc#25: Modifies AudioDeviceModule.swift to change voice-processing bypass and stereo behavior while also touching the same delegate callback forwarding paths as this PR.
  • GetStream/react-native-webrtc#23: Updates AudioDeviceModule implementation with RTCAudioDeviceModuleDelegate callback integration and observer pattern, directly related to the core mechanism introduced here.

Suggested reviewers

  • greenfrvr

Poem

🐰 Audio events now hop along the bridge,
From native whispers to JavaScript's ridge,
Observers listen, observers relay,
Speech and engines dance the observer's way! 🎵
A crypto-state joins the festive parade,
Our audio module's grand masquerade! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title claims to address 'mute state management along with callkit', but the changeset primarily introduces AudioDeviceModuleObserver for WebRTC event bridging and audio device module callbacks. While mute state management exists in WebRTCModule+RTCMediaStream.m, the core changes focus on audio device module integration and event emission rather than CallKit interoperability improvements. Revise the title to accurately reflect the main changes: e.g., 'feat: add AudioDeviceModuleObserver for WebRTC audio device event bridging' or 'feat: implement audio device module delegate for event emission'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch audio-track-recording-logic

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@santhoshvai santhoshvai changed the title fix: mute state management along with callkit fix: handle mute state management along with callkit Jan 28, 2026
@santhoshvai santhoshvai marked this pull request as ready for review February 6, 2026 14:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@ios/RCTWebRTC/WebRTCModule.h`:
- Around line 51-54: The three properties frameCryptors, keyProviders, and
dataPacketCryptors on WebRTCModule (RTCFrameCryptor, RTCFrameCryptorKeyProvider,
RTCDataPacketCryptor) are declared but never used; either remove these unused
NSMutableDictionary properties from WebRTCModule.h and any corresponding
initialization in WebRTCModule.m, or retain them but add a clear TODO/FIXME
comment above each declaration referencing a short issue ID/URL that tracks the
planned implementation and intended usage so future readers know why they
remain; update any related Obj-C .m initialization code to match the change
(remove initialization if deleting properties, or keep/init and reference the
TODO if retaining).
🧹 Nitpick comments (8)
ios/RCTWebRTC/Utils/AudioDeviceModule/AudioDeviceModule.swift (2)

354-357: Debug log prefix "Callingx" looks like a leftover from testing.

Consider using a consistent log tag (e.g., "AudioDeviceModule") or removing these NSLog calls if they're not intended for production.

🔧 Suggested fix
-            NSLog("[Callingx | AudioDeviceModule] speechActivityStarted")
+            NSLog("[AudioDeviceModule] speechActivityStarted")
             subject.send(.speechActivityStarted)
         case .ended:
-            NSLog("[Callingx | AudioDeviceModule] speechActivityEnded")
+            NSLog("[AudioDeviceModule] speechActivityEnded")

263-263: Commented-out code should be removed or documented.

Line 263 contains a commented-out mute mode call. The surrounding doc comment (lines 257-262) explains the trade-offs of different modes, but the actual commented-out call is dead code. Either remove it or add a brief note explaining why it's retained for reference.

ios/RCTWebRTC/WebRTCModule.m (1)

122-124: Frame cryptor dictionaries are initialized but not cleaned up in dealloc.

Existing dictionaries _localTracks and _localStreams are explicitly cleaned up in dealloc (lines 38-41). Consider whether _frameCryptors, _keyProviders, and _dataPacketCryptors need similar cleanup — particularly if their values hold references to native WebRTC objects that should be explicitly released or closed.

ios/RCTWebRTC/AudioDeviceModuleObserver.m (2)

25-37: Inconsistent log ordering relative to event dispatch.

In this method the log comes after the event dispatch, but in didCreateEngine (line 40) the log comes before the dispatch. This inconsistency is harmless but makes log-based debugging harder — if sendEventWithName: were to crash, some events would leave a log trail and others wouldn't.

Consider placing all RCTLog calls before the bridge dispatch across all delegate methods for consistent traceability.


135-153: Configuration hooks silently succeed — consider documenting intent.

These two methods log the format and return 0 (success) without performing any configuration or forwarding events to JS. If this is intentional (letting the default pipeline handle configuration), a brief comment clarifying that would help future maintainers understand these aren't accidental stubs.

Suggested inline comment
 - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule
                         engine:(AVAudioEngine *)engine
       configureInputFromSource:(nullable AVAudioNode *)source
                  toDestination:(AVAudioNode *)destination
                     withFormat:(AVAudioFormat *)format
                        context:(NSDictionary *)context {
+    // No custom input configuration needed; returning 0 lets the default pipeline proceed.
     RCTLog(@"[AudioDeviceModuleObserver] Configure input - format: %@", format);
     return 0;
 }
src/AudioDeviceModuleEvents.ts (3)

36-45: setupListeners() doesn't set up listeners — consider renaming for clarity.

The method initializes the NativeEventEmitter but doesn't register any listeners. Callers might be confused by the name. Something like initialize() or setup() would better convey the actual behavior.

Also, the platform guard Platform.OS !== 'android' is broader than "iOS/macOS only" (it would pass on 'web', 'windows', etc.). This is safe because the && WebRTCModule check prevents initialization when the native module isn't present, but a comment or a more precise check (e.g., ['ios', 'macos'].includes(Platform.OS)) would match the stated intent.


50-144: Consider a private helper to reduce repetition across all listener methods.

All nine addXxxListener methods repeat the same null-check-and-throw guard. A small private method would eliminate this duplication.

♻️ Example DRY refactor
+    private getEmitter(): NativeEventEmitter {
+        if (!this.eventEmitter) {
+            throw new Error('AudioDeviceModuleEvents is only available on iOS/macOS. Did you call setupListeners()?');
+        }
+        return this.eventEmitter;
+    }
+
     addSpeechActivityListener(listener: (data: SpeechActivityEventData) => void) {
-        if (!this.eventEmitter) {
-            throw new Error('AudioDeviceModuleEvents is only available on iOS/macOS');
-        }
-
-        return this.eventEmitter.addListener('audioDeviceModuleSpeechActivity', listener);
+        return this.getEmitter().addListener('audioDeviceModuleSpeechActivity', listener);
     }

Apply the same pattern to all other addXxxListener methods.


50-56: Throwing on Android may be surprising — consumers must guard calls.

Every addXxxListener method throws if the emitter is null (i.e., on Android or if setupListeners() wasn't called). Since the PR description says these events are "currently unused by Stream SDK," this may bite early adopters who call addSpeechActivityListener without a platform check and crash on Android.

Returning a no-op subscription instead of throwing would make cross-platform consumption safer:

// no-op pattern
return { remove: () => {} } as EmitterSubscription;

Alternatively, the error message could mention calling setupListeners() first, since that's also a failure path.

Comment on lines +51 to +54
@property(nonatomic, strong) NSMutableDictionary<NSString *, RTCFrameCryptor *> *frameCryptors;
@property(nonatomic, strong) NSMutableDictionary<NSString *, RTCFrameCryptorKeyProvider *> *keyProviders;
@property(nonatomic, strong) NSMutableDictionary<NSString *, RTCDataPacketCryptor *> *dataPacketCryptors;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for usages of the new dictionary properties
rg -n --type=objc 'frameCryptors|keyProviders|dataPacketCryptors' -g '!WebRTCModule.h' -g '!WebRTCModule.m'

Repository: GetStream/react-native-webrtc

Length of output: 55


🏁 Script executed:

# Also check WebRTCModule.m to see if these are used there
rg -n --type=objc 'frameCryptors|keyProviders|dataPacketCryptors' ios/RCTWebRTC/WebRTCModule.m

Repository: GetStream/react-native-webrtc

Length of output: 244


🏁 Script executed:

# Get an overview of the file structure to understand the scope
git ls-files ios/RCTWebRTC/ | head -20

Repository: GetStream/react-native-webrtc

Length of output: 824


🏁 Script executed:

# Check context around initialization in WebRTCModule.m (lines 115-135)
sed -n '115,135p' ios/RCTWebRTC/WebRTCModule.m

Repository: GetStream/react-native-webrtc

Length of output: 976


🏁 Script executed:

# Look for any method names that might suggest usage (frameCryptor, keyProvider, dataPacketCryptor methods)
rg -n --type=objc 'frameCryptor|keyProvider|dataPacketCryptor' ios/RCTWebRTC/WebRTCModule.m | head -30

Repository: GetStream/react-native-webrtc

Length of output: 244


🏁 Script executed:

# Check if there are any TODOs or comments in WebRTCModule.m related to encryption
rg -n -i 'todo|fixme|future|upcoming|planned' ios/RCTWebRTC/WebRTCModule.m | grep -i 'crypt\|encrypt'

Repository: GetStream/react-native-webrtc

Length of output: 55


Remove unused cryptor dictionaries or add tracking for planned implementation.

The three dictionaries (frameCryptors, keyProviders, dataPacketCryptors) are initialized but never accessed anywhere in the codebase, and there are no TODO/FIXME comments indicating planned usage. Either remove these unused properties or add an issue/comment to track the intended implementation.

🤖 Prompt for AI Agents
In `@ios/RCTWebRTC/WebRTCModule.h` around lines 51 - 54, The three properties
frameCryptors, keyProviders, and dataPacketCryptors on WebRTCModule
(RTCFrameCryptor, RTCFrameCryptorKeyProvider, RTCDataPacketCryptor) are declared
but never used; either remove these unused NSMutableDictionary properties from
WebRTCModule.h and any corresponding initialization in WebRTCModule.m, or retain
them but add a clear TODO/FIXME comment above each declaration referencing a
short issue ID/URL that tracks the planned implementation and intended usage so
future readers know why they remain; update any related Obj-C .m initialization
code to match the change (remove initialization if deleting properties, or
keep/init and reference the TODO if retaining).

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.

3 participants