[iOS] Add initial CallKit support
This commit is contained in:
parent
8d11b3024e
commit
3b5ee2d4c6
|
@ -174,13 +174,13 @@ null and the Welcome page is enabled, the Welcome page is displayed instead.
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
Bundle configOverwrite = new Bundle();
|
Bundle config = new Bundle();
|
||||||
configOverwrite.putBoolean("startWithAudioMuted", true);
|
config.putBoolean("startWithAudioMuted", true);
|
||||||
configOverwrite.putBoolean("startWithVideoMuted", false);
|
config.putBoolean("startWithVideoMuted", false);
|
||||||
Bundle urlBundle = new Bundle();
|
Bundle urlObject = new Bundle();
|
||||||
urlBundle.putBundle("configOverwrite", configOverwrite);
|
urlObject.putBundle("config", config);
|
||||||
urlBundle.putString("url", "https://meet.jit.si/Test123");
|
urlObject.putString("url", "https://meet.jit.si/Test123");
|
||||||
view.loadURLObject(urlBundle);
|
view.loadURLObject(urlObject);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### setDefaultURL(URL)
|
#### setDefaultURL(URL)
|
||||||
|
|
|
@ -76,11 +76,11 @@ instead.
|
||||||
|
|
||||||
```objc
|
```objc
|
||||||
[jitsiMeetView loadURLObject:@{
|
[jitsiMeetView loadURLObject:@{
|
||||||
@"url": @"https://meet.jit.si/test123",
|
@"config": @{
|
||||||
@"configOverwrite": @{
|
|
||||||
@"startWithAudioMuted": @YES,
|
@"startWithAudioMuted": @YES,
|
||||||
@"startWithVideoMuted": @NO
|
@"startWithVideoMuted": @NO
|
||||||
}
|
},
|
||||||
|
@"url": @"https://meet.jit.si/test123"
|
||||||
}];
|
}];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -22,36 +22,49 @@
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
#import <React/RCTBridge.h>
|
|
||||||
#import <React/RCTConvert.h>
|
|
||||||
#import <React/RCTEventEmitter.h>
|
|
||||||
#import <React/RCTEventDispatcher.h>
|
|
||||||
#import <React/RCTUtils.h>
|
|
||||||
|
|
||||||
// Weakly load CallKit, because it's not available on iOS 9.
|
|
||||||
@import CallKit;
|
@import CallKit;
|
||||||
|
|
||||||
|
#import <React/RCTBridge.h>
|
||||||
|
#import <React/RCTConvert.h>
|
||||||
|
#import <React/RCTEventDispatcher.h>
|
||||||
|
#import <React/RCTEventEmitter.h>
|
||||||
|
#import <React/RCTUtils.h>
|
||||||
|
|
||||||
// Events we will emit.
|
// The events emitted/supported by RNCallKit:
|
||||||
static NSString *const RNCallKitPerformAnswerCallAction = @"performAnswerCallAction";
|
static NSString * const RNCallKitPerformAnswerCallAction
|
||||||
static NSString *const RNCallKitPerformEndCallAction = @"performEndCallAction";
|
= @"performAnswerCallAction";
|
||||||
static NSString *const RNCallKitPerformSetMutedCallAction = @"performSetMutedCallAction";
|
static NSString * const RNCallKitPerformEndCallAction
|
||||||
static NSString *const RNCallKitProviderDidReset = @"providerDidReset";
|
= @"performEndCallAction";
|
||||||
|
static NSString * const RNCallKitPerformSetMutedCallAction
|
||||||
|
= @"performSetMutedCallAction";
|
||||||
|
static NSString * const RNCallKitProviderDidReset
|
||||||
|
= @"providerDidReset";
|
||||||
|
|
||||||
@interface RNCallKit : RCTEventEmitter <CXProviderDelegate>
|
@interface RNCallKit : RCTEventEmitter <CXProviderDelegate>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation RNCallKit
|
@implementation RNCallKit
|
||||||
{
|
{
|
||||||
CXCallController *callKitCallController;
|
CXCallController *_callController;
|
||||||
CXProvider *callKitProvider;
|
CXProvider *_provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_MODULE()
|
RCT_EXTERN void RCTRegisterModule(Class);
|
||||||
|
|
||||||
- (NSArray<NSString *> *)supportedEvents
|
+ (void)load {
|
||||||
{
|
// Make the react-native module RNCallKit available (to JS) only if CallKit
|
||||||
|
// is available on the executing operating sytem. For example, CallKit is
|
||||||
|
// not available on iOS 9.
|
||||||
|
if ([CXCallController class]) {
|
||||||
|
RCTRegisterModule(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)moduleName {
|
||||||
|
return @"RNCallKit";
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray<NSString *> *)supportedEvents {
|
||||||
return @[
|
return @[
|
||||||
RNCallKitPerformAnswerCallAction,
|
RNCallKitPerformAnswerCallAction,
|
||||||
RNCallKitPerformEndCallAction,
|
RNCallKitPerformEndCallAction,
|
||||||
|
@ -60,33 +73,17 @@ RCT_EXPORT_MODULE()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure CallKit
|
|
||||||
RCT_EXPORT_METHOD(setup:(NSDictionary *)options)
|
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
|
||||||
NSLog(@"[RNCallKit][setup] options = %@", options);
|
|
||||||
#endif
|
|
||||||
callKitCallController = [[CXCallController alloc] init];
|
|
||||||
if (callKitProvider) {
|
|
||||||
[callKitProvider invalidate];
|
|
||||||
}
|
|
||||||
callKitProvider = [[CXProvider alloc] initWithConfiguration:[self getProviderConfiguration: options]];
|
|
||||||
[callKitProvider setDelegate:self queue:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - CXCallController call actions
|
|
||||||
|
|
||||||
// Display the incoming call to the user
|
// Display the incoming call to the user
|
||||||
RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
|
RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)callUUID
|
||||||
handle:(NSString *)handle
|
handle:(NSString *)handle
|
||||||
hasVideo:(BOOL)hasVideo
|
hasVideo:(BOOL)hasVideo
|
||||||
resolve:(RCTPromiseResolveBlock)resolve
|
resolve:(RCTPromiseResolveBlock)resolve
|
||||||
reject:(RCTPromiseRejectBlock)reject)
|
reject:(RCTPromiseRejectBlock)reject) {
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][displayIncomingCall] uuidString = %@", uuidString);
|
NSLog(@"[RNCallKit][displayIncomingCall] callUUID = %@", callUUID);
|
||||||
#endif
|
#endif
|
||||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
||||||
|
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||||
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
||||||
callUpdate.remoteHandle
|
callUpdate.remoteHandle
|
||||||
= [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
= [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
||||||
|
@ -95,149 +92,208 @@ RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
|
||||||
callUpdate.supportsGrouping = NO;
|
callUpdate.supportsGrouping = NO;
|
||||||
callUpdate.supportsUngrouping = NO;
|
callUpdate.supportsUngrouping = NO;
|
||||||
callUpdate.hasVideo = hasVideo;
|
callUpdate.hasVideo = hasVideo;
|
||||||
|
|
||||||
[callKitProvider reportNewIncomingCallWithUUID:uuid
|
[self.provider reportNewIncomingCallWithUUID:callUUID_
|
||||||
update:callUpdate
|
update:callUpdate
|
||||||
completion:^(NSError * _Nullable error) {
|
completion:^(NSError * _Nullable error) {
|
||||||
if (error == nil) {
|
if (error) {
|
||||||
resolve(nil);
|
|
||||||
} else {
|
|
||||||
reject(nil, @"Error reporting new incoming call", error);
|
reject(nil, @"Error reporting new incoming call", error);
|
||||||
|
} else {
|
||||||
|
resolve(nil);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
// End call
|
// End call
|
||||||
RCT_EXPORT_METHOD(endCall:(NSString *)uuidString
|
RCT_EXPORT_METHOD(endCall:(NSString *)callUUID
|
||||||
resolve:(RCTPromiseResolveBlock)resolve
|
resolve:(RCTPromiseResolveBlock)resolve
|
||||||
reject:(RCTPromiseRejectBlock)reject)
|
reject:(RCTPromiseRejectBlock)reject) {
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][endCall] uuidString = %@", uuidString);
|
NSLog(@"[RNCallKit][endCall] callUUID = %@", callUUID);
|
||||||
#endif
|
#endif
|
||||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
||||||
CXEndCallAction *action = [[CXEndCallAction alloc] initWithCallUUID:uuid];
|
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||||
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
|
CXEndCallAction *action
|
||||||
[self requestTransaction:transaction resolve:resolve reject:reject];
|
= [[CXEndCallAction alloc] initWithCallUUID:callUUID_];
|
||||||
|
[self requestTransaction:[[CXTransaction alloc] initWithAction:action]
|
||||||
|
resolve:resolve
|
||||||
|
reject:reject];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mute / unmute (audio)
|
// Mute / unmute (audio)
|
||||||
RCT_EXPORT_METHOD(setMuted:(NSString *)uuidString
|
RCT_EXPORT_METHOD(setMuted:(NSString *)callUUID
|
||||||
muted:(BOOL) muted
|
muted:(BOOL)muted
|
||||||
resolve:(RCTPromiseResolveBlock)resolve
|
resolve:(RCTPromiseResolveBlock)resolve
|
||||||
reject:(RCTPromiseRejectBlock)reject)
|
reject:(RCTPromiseRejectBlock)reject) {
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][setMuted] uuidString = %@", uuidString);
|
NSLog(@"[RNCallKit][setMuted] callUUID = %@", callUUID);
|
||||||
#endif
|
#endif
|
||||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
||||||
|
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||||
CXSetMutedCallAction *action
|
CXSetMutedCallAction *action
|
||||||
= [[CXSetMutedCallAction alloc] initWithCallUUID:uuid muted:muted];
|
= [[CXSetMutedCallAction alloc] initWithCallUUID:callUUID_ muted:muted];
|
||||||
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
|
[self requestTransaction:[[CXTransaction alloc] initWithAction:action]
|
||||||
[self requestTransaction:transaction resolve:resolve reject:reject];
|
resolve:resolve
|
||||||
|
reject:reject];
|
||||||
|
}
|
||||||
|
|
||||||
|
RCT_EXPORT_METHOD(setProviderConfiguration:(NSDictionary *)dictionary) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(
|
||||||
|
@"[RNCallKit][setProviderConfiguration:] dictionary = %@",
|
||||||
|
dictionary);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CXProviderConfiguration *configuration
|
||||||
|
= [self providerConfigurationFromDictionary:dictionary];
|
||||||
|
if (_provider) {
|
||||||
|
_provider.configuration = configuration;
|
||||||
|
} else {
|
||||||
|
_provider = [[CXProvider alloc] initWithConfiguration:configuration];
|
||||||
|
[_provider setDelegate:self queue:nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start outgoing call
|
// Start outgoing call
|
||||||
RCT_EXPORT_METHOD(startCall:(NSString *)uuidString
|
RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
|
||||||
handle:(NSString *)handle
|
handle:(NSString *)handle
|
||||||
video:(BOOL)video
|
video:(BOOL)video
|
||||||
resolve:(RCTPromiseResolveBlock)resolve
|
resolve:(RCTPromiseResolveBlock)resolve
|
||||||
reject:(RCTPromiseRejectBlock)reject)
|
reject:(RCTPromiseRejectBlock)reject) {
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][startCall] uuidString = %@", uuidString);
|
NSLog(@"[RNCallKit][startCall] callUUID = %@", callUUID);
|
||||||
#endif
|
#endif
|
||||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
||||||
CXHandle *callHandle
|
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||||
|
CXHandle *handle_
|
||||||
= [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
= [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
|
||||||
CXStartCallAction *action
|
CXStartCallAction *action
|
||||||
= [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle];
|
= [[CXStartCallAction alloc] initWithCallUUID:callUUID_
|
||||||
|
handle:handle_];
|
||||||
action.video = video;
|
action.video = video;
|
||||||
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
|
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
|
||||||
[self requestTransaction:transaction resolve:resolve reject:reject];
|
[self requestTransaction:transaction resolve:resolve reject:reject];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicate call failed
|
// Indicate call failed
|
||||||
RCT_EXPORT_METHOD(reportCallFailed:(NSString *)uuidString
|
RCT_EXPORT_METHOD(reportCallFailed:(NSString *)callUUID
|
||||||
resolve:(RCTPromiseResolveBlock)resolve
|
resolve:(RCTPromiseResolveBlock)resolve
|
||||||
reject:(RCTPromiseRejectBlock)reject)
|
reject:(RCTPromiseRejectBlock)reject) {
|
||||||
{
|
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
[self.provider reportCallWithUUID:callUUID_
|
||||||
[callKitProvider reportCallWithUUID:uuid
|
endedAtDate:[NSDate date]
|
||||||
endedAtDate:[NSDate date]
|
reason:CXCallEndedReasonFailed];
|
||||||
reason:CXCallEndedReasonFailed];
|
|
||||||
resolve(nil);
|
resolve(nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicate outgoing call connected
|
// Indicate outgoing call connected.
|
||||||
RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)uuidString
|
RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)callUUID
|
||||||
resolve:(RCTPromiseResolveBlock)resolve
|
resolve:(RCTPromiseResolveBlock)resolve
|
||||||
reject:(RCTPromiseRejectBlock)reject)
|
reject:(RCTPromiseRejectBlock)reject) {
|
||||||
{
|
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
[self.provider reportOutgoingCallWithUUID:callUUID_
|
||||||
[callKitProvider reportOutgoingCallWithUUID:uuid
|
connectedAtDate:[NSDate date]];
|
||||||
connectedAtDate:[NSDate date]];
|
|
||||||
resolve(nil);
|
resolve(nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update call in case we have a display name or video capability changes
|
// Update call in case we have a display name or video capability changes.
|
||||||
RCT_EXPORT_METHOD(updateCall:(NSString *)uuidString
|
RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
|
||||||
options:(NSDictionary *)options
|
options:(NSDictionary *)options
|
||||||
resolve:(RCTPromiseResolveBlock)resolve
|
resolve:(RCTPromiseResolveBlock)resolve
|
||||||
reject:(RCTPromiseRejectBlock)reject)
|
reject:(RCTPromiseRejectBlock)reject) {
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][updateCall] uuidString = %@ options = %@", uuidString, options);
|
NSLog(
|
||||||
|
@"[RNCallKit][updateCall] callUUID = %@ options = %@",
|
||||||
|
callUUID,
|
||||||
|
options);
|
||||||
#endif
|
#endif
|
||||||
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
||||||
CXCallUpdate *update = [[CXCallUpdate alloc] init];
|
NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
|
||||||
|
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
||||||
if (options[@"displayName"]) {
|
if (options[@"displayName"]) {
|
||||||
update.localizedCallerName = options[@"displayName"];
|
callUpdate.localizedCallerName = options[@"displayName"];
|
||||||
}
|
}
|
||||||
if (options[@"hasVideo"]) {
|
if (options[@"hasVideo"]) {
|
||||||
update.hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
|
callUpdate.hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
|
||||||
}
|
}
|
||||||
[callKitProvider reportCallWithUUID:uuid updated:update];
|
[self.provider reportCallWithUUID:callUUID_ updated:callUpdate];
|
||||||
resolve(nil);
|
resolve(nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Helper methods
|
#pragma mark - Helper methods
|
||||||
|
|
||||||
- (CXProviderConfiguration *)getProviderConfiguration:(NSDictionary* )settings
|
- (CXCallController *)callController {
|
||||||
{
|
if (!_callController) {
|
||||||
|
_callController = [[CXCallController alloc] init];
|
||||||
|
}
|
||||||
|
|
||||||
|
return _callController;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CXProvider *)provider {
|
||||||
|
if (!_provider) {
|
||||||
|
[self setProviderConfiguration:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
return _provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CXProviderConfiguration *)providerConfigurationFromDictionary:(NSDictionary* )dictionary {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][getProviderConfiguration]");
|
NSLog(@"[RNCallKit][providerConfigurationFromDictionary:]");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!dictionary) {
|
||||||
|
dictionary = @{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// localizedName
|
||||||
|
NSString *localizedName = dictionary[@"localizedName"];
|
||||||
|
if (!localizedName) {
|
||||||
|
localizedName
|
||||||
|
= [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];
|
||||||
|
}
|
||||||
|
|
||||||
CXProviderConfiguration *providerConfiguration
|
CXProviderConfiguration *providerConfiguration
|
||||||
= [[CXProviderConfiguration alloc] initWithLocalizedName:settings[@"appName"]];
|
= [[CXProviderConfiguration alloc] initWithLocalizedName:localizedName];
|
||||||
providerConfiguration.supportsVideo = YES;
|
|
||||||
|
// iconTemplateImageData
|
||||||
|
NSString *iconTemplateImageName = dictionary[@"iconTemplateImageName"];
|
||||||
|
if (iconTemplateImageName) {
|
||||||
|
UIImage *iconTemplateImage = [UIImage imageNamed:iconTemplateImageName];
|
||||||
|
if (iconTemplateImage) {
|
||||||
|
providerConfiguration.iconTemplateImageData
|
||||||
|
= UIImagePNGRepresentation(iconTemplateImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
providerConfiguration.maximumCallGroups = 1;
|
providerConfiguration.maximumCallGroups = 1;
|
||||||
providerConfiguration.maximumCallsPerCallGroup = 1;
|
providerConfiguration.maximumCallsPerCallGroup = 1;
|
||||||
|
providerConfiguration.ringtoneSound = dictionary[@"ringtoneSound"];
|
||||||
providerConfiguration.supportedHandleTypes
|
providerConfiguration.supportedHandleTypes
|
||||||
= [NSSet setWithObjects:[NSNumber numberWithInteger:CXHandleTypeGeneric], nil];
|
= [NSSet setWithObjects:@(CXHandleTypeGeneric), nil];
|
||||||
if (settings[@"imageName"]) {
|
providerConfiguration.supportsVideo = YES;
|
||||||
providerConfiguration.iconTemplateImageData
|
|
||||||
= UIImagePNGRepresentation([UIImage imageNamed:settings[@"imageName"]]);
|
|
||||||
}
|
|
||||||
if (settings[@"ringtoneSound"]) {
|
|
||||||
providerConfiguration.ringtoneSound = settings[@"ringtoneSound"];
|
|
||||||
}
|
|
||||||
return providerConfiguration;
|
return providerConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)requestTransaction:(CXTransaction *)transaction
|
- (void)requestTransaction:(CXTransaction *)transaction
|
||||||
resolve:(RCTPromiseResolveBlock)resolve
|
resolve:(RCTPromiseResolveBlock)resolve
|
||||||
reject:(RCTPromiseRejectBlock)reject
|
reject:(RCTPromiseRejectBlock)reject {
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][requestTransaction] transaction = %@", transaction);
|
NSLog(@"[RNCallKit][requestTransaction] transaction = %@", transaction);
|
||||||
#endif
|
#endif
|
||||||
[callKitCallController requestTransaction:transaction completion:^(NSError * _Nullable error) {
|
|
||||||
if (error == nil) {
|
[self.callController requestTransaction:transaction
|
||||||
resolve(nil);
|
completion:^(NSError * _Nullable error) {
|
||||||
} else {
|
if (error) {
|
||||||
NSLog(@"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)", transaction.actions, error);
|
NSLog(
|
||||||
|
@"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)",
|
||||||
|
transaction.actions,
|
||||||
|
error);
|
||||||
reject(nil, @"Error processing CallKit transaction", error);
|
reject(nil, @"Error processing CallKit transaction", error);
|
||||||
|
} else {
|
||||||
|
resolve(nil);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -247,53 +303,62 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)uuidString
|
||||||
// Called when the provider has been reset. We should terminate all calls.
|
// Called when the provider has been reset. We should terminate all calls.
|
||||||
- (void)providerDidReset:(CXProvider *)provider {
|
- (void)providerDidReset:(CXProvider *)provider {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:providerDidReset]");
|
NSLog(@"[RNCallKit][CXProviderDelegate][providerDidReset:]");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[self sendEventWithName:RNCallKitProviderDidReset body:nil];
|
[self sendEventWithName:RNCallKitProviderDidReset body:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Answering incoming call
|
// Answering incoming call
|
||||||
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action
|
- (void) provider:(CXProvider *)provider
|
||||||
{
|
performAnswerCallAction:(CXAnswerCallAction *)action {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performAnswerCallAction]");
|
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performAnswerCallAction:]");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[self sendEventWithName:RNCallKitPerformAnswerCallAction
|
[self sendEventWithName:RNCallKitPerformAnswerCallAction
|
||||||
body:@{ @"callUUID": action.callUUID.UUIDString }];
|
body:@{ @"callUUID": action.callUUID.UUIDString }];
|
||||||
[action fulfill];
|
[action fulfill];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call ended, user request
|
// Call ended, user request
|
||||||
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action
|
- (void) provider:(CXProvider *)provider
|
||||||
{
|
performEndCallAction:(CXEndCallAction *)action {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performEndCallAction]");
|
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performEndCallAction:]");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[self sendEventWithName:RNCallKitPerformEndCallAction
|
[self sendEventWithName:RNCallKitPerformEndCallAction
|
||||||
body:@{ @"callUUID": action.callUUID.UUIDString }];
|
body:@{ @"callUUID": action.callUUID.UUIDString }];
|
||||||
[action fulfill];
|
[action fulfill];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle audio mute from CallKit view
|
// Handle audio mute from CallKit view
|
||||||
- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action {
|
- (void) provider:(CXProvider *)provider
|
||||||
|
performSetMutedCallAction:(CXSetMutedCallAction *)action {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performSetMutedCallAction]");
|
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performSetMutedCallAction:]");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[self sendEventWithName:RNCallKitPerformSetMutedCallAction
|
[self sendEventWithName:RNCallKitPerformSetMutedCallAction
|
||||||
body:@{ @"callUUID": action.callUUID.UUIDString,
|
body:@{
|
||||||
@"muted": [NSNumber numberWithBool:action.muted]}];
|
@"callUUID": action.callUUID.UUIDString,
|
||||||
|
@"muted": @(action.muted)
|
||||||
|
}];
|
||||||
[action fulfill];
|
[action fulfill];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starting outgoing call
|
// Starting outgoing call
|
||||||
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action
|
- (void) provider:(CXProvider *)provider
|
||||||
{
|
performStartCallAction:(CXStartCallAction *)action {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction]");
|
NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction:]");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[action fulfill];
|
[action fulfill];
|
||||||
|
|
||||||
// Update call info
|
// Update call info.
|
||||||
|
NSUUID *callUUID = action.callUUID;
|
||||||
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
|
||||||
callUpdate.remoteHandle = action.handle;
|
callUpdate.remoteHandle = action.handle;
|
||||||
callUpdate.supportsDTMF = NO;
|
callUpdate.supportsDTMF = NO;
|
||||||
|
@ -301,34 +366,31 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)uuidString
|
||||||
callUpdate.supportsGrouping = NO;
|
callUpdate.supportsGrouping = NO;
|
||||||
callUpdate.supportsUngrouping = NO;
|
callUpdate.supportsUngrouping = NO;
|
||||||
callUpdate.hasVideo = action.isVideo;
|
callUpdate.hasVideo = action.isVideo;
|
||||||
[callKitProvider reportCallWithUUID:action.callUUID updated:callUpdate];
|
[provider reportCallWithUUID:callUUID updated:callUpdate];
|
||||||
|
|
||||||
// Notify the system about the outgoing call
|
// Notify the system about the outgoing call.
|
||||||
[callKitProvider reportOutgoingCallWithUUID:action.callUUID
|
[provider reportOutgoingCallWithUUID:callUUID
|
||||||
startedConnectingAtDate:[NSDate date]];
|
startedConnectingAtDate:[NSDate date]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// These just help with debugging
|
// The following just help with debugging:
|
||||||
|
|
||||||
- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession
|
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession]");
|
|
||||||
#endif
|
- (void) provider:(CXProvider *)provider
|
||||||
|
didActivateAudioSession:(AVAudioSession *)audioSession {
|
||||||
|
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession:]");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession
|
- (void) provider:(CXProvider *)provider
|
||||||
{
|
didDeactivateAudioSession:(AVAudioSession *)audioSession {
|
||||||
#ifdef DEBUG
|
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession:]");
|
||||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession]");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)provider:(CXProvider *)provider timedOutPerformingAction:(CXAction *)action
|
- (void) provider:(CXProvider *)provider
|
||||||
{
|
timedOutPerformingAction:(CXAction *)action {
|
||||||
#ifdef DEBUG
|
NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction:]");
|
||||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction]");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||||
|
|
||||||
@property (copy, nonatomic) NSURL *defaultURL;
|
@property (copy, nonatomic, nullable) NSURL *defaultURL;
|
||||||
|
|
||||||
@property (nonatomic) BOOL welcomePageEnabled;
|
@property (nonatomic) BOOL welcomePageEnabled;
|
||||||
|
|
||||||
|
|
|
@ -17,26 +17,15 @@
|
||||||
#import <CoreText/CoreText.h>
|
#import <CoreText/CoreText.h>
|
||||||
#include <mach/mach_time.h>
|
#include <mach/mach_time.h>
|
||||||
|
|
||||||
|
@import Intents;
|
||||||
|
|
||||||
#import <React/RCTAssert.h>
|
#import <React/RCTAssert.h>
|
||||||
#import <React/RCTLinkingManager.h>
|
#import <React/RCTLinkingManager.h>
|
||||||
#import <React/RCTRootView.h>
|
#import <React/RCTRootView.h>
|
||||||
|
|
||||||
#include <Availability.h>
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
|
|
||||||
#import "JitsiMeetView+Private.h"
|
#import "JitsiMeetView+Private.h"
|
||||||
#import "RCTBridgeWrapper.h"
|
#import "RCTBridgeWrapper.h"
|
||||||
|
|
||||||
// Weakly load the Intents framework since it's not available on iOS 9.
|
|
||||||
@import Intents;
|
|
||||||
|
|
||||||
// Constant describing iOS 10.0.0
|
|
||||||
static const NSOperatingSystemVersion ios10 = {
|
|
||||||
.majorVersion = 10,
|
|
||||||
.minorVersion = 0,
|
|
||||||
.patchVersion = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <tt>RCTFatalHandler</tt> implementation which swallows JavaScript errors.
|
* A <tt>RCTFatalHandler</tt> implementation which swallows JavaScript errors.
|
||||||
* In the Release configuration, React Native will (intentionally) raise an
|
* In the Release configuration, React Native will (intentionally) raise an
|
||||||
|
@ -153,50 +142,45 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||||
continueUserActivity:(NSUserActivity *)userActivity
|
continueUserActivity:(NSUserActivity *)userActivity
|
||||||
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
|
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
|
||||||
{
|
{
|
||||||
|
NSString *activityType = userActivity.activityType;
|
||||||
|
|
||||||
// XXX At least twice we received bug reports about malfunctioning loadURL
|
// XXX At least twice we received bug reports about malfunctioning loadURL
|
||||||
// in the Jitsi Meet SDK while the Jitsi Meet app seemed to functioning as
|
// in the Jitsi Meet SDK while the Jitsi Meet app seemed to functioning as
|
||||||
// expected in our testing. But that was to be expected because the app does
|
// expected in our testing. But that was to be expected because the app does
|
||||||
// not exercise loadURL. In order to increase the test coverage of loadURL,
|
// not exercise loadURL. In order to increase the test coverage of loadURL,
|
||||||
// channel Universal linking through loadURL.
|
// channel Universal linking through loadURL.
|
||||||
if ([userActivity.activityType
|
if ([activityType isEqualToString:NSUserActivityTypeBrowsingWeb]
|
||||||
isEqualToString:NSUserActivityTypeBrowsingWeb]
|
&& [self loadURLInViews:userActivity.webpageURL]) {
|
||||||
&& [JitsiMeetView loadURLInViews:userActivity.webpageURL]) {
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for CallKit intents only on iOS >= 10
|
// Check for a CallKit intent.
|
||||||
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:ios10]) {
|
if ([activityType isEqualToString:@"INStartAudioCallIntent"]
|
||||||
if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]
|
|| [activityType isEqualToString:@"INStartVideoCallIntent"]) {
|
||||||
|| [userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
|
INIntent *intent = userActivity.interaction.intent;
|
||||||
INInteraction *interaction = [userActivity interaction];
|
NSArray<INPerson *> *contacts;
|
||||||
INIntent *intent = interaction.intent;
|
NSString *url;
|
||||||
NSString *handle;
|
BOOL startAudioOnly = NO;
|
||||||
BOOL isAudio = NO;
|
|
||||||
|
|
||||||
if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
|
if ([intent isKindOfClass:[INStartAudioCallIntent class]]) {
|
||||||
INStartAudioCallIntent *startCallIntent
|
contacts = ((INStartAudioCallIntent *) intent).contacts;
|
||||||
= (INStartAudioCallIntent *)intent;
|
startAudioOnly = YES;
|
||||||
handle = startCallIntent.contacts.firstObject.personHandle.value;
|
} else if ([intent isKindOfClass:[INStartVideoCallIntent class]]) {
|
||||||
isAudio = YES;
|
contacts = ((INStartVideoCallIntent *) intent).contacts;
|
||||||
} else {
|
}
|
||||||
INStartVideoCallIntent *startCallIntent
|
|
||||||
= (INStartVideoCallIntent *)intent;
|
|
||||||
handle = startCallIntent.contacts.firstObject.personHandle.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handle) {
|
if (contacts && (url = contacts.firstObject.personHandle.value)) {
|
||||||
// Load the URL contained in the handle
|
// Load the URL contained in the handle.
|
||||||
[view loadURLObject:@{
|
[self loadURLObjectInViews:@{
|
||||||
@"url": handle,
|
@"config": @{
|
||||||
@"configOverwrite": @{
|
@"startAudioOnly": @(startAudioOnly)
|
||||||
@"startAudioOnly": @(isAudio)
|
},
|
||||||
}
|
@"url": url
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return [RCTLinkingManager application:application
|
return [RCTLinkingManager application:application
|
||||||
continueUserActivity:userActivity
|
continueUserActivity:userActivity
|
||||||
|
@ -212,7 +196,7 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||||
// expected in our testing. But that was to be expected because the app does
|
// expected in our testing. But that was to be expected because the app does
|
||||||
// not exercise loadURL. In order to increase the test coverage of loadURL,
|
// not exercise loadURL. In order to increase the test coverage of loadURL,
|
||||||
// channel Universal linking through loadURL.
|
// channel Universal linking through loadURL.
|
||||||
if ([JitsiMeetView loadURLInViews:url]) {
|
if ([self loadURLInViews:url]) {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,15 +325,20 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
|
||||||
* at least one {@code JitsiMeetView}; otherwise, {@code NO}.
|
* at least one {@code JitsiMeetView}; otherwise, {@code NO}.
|
||||||
*/
|
*/
|
||||||
+ (BOOL)loadURLInViews:(NSURL *)url {
|
+ (BOOL)loadURLInViews:(NSURL *)url {
|
||||||
|
return
|
||||||
|
[self loadURLObjectInViews:url ? @{ @"url": url.absoluteString } : nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (BOOL)loadURLObjectInViews:(NSDictionary *)urlObject {
|
||||||
BOOL handled = NO;
|
BOOL handled = NO;
|
||||||
|
|
||||||
if (views) {
|
if (views) {
|
||||||
for (NSString *externalAPIScope in views) {
|
for (NSString *externalAPIScope in views) {
|
||||||
JitsiMeetView *view
|
JitsiMeetView *view
|
||||||
= [JitsiMeetView viewForExternalAPIScope:externalAPIScope];
|
= [self viewForExternalAPIScope:externalAPIScope];
|
||||||
|
|
||||||
if (view) {
|
if (view) {
|
||||||
[view loadURL:url];
|
[view loadURLObject:urlObject];
|
||||||
handled = YES;
|
handled = YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||||
|
import { toState } from '../redux';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach a set of local tracks to a conference.
|
* Attach a set of local tracks to a conference.
|
||||||
*
|
*
|
||||||
* NOTE The function is internal to this feature.
|
|
||||||
*
|
|
||||||
* @param {JitsiConference} conference - Conference instance.
|
* @param {JitsiConference} conference - Conference instance.
|
||||||
* @param {JitsiLocalTrack[]} localTracks - List of local media tracks.
|
* @param {JitsiLocalTrack[]} localTracks - List of local media tracks.
|
||||||
|
* @protected
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
export function _addLocalTracksToConference(conference, localTracks) {
|
export function _addLocalTracksToConference(conference, localTracks) {
|
||||||
|
@ -29,14 +29,33 @@ export function _addLocalTracksToConference(conference, localTracks) {
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current {@code JitsiConference} which is joining or joined and is
|
||||||
|
* not leaving. Please note the contrast with merely reading the
|
||||||
|
* {@code conference} state of the feature base/conference which is not joining
|
||||||
|
* but may be leaving already.
|
||||||
|
*
|
||||||
|
* @param {Function|Object} stateful - The redux store, state, or
|
||||||
|
* {@code getState} function.
|
||||||
|
* @returns {JitsiConference|undefined}
|
||||||
|
*/
|
||||||
|
export function getCurrentConference(stateful) {
|
||||||
|
const { conference, joining, leaving }
|
||||||
|
= toState(stateful)['features/base/conference'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
conference
|
||||||
|
? conference === leaving ? undefined : conference
|
||||||
|
: joining);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an error thrown by the backend (i.e. lib-jitsi-meet) while
|
* Handle an error thrown by the backend (i.e. lib-jitsi-meet) while
|
||||||
* manipulating a conference participant (e.g. pin or select participant).
|
* manipulating a conference participant (e.g. pin or select participant).
|
||||||
*
|
*
|
||||||
* NOTE The function is internal to this feature.
|
|
||||||
*
|
|
||||||
* @param {Error} err - The Error which was thrown by the backend while
|
* @param {Error} err - The Error which was thrown by the backend while
|
||||||
* manipulating a conference participant and which is to be handled.
|
* manipulating a conference participant and which is to be handled.
|
||||||
|
* @protected
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
export function _handleParticipantError(err) {
|
export function _handleParticipantError(err) {
|
||||||
|
@ -65,10 +84,9 @@ export function isRoomValid(room) {
|
||||||
/**
|
/**
|
||||||
* Remove a set of local tracks from a conference.
|
* Remove a set of local tracks from a conference.
|
||||||
*
|
*
|
||||||
* NOTE The function is internal to this feature.
|
|
||||||
*
|
|
||||||
* @param {JitsiConference} conference - Conference instance.
|
* @param {JitsiConference} conference - Conference instance.
|
||||||
* @param {JitsiLocalTrack[]} localTracks - List of local media tracks.
|
* @param {JitsiLocalTrack[]} localTracks - List of local media tracks.
|
||||||
|
* @protected
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
export function _removeLocalTracksFromConference(conference, localTracks) {
|
export function _removeLocalTracksFromConference(conference, localTracks) {
|
||||||
|
@ -93,8 +111,6 @@ export function _removeLocalTracksFromConference(conference, localTracks) {
|
||||||
* time of this writing, the intention of the function is to abstract the
|
* time of this writing, the intention of the function is to abstract the
|
||||||
* reporting of errors and facilitate elaborating on it in the future.
|
* reporting of errors and facilitate elaborating on it in the future.
|
||||||
*
|
*
|
||||||
* NOTE The function is internal to this feature.
|
|
||||||
*
|
|
||||||
* @param {string} msg - The error message to report.
|
* @param {string} msg - The error message to report.
|
||||||
* @param {Error} err - The Error to report.
|
* @param {Error} err - The Error to report.
|
||||||
* @private
|
* @private
|
||||||
|
|
|
@ -16,9 +16,7 @@ import {
|
||||||
SET_RECEIVE_VIDEO_QUALITY,
|
SET_RECEIVE_VIDEO_QUALITY,
|
||||||
SET_ROOM
|
SET_ROOM
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import {
|
import { VIDEO_QUALITY_LEVELS } from './constants';
|
||||||
VIDEO_QUALITY_LEVELS
|
|
||||||
} from './constants';
|
|
||||||
import { isRoomValid } from './functions';
|
import { isRoomValid } from './functions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,44 +1,50 @@
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
import { toState } from '../redux';
|
||||||
|
|
||||||
import { VIDEO_MUTISM_AUTHORITY } from './constants';
|
import { VIDEO_MUTISM_AUTHORITY } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether video is currently muted by the audio-only authority.
|
* Determines whether video is currently muted by the audio-only authority.
|
||||||
*
|
*
|
||||||
* @param {Store} store - The redux store.
|
* @param {Function|Object} stateful - The redux store, state, or
|
||||||
|
* {@code getState} function.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isVideoMutedByAudioOnly(store: { getState: Function }) {
|
export function isVideoMutedByAudioOnly(stateful: Function | Object) {
|
||||||
return _isVideoMutedByAuthority(store, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY);
|
return (
|
||||||
|
_isVideoMutedByAuthority(stateful, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether video is currently muted by a specific
|
* Determines whether video is currently muted by a specific
|
||||||
* <tt>VIDEO_MUTISM_AUTHORITY</tt>.
|
* <tt>VIDEO_MUTISM_AUTHORITY</tt>.
|
||||||
*
|
*
|
||||||
* @param {Store} store - The redux store.
|
* @param {Function|Object} stateful - The redux store, state, or
|
||||||
|
* {@code getState} function.
|
||||||
* @param {number} videoMutismAuthority - The <tt>VIDEO_MUTISM_AUTHORITY</tt>
|
* @param {number} videoMutismAuthority - The <tt>VIDEO_MUTISM_AUTHORITY</tt>
|
||||||
* which is to be checked whether it has muted video.
|
* which is to be checked whether it has muted video.
|
||||||
* @returns {boolean} If video is currently muted by the specified
|
* @returns {boolean} If video is currently muted by the specified
|
||||||
* <tt>videoMutismAuthority</tt>, then <tt>true</tt>; otherwise, <tt>false</tt>.
|
* <tt>videoMutismAuthority</tt>, then <tt>true</tt>; otherwise, <tt>false</tt>.
|
||||||
*/
|
*/
|
||||||
function _isVideoMutedByAuthority(
|
function _isVideoMutedByAuthority(
|
||||||
{ getState }: { getState: Function },
|
stateful: Function | Object,
|
||||||
videoMutismAuthority: number) {
|
videoMutismAuthority: number) {
|
||||||
return Boolean(
|
const { muted } = toState(stateful)['features/base/media'].video;
|
||||||
|
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
getState()['features/base/media'].video.muted & videoMutismAuthority);
|
return Boolean(muted & videoMutismAuthority);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether video is currently muted by the user authority.
|
* Determines whether video is currently muted by the user authority.
|
||||||
*
|
*
|
||||||
* @param {Store} store - The redux store.
|
* @param {Function|Object} stateful - The redux store, state, or
|
||||||
|
* {@code getState} function.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isVideoMutedByUser(store: { getState: Function }) {
|
export function isVideoMutedByUser(stateful: Function | Object) {
|
||||||
return _isVideoMutedByAuthority(store, VIDEO_MUTISM_AUTHORITY.USER);
|
return _isVideoMutedByAuthority(stateful, VIDEO_MUTISM_AUTHORITY.USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
import {
|
import { NativeModules, NativeEventEmitter } from 'react-native';
|
||||||
NativeModules,
|
|
||||||
NativeEventEmitter,
|
|
||||||
Platform
|
|
||||||
} from 'react-native';
|
|
||||||
|
|
||||||
const RNCallKit = NativeModules.RNCallKit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thin wrapper around Apple's CallKit functionality.
|
* Thin wrapper around Apple's CallKit functionality.
|
||||||
|
@ -18,218 +12,28 @@ const RNCallKit = NativeModules.RNCallKit;
|
||||||
* the "endCall" method in this class, for example.
|
* the "endCall" method in this class, for example.
|
||||||
*
|
*
|
||||||
* Emitted events:
|
* Emitted events:
|
||||||
* - performAnswerCallAction: The user pressed the answer button.
|
* - performAnswerCallAction: The user pressed the answer button.
|
||||||
* - performEndCallAction: The call should be ended.
|
* - performEndCallAction: The call should be ended.
|
||||||
* - performSetMutedCallAction: The call muted state should change. The
|
* - performSetMutedCallAction: The call muted state should change. The
|
||||||
* ancillary `data` object contains a `muted` attribute.
|
* ancillary `data` object contains a `muted` attribute.
|
||||||
* - providerDidReset: The system has reset, all calls should be terminated.
|
* - providerDidReset: The system has reset, all calls should be terminated.
|
||||||
* This event gets no associated data.
|
* This event gets no associated data.
|
||||||
*
|
*
|
||||||
* All events get a `data` object with a `callUUID` property, unless stated
|
* All events get a `data` object with a `callUUID` property, unless stated
|
||||||
* otherwise.
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
class CallKit extends NativeEventEmitter {
|
let CallKit = NativeModules.RNCallKit;
|
||||||
/**
|
|
||||||
* Initializes a new {@code CallKit} instance.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
super(RNCallKit);
|
|
||||||
this._setup = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// XXX Rather than wrapping RNCallKit in a new class and forwarding the many
|
||||||
* Returns True if the current platform is supported, false otherwise. The
|
// methods of the latter to the former, add the one additional method that we
|
||||||
* supported platforms are: iOS >= 10.
|
// need to RNCallKit.
|
||||||
*
|
if (CallKit) {
|
||||||
* @private
|
const eventEmitter = new NativeEventEmitter(CallKit);
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
static isSupported() {
|
|
||||||
return Platform.OS === 'ios' && parseInt(Platform.Version, 10) >= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
CallKit = {
|
||||||
* Checks if CallKit was setup, and throws an exception in that case.
|
...CallKit,
|
||||||
*
|
addListener: eventEmitter.addListener.bind(eventEmitter)
|
||||||
* @private
|
};
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_checkSetup() {
|
|
||||||
if (!this._setup) {
|
|
||||||
throw new Error('CallKit not initialized, call setup() first.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a listener for the given event.
|
|
||||||
*
|
|
||||||
* @param {string} event - Name of the event we are interested in.
|
|
||||||
* @param {Function} listener - Function which will be called when the
|
|
||||||
* desired event is emitted.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
addEventListener(event, listener) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addListener(event, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies CallKit about an incoming call. This will display the system
|
|
||||||
* incoming call view.
|
|
||||||
*
|
|
||||||
* @param {string} uuid - Unique identifier for the call.
|
|
||||||
* @param {string} handle - Call handle in CallKit's terms. The room URL.
|
|
||||||
* @param {boolean} hasVideo - True if it's a video call, false otherwise.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
displayIncomingCall(uuid, handle, hasVideo = true) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return RNCallKit.displayIncomingCall(uuid, handle, hasVideo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request CallKit to end the call.
|
|
||||||
*
|
|
||||||
* @param {string} uuid - Unique identifier for the call.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
endCall(uuid) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return RNCallKit.endCall(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a listener for the given event.
|
|
||||||
*
|
|
||||||
* @param {string} event - Name of the event we are no longer interested in.
|
|
||||||
* @param {Function} listener - Function which used to be called when the
|
|
||||||
* desired event was emitted.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
removeEventListener(event, listener) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.removeListener(event, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate CallKit that the outgoing call with the given UUID is now
|
|
||||||
* connected.
|
|
||||||
*
|
|
||||||
* @param {string} uuid - Unique identifier for the call.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
reportConnectedOutgoingCall(uuid) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return RNCallKit.reportConnectedOutgoingCall(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate CallKit that the call with the given UUID has failed.
|
|
||||||
*
|
|
||||||
* @param {string} uuid - Unique identifier for the call.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
reportCallFailed(uuid) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return RNCallKit.reportCallFailed(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tell CallKit about the audio muted state.
|
|
||||||
*
|
|
||||||
* @param {string} uuid - Unique identifier for the call.
|
|
||||||
* @param {boolean} muted - True if audio is muted, false otherwise.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
setMuted(uuid, muted) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return RNCallKit.setMuted(uuid, muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare / initialize CallKit. This method must be called before any
|
|
||||||
* other.
|
|
||||||
*
|
|
||||||
* @param {Object} options - Initialization options.
|
|
||||||
* @param {string} options.imageName - Image to be used in CallKit's
|
|
||||||
* application button..
|
|
||||||
* @param {string} options.ringtoneSound - Ringtone to be used for incoming
|
|
||||||
* calls.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
setup(options = {}) {
|
|
||||||
if (CallKit.isSupported()) {
|
|
||||||
options.appName = NativeModules.AppInfo.name;
|
|
||||||
RNCallKit.setup(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._setup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate CallKit about a new outgoing call.
|
|
||||||
*
|
|
||||||
* @param {string} uuid - Unique identifier for the call.
|
|
||||||
* @param {string} handle - Call handle in CallKit's terms. The room URL in
|
|
||||||
* our case.
|
|
||||||
* @param {boolean} hasVideo - True if it's a video call, false otherwise.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
startCall(uuid, handle, hasVideo = true) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return RNCallKit.startCall(uuid, handle, hasVideo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an ongoing call's parameters.
|
|
||||||
*
|
|
||||||
* @param {string} uuid - Unique identifier for the call.
|
|
||||||
* @param {Object} options - Object with properties which should be updated.
|
|
||||||
* @param {string} options.displayName - Display name for the caller.
|
|
||||||
* @param {boolean} options.hasVideo - True if the call has video, false
|
|
||||||
* otherwise.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
updateCall(uuid, options) {
|
|
||||||
this._checkSetup();
|
|
||||||
if (!CallKit.isSupported()) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return RNCallKit.updateCall(uuid, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new CallKit();
|
export default CallKit;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/**
|
/**
|
||||||
* The type of redux action to set the CallKit event listeners.
|
* The type of redux action to set CallKit's event subscriptions.
|
||||||
*
|
*
|
||||||
* {
|
* {
|
||||||
* type: _SET_CALLKIT_LISTENERS,
|
* type: _SET_CALLKIT_SUBSCRIPTIONS,
|
||||||
* listeners: Map|null
|
* subscriptions: Array|undefined
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
export const _SET_CALLKIT_LISTENERS = Symbol('_SET_CALLKIT_LISTENERS');
|
export const _SET_CALLKIT_SUBSCRIPTIONS = Symbol('_SET_CALLKIT_SUBSCRIPTIONS');
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
|
import { NativeModules } from 'react-native';
|
||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
|
|
||||||
import {
|
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT, appNavigate } from '../../app';
|
||||||
APP_WILL_MOUNT,
|
|
||||||
APP_WILL_UNMOUNT,
|
|
||||||
appNavigate
|
|
||||||
} from '../../app';
|
|
||||||
import {
|
import {
|
||||||
CONFERENCE_FAILED,
|
CONFERENCE_FAILED,
|
||||||
CONFERENCE_LEFT,
|
CONFERENCE_LEFT,
|
||||||
CONFERENCE_WILL_JOIN,
|
CONFERENCE_WILL_JOIN,
|
||||||
CONFERENCE_JOINED
|
CONFERENCE_JOINED,
|
||||||
|
getCurrentConference
|
||||||
} from '../../base/conference';
|
} from '../../base/conference';
|
||||||
import { getInviteURL } from '../../base/connection';
|
import { getInviteURL } from '../../base/connection';
|
||||||
import {
|
import {
|
||||||
|
isVideoMutedByAudioOnly,
|
||||||
SET_AUDIO_MUTED,
|
SET_AUDIO_MUTED,
|
||||||
SET_VIDEO_MUTED,
|
SET_VIDEO_MUTED,
|
||||||
isVideoMutedByAudioOnly,
|
|
||||||
setAudioMuted
|
setAudioMuted
|
||||||
} from '../../base/media';
|
} from '../../base/media';
|
||||||
import { MiddlewareRegistry, toState } from '../../base/redux';
|
import { MiddlewareRegistry } from '../../base/redux';
|
||||||
import { _SET_CALLKIT_LISTENERS } from './actionTypes';
|
|
||||||
|
import { _SET_CALLKIT_SUBSCRIPTIONS } from './actionTypes';
|
||||||
import CallKit from './CallKit';
|
import CallKit from './CallKit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,165 +29,319 @@ import CallKit from './CallKit';
|
||||||
* @param {Store} store - The redux store.
|
* @param {Store} store - The redux store.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
CallKit && MiddlewareRegistry.register(store => next => action => {
|
||||||
const result = next(action);
|
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case _SET_CALLKIT_LISTENERS: {
|
case _SET_CALLKIT_SUBSCRIPTIONS:
|
||||||
const { listeners } = getState()['features/callkit'];
|
return _setCallKitSubscriptions(store, next, action);
|
||||||
|
|
||||||
if (listeners) {
|
case APP_WILL_MOUNT:
|
||||||
for (const [ event, listener ] of listeners) {
|
return _appWillMount(store, next, action);
|
||||||
CallKit.removeEventListener(event, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.listeners) {
|
|
||||||
for (const [ event, listener ] of action.listeners) {
|
|
||||||
CallKit.addEventListener(event, listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case APP_WILL_MOUNT: {
|
|
||||||
CallKit.setup(); // TODO: set app icon.
|
|
||||||
const listeners = new Map();
|
|
||||||
const callEndListener = data => {
|
|
||||||
const conference = getCurrentConference(getState);
|
|
||||||
|
|
||||||
if (conference && conference.callUUID === data.callUUID) {
|
|
||||||
// We arrive here when a call is ended by the system, for
|
|
||||||
// for example when another incoming call is received and the
|
|
||||||
// user selects "End & Accept".
|
|
||||||
delete conference.callUUID;
|
|
||||||
dispatch(appNavigate(undefined));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
listeners.set('performEndCallAction', callEndListener);
|
|
||||||
|
|
||||||
// Set the same listener for providerDidReset. According to the docs,
|
|
||||||
// when the system resets we should terminate all calls.
|
|
||||||
listeners.set('providerDidReset', callEndListener);
|
|
||||||
|
|
||||||
const setMutedListener = data => {
|
|
||||||
const conference = getCurrentConference(getState);
|
|
||||||
|
|
||||||
if (conference && conference.callUUID === data.callUUID) {
|
|
||||||
// Break the loop. Audio can be muted both from the CallKit
|
|
||||||
// interface and from the Jitsi Meet interface. We must keep
|
|
||||||
// them in sync, but at some point the loop needs to be broken.
|
|
||||||
// We are doing it here, on the CallKit handler.
|
|
||||||
const { muted } = getState()['features/base/media'].audio;
|
|
||||||
|
|
||||||
if (muted !== data.muted) {
|
|
||||||
dispatch(setAudioMuted(Boolean(data.muted)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
listeners.set('performSetMutedCallAction', setMutedListener);
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: _SET_CALLKIT_LISTENERS,
|
|
||||||
listeners
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case APP_WILL_UNMOUNT:
|
case APP_WILL_UNMOUNT:
|
||||||
dispatch({
|
store.dispatch({
|
||||||
type: _SET_CALLKIT_LISTENERS,
|
type: _SET_CALLKIT_SUBSCRIPTIONS,
|
||||||
listeners: null
|
subscriptions: undefined
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CONFERENCE_FAILED: {
|
case CONFERENCE_FAILED:
|
||||||
const { callUUID } = action.conference;
|
return _conferenceFailed(store, next, action);
|
||||||
|
|
||||||
if (callUUID) {
|
case CONFERENCE_JOINED:
|
||||||
CallKit.reportCallFailed(callUUID);
|
return _conferenceJoined(store, next, action);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
case CONFERENCE_LEFT:
|
||||||
|
return _conferenceLeft(store, next, action);
|
||||||
|
|
||||||
|
case CONFERENCE_WILL_JOIN:
|
||||||
|
return _conferenceWillJoin(store, next, action);
|
||||||
|
|
||||||
|
case SET_AUDIO_MUTED:
|
||||||
|
return _setAudioMuted(store, next, action);
|
||||||
|
|
||||||
|
case SET_VIDEO_MUTED:
|
||||||
|
return _setVideoMuted(store, next, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
case CONFERENCE_LEFT: {
|
return next(action);
|
||||||
const { callUUID } = action.conference;
|
|
||||||
|
|
||||||
if (callUUID) {
|
|
||||||
CallKit.endCall(callUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CONFERENCE_JOINED: {
|
|
||||||
const { callUUID } = action.conference;
|
|
||||||
|
|
||||||
if (callUUID) {
|
|
||||||
CallKit.reportConnectedOutgoingCall(callUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case CONFERENCE_WILL_JOIN: {
|
|
||||||
const conference = action.conference;
|
|
||||||
const url = getInviteURL(getState);
|
|
||||||
const hasVideo = !isVideoMutedByAudioOnly({ getState });
|
|
||||||
|
|
||||||
// When assigning the call UUID, do so in upper case, since iOS will
|
|
||||||
// return it upper cased.
|
|
||||||
conference.callUUID = uuid.v4().toUpperCase();
|
|
||||||
CallKit.startCall(conference.callUUID, url.toString(), hasVideo)
|
|
||||||
.then(() => {
|
|
||||||
const { room } = getState()['features/base/conference'];
|
|
||||||
|
|
||||||
CallKit.updateCall(conference.callUUID, { displayName: room });
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SET_AUDIO_MUTED: {
|
|
||||||
const conference = getCurrentConference(getState);
|
|
||||||
|
|
||||||
if (conference && conference.callUUID) {
|
|
||||||
CallKit.setMuted(conference.callUUID, action.muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SET_VIDEO_MUTED: {
|
|
||||||
const conference = getCurrentConference(getState);
|
|
||||||
|
|
||||||
if (conference && conference.callUUID) {
|
|
||||||
const hasVideo = !isVideoMutedByAudioOnly({ getState });
|
|
||||||
|
|
||||||
CallKit.updateCall(conference.callUUID, { hasVideo });
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the currently active conference.
|
* Notifies the feature jwt that the action {@link APP_WILL_MOUNT} is being
|
||||||
|
* dispatched within a specific redux <tt>store</tt>.
|
||||||
*
|
*
|
||||||
* @param {Function|Object} stateOrGetState - The redux state or redux's
|
* @param {Store} store - The redux store in which the specified <tt>action</tt>
|
||||||
* {@code getState} function.
|
* is being dispatched.
|
||||||
* @returns {Conference|undefined}
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||||
|
* specified <tt>action</tt> to the specified <tt>store</tt>.
|
||||||
|
* @param {Action} action - The redux action <tt>APP_WILL_MOUNT</tt> which is
|
||||||
|
* being dispatched in the specified <tt>store</tt>.
|
||||||
|
* @private
|
||||||
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
function getCurrentConference(stateOrGetState: Function | Object): ?Object {
|
function _appWillMount({ dispatch, getState }, next, action) {
|
||||||
const state = toState(stateOrGetState);
|
const result = next(action);
|
||||||
const { conference, joining } = state['features/base/conference'];
|
|
||||||
|
|
||||||
return conference || joining;
|
CallKit.setProviderConfiguration({
|
||||||
|
iconTemplateImageName: 'AppIcon40x40',
|
||||||
|
localizedName: NativeModules.AppInfo.name
|
||||||
|
});
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
dispatch,
|
||||||
|
getState
|
||||||
|
};
|
||||||
|
const subscriptions = [
|
||||||
|
CallKit.addListener(
|
||||||
|
'performEndCallAction',
|
||||||
|
_onPerformEndCallAction,
|
||||||
|
context),
|
||||||
|
CallKit.addListener(
|
||||||
|
'performSetMutedCallAction',
|
||||||
|
_onPerformSetMutedCallAction,
|
||||||
|
context),
|
||||||
|
|
||||||
|
// According to CallKit's documentation, when the system resets we
|
||||||
|
// should terminate all calls. Hence, providerDidReset is the same
|
||||||
|
// to us as performEndCallAction.
|
||||||
|
CallKit.addListener(
|
||||||
|
'providerDidReset',
|
||||||
|
_onPerformEndCallAction,
|
||||||
|
context)
|
||||||
|
];
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: _SET_CALLKIT_SUBSCRIPTIONS,
|
||||||
|
subscriptions
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the feature jwt that the action {@link CONFERENCE_FAILED} is being
|
||||||
|
* dispatched within a specific redux <tt>store</tt>.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified <tt>action</tt>
|
||||||
|
* is being dispatched.
|
||||||
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||||
|
* specified <tt>action</tt> to the specified <tt>store</tt>.
|
||||||
|
* @param {Action} action - The redux action <tt>CONFERENCE_FAILED</tt> which is
|
||||||
|
* being dispatched in the specified <tt>store</tt>.
|
||||||
|
* @private
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function _conferenceFailed(store, next, action) {
|
||||||
|
const result = next(action);
|
||||||
|
|
||||||
|
const { callUUID } = action.conference;
|
||||||
|
|
||||||
|
if (callUUID) {
|
||||||
|
CallKit.reportCallFailed(callUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the feature jwt that the action {@link CONFERENCE_JOINED} is being
|
||||||
|
* dispatched within a specific redux <tt>store</tt>.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified <tt>action</tt>
|
||||||
|
* is being dispatched.
|
||||||
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||||
|
* specified <tt>action</tt> to the specified <tt>store</tt>.
|
||||||
|
* @param {Action} action - The redux action <tt>CONFERENCE_JOINED</tt> which is
|
||||||
|
* being dispatched in the specified <tt>store</tt>.
|
||||||
|
* @private
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function _conferenceJoined(store, next, action) {
|
||||||
|
const result = next(action);
|
||||||
|
|
||||||
|
const { callUUID } = action.conference;
|
||||||
|
|
||||||
|
if (callUUID) {
|
||||||
|
CallKit.reportConnectedOutgoingCall(callUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the feature jwt that the action {@link CONFERENCE_LEFT} is being
|
||||||
|
* dispatched within a specific redux <tt>store</tt>.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified <tt>action</tt>
|
||||||
|
* is being dispatched.
|
||||||
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||||
|
* specified <tt>action</tt> to the specified <tt>store</tt>.
|
||||||
|
* @param {Action} action - The redux action <tt>CONFERENCE_LEFT</tt> which is
|
||||||
|
* being dispatched in the specified <tt>store</tt>.
|
||||||
|
* @private
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function _conferenceLeft(store, next, action) {
|
||||||
|
const result = next(action);
|
||||||
|
|
||||||
|
const { callUUID } = action.conference;
|
||||||
|
|
||||||
|
if (callUUID) {
|
||||||
|
CallKit.endCall(callUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the feature jwt that the action {@link CONFERENCE_WILL_JOIN} is
|
||||||
|
* being dispatched within a specific redux <tt>store</tt>.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified <tt>action</tt>
|
||||||
|
* is being dispatched.
|
||||||
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||||
|
* specified <tt>action</tt> to the specified <tt>store</tt>.
|
||||||
|
* @param {Action} action - The redux action <tt>CONFERENCE_WILL_JOIN</tt> which
|
||||||
|
* is being dispatched in the specified <tt>store</tt>.
|
||||||
|
* @private
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function _conferenceWillJoin({ getState }, next, action) {
|
||||||
|
const result = next(action);
|
||||||
|
|
||||||
|
const conference = action.conference;
|
||||||
|
const url = getInviteURL(getState);
|
||||||
|
const hasVideo = !isVideoMutedByAudioOnly(getState);
|
||||||
|
|
||||||
|
// When assigning the call UUID, do so in upper case, since iOS will
|
||||||
|
// return it upper cased.
|
||||||
|
conference.callUUID = uuid.v4().toUpperCase();
|
||||||
|
CallKit.startCall(conference.callUUID, url.toString(), hasVideo)
|
||||||
|
.then(() => {
|
||||||
|
const { room } = getState()['features/base/conference'];
|
||||||
|
|
||||||
|
CallKit.updateCall(conference.callUUID, { displayName: room });
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles CallKit's event <tt>performEndCallAction</tt>.
|
||||||
|
*
|
||||||
|
* @param {Object} event - The details of the CallKit event
|
||||||
|
* <tt>performEndCallAction</tt>.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _onPerformEndCallAction({ callUUID }) {
|
||||||
|
const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
|
||||||
|
const conference = getCurrentConference(getState);
|
||||||
|
|
||||||
|
if (conference && conference.callUUID === callUUID) {
|
||||||
|
// We arrive here when a call is ended by the system, for
|
||||||
|
// example when another incoming call is received and the user
|
||||||
|
// selects "End & Accept".
|
||||||
|
delete conference.callUUID;
|
||||||
|
dispatch(appNavigate(undefined));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles CallKit's event <tt>performSetMutedCallAction</tt>.
|
||||||
|
*
|
||||||
|
* @param {Object} event - The details of the CallKit event
|
||||||
|
* <tt>performSetMutedCallAction</tt>.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _onPerformSetMutedCallAction({ callUUID, muted: newValue }) {
|
||||||
|
const { dispatch, getState } = this; // eslint-disable-line no-invalid-this
|
||||||
|
const conference = getCurrentConference(getState);
|
||||||
|
|
||||||
|
if (conference && conference.callUUID === callUUID) {
|
||||||
|
// Break the loop. Audio can be muted from both CallKit and Jitsi Meet.
|
||||||
|
// We must keep them in sync, but at some point the loop needs to be
|
||||||
|
// broken. We are doing it here, on the CallKit handler.
|
||||||
|
const { muted: oldValue } = getState()['features/base/media'].audio;
|
||||||
|
|
||||||
|
if (oldValue !== newValue) {
|
||||||
|
dispatch(setAudioMuted(Boolean(newValue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the feature jwt that the action {@link SET_AUDIO_MUTED} is being
|
||||||
|
* dispatched within a specific redux <tt>store</tt>.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified <tt>action</tt>
|
||||||
|
* is being dispatched.
|
||||||
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||||
|
* specified <tt>action</tt> to the specified <tt>store</tt>.
|
||||||
|
* @param {Action} action - The redux action <tt>SET_AUDIO_MUTED</tt> which is
|
||||||
|
* being dispatched in the specified <tt>store</tt>.
|
||||||
|
* @private
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function _setAudioMuted({ getState }, next, action) {
|
||||||
|
const result = next(action);
|
||||||
|
|
||||||
|
const conference = getCurrentConference(getState);
|
||||||
|
|
||||||
|
if (conference && conference.callUUID) {
|
||||||
|
CallKit.setMuted(conference.callUUID, action.muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the feature jwt that the action {@link _SET_CALLKIT_SUBSCRIPTIONS}
|
||||||
|
* is being dispatched within a specific redux <tt>store</tt>.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified <tt>action</tt>
|
||||||
|
* is being dispatched.
|
||||||
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||||
|
* specified <tt>action</tt> to the specified <tt>store</tt>.
|
||||||
|
* @param {Action} action - The redux action <tt>_SET_CALLKIT_SUBSCRIPTIONS</tt>
|
||||||
|
* which is being dispatched in the specified <tt>store</tt>.
|
||||||
|
* @private
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function _setCallKitSubscriptions({ getState }, next, action) {
|
||||||
|
const { subscriptions } = getState()['features/callkit'];
|
||||||
|
|
||||||
|
if (subscriptions) {
|
||||||
|
for (const subscription of subscriptions) {
|
||||||
|
subscription.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the feature jwt that the action {@link SET_VIDEO_MUTED} is being
|
||||||
|
* dispatched within a specific redux <tt>store</tt>.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store in which the specified <tt>action</tt>
|
||||||
|
* is being dispatched.
|
||||||
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
||||||
|
* specified <tt>action</tt> to the specified <tt>store</tt>.
|
||||||
|
* @param {Action} action - The redux action <tt>SET_VIDEO_MUTED</tt> which is
|
||||||
|
* being dispatched in the specified <tt>store</tt>.
|
||||||
|
* @private
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function _setVideoMuted({ getState }, next, action) {
|
||||||
|
const result = next(action);
|
||||||
|
|
||||||
|
const conference = getCurrentConference(getState);
|
||||||
|
|
||||||
|
if (conference && conference.callUUID) {
|
||||||
|
CallKit.updateCall(
|
||||||
|
conference.callUUID,
|
||||||
|
{ hasVideo: !isVideoMutedByAudioOnly(getState) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import { ReducerRegistry } from '../../base/redux';
|
import { assign, ReducerRegistry } from '../../base/redux';
|
||||||
|
|
||||||
import {
|
import { _SET_CALLKIT_SUBSCRIPTIONS } from './actionTypes';
|
||||||
_SET_CALLKIT_LISTENERS
|
import CallKit from './CallKit';
|
||||||
} from './actionTypes';
|
|
||||||
|
|
||||||
ReducerRegistry.register('features/callkit', (state = {}, action) => {
|
CallKit && ReducerRegistry.register(
|
||||||
switch (action.type) {
|
'features/callkit',
|
||||||
case _SET_CALLKIT_LISTENERS:
|
(state = {}, action) => {
|
||||||
return {
|
switch (action.type) {
|
||||||
...state,
|
case _SET_CALLKIT_SUBSCRIPTIONS:
|
||||||
listeners: action.listeners
|
return assign(state, 'subscriptions', action.subscriptions);
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue