@cloudflare/realtimekit-react-native 0.2.1 → 0.2.2-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/android/build.gradle +0 -2
  2. package/android/src/main/java/com/cloudflare/realtimekit/CoreModule.java +19 -0
  3. package/android/src/main/java/com/cloudflare/realtimekit/ForegroundService.java +2 -4
  4. package/android/src/main/java/com/cloudflare/realtimekit/RTKHelperModule.java +1 -2
  5. package/android/src/main/java/com/cloudflare/realtimekit/RTKLogger.java +116 -0
  6. package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/AppRTCBluetoothManager.java +75 -74
  7. package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/AppRTCUtils.java +10 -9
  8. package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallManagerModule.java +175 -872
  9. package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallProximityManager.java +9 -8
  10. package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallWakeLockUtils.java +8 -7
  11. package/ios/BroadcastEventEmitter.m +9 -0
  12. package/ios/Core.h +9 -1
  13. package/ios/Core.m +42 -0
  14. package/ios/RNInCallManager.m +639 -700
  15. package/ios/RTKLogger.h +51 -0
  16. package/ios/RTKLogger.m +107 -0
  17. package/ios/RTKRNBackgroundTimer.m +8 -7
  18. package/ios/RTKRNPermissionHandlerNotifications.m +12 -0
  19. package/ios/RTKRNPermissions.m +24 -1
  20. package/ios/RTKScreensharePickerView.m +6 -0
  21. package/ios/screenshare/RTKScreenshareHandler.swift +1 -1
  22. package/ios/screenshare/RTKScreenshareUploader.swift +3 -3
  23. package/ios/screenshare/RTKSocketConnection.swift +8 -8
  24. package/lib/commonjs/LocalMediaHandler.js +82 -24
  25. package/lib/commonjs/LocalMediaHandler.js.map +1 -1
  26. package/lib/commonjs/LocalMediaUtils.js +60 -5
  27. package/lib/commonjs/LocalMediaUtils.js.map +1 -1
  28. package/lib/commonjs/NativeAudioManager.js +7 -3
  29. package/lib/commonjs/NativeAudioManager.js.map +1 -1
  30. package/lib/commonjs/utils/version.js +1 -1
  31. package/lib/commonjs/utils/version.js.map +1 -1
  32. package/lib/module/LocalMediaHandler.js +82 -24
  33. package/lib/module/LocalMediaHandler.js.map +1 -1
  34. package/lib/module/LocalMediaUtils.js +60 -5
  35. package/lib/module/LocalMediaUtils.js.map +1 -1
  36. package/lib/module/NativeAudioManager.js +7 -3
  37. package/lib/module/NativeAudioManager.js.map +1 -1
  38. package/lib/module/utils/version.js +1 -1
  39. package/lib/module/utils/version.js.map +1 -1
  40. package/lib/typescript/LocalMediaHandler.d.ts +1 -0
  41. package/lib/typescript/LocalMediaUtils.d.ts +1 -0
  42. package/lib/typescript/NativeAudioManager.d.ts +1 -0
  43. package/lib/typescript/utils/version.d.ts +1 -1
  44. package/package.json +2 -4
@@ -1,4 +1,5 @@
1
1
  #import "RNInCallManager.h"
2
+ #import "RTKLogger.h"
2
3
  #import <React/RCTBridge.h>
3
4
  #import <React/RCTConvert.h>
4
5
  #import <React/RCTEventDispatcher.h>
@@ -14,16 +15,7 @@
14
15
  bool hasListeners;
15
16
 
16
17
  AVAudioSession *_audioSession;
17
- AVAudioPlayer *_ringtone;
18
- AVAudioPlayer *_ringback;
19
- AVAudioPlayer *_busytone;
20
-
21
- NSURL *_defaultRingtoneUri;
22
- NSURL *_defaultRingbackUri;
23
- NSURL *_defaultBusytoneUri;
24
- NSURL *_bundleRingtoneUri;
25
- NSURL *_bundleRingbackUri;
26
- NSURL *_bundleBusytoneUri;
18
+ BOOL _isUpdatingAudioRoute;
27
19
 
28
20
  //BOOL isProximitySupported;
29
21
  BOOL _proximityIsNear;
@@ -53,7 +45,6 @@
53
45
  BOOL _audioSessionInitialized;
54
46
  int _forceSpeakerOn;
55
47
  int _turnBluetoothOn;
56
- NSString *_media;
57
48
  }
58
49
 
59
50
  - (BOOL)canSendEvents {
@@ -84,16 +75,6 @@ RCT_EXPORT_MODULE(InCallManager)
84
75
  if (self = [super init]) {
85
76
  _currentDevice = [UIDevice currentDevice];
86
77
  _audioSession = [AVAudioSession sharedInstance];
87
- _ringtone = nil;
88
- _ringback = nil;
89
- _busytone = nil;
90
-
91
- _defaultRingtoneUri = nil;
92
- _defaultRingbackUri = nil;
93
- _defaultBusytoneUri = nil;
94
- _bundleRingtoneUri = nil;
95
- _bundleRingbackUri = nil;
96
- _bundleBusytoneUri = nil;
97
78
 
98
79
  _proximityIsNear = NO;
99
80
 
@@ -111,15 +92,18 @@ RCT_EXPORT_MODULE(InCallManager)
111
92
  _audioSessionMediaServicesWereResetObserver = nil;
112
93
  _audioSessionSilenceSecondaryAudioHintObserver = nil;
113
94
 
114
- _incallAudioMode = AVAudioSessionModeVoiceChat;
95
+ _incallAudioMode = AVAudioSessionModeVideoChat;
115
96
  _incallAudioCategory = AVAudioSessionCategoryPlayAndRecord;
116
97
  _origAudioCategory = nil;
117
98
  _origAudioMode = nil;
118
99
  _audioSessionInitialized = NO;
119
100
  _forceSpeakerOn = 0;
120
- _media = @"audio";
101
+ _turnBluetoothOn = 0;
102
+ _userSelectedAudioRoute = nil;
103
+ _currentAudioRoute = nil;
104
+ _isUpdatingAudioRoute = NO;
121
105
 
122
- // NSLog(@"RNInCallManager.init(): initialized");
106
+ [RTKLogger debug:@"RNInCallManager.init(): initialized"];
123
107
  }
124
108
  return self;
125
109
  }
@@ -139,29 +123,27 @@ RCT_EXPORT_MODULE(InCallManager)
139
123
  @"Bluetooth"];
140
124
  }
141
125
 
142
- RCT_EXPORT_METHOD(start:(NSString *)mediaType
143
- auto:(BOOL)_auto
144
- ringbackUriType:(NSString *)ringbackUriType)
126
+ RCT_EXPORT_METHOD(start)
145
127
  {
146
- // NSLog(@"🤟 Starting InCall manager 🤟 ");
128
+ [RTKLogger debug:@"Starting InCall manager"];
147
129
  if (_audioSessionInitialized) {
148
130
  return;
149
131
  }
150
- _media = mediaType;
151
-
152
- // --- auto is always true on ios
153
- // if ([_media isEqualToString:@"video"]) {
154
- // _incallAudioMode = AVAudioSessionModeVideoChat;
155
- // } else {
156
- // _incallAudioMode = AVAudioSessionModeVoiceChat;
157
- // }
158
- _incallAudioMode = AVAudioSessionModeVoiceChat;
159
- // NSLog(@"🧑‍💻 RNInCallManager.start() start InCallManager. media=%@, type=%@, mode=%@", _media, _media, _incallAudioMode);
132
+
133
+ // Default to video call behavior (speaker)
134
+ _incallAudioMode = AVAudioSessionModeVideoChat;
135
+
136
+ [RTKLogger debug:@"RNInCallManager.start() start InCallManager. mode=%@", _incallAudioMode];
160
137
  [self storeOriginalAudioSetup];
161
- _forceSpeakerOn = 0;
138
+
139
+ // Default to speaker for video calls
140
+ _forceSpeakerOn = 1;
141
+
162
142
  [self startAudioSessionNotification];
143
+
144
+ // Always set category with AllowBluetooth to keep Bluetooth visible
163
145
  [self audioSessionSetCategory:_incallAudioCategory
164
- options:0
146
+ options:AVAudioSessionCategoryOptionAllowBluetooth
165
147
  callerMemo:NSStringFromSelector(_cmd)];
166
148
  [self audioSessionSetMode:_incallAudioMode
167
149
  callerMemo:NSStringFromSelector(_cmd)];
@@ -169,52 +151,89 @@ RCT_EXPORT_METHOD(start:(NSString *)mediaType
169
151
  options:0
170
152
  callerMemo:NSStringFromSelector(_cmd)];
171
153
 
172
- if (ringbackUriType.length > 0) {
173
- // NSLog(@"RNInCallManager.start() play ringback first. type=%@", ringbackUriType);
174
- [self startRingback:ringbackUriType];
154
+ // Route to speaker by default
155
+ // Don't call routeAudioFromSpeakerphone() to avoid setting category twice
156
+ NSError *error = nil;
157
+ [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
158
+ if (error) {
159
+ [RTKLogger error:@"Failed to route to speaker on start: %@", error];
160
+ } else {
161
+ [RTKLogger debug:@"Routed to speaker on start (Bluetooth stays visible)"];
175
162
  }
163
+
164
+ // Send initial device event
165
+ [self sendAudioDeviceEvent];
166
+
167
+ // Give iOS time to discover Bluetooth devices and send initial device list
168
+ // This is needed for setup screens where audio tracks aren't acquired yet
169
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
170
+ dispatch_get_main_queue(), ^{
171
+ [RTKLogger debug:@"Sending delayed audio device event for setup screen"];
172
+ [self sendAudioDeviceEvent];
173
+ });
176
174
 
177
- // if ([_media isEqualToString:@"audio"]) {
178
- // [self startProximitySensor];
179
- // }
180
175
  [self setKeepScreenOn:YES];
181
176
  _audioSessionInitialized = YES;
182
- //self.debugAudioSession()
183
- // NSLog(@"🤟 Audio Session Initalized 🤟 ");
177
+
178
+ [RTKLogger debug:@"Audio Session Initialized with default speaker routing"];
179
+ }
180
+
181
+ RCT_EXPORT_METHOD(setVoiceCallMode)
182
+ {
183
+ [RTKLogger debug:@"Setting voice call mode (earpiece default)"];
184
+
185
+ // Switch to audio call behavior (earpiece)
186
+ _incallAudioMode = AVAudioSessionModeVoiceChat;
187
+ _forceSpeakerOn = 0;
188
+
189
+ // Apply the mode change to the audio session
190
+ [self audioSessionSetMode:_incallAudioMode
191
+ callerMemo:NSStringFromSelector(_cmd)];
192
+
193
+ // Route to earpiece
194
+ [self routeAudioFromEarpiece];
195
+
196
+ [RTKLogger debug:@"Voice call mode set - audio routed to earpiece"];
184
197
  }
185
198
 
186
199
  RCT_EXPORT_METHOD(chooseAudioRoute: (NSString *)audioRoute Promise:(RCTPromiseResolveBlock)resolve
187
200
  reject:(RCTPromiseRejectBlock)reject) {
188
- // NSLog(@"💂‍♂️💂‍♂️ choseAudio Route Called");
189
- // NSLog(@"👨‍💻 Selected Audio Route: %@", audioRoute);
190
- BOOL success;
201
+ [RTKLogger debug:@"choseAudio Route Called"];
202
+ [RTKLogger debug:@"Selected Audio Route: %@, Current Route: %@", audioRoute, _currentAudioRoute];
203
+ BOOL success = NO;
191
204
  // SPEAKER_PHONE,
192
205
  // WIRED_HEADSET,
193
206
  // EARPIECE,
194
207
  // BLUETOOTH,
195
208
  // NONE
196
209
  // TODO: Create enums for AudioRoute
197
- _userSelectedAudioRoute = audioRoute;
198
- if (![_userSelectedAudioRoute isEqualToString:_currentAudioRoute]) {
199
- if ([audioRoute isEqual: @"SPEAKER_PHONE"] || [audioRoute isEqualToString:@"speaker"]) {
200
- _forceSpeakerOn = 1;
201
- success = [self routeAudioFromSpeakerphone];
202
- } else if ([audioRoute isEqualToString:@"WIRED_HEADSET"] || [audioRoute isEqualToString:@"wired"]) {
203
- _forceSpeakerOn = 0;
204
- success = [self routeAudioFromEarpiece];
205
- } else if ([audioRoute isEqual: @"EARPIECE"] || [audioRoute isEqualToString:@"earpiece"]) {
206
- _forceSpeakerOn = 0;
207
- success = [self routeAudioFromEarpiece];
208
- } else if ([audioRoute isEqualToString:@"BLUETOOTH"] || [audioRoute isEqualToString:@"bluetooth"]) {
209
- _forceSpeakerOn = 0;
210
- success = [self routeAudioFromBluetooth];
211
- }
212
- //TODO: Better Error Handling
213
- if (success) {
214
- resolve(@"🎉 Audio Route sucessfully changed");
215
- } else {
216
- reject(@"😭 error_code", @"getAudioUriJS() failed in chooseAudioRoute", RCTErrorWithMessage(@"Failed to change audio route"));
217
- }
210
+ // This ensures we override any iOS auto-routing behavior
211
+ if ([audioRoute isEqual: @"SPEAKER_PHONE"] || [audioRoute isEqualToString:@"speaker"]) {
212
+ _forceSpeakerOn = 1;
213
+ _userSelectedAudioRoute = @"speaker";
214
+ success = [self routeAudioFromSpeakerphone];
215
+ } else if ([audioRoute isEqualToString:@"WIRED_HEADSET"] || [audioRoute isEqualToString:@"wired"]) {
216
+ _forceSpeakerOn = 0;
217
+ _userSelectedAudioRoute = @"wired";
218
+ success = [self routeAudioFromEarpiece];
219
+ } else if ([audioRoute isEqual: @"EARPIECE"] || [audioRoute isEqualToString:@"earpiece"]) {
220
+ _forceSpeakerOn = 0;
221
+ _userSelectedAudioRoute = @"earpiece";
222
+ success = [self routeAudioFromEarpiece];
223
+ } else if ([audioRoute isEqualToString:@"BLUETOOTH"] || [audioRoute isEqualToString:@"bluetooth"]) {
224
+ _forceSpeakerOn = 0;
225
+ _userSelectedAudioRoute = @"bluetooth";
226
+ success = [self routeAudioFromBluetooth];
227
+ }
228
+
229
+ if (success) {
230
+ _currentAudioRoute = _userSelectedAudioRoute;
231
+ // Send event to notify JavaScript of the route change
232
+ // This ensures immediate feedback even before iOS route change notifications fire
233
+ [self sendAudioDeviceEvent];
234
+ resolve(@"🎉 Audio Route sucessfully changed");
235
+ } else {
236
+ reject(@"😭 error_code", @"getAudioUriJS() failed in chooseAudioRoute", RCTErrorWithMessage(@"Failed to change audio route"));
218
237
  }
219
238
  }
220
239
 
@@ -229,36 +248,27 @@ RCT_EXPORT_METHOD(stop:(NSString *)busytoneUriType)
229
248
  return;
230
249
  }
231
250
 
232
- [self stopRingback];
233
-
234
- if (busytoneUriType.length > 0 && [self startBusytone:busytoneUriType]) {
235
- // play busytone first, and call this func again when finish
236
- // NSLog(@"RNInCallManager.stop(): play busytone before stop");
237
- return;
238
- } else {
239
- // NSLog(@"RNInCallManager.stop(): stop InCallManager");
240
- [self restoreOriginalAudioSetup];
241
- [self stopBusytone];
242
- [self stopProximitySensor];
243
- [self audioSessionSetActive:NO
244
- options:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
245
- callerMemo:NSStringFromSelector(_cmd)];
246
- [self setKeepScreenOn:NO];
247
- [self stopAudioSessionNotification];
248
- [[NSNotificationCenter defaultCenter] removeObserver:self];
249
- _forceSpeakerOn = 0;
250
- _audioSessionInitialized = NO;
251
- }
251
+ [RTKLogger debug:@"RNInCallManager.stop(): stop InCallManager"];
252
+ [self restoreOriginalAudioSetup];
253
+ [self stopProximitySensor];
254
+ [self audioSessionSetActive:NO
255
+ options:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
256
+ callerMemo:NSStringFromSelector(_cmd)];
257
+ [self setKeepScreenOn:NO];
258
+ [self stopAudioSessionNotification];
259
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
260
+ _forceSpeakerOn = 0;
261
+ _audioSessionInitialized = NO;
252
262
  }
253
263
 
254
264
  RCT_EXPORT_METHOD(turnScreenOn)
255
265
  {
256
- // NSLog(@"RNInCallManager.turnScreenOn(): ios doesn't support turnScreenOn()");
266
+ [RTKLogger debug:@"RNInCallManager.turnScreenOn(): ios doesn't support turnScreenOn()"];
257
267
  }
258
268
 
259
269
  RCT_EXPORT_METHOD(turnScreenOff)
260
270
  {
261
- // NSLog(@"RNInCallManager.turnScreenOff(): ios doesn't support turnScreenOff()");
271
+ [RTKLogger debug:@"RNInCallManager.turnScreenOff(): ios doesn't support turnScreenOff()"];
262
272
  }
263
273
 
264
274
  RCT_EXPORT_METHOD(setFlashOn:(BOOL)enable
@@ -284,7 +294,7 @@ RCT_EXPORT_METHOD(setFlashOn:(BOOL)enable
284
294
 
285
295
  RCT_EXPORT_METHOD(setKeepScreenOn:(BOOL)enable)
286
296
  {
287
- // NSLog(@"RNInCallManager.setKeepScreenOn(): enable: %@", enable ? @"YES" : @"NO");
297
+ [RTKLogger debug:@"RNInCallManager.setKeepScreenOn(): enable: %@", enable ? @"YES" : @"NO"];
288
298
  dispatch_async(dispatch_get_main_queue(), ^{
289
299
  [[UIApplication sharedApplication] setIdleTimerDisabled:enable];
290
300
  });
@@ -304,102 +314,185 @@ RCT_EXPORT_METHOD(setSpeakerphoneOn:(BOOL)enable)
304
314
  - (BOOL)routeAudioFromBluetooth {
305
315
  BOOL success;
306
316
  NSError *error = nil;
307
- _audioSession = [AVAudioSession sharedInstance];
308
- success = [_audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
309
- withOptions:AVAudioSessionCategoryOptionAllowBluetooth
310
- error:&error];
311
- if (!success) {
312
- NSLog(@"📕 Cannot set category due to error: %@", error);
313
- }
314
- success = [_audioSession setMode:AVAudioSessionModeVoiceChat error: &error];
315
- if (!success) {
316
- NSLog(@"📕 Cannot set mode due to error: %@", error);
317
- }
317
+ _audioSession = [AVAudioSession sharedInstance];
318
+
319
+ [RTKLogger debug:@"Routing audio to Bluetooth"];
320
+ [RTKLogger debug:@"Current route before: %@", [_audioSession currentRoute]];
321
+
322
+ // Switch back to VideoChat mode if we were in VoiceChat mode (from earpiece)
323
+ // VideoChat mode works fine with Bluetooth
324
+ if ([_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
325
+ [self audioSessionSetMode:AVAudioSessionModeVideoChat
326
+ callerMemo:NSStringFromSelector(_cmd)];
327
+ _incallAudioMode = AVAudioSessionModeVideoChat;
328
+ [RTKLogger debug:@"Switched back to VideoChat mode for Bluetooth routing"];
329
+ }
330
+
331
+ // Re-enable Bluetooth routing (already enabled, but ensure it's set)
332
+ [self audioSessionSetCategory:_incallAudioCategory
333
+ options:AVAudioSessionCategoryOptionAllowBluetooth
334
+ callerMemo:NSStringFromSelector(_cmd)];
335
+ [RTKLogger debug:@"Bluetooth routing enabled"];
336
+
337
+ // Clear audio port override first to allow Bluetooth routing
338
+ [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
339
+ if (error) {
340
+ [RTKLogger error:@"Failed to clear output port override: %@", error];
341
+ return false;
342
+ } else {
343
+ [RTKLogger debug:@"Cleared output port override"];
344
+ }
345
+
346
+ // Find and set Bluetooth as preferred input
318
347
  for (AVAudioSessionPortDescription* input in [_audioSession availableInputs]) {
319
348
  if ([[input portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
320
349
  [[input portType] isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
321
350
  [[input portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
322
- // Turn off current input
323
- @try {
324
- [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
325
- } @catch (NSException *e) {
326
- // NSLog(@"😭 Unable to override audio port none in bluetooth %@", e.reason);
327
- return false;
328
- }
329
- // set current input to bluetooth
351
+
330
352
  @try {
331
- [_audioSession setPreferredInput:input error:&error];
332
- } @catch (NSException *e) {
333
- // NSLog(@"😭 Unable to set audio input port to bluetooth%@", e.reason);
334
- // Set Prefeered route to speaker
335
- [self routeAudioFromSpeakerphone];
353
+ [RTKLogger debug:@"Setting preferred input to Bluetooth: %@", input.portName];
354
+
355
+ // Setting Bluetooth as preferred input automatically routes output to Bluetooth too
356
+ success = [_audioSession setPreferredInput:input error:&error];
357
+ if (!success || error) {
358
+ [RTKLogger error:@"Unable to set Bluetooth as preferred input: %@", error];
359
+ return false;
360
+ }
361
+
362
+ [RTKLogger debug:@"Set preferred input successfully"];
363
+
364
+ // Verify the route changed
365
+ AVAudioSessionRouteDescription* newRoute = [_audioSession currentRoute];
366
+ [RTKLogger debug:@"Current route after: %@", newRoute];
367
+
368
+ BOOL isRoutedToBluetooth = NO;
369
+ for (AVAudioSessionPortDescription* output in newRoute.outputs) {
370
+ if ([[output portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
371
+ [[output portType] isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
372
+ [[output portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
373
+ isRoutedToBluetooth = YES;
374
+ [RTKLogger debug:@"✅ Successfully routed audio to Bluetooth: %@", output.portName];
375
+ break;
376
+ }
377
+ }
378
+
379
+ if (!isRoutedToBluetooth) {
380
+ [RTKLogger warning:@"⚠️ setPreferredInput succeeded but route didn't change to Bluetooth"];
381
+ }
382
+
383
+ return true;
384
+ } @catch (NSException *e) {
385
+ [RTKLogger error:@"Exception setting Bluetooth routing: %@", e.reason];
336
386
  return false;
337
387
  }
338
388
  }
339
389
  }
340
- return true;
390
+
391
+ [RTKLogger warning:@"No Bluetooth device found in available inputs"];
392
+ return false;
341
393
  }
342
394
 
343
395
 
344
396
 
345
397
  - (BOOL)routeAudioFromEarpiece {
346
- BOOL success;
347
398
  NSError *error = nil;
348
- // NSLog(@"Routing audio via Earpiece");
399
+ [RTKLogger debug:@"Routing audio via Earpiece"];
349
400
  @try {
401
+ // VideoChat mode defaults to speaker even with overrideOutputAudioPort:None
402
+ if (![_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
403
+ [self audioSessionSetMode:AVAudioSessionModeVoiceChat
404
+ callerMemo:NSStringFromSelector(_cmd)];
405
+ _incallAudioMode = AVAudioSessionModeVoiceChat;
406
+ [RTKLogger debug:@"Switched to VoiceChat mode for earpiece routing"];
407
+ }
408
+
409
+ // Always keep Bluetooth enabled in category so it stays visible
410
+ // Check current category options to see if we need to update
411
+ AVAudioSessionCategoryOptions currentOptions = [_audioSession categoryOptions];
412
+ BOOL hasAllowBluetooth = (currentOptions & AVAudioSessionCategoryOptionAllowBluetooth) != 0;
413
+
414
+ if (!hasAllowBluetooth) {
415
+ [RTKLogger warning:@"Bluetooth was disabled! Re-enabling it now"];
416
+ [self audioSessionSetCategory:_incallAudioCategory
417
+ options:AVAudioSessionCategoryOptionAllowBluetooth
418
+ callerMemo:NSStringFromSelector(_cmd)];
419
+ } else {
420
+ [RTKLogger debug:@"Bluetooth already enabled in category options"];
421
+ }
350
422
 
351
- success = [_audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
352
- if (!success) NSLog(@"Cannot set category due to error: %@", error);
353
- success = [_audioSession setMode:AVAudioSessionModeVoiceChat error:&error];
354
- if (!success) NSLog(@"Cannot set mode due to error: %@", error);
355
- [_audioSession setPreferredOutputNumberOfChannels:0 error:nil];
356
- if (!success) NSLog(@"Port override failed due to: %@", error);
357
- [_audioSession overrideOutputAudioPort:[AVAudioSessionPortBuiltInReceiver intValue] error:&error];
358
- success = [_audioSession setActive:YES error:&error];
359
- if (!success) {
360
- NSLog(@"Audio session override failed: %@", error);
423
+ // Override to None routes to earpiece in VoiceChat mode
424
+ // Note: We do NOT change preferred input - if Bluetooth mic is active, it stays active
425
+ [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&error];
426
+ if (error) {
427
+ [RTKLogger error:@"Port override failed due to: %@", error];
361
428
  return false;
362
429
  }
363
- else {
364
- // NSLog(@"AudioSession override is successful ");
365
- return true;
366
- }
430
+
431
+ [RTKLogger debug:@"Successfully routed audio to Earpiece (Bluetooth remains visible)"];
432
+ return true;
367
433
  } @catch (NSException *e) {
368
- NSLog(@"Error occurred while routing audio via Earpiece: %@", e.reason);
434
+ [RTKLogger error:@"Error occurred while routing audio via Earpiece: %@", e.reason];
369
435
  return false;
370
436
  }
371
437
  }
372
438
 
373
439
  - (BOOL)routeAudioFromSpeakerphone
374
440
  {
375
- // NSLog(@"📘 Routing audio via Loudspeaker");
376
- BOOL success;
441
+ [RTKLogger debug:@"Routing audio via Loudspeaker"];
377
442
  NSError *error = nil;
378
443
  @try {
379
- success = [_audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
380
- withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker
381
- error:&error];
382
- if (!success) {
383
- NSLog(@"📕 Cannot set category due to error: %@", error);
384
- }
385
- success = [_audioSession setMode:AVAudioSessionModeVoiceChat error: &error];
386
- if (!success) {
387
- NSLog(@"📕 Cannot set mode due to error: %@", error);
444
+ // Switch back to VideoChat mode if we were in VoiceChat mode (from earpiece)
445
+ // VideoChat mode is better for speaker as it's optimized for video calls
446
+ if ([_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
447
+ [self audioSessionSetMode:AVAudioSessionModeVideoChat
448
+ callerMemo:NSStringFromSelector(_cmd)];
449
+ _incallAudioMode = AVAudioSessionModeVideoChat;
450
+ [RTKLogger debug:@"Switched back to VideoChat mode for speaker routing"];
388
451
  }
389
- [_audioSession overrideOutputAudioPort:[AVAudioSessionPortBuiltInSpeaker intValue] error: &error];
390
- if (!success) {
391
- NSLog(@"📕 Port override failed due to: %@", error);
452
+
453
+ // Always keep Bluetooth enabled in category so it stays visible
454
+ // Check current category options to see if we need to update
455
+ AVAudioSessionCategoryOptions currentOptions = [_audioSession categoryOptions];
456
+ BOOL hasAllowBluetooth = (currentOptions & AVAudioSessionCategoryOptionAllowBluetooth) != 0;
457
+
458
+ if (!hasAllowBluetooth) {
459
+ [RTKLogger warning:@"Bluetooth was disabled! Re-enabling it now"];
460
+ [self audioSessionSetCategory:_incallAudioCategory
461
+ options:AVAudioSessionCategoryOptionAllowBluetooth
462
+ callerMemo:NSStringFromSelector(_cmd)];
463
+ } else {
464
+ [RTKLogger debug:@"Bluetooth already enabled in category options"];
392
465
  }
393
- success = [_audioSession setActive:YES error:&error];
394
- if (!success) {
395
- NSLog(@"📕 Audio session override failed: %@", error);
466
+
467
+ // Override to Speaker - this forces output to speaker
468
+ // Note: We do NOT change preferred input - if Bluetooth mic is active, it stays active
469
+ [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
470
+ if (error) {
471
+ [RTKLogger error:@"Port override failed due to: %@", error];
396
472
  return false;
397
- } else {
398
- // NSLog(@"📗 Sucessfully routed audio from Loudspeaker");
399
- return true;
400
473
  }
474
+
475
+ // Verify the route actually changed to speaker
476
+ AVAudioSessionRouteDescription* currentRoute = [_audioSession currentRoute];
477
+ BOOL isRoutedToSpeaker = NO;
478
+
479
+ for (AVAudioSessionPortDescription* output in currentRoute.outputs) {
480
+ if ([[output portType] isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
481
+ isRoutedToSpeaker = YES;
482
+ [RTKLogger debug:@"✅ Successfully routed audio to Speaker: %@", output.portName];
483
+ break;
484
+ }
485
+ }
486
+
487
+ if (!isRoutedToSpeaker) {
488
+ [RTKLogger warning:@"⚠️ Speaker override didn't take effect immediately, current route: %@", currentRoute.outputs];
489
+ // iOS might route to speaker after a delay, or it might auto-route to Bluetooth
490
+ // The handleNewDeviceAvailable will detect if iOS routes back to Bluetooth
491
+ }
492
+
493
+ return true;
401
494
  } @catch (NSException *e) {
402
- NSLog(@"📕 Error occurred while routing audio via Loudspeaker: %@", e.reason);
495
+ [RTKLogger error:@"Error occurred while routing audio via Loudspeaker: %@", e.reason];
403
496
  return false;
404
497
  }
405
498
  }
@@ -407,183 +500,54 @@ RCT_EXPORT_METHOD(setSpeakerphoneOn:(BOOL)enable)
407
500
  RCT_EXPORT_METHOD(setForceSpeakerphoneOn:(int)flag)
408
501
  {
409
502
  _forceSpeakerOn = flag;
410
- // NSLog(@"RNInCallManager.setForceSpeakerphoneOn(): flag: %d", flag);
411
- [self updateAudioRoute];
503
+ [RTKLogger debug:@"RNInCallManager.setForceSpeakerphoneOn(): flag: %d", flag];
504
+ [self updateAudioRoute:YES];
412
505
  }
413
506
 
414
507
  RCT_EXPORT_METHOD(setMicrophoneMute:(BOOL)enable)
415
508
  {
416
- // NSLog(@"RNInCallManager.setMicrophoneMute(): ios doesn't support setMicrophoneMute()");
417
- }
418
-
419
- RCT_EXPORT_METHOD(startRingback:(NSString *)_ringbackUriType)
420
- {
421
- // you may rejected by apple when publish app if you use system sound instead of bundled sound.
422
- // NSLog(@"RNInCallManager.startRingback(): type=%@", _ringbackUriType);
423
-
424
- @try {
425
- if (_ringback != nil) {
426
- if ([_ringback isPlaying]) {
427
- // NSLog(@"RNInCallManager.startRingback(): is already playing");
428
- return;
429
- } else {
430
- [self stopRingback];
431
- }
432
- }
433
- // ios don't have embedded DTMF tone generator. use system dtmf sound files.
434
- NSString *ringbackUriType = [_ringbackUriType isEqualToString:@"_DTMF_"]
435
- ? @"_DEFAULT_"
436
- : _ringbackUriType;
437
- NSURL *ringbackUri = [self getRingbackUri:ringbackUriType];
438
- if (ringbackUri == nil) {
439
- // NSLog(@"RNInCallManager.startRingback(): no available media");
440
- return;
441
- }
442
- //self.storeOriginalAudioSetup()
443
- _ringback = [[AVAudioPlayer alloc] initWithContentsOfURL:ringbackUri error:nil];
444
- _ringback.delegate = self;
445
- _ringback.numberOfLoops = -1; // you need to stop it explicitly
446
- [_ringback prepareToPlay];
447
-
448
- //self.audioSessionSetCategory(self.incallAudioCategory, [.DefaultToSpeaker, .AllowBluetooth], #function)
449
- [self audioSessionSetCategory:_incallAudioCategory
450
- options:0
451
- callerMemo:NSStringFromSelector(_cmd)];
452
- [self audioSessionSetMode:_incallAudioMode
453
- callerMemo:NSStringFromSelector(_cmd)];
454
- [_ringback play];
455
- } @catch (NSException *e) {
456
- NSLog(@"RNInCallManager.startRingback(): caught error=%@", e.reason);
457
- }
458
- }
459
-
460
- RCT_EXPORT_METHOD(stopRingback)
461
- {
462
- if (_ringback != nil) {
463
- // NSLog(@"RNInCallManager.stopRingback()");
464
- [_ringback stop];
465
- _ringback = nil;
466
- // --- need to reset route based on config because WebRTC seems will switch audio mode automatically when call established.
467
- //[self updateAudioRoute];
468
- }
509
+ [RTKLogger debug:@"RNInCallManager.setMicrophoneMute(): ios doesn't support setMicrophoneMute()"];
469
510
  }
470
511
 
471
- RCT_EXPORT_METHOD(startRingtone:(NSString *)ringtoneUriType
472
- ringtoneCategory:(NSString *)ringtoneCategory)
512
+ RCT_EXPORT_METHOD(getIsWiredHeadsetPluggedIn:(RCTPromiseResolveBlock)resolve
513
+ reject:(RCTPromiseRejectBlock)reject)
473
514
  {
474
- // you may rejected by apple when publish app if you use system sound instead of bundled sound.
475
- // NSLog(@"RNInCallManager.startRingtone(): type: %@", ringtoneUriType);
476
- @try {
477
- if (_ringtone != nil) {
478
- if ([_ringtone isPlaying]) {
479
- // NSLog(@"RNInCallManager.startRingtone(): is already playing.");
480
- return;
481
- } else {
482
- [self stopRingtone];
483
- }
484
- }
485
- NSURL *ringtoneUri = [self getRingtoneUri:ringtoneUriType];
486
- if (ringtoneUri == nil) {
487
- // NSLog(@"RNInCallManager.startRingtone(): no available media");
488
- return;
489
- }
490
-
491
- // --- ios has Ringer/Silent switch, so just play without check ringer volume.
492
- [self storeOriginalAudioSetup];
493
- _ringtone = [[AVAudioPlayer alloc] initWithContentsOfURL:ringtoneUri error:nil];
494
- _ringtone.delegate = self;
495
- _ringtone.numberOfLoops = -1; // you need to stop it explicitly
496
- [_ringtone prepareToPlay];
497
-
498
- // --- 1. if we use Playback, it can supports background playing (starting from foreground), but it would not obey Ring/Silent switch.
499
- // --- make sure you have enabled 'audio' tag ( or 'voip' tag ) at XCode -> Capabilities -> BackgroundMode
500
- // --- 2. if we use SoloAmbient, it would obey Ring/Silent switch in the foreground, but does not support background playing,
501
- // --- thus, then you should play ringtone again via local notification after back to home during a ring session.
502
-
503
- // we prefer 2. by default, since most of users doesn't want to interrupted by a ringtone if Silent mode is on.
504
-
505
- //self.audioSessionSetCategory(AVAudioSessionCategoryPlayback, [.DuckOthers], #function)
506
- if ([ringtoneCategory isEqualToString:@"playback"]) {
507
- [self audioSessionSetCategory:AVAudioSessionCategoryPlayback
508
- options:0
509
- callerMemo:NSStringFromSelector(_cmd)];
510
- } else {
511
- [self audioSessionSetCategory:AVAudioSessionCategorySoloAmbient
512
- options:0
513
- callerMemo:NSStringFromSelector(_cmd)];
514
- }
515
- [self audioSessionSetMode:AVAudioSessionModeDefault
516
- callerMemo:NSStringFromSelector(_cmd)];
517
- //[self audioSessionSetActive:YES
518
- // options:nil
519
- // callerMemo:NSStringFromSelector(_cmd)];
520
- [_ringtone play];
521
- } @catch (NSException *e) {
522
- NSLog(@"RNInCallManager.startRingtone(): caught error = %@", e.reason);
523
- }
515
+ BOOL wiredHeadsetPluggedIn = [self isWiredHeadsetPluggedIn];
516
+ resolve(wiredHeadsetPluggedIn ? @YES : @NO);
524
517
  }
525
518
 
526
- RCT_EXPORT_METHOD(stopRingtone)
519
+ RCT_EXPORT_METHOD(getAvailableAudioDevices:(RCTPromiseResolveBlock)resolve
520
+ reject:(RCTPromiseRejectBlock)reject)
527
521
  {
528
- if (_ringtone != nil) {
529
- // NSLog(@"RNInCallManager.stopRingtone()");
530
- [_ringtone stop];
531
- _ringtone = nil;
532
- [self restoreOriginalAudioSetup];
533
- [self audioSessionSetActive:NO
534
- options:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
535
- callerMemo:NSStringFromSelector(_cmd)];
536
- }
522
+ NSArray<NSString *> *devices = [self getAvailableAudioDeviceList];
523
+ resolve(devices);
537
524
  }
538
525
 
539
-
540
-
541
-
542
-
543
-
544
- RCT_EXPORT_METHOD(getAudioUriJS:(NSString *)audioType
545
- fileType:(NSString *)fileType
546
- resolve:(RCTPromiseResolveBlock)resolve
547
- reject:(RCTPromiseRejectBlock)reject)
526
+ RCT_EXPORT_METHOD(getCurrentAudioDevice:(RCTPromiseResolveBlock)resolve
527
+ reject:(RCTPromiseRejectBlock)reject)
548
528
  {
549
- NSURL *result = nil;
550
- if ([audioType isEqualToString:@"ringback"]) {
551
- result = [self getRingbackUri:fileType];
552
- } else if ([audioType isEqualToString:@"busytone"]) {
553
- result = [self getBusytoneUri:fileType];
554
- } else if ([audioType isEqualToString:@"ringtone"]) {
555
- result = [self getRingtoneUri:fileType];
556
- }
557
- if (result != nil) {
558
- if (result.absoluteString.length > 0) {
559
- resolve(result.absoluteString);
560
- return;
561
- }
562
- }
563
- reject(@"error_code", @"getAudioUriJS() failed in main fn", RCTErrorWithMessage(@"getAudioUriJS() failed"));
529
+ NSString *device = [self getSelectedAudioDevice];
530
+ resolve(device);
564
531
  }
565
532
 
566
- RCT_EXPORT_METHOD(getIsWiredHeadsetPluggedIn:(RCTPromiseResolveBlock)resolve
567
- reject:(RCTPromiseRejectBlock)reject)
533
+ RCT_EXPORT_METHOD(forceUpdateAudioDevices)
568
534
  {
569
- BOOL wiredHeadsetPluggedIn = [self isWiredHeadsetPluggedIn];
570
- resolve(wiredHeadsetPluggedIn ? @YES : @NO);
535
+ [RTKLogger debug:@"RNInCallManager.forceUpdateAudioDevices(): Manually triggering audio device update"];
536
+ [self sendAudioDeviceEvent];
571
537
  }
572
538
 
573
539
 
574
540
  RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
575
541
  reject:(RCTPromiseRejectBlock) reject)
576
542
  {
577
- // NSLog(@"👉 Checking if Bluetooth device is connected");
543
+ [RTKLogger debug:@"Checking if Bluetooth device is connected"];
578
544
  NSMutableArray *devices = [NSMutableArray array];
579
- NSError *error = nil;
580
- [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord
581
- withOptions:AVAudioSessionCategoryOptionAllowBluetooth
582
- error:&error];
583
- [[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:nil];
584
- AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute];
545
+
546
+ // Read the current state without changing anything
547
+ AVAudioSessionRouteDescription* route = [_audioSession currentRoute];
548
+
549
+ // Check current route outputs for Bluetooth
585
550
  for (AVAudioSessionPortDescription* desc in [route outputs]) {
586
- // NSLog(@"🧑‍💻 Bluetooth Desc: %@", desc);
587
551
  if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
588
552
  [[desc portType] isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
589
553
  [[desc portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
@@ -591,20 +555,26 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
591
555
  }
592
556
  }
593
557
 
594
- // If no Bluetooth device found in current route, check available outputs
558
+ // Check current route inputs for Bluetooth
595
559
  if (devices.count == 0) {
596
- NSArray<AVAudioSessionPortDescription *> *inputs = [route inputs];
597
-
598
- // Check if Bluetooth is available in inputs (for headsets that function as both input and output)
599
- for (AVAudioSessionPortDescription* desc in inputs) {
560
+ for (AVAudioSessionPortDescription* desc in [route inputs]) {
600
561
  if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
601
562
  [[desc portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
602
563
  [devices addObject:[desc portName]];
603
564
  }
604
565
  }
605
566
  }
606
-
607
- // NSLog(@"🧑‍💻 Bluetooth Devices %@", devices);
567
+
568
+ // Check availableInputs for connected but not currently routed Bluetooth devices
569
+ if (devices.count == 0) {
570
+ for (AVAudioSessionPortDescription* input in [_audioSession availableInputs]) {
571
+ if ([[input portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
572
+ [[input portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
573
+ [devices addObject:[input portName]];
574
+ }
575
+ }
576
+ }
577
+
608
578
  if (!devices || !devices.count){
609
579
  resolve(@NO);
610
580
  } else {
@@ -613,9 +583,16 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
613
583
  }
614
584
 
615
585
  //TODO: Rewrite this function so that we manage bluetooth, wiredHeadset and all the updates from here
616
- - (BOOL)updateAudioRoute
586
+ - (BOOL)updateAudioRoute:(BOOL)shouldSendEvent
617
587
  {
618
- // NSLog(@" 👉 RNInCallManager.updateAudioRoute(): [Enter] forceSpeakerOn flag=%d media=%@ category=%@ mode=%@", _forceSpeakerOn, _media, _audioSession.category, _audioSession.mode);
588
+ // Prevent re-entrant calls that cause infinite loops
589
+ if (_isUpdatingAudioRoute) {
590
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Already updating, skipping to prevent loop"];
591
+ return NO;
592
+ }
593
+
594
+ _isUpdatingAudioRoute = YES;
595
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): [Enter] forceSpeakerOn flag=%d category=%@ mode=%@", _forceSpeakerOn, _audioSession.category, _audioSession.mode];
619
596
  //self.debugAudioSession()
620
597
 
621
598
  //AVAudioSessionPortOverride overrideAudioPort;
@@ -628,50 +605,85 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
628
605
  // --- So we should not change audio mode as possible as we can. Only when default video call which wants to force speaker off.
629
606
  // --- audio: only override speaker on/off; video: should change category if needed and handle proximity sensor. ( because default proximity is off when video call )
630
607
  if (_forceSpeakerOn == 1) {
631
- // NSLog(@"👨‍💻 Force Speaker on");
608
+ [RTKLogger debug:@"Force Speaker on"];
632
609
  // --- force ON, override speaker only, keep audio mode remain.
633
610
  overrideAudioPort = AVAudioSessionPortOverrideSpeaker;
634
611
  overrideAudioPortString = @".Speaker";
635
- // if ([_media isEqualToString:@"video"]) {
636
- // audioMode = AVAudioSessionModeVideoChat;
637
- // [self stopProximitySensor];
638
- // }
639
- //Bluetooth device
640
- } else { // use default behavior
641
- // NSLog(@"👨‍💻 Else Audio Route");
612
+ } else if ([_userSelectedAudioRoute isEqualToString:@"earpiece"]) {
613
+ // User explicitly selected earpiece
614
+ [RTKLogger debug:@"User selected Earpiece"];
642
615
  overrideAudioPort = AVAudioSessionPortOverrideNone;
643
616
  overrideAudioPortString = @".None";
644
- // if ([_media isEqualToString:@"video"]) {
645
- // audioMode = AVAudioSessionModeVideoChat;
646
- // [self stopProximitySensor];
647
- // }
617
+ } else {
618
+ // Default to speaker when no explicit selection or Bluetooth selected
619
+ [RTKLogger debug:@"Default Audio Route - Speaker"];
620
+ overrideAudioPort = AVAudioSessionPortOverrideSpeaker;
621
+ overrideAudioPortString = @".Speaker";
648
622
  }
649
- //NOTE: Check Current route to speaker
650
- BOOL isCurrentRouteToSpeaker;
651
- isCurrentRouteToSpeaker = [self checkAudioRoute:@[AVAudioSessionPortBuiltInSpeaker]
623
+ //NOTE: Check Current route
624
+ BOOL isCurrentRouteToSpeaker = [self checkAudioRoute:@[AVAudioSessionPortBuiltInSpeaker]
652
625
  routeType:@"output"];
653
- if ((overrideAudioPort == AVAudioSessionPortOverrideSpeaker && !isCurrentRouteToSpeaker)
654
- || (overrideAudioPort == AVAudioSessionPortOverrideNone && isCurrentRouteToSpeaker)) {
655
- @try {
656
- [_audioSession overrideOutputAudioPort:overrideAudioPort error:nil];
657
- // NSLog(@"💯 RNInCallManager.updateAudioRoute(): audioSession.overrideOutputAudioPort(%@) success", overrideAudioPortString);
658
- } @catch (NSException *e) {
659
- NSLog(@"😭 RNInCallManager.updateAudioRoute(): audioSession.overrideOutputAudioPort(%@) fail: %@", overrideAudioPortString, e.reason);
626
+ BOOL isCurrentRouteToBluetooth = [self checkAudioRoute:@[AVAudioSessionPortBluetoothHFP, AVAudioSessionPortBluetoothA2DP, AVAudioSessionPortBluetoothLE]
627
+ routeType:@"output"];
628
+
629
+ // Priority 1: If forceSpeakerOn is set, ALWAYS route to speaker (even if Bluetooth is available)
630
+ if (_forceSpeakerOn == 1) {
631
+ if (!isCurrentRouteToSpeaker) {
632
+ @try {
633
+ [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
634
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Forcing speaker (overriding Bluetooth if present)"];
635
+ } @catch (NSException *e) {
636
+ [RTKLogger error:@"RNInCallManager.updateAudioRoute(): Failed to force speaker: %@", e.reason];
637
+ }
638
+ } else {
639
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Already on speaker, forceSpeakerOn active"];
640
+ }
641
+ }
642
+ // Priority 2: Don't override if user selected Bluetooth (and forceSpeaker is OFF)
643
+ else if ([_userSelectedAudioRoute isEqualToString:@"bluetooth"] && isCurrentRouteToBluetooth) {
644
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): User selected Bluetooth, not overriding"];
645
+ }
646
+ // Priority 3: Route based on overrideAudioPort value
647
+ else {
648
+ // overrideAudioPort is either .Speaker (default/forceSpeaker) or .None (earpiece)
649
+ if (overrideAudioPort == AVAudioSessionPortOverrideSpeaker && !isCurrentRouteToSpeaker && !isCurrentRouteToBluetooth) {
650
+ // Need to route to speaker (and not on Bluetooth)
651
+ @try {
652
+ [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
653
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Routing to speaker"];
654
+ } @catch (NSException *e) {
655
+ [RTKLogger error:@"RNInCallManager.updateAudioRoute(): Failed to route to speaker: %@", e.reason];
656
+ }
657
+ } else if (overrideAudioPort == AVAudioSessionPortOverrideNone && isCurrentRouteToSpeaker) {
658
+ // User selected earpiece, need to switch from speaker to earpiece
659
+ @try {
660
+ [_audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:nil];
661
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): Routing to earpiece"];
662
+ } @catch (NSException *e) {
663
+ [RTKLogger error:@"RNInCallManager.updateAudioRoute(): Failed to route to earpiece: %@", e.reason];
664
+ }
665
+ } else {
666
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(): No route change needed"];
660
667
  }
661
- } else {
662
- // NSLog(@"👨‍💻 RNInCallManager.updateAudioRoute(): did NOT overrideOutputAudioPort()");
663
668
  }
664
669
 
670
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute(), sending Event"];
671
+ if(shouldSendEvent) [self sendAudioDeviceEvent];
672
+
673
+ BOOL didChangeMode = NO;
665
674
  if (audioMode.length > 0 && ![_audioSession.mode isEqualToString:audioMode]) {
666
675
  [self audioSessionSetMode:audioMode
667
676
  callerMemo:NSStringFromSelector(_cmd)];
668
677
 
669
- // NSLog(@"👨‍💻 RNInCallManager.updateAudioRoute() audio mode has changed to %@", audioMode);
670
- return true;
678
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute() audio mode has changed to %@", audioMode];
679
+ didChangeMode = YES;
671
680
  } else {
672
- // NSLog(@"👨‍💻 RNInCallManager.updateAudioRoute() did NOT change audio mode");
673
- return false;
681
+ [RTKLogger debug:@"RNInCallManager.updateAudioRoute() did NOT change audio mode"];
682
+ didChangeMode = NO;
674
683
  }
684
+
685
+ _isUpdatingAudioRoute = NO;
686
+ return didChangeMode;
675
687
  //self.debugAudioSession()
676
688
  }
677
689
 
@@ -680,80 +692,22 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
680
692
  {
681
693
  //TODO: Checking bluetooth route should be called from here
682
694
  AVAudioSessionRouteDescription *currentRoute = _audioSession.currentRoute;
683
- // NSLog(@"👉 Check Audio Route called ");
684
- // NSLog(@"👨‍💻 Route Type: %@", routeType);
685
- // NSLog(@"👨‍💻 Current Audio Route: %@", currentRoute);
686
- // NSLog(@"👨‍💻 targetPortTypeArray: %@", targetPortTypeArray);
687
695
  if (currentRoute != nil) {
688
696
  NSArray<AVAudioSessionPortDescription *> *routes = [routeType isEqualToString:@"input"]
689
697
  ? currentRoute.inputs
690
698
  : currentRoute.outputs;
691
699
  for (AVAudioSessionPortDescription *portDescription in routes) {
692
700
  if ([targetPortTypeArray containsObject:portDescription.portType]) {
693
- // NSLog(@"🌏 Audio Route for %@ does exist", targetPortTypeArray);
694
701
  return YES;
695
702
  }
696
703
  }
697
704
  }
698
- // NSLog(@"🌏 Audio Route for %@ does not exist", targetPortTypeArray);
699
705
  return NO;
700
706
  }
701
707
 
702
- - (BOOL)startBusytone:(NSString *)_busytoneUriType
703
- {
704
- // you may rejected by apple when publish app if you use system sound instead of bundled sound.
705
- NSLog(@"RNInCallManager.startBusytone(): type: %@", _busytoneUriType);
706
- @try {
707
- if (_busytone != nil) {
708
- if ([_busytone isPlaying]) {
709
- // NSLog(@"RNInCallManager.startBusytone(): is already playing");
710
- return NO;
711
- } else {
712
- [self stopBusytone];
713
- }
714
- }
715
-
716
- // ios don't have embedded DTMF tone generator. use system dtmf sound files.
717
- NSString *busytoneUriType = [_busytoneUriType isEqualToString:@"_DTMF_"]
718
- ? @"_DEFAULT_"
719
- : _busytoneUriType;
720
- NSURL *busytoneUri = [self getBusytoneUri:busytoneUriType];
721
- if (busytoneUri == nil) {
722
- // NSLog(@"RNInCallManager.startBusytone(): no available media");
723
- return NO;
724
- }
725
- //[self storeOriginalAudioSetup];
726
- _busytone = [[AVAudioPlayer alloc] initWithContentsOfURL:busytoneUri error:nil];
727
- _busytone.delegate = self;
728
- _busytone.numberOfLoops = 0; // it's part of start(), will stop at stop()
729
- [_busytone prepareToPlay];
730
-
731
- //self.audioSessionSetCategory(self.incallAudioCategory, [.DefaultToSpeaker, .AllowBluetooth], #function)
732
- [self audioSessionSetCategory:_incallAudioCategory
733
- options:0
734
- callerMemo:NSStringFromSelector(_cmd)];
735
- [self audioSessionSetMode:_incallAudioMode
736
- callerMemo:NSStringFromSelector(_cmd)];
737
- [_busytone play];
738
- } @catch (NSException *e) {
739
- // NSLog(@"RNInCallManager.startBusytone(): caught error = %@", e.reason);
740
- return NO;
741
- }
742
- return YES;
743
- }
744
-
745
- - (void)stopBusytone
746
- {
747
- if (_busytone != nil) {
748
- // NSLog(@"RNInCallManager.stopBusytone()");
749
- [_busytone stop];
750
- _busytone = nil;
751
- }
752
- }
753
-
754
708
  - (BOOL)isWiredHeadsetPluggedIn
755
709
  {
756
- // NSLog(@"👉 checking if isWiredHeadset is plusgged in or not");
710
+ [RTKLogger debug:@"Checking if isWiredHeadset is plugged in or not"];
757
711
  // --- only check for a audio device plugged into headset port instead bluetooth/usb/hdmi
758
712
  return [self checkAudioRoute:@[AVAudioSessionPortHeadphones]
759
713
  routeType:@"output"]
@@ -774,9 +728,9 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
774
728
  [_audioSession setCategory:audioCategory
775
729
  error:nil];
776
730
  }
777
- // NSLog(@"RNInCallManager.%@: audioSession.setCategory: %@, withOptions: %lu success", callerMemo, audioCategory, (unsigned long)options);
731
+ [RTKLogger debug:@"RNInCallManager.%@: audioSession.setCategory: %@, withOptions: %lu success", callerMemo, audioCategory, (unsigned long)options];
778
732
  } @catch (NSException *e) {
779
- // NSLog(@"RNInCallManager.%@: audioSession.setCategory: %@, withOptions: %lu fail: %@", callerMemo, audioCategory, (unsigned long)options, e.reason);
733
+ [RTKLogger error:@"RNInCallManager.%@: audioSession.setCategory: %@, withOptions: %lu fail: %@", callerMemo, audioCategory, (unsigned long)options, e.reason];
780
734
  }
781
735
  }
782
736
 
@@ -785,9 +739,9 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
785
739
  {
786
740
  @try {
787
741
  [_audioSession setMode:audioMode error:nil];
788
- // NSLog(@"RNInCallManager.%@: audioSession.setMode(%@) success", callerMemo, audioMode);
742
+ [RTKLogger debug:@"RNInCallManager.%@: audioSession.setMode(%@) success", callerMemo, audioMode];
789
743
  } @catch (NSException *e) {
790
- // NSLog(@"RNInCallManager.%@: audioSession.setMode(%@) fail: %@", callerMemo, audioMode, e.reason);
744
+ [RTKLogger error:@"RNInCallManager.%@: audioSession.setMode(%@) fail: %@", callerMemo, audioMode, e.reason];
791
745
  }
792
746
  }
793
747
 
@@ -804,22 +758,22 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
804
758
  [_audioSession setActive:audioActive
805
759
  error:nil];
806
760
  }
807
- // NSLog(@"RNInCallManager.%@: audioSession.setActive(%@), withOptions: %lu success", callerMemo, audioActive ? @"YES" : @"NO", (unsigned long)options);
761
+ [RTKLogger debug:@"RNInCallManager.%@: audioSession.setActive(%@), withOptions: %lu success", callerMemo, audioActive ? @"YES" : @"NO", (unsigned long)options];
808
762
  } @catch (NSException *e) {
809
- // NSLog(@"RNInCallManager.%@: audioSession.setActive(%@), withOptions: %lu fail: %@", callerMemo, audioActive ? @"YES" : @"NO", (unsigned long)options, e.reason);
763
+ [RTKLogger error:@"RNInCallManager.%@: audioSession.setActive(%@), withOptions: %lu fail: %@", callerMemo, audioActive ? @"YES" : @"NO", (unsigned long)options, e.reason];
810
764
  }
811
765
  }
812
766
 
813
767
  - (void)storeOriginalAudioSetup
814
768
  {
815
- // NSLog(@"👨‍💻 RNInCallManager.storeOriginalAudioSetup(): origAudioCategory=%@, origAudioMode=%@", _audioSession.category, _audioSession.mode);
769
+ [RTKLogger debug:@"RNInCallManager.storeOriginalAudioSetup(): origAudioCategory=%@, origAudioMode=%@", _audioSession.category, _audioSession.mode];
816
770
  _origAudioCategory = _audioSession.category;
817
771
  _origAudioMode = _audioSession.mode;
818
772
  }
819
773
 
820
774
  - (void)restoreOriginalAudioSetup
821
775
  {
822
- // NSLog(@"RNInCallManager.restoreOriginalAudioSetup(): origAudioCategory=%@, origAudioMode=%@", _audioSession.category, _audioSession.mode);
776
+ [RTKLogger debug:@"RNInCallManager.restoreOriginalAudioSetup(): origAudioCategory=%@, origAudioMode=%@", _audioSession.category, _audioSession.mode];
823
777
  [self audioSessionSetCategory:_origAudioCategory
824
778
  options:0
825
779
  callerMemo:NSStringFromSelector(_cmd)];
@@ -833,7 +787,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
833
787
  return;
834
788
  }
835
789
 
836
- // NSLog(@"RNInCallManager.startProximitySensor()");
790
+ [RTKLogger debug:@"RNInCallManager.startProximitySensor()"];
837
791
  dispatch_async(dispatch_get_main_queue(), ^{
838
792
  self->_currentDevice.proximityMonitoringEnabled = YES;
839
793
  });
@@ -849,14 +803,14 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
849
803
  block:^(NSNotification *notification) {
850
804
  BOOL state = self->_currentDevice.proximityState;
851
805
  if (state != self->_proximityIsNear) {
852
- // NSLog(@"RNInCallManager.UIDeviceProximityStateDidChangeNotification(): isNear: %@", state ? @"YES" : @"NO");
806
+ [RTKLogger debug:@"RNInCallManager.UIDeviceProximityStateDidChangeNotification(): isNear: %@", state ? @"YES" : @"NO"];
853
807
  self->_proximityIsNear = state;
854
808
  @try {
855
809
  if (self->hasListeners && [self canSendEvents]) {
856
810
  [self sendEventWithName:@"Proximity" body:@{@"isNear": state ? @YES : @NO}];
857
811
  }
858
812
  } @catch (NSException *exception) {
859
- NSLog(@"Error sending event 'Proximity': %@", exception.reason);
813
+ [RTKLogger error:@"Error sending event 'Proximity': %@", exception.reason];
860
814
  }
861
815
  }
862
816
  }];
@@ -870,7 +824,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
870
824
  return;
871
825
  }
872
826
 
873
- // NSLog(@"RNInCallManager.stopProximitySensor()");
827
+ [RTKLogger debug:@"RNInCallManager.stopProximitySensor()"];
874
828
  dispatch_async(dispatch_get_main_queue(), ^{
875
829
  self->_currentDevice.proximityMonitoringEnabled = NO;
876
830
  });
@@ -883,9 +837,191 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
883
837
  _isProximityRegistered = NO;
884
838
  }
885
839
 
840
+ - (NSArray<NSString *> *)getAvailableAudioDeviceList
841
+ {
842
+ NSMutableArray<NSString *> *devices = [NSMutableArray arrayWithObjects:@"speaker", @"earpiece", nil];
843
+
844
+ // Check for wired headset
845
+ if ([self checkAudioRoute:@[AVAudioSessionPortHeadsetMic] routeType:@"input"] ||
846
+ [self checkAudioRoute:@[AVAudioSessionPortHeadphones] routeType:@"output"]) {
847
+ [devices addObject:@"wired"];
848
+ }
849
+
850
+ // Check for Bluetooth in current route outputs
851
+ AVAudioSessionRouteDescription* route = [_audioSession currentRoute];
852
+ BOOL bluetoothFound = NO;
853
+
854
+ for (AVAudioSessionPortDescription* desc in [route outputs]) {
855
+ if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
856
+ [[desc portType] isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
857
+ [[desc portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
858
+ [devices addObject:@"bluetooth"];
859
+ bluetoothFound = YES;
860
+ [RTKLogger debug:@"Bluetooth found in outputs: %@", [desc portName]];
861
+ break;
862
+ }
863
+ }
864
+
865
+ // Check for Bluetooth in current route inputs (HFP devices appear here)
866
+ if (!bluetoothFound) {
867
+ for (AVAudioSessionPortDescription* desc in [route inputs]) {
868
+ if ([[desc portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
869
+ [[desc portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
870
+ [devices addObject:@"bluetooth"];
871
+ bluetoothFound = YES;
872
+ [RTKLogger debug:@"Bluetooth found in inputs: %@", [desc portName]];
873
+ break;
874
+ }
875
+ }
876
+ }
877
+
878
+ // Check availableInputs for connected but not currently routed Bluetooth devices
879
+ if (!bluetoothFound) {
880
+ for (AVAudioSessionPortDescription* input in [_audioSession availableInputs]) {
881
+ if ([[input portType] isEqualToString:AVAudioSessionPortBluetoothHFP] ||
882
+ [[input portType] isEqualToString:AVAudioSessionPortBluetoothLE]) {
883
+ [devices addObject:@"bluetooth"];
884
+ bluetoothFound = YES;
885
+ [RTKLogger debug:@"Bluetooth found in availableInputs: %@", [input portName]];
886
+ break;
887
+ }
888
+ }
889
+ }
890
+
891
+ [RTKLogger debug:@"Available audio devices: %@", devices];
892
+ return devices;
893
+ }
894
+
895
+ - (NSString *)getSelectedAudioDevice
896
+ {
897
+ AVAudioSessionRouteDescription* route = [_audioSession currentRoute];
898
+
899
+ // Check current output route
900
+ for (AVAudioSessionPortDescription* desc in [route outputs]) {
901
+ NSString *portType = [desc portType];
902
+
903
+ if ([portType isEqualToString:AVAudioSessionPortBluetoothHFP] ||
904
+ [portType isEqualToString:AVAudioSessionPortBluetoothA2DP] ||
905
+ [portType isEqualToString:AVAudioSessionPortBluetoothLE]) {
906
+ return @"bluetooth";
907
+ } else if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
908
+ return @"wired";
909
+ } else if ([portType isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
910
+ return @"speaker";
911
+ } else if ([portType isEqualToString:AVAudioSessionPortBuiltInReceiver]) {
912
+ return @"earpiece";
913
+ }
914
+ }
915
+
916
+ // Check input route for wired headset mic
917
+ for (AVAudioSessionPortDescription* desc in [route inputs]) {
918
+ if ([[desc portType] isEqualToString:AVAudioSessionPortHeadsetMic]) {
919
+ return @"wired";
920
+ }
921
+ }
922
+
923
+ // Default fallback
924
+ return @"speaker";
925
+ }
926
+
927
+ - (void)sendAudioDeviceEvent
928
+ {
929
+ @try {
930
+ if (self->hasListeners && [self canSendEvents]) {
931
+ // Log current audio session state for debugging
932
+ AVAudioSessionCategoryOptions currentOptions = [_audioSession categoryOptions];
933
+ BOOL hasAllowBluetooth = (currentOptions & AVAudioSessionCategoryOptionAllowBluetooth) != 0;
934
+ [RTKLogger debug:@"Category: %@, Options: %lu, AllowBluetooth: %@",
935
+ _audioSession.category,
936
+ (unsigned long)currentOptions,
937
+ hasAllowBluetooth ? @"YES" : @"NO"];
938
+
939
+ AVAudioSessionRouteDescription* route = [_audioSession currentRoute];
940
+ [RTKLogger debug:@"Current route outputs: %@", route.outputs];
941
+ [RTKLogger debug:@"Current route inputs: %@", route.inputs];
942
+ [RTKLogger debug:@"Available inputs: %@", [_audioSession availableInputs]];
943
+
944
+ NSArray<NSString *> *availableDevices = [self getAvailableAudioDeviceList];
945
+ NSString *selectedDevice = [self getSelectedAudioDevice];
946
+ [RTKLogger debug:@"available: %@, selected: %@", availableDevices, selectedDevice];
947
+
948
+ // Convert array to JSON string format
949
+ NSError *error;
950
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:availableDevices
951
+ options:0
952
+ error:&error];
953
+ [RTKLogger debug:@"JSON: %@", jsonData];
954
+ NSString *deviceListString = @"[speaker, earpiece]";
955
+ if (jsonData && !error) {
956
+ deviceListString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
957
+ }
958
+
959
+ [self sendEventWithName:@"onAudioDeviceChanged"
960
+ body:@{
961
+ @"availableAudioDeviceList": deviceListString,
962
+ @"selectedAudioDevice": selectedDevice,
963
+ }];
964
+
965
+ [RTKLogger debug:@"Sent audio device event - available: %@, selected: %@", deviceListString, selectedDevice];
966
+ }
967
+ } @catch (NSException *exception) {
968
+ [RTKLogger error:@"Error sending audio device event: %@", exception.reason];
969
+ }
970
+ }
971
+
972
+ - (void)handleNewDeviceAvailable
973
+ {
974
+ // Get the actual current route from iOS
975
+ NSString *currentRoute = [self getSelectedAudioDevice];
976
+
977
+ [RTKLogger debug:@"handleNewDeviceAvailable: iOS routed to '%@', user selected '%@'",
978
+ currentRoute,
979
+ _userSelectedAudioRoute ?: @"(none)"];
980
+
981
+ // Update our state to match iOS's decision
982
+ _userSelectedAudioRoute = currentRoute;
983
+ _currentAudioRoute = currentRoute;
984
+
985
+ _forceSpeakerOn = 0;
986
+
987
+ // Earpiece requires VoiceChat mode, others work with VideoChat mode
988
+ if ([currentRoute isEqualToString:@"earpiece"]) {
989
+ // iOS routed to earpiece - ensure we're in VoiceChat mode
990
+ if (![_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
991
+ [self audioSessionSetMode:AVAudioSessionModeVoiceChat
992
+ callerMemo:NSStringFromSelector(_cmd)];
993
+ _incallAudioMode = AVAudioSessionModeVoiceChat;
994
+ [RTKLogger debug:@"Switched to VoiceChat mode for earpiece"];
995
+ }
996
+ } else {
997
+ // iOS routed to speaker/bluetooth/wired - ensure we're in VideoChat mode
998
+ if ([_audioSession.mode isEqualToString:AVAudioSessionModeVoiceChat]) {
999
+ [self audioSessionSetMode:AVAudioSessionModeVideoChat
1000
+ callerMemo:NSStringFromSelector(_cmd)];
1001
+ _incallAudioMode = AVAudioSessionModeVideoChat;
1002
+ [RTKLogger debug:@"Switched back to VideoChat mode for non-earpiece device"];
1003
+ }
1004
+ }
1005
+
1006
+ // Log what device iOS routed to
1007
+ if ([self checkAudioRoute:@[AVAudioSessionPortHeadsetMic] routeType:@"input"]) {
1008
+ [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): HeadsetMic - synced state to 'wired'"];
1009
+ } else if ([self checkAudioRoute:@[AVAudioSessionPortHeadphones] routeType:@"output"]) {
1010
+ [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): Headphones - synced state to 'wired'"];
1011
+ } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothHFP] routeType:@"output"]) {
1012
+ [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): BluetoothHFP - synced state to 'bluetooth'"];
1013
+ } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothA2DP] routeType:@"output"]) {
1014
+ [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): BluetoothA2DP - synced state to 'bluetooth'"];
1015
+ } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothLE] routeType:@"output"]) {
1016
+ [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): BluetoothLE - synced state to 'bluetooth'"];
1017
+ } else {
1018
+ [RTKLogger debug:@"RNInCallManager.handleNewDeviceAvailable(): Other device - synced state to '%@'", currentRoute];
1019
+ }
1020
+ }
1021
+
886
1022
  - (void)startAudioSessionNotification
887
1023
  {
888
- // NSLog(@"👉 RNInCallManager.startAudioSessionNotification() starting...");
1024
+ [RTKLogger debug:@"RNInCallManager.startAudioSessionNotification() starting..."];
889
1025
  [self startAudioSessionInterruptionNotification];
890
1026
  [self startAudioSessionRouteChangeNotification];
891
1027
  [self startAudioSessionMediaServicesWereLostNotification];
@@ -895,7 +1031,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
895
1031
 
896
1032
  - (void)stopAudioSessionNotification
897
1033
  {
898
- // NSLog(@"👉 RNInCallManager.startAudioSessionNotification() stopping...");
1034
+ [RTKLogger debug:@"RNInCallManager.startAudioSessionNotification() stopping..."];
899
1035
  [self stopAudioSessionInterruptionNotification];
900
1036
  [self stopAudioSessionRouteChangeNotification];
901
1037
  [self stopAudioSessionMediaServicesWereLostNotification];
@@ -908,7 +1044,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
908
1044
  if (_isAudioSessionInterruptionRegistered) {
909
1045
  return;
910
1046
  }
911
- // NSLog(@"👉 RNInCallManager.startAudioSessionInterruptionNotification()");
1047
+ [RTKLogger debug:@"RNInCallManager.startAudioSessionInterruptionNotification()"];
912
1048
 
913
1049
  // --- in case it didn't deallocate when ViewDidUnload
914
1050
  [self stopObserve:_audioSessionInterruptionObserver
@@ -927,11 +1063,12 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
927
1063
  //NSUInteger rawValue = notification.userInfo[AVAudioSessionInterruptionTypeKey].unsignedIntegerValue;
928
1064
  NSNumber *interruptType = [notification.userInfo objectForKey:@"AVAudioSessionInterruptionTypeKey"];
929
1065
  if ([interruptType unsignedIntegerValue] == AVAudioSessionInterruptionTypeBegan) {
930
- // NSLog(@"RNInCallManager.AudioSessionInterruptionNotification: Began");
1066
+ [RTKLogger debug:@"RNInCallManager.AudioSessionInterruptionNotification: Began"];
1067
+ [self updateAudioRoute:YES];
931
1068
  } else if ([interruptType unsignedIntegerValue] == AVAudioSessionInterruptionTypeEnded) {
932
- // NSLog(@"🧑‍💻 RNInCallManager.AudioSessionInterruptionNotification: Ended");
1069
+ [RTKLogger debug:@"RNInCallManager.AudioSessionInterruptionNotification: Ended"];
933
1070
  } else {
934
- // NSLog(@"🧑‍💻 RNInCallManager.AudioSessionInterruptionNotification: Unknow Value");
1071
+ [RTKLogger debug:@"RNInCallManager.AudioSessionInterruptionNotification: Unknown Value"];
935
1072
  }
936
1073
  //NSLog(@"RNInCallManager.AudioSessionInterruptionNotification: could not resolve notification");
937
1074
  }];
@@ -944,7 +1081,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
944
1081
  if (!_isAudioSessionInterruptionRegistered) {
945
1082
  return;
946
1083
  }
947
- // NSLog(@"RNInCallManager.stopAudioSessionInterruptionNotification()");
1084
+ [RTKLogger debug:@"RNInCallManager.stopAudioSessionInterruptionNotification()"];
948
1085
  // --- remove all no matter what object
949
1086
  [self stopObserve:_audioSessionInterruptionObserver
950
1087
  name:AVAudioSessionInterruptionNotification
@@ -958,7 +1095,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
958
1095
  return;
959
1096
  }
960
1097
 
961
- // NSLog(@"RNInCallManager.startAudioSessionRouteChangeNotification()");
1098
+ [RTKLogger debug:@"RNInCallManager.startAudioSessionRouteChangeNotification()"];
962
1099
 
963
1100
  // --- in case it didn't deallocate when ViewDidUnload
964
1101
  [self stopObserve:_audioSessionRouteChangeObserver
@@ -969,7 +1106,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
969
1106
  object: nil
970
1107
  queue: nil
971
1108
  block:^(NSNotification *notification) {
972
- // NSLog(@"🧑‍💻 Audio Session changed, user route %@ and %@ current route", _userSelectedAudioRoute, self->_audioSession.currentRoute);
1109
+ // [RTKLogger debug:@"Audio Session changed, user route %@ and %@ current route", _userSelectedAudioRoute, self->_audioSession.currentRoute];
973
1110
  if (notification.userInfo == nil
974
1111
  || ![notification.name isEqualToString:AVAudioSessionRouteChangeNotification]) {
975
1112
  return;
@@ -980,129 +1117,90 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
980
1117
 
981
1118
  switch (routeChangeTypeValue) {
982
1119
  case AVAudioSessionRouteChangeReasonUnknown:
983
- // NSLog(@"🧑‍💻 RNInCallManager.AudioRouteChange.Reason: Unknown");
1120
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: Unknown"];
984
1121
  break;
985
- case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
986
- // NSLog(@"🧑‍💻 RNInCallManager.AudioRouteChange.Reason: NewDeviceAvailable");
987
- if ([self checkAudioRoute:@[AVAudioSessionPortHeadsetMic]
988
- routeType:@"input"]) {
989
- self->_forceSpeakerOn = 0;
990
- @try {
991
- if (self->hasListeners && [self canSendEvents]) {
992
- [self sendEventWithName:@"onAudioDeviceChanged"
993
- body:@{
994
- @"availableAudioDeviceList": @"[speaker, earpiece, wired]",
995
- @"selectedAudioDevice": @"wired",
996
- }];
997
- }
998
- } @catch (NSException *exception) {
999
- NSLog(@"Error sending event 'onAudioDeviceChanged' (HeadsetMic): %@", exception.reason);
1000
- }
1001
- } else if ([self checkAudioRoute:@[AVAudioSessionPortHeadphones]
1002
- routeType:@"output"]) {
1003
- self->_forceSpeakerOn = 0;
1004
- @try {
1005
- if (self->hasListeners && [self canSendEvents]) {
1006
- [self sendEventWithName:@"onAudioDeviceChanged"
1007
- body:@{
1008
- @"availableAudioDeviceList": @"[speaker, earpiece, wired]",
1009
- @"selectedAudioDevice": @"wired",
1010
- }];
1011
- }
1012
- } @catch (NSException *exception) {
1013
- NSLog(@"Error sending event 'onAudioDeviceChanged' (Headphones): %@", exception.reason);
1014
- }
1015
- } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothHFP]
1016
- routeType:@"input"]) {
1017
- self->_forceSpeakerOn = 0;
1018
- [self routeAudioFromBluetooth];
1019
- @try {
1020
- if (self->hasListeners && [self canSendEvents]) {
1021
- [self sendEventWithName:@"onAudioDeviceChanged"
1022
- body:@{
1023
- @"availableAudioDeviceList": @"[speaker, earpiece, bluetooth]",
1024
- @"selectedAudioDevice": @"bluetooth",
1025
- }];
1026
- }
1027
- } @catch (NSException *exception) {
1028
- NSLog(@"Error sending event 'onAudioDeviceChanged' (BluetoothHFP): %@", exception.reason);
1029
- }
1030
- } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothA2DP]
1031
- routeType:@"input"]) {
1032
- self->_forceSpeakerOn = 0;
1033
- [self routeAudioFromBluetooth];
1034
- @try {
1035
- if (self->hasListeners && [self canSendEvents]) {
1036
- [self sendEventWithName:@"onAudioDeviceChanged"
1037
- body:@{
1038
- @"availableAudioDeviceList": @"[speaker, earpiece, bluetooth]",
1039
- @"selectedAudioDevice": @"bluetooth",
1040
- }];
1041
- }
1042
- } @catch (NSException *exception) {
1043
- NSLog(@"Error sending event 'onAudioDeviceChanged' (BluetoothA2DP): %@", exception.reason);
1044
- }
1045
- } else if ([self checkAudioRoute:@[AVAudioSessionPortBluetoothLE]
1046
- routeType:@"input"]) {
1047
- self->_forceSpeakerOn = 0;
1048
- [self routeAudioFromBluetooth];
1049
- @try {
1050
- if (self->hasListeners && [self canSendEvents]) {
1051
- [self sendEventWithName:@"onAudioDeviceChanged"
1052
- body:@{
1053
- @"availableAudioDeviceList": @"[speaker, earpiece, bluetooth]",
1054
- @"selectedAudioDevice": @"bluetooth",
1055
- }];
1056
- }
1057
- } @catch (NSException *exception) {
1058
- NSLog(@"Error sending event 'onAudioDeviceChanged' (BluetoothLE): %@", exception.reason);
1122
+ case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
1123
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: NewDeviceAvailable"];
1124
+
1125
+ // Get what iOS routed to
1126
+ NSString *currentRoute = [self getSelectedAudioDevice];
1127
+ [RTKLogger debug:@"iOS auto-routed to '%@', user selected '%@'", currentRoute, _userSelectedAudioRoute ?: @"(none)"];
1128
+
1129
+ // If user explicitly selected a route, don't let iOS override it
1130
+ if (_userSelectedAudioRoute != nil && ![currentRoute isEqualToString:_userSelectedAudioRoute]) {
1131
+ [RTKLogger warning:@"⚠️ iOS tried to auto-route to '%@' but user selected '%@'. Re-asserting user's choice...", currentRoute, _userSelectedAudioRoute];
1132
+
1133
+ // Re-assert the user's choice immediately
1134
+ if ([_userSelectedAudioRoute isEqualToString:@"speaker"]) {
1135
+ [self routeAudioFromSpeakerphone];
1136
+ } else if ([_userSelectedAudioRoute isEqualToString:@"earpiece"]) {
1137
+ [self routeAudioFromEarpiece];
1138
+ } else if ([_userSelectedAudioRoute isEqualToString:@"bluetooth"]) {
1139
+ [self routeAudioFromBluetooth];
1059
1140
  }
1141
+ } else {
1142
+ // No user selection, accept iOS's routing decision
1143
+ [self handleNewDeviceAvailable];
1060
1144
  }
1145
+
1146
+ [self sendAudioDeviceEvent];
1061
1147
  break;
1148
+ }
1062
1149
  case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
1063
- @try {
1064
- if (self->hasListeners && [self canSendEvents]) {
1065
- [self sendEventWithName:@"onAudioDeviceChanged"
1066
- body:@{
1067
- @"availableAudioDeviceList": @"[speaker, earpiece]",
1068
- @"selectedAudioDevice": @"speaker",
1069
- }];
1070
- }
1071
- } @catch (NSException *exception) {
1072
- NSLog(@"Error sending event 'onAudioDeviceChanged' (OldDeviceUnavailable): %@", exception.reason);
1073
- }
1150
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: OldDeviceUnavailable"];
1151
+ [self updateAudioRoute:YES];
1074
1152
  break;
1075
1153
  case AVAudioSessionRouteChangeReasonCategoryChange:
1076
- // NSLog(@"🧑‍💻 RNInCallManager.AudioRouteChange.Reason: CategoryChange. category=%@ mode=%@", self->_audioSession.category, self->_audioSession.mode);
1154
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: CategoryChange. category=%@ mode=%@", self->_audioSession.category, self->_audioSession.mode];
1155
+ // Only send event, don't trigger route changes to avoid loops
1156
+ [self sendAudioDeviceEvent];
1077
1157
  break;
1078
1158
  case AVAudioSessionRouteChangeReasonOverride:
1079
- // NSLog(@"🧑‍💻 RNInCallManager.AudioRouteChange.Reason: Override");
1159
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: Override - Skipping event"];
1160
+ // Skip sending event for Override notifications
1161
+ // These fire during transitions and can report incorrect routes
1162
+ // Actual route changes will be caught by NewDeviceAvailable/OldDeviceUnavailable
1080
1163
  break;
1081
1164
  case AVAudioSessionRouteChangeReasonWakeFromSleep:
1082
- // NSLog(@"RNInCallManager.AudioRouteChange.Reason: WakeFromSleep");
1165
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: WakeFromSleep"];
1083
1166
  break;
1084
1167
  case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
1085
- // NSLog(@"🧑‍💻 RNInCallManager.AudioRouteChange.Reason: NoSuitableRouteForCategory");
1168
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: NoSuitableRouteForCategory"];
1086
1169
  break;
1087
1170
  case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
1088
- // NSLog(@"🧑‍💻 RNInCallManager.AudioRouteChange.Reason: RouteConfigurationChange. category=%@ mode=%@", self->_audioSession.category, self->_audioSession.mode);
1171
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: RouteConfigurationChange. category=%@ mode=%@", self->_audioSession.category, self->_audioSession.mode];
1172
+
1173
+ // WebRTC/Video capture may change category and disable Bluetooth
1174
+ // Re-enable it if it was disabled
1175
+ AVAudioSessionCategoryOptions currentOptions = [self->_audioSession categoryOptions];
1176
+ BOOL hasAllowBluetooth = (currentOptions & AVAudioSessionCategoryOptionAllowBluetooth) != 0;
1177
+
1178
+ if (!hasAllowBluetooth && self->_audioSessionInitialized) {
1179
+ [RTKLogger warning:@"⚠️ WebRTC/System disabled Bluetooth! Re-enabling it now"];
1180
+ [self audioSessionSetCategory:self->_incallAudioCategory
1181
+ options:AVAudioSessionCategoryOptionAllowBluetooth
1182
+ callerMemo:@"RouteConfigurationChange"];
1183
+
1184
+ // Ensure we're in VideoChat mode after WebRTC changes
1185
+ if (![self->_audioSession.mode isEqualToString:AVAudioSessionModeVideoChat]) {
1186
+ [self audioSessionSetMode:AVAudioSessionModeVideoChat
1187
+ callerMemo:@"RouteConfigurationChange"];
1188
+ self->_incallAudioMode = AVAudioSessionModeVideoChat;
1189
+ }
1190
+
1191
+ [RTKLogger debug:@"✅ Re-enabled Bluetooth after WebRTC changed category"];
1192
+ }
1193
+
1194
+ // Send event to update device list
1195
+ [self sendAudioDeviceEvent];
1089
1196
  break;
1090
1197
  default:
1091
- // NSLog(@"🧑‍💻 RNInCallManager.AudioRouteChange.Reason: Unknow Value");
1198
+ [RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: Unknown Value"];
1092
1199
  break;
1093
1200
  }
1094
- [self updateAudioRoute];
1095
1201
 
1096
1202
  NSNumber *silenceSecondaryAudioHintType = [notification.userInfo objectForKey:@"AVAudioSessionSilenceSecondaryAudioHintTypeKey"];
1097
1203
  NSUInteger silenceSecondaryAudioHintTypeValue = [silenceSecondaryAudioHintType unsignedIntegerValue];
1098
- // switch (silenceSecondaryAudioHintTypeValue) {
1099
- // case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
1100
- // // NSLog(@"RNInCallManager.AudioRouteChange.SilenceSecondaryAudioHint: Begin");
1101
- // case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
1102
- // // NSLog(@"RNInCallManager.AudioRouteChange.SilenceSecondaryAudioHint: End");
1103
- // default:
1104
- // // NSLog(@"RNInCallManager.AudioRouteChange.SilenceSecondaryAudioHint: Unknow Value");
1105
- // }
1106
1204
  }];
1107
1205
 
1108
1206
  _isAudioSessionRouteChangeRegistered = YES;
@@ -1114,7 +1212,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1114
1212
  return;
1115
1213
  }
1116
1214
 
1117
- // NSLog(@"RNInCallManager.stopAudioSessionRouteChangeNotification()");
1215
+ [RTKLogger debug:@"RNInCallManager.stopAudioSessionRouteChangeNotification()"];
1118
1216
  // --- remove all no matter what object
1119
1217
  [self stopObserve:_audioSessionRouteChangeObserver
1120
1218
  name:AVAudioSessionRouteChangeNotification
@@ -1128,7 +1226,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1128
1226
  return;
1129
1227
  }
1130
1228
 
1131
- // NSLog(@"RNInCallManager.startAudioSessionMediaServicesWereLostNotification()");
1229
+ [RTKLogger debug:@"RNInCallManager.startAudioSessionMediaServicesWereLostNotification()"];
1132
1230
 
1133
1231
  // --- in case it didn't deallocate when ViewDidUnload
1134
1232
  [self stopObserve:_audioSessionMediaServicesWereLostObserver
@@ -1140,7 +1238,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1140
1238
  queue:nil
1141
1239
  block:^(NSNotification *notification) {
1142
1240
  // --- This notification has no userInfo dictionary.
1143
- // NSLog(@"RNInCallManager.AudioSessionMediaServicesWereLostNotification: Media Services Were Lost");
1241
+ [RTKLogger debug:@"RNInCallManager.AudioSessionMediaServicesWereLostNotification: Media Services Were Lost"];
1144
1242
  }];
1145
1243
 
1146
1244
  _isAudioSessionMediaServicesWereLostRegistered = YES;
@@ -1152,7 +1250,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1152
1250
  return;
1153
1251
  }
1154
1252
 
1155
- // NSLog(@"RNInCallManager.stopAudioSessionMediaServicesWereLostNotification()");
1253
+ [RTKLogger debug:@"RNInCallManager.stopAudioSessionMediaServicesWereLostNotification()"];
1156
1254
 
1157
1255
  // --- remove all no matter what object
1158
1256
  [self stopObserve:_audioSessionMediaServicesWereLostObserver
@@ -1168,7 +1266,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1168
1266
  return;
1169
1267
  }
1170
1268
 
1171
- // NSLog(@"RNInCallManager.startAudioSessionMediaServicesWereResetNotification()");
1269
+ [RTKLogger debug:@"RNInCallManager.startAudioSessionMediaServicesWereResetNotification()"];
1172
1270
 
1173
1271
  // --- in case it didn't deallocate when ViewDidUnload
1174
1272
  [self stopObserve:_audioSessionMediaServicesWereResetObserver
@@ -1180,7 +1278,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1180
1278
  queue:nil
1181
1279
  block:^(NSNotification *notification) {
1182
1280
  // --- This notification has no userInfo dictionary.
1183
- // NSLog(@"RNInCallManager.AudioSessionMediaServicesWereResetNotification: Media Services Were Reset");
1281
+ [RTKLogger debug:@"RNInCallManager.AudioSessionMediaServicesWereResetNotification: Media Services Were Reset"];
1184
1282
  }];
1185
1283
 
1186
1284
  _isAudioSessionMediaServicesWereResetRegistered = YES;
@@ -1192,7 +1290,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1192
1290
  return;
1193
1291
  }
1194
1292
 
1195
- // NSLog(@"RNInCallManager.stopAudioSessionMediaServicesWereResetNotification()");
1293
+ [RTKLogger debug:@"RNInCallManager.stopAudioSessionMediaServicesWereResetNotification()"];
1196
1294
 
1197
1295
  // --- remove all no matter what object
1198
1296
  [self stopObserve:_audioSessionMediaServicesWereResetObserver
@@ -1208,7 +1306,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1208
1306
  return;
1209
1307
  }
1210
1308
 
1211
- // NSLog(@"RNInCallManager.startAudioSessionSilenceSecondaryAudioHintNotification()");
1309
+ [RTKLogger debug:@"RNInCallManager.startAudioSessionSilenceSecondaryAudioHintNotification()"];
1212
1310
 
1213
1311
  // --- in case it didn't deallocate when ViewDidUnload
1214
1312
  [self stopObserve:_audioSessionSilenceSecondaryAudioHintObserver
@@ -1228,13 +1326,13 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1228
1326
  NSUInteger silenceSecondaryAudioHintTypeValue = [silenceSecondaryAudioHintType unsignedIntegerValue];
1229
1327
  switch (silenceSecondaryAudioHintTypeValue) {
1230
1328
  case AVAudioSessionSilenceSecondaryAudioHintTypeBegin:
1231
- // NSLog(@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: Begin");
1329
+ [RTKLogger debug:@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: Begin"];
1232
1330
  break;
1233
1331
  case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
1234
- // NSLog(@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: End");
1332
+ [RTKLogger debug:@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: End"];
1235
1333
  break;
1236
1334
  default:
1237
- // NSLog(@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: Unknow Value");
1335
+ [RTKLogger debug:@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: Unknow Value"];
1238
1336
  break;
1239
1337
  }
1240
1338
  }];
@@ -1247,7 +1345,7 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1247
1345
  return;
1248
1346
  }
1249
1347
 
1250
- // NSLog(@"RNInCallManager.stopAudioSessionSilenceSecondaryAudioHintNotification()");
1348
+ [RTKLogger debug:@"RNInCallManager.stopAudioSessionSilenceSecondaryAudioHintNotification()"];
1251
1349
  // --- remove all no matter what object
1252
1350
  [self stopObserve:_audioSessionSilenceSecondaryAudioHintObserver
1253
1351
  name:AVAudioSessionSilenceSecondaryAudioHintNotification
@@ -1277,163 +1375,4 @@ RCT_EXPORT_METHOD(isBluetoothHeadsetConnected: (RCTPromiseResolveBlock) resolve
1277
1375
  object:object];
1278
1376
  }
1279
1377
 
1280
- - (NSURL *)getRingbackUri:(NSString *)_type
1281
- {
1282
- NSString *fileBundle = @"incallmanager_ringback";
1283
- NSString *fileBundleExt = @"mp3";
1284
- //NSString *fileSysWithExt = @"vc~ringing.caf"; // --- ringtone of facetime, but can't play it.
1285
- //NSString *fileSysPath = @"/System/Library/Audio/UISounds";
1286
- NSString *fileSysWithExt = @"Marimba.m4r";
1287
- NSString *fileSysPath = @"/Library/Ringtones";
1288
-
1289
- // --- you can't get default user perfrence sound in ios
1290
- NSString *type = [_type isEqualToString:@""] || [_type isEqualToString:@"_DEFAULT_"]
1291
- ? fileSysWithExt
1292
- : _type;
1293
-
1294
- NSURL *bundleUri = _bundleRingbackUri;
1295
- NSURL *defaultUri = _defaultRingbackUri;
1296
-
1297
- NSURL *uri = [self getAudioUri:type
1298
- fileBundle:fileBundle
1299
- fileBundleExt:fileBundleExt
1300
- fileSysWithExt:fileSysWithExt
1301
- fileSysPath:fileSysPath
1302
- uriBundle:&bundleUri
1303
- uriDefault:&defaultUri];
1304
-
1305
- _bundleRingbackUri = bundleUri;
1306
- _defaultRingbackUri = defaultUri;
1307
-
1308
- return uri;
1309
- }
1310
-
1311
- - (NSURL *)getBusytoneUri:(NSString *)_type
1312
- {
1313
- NSString *fileBundle = @"incallmanager_busytone";
1314
- NSString *fileBundleExt = @"mp3";
1315
- NSString *fileSysWithExt = @"ct-busy.caf"; //ct-congestion.caf
1316
- NSString *fileSysPath = @"/System/Library/Audio/UISounds";
1317
- // --- you can't get default user perfrence sound in ios
1318
- NSString *type = [_type isEqualToString:@""] || [_type isEqualToString:@"_DEFAULT_"]
1319
- ? fileSysWithExt
1320
- : _type;
1321
-
1322
- NSURL *bundleUri = _bundleBusytoneUri;
1323
- NSURL *defaultUri = _defaultBusytoneUri;
1324
-
1325
- NSURL *uri = [self getAudioUri:type
1326
- fileBundle:fileBundle
1327
- fileBundleExt:fileBundleExt
1328
- fileSysWithExt:fileSysWithExt
1329
- fileSysPath:fileSysPath
1330
- uriBundle:&bundleUri
1331
- uriDefault:&defaultUri];
1332
-
1333
- _bundleBusytoneUri = bundleUri;
1334
- _defaultBusytoneUri = defaultUri;
1335
-
1336
- return uri;
1337
- }
1338
-
1339
- - (NSURL *)getRingtoneUri:(NSString *)_type
1340
- {
1341
- NSString *fileBundle = @"incallmanager_ringtone";
1342
- NSString *fileBundleExt = @"mp3";
1343
- NSString *fileSysWithExt = @"Opening.m4r"; //Marimba.m4r
1344
- NSString *fileSysPath = @"/Library/Ringtones";
1345
- // --- you can't get default user perfrence sound in ios
1346
- NSString *type = [_type isEqualToString:@""] || [_type isEqualToString:@"_DEFAULT_"]
1347
- ? fileSysWithExt
1348
- : _type;
1349
-
1350
- NSURL *bundleUri = _bundleRingtoneUri;
1351
- NSURL *defaultUri = _defaultRingtoneUri;
1352
-
1353
- NSURL *uri = [self getAudioUri:type
1354
- fileBundle:fileBundle
1355
- fileBundleExt:fileBundleExt
1356
- fileSysWithExt:fileSysWithExt
1357
- fileSysPath:fileSysPath
1358
- uriBundle:&bundleUri
1359
- uriDefault:&defaultUri];
1360
-
1361
- _bundleRingtoneUri = bundleUri;
1362
- _defaultRingtoneUri = defaultUri;
1363
-
1364
- return uri;
1365
- }
1366
-
1367
- - (NSURL *)getAudioUri:(NSString *)_type
1368
- fileBundle:(NSString *)fileBundle
1369
- fileBundleExt:(NSString *)fileBundleExt
1370
- fileSysWithExt:(NSString *)fileSysWithExt
1371
- fileSysPath:(NSString *)fileSysPath
1372
- uriBundle:(NSURL **)uriBundle
1373
- uriDefault:(NSURL **)uriDefault
1374
- {
1375
- NSString *type = _type;
1376
- if ([type isEqualToString:@"_BUNDLE_"]) {
1377
- if (*uriBundle == nil) {
1378
- *uriBundle = [[NSBundle mainBundle] URLForResource:fileBundle withExtension:fileBundleExt];
1379
- if (*uriBundle == nil) {
1380
- // NSLog(@"RNInCallManager.getAudioUri(): %@.%@ not found in bundle.", fileBundle, fileBundleExt);
1381
- type = fileSysWithExt;
1382
- } else {
1383
- return *uriBundle;
1384
- }
1385
- } else {
1386
- return *uriBundle;
1387
- }
1388
- }
1389
-
1390
- if (*uriDefault == nil) {
1391
- NSString *target = [NSString stringWithFormat:@"%@/%@", fileSysPath, type];
1392
- *uriDefault = [self getSysFileUri:target];
1393
- }
1394
- return *uriDefault;
1395
- }
1396
-
1397
- - (NSURL *)getSysFileUri:(NSString *)target
1398
- {
1399
- NSURL *url = [[NSURL alloc] initFileURLWithPath:target isDirectory:NO];
1400
-
1401
- if (url != nil) {
1402
- NSString *path = url.path;
1403
- if (path != nil) {
1404
- NSFileManager *fileManager = [[NSFileManager alloc] init];
1405
- BOOL isTargetDirectory;
1406
- if ([fileManager fileExistsAtPath:path isDirectory:&isTargetDirectory]) {
1407
- if (!isTargetDirectory) {
1408
- return url;
1409
- }
1410
- }
1411
- }
1412
- }
1413
- // NSLog(@"RNInCallManager.getSysFileUri(): can not get url for %@", target);
1414
- return nil;
1415
- }
1416
-
1417
- #pragma mark - AVAudioPlayerDelegate
1418
-
1419
- // --- this only called when all loop played. it means, an infinite (numberOfLoops = -1) loop will never into here.
1420
- - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
1421
- successfully:(BOOL)flag
1422
- {
1423
- NSString *filename = player.url.URLByDeletingPathExtension.lastPathComponent;
1424
- // NSLog(@"RNInCallManager.audioPlayerDidFinishPlaying(): finished playing: %@", filename);
1425
- if ([filename isEqualToString:_bundleBusytoneUri.URLByDeletingPathExtension.lastPathComponent]
1426
- || [filename isEqualToString:_defaultBusytoneUri.URLByDeletingPathExtension.lastPathComponent]) {
1427
- //[self stopBusytone];
1428
- // NSLog(@"RNInCallManager.audioPlayerDidFinishPlaying(): busytone finished, invoke stop()");
1429
- [self stop:@""];
1430
- }
1431
- }
1432
-
1433
- - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player
1434
- error:(NSError *)error
1435
- {
1436
- NSString *filename = player.url.URLByDeletingPathExtension.lastPathComponent;
1437
- // NSLog(@"RNInCallManager.audioPlayerDecodeErrorDidOccur(): player=%@, error=%@", filename, error.localizedDescription);
1438
- }
1439
1378
  @end