Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

CallKit.m 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. //
  2. // Based on RNCallKit
  3. //
  4. // Original license:
  5. //
  6. // Copyright (c) 2016, Ian Yu-Hsun Lin
  7. //
  8. // Permission to use, copy, modify, and/or distribute this software for any
  9. // purpose with or without fee is hereby granted, provided that the above
  10. // copyright notice and this permission notice appear in all copies.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  13. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  14. // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  15. // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  16. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  17. // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  18. // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  19. //
  20. #import <AVFoundation/AVFoundation.h>
  21. #import <CallKit/CallKit.h>
  22. #import <Foundation/Foundation.h>
  23. #import <UIKit/UIKit.h>
  24. #import <React/RCTBridge.h>
  25. #import <React/RCTEventEmitter.h>
  26. #import <React/RCTUtils.h>
  27. #import <WebRTC/WebRTC.h>
  28. #import <JitsiMeet/JitsiMeet-Swift.h>
  29. #import "LogUtils.h"
  30. // The events emitted/supported by RNCallKit:
  31. static NSString * const RNCallKitPerformAnswerCallAction
  32. = @"performAnswerCallAction";
  33. static NSString * const RNCallKitPerformEndCallAction
  34. = @"performEndCallAction";
  35. static NSString * const RNCallKitPerformSetMutedCallAction
  36. = @"performSetMutedCallAction";
  37. static NSString * const RNCallKitProviderDidReset
  38. = @"providerDidReset";
  39. @interface RNCallKit : RCTEventEmitter <JMCallKitListener>
  40. @end
  41. @implementation RNCallKit
  42. RCT_EXPORT_MODULE();
  43. - (NSArray<NSString *> *)supportedEvents {
  44. return @[
  45. RNCallKitPerformAnswerCallAction,
  46. RNCallKitPerformEndCallAction,
  47. RNCallKitPerformSetMutedCallAction,
  48. RNCallKitProviderDidReset
  49. ];
  50. }
  51. - (void)dealloc {
  52. [JMCallKitProxy removeListener:self];
  53. }
  54. - (dispatch_queue_t)methodQueue {
  55. // Make sure all our methods run in the main thread.
  56. return dispatch_get_main_queue();
  57. }
  58. // End call
  59. RCT_EXPORT_METHOD(endCall:(NSString *)callUUID
  60. resolve:(RCTPromiseResolveBlock)resolve
  61. reject:(RCTPromiseRejectBlock)reject) {
  62. DDLogInfo(@"[RNCallKit][endCall] callUUID = %@", callUUID);
  63. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  64. if (!callUUID_) {
  65. reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
  66. return;
  67. }
  68. CXEndCallAction *action
  69. = [[CXEndCallAction alloc] initWithCallUUID:callUUID_];
  70. [self requestTransaction:[[CXTransaction alloc] initWithAction:action]
  71. resolve:resolve
  72. reject:reject];
  73. }
  74. // Mute / unmute (audio)
  75. RCT_EXPORT_METHOD(setMuted:(NSString *)callUUID
  76. muted:(BOOL)muted
  77. resolve:(RCTPromiseResolveBlock)resolve
  78. reject:(RCTPromiseRejectBlock)reject) {
  79. DDLogInfo(@"[RNCallKit][setMuted] callUUID = %@", callUUID);
  80. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  81. if (!callUUID_) {
  82. reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
  83. return;
  84. }
  85. CXSetMutedCallAction *action
  86. = [[CXSetMutedCallAction alloc] initWithCallUUID:callUUID_ muted:muted];
  87. [self requestTransaction:[[CXTransaction alloc] initWithAction:action]
  88. resolve:resolve
  89. reject:reject];
  90. }
  91. RCT_EXPORT_METHOD(setProviderConfiguration:(NSDictionary *)dictionary) {
  92. DDLogInfo(@"[RNCallKit][setProviderConfiguration:] dictionary = %@", dictionary);
  93. if (![JMCallKitProxy isProviderConfigured]) {
  94. [self configureProviderFromDictionary:dictionary];
  95. }
  96. // register to receive CallKit proxy events
  97. [JMCallKitProxy addListener:self];
  98. }
  99. // Start outgoing call
  100. RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
  101. handle:(NSString *)handle
  102. video:(BOOL)video
  103. resolve:(RCTPromiseResolveBlock)resolve
  104. reject:(RCTPromiseRejectBlock)reject) {
  105. DDLogInfo(@"[RNCallKit][startCall] callUUID = %@", callUUID);
  106. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  107. if (!callUUID_) {
  108. reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
  109. return;
  110. }
  111. // Don't start a new call if there's an active call for the specified
  112. // callUUID. JitsiMeetView was configured for an incoming call.
  113. if ([JMCallKitProxy hasActiveCallForUUID:callUUID]) {
  114. resolve(nil);
  115. return;
  116. }
  117. CXHandle *handle_
  118. = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
  119. CXStartCallAction *action
  120. = [[CXStartCallAction alloc] initWithCallUUID:callUUID_
  121. handle:handle_];
  122. action.video = video;
  123. CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
  124. [self requestTransaction:transaction resolve:resolve reject:reject];
  125. }
  126. // Indicate call failed
  127. RCT_EXPORT_METHOD(reportCallFailed:(NSString *)callUUID
  128. resolve:(RCTPromiseResolveBlock)resolve
  129. reject:(RCTPromiseRejectBlock)reject) {
  130. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  131. if (!callUUID_) {
  132. reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
  133. return;
  134. }
  135. [JMCallKitProxy reportCallWith:callUUID_
  136. endedAt:nil
  137. reason:CXCallEndedReasonFailed];
  138. resolve(nil);
  139. }
  140. // Indicate outgoing call connected.
  141. RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)callUUID
  142. resolve:(RCTPromiseResolveBlock)resolve
  143. reject:(RCTPromiseRejectBlock)reject) {
  144. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  145. if (!callUUID_) {
  146. reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
  147. return;
  148. }
  149. [JMCallKitProxy reportOutgoingCallWith:callUUID_
  150. connectedAt:nil];
  151. resolve(nil);
  152. }
  153. // Update call in case we have a display name or video capability changes.
  154. RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
  155. options:(NSDictionary *)options
  156. resolve:(RCTPromiseResolveBlock)resolve
  157. reject:(RCTPromiseRejectBlock)reject) {
  158. DDLogInfo(@"[RNCallKit][updateCall] callUUID = %@ options = %@", callUUID, options);
  159. NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
  160. if (!callUUID_) {
  161. reject(nil, [NSString stringWithFormat:@"Invalid UUID: %@", callUUID], nil);
  162. return;
  163. }
  164. NSString *displayName = options[@"displayName"];
  165. BOOL hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
  166. [JMCallKitProxy reportCallUpdateWith:callUUID_
  167. handle:nil
  168. displayName:displayName
  169. hasVideo:hasVideo];
  170. resolve(nil);
  171. }
  172. #pragma mark - Helper methods
  173. - (void)configureProviderFromDictionary:(NSDictionary* )dictionary {
  174. DDLogInfo(@"[RNCallKit][providerConfigurationFromDictionary: %@]", dictionary);
  175. if (!dictionary) {
  176. dictionary = @{};
  177. }
  178. // localizedName
  179. NSString *localizedName = dictionary[@"localizedName"];
  180. if (!localizedName) {
  181. localizedName
  182. = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];
  183. }
  184. // iconTemplateImageData
  185. NSString *iconTemplateImageName = dictionary[@"iconTemplateImageName"];
  186. NSData *iconTemplateImageData;
  187. UIImage *iconTemplateImage;
  188. if (iconTemplateImageName) {
  189. // First try to load the resource from the main bundle.
  190. iconTemplateImage = [UIImage imageNamed:iconTemplateImageName];
  191. // If that didn't work, use the one built-in.
  192. if (!iconTemplateImage) {
  193. iconTemplateImage = [UIImage imageNamed:iconTemplateImageName
  194. inBundle:[NSBundle bundleForClass:self.class]
  195. compatibleWithTraitCollection:nil];
  196. }
  197. if (iconTemplateImage) {
  198. iconTemplateImageData = UIImagePNGRepresentation(iconTemplateImage);
  199. }
  200. }
  201. NSString *ringtoneSound = dictionary[@"ringtoneSound"];
  202. [JMCallKitProxy
  203. configureProviderWithLocalizedName:localizedName
  204. ringtoneSound:ringtoneSound
  205. iconTemplateImageData:iconTemplateImageData];
  206. }
  207. - (void)requestTransaction:(CXTransaction *)transaction
  208. resolve:(RCTPromiseResolveBlock)resolve
  209. reject:(RCTPromiseRejectBlock)reject {
  210. DDLogInfo(@"[RNCallKit][requestTransaction] transaction = %@", transaction);
  211. [JMCallKitProxy request:transaction
  212. completion:^(NSError * _Nullable error) {
  213. if (error) {
  214. DDLogError(@"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)", transaction.actions, error);
  215. reject(nil, @"Error processing CallKit transaction", error);
  216. } else {
  217. resolve(nil);
  218. }
  219. }];
  220. }
  221. #pragma mark - JMCallKitListener
  222. // Called when the provider has been reset. We should terminate all calls.
  223. - (void)providerDidReset {
  224. DDLogInfo(@"[RNCallKit][CXProviderDelegate][providerDidReset:]");
  225. [self sendEventWithName:RNCallKitProviderDidReset body:nil];
  226. }
  227. // Answering incoming call
  228. - (void) performAnswerCallWithUUID:(NSUUID *)UUID {
  229. DDLogInfo(@"[RNCallKit][CXProviderDelegate][provider:performAnswerCallAction:]");
  230. [self sendEventWithName:RNCallKitPerformAnswerCallAction
  231. body:@{ @"callUUID": UUID.UUIDString }];
  232. }
  233. // Call ended, user request
  234. - (void) performEndCallWithUUID:(NSUUID *)UUID {
  235. DDLogInfo(@"[RNCallKit][CXProviderDelegate][provider:performEndCallAction:]");
  236. [self sendEventWithName:RNCallKitPerformEndCallAction
  237. body:@{ @"callUUID": UUID.UUIDString }];
  238. }
  239. // Handle audio mute from CallKit view
  240. - (void) performSetMutedCallWithUUID:(NSUUID *)UUID
  241. isMuted:(BOOL)isMuted {
  242. DDLogInfo(@"[RNCallKit][CXProviderDelegate][provider:performSetMutedCallAction:]");
  243. [self sendEventWithName:RNCallKitPerformSetMutedCallAction
  244. body:@{
  245. @"callUUID": UUID.UUIDString,
  246. @"muted": @(isMuted)
  247. }];
  248. }
  249. // Starting outgoing call
  250. - (void) performStartCallWithUUID:(NSUUID *)UUID
  251. isVideo:(BOOL)isVideo {
  252. DDLogInfo(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction:]");
  253. [JMCallKitProxy reportOutgoingCallWith:UUID
  254. startedConnectingAt:nil];
  255. }
  256. - (void) providerDidActivateAudioSessionWithSession:(AVAudioSession *)session {
  257. DDLogInfo(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession:]");
  258. [[RTCAudioSession sharedInstance] audioSessionDidActivate:session];
  259. }
  260. - (void) providerDidDeactivateAudioSessionWithSession:(AVAudioSession *)session {
  261. DDLogInfo(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession:]");
  262. [[RTCAudioSession sharedInstance] audioSessionDidDeactivate:session];
  263. }
  264. - (void) providerTimedOutPerformingActionWithAction:(CXAction *)action {
  265. DDLogWarn(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction:]");
  266. }
  267. // The bridge might already be invalidated by the time a CallKit event is processed,
  268. // just ignore it and don't emit it.
  269. - (void)sendEventWithName:(NSString *)name body:(id)body {
  270. if (!self.bridge) {
  271. return;
  272. }
  273. [super sendEventWithName:name body:body];
  274. }
  275. @end