@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.
- package/android/build.gradle +0 -2
- package/android/src/main/java/com/cloudflare/realtimekit/CoreModule.java +19 -0
- package/android/src/main/java/com/cloudflare/realtimekit/ForegroundService.java +2 -4
- package/android/src/main/java/com/cloudflare/realtimekit/RTKHelperModule.java +1 -2
- package/android/src/main/java/com/cloudflare/realtimekit/RTKLogger.java +116 -0
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/AppRTCBluetoothManager.java +75 -74
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/AppRTCUtils.java +10 -9
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallManagerModule.java +175 -872
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallProximityManager.java +9 -8
- package/android/src/main/java/com/cloudflare/realtimekit/incallmanager/InCallWakeLockUtils.java +8 -7
- package/ios/BroadcastEventEmitter.m +9 -0
- package/ios/Core.h +9 -1
- package/ios/Core.m +42 -0
- package/ios/RNInCallManager.m +639 -700
- package/ios/RTKLogger.h +51 -0
- package/ios/RTKLogger.m +107 -0
- package/ios/RTKRNBackgroundTimer.m +8 -7
- package/ios/RTKRNPermissionHandlerNotifications.m +12 -0
- package/ios/RTKRNPermissions.m +24 -1
- package/ios/RTKScreensharePickerView.m +6 -0
- package/ios/screenshare/RTKScreenshareHandler.swift +1 -1
- package/ios/screenshare/RTKScreenshareUploader.swift +3 -3
- package/ios/screenshare/RTKSocketConnection.swift +8 -8
- package/lib/commonjs/LocalMediaHandler.js +82 -24
- package/lib/commonjs/LocalMediaHandler.js.map +1 -1
- package/lib/commonjs/LocalMediaUtils.js +60 -5
- package/lib/commonjs/LocalMediaUtils.js.map +1 -1
- package/lib/commonjs/NativeAudioManager.js +7 -3
- package/lib/commonjs/NativeAudioManager.js.map +1 -1
- package/lib/commonjs/utils/version.js +1 -1
- package/lib/commonjs/utils/version.js.map +1 -1
- package/lib/module/LocalMediaHandler.js +82 -24
- package/lib/module/LocalMediaHandler.js.map +1 -1
- package/lib/module/LocalMediaUtils.js +60 -5
- package/lib/module/LocalMediaUtils.js.map +1 -1
- package/lib/module/NativeAudioManager.js +7 -3
- package/lib/module/NativeAudioManager.js.map +1 -1
- package/lib/module/utils/version.js +1 -1
- package/lib/module/utils/version.js.map +1 -1
- package/lib/typescript/LocalMediaHandler.d.ts +1 -0
- package/lib/typescript/LocalMediaUtils.d.ts +1 -0
- package/lib/typescript/NativeAudioManager.d.ts +1 -0
- package/lib/typescript/utils/version.d.ts +1 -1
- package/package.json +2 -4
package/ios/RNInCallManager.m
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
95
|
+
_incallAudioMode = AVAudioSessionModeVideoChat;
|
|
115
96
|
_incallAudioCategory = AVAudioSessionCategoryPlayAndRecord;
|
|
116
97
|
_origAudioCategory = nil;
|
|
117
98
|
_origAudioMode = nil;
|
|
118
99
|
_audioSessionInitialized = NO;
|
|
119
100
|
_forceSpeakerOn = 0;
|
|
120
|
-
|
|
101
|
+
_turnBluetoothOn = 0;
|
|
102
|
+
_userSelectedAudioRoute = nil;
|
|
103
|
+
_currentAudioRoute = nil;
|
|
104
|
+
_isUpdatingAudioRoute = NO;
|
|
121
105
|
|
|
122
|
-
|
|
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
|
|
143
|
-
auto:(BOOL)_auto
|
|
144
|
-
ringbackUriType:(NSString *)ringbackUriType)
|
|
126
|
+
RCT_EXPORT_METHOD(start)
|
|
145
127
|
{
|
|
146
|
-
|
|
128
|
+
[RTKLogger debug:@"Starting InCall manager"];
|
|
147
129
|
if (_audioSessionInitialized) {
|
|
148
130
|
return;
|
|
149
131
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
[
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
266
|
+
[RTKLogger debug:@"RNInCallManager.turnScreenOn(): ios doesn't support turnScreenOn()"];
|
|
257
267
|
}
|
|
258
268
|
|
|
259
269
|
RCT_EXPORT_METHOD(turnScreenOff)
|
|
260
270
|
{
|
|
261
|
-
|
|
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
|
-
|
|
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 =
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
332
|
-
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
430
|
+
|
|
431
|
+
[RTKLogger debug:@"Successfully routed audio to Earpiece (Bluetooth remains visible)"];
|
|
432
|
+
return true;
|
|
367
433
|
} @catch (NSException *e) {
|
|
368
|
-
|
|
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
|
-
|
|
376
|
-
BOOL success;
|
|
441
|
+
[RTKLogger debug:@"Routing audio via Loudspeaker"];
|
|
377
442
|
NSError *error = nil;
|
|
378
443
|
@try {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
472
|
-
|
|
512
|
+
RCT_EXPORT_METHOD(getIsWiredHeadsetPluggedIn:(RCTPromiseResolveBlock)resolve
|
|
513
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
473
514
|
{
|
|
474
|
-
|
|
475
|
-
|
|
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(
|
|
519
|
+
RCT_EXPORT_METHOD(getAvailableAudioDevices:(RCTPromiseResolveBlock)resolve
|
|
520
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
527
521
|
{
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
550
|
-
|
|
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(
|
|
567
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
533
|
+
RCT_EXPORT_METHOD(forceUpdateAudioDevices)
|
|
568
534
|
{
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
543
|
+
[RTKLogger debug:@"Checking if Bluetooth device is connected"];
|
|
578
544
|
NSMutableArray *devices = [NSMutableArray array];
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
//
|
|
558
|
+
// Check current route inputs for Bluetooth
|
|
595
559
|
if (devices.count == 0) {
|
|
596
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
636
|
-
//
|
|
637
|
-
|
|
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
|
-
|
|
645
|
-
//
|
|
646
|
-
|
|
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
|
|
650
|
-
BOOL isCurrentRouteToSpeaker
|
|
651
|
-
isCurrentRouteToSpeaker = [self checkAudioRoute:@[AVAudioSessionPortBuiltInSpeaker]
|
|
623
|
+
//NOTE: Check Current route
|
|
624
|
+
BOOL isCurrentRouteToSpeaker = [self checkAudioRoute:@[AVAudioSessionPortBuiltInSpeaker]
|
|
652
625
|
routeType:@"output"];
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
|
|
670
|
-
|
|
678
|
+
[RTKLogger debug:@"RNInCallManager.updateAudioRoute() audio mode has changed to %@", audioMode];
|
|
679
|
+
didChangeMode = YES;
|
|
671
680
|
} else {
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
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
|
-
|
|
731
|
+
[RTKLogger debug:@"RNInCallManager.%@: audioSession.setCategory: %@, withOptions: %lu success", callerMemo, audioCategory, (unsigned long)options];
|
|
778
732
|
} @catch (NSException *e) {
|
|
779
|
-
|
|
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
|
-
|
|
742
|
+
[RTKLogger debug:@"RNInCallManager.%@: audioSession.setMode(%@) success", callerMemo, audioMode];
|
|
789
743
|
} @catch (NSException *e) {
|
|
790
|
-
|
|
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
|
-
|
|
761
|
+
[RTKLogger debug:@"RNInCallManager.%@: audioSession.setActive(%@), withOptions: %lu success", callerMemo, audioActive ? @"YES" : @"NO", (unsigned long)options];
|
|
808
762
|
} @catch (NSException *e) {
|
|
809
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1066
|
+
[RTKLogger debug:@"RNInCallManager.AudioSessionInterruptionNotification: Began"];
|
|
1067
|
+
[self updateAudioRoute:YES];
|
|
931
1068
|
} else if ([interruptType unsignedIntegerValue] == AVAudioSessionInterruptionTypeEnded) {
|
|
932
|
-
|
|
1069
|
+
[RTKLogger debug:@"RNInCallManager.AudioSessionInterruptionNotification: Ended"];
|
|
933
1070
|
} else {
|
|
934
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1120
|
+
[RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: Unknown"];
|
|
984
1121
|
break;
|
|
985
|
-
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
-
|
|
1064
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1165
|
+
[RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: WakeFromSleep"];
|
|
1083
1166
|
break;
|
|
1084
1167
|
case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
|
|
1085
|
-
|
|
1168
|
+
[RTKLogger debug:@"RNInCallManager.AudioRouteChange.Reason: NoSuitableRouteForCategory"];
|
|
1086
1169
|
break;
|
|
1087
1170
|
case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
|
|
1088
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1329
|
+
[RTKLogger debug:@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: Begin"];
|
|
1232
1330
|
break;
|
|
1233
1331
|
case AVAudioSessionSilenceSecondaryAudioHintTypeEnd:
|
|
1234
|
-
|
|
1332
|
+
[RTKLogger debug:@"RNInCallManager.AVAudioSessionSilenceSecondaryAudioHintNotification: End"];
|
|
1235
1333
|
break;
|
|
1236
1334
|
default:
|
|
1237
|
-
|
|
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
|
-
|
|
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
|