@@ -24,10 +24,13 @@ @interface ViewController () <PKPushRegistryDelegate, TVONotificationDelegate, T
24
24
25
25
@property (nonatomic , strong ) PKPushRegistry *voipRegistry;
26
26
@property (nonatomic , strong ) void (^incomingPushCompletionCallback)(void );
27
- @property (nonatomic , strong ) TVOCallInvite *callInvite;
28
- @property (nonatomic , strong ) TVOCall *call;
29
27
@property (nonatomic , strong ) void (^callKitCompletionCallback)(BOOL );
30
28
@property (nonatomic , strong ) TVODefaultAudioDevice *audioDevice;
29
+ @property (nonatomic , strong ) NSMutableDictionary *activeCallInvites;
30
+ @property (nonatomic , strong ) NSMutableDictionary *activeCalls;
31
+
32
+ // activeCall represents the last connected call
33
+ @property (nonatomic , strong ) TVOCall *activeCall;
31
34
32
35
@property (nonatomic , strong ) CXProvider *callKitProvider;
33
36
@property (nonatomic , strong ) CXCallController *callKitCallController;
@@ -65,6 +68,9 @@ - (void)viewDidLoad {
65
68
*/
66
69
self.audioDevice = [TVODefaultAudioDevice audioDevice ];
67
70
TwilioVoice.audioDevice = self.audioDevice ;
71
+
72
+ self.activeCallInvites = [NSMutableDictionary dictionary ];
73
+ self.activeCalls = [NSMutableDictionary dictionary ];
68
74
}
69
75
70
76
- (void )configureCallKit {
@@ -100,10 +106,10 @@ - (NSString *)fetchAccessToken {
100
106
return accessToken;
101
107
}
102
108
103
- - (IBAction )placeCall : (id )sender {
104
- if (self.call && self. call . state == TVOCallStateConnected ) {
109
+ - (IBAction )mainButtonPressed : (id )sender {
110
+ if (self.activeCall != nil ) {
105
111
self.userInitiatedDisconnect = YES ;
106
- [self performEndCallActionWithUUID: self .call .uuid];
112
+ [self performEndCallActionWithUUID: self .activeCall .uuid];
107
113
[self toggleUIState: NO showCallControl: NO ];
108
114
} else {
109
115
NSUUID *uuid = [NSUUID UUID ];
@@ -189,7 +195,10 @@ - (void)toggleUIState:(BOOL)isEnabled showCallControl:(BOOL)showCallControl {
189
195
}
190
196
191
197
- (IBAction )muteSwitchToggled : (UISwitch *)sender {
192
- self.call .muted = sender.on ;
198
+ // The sample app supports toggling mute from app UI only on the last connected call.
199
+ if (self.activeCall != nil ) {
200
+ self.activeCall .muted = sender.on ;
201
+ }
193
202
}
194
203
195
204
- (IBAction )speakerSwitchToggled : (UISwitch *)sender {
@@ -311,28 +320,18 @@ - (void)callInviteReceived:(TVOCallInvite *)callInvite {
311
320
*/
312
321
313
322
NSLog (@" callInviteReceived:" );
314
-
315
- if (self.callInvite ) {
316
- NSLog (@" A CallInvite is already in progress. Ignoring the incoming CallInvite from %@ " , callInvite.from );
317
- if ([[NSProcessInfo processInfo ] operatingSystemVersion ].majorVersion < 13 ) {
318
- [self incomingPushHandled ];
319
- }
320
- return ;
321
- } else if (self.call ) {
322
- NSLog (@" Already an active call. Ignoring the incoming CallInvite from %@ " , callInvite.from );
323
- if ([[NSProcessInfo processInfo ] operatingSystemVersion ].majorVersion < 13 ) {
324
- [self incomingPushHandled ];
325
- }
326
- return ;
327
- }
328
-
329
- self.callInvite = callInvite;
330
323
331
324
NSString *from = @" Voice Bot" ;
332
325
if (callInvite.from ) {
333
326
from = [callInvite.from stringByReplacingOccurrencesOfString: @" client:" withString: @" " ];
334
327
}
328
+
329
+ // Always report to CallKit
335
330
[self reportIncomingCallFrom: from withUUID: callInvite.uuid];
331
+ self.activeCallInvites [[callInvite.uuid UUIDString ]] = callInvite;
332
+ if ([[NSProcessInfo processInfo ] operatingSystemVersion ].majorVersion < 13 ) {
333
+ [self incomingPushHandled ];
334
+ }
336
335
}
337
336
338
337
- (void )cancelledCallInviteReceived : (TVOCancelledCallInvite *)cancelledCallInvite error : (NSError *)error {
@@ -345,15 +344,17 @@ - (void)cancelledCallInviteReceived:(TVOCancelledCallInvite *)cancelledCallInvit
345
344
346
345
NSLog (@" cancelledCallInviteReceived:" );
347
346
348
- if (!self.callInvite ||
349
- ![self .callInvite.callSid isEqualToString: cancelledCallInvite.callSid]) {
350
- NSLog (@" No matching pending CallInvite. Ignoring the Cancelled CallInvite" );
351
- return ;
347
+ TVOCallInvite *callInvite;
348
+ for (TVOCallInvite *invite in self.activeCallInvites ) {
349
+ if ([cancelledCallInvite.callSid isEqualToString: invite.callSid]) {
350
+ callInvite = invite;
351
+ break ;
352
+ }
353
+ }
354
+
355
+ if (callInvite) {
356
+ [self performEndCallActionWithUUID: callInvite.uuid];
352
357
}
353
-
354
- [self performEndCallActionWithUUID: self .callInvite.uuid];
355
-
356
- self.callInvite = nil ;
357
358
}
358
359
359
360
#pragma mark - TVOCallDelegate
@@ -366,9 +367,7 @@ - (void)callDidStartRinging:(TVOCall *)call {
366
367
- (void )callDidConnect : (TVOCall *)call {
367
368
NSLog (@" callDidConnect:" );
368
369
369
- self.call = call;
370
370
self.callKitCompletionCallback (YES );
371
- self.callKitCompletionCallback = nil ;
372
371
373
372
[self .placeCallButton setTitle: @" Hang Up" forState: UIControlStateNormal];
374
373
@@ -396,7 +395,7 @@ - (void)call:(TVOCall *)call didFailToConnectWithError:(NSError *)error {
396
395
397
396
self.callKitCompletionCallback (NO );
398
397
[self performEndCallActionWithUUID: call.uuid];
399
- [self callDisconnected ];
398
+ [self callDisconnected: call ];
400
399
}
401
400
402
401
- (void )call : (TVOCall *)call didDisconnectWithError : (NSError *)error {
@@ -415,12 +414,15 @@ - (void)call:(TVOCall *)call didDisconnectWithError:(NSError *)error {
415
414
[self .callKitProvider reportCallWithUUID: call.uuid endedAtDate: [NSDate date ] reason: reason];
416
415
}
417
416
418
- [self callDisconnected ];
417
+ [self callDisconnected: call ];
419
418
}
420
419
421
- - (void )callDisconnected {
422
- self.call = nil ;
423
- self.callKitCompletionCallback = nil ;
420
+ - (void )callDisconnected : (TVOCall *)call {
421
+ if ([call isEqual: self .activeCall]) {
422
+ self.activeCall = nil ;
423
+ }
424
+ [self .activeCalls removeObjectForKey: call.uuid.UUIDString];
425
+
424
426
self.userInitiatedDisconnect = NO ;
425
427
426
428
[self stopSpin ];
@@ -533,8 +535,6 @@ - (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallActio
533
535
534
536
- (void )provider : (CXProvider *)provider performAnswerCallAction : (CXAnswerCallAction *)action {
535
537
NSLog (@" provider:performAnswerCallAction:" );
536
-
537
- NSAssert ([self .callInvite.uuid isEqual: action.callUUID], @" We only support one Invite at a time." );
538
538
539
539
self.audioDevice .enabled = NO ;
540
540
self.audioDevice .block ();
@@ -552,21 +552,27 @@ - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAct
552
552
553
553
- (void )provider : (CXProvider *)provider performEndCallAction : (CXEndCallAction *)action {
554
554
NSLog (@" provider:performEndCallAction:" );
555
-
556
- if (self.callInvite ) {
557
- [self .callInvite reject ];
558
- self.callInvite = nil ;
559
- } else if (self.call ) {
560
- [self .call disconnect ];
555
+
556
+ TVOCallInvite *callInvite = self.activeCallInvites [action.callUUID.UUIDString];
557
+ TVOCall *call = self.activeCalls [action.callUUID.UUIDString];
558
+
559
+ if (callInvite) {
560
+ [callInvite reject ];
561
+ [self .activeCallInvites removeObjectForKey: callInvite.uuid.UUIDString];
562
+ } else if (call) {
563
+ [call disconnect ];
564
+ } else {
565
+ NSLog (@" Unknown UUID to perform end-call action with" );
561
566
}
562
567
563
568
self.audioDevice .enabled = YES ;
564
569
[action fulfill ];
565
570
}
566
571
567
572
- (void )provider : (CXProvider *)provider performSetHeldCallAction : (CXSetHeldCallAction *)action {
568
- if (self.call && self.call .state == TVOCallStateConnected) {
569
- [self .call setOnHold: action.isOnHold];
573
+ TVOCall *call = self.activeCalls [action.callUUID.UUIDString];
574
+ if (call && call.state == TVOCallStateConnected) {
575
+ [call setOnHold: action.isOnHold];
570
576
[action fulfill ];
571
577
} else {
572
578
[action fail ];
@@ -646,27 +652,34 @@ - (void)performVoiceCallWithUUID:(NSUUID *)uuid
646
652
builder.params = @{kTwimlParamTo : strongSelf.outgoingValue .text };
647
653
builder.uuid = uuid;
648
654
}];
649
- self.call = [TwilioVoice connectWithOptions: connectOptions delegate: self ];
655
+ TVOCall *call = [TwilioVoice connectWithOptions: connectOptions delegate: self ];
656
+ if (call) {
657
+ self.activeCall = call;
658
+ self.activeCalls [call.uuid.UUIDString] = call;
659
+ }
650
660
self.callKitCompletionCallback = completionHandler;
651
661
}
652
662
653
663
- (void )performAnswerVoiceCallWithUUID : (NSUUID *)uuid
654
664
completion : (void (^)(BOOL success))completionHandler {
655
- __weak typeof (self) weakSelf = self;
656
- TVOAcceptOptions *acceptOptions = [TVOAcceptOptions optionsWithCallInvite: self .callInvite block: ^(TVOAcceptOptionsBuilder *builder) {
657
- __strong typeof (self) strongSelf = weakSelf;
658
- builder.uuid = strongSelf.callInvite .uuid ;
665
+ TVOCallInvite *callInvite = self.activeCallInvites [uuid.UUIDString];
666
+ NSAssert (callInvite, @" No CallInvite matches the UUID" );
667
+
668
+ TVOAcceptOptions *acceptOptions = [TVOAcceptOptions optionsWithCallInvite: callInvite block: ^(TVOAcceptOptionsBuilder *builder) {
669
+ builder.uuid = callInvite.uuid ;
659
670
}];
660
671
661
- self. call = [self . callInvite acceptWithOptions: acceptOptions delegate: self ];
672
+ TVOCall * call = [callInvite acceptWithOptions: acceptOptions delegate: self ];
662
673
663
- if (!self. call ) {
674
+ if (!call) {
664
675
completionHandler (NO );
665
676
} else {
666
677
self.callKitCompletionCallback = completionHandler;
678
+ self.activeCall = call;
679
+ self.activeCalls [call.uuid.UUIDString] = call;
667
680
}
668
-
669
- self.callInvite = nil ;
681
+
682
+ [ self .activeCallInvites removeObjectForKey: callInvite.uuid.UUIDString] ;
670
683
671
684
if ([[NSProcessInfo processInfo ] operatingSystemVersion ].majorVersion < 13 ) {
672
685
[self incomingPushHandled ];
0 commit comments