ソースを参照

[iOS] Proxy CallKit API to be a feature of the SDK

With this the RN component and the consumer app can share same CallKit
provider, configuration, and enable to be part of multiple listeners of
the CallKit flow events. The main driver of this is to enable the
consumer app to be able to report an incoming call to the OS before
loading the JitsiMeetView. Once the user answers the call, the app can
instantiate a JitsiMeetView, pass the CallKit call UUIID, and the Jitsi
Meet components will handle the connection and report back to CallKit
that the  call has been established.
master
Daniel Ornelas 7年前
コミット
e5309a6482

+ 20
- 0
ios/sdk/sdk.xcodeproj/project.pbxproj ファイルの表示

@@ -38,6 +38,9 @@
38 38
 		B386B85D20981A75000DEF7A /* Invite.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85620981A75000DEF7A /* Invite.m */; };
39 39
 		C6245F5D2053091D0040BE68 /* image-resize@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5B2053091D0040BE68 /* image-resize@2x.png */; };
40 40
 		C6245F5E2053091D0040BE68 /* image-resize@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C6245F5C2053091D0040BE68 /* image-resize@3x.png */; };
41
+		C69EFA0C209A0F660027712B /* JMCallKitNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA09209A0F650027712B /* JMCallKitNotifier.swift */; };
42
+		C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */; };
43
+		C69EFA0E209A0F660027712B /* JMCallKitEventListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitEventListener.swift */; };
41 44
 		C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; };
42 45
 		C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
43 46
 		C6F99C15204DB63E0001F710 /* JitsiMeetView+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */; };
@@ -83,6 +86,9 @@
83 86
 		B386B85620981A75000DEF7A /* Invite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Invite.m; sourceTree = "<group>"; };
84 87
 		C6245F5B2053091D0040BE68 /* image-resize@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@2x.png"; path = "src/picture-in-picture/image-resize@2x.png"; sourceTree = "<group>"; };
85 88
 		C6245F5C2053091D0040BE68 /* image-resize@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "image-resize@3x.png"; path = "src/picture-in-picture/image-resize@3x.png"; sourceTree = "<group>"; };
89
+		C69EFA09209A0F650027712B /* JMCallKitNotifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitNotifier.swift; sourceTree = "<group>"; };
90
+		C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitProxy.swift; sourceTree = "<group>"; };
91
+		C69EFA0B209A0F660027712B /* JMCallKitEventListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JMCallKitEventListener.swift; sourceTree = "<group>"; };
86 92
 		C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = "<group>"; };
87 93
 		C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = "<group>"; };
88 94
 		C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = "<group>"; };
@@ -138,6 +144,7 @@
138 144
 		0BD906E71EC0C00300C8C18E /* src */ = {
139 145
 			isa = PBXGroup;
140 146
 			children = (
147
+				C69EFA02209A0EFD0027712B /* callkit */,
141 148
 				B386B84F20981A11000DEF7A /* invite */,
142 149
 				C6A3426B204F127900E062DD /* picture-in-picture */,
143 150
 				0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
@@ -198,6 +205,16 @@
198 205
 			name = Pods;
199 206
 			sourceTree = "<group>";
200 207
 		};
208
+		C69EFA02209A0EFD0027712B /* callkit */ = {
209
+			isa = PBXGroup;
210
+			children = (
211
+				C69EFA0B209A0F660027712B /* JMCallKitEventListener.swift */,
212
+				C69EFA09209A0F650027712B /* JMCallKitNotifier.swift */,
213
+				C69EFA0A209A0F660027712B /* JMCallKitProxy.swift */,
214
+			);
215
+			path = callkit;
216
+			sourceTree = "<group>";
217
+		};
201 218
 		C6A3426B204F127900E062DD /* picture-in-picture */ = {
202 219
 			isa = PBXGroup;
203 220
 			children = (
@@ -390,7 +407,10 @@
390 407
 				0BCA495F1EC4B6C600B793EE /* AudioMode.m in Sources */,
391 408
 				0B44A0191F902126009D1D64 /* MPVolumeViewManager.m in Sources */,
392 409
 				0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
410
+				C69EFA0C209A0F660027712B /* JMCallKitNotifier.swift in Sources */,
393 411
 				C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
412
+				C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
413
+				C69EFA0E209A0F660027712B /* JMCallKitEventListener.swift in Sources */,
394 414
 				0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
395 415
 			);
396 416
 			runOnlyForDeploymentPostprocessing = 0;

+ 59
- 125
ios/sdk/src/CallKit.m ファイルの表示

@@ -27,6 +27,8 @@
27 27
 #import <React/RCTEventEmitter.h>
28 28
 #import <React/RCTUtils.h>
29 29
 
30
+#import <JitsiMeet/JitsiMeet-Swift.h>
31
+
30 32
 // The events emitted/supported by RNCallKit:
31 33
 static NSString * const RNCallKitPerformAnswerCallAction
32 34
     = @"performAnswerCallAction";
@@ -37,14 +39,10 @@ static NSString * const RNCallKitPerformSetMutedCallAction
37 39
 static NSString * const RNCallKitProviderDidReset
38 40
     = @"providerDidReset";
39 41
 
40
-@interface RNCallKit : RCTEventEmitter <CXProviderDelegate>
42
+@interface RNCallKit : RCTEventEmitter <JMCallKitEventListener>
41 43
 @end
42 44
 
43 45
 @implementation RNCallKit
44
-{
45
-    CXCallController *_callController;
46
-    CXProvider *_provider;
47
-}
48 46
 
49 47
 RCT_EXTERN void RCTRegisterModule(Class);
50 48
 
@@ -70,35 +68,8 @@ RCT_EXTERN void RCTRegisterModule(Class);
70 68
     ];
71 69
 }
72 70
 
73
-// Display the incoming call to the user
74
-RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)callUUID
75
-                               handle:(NSString *)handle
76
-                             hasVideo:(BOOL)hasVideo
77
-                              resolve:(RCTPromiseResolveBlock)resolve
78
-                               reject:(RCTPromiseRejectBlock)reject) {
79
-#ifdef DEBUG
80
-    NSLog(@"[RNCallKit][displayIncomingCall] callUUID = %@", callUUID);
81
-#endif
82
-
83
-    NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
84
-    CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
85
-    callUpdate.remoteHandle
86
-        = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
87
-    callUpdate.supportsDTMF = NO;
88
-    callUpdate.supportsHolding = NO;
89
-    callUpdate.supportsGrouping = NO;
90
-    callUpdate.supportsUngrouping = NO;
91
-    callUpdate.hasVideo = hasVideo;
92
-
93
-    [self.provider reportNewIncomingCallWithUUID:callUUID_
94
-                                          update:callUpdate
95
-                                      completion:^(NSError * _Nullable error) {
96
-        if (error) {
97
-            reject(nil, @"Error reporting new incoming call", error);
98
-        } else {
99
-            resolve(nil);
100
-        }
101
-    }];
71
+- (void)dealloc {
72
+    [JMCallKitProxy removeListener:self];
102 73
 }
103 74
 
104 75
 // End call
@@ -141,14 +112,12 @@ RCT_EXPORT_METHOD(setProviderConfiguration:(NSDictionary *)dictionary) {
141 112
         dictionary);
142 113
 #endif
143 114
 
144
-    CXProviderConfiguration *configuration
145
-        = [self providerConfigurationFromDictionary:dictionary];
146
-    if (_provider) {
147
-        _provider.configuration = configuration;
148
-    } else {
149
-        _provider = [[CXProvider alloc] initWithConfiguration:configuration];
150
-        [_provider setDelegate:self queue:nil];
115
+    if (![JMCallKitProxy hasProviderBeenConfigurated]) {
116
+        [self configureProviderFromDictionary:dictionary];
151 117
     }
118
+    
119
+    // register to receive CallKit proxy events
120
+    [JMCallKitProxy addListener: self];
152 121
 }
153 122
 
154 123
 // Start outgoing call
@@ -162,6 +131,15 @@ RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
162 131
 #endif
163 132
 
164 133
     NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
134
+    
135
+    // Don't start a call action if there's
136
+    // an active call for this UUID
137
+    // (i.e. JitsiMeetView was configured from an incoming call
138
+    if ([JMCallKitProxy hasActiveCallForUUID:callUUID]) {
139
+        resolve(nil);
140
+        return;
141
+    }
142
+    
165 143
     CXHandle *handle_
166 144
         = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
167 145
     CXStartCallAction *action
@@ -177,9 +155,9 @@ RCT_EXPORT_METHOD(reportCallFailed:(NSString *)callUUID
177 155
                            resolve:(RCTPromiseResolveBlock)resolve
178 156
                             reject:(RCTPromiseRejectBlock)reject) {
179 157
     NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
180
-    [self.provider reportCallWithUUID:callUUID_
181
-                          endedAtDate:[NSDate date]
182
-                               reason:CXCallEndedReasonFailed];
158
+    [JMCallKitProxy reportCallWith:callUUID_
159
+                         endedAt:nil
160
+                          reason:CXCallEndedReasonFailed];
183 161
     resolve(nil);
184 162
 }
185 163
 
@@ -188,8 +166,8 @@ RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)callUUID
188 166
                                       resolve:(RCTPromiseResolveBlock)resolve
189 167
                                        reject:(RCTPromiseRejectBlock)reject) {
190 168
     NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
191
-    [self.provider reportOutgoingCallWithUUID:callUUID_
192
-                              connectedAtDate:[NSDate date]];
169
+    [JMCallKitProxy reportOutgoingCallWith:callUUID_
170
+                             connectedAt:nil];
193 171
     resolve(nil);
194 172
 }
195 173
 
@@ -206,36 +184,20 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
206 184
 #endif
207 185
 
208 186
     NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
209
-    CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
210
-    if (options[@"displayName"]) {
211
-        callUpdate.localizedCallerName = options[@"displayName"];
212
-    }
213
-    if (options[@"hasVideo"]) {
214
-        callUpdate.hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
215
-    }
216
-    [self.provider reportCallWithUUID:callUUID_ updated:callUpdate];
187
+    NSString *displayName = options[@"displayName"];
188
+    BOOL hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
189
+    
190
+    [JMCallKitProxy reportCallUpdateWith:callUUID_
191
+                                handle:nil
192
+                           displayName:displayName
193
+                              hasVideo:hasVideo];
194
+    
217 195
     resolve(nil);
218 196
 }
219 197
 
220 198
 #pragma mark - Helper methods
221 199
 
222
-- (CXCallController *)callController {
223
-    if (!_callController) {
224
-        _callController = [[CXCallController alloc] init];
225
-    }
226
-
227
-    return _callController;
228
-}
229
-
230
-- (CXProvider *)provider {
231
-    if (!_provider) {
232
-        [self setProviderConfiguration:nil];
233
-    }
234
-
235
-    return _provider;
236
-}
237
-
238
-- (CXProviderConfiguration *)providerConfigurationFromDictionary:(NSDictionary* )dictionary {
200
+- (void)configureProviderFromDictionary:(NSDictionary* )dictionary {
239 201
 #ifdef DEBUG
240 202
     NSLog(@"[RNCallKit][providerConfigurationFromDictionary:]");
241 203
 #endif
@@ -251,30 +213,25 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
251 213
             = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];
252 214
     }
253 215
 
254
-    CXProviderConfiguration *providerConfiguration
255
-        = [[CXProviderConfiguration alloc] initWithLocalizedName:localizedName];
256
-
257 216
     // iconTemplateImageData
258 217
     NSString *iconTemplateImageName = dictionary[@"iconTemplateImageName"];
218
+    NSData *iconTemplateImageData;
259 219
     if (iconTemplateImageName) {
260 220
         UIImage *iconTemplateImage
261 221
             = [UIImage imageNamed:iconTemplateImageName
262 222
                          inBundle:[NSBundle bundleForClass:self.class]
263 223
     compatibleWithTraitCollection:nil];
264 224
         if (iconTemplateImage) {
265
-            providerConfiguration.iconTemplateImageData
225
+            iconTemplateImageData
266 226
                 = UIImagePNGRepresentation(iconTemplateImage);
267 227
         }
268 228
     }
269 229
 
270
-    providerConfiguration.maximumCallGroups = 1;
271
-    providerConfiguration.maximumCallsPerCallGroup = 1;
272
-    providerConfiguration.ringtoneSound = dictionary[@"ringtoneSound"];
273
-    providerConfiguration.supportedHandleTypes
274
-        = [NSSet setWithObjects:@(CXHandleTypeGeneric), nil];
275
-    providerConfiguration.supportsVideo = YES;
276
-
277
-    return providerConfiguration;
230
+    NSString *ringtoneSound = dictionary[@"ringtoneSound"];
231
+    
232
+    [JMCallKitProxy configureCallKitProviderWithLocalizedName:localizedName
233
+                                              ringtoneSound:ringtoneSound
234
+                                      iconTemplateImageData:iconTemplateImageData];
278 235
 }
279 236
 
280 237
 - (void)requestTransaction:(CXTransaction *)transaction
@@ -284,8 +241,8 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
284 241
     NSLog(@"[RNCallKit][requestTransaction] transaction = %@", transaction);
285 242
 #endif
286 243
 
287
-    [self.callController requestTransaction:transaction
288
-                                 completion:^(NSError * _Nullable error) {
244
+    [JMCallKitProxy request:transaction
245
+               completion:^(NSError * _Nullable error) {
289 246
         if (error) {
290 247
             NSLog(
291 248
                 @"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)",
@@ -298,10 +255,10 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
298 255
     }];
299 256
 }
300 257
 
301
-#pragma mark - CXProviderDelegate
258
+#pragma mark - JitsiMeetCallKitListener
302 259
 
303 260
 // Called when the provider has been reset. We should terminate all calls.
304
-- (void)providerDidReset:(CXProvider *)provider {
261
+- (void)providerDidReset {
305 262
 #ifdef DEBUG
306 263
     NSLog(@"[RNCallKit][CXProviderDelegate][providerDidReset:]");
307 264
 #endif
@@ -310,84 +267,61 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
310 267
 }
311 268
 
312 269
 // Answering incoming call
313
-- (void)         provider:(CXProvider *)provider
314
-  performAnswerCallAction:(CXAnswerCallAction *)action {
270
+- (void) performAnswerCallWithUUID:(NSUUID *)UUID {
315 271
 #ifdef DEBUG
316 272
     NSLog(@"[RNCallKit][CXProviderDelegate][provider:performAnswerCallAction:]");
317 273
 #endif
318 274
 
319 275
     [self sendEventWithName:RNCallKitPerformAnswerCallAction
320
-                       body:@{ @"callUUID": action.callUUID.UUIDString }];
321
-    [action fulfill];
276
+                       body:@{ @"callUUID": UUID.UUIDString }];
322 277
 }
323 278
 
324 279
 // Call ended, user request
325
-- (void)      provider:(CXProvider *)provider
326
-  performEndCallAction:(CXEndCallAction *)action {
280
+- (void) performEndCallWithUUID:(NSUUID *)UUID {
327 281
 #ifdef DEBUG
328 282
     NSLog(@"[RNCallKit][CXProviderDelegate][provider:performEndCallAction:]");
329 283
 #endif
330 284
 
331 285
     [self sendEventWithName:RNCallKitPerformEndCallAction
332
-                       body:@{ @"callUUID": action.callUUID.UUIDString }];
333
-    [action fulfill];
286
+                       body:@{ @"callUUID": UUID.UUIDString }];
334 287
 }
335 288
 
336 289
 // Handle audio mute from CallKit view
337
-- (void)           provider:(CXProvider *)provider
338
-  performSetMutedCallAction:(CXSetMutedCallAction *)action {
290
+- (void) performSetMutedCallWithUUID:(NSUUID *)UUID
291
+                             isMuted:(BOOL)isMuted {
339 292
 #ifdef DEBUG
340 293
     NSLog(@"[RNCallKit][CXProviderDelegate][provider:performSetMutedCallAction:]");
341 294
 #endif
342 295
 
343 296
     [self sendEventWithName:RNCallKitPerformSetMutedCallAction
344 297
                        body:@{
345
-                           @"callUUID": action.callUUID.UUIDString,
346
-                           @"muted": @(action.muted)
298
+                           @"callUUID": UUID.UUIDString,
299
+                           @"muted": @(isMuted)
347 300
                        }];
348
-    [action fulfill];
349 301
 }
350 302
 
351 303
 // Starting outgoing call
352
-- (void)        provider:(CXProvider *)provider
353
-  performStartCallAction:(CXStartCallAction *)action {
304
+- (void) performStartCallWithUUID:(NSUUID *)UUID
305
+                          isVideo:(BOOL)isVideo {
354 306
 #ifdef DEBUG
355 307
     NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction:]");
356 308
 #endif
357
-
358
-    [action fulfill];
359
-
360
-    // Update call info.
361
-    NSUUID *callUUID = action.callUUID;
362
-    CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
363
-    callUpdate.remoteHandle = action.handle;
364
-    callUpdate.supportsDTMF = NO;
365
-    callUpdate.supportsHolding = NO;
366
-    callUpdate.supportsGrouping = NO;
367
-    callUpdate.supportsUngrouping = NO;
368
-    callUpdate.hasVideo = action.isVideo;
369
-    [provider reportCallWithUUID:callUUID updated:callUpdate];
370
-
371
-    // Notify the system about the outgoing call.
372
-    [provider reportOutgoingCallWithUUID:callUUID
373
-                 startedConnectingAtDate:[NSDate date]];
309
+    [JMCallKitProxy reportOutgoingCallWith:UUID
310
+                     startedConnectingAt:nil];
374 311
 }
375 312
 
376 313
 // The following just help with debugging:
377 314
 #ifdef DEBUG
378 315
 
379
-- (void)         provider:(CXProvider *)provider
380
-  didActivateAudioSession:(AVAudioSession *)audioSession {
316
+- (void) providerDidActivateAudioSessionWithSession:(AVAudioSession *)session {
381 317
     NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession:]");
382 318
 }
383 319
 
384
-- (void)           provider:(CXProvider *)provider
385
-  didDeactivateAudioSession:(AVAudioSession *)audioSession {
320
+- (void) providerDidDeactivateAudioSessionWithSession:(AVAudioSession *)session {
386 321
     NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession:]");
387 322
 }
388 323
 
389
-- (void)          provider:(CXProvider *)provider
390
-  timedOutPerformingAction:(CXAction *)action {
324
+- (void) providerTimedOutPerformingActionWithAction:(CXAction *)action {
391 325
     NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction:]");
392 326
 }
393 327
 

+ 63
- 0
ios/sdk/src/callkit/JMCallKitEventListener.swift ファイルの表示

@@ -0,0 +1,63 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+import AVKit
18
+import CallKit
19
+import Foundation
20
+
21
+@objc public protocol JMCallKitEventListener: NSObjectProtocol {
22
+    
23
+    @available(iOS 10.0, *)
24
+    @objc optional func providerDidReset()
25
+    
26
+    @available(iOS 10.0, *)
27
+    @objc optional func performAnswerCall(UUID: UUID)
28
+    
29
+    @available(iOS 10.0, *)
30
+    @objc optional func performEndCall(UUID: UUID)
31
+    
32
+    @available(iOS 10.0, *)
33
+    @objc optional func performSetMutedCall(UUID: UUID, isMuted: Bool)
34
+    
35
+    @available(iOS 10.0, *)
36
+    @objc optional func performStartCall(UUID: UUID, isVideo: Bool)
37
+    
38
+    @available(iOS 10.0, *)
39
+    @objc optional func providerDidActivateAudioSession(session: AVAudioSession)
40
+    
41
+    @available(iOS 10.0, *)
42
+    @objc optional func providerDidDeactivateAudioSession(session: AVAudioSession)
43
+    
44
+    @available(iOS 10.0, *)
45
+    @objc optional func providerTimedOutPerformingAction(action: CXAction)
46
+}
47
+
48
+internal struct JMCallKitEventListenerWrapper: Hashable {
49
+    
50
+    public var hashValue: Int
51
+    
52
+    internal weak var listener: JMCallKitEventListener?
53
+    
54
+    public init(listener: JMCallKitEventListener) {
55
+        self.listener = listener
56
+        self.hashValue = listener.hash
57
+    }
58
+    
59
+    public static func ==(lhs: JMCallKitEventListenerWrapper,
60
+                          rhs: JMCallKitEventListenerWrapper) -> Bool {
61
+        return lhs.hashValue == rhs.hashValue
62
+    }
63
+}

+ 104
- 0
ios/sdk/src/callkit/JMCallKitNotifier.swift ファイルの表示

@@ -0,0 +1,104 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+import AVKit
18
+import CallKit
19
+import Foundation
20
+
21
+internal final class JMCallKitNotifier: NSObject, CXProviderDelegate {
22
+    
23
+    private var listeners = Set<JMCallKitEventListenerWrapper>()
24
+    
25
+    internal override init() {}
26
+    
27
+    // MARK: - Add/remove listeners
28
+    
29
+    func addListener(_ listener: JMCallKitEventListener) {
30
+        let wrapper = JMCallKitEventListenerWrapper(listener: listener)
31
+        objc_sync_enter(listeners)
32
+        listeners.insert(wrapper)
33
+        objc_sync_exit(listeners)
34
+    }
35
+    
36
+    func removeListener(_ listener: JMCallKitEventListener) {
37
+        let wrapper = JMCallKitEventListenerWrapper(listener: listener)
38
+        objc_sync_enter(listeners)
39
+        listeners.remove(wrapper)
40
+        objc_sync_exit(listeners)
41
+    }
42
+    
43
+    // MARK: - CXProviderDelegate
44
+    
45
+    func providerDidReset(_ provider: CXProvider) {
46
+        objc_sync_enter(listeners)
47
+        listeners.forEach { $0.listener?.providerDidReset?() }
48
+        objc_sync_exit(listeners)
49
+    }
50
+    
51
+    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
52
+        objc_sync_enter(listeners)
53
+        listeners.forEach { $0.listener?.performAnswerCall?(UUID: action.callUUID) }
54
+        objc_sync_exit(listeners)
55
+        
56
+        action.fulfill()
57
+    }
58
+    
59
+    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
60
+        objc_sync_enter(listeners)
61
+        listeners.forEach { $0.listener?.performEndCall?(UUID: action.callUUID) }
62
+        objc_sync_exit(listeners)
63
+        
64
+        action.fulfill()
65
+    }
66
+    
67
+    func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
68
+        objc_sync_enter(listeners)
69
+        listeners.forEach {
70
+            $0.listener?.performSetMutedCall?(UUID: action.callUUID,
71
+                                             isMuted: action.isMuted)
72
+        }
73
+        objc_sync_exit(listeners)
74
+        
75
+        action.fulfill()
76
+    }
77
+    
78
+    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
79
+        objc_sync_enter(listeners)
80
+        listeners.forEach {
81
+            $0.listener?.performStartCall?(UUID: action.callUUID,
82
+                                          isVideo: action.isVideo)
83
+        }
84
+        objc_sync_exit(listeners)
85
+        
86
+        action.fulfill()
87
+    }
88
+    
89
+    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
90
+        objc_sync_enter(listeners)
91
+        listeners.forEach {
92
+            $0.listener?.providerDidActivateAudioSession?(session: audioSession)
93
+        }
94
+        objc_sync_exit(listeners)
95
+    }
96
+    
97
+    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
98
+        objc_sync_enter(listeners)
99
+        listeners.forEach {
100
+            $0.listener?.providerDidDeactivateAudioSession?(session: audioSession)
101
+        }
102
+        objc_sync_exit(listeners)
103
+    }
104
+}

+ 177
- 0
ios/sdk/src/callkit/JMCallKitProxy.swift ファイルの表示

@@ -0,0 +1,177 @@
1
+/*
2
+ * Copyright @ 2018-present Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+import CallKit
18
+import Foundation
19
+
20
+/// JitsiMeet CallKit proxy
21
+@available(iOS 10.0, *)
22
+@objc public final class JMCallKitProxy: NSObject {
23
+    
24
+    override private init() {}
25
+    
26
+    // MARK: - CallKit proxy
27
+    
28
+    internal static let cxProvider: CXProvider = {
29
+        let config = CXProviderConfiguration(localizedName: "")
30
+        let provider = CXProvider(configuration: config)
31
+        return provider
32
+    }()
33
+    
34
+    internal static let cxCallController: CXCallController = {
35
+        return CXCallController()
36
+    }()
37
+    
38
+    internal static let callKitNotifier: JMCallKitNotifier = {
39
+        return JMCallKitNotifier()
40
+    }()
41
+    
42
+    internal static var cxProviderConfiguration: CXProviderConfiguration? {
43
+        didSet {
44
+            guard let providerConfiguration = cxProviderConfiguration else { return }
45
+            cxProvider.configuration = providerConfiguration
46
+            cxProvider.setDelegate(callKitNotifier, queue: nil)
47
+        }
48
+    }
49
+    
50
+    /// Enables the proxy in between callkit and the consumers of the SDK
51
+    /// Default to enabled, set to false when you don't want to use callkit
52
+    @objc public static var enabled: Bool = true {
53
+        didSet {
54
+            if enabled == false {
55
+                cxProvider.setDelegate(nil, queue: nil)
56
+            }
57
+        }
58
+    }
59
+    
60
+    @objc public static func hasProviderBeenConfigurated() -> Bool {
61
+        return cxProviderConfiguration != nil
62
+    }
63
+        
64
+    @objc public static func configureCallKitProvider(localizedName: String,
65
+                                                      ringtoneSound: String?,
66
+                                                      iconTemplateImageData: Data?) {
67
+        let configuration = CXProviderConfiguration(localizedName: localizedName)
68
+        configuration.ringtoneSound = ringtoneSound
69
+        configuration.iconTemplateImageData = iconTemplateImageData
70
+        
71
+        configuration.maximumCallGroups = 1
72
+        configuration.maximumCallsPerCallGroup = 1
73
+        configuration.supportedHandleTypes = [CXHandle.HandleType.generic]
74
+        configuration.supportsVideo = true
75
+        cxProviderConfiguration = configuration
76
+    }
77
+    
78
+    @objc public static func addListener(_ listener: JMCallKitEventListener) {
79
+        callKitNotifier.addListener(listener)
80
+    }
81
+    
82
+    @objc public static func removeListener(_ listener: JMCallKitEventListener) {
83
+        callKitNotifier.removeListener(listener)
84
+    }
85
+    
86
+    @objc public static func hasActiveCallForUUID(_ callUUID: String) -> Bool {
87
+        let activeCallForUUID = cxCallController.callObserver.calls.first {
88
+            $0.uuid == UUID(uuidString: callUUID)
89
+        }
90
+        guard activeCallForUUID != nil else { return false }
91
+        return true
92
+    }
93
+    
94
+    @objc public static func reportNewIncomingCall(UUID: UUID,
95
+                                             handle: String?,
96
+                                             displayName: String?,
97
+                                             hasVideo: Bool,
98
+                                             completion: @escaping (Error?) -> Void) {
99
+        guard enabled else { return }
100
+        
101
+        let callUpdate = makeCXUpdate(handle: handle,
102
+                                      displayName: displayName,
103
+                                      hasVideo: hasVideo)
104
+        cxProvider.reportNewIncomingCall(with: UUID,
105
+                                         update: callUpdate,
106
+                                         completion: completion)
107
+    }
108
+    
109
+    @objc public static func reportCallUpdate(with UUID: UUID,
110
+                                        handle: String?,
111
+                                        displayName: String?,
112
+                                        hasVideo: Bool) {
113
+        guard enabled else { return }
114
+        
115
+        let callUpdate = makeCXUpdate(handle: handle,
116
+                                      displayName: displayName,
117
+                                      hasVideo: hasVideo)
118
+        cxProvider.reportCall(with: UUID, updated: callUpdate)
119
+    }
120
+    
121
+    @objc public static func reportCall(with UUID: UUID,
122
+                           endedAt dateEnded: Date?,
123
+                           reason endedReason: CXCallEndedReason) {
124
+        guard enabled else { return }
125
+        
126
+        cxProvider.reportCall(with: UUID,
127
+                              endedAt: dateEnded,
128
+                              reason: endedReason)
129
+    }
130
+    
131
+    @objc public static func reportOutgoingCall(with UUID: UUID,
132
+          startedConnectingAt dateStartedConnecting: Date?) {
133
+        guard enabled else { return }
134
+        
135
+        cxProvider.reportOutgoingCall(with: UUID,
136
+                                      startedConnectingAt: dateStartedConnecting)
137
+    }
138
+    
139
+    @objc public static func reportOutgoingCall(with UUID: UUID,
140
+                                          connectedAt dateConnected: Date?) {
141
+        guard enabled else { return }
142
+        
143
+        cxProvider.reportOutgoingCall(with: UUID, connectedAt: dateConnected)
144
+    }
145
+    
146
+    @objc public static func request(_ transaction: CXTransaction,
147
+                                completion: @escaping (Error?) -> Swift.Void) {
148
+        guard enabled else { return }
149
+        
150
+        cxCallController.request(transaction, completion: completion)
151
+    }
152
+    
153
+    // MARK: - Callkit Proxy helpers
154
+    
155
+    private static func makeCXUpdate(handle: String?,
156
+                                     displayName: String?,
157
+                                     hasVideo: Bool) -> CXCallUpdate {
158
+        let update = CXCallUpdate()
159
+        update.supportsDTMF = false
160
+        update.supportsHolding = false
161
+        update.supportsGrouping = false
162
+        update.supportsUngrouping = false
163
+        update.hasVideo = hasVideo
164
+        
165
+        if let handle = handle {
166
+            update.remoteHandle = CXHandle(type: .generic,
167
+                                           value: handle)
168
+        }
169
+        
170
+        if let displayName = displayName {
171
+            update.localizedCallerName = displayName
172
+        }
173
+        
174
+        return update
175
+    }
176
+
177
+}

+ 1
- 0
react/features/base/config/functions.js ファイルの表示

@@ -26,6 +26,7 @@ const WHITELISTED_KEYS = [
26 26
     'callStatsConfIDNamespace',
27 27
     'callStatsID',
28 28
     'callStatsSecret',
29
+    'callUUID',
29 30
     'channelLastN',
30 31
     'constraints',
31 32
     'debug',

+ 3
- 1
react/features/mobile/callkit/middleware.js ファイルの表示

@@ -235,10 +235,12 @@ function _conferenceWillJoin({ getState }, next, action) {
235 235
     const state = getState();
236 236
     const url = getInviteURL(state);
237 237
     const hasVideo = !isVideoMutedByAudioOnly(state);
238
+    const { callUUID } = state['features/base/config'];
238 239
 
239 240
     // When assigning the call UUID, do so in upper case, since iOS will
240 241
     // return it upper cased.
241
-    conference.callUUID = uuid.v4().toUpperCase();
242
+    conference.callUUID = (callUUID || uuid.v4()).toUpperCase();
243
+
242 244
     CallKit.startCall(conference.callUUID, url.toString(), hasVideo)
243 245
         .then(() => {
244 246
             const { room } = state['features/base/conference'];

読み込み中…
キャンセル
保存