diff --git a/ios/RCTWebRTC/AudioDeviceModuleObserver.h b/ios/RCTWebRTC/AudioDeviceModuleObserver.h index a1f415909..16f61851d 100644 --- a/ios/RCTWebRTC/AudioDeviceModuleObserver.h +++ b/ios/RCTWebRTC/AudioDeviceModuleObserver.h @@ -7,13 +7,14 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithWebRTCModule:(WebRTCModule *)module; -// Methods to receive results from JS -- (void)resolveEngineCreatedWithResult:(NSInteger)result; -- (void)resolveWillEnableEngineWithResult:(NSInteger)result; -- (void)resolveWillStartEngineWithResult:(NSInteger)result; -- (void)resolveDidStopEngineWithResult:(NSInteger)result; -- (void)resolveDidDisableEngineWithResult:(NSInteger)result; -- (void)resolveWillReleaseEngineWithResult:(NSInteger)result; +// Methods to receive results from JS. requestId echoes the id sent with the +// corresponding event so stale responses from timed-out rounds can be dropped. +- (void)resolveEngineCreatedWithRequestId:(NSInteger)requestId result:(NSInteger)result; +- (void)resolveWillEnableEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result; +- (void)resolveWillStartEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result; +- (void)resolveDidStopEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result; +- (void)resolveDidDisableEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result; +- (void)resolveWillReleaseEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result; @end diff --git a/ios/RCTWebRTC/AudioDeviceModuleObserver.m b/ios/RCTWebRTC/AudioDeviceModuleObserver.m index b619a5c37..a12b59772 100644 --- a/ios/RCTWebRTC/AudioDeviceModuleObserver.m +++ b/ios/RCTWebRTC/AudioDeviceModuleObserver.m @@ -1,8 +1,34 @@ #import "AudioDeviceModuleObserver.h" #import +#import NS_ASSUME_NONNULL_BEGIN +// Upper bound on how long a delegate callback parks the native audio thread +// waiting for JS to respond. The wait used to be DISPATCH_TIME_FOREVER, which +// deadlocks the app if the JS thread is itself blocked inside a synchronous +// bridge call (e.g. peerConnectionAddTransceiver) that is transitively waiting +// on this same audio operation. Bounding the wait turns that permanent freeze +// into a recoverable stall: on timeout we return the default 0 ("proceed") so +// the engine operation degrades gracefully. +// +// Caveat for willEnableEngine: returning 0 here lets the engine proceed even +// though the JS handler (which configures/activates the AVAudioSession) never +// completed. libwebrtc re-validates the session *category* after this callback +// but not that it was *activated*, so a timeout on willEnableEngine can start +// the engine against an unconfigured session. That degraded-but-recoverable +// outcome is deliberately preferred over the unrecoverable deadlock. +static const int64_t kJSResponseTimeoutSeconds = 2; + +static os_log_t ADMObserverLog(void) { + static os_log_t log; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + log = os_log_create("com.livekit.react-native-webrtc", "AudioDeviceModuleObserver"); + }); + return log; +} + @interface AudioDeviceModuleObserver () @property(weak, nonatomic) WebRTCModule *module; @@ -20,6 +46,15 @@ @interface AudioDeviceModuleObserver () @property(nonatomic, assign) NSInteger didDisableEngineResult; @property(nonatomic, assign) NSInteger willReleaseEngineResult; +// Monotonic id stamped on every event sent to JS, and the id of the round +// currently being awaited (0 = none). JS echoes the id back when it resolves; +// the observer only accepts a resolve whose id matches the in-flight round, so a +// late resolve from a round that already timed out cannot be misattributed to +// the next round. Both are guarded by @synchronized(self) since the send side +// runs on the native audio thread and the resolve side on the JS thread. +@property(nonatomic, assign) NSInteger requestIdSeq; +@property(nonatomic, assign) NSInteger awaitingRequestId; + @end @implementation AudioDeviceModuleObserver @@ -38,6 +73,57 @@ - (instancetype)initWithWebRTCModule:(WebRTCModule *)module { return self; } +#pragma mark - Bounded JS round-trip + +// Sends an event to JS and blocks the calling (native audio) thread until JS +// resolves the matching semaphore or kJSResponseTimeoutSeconds elapses. +// +// Each round is tagged with a unique requestId that JS echoes back on resolve; +// -resolveRequestId:... drops any resolve whose id does not match the in-flight +// round, so a late resolve from a previously timed-out round cannot signal this +// round's semaphore. The pre-send drain below is the remaining safety net: it +// clears a stray signal from the narrow case where a matching resolve raced the +// previous round past its timeout deadline (signalling after that round had +// already given up). No legitimate signal for *this* round can exist at drain +// time because the event has not been sent yet. +// +// Returns the JS-provided result on success, or 0 on timeout. +- (NSInteger)sendEventAndWaitWithName:(NSString *)eventName + body:(NSDictionary *)body + semaphore:(dispatch_semaphore_t)semaphore + resultBlock:(NSInteger (^)(void))resultBlock { + NSInteger requestId; + @synchronized(self) { + requestId = ++self.requestIdSeq; + self.awaitingRequestId = requestId; + } + + while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW) == 0) { + // Drain a stray signal left by a resolve that raced the previous round's timeout. + } + + NSMutableDictionary *payload = [body mutableCopy]; + payload[@"requestId"] = @(requestId); + [self.module sendEventWithName:eventName body:payload]; + + dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, kJSResponseTimeoutSeconds * NSEC_PER_SEC); + if (dispatch_semaphore_wait(semaphore, deadline) != 0) { + @synchronized(self) { + // Stop accepting this round's resolve; if it arrives now it is stale. + if (self.awaitingRequestId == requestId) { + self.awaitingRequestId = 0; + } + } + os_log_error(ADMObserverLog(), + "Timed out after %llds waiting for JS to respond to %{public}@; returning default 0", + (long long)kJSResponseTimeoutSeconds, + eventName); + return 0; + } + + return resultBlock(); +} + #pragma mark - RTCAudioDeviceModuleDelegate - (void)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule @@ -55,13 +141,15 @@ - (void)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule didCreateEngine:(AVAudioEngine *)engine { RCTLog(@"[AudioDeviceModuleObserver] Engine created - waiting for JS response"); - [self.module sendEventWithName:kEventAudioDeviceModuleEngineCreated body:@{}]; - - // Wait indefinitely for JS to respond - dispatch_semaphore_wait(self.engineCreatedSemaphore, DISPATCH_TIME_FOREVER); + NSInteger result = [self sendEventAndWaitWithName:kEventAudioDeviceModuleEngineCreated + body:@{} + semaphore:self.engineCreatedSemaphore + resultBlock:^NSInteger { + return self.engineCreatedResult; + }]; - RCTLog(@"[AudioDeviceModuleObserver] Engine created - JS returned: %ld", (long)self.engineCreatedResult); - return self.engineCreatedResult; + RCTLog(@"[AudioDeviceModuleObserver] Engine created - JS returned: %ld", (long)result); + return result; } - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule @@ -72,21 +160,22 @@ - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule isPlayoutEnabled, isRecordingEnabled); - [self.module sendEventWithName:kEventAudioDeviceModuleEngineWillEnable - body:@{ - @"isPlayoutEnabled" : @(isPlayoutEnabled), - @"isRecordingEnabled" : @(isRecordingEnabled), - }]; + NSInteger result = [self sendEventAndWaitWithName:kEventAudioDeviceModuleEngineWillEnable + body:@{ + @"isPlayoutEnabled" : @(isPlayoutEnabled), + @"isRecordingEnabled" : @(isRecordingEnabled), + } + semaphore:self.willEnableEngineSemaphore + resultBlock:^NSInteger { + return self.willEnableEngineResult; + }]; - // Wait indefinitely for JS to respond - dispatch_semaphore_wait(self.willEnableEngineSemaphore, DISPATCH_TIME_FOREVER); - - RCTLog(@"[AudioDeviceModuleObserver] Engine will enable - JS returned: %ld", (long)self.willEnableEngineResult); + RCTLog(@"[AudioDeviceModuleObserver] Engine will enable - JS returned: %ld", (long)result); AVAudioSession *audioSession = [AVAudioSession sharedInstance]; RCTLog(@"[AudioDeviceModuleObserver] Audio session category: %@", audioSession.category); - return self.willEnableEngineResult; + return result; } - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule @@ -97,17 +186,18 @@ - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule isPlayoutEnabled, isRecordingEnabled); - [self.module sendEventWithName:kEventAudioDeviceModuleEngineWillStart - body:@{ - @"isPlayoutEnabled" : @(isPlayoutEnabled), - @"isRecordingEnabled" : @(isRecordingEnabled), - }]; - - // Wait indefinitely for JS to respond - dispatch_semaphore_wait(self.willStartEngineSemaphore, DISPATCH_TIME_FOREVER); - - RCTLog(@"[AudioDeviceModuleObserver] Engine will start - JS returned: %ld", (long)self.willStartEngineResult); - return self.willStartEngineResult; + NSInteger result = [self sendEventAndWaitWithName:kEventAudioDeviceModuleEngineWillStart + body:@{ + @"isPlayoutEnabled" : @(isPlayoutEnabled), + @"isRecordingEnabled" : @(isRecordingEnabled), + } + semaphore:self.willStartEngineSemaphore + resultBlock:^NSInteger { + return self.willStartEngineResult; + }]; + + RCTLog(@"[AudioDeviceModuleObserver] Engine will start - JS returned: %ld", (long)result); + return result; } - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule @@ -118,17 +208,18 @@ - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule isPlayoutEnabled, isRecordingEnabled); - [self.module sendEventWithName:kEventAudioDeviceModuleEngineDidStop - body:@{ - @"isPlayoutEnabled" : @(isPlayoutEnabled), - @"isRecordingEnabled" : @(isRecordingEnabled), - }]; - - // Wait indefinitely for JS to respond - dispatch_semaphore_wait(self.didStopEngineSemaphore, DISPATCH_TIME_FOREVER); - - RCTLog(@"[AudioDeviceModuleObserver] Engine did stop - JS returned: %ld", (long)self.didStopEngineResult); - return self.didStopEngineResult; + NSInteger result = [self sendEventAndWaitWithName:kEventAudioDeviceModuleEngineDidStop + body:@{ + @"isPlayoutEnabled" : @(isPlayoutEnabled), + @"isRecordingEnabled" : @(isRecordingEnabled), + } + semaphore:self.didStopEngineSemaphore + resultBlock:^NSInteger { + return self.didStopEngineResult; + }]; + + RCTLog(@"[AudioDeviceModuleObserver] Engine did stop - JS returned: %ld", (long)result); + return result; } - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule @@ -139,29 +230,32 @@ - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule isPlayoutEnabled, isRecordingEnabled); - [self.module sendEventWithName:kEventAudioDeviceModuleEngineDidDisable - body:@{ - @"isPlayoutEnabled" : @(isPlayoutEnabled), - @"isRecordingEnabled" : @(isRecordingEnabled), - }]; - - // Wait indefinitely for JS to respond - dispatch_semaphore_wait(self.didDisableEngineSemaphore, DISPATCH_TIME_FOREVER); - - RCTLog(@"[AudioDeviceModuleObserver] Engine did disable - JS returned: %ld", (long)self.didDisableEngineResult); - return self.didDisableEngineResult; + NSInteger result = [self sendEventAndWaitWithName:kEventAudioDeviceModuleEngineDidDisable + body:@{ + @"isPlayoutEnabled" : @(isPlayoutEnabled), + @"isRecordingEnabled" : @(isRecordingEnabled), + } + semaphore:self.didDisableEngineSemaphore + resultBlock:^NSInteger { + return self.didDisableEngineResult; + }]; + + RCTLog(@"[AudioDeviceModuleObserver] Engine did disable - JS returned: %ld", (long)result); + return result; } - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule willReleaseEngine:(AVAudioEngine *)engine { RCTLog(@"[AudioDeviceModuleObserver] Engine will release - waiting for JS response"); - [self.module sendEventWithName:kEventAudioDeviceModuleEngineWillRelease body:@{}]; - - // Wait indefinitely for JS to respond - dispatch_semaphore_wait(self.willReleaseEngineSemaphore, DISPATCH_TIME_FOREVER); + NSInteger result = [self sendEventAndWaitWithName:kEventAudioDeviceModuleEngineWillRelease + body:@{} + semaphore:self.willReleaseEngineSemaphore + resultBlock:^NSInteger { + return self.willReleaseEngineResult; + }]; - RCTLog(@"[AudioDeviceModuleObserver] Engine will release - JS returned: %ld", (long)self.willReleaseEngineResult); - return self.willReleaseEngineResult; + RCTLog(@"[AudioDeviceModuleObserver] Engine will release - JS returned: %ld", (long)result); + return result; } - (NSInteger)audioDeviceModule:(RTCAudioDeviceModule *)audioDeviceModule @@ -192,34 +286,73 @@ - (void)audioDeviceModuleDidUpdateDevices:(RTCAudioDeviceModule *)audioDeviceMod #pragma mark - Resolve methods from JS -- (void)resolveEngineCreatedWithResult:(NSInteger)result { - self.engineCreatedResult = result; - dispatch_semaphore_signal(self.engineCreatedSemaphore); +// Applies a JS response only if its requestId matches the round currently being +// awaited. A non-matching id means the round already timed out and moved on, so +// the response is dropped without touching the result or signalling — preventing +// a stale value from being handed to a later round. +- (void)resolveRequestId:(NSInteger)requestId store:(void (^)(void))store semaphore:(dispatch_semaphore_t)semaphore { + @synchronized(self) { + if (requestId != self.awaitingRequestId) { + return; + } + self.awaitingRequestId = 0; + store(); + // Signal inside the lock so this signal is always posted before the next + // round's send-side @synchronized block can start. Otherwise a resolve + // preempted between releasing the lock and signalling could, across a round + // boundary, wake the next round and hand it this round's result. Keeping it + // inside the lock also lets the next round's pre-send drain reliably clear + // any stray signal left by a resolve that raced its own timeout. + dispatch_semaphore_signal(semaphore); + } +} + +- (void)resolveEngineCreatedWithRequestId:(NSInteger)requestId result:(NSInteger)result { + [self resolveRequestId:requestId + store:^{ + self.engineCreatedResult = result; + } + semaphore:self.engineCreatedSemaphore]; } -- (void)resolveWillEnableEngineWithResult:(NSInteger)result { - self.willEnableEngineResult = result; - dispatch_semaphore_signal(self.willEnableEngineSemaphore); +- (void)resolveWillEnableEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result { + [self resolveRequestId:requestId + store:^{ + self.willEnableEngineResult = result; + } + semaphore:self.willEnableEngineSemaphore]; } -- (void)resolveWillStartEngineWithResult:(NSInteger)result { - self.willStartEngineResult = result; - dispatch_semaphore_signal(self.willStartEngineSemaphore); +- (void)resolveWillStartEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result { + [self resolveRequestId:requestId + store:^{ + self.willStartEngineResult = result; + } + semaphore:self.willStartEngineSemaphore]; } -- (void)resolveDidStopEngineWithResult:(NSInteger)result { - self.didStopEngineResult = result; - dispatch_semaphore_signal(self.didStopEngineSemaphore); +- (void)resolveDidStopEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result { + [self resolveRequestId:requestId + store:^{ + self.didStopEngineResult = result; + } + semaphore:self.didStopEngineSemaphore]; } -- (void)resolveDidDisableEngineWithResult:(NSInteger)result { - self.didDisableEngineResult = result; - dispatch_semaphore_signal(self.didDisableEngineSemaphore); +- (void)resolveDidDisableEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result { + [self resolveRequestId:requestId + store:^{ + self.didDisableEngineResult = result; + } + semaphore:self.didDisableEngineSemaphore]; } -- (void)resolveWillReleaseEngineWithResult:(NSInteger)result { - self.willReleaseEngineResult = result; - dispatch_semaphore_signal(self.willReleaseEngineSemaphore); +- (void)resolveWillReleaseEngineWithRequestId:(NSInteger)requestId result:(NSInteger)result { + [self resolveRequestId:requestId + store:^{ + self.willReleaseEngineResult = result; + } + semaphore:self.willReleaseEngineSemaphore]; } @end diff --git a/ios/RCTWebRTC/WebRTCModule+RTCAudioDeviceModule.m b/ios/RCTWebRTC/WebRTCModule+RTCAudioDeviceModule.m index fad0f7c3f..0aa296bb9 100644 --- a/ios/RCTWebRTC/WebRTCModule+RTCAudioDeviceModule.m +++ b/ios/RCTWebRTC/WebRTCModule+RTCAudioDeviceModule.m @@ -229,33 +229,45 @@ @implementation WebRTCModule (RTCAudioDeviceModule) #pragma mark - Observer Delegate Response Methods -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveEngineCreated : (NSInteger)result) { - [self.audioDeviceModuleObserver resolveEngineCreatedWithResult:result]; +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveEngineCreated + : (NSInteger)requestId result + : (NSInteger)result) { + [self.audioDeviceModuleObserver resolveEngineCreatedWithRequestId:requestId result:result]; return nil; } -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveWillEnableEngine : (NSInteger)result) { - [self.audioDeviceModuleObserver resolveWillEnableEngineWithResult:result]; +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveWillEnableEngine + : (NSInteger)requestId result + : (NSInteger)result) { + [self.audioDeviceModuleObserver resolveWillEnableEngineWithRequestId:requestId result:result]; return nil; } -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveWillStartEngine : (NSInteger)result) { - [self.audioDeviceModuleObserver resolveWillStartEngineWithResult:result]; +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveWillStartEngine + : (NSInteger)requestId result + : (NSInteger)result) { + [self.audioDeviceModuleObserver resolveWillStartEngineWithRequestId:requestId result:result]; return nil; } -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveDidStopEngine : (NSInteger)result) { - [self.audioDeviceModuleObserver resolveDidStopEngineWithResult:result]; +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveDidStopEngine + : (NSInteger)requestId result + : (NSInteger)result) { + [self.audioDeviceModuleObserver resolveDidStopEngineWithRequestId:requestId result:result]; return nil; } -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveDidDisableEngine : (NSInteger)result) { - [self.audioDeviceModuleObserver resolveDidDisableEngineWithResult:result]; +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveDidDisableEngine + : (NSInteger)requestId result + : (NSInteger)result) { + [self.audioDeviceModuleObserver resolveDidDisableEngineWithRequestId:requestId result:result]; return nil; } -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveWillReleaseEngine : (NSInteger)result) { - [self.audioDeviceModuleObserver resolveWillReleaseEngineWithResult:result]; +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(audioDeviceModuleResolveWillReleaseEngine + : (NSInteger)requestId result + : (NSInteger)result) { + [self.audioDeviceModuleObserver resolveWillReleaseEngineWithRequestId:requestId result:result]; return nil; } diff --git a/src/AudioDeviceModuleEvents.ts b/src/AudioDeviceModuleEvents.ts index 5c63c212e..f830a1a46 100644 --- a/src/AudioDeviceModuleEvents.ts +++ b/src/AudioDeviceModuleEvents.ts @@ -15,6 +15,18 @@ export interface EngineStateEventData { isRecordingEnabled: boolean; } +/** + * Raw native event payload. Every engine event carries a `requestId` that must + * be echoed back to the matching resolve call so the native side can drop a + * response from a round that already timed out. This id is internal and is not + * surfaced to app-registered handlers. + */ +interface EngineEventPayload { + requestId: number; +} + +interface EngineStateEventPayload extends EngineEventPayload, EngineStateEventData {} + export type AudioDeviceModuleEventType = | 'speechActivity' | 'devicesUpdated'; @@ -50,7 +62,8 @@ class AudioDeviceModuleEventEmitter { public setupListeners() { if (Platform.OS !== 'android' && WebRTCModule) { // Setup handlers for blocking delegate methods - addListener(this, 'audioDeviceModuleEngineCreated', async () => { + addListener(this, 'audioDeviceModuleEngineCreated', async (event: unknown) => { + const { requestId } = event as EngineEventPayload; let result = 0; if (this.engineCreatedHandler) { @@ -62,14 +75,14 @@ class AudioDeviceModuleEventEmitter { } } - WebRTCModule.audioDeviceModuleResolveEngineCreated(result); + WebRTCModule.audioDeviceModuleResolveEngineCreated(requestId, result); }); addListener( this, 'audioDeviceModuleEngineWillEnable', async (event: unknown) => { - const { isPlayoutEnabled, isRecordingEnabled } = event as EngineStateEventData; + const { requestId, isPlayoutEnabled, isRecordingEnabled } = event as EngineStateEventPayload; let result = 0; if (this.willEnableEngineHandler) { @@ -81,7 +94,7 @@ class AudioDeviceModuleEventEmitter { } } - WebRTCModule.audioDeviceModuleResolveWillEnableEngine(result); + WebRTCModule.audioDeviceModuleResolveWillEnableEngine(requestId, result); }, ); @@ -89,7 +102,7 @@ class AudioDeviceModuleEventEmitter { this, 'audioDeviceModuleEngineWillStart', async (event: unknown) => { - const { isPlayoutEnabled, isRecordingEnabled } = event as EngineStateEventData; + const { requestId, isPlayoutEnabled, isRecordingEnabled } = event as EngineStateEventPayload; let result = 0; if (this.willStartEngineHandler) { @@ -101,7 +114,7 @@ class AudioDeviceModuleEventEmitter { } } - WebRTCModule.audioDeviceModuleResolveWillStartEngine(result); + WebRTCModule.audioDeviceModuleResolveWillStartEngine(requestId, result); }, ); @@ -109,7 +122,7 @@ class AudioDeviceModuleEventEmitter { this, 'audioDeviceModuleEngineDidStop', async (event: unknown) => { - const { isPlayoutEnabled, isRecordingEnabled } = event as EngineStateEventData; + const { requestId, isPlayoutEnabled, isRecordingEnabled } = event as EngineStateEventPayload; let result = 0; if (this.didStopEngineHandler) { @@ -121,7 +134,7 @@ class AudioDeviceModuleEventEmitter { } } - WebRTCModule.audioDeviceModuleResolveDidStopEngine(result); + WebRTCModule.audioDeviceModuleResolveDidStopEngine(requestId, result); }, ); @@ -129,7 +142,7 @@ class AudioDeviceModuleEventEmitter { this, 'audioDeviceModuleEngineDidDisable', async (event: unknown) => { - const { isPlayoutEnabled, isRecordingEnabled } = event as EngineStateEventData; + const { requestId, isPlayoutEnabled, isRecordingEnabled } = event as EngineStateEventPayload; let result = 0; if (this.didDisableEngineHandler) { @@ -141,11 +154,12 @@ class AudioDeviceModuleEventEmitter { } } - WebRTCModule.audioDeviceModuleResolveDidDisableEngine(result); + WebRTCModule.audioDeviceModuleResolveDidDisableEngine(requestId, result); }, ); - addListener(this, 'audioDeviceModuleEngineWillRelease', async () => { + addListener(this, 'audioDeviceModuleEngineWillRelease', async (event: unknown) => { + const { requestId } = event as EngineEventPayload; let result = 0; if (this.willReleaseEngineHandler) { @@ -157,7 +171,7 @@ class AudioDeviceModuleEventEmitter { } } - WebRTCModule.audioDeviceModuleResolveWillReleaseEngine(result); + WebRTCModule.audioDeviceModuleResolveWillReleaseEngine(requestId, result); }); } }