Adding the liblinphone dependency to your iOS project

Liblinphone for iOS is available using Cocoapods, the de-facto standard in the Apple developer world for "dependency management for Swift and Objective-C". Liblinphone can also be compiled from the sources.

Using Cocoapods

For a project named "Myproject"

open Myproject/, use commands:

pod init

Modify the generated Podfile according to your project:

# Uncomment the next line to define a global platform for your project

##For macosx
# platform :osx, '10.9'
#source "https://gitlab.linphone.org/BC/public/podspec-macos.git"

#For iOS
platform :ios, '9.0'
source "https://gitlab.linphone.org/BC/public/podspec.git"

target 'Myproject' do
 use_frameworks!

  # Pods for Myproject
 pod 'linphone-sdk' , '4.3'
end

 To install or update the liblinphone version, do the following command 

pod repo update
pod install

And you're done.

Some alpha (development versions) of liblinphone may also be available. To get them, replace the line:

pod 'linphone-sdk' , '4.3'

by

pod 'linphone-sdk', '> 4.4.0-alpha' 

followed by:

pod install

Compiling linphone-sdk

Linphone-sdk is the name of the git project that contains liblinphone and all its dependencies.

Compilation instructions of the SDK are available at: linphone-sdk/README.md

Once done, cocoapods needs to be invoked to update the Xcode workspace to use your locally built linphone-sdk, as follows:

PODFILE_PATH=<path to linphone-sdk-ios> pod install
where <path to linphone-sdk-ios>  is your build directory of the linphone-sdk project, containing the linphone-sdk.podspec file and a linphone-sdk output directory comprising built frameworks and resources.

Using liblinphone

Liblinphone has a C API, suitable to be used with Objective-C, and a modern Swift API. To use in swift, import the liblinphone swift module in your source file using

import linphonesw

API documentation

You can find the liblinphone C API documentation here.

For Swift, online documentation is available here . In addition, this iOS sample app shows how to use liblinphone in a Swift project.

Guidelines for integrating with push notifications

Introduction

An iOS application has in general a very limited capability to run in background, for example to keep a connection to a SIP server in order to receive calls and IM at any time. When the application goes in background, the network connections are killed and the application no longer executes.

The solution to circumvent this limitation is to rely on Apple's push notification systems. The push notification first resumes the app in background, that can then connect to the SIP service and retrieve the pending INVITE or MESSAGE request.

New limitations and restrictions were added by Apple with Xcode 11 and iOS 13. These restrictions are:

  • applications can no longer use PushKit kind of push notifications for anything else than presenting a VoIP call with CallKit. This is disruptive because previously, communication apps were also using PushKit get notified of incoming IM messages. Indeed, PushKit has the main advantage of waking up the application in background without displaying anything to the user. Instead, the application can silently connect to the service, receive the pending message or call, and display it with all its associated data (such as sender or caller ID) to the end-user through either a Callkit call or a notification popup.
  • applications receiving a PushKit notification are required to immediately invoke Callkit to display the call to the end-user. This is disruptive also, because at the time of receiving the push notification, the app has not yet the signaling information about the call (ie, the SIP INVITE).  The end user may even accept the call while the INVITE is not yet arrived, in which case the app has to postpone the call acceptance to the actual receiving of the INVITE, and also update the CallKit view with the missing information.

The next chapters explain the detailed steps to create a Liblinphone based application that comply with the above requirements. It can be used of course as a guide to upgrade an existing liblinphone based application to iOS 13 / Xcode 11.

Please note that an iOS application compiled with Xcode 10 still executes normally on iOS 13. The disruption happens only while compiling with Xcode 11.

Technical solutions to advertise incoming calls

The table below summarizes the technical solutions offered to advertise incoming calls.

Use casePush notification typeSolution
App showing calls with Callkit, with access to internet.PushkitIntegrate CallKit into your application according to the guidelines provided in the section "Callkit integration" of this document.
App showing calls with Callkit, in an isolated network.No push notification at all.

Use UIApplication's keepAliveTimeout to trigger periodic calls to Core.refreshRegisters() and background sockets. Use Callkit in your app, but without Pushkit. Liblinphone by default attempts to activate background sockets.

Using this solution requires a special authorization from Apple. The app must have its main usage done without connection to the internet.

App not showing calls with callkit.

This is the case where using the Callkit interface is not adapted

  

CallKit Integration

Starting from linphone-sdk >= 4.3, Callkit must be integrated in the following way:

  • Add the CallKit framework into your application's dependencies in Xcode.
  • Implement the CXProviderDelegate protocol within your ApplicationDelegate
  • Add the configuration "use_callkit=1" in the section "app" or call linphone_core_enable_callkit before the linphone core starts.
  • Since iOS 13, Apple requests CallKit to be invoked to display the incoming call immediately when a PushKit notification is received. So sometimes you can answer the CallKit before a LinphoneCall is received. In the callback CXAnswerCallAction,  if a LinphoneCall has not yet been received,  you need to configure your AVAudioSession and accept the call when you receive it. Otherwise, accept the call directly.
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
   if (call == nil || call.state != Call.State.IncomingReceived) {
           // configure audio session here. Use 48000 Hz as sampling rate.
    } else {
       // accept call here
    }
    action.fulfill()
}
  • CallKit MUST inform the LinphoneCore when AVAudioSession is activated, as follows:
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        lc.activateAudioSession(actived: true)
 }

func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        lc.activateAudioSession(actived: false)
 }

Handling liblinphone log

In order to see liblinphone logs in your IOS app (for example in your Xcode console) follow these steps :

Put in your code at the launching of the app :

linphone_core_set_log_handler(your_log_handler);

This will make the liblinphone logs to call your_log_handler and so be processed as a log from your app.

You can set the liblinphone log level by using the functions documented here.

IOS log handler for liblinphone

Once you have set your log handler, you need to process liblinphone log in order to incorporate them into your app logs.

Here is a short example of how to manage liblinphone log into an IOS app :

void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args) {
NSString *format = [[NSString alloc] initWithUTF8String:fmt];
NSString *formatedString = [[NSString alloc] initWithFormat:format arguments:args];
NSString *lvl;

if (!domain)
  domain = "lib";
// since \r are interpreted like \n, avoid double new lines when logging network packets (belle-sip)
// output format is like: I/ios/some logs. We truncate domain to **exactly** DOMAIN_SIZE characters to have
// fixed-length aligned logs
switch (lev) {
 case ORTP_FATAL:
   lvl = @"Fatal";
  break;
 case ORTP_ERROR:
   lvl = @"Error";
  break;
 case ORTP_WARNING:
   lvl = @"Warning";
  break;
 case ORTP_MESSAGE:
   lvl = @"Message";
  break;
 case ORTP_DEBUG:
   lvl = @"Debug";
  break;
 case ORTP_TRACE:
   lvl = @"Trace";
  break;
 case ORTP_LOGLEV_END:
  return;
 }
if ([formatedString containsString:@"\n"]) {
 NSArray *myWords = [[formatedString stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"]
  componentsSeparatedByString:@"\n"];
 for (int i = 0; i < myWords.count; i++) {
  NSString *tab = i > 0 ? @"\t" : @"";
  if (((NSString *)myWords[i]).length > 0) {
    NSLog(@"[%@] %@%@", lvl, tab, (NSString *)myWords[i]);
   }
  }
 } else {
  NSLog(@"[%@] %@", lvl, [formatedString stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"]);
 }
}

CallKit Integration

Starting from linphone-sdk >= 4.3, Callkit must be integrated in the following way:

  • Add the CallKit framework into your application's dependencies in Xcode.
  • Implement the CXProviderDelegate protocol within your ApplicationDelegate
  • Add the configuration "use_callkit=1" in the section "app" or call linphone_core_enable_callkit before the linphone core starts.
  • Since iOS 13, Apple requests CallKit to be invoked to display the incoming call immediately when a PushKit notification is received. So sometimes you can answer the CallKit before a LinphoneCall is received. In the callback CXAnswerCallAction,  if a LinphoneCall has not yet been received,  you need to configure your AVAudioSession and accept the call when you receive it. Otherwise, accept the call directly.
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
   if (call == nil || call.state != Call.State.IncomingReceived) {
           // configure audio session here. Use 48000 Hz as sampling rate.
    } else {
       // accept call here
    }
    action.fulfill()
}
  • CallKit MUST inform the LinphoneCore when AVAudioSession is activated, as follows:
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        lc.activateAudioSession(actived: true)
 }

func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        lc.activateAudioSession(actived: false)
 }
Tags: IOS