This is a library that handles the challenges involved in integrating VoIP functionality into the iOS platform (hence Phone Integration Lib, or PIL for short).
No, it relies on iOSVoIPLib. A library that we also maintain which currently uses Linphone as the underlying SIP technology.
- Starting and stopping the VoIP layer based on application state
- Managing registrations
- Managing call objects
- Integration with Call Kit
- Audio Routing
- Bluetooth calling
- Responding to bluetooth headset input
- Fetching contact data
- Displaying ViewControllers appropriately
- Implement a closure to launch the Call ViewController
- [Optional] Implement a middleware class if required by your VoIP architecture
- [Optional] Implement a class to handle logs
This repo contains an example application with implementations of the basic functionality of the library, please check that if there are any questions not answered by this document.
In your AppDelegate under the didFinishLaunchingWithOptions method, you must perform a number of actions.
- Create the ApplicationSetup object:
let applicationSetup = ApplicationSetup(
middleware: self,
requestCallUi: {
if let nav = self.window?.rootViewController as? UITabBarController {
nav.performSegue(withIdentifier: "LaunchCallSegue", sender: nav)
}
},
logDelegate: self
)
The AppplicationSetup object is a way to bind certain parts of your code to functions in the PhoneIntegrationLib. These should all be considered static and should not change during the lifetime of your application.
Mandatory parameters:
- requestCallUi = A closure that will be automatically triggered when the call ui should be launched, in this closure you are responsible for executing the code required to launch your call ui.
Optional parameters:
- logger = Receive logs from the PIL and the underlying VoIP library
- middleware = If your VoIP architecture uses a middleware, in that you use APNS notifications to wake the phone, you must provide an implementation of Middleware. While this isn't required, incoming calls will not work in the background without it.
- Run startIOSPIL
_ = startIOSPIL(
applicationSetup: applicationSetup,
auth: Auth(
username: "user123",
password: "password123",
domain: "sip.domain.com",
port: 5061,
secure: true
),
autoStart: true
)
In this example we are hard-coding authentication details, in reality they will probably be fetched from some sort of storage. Passing TRUE to the autoStart parameter will simply automatically start the PIL rather than require an additional call to pil.start().
It is possible to get an instance of the PIL at any point:
let pil = PIL.instance
In xCode you must enable the following capabilities:
- Background Modes - Voice over VoIP
- Background Modes - Remote notifications
- Push notifications
And you must specify descriptions for:
- Privacy - Microphone Usage Description
- [Optional] Privacy - Contacts Usage Description
pil.call("0123456789")
If configured correctly, everything else should be handled for you, including launching your ViewController.
To retrieve a call object, simply request it from the PIL instance:
let call: Call? = pil.call
This call object is immutable and is a snap-shot of the call at the time it was requested.
The PIL will communicate with your app via events, the following is an example as to how you would use this to render your call ui.
- Your ViewController should implement the PILEventDelegate protocol and begin listening for events in the appropriate lifecycle methods.
class CallViewController: UIViewController, PILEventDelegate
override func viewWillAppear(_ animated: Bool) {
pil.events.listen(delegate: self)
}
override func viewDidDisappear(_ animated: Bool) {
pil.events.stopListening(delegate: self)
}
- You must then implement an onEvent method to handle these events.
func onEvent(event: Event, call: CallSessionState) {
}
As you can see, there is a CallSessionState parameter given which owns the properties below. Those should be used in combination with the event to render the call and tranfer ui:
public var activeCall: Call?
public var inactiveCall: Call?
public var audioState: AudioState
The audio state can be requested by querying:
let audioState: AudioState = pil.audio.state
Like the call object, this is also an immutable snap-shot at the time it was requested.
To check where you are currently routing audio simply call:
switch pi.audio.state.currentRoute {
case .speaker:
case .phone:
case .bluetooth
}
or if you need to know if Bluetooth is available:
pil.audio.state.availableRoutes.contains(.bluetooth)
All call interactions can be found on the CallActions object which is accessed via the actions property on the PIL.
pil.actions.end()
pil.actions.toggleHold()
Audio is not necessarily directly tied to a call so it can be found under the audio property on the PIL.
pil.audio.mute()
pil.audio.routeAudio(.bluetooth)
There are two files that you should replace as they are used by CallKit:
- Image: PhoneIntegrationLibCallKitIcon - This is an icon that will appear on the CallKit UI and should always be replaced.
- Audio: phone_integration_lib_call_kit_ringtone.wav - This is the alternative ring tone that can be used if chosen.