|
| 1 | +--- |
| 2 | +ms.date: 01/06/2023 |
| 3 | +ms.topic: how-to |
| 4 | +author: raosanat |
| 5 | +ms.author: sanathr |
| 6 | +title: CallKit integration in ACS Calling SDK |
| 7 | +ms.service: azure-communication-services |
| 8 | +ms.subservice: calling |
| 9 | +description: Steps on how to integrate CallKit with ACS Calling SDK |
| 10 | +--- |
| 11 | + |
| 12 | + # Integrate with CallKit |
| 13 | + |
| 14 | + In this document, we'll go through how to integrate CallKit with your iOS application. |
| 15 | + |
| 16 | + > [!NOTE] |
| 17 | + > This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. To use this api please use 'beta' release of Azure Communication Services Calling iOS SDK |
| 18 | +
|
| 19 | + ## Prerequisites |
| 20 | + |
| 21 | + - An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?WT.mc_id=A261C142F). |
| 22 | + - A deployed Communication Services resource. [Create a Communication Services resource](../../quickstarts/create-communication-resource.md). |
| 23 | + - A user access token to enable the calling client. For more information, see [Create and manage access tokens](../../quickstarts/access-tokens.md). |
| 24 | + - Optional: Complete the quickstart to [add voice calling to your application](../../quickstarts/voice-video-calling/getting-started-with-calling.md) |
| 25 | + |
| 26 | + ## CallKit Integration (within SDK) |
| 27 | + |
| 28 | + CallKit Integration in the ACS iOS SDK handles interaction with CallKit for us. To perform any call operations like mute/unmute, hold/resume, we only need to call the API on the ACS SDK. |
| 29 | + |
| 30 | + ### Initialize call agent with CallKitOptions |
| 31 | + |
| 32 | + With configured instance of `CallKitOptions`, we can create the `CallAgent` with handling of `CallKit`. |
| 33 | + |
| 34 | + ```Swift |
| 35 | + let options = CallAgentOptions() |
| 36 | + let callKitOptions = CallKitOptions(with: createProviderConfig()) |
| 37 | + options.callKitOptions = callKitOptions |
| 38 | + |
| 39 | + // Configure the properties of `CallKitOptions` instance here |
| 40 | + |
| 41 | + self.callClient!.createCallAgent(userCredential: userCredential, |
| 42 | + options: options, |
| 43 | + completionHandler: { (callAgent, error) in |
| 44 | + // Initialization |
| 45 | + }) |
| 46 | + ``` |
| 47 | + |
| 48 | + ### Specify call recipient info for outgoing calls |
| 49 | + |
| 50 | + First we need to create an instance of `StartCallOptions()` for outgoing calls, or `JoinCallOptions()` for group call: |
| 51 | + ```Swift |
| 52 | + let options = StartCallOptions() |
| 53 | + ``` |
| 54 | + or |
| 55 | + ```Swift |
| 56 | + let options = JoinCallOptions() |
| 57 | + ``` |
| 58 | + Then create an instance of `CallKitRemoteInfo` |
| 59 | + ```Swift |
| 60 | + options.callKitRemoteInfo = CallKitRemoteInfo() |
| 61 | + ``` |
| 62 | + |
| 63 | + 1. Assign value for `callKitRemoteInfo.displayNameForCallKit` to customize display name for call recipients and configure `CXHandle` value. This value specified in `displayNameForCallKit` is exactly how it will show up in the last dialed call log. |
| 64 | + |
| 65 | + ```Swift |
| 66 | + options.callKitRemoteInfo.displayNameForCallKit = "DISPLAY_NAME" |
| 67 | + ``` |
| 68 | + 2. Assign the `cxHandle` value is what the application will receive when user calls back on that contact |
| 69 | + ```Swift |
| 70 | + options.callKitRemoteInfo.cxHandle = CXHandle(type: .generic, value: "VALUE_TO_CXHANDLE") |
| 71 | + ``` |
| 72 | + |
| 73 | + ### Specify call recipient info for incoming calls |
| 74 | + |
| 75 | + First we need to create an instance of `CallKitOptions`: |
| 76 | + |
| 77 | + ```Swift |
| 78 | + let callKitOptions = CallKitOptions(with: createProviderConfig()) |
| 79 | + ``` |
| 80 | + |
| 81 | + Configure the properties of `CallKitOptions` instance: |
| 82 | + |
| 83 | + Block that is passed to variable `provideRemoteInfo` will be called by the SDK when we receive an incoming call and we need to get a display name for the incoming caller, which we need to pass to the CallKit. |
| 84 | + |
| 85 | + ```Swift |
| 86 | + callKitOptions.provideRemoteInfo = self.provideCallKitRemoteInfo |
| 87 | + |
| 88 | + func provideCallKitRemoteInfo(callerInfo: CallerInfo) -> CallKitRemoteInfo |
| 89 | + { |
| 90 | + let callKitRemoteInfo = CallKitRemoteInfo() |
| 91 | + callKitRemoteInfo.displayName = "CALL_TO_PHONENUMBER_BY_APP" |
| 92 | + callKitRemoteInfo.cxHandle = CXHandle(type: .generic, value: "VALUE_TO_CXHANDLE") |
| 93 | + return callKitRemoteInfo |
| 94 | + } |
| 95 | + ``` |
| 96 | + |
| 97 | + ### Configure audio session |
| 98 | + |
| 99 | + Configure audio session will be called before placing or accepting incoming call and before resuming the call after it has been put on hold. |
| 100 | + |
| 101 | + ```Swift |
| 102 | + callKitOptions.configureAudioSession = self.configureAudioSession |
| 103 | + |
| 104 | + public func configureAudioSession() -> Error? { |
| 105 | + let audioSession: AVAudioSession = AVAudioSession.sharedInstance() |
| 106 | + var configError: Error? |
| 107 | + do { |
| 108 | + try audioSession.setCategory(.playAndRecord) |
| 109 | + } catch { |
| 110 | + configError = error |
| 111 | + } |
| 112 | + return configError |
| 113 | + } |
| 114 | + ``` |
| 115 | + |
| 116 | + NOTE: In cases where Contoso has already configured audio sessions DO NOT provide `nil` but return `nil` error in the block |
| 117 | + |
| 118 | + ```Swift |
| 119 | + callKitOptions.configureAudioSession = self.configureAudioSession |
| 120 | + |
| 121 | + public func configureAudioSession() -> Error? { |
| 122 | + return nil |
| 123 | + } |
| 124 | + ``` |
| 125 | + if `nil` is provided for `configureAudioSession` then SDK will call the default implementation in the SDK. |
| 126 | + |
| 127 | + ### Handle incoming push notification payload |
| 128 | + |
| 129 | + When the app receives incoming push notification payload, we need to call `handlePush` to process it. ACS Calling SDK will then raise the `IncomingCall` event. |
| 130 | + |
| 131 | + ```Swift |
| 132 | + public func handlePushNotification(_ pushPayload: PKPushPayload) |
| 133 | + { |
| 134 | + let callNotification = PushNotificationInfo.fromDictionary(pushPayload.dictionaryPayload) |
| 135 | + if let agent = self.callAgent { |
| 136 | + agent.handlePush(notification: callNotification) { (error) in } |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + // Event raised by the SDK |
| 141 | + public func callAgent(_ callAgent: CallAgent, didRecieveIncomingCall incomingcall: IncomingCall) { |
| 142 | + } |
| 143 | + ``` |
| 144 | + |
| 145 | + We can use `reportIncomingCallFromKillState` to handle push notifications when the app is closed. |
| 146 | + `reportIncomingCallFromKillState` API shouldn't be called if `CallAgent` instance is already available when push is received. |
| 147 | + |
| 148 | + ```Swift |
| 149 | + if let agent = self.callAgent { |
| 150 | + /* App is not in a killed state */ |
| 151 | + agent.handlePush(notification: callNotification) { (error) in } |
| 152 | + } else { |
| 153 | + /* App is in a killed state */ |
| 154 | + CallClient.reportIncomingCallFromKillState(with: callNotification, callKitOptions: callKitOptions) { (error) in |
| 155 | + if (error == nil) { |
| 156 | + DispatchQueue.global().async { |
| 157 | + self.callClient = CallClient() |
| 158 | + let options = CallAgentOptions() |
| 159 | + let callKitOptions = CallKitOptions(with: createProviderConfig()) |
| 160 | + callKitOptions.provideRemoteInfo = self.provideCallKitRemoteInfo |
| 161 | + callKitOptions.configureAudioSession = self.configureAudioSession |
| 162 | + options.callKitOptions = callKitOptions |
| 163 | + self.callClient!.createCallAgent(userCredential: userCredential, |
| 164 | + options: options, |
| 165 | + completionHandler: { (callAgent, error) in |
| 166 | + if (error == nil) { |
| 167 | + self.callAgent = callAgent |
| 168 | + self.callAgent!.handlePush(notification: callNotification) { (error) in } |
| 169 | + } |
| 170 | + }) |
| 171 | + } |
| 172 | + } else { |
| 173 | + os_log("SDK couldn't handle push notification KILL mode reportToCallKit FAILED", log:self.log) |
| 174 | + } |
| 175 | + } |
| 176 | + } |
| 177 | + ``` |
| 178 | + |
| 179 | + ## CallKit Integration (within App) |
| 180 | + |
| 181 | + If you wish to integrate the CallKit within the app and not use the CallKit implementation in the SDK, please take a look at the quickstart sample [here](https://github.com/Azure-Samples/communication-services-ios-quickstarts/tree/main/Add%20Video%20Calling). |
| 182 | + But one of the important things to take care of is to start the audio at the right time. Like following |
| 183 | + |
| 184 | + ```Swift |
| 185 | +let mutedAudioOptions = AudioOptions() |
| 186 | +mutedAudioOptions.speakerMuted = true |
| 187 | +mutedAudioOptions.muted = true |
| 188 | + |
| 189 | +let copyStartCallOptions = StartCallOptions() |
| 190 | +copyStartCallOptions.audioOptions = mutedAudioOptions |
| 191 | + |
| 192 | +callAgent.startCall(participants: participants, |
| 193 | + options: copyStartCallOptions, |
| 194 | + completionHandler: completionBlock) |
| 195 | +``` |
| 196 | + |
| 197 | +Muting speaker and microphone will ensure that physical audio devices aren't used until the CallKit calls the `didActivateAudioSession` on `CXProviderDelegate`. Otherwise the call may get dropped or no audio will be flowing. |
| 198 | + |
| 199 | +```Swift |
| 200 | +func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { |
| 201 | + activeCall.unmute { error in |
| 202 | + if error == nil { |
| 203 | + print("Successfully unmuted mic") |
| 204 | + activeCall.speaker(mute: false) { error in |
| 205 | + if error == nil { |
| 206 | + print("Successfully unmuted speaker") |
| 207 | + } |
| 208 | + } |
| 209 | + } |
| 210 | + } |
| 211 | +} |
| 212 | +``` |
| 213 | + |
| 214 | +> [!NOTE] |
| 215 | +> In some cases CallKit doesn't call `didActivateAudioSession` even though the app has elevated audio permissions, in that case the audio will stay muted until the call back is received. And the UI has to reflect the state of the speaker and microphone. The remote participant/s in the call will see that the user has muted audio as well. User will have to manually unmute in those cases. |
| 216 | +
|
| 217 | + ## Next steps |
| 218 | + - [Learn how to manage video](./manage-video.md) |
| 219 | + - [Learn how to manage calls](./manage-calls.md) |
| 220 | + - [Learn how to record calls](./record-calls.md) |
0 commit comments