1-to-1 audio call
This page provides a code example for making a 1-to-1 audio call.
Prerequisites
Before you begin, you must do the following:
- Initialize PlanetKit.
- Get a proper access token.
- Check the overall API usage flow in 1-to-1 call flow.
Considerations for implementing a 1-to-1 audio call
To receive call notifications on the callee side, you must implement a notification system or integrate an external push notification system such as Apple Push Notification service (APNs).
You also need to know what information must be delivered to the callee. Refer to Role of the app server, which describes cc_param
, the data that must be delivered by your application.
After calling makeCall()
or verifyCall()
, you must check the reason
property of the returned PlanetKitCallMakeResult
or PlanetKitCallVerifyResult
.
- If the
reason
isPlanetKitStartFailReason.none
, it means success. - Otherwise, it means failure and you must take an appropriate action based on the
reason
.
Implement event delegates
Implement the event delegates to be used on the call.
PlanetKitCallDelegate
is used to handle events related to call setup.PlanetKitMyMediaStatusDelegate
is used to handle the local user's media status change events. You can update the local user's UI based on these events, such as when the mic becomes muted or unmuted, or when the audio description is updated.
extension CallDelegateExample : PlanetKitCallDelegate {
func didWaitConnect(_ call: PlanetKitCall) {
// This is called after making a call on the caller side.
// Write your own code here.
}
func didVerify(_ call: PlanetKitCall, peerStartMessage: PlanetKitCallStartMessage?, peerUseResponderPreparation: Bool) {
// This is called after verifying a call on the callee side.
// Write your own code here.
}
func didConnect(_ call: PlanetKitCall, connected: PlanetKitCallConnectedParam) {
// This is called after the call is connected on both sides.
// Write your own code here.
}
func didDisconnect(_ call: PlanetKitCall, disconnected: PlanetKitDisconnectedParam) {
// This is called after the call is disconnected on both sides.
// Write your own code here.
}
func didFinishPreparation(_ call: PlanetKitCall) {
// This is called after making a call on the caller side.
// Write your own code here.
}
...
//
// Also, you should implement other methods defined in the PlanetKitCallDelegate protocol.
//
...
}
extension MyMediaStatusDelegateExample: PlanetKitMyMediaStatusDelegate {
func didMuteMic(_ myMediaStatus: PlanetKitMyMediaStatus) {
// This is called when the local user's audio is muted.
// Write your own code here.
}
func didUnmuteMic(_ myMediaStatus: PlanetKitMyMediaStatus) {
// This is called when the local user's audio is unmuted.
// Write your own code here.
}
func didUpdateAudioDescription(_ myMediaStatus: PlanetKitMyMediaStatus, description: PlanetKitMyAudioDescription) {
// This is called when the local user's audio description is updated.
// Write your own code here.
}
}
Make an outgoing call (caller side)
The steps for making a call are as follows:
- Build a setting through
PlanetKitMakeCallSettingBuilder
. - Create
PlanetKitCallParam
withPlanetKitCallDelegate
. - Call
PlanetKitManager.shared.makeCall()
. - Check the
PlanetKitCallMakeResult
.
class CallerExample
{
var call: PlanetKitCall?
var myMediaStatusDelegate: MyMediaStatusDelegateExample
}
...
extension CallerExample
{
func makeCallExample(myId: String, myServiceId: String, peerId: String, peerServiceId: String, accessToken: String, delegate: PlanetKitCallDelegate) {
let myUserId = PlanetKitUserId(id: myId, serviceId: myServiceId)
let peerUserId = PlanetKitUserId(id: peerId, serviceId: peerServiceId)
let settings = try! PlanetKitMakeCallSettingBuilder().build()
let param = PlanetKitCallParam(myUserId: myUserId, peerUserId: peerUserId, delegate: delegate, accessToken: accessToken)
let result = PlanetKitManager.shared.makeCall(param: param, settings: settings)
guard result.reason == .none else {
NSLog("Failed reason: result. \(result.reason)")
return
}
// The result.call instance is a call instance for calling other APIs from now on.
// You must keep this instance in your own context.
// In this example, the "call" variable holds the instance.
call = result.call
}
}
Receive a push notification (callee side)
The call recipient comes to know that a new call has arrived through a push notification. The application must parse cc_param
from the userInfo
.
After receiving the push notification for a new call, you must respond to the call to continue.
extension YourPushHandler : PKPushRegistryDelegate {
...
var callee: CalleeExample
var callDelegate: PlanetKitCallDelegate
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
// Update the push token for the application's push server.
}
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
let message = payload.dictionaryPayload as NSDictionary as! [String: AnyObject]
let param = message["cc_param"] as! String
let ccParam = PlanetKitCCParam(ccParam: param)
// Now we received a push notification.
// We should write code here to respond to the new call.
// For convenience, we assume this is done in verifyCallExample()
let myUserId = "Callee User ID value"
let myServiceId = "Callee Service ID value"
callee.verifyCallExample(myId: myUserId, myServiceId: myServiceId, ccParam: ccParam, delegate: PlanetKitCallDelegate)
}
}
Respond to an incoming call (callee side)
Now you have to respond to the call by calling PlanetKitManager.shared.verifyCall()
.
The following verifyCallExample()
shows how to respond to the call. Note that myId
is the callee's user ID and myServiceId
is the callee's service ID.
class CalleeExample
{
var verifiedCall: PlanetKitCall?
var myMediaStatusDelegate: MyMediaStatusDelegateExample
}
...
extension CalleeExample
{
func verifyCallExample(myId: String, myServiceId: String, ccParam: PlanetKitCCParam, delegate: PlanetKitCallDelegate) {
let settings = try! PlanetKitVerifyCallSettingBuilder().build()
let myUserId = PlanetKitUserId(id: myId, serviceId: myServiceId)
let result = PlanetKitManager.shared.verifyCall(myUserId: myUserId, ccParam: ccParam, delegate: delegate)
guard result.reason == .none else {
NSLog("Failed reason: result. \(result.reason)")
return
}
// The result.call instance is the verified call instance.
// You have to keep this instance in an application layer to call other APIs provided after call setup.
// This example keeps the verified call instance in the following verifiedCall variable.
verifiedCall = result.call
}
}
Accept an incoming call (callee side)
In general, the recipient needs time to decide whether to accept a new call after responding to the call. To accept a call, call acceptCall()
.
The verifiedCall
variable is the PlanetKitCall
instance that has been verified by verifyCall()
.
extension CalleeExample
{
func acceptCallExample() {
verifiedCall.acceptCall(startMessage: nil, useResponderPreparation: false)
}
}
Set the media status event delegate for the local user
Once the call is connected, add the media status event delegate for the local user.
// Caller side
extension CallerExample
{
func addMyMediaStatusHandlerExample() {
call.myMediaStatus.addHandler(myMediaStatusDelegate) {
// completion callback
}
}
}
// Callee side
extension CalleeExample
{
func addMyMediaStatusHandlerExample() {
verifiedCall.myMediaStatus.addHandler(myMediaStatusDelegate) {
// completion callback
}
}
}
End a call
To end a call, call endCall()
.
// Caller side
extension CallerExample
{
func endCallExample() {
call.endCall()
}
}
// Callee side
extension CalleeExample
{
func endCallExample() {
verifiedCall.endCall()
}
}
Implement Apple CallKit in the application
This step applies to iOS applications only.
CallKit is one of Apple's frameworks that provides the calling interface. PlanetKit SDK supports three options for CallKit: integration of user CallKit implementation, integration of PlanetKit internal implementation, and no integration.
By default, PlanetKit SDK makes or verifies a call without CallKit integration. If you want to integrate your own implementation of CallKit, you must set type
of PlanetKitCallKitSetting
to user
when making or verifying a call with PlanetKit SDK.
Caller example
-
Create a
PlanetKitCallKitSetting
instance and settype
ofPlanetKitCallKitSetting
touser
. -
Pass the instance to
withCallKitSettingsKey()
ofPlanetKitMakeCallSettingBuilder
and build settings.// Code for Step 1 and Step 2
func createMakeCallSettingsWithUserCallKitExample() -> [String: Any] {
let callKitSetting = PlanetKitCallKitSetting(type: .user, param: nil)
var settingsBuilder = try! PlanetKitMakeCallSettingBuilder().withCallKitSettingsKey(setting: callKitSetting)
let makeCallSettings = settingsBuilder.build()
} -
On
provider(_:didActivate:)
ofCXProviderDelegate
, callnotifyCallKitAudioActivation()
ofPlanetKitCall
.// Code for Step 3
extension YourCallKitHandler : CXProviderDelegate {
...
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
// PlanetKitCall instance created from PlanetKitManager.makeCall()
call.notifyCallKitAudioActivation()
}
...
}
Callee example
-
Create a
PlanetKitCallKitSetting
instance and settype
ofPlanetKitCallKitSetting
touser
. -
Pass the instance to
withCallKitSettingsKey()
ofPlanetKitVerifyCallSettingBuilder
and build settings.// Code for Step 1 and Step 2
func createVerifyCallSettingsWithUserCallKitExample() -> [String: Any] {
let callKitSetting = PlanetKitCallKitSetting(type: .user, param: nil)
var settingsBuilder = try! PlanetKitVerifyCallSettingBuilder().withCallKitSettingsKey(setting: callKitSetting)
let verifyCallSettings = settingsBuilder.build()
} -
Implement a CallKit handler to handle CallKit operations.
class YourCallKitHandler {
static let shared = YourCallKitHandler()
private var provider: CXProvider?
private var callController: CXCallController?
private var call: PlanetKitCall?
...
// Implement initialization and other features to handle CallKit operations.
} -
Call
acceptCall()
andendCall()
ofPlanetKitCall
based onCXCallAction
ofprovider(_:perform:)
ofCXProviderDelegate
.extension YourCallKitHandler : CXProviderDelegate {
...
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
call.acceptCall(useResponderPreparation: false, startMessage: nil)
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
call.endCall()
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
call.muteMyAudio(action.isMuted) { success in }
action.fulfill()
}
// Implement other delegate functions to handle other CXCallActions
...
} -
On
provider(_:didActivate:)
ofCXProviderDelegate
, callnotifyCallKitAudioActivation()
ofPlanetKitCall
.extension YourCallKitHandler : CXProviderDelegate {
...
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
// PlanetKitCall instance created from PlanetKitManager.makeCall()
call.notifyCallKitAudioActivation()
}
// Implement other delegate functions to handle other CXCallActions
...
} -
Modify UI code to call
muteMyAudio()
,hold()
, andunhold()
ofPlanetKitCall
through CallKit to synchronize CallKit and PlanetKit.extension YourCallViewController {
@IBAction func muteCall(_ sender: UIButton) {
YourCallKitHandler.shared.muteCall()
}
}
extension YourCallKitHandler {
func muteCall() {
let action = CXSetMutedCallAction(call: call.uuid , muted: true)
callController.requestTransaction(with: action) { error in
NSLog("\(error)")
}
}
}
For more information, see CallKit documentation.