Using the iOS SDK

Overview

The VoiceSDK facade object is the starting point for all communications between the client and the Voice SDK.

Lifecycle

The Voice SDK is designed to fit the concept of account based applications where the application has login (auto-login) and logout actions.

The SDK client initializes the Voice SDK once and then may have multiple activation/deactivation cycles following user login or logout.

Configuration

To configure the Voice SDK you assign the user account configuration (Configuration) as follows:

 let sdk = VoiceSDK.shared
 // ā€¦
 sdk.configuration = Configuration(...)

The configuration can be updated at any time after SDK initialization.

struct Configuration: Hashable {
   var accountId: String
   var userId: String
   // ā€¦	
   var displayName: String
   var phoneNumber: String?
   // ā€¦
   var callKit: CallKitOptions
}

User account

The accountId and userId can only be changed when the SDK is in the .inactive state.

CallKit

The SDK has a built-in integration with the CallKit framework.
The SDK client can use CallKitOptions to customize the CallKit configuration.

struct CallKitOptions: Hashable {
   var localizedName: String
   // ā€¦	
   var ringtoneSound: String?
   // ā€¦
   var iconFileName: String?
}

The callKit property can only be changed when there are no active calls.

Authentication

The SDK client passes the jwtToken to the SDK by setting (updating) the authenticationContext property of VoiceSDK object as follows:

sdk.authenticationContext.jwtToken = jwtToken
sdk.authenticationContext.callback = { [weak self] token in
   // token refresh is requested
   self?.refreshToken()
}

Callbacks

You can modify certain aspects of SDK functionality by setting the following callbacks:

Log message

If the logMessageCallback is set, the Voice SDK stops to print messages to the console and passes them to the client:

VoiceSDK.logMessageCallback = { module, message, level, context in
   // print log message to the console
}

The log message logMessageCallback can be set before Voice SDK initialization.

Contact resolution

If the contactResolverCallback is set, the Voice SDK uses this callback to request the contact (caller) details when the incoming call notification (push) arrives on a device.

The Voice SDK uses the displayName property value (from contact details) as the caller name on an incoming call screen.

struct ContactInfo: Contact {
   var contactId: String
   var displayName: String?
   var avatarUrl: String?
   var phoneNumber: String?
   // ā€¦
}
ā€¦
VoiceSDK.contactResolverCallback = { context, completion in
   // ā€¦
   let contact = ContactInfo(contactId: context.callerId, 
                             displayName: context.callerName, 
                             avatarUrl: nil, phoneNumber: nil)
   completion?(contact)
}

The SDK client adopts the Contact protocol and returns the adjusted information in a completion handler.

Audio session

The Voice SDK requests client to configure an Audio Session via audioSessionConfigurationCallback.

VoiceSDK.audioSessionConfigurationCallback = { session in
   do {
      let mode: AVAudioSession.Mode = .voiceChat
      try session.setCategory(.playAndRecord, mode: mode, options: [.allowBluetooth])
      try session.overrideOutputAudioPort(.none)
      try session.setPreferredIOBufferDuration(0.01)
   } catch let error {
      //ā€¦
   }
}

The Voice SDK has audioSessionActivated property which indicates if Audio Session is activated (or deactivated) by CallKit.

var audioSessionActivated: CurrentValueSubject<Bool, Never> { get }

Activation

To activate the VoiceSDK, the client invokes the activate function:

func activate(completion: @escaping (_ result: Result<Void, ErrorType>) -> Void)
sdk.activate { result in
   switch result {
   case .success:
   //ā€¦
   case .failure:
   //ā€¦
   }
}

Upon a .success notification, the VoiceSDK object transitions to the .active state.

Otherwise, it comes back to the .inactive state.

enum State {
  case inactive, activating, active, deactivating
}
//ā€¦
sdk.state

The client can adopt the VoiceSDKObserverProtocol protocol and receive notifications on the VoiceSDK object state change:

func addObserver(_ observer: AnyObject)
sdk.addObserver(client)
//ā€¦

func handleStateChanged(_ state: VoiceSDK.State) {
   // ā€¦
}

Deactivation

To deactivate the VoiceSDK client, invoke the deactivate function:

func deactivate(completion: @escaping (_ result: Result<Void, ErrorType>) -> Void)
sdk.deactivate { result in
   switch result {
   case .success:
   //ā€¦
   case .failure:
   //ā€¦
   }
}

Upon successful completion, the VoiceSDK object transitions to an .inactive state.

Call features

Place an outgoing call

When an outgoing call is placed the following function is used:

func placeCall(callType: CallType, to callee: Contact,
               completion: @escaping (Result<VoiceCall, ErrorType>) -> Void)
//ā€¦
let completionBlock: (Result<VoiceCall, ErrorType>) -> Void = { result in
   switch result {
   case .success(let call):
   // present UI
   default: break
   }
   completion(result)
}
let parameters = OutgoingCallParameters(callType: .voip, callee: callee)
sdk.placeCall(with: parameters, completion: completionBlock)
//ā€¦

Receive an incoming call

When you configure the SDK for incoming calls, the client adopts the CallSetObserverProtocol protocol and registers the observer with the VoiceSDK object:

class MyVoiceSDKClient: CallSetObserverProtocol {
//ā€¦
   func handleCallAdded(_ call: VoiceCall) {
      if call.direction == .inbound {
      // present UI
      }
   }
//ā€¦
}
let client = MyVoiceSDKClient(...)
//ā€¦
sdk.addObserver(client)

Call observer protocols

The VoiceCall object is observable.

The SDK client can subscribe for changes by adopting the following protocols:

  • VoiceCallStateObserverProtocol
  • VoiceCallConnectionQualityObserverProtocol
  • VoiceCallMutedStateObserverProtocol

and registering as an observer with the VoiceCall object:

//ā€¦
call.addObserver(client)
//ā€¦

Mute a call

The VoiceCall object has the muted property which enables call muting as follows:

protocol VoiceCall: class {
//ā€¦
   var muted: CallMutedState { get set }
//ā€¦	
}

You can mute (or unmute) the call by assigning the corresponding value to the property:

call.muted = .on // .off

Put a call on hold

Use the following hold function for call holding:

protocol VoiceCall: class {
//ā€¦
   func hold(_ completion: ((CallActionCompletionStatus) -> Void)?)
   func resume(_ completion: ((CallActionCompletionStatus) -> Void)?)
//ā€¦
}
call.hold { status in
   switch status {
   //
   case .done:
   //
   case .canceled:
   //
   case .failed:
   }
}

The execution of the hold (resume) operation implicitly triggers the call state change.

Hang up a call

Use the hangup function to end the call:

protocol VoiceCall: class {
//ā€¦
   func hangup(_ completion: ((CallActionCompletionStatus) -> Void)?)
//ā€¦
}
call.hangup { status in
   switch status {
   case .done:
   //
   case .canceled:
   //
   case .failed:        
   }
}

On completion the call transitions to the disconnected state.
Additionally, the Voice SDK removes the call from the calls list.

Push Notifications

The Voice SDK handles PushKit notifications internally.

However, for debugging purpose, the SDK exposes the pushToken property as part of the VoiceSDK object.

Additionally, the client can monitor the push notification payload or observe the token change by using the PushNotificationObserverProtocol protocol.