Hide last authors
SandrineAvakian 12.1 1 (% class="row" %)
SandrineAvakian 7.5 2 (((
SandrineAvakian 12.1 3 (% class="col-xs-12 col-sm-8" %)
4 (((
Quentin Arguillère 69.1 5 = Getting started with our tutorials =
SandrineAvakian 1.1 6
Quentin Arguillère 69.1 7 With the release of the linphone SDK 5.0, you will also find a set of [[Swift liblinphone tutorials for IOS>>https://gitlab.linphone.org/BC/public/tutorials/-/tree/master/ios]]. Use them to experiment with simple applications that covers the most common usages : call, chat, push notifications..
8
Simon Morlat 44.2 9 = Adding the liblinphone dependency to your iOS project =
Admin Admin 13.1 10
Simon Morlat 44.2 11 Liblinphone for iOS is available using [[Cocoapods>>https://cocoapods.org]], 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.
Admin Admin 13.1 12
Simon Morlat 44.2 13 Using Cocoapods
Danmei Chen 26.2 14
jehan monnier 32.2 15 For a project named "Myproject"
Danmei Chen 26.2 16
Danmei Chen 27.2 17 open Myproject/, use commands:
Admin Admin 13.1 18
Danmei Chen 27.2 19 {{code}}
20 pod init
21 {{/code}}
Admin Admin 13.1 22
jehan monnier 32.2 23 Modify the generated Podfile according to your project:
Danmei Chen 27.2 24
Admin Admin 13.1 25 {{code}}
Danmei Chen 27.2 26 # Uncomment the next line to define a global platform for your project
27
28 ##For macosx
29 # platform :osx, '10.9'
30 #source "https://gitlab.linphone.org/BC/public/podspec-macos.git"
31
32 #For iOS
33 platform :ios, '9.0'
34 source "https://gitlab.linphone.org/BC/public/podspec.git"
35
36 target 'Myproject' do
37 use_frameworks!
38
39 # Pods for Myproject
Quentin Arguillère 68.1 40 pod 'linphone-sdk' , '5.0'
Danmei Chen 27.2 41 end
42
Admin Admin 13.1 43 {{/code}}
44
jehan monnier 34.2 45 To install or update the liblinphone version, do the following command
Admin Admin 13.1 46
Danmei Chen 27.2 47 {{code}}
48 pod repo update
49 pod install
50 {{/code}}
Admin Admin 16.1 51
Danmei Chen 56.1 52 == ==
Admin Admin 16.1 53
Simon Morlat 44.2 54 And you're done.
Admin Admin 13.1 55
Simon Morlat 44.2 56 Some alpha (development versions) of liblinphone may also be available. To get them, replace the line:
jehan monnier 38.1 57
jehan monnier 32.2 58 {{box}}
Quentin Arguillère 68.1 59 pod 'linphone-sdk' , '5.0'
jehan monnier 32.2 60 {{/box}}
61
62 by
63
Danmei Chen 27.2 64 {{code}}
Quentin Arguillère 68.1 65 pod 'linphone-sdk', '> 5.1.0-alpha'
Danmei Chen 32.1 66 {{/code}}
67
jehan monnier 32.3 68 followed by:
69
70 {{code}}
71 pod install
72 {{/code}}
73
Simon Morlat 44.2 74 == Compiling linphone-sdk ==
Danmei Chen 32.1 75
Simon Morlat 44.2 76 Linphone-sdk is the name of the git project that contains liblinphone and all its dependencies.
Admin Admin 13.1 77
Simon Morlat 44.2 78 Compilation instructions of the SDK are available at: [[linphone-sdk/README.md>>https://gitlab.linphone.org/BC/public/linphone-sdk/blob/master/README.md]]
jehan monnier 38.2 79
Simon Morlat 44.2 80 Once done, cocoapods needs to be invoked to update the Xcode workspace to use your locally built linphone-sdk, as follows:
jehan monnier 32.2 81
Simon Morlat 44.2 82 {{code language="sh"}}
83 PODFILE_PATH=<path to linphone-sdk-ios> pod install
Danmei Chen 27.2 84 {{/code}}
85
Simon Morlat 44.2 86 {{{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.
87 }}}
jehan monnier 38.2 88
Simon Morlat 44.2 89 = Using liblinphone =
Danmei Chen 27.2 90
Simon Morlat 44.3 91 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
Simon Morlat 44.2 92
Danmei Chen 27.2 93 {{code}}
Simon Morlat 44.2 94 import linphonesw
Danmei Chen 27.2 95 {{/code}}
96
Simon Morlat 44.2 97 == API documentation ==
Danmei Chen 27.2 98
Quentin Arguillère 70.1 99 You can find the liblinphone C API documentation [[here>>https://linphone.org/releases/docs/liblinphone/5.0/c/]].
jehan monnier 32.2 100
Quentin Arguillère 70.1 101 For Swift, online documentation is available [[here>>https://linphone.org/releases/docs/liblinphone/5.0/swift/]] . In addition, this [[iOS sample app>>https://gitlab.linphone.org/BC/public/tutorials/-/tree/master/ios]] shows how to use liblinphone in a Swift project.
jehan monnier 32.2 102
Simon Morlat 44.4 103 = Guidelines for integrating with push notifications =
104
Simon Morlat 46.2 105 == Introduction ==
Simon Morlat 44.4 106
Simon Morlat 46.2 107 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.**
108
Simon Morlat 51.2 109 The solution to circumvent this limitation is to rely on **Apple's push notification** system. The push notification first launch or resumes the app, that can then connect to the SIP service and retrieve the pending INVITE or MESSAGE request. In iOS there are three kinds of push notifications:
Simon Morlat 44.8 110
Simon Morlat 51.2 111 * The [[Remote Notification>>https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/HandlingRemoteNotifications.html#//apple_ref/doc/uid/TP40008194-CH6-SW1]], for general purpose notifications
112 * The [[PushKit notifications>>https://developer.apple.com/documentation/pushkit]], dedicated to VoIP calls. Unlike Remote Push Notifications, they do not display anything by themselves.
Simon Morlat 51.3 113 * The [[Background Update Notifications>>https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app]], used for service like notifications. They are not real-time and severely rate limited (no more than 3 per hour). They are currently not used by Linphone, liblinphone or Flexisip.
Simon Morlat 51.2 114
Simon Morlat 51.3 115 **New limitations and restrictions were added by Apple with Xcode 11 and iOS 13:**
Simon Morlat 51.2 116
Simon Morlat 51.3 117 * **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 tend to also use PushKit to get notified of incoming IM messages. Since they can wake up the app in background, it was convenient to perform background processing before displaying the message to the user.
118 * **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.
Simon Morlat 44.8 119
Simon Morlat 46.2 120 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.
Simon Morlat 44.8 121
Simon Morlat 46.2 122 (% class="box infomessage" %)
123 (((
Simon Morlat 44.8 124 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.
Simon Morlat 46.2 125 )))
Simon Morlat 44.8 126
Simon Morlat 46.2 127 == Technical solutions to advertise incoming calls ==
Simon Morlat 44.8 128
Simon Morlat 46.2 129 The table below summarizes the technical solutions offered to advertise incoming calls.
Simon Morlat 44.8 130
Simon Morlat 46.2 131 |=Use case|=Push notification type|=Solution
Simon Morlat 47.1 132 |App showing calls with **Callkit**, with access to internet.|[[Pushkit>>https://developer.apple.com/documentation/pushkit]]|(((
133 Integrate CallKit into your application according to the guidelines provided in the section "[[Callkit integration>>doc:||anchor="Callkit integration"]]" of this document.
134
135 This solution is the one implemented in the Linphone application.
136 )))
Simon Morlat 46.3 137 |App showing calls with **Callkit**, in an isolated network.|No push notification at all.|(((
138 Use UIApplication's [[keepAliveTimeout>>https://developer.apple.com/documentation/uikit/uiapplication/1622989-setkeepalivetimeout]] to trigger periodic calls to [[Core.refreshRegisters()>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/Core.html#/s:10linphonesw4CoreC16refreshRegistersyyF]] and [[background sockets>>https://developer.apple.com/documentation/foundation/nsstreamnetworkservicetypevoip?language=objc]]. Use Callkit in your app, but without Pushkit. Liblinphone by default attempts to activate background sockets.
Simon Morlat 46.2 139
Simon Morlat 46.3 140 **Using this solution requires a special authorization from Apple. The app must have its main usage done without connection to the internet.**
Simon Morlat 46.2 141 )))
Simon Morlat 46.3 142 |(((
Simon Morlat 46.5 143 Basic app **not showing calls with Callkit.**
Simon Morlat 46.2 144
Simon Morlat 46.4 145 This is the case where the Callkit UI is not adapted.
Simon Morlat 46.2 146
Simon Morlat 46.4 147 For example: an app that needs to establish video at first, home automation app showing video before the call is accepted.
Simon Morlat 51.1 148 )))|[[Remote Push Notifications>>https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns]]|(((
149 Use [[Remote Push Notifications>>https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns]] in a basic scheme.
Simon Morlat 46.2 150
Simon Morlat 46.5 151 * They will play a sound and vibrate once.
152 * They cannot be cancelled (for example if the caller cancels the call).
153 * The caller identity must be added by the SIP server in the remote push notification in order to be shown to the user.
Simon Morlat 46.2 154
Simon Morlat 46.5 155 When pressed by the user, the notification will launch or resume the app. The app can then connect to the SIP service and display the incoming call in its UI.
156 )))
157 |(((
158 **Advanced** app **not showing calls with Callkit**.
Simon Morlat 46.3 159
Simon Morlat 46.6 160 This is the case where the Callkit UI is not adapted.
Simon Morlat 46.4 161
Simon Morlat 46.6 162 For example: an app that needs to establish video at first, home automation app showing video before the call is accepted.
Simon Morlat 51.1 163 )))|[[Remote Push Notifications>>https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns]] used together with a [[Notification Content Extension>>https://developer.apple.com/documentation/usernotificationsui/unnotificationcontentextension]]|(((
164 Use [[Remote Push Notifications>>https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns]], but handle them inside an app extension of type "[[Notification Content>>https://developer.apple.com/documentation/usernotificationsui/unnotificationcontentextension]]". The app extension is a companion bundled with main application. It executes in a different process with restricted permissions, for example it cannot access geolocation information.
Simon Morlat 46.5 165
Simon Morlat 46.7 166 * The app extension can vibrate repeatedly
167 * The app extension can wait for the INVITE to be received, present caller information, and can be cancelled if the caller cancelled the call.
168 * The app extension can show video received as early-media in the notification area.
169
170 When the notification is pressed by the user, the main app is resumed or launched. The app extension stops, and the call needs to be received a second time by the main app. The main app can know if the user has accepted the call in the notification, and then immediately transition the call to a running state.
Simon Morlat 46.6 171 )))
Simon Morlat 46.5 172
Simon Morlat 48.1 173 (% class="box infomessage" %)
174 (((
175 Flexisip SIP proxy server version >= 2.0 has support for all the push notification solutions described above.
176 )))
177
Quentin Arguillère 66.1 178 == Push integration in SDK > 5.0 ==
179
180 Several features have been added in the version 5.0 of the linphone SDK to make the integration of push notifications (voip & remote) easier for the application developper. If you wish to enable them, you can have a look here : [[Enabling 5.0 Push Management in SDK>>https://wiki.linphone.org/xwiki/wiki/public/view/Lib/Features/Liblinphone%205.0%20new%20features%20and%20how%20to%20use%20them/#HiOS]]
181
Danmei Chen 50.1 182 == CallKit integration ==
Simon Morlat 46.5 183
Simon Morlat 44.5 184 Starting from linphone-sdk >= 4.3, **Callkit** must be integrated in the following way:
185
186 * Add the **CallKit** framework into your application's dependencies in Xcode.
187 * Implement the **CXProviderDelegate** protocol within your **ApplicationDelegate**
Danmei Chen 49.1 188 * Add the configuration **"use_callkit=1"** in the section **"app" **or call [[**linphone_core_enable_callkit()**>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/Core.html#/s:10linphonesw4CoreC14callkitEnabledSbvp]] before the linphone core starts.
189 * **Your CallKit delegate** MUST inform the **LinphoneCore** when **AVAudioSession** is activated, as follows:
Simon Morlat 44.5 190
191 {{code language="swift"}}
Danmei Chen 49.1 192 func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
193 lc.activateAudioSession(actived: true)
194 }
195
196 func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
197 lc.activateAudioSession(actived: false)
198 }
199 {{/code}}
200
Danmei Chen 64.1 201 * Since linphone-sdk >= 4.5, **your CallKit delegate** MUST configure the **AVAudioSession **when a new call is incoming/outgoing, as follows:
Danmei Chen 59.1 202
203 (((
204 {{code language="swift"}}
205 func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
206 CallManager.instance().lc?.configureAudioSession()
Danmei Chen 61.1 207 // accept call here
Danmei Chen 59.1 208 action.fulfill()
209 }
210
211 func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
212 CallManager.instance().lc?.configureAudioSession()
213 // start an outgoing call
214 action.fulfill()
215 }
216 {{/code}}
217 )))
218
Danmei Chen 56.1 219 === Display an incoming call ===
220
221 {{code language="swift"}}
222 let update = CXCallUpdate()
223 update.remoteHandle = CXHandle(type:.generic, value: "Call incoming")
224 let uuid = UUID()
225 provider.reportNewIncomingCall(with: uuid, update: update) {error in}
226 {{/code}}
227
Danmei Chen 50.1 228 === Answer an incoming call ===
Danmei Chen 49.1 229
230 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.
231
232 {{code language="swift"}}
Simon Morlat 44.5 233 func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
234 if (call == nil || call.state != Call.State.IncomingReceived) {
235 // configure audio session here. Use 48000 Hz as sampling rate.
236 } else {
237 // accept call here
Danmei Chen 54.1 238 call.acceptWithParams(params: callParams)
Simon Morlat 44.5 239 }
240 action.fulfill()
241 }
242 {{/code}}
243
Danmei Chen 50.1 244 === Terminate a call ===
Simon Morlat 44.5 245
246 {{code language="swift"}}
Danmei Chen 49.1 247 func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
248 // terminate call here
Danmei Chen 54.1 249 call.terminate()
Danmei Chen 49.1 250 action.fulfill()
251 }
252 {{/code}}
Simon Morlat 44.5 253
Danmei Chen 50.1 254 === Start an outgoing call ===
Danmei Chen 49.1 255
256 When starting an outgoing call from the application view, it must following these steps:
257
Danmei Chen 58.1 258 Before starting an outgoing call, make sure others calls are paused by callkit.
Danmei Chen 49.1 259
Danmei Chen 58.1 260 Then add these codes to your class when starting an outgoing call
261
Danmei Chen 49.1 262 {{code language="swift"}}
263 let handle = CXHandle(type: .generic, value: displayName)
264 let startCallAction = CXStartCallAction(call: uuid, handle: handle)
265 let transaction = CXTransaction(action: startCallAction)
266 let callController = CXCallController()
267 callController.request(_ transaction: transaction, completion: @escaping (Error?) -> Void)
Simon Morlat 44.5 268 {{/code}}
269
Danmei Chen 49.1 270 Then this callback will be called.
271
272 {{code language="swift"}}
273 func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
274 // start an outgoing call
Danmei Chen 54.1 275 lc.inviteAddressWithParams(addr: addr, params: params)
Danmei Chen 49.1 276 action.fulfill()
277 }
278
279 {{/code}}
280
281 To ensure the audio session works properly for this outgoing call.
282
283 {{code language="swift"}}
284 // When outgoing call is created
285 reportOutgoingCall(with UUID: UUID, startedConnectingAt dateStartedConnecting: Date?)
286 // When outgoing call is answered
287 reportOutgoingCall(with UUID: UUID, connectedAt dateConnected: Date?)
288
289
290 {{/code}}
291
Danmei Chen 50.1 292 === Hold/resume a call ===
Danmei Chen 49.1 293
Danmei Chen 58.1 294 Before resuming a call, make sure others calls are paused by callkit.
295
Danmei Chen 49.1 296 {{code language="swift"}}
297 func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
298 // If this is a conference, leave/enter the conference.
299 // Otherwide, pause/resume the call.
Danmei Chen 54.1 300 call.pause()
Danmei Chen 49.1 301 action.fulfill()
302 }
303
304 {{/code}}
305
Danmei Chen 50.1 306 === Mute/un-mute a call ===
Danmei Chen 49.1 307
308 {{code language="swift"}}
309 func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
310 // Mute/un-mute a call
Danmei Chen 54.1 311 lc.micEnabled = false
Danmei Chen 49.1 312 action.fulfill()
313 }
314
315 {{/code}}
316
Danmei Chen 50.1 317 === Play DTMF ===
Danmei Chen 49.1 318
319 {{code language="swift"}}
320 func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {
321 // Send DTMF
Danmei Chen 54.1 322 call.sendDtmf(dtmf: digit)
Danmei Chen 49.1 323 action.fulfill()
324 }
325 {{/code}}
326
Danmei Chen 50.1 327 === Group calls ===
Danmei Chen 49.1 328
329 {{code language="swift"}}
330 func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
331 // add all to conference
Danmei Chen 54.1 332 lc.addAllToConference()
Danmei Chen 49.1 333 action.fulfill()
334 }
335 {{/code}}
336
Danmei Chen 50.1 337 === Transfer a call ===
Danmei Chen 49.1 338
339 Callkit does not support call transfer when Linphone does. If you wan to realise the call transfer with Callkit, you must follow these tips:
340
341 * For callkit, an unique uuid represents a call.
342 * For Linphone, the referred call will use a different callid than the original call.
343
Danmei Chen 55.1 344 Here is the integration of callkit in [[linphone-iphone/ProviderDelegate.swift>>https://gitlab.linphone.org/BC/public/linphone-iphone/blob/master/Classes/ProviderDelegate.swift]].
345
Danmei Chen 61.1 346 === Troubleshooting ===
347
348 ==== Distorted voice when using bluetooth devices ( using SDK 4.4.x) ====
349
350 Error log: **"Too many samples to drop, dropping entire frame."**
351
Danmei Chen 62.1 352 Solution: Add callback of **AVAudioSessionRouteChangeNotification** in your application, and call linphone_core_audio_route_changed(lc) in this callback. Here is the integration of Linphone [[linphone-iphone/LinphoneManager.m>>https://gitlab.linphone.org/BC/public/linphone-iphone/-/blob/release/4.3/Classes/LinphoneManager.m]].
Danmei Chen 61.1 353
354
Simon Morlat 47.1 355 == Technical solutions to advertise instant messages ==
Simon Morlat 44.5 356
Simon Morlat 47.1 357 The table below summarizes the possible options. Remember that using PushKit is no longer possible since iOS 13 / Xcode 11.
358
359 |=Use case|=Push notification type|=Solution
360 |Basic app, not using end to end encryption.|[[Remote Push Notification>>https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns]]|(((
361 The sender identity and the text content must be placed into the push notification sent by the server. The push notification is displayed to the user. When pressed, the app is resumed or launched, and can display the conversation related to the received notification.
362
363 Please note that the app has no way to perform any kind of pre-processing on the notification, such as perform deciphering of the message or fetching additional content (photos for example).
364 )))
365 |Advanced app, with security requirements.|[[Remote Push Notification>>https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns]] used together with a [[Notification Service Extension>>https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension]].|(((
366 Use [[Remote Push Notifications>>https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns]] handled by an app extension of type "[[Notification Service Extension>>https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension]]".
367
368 The app extension is bundled with the main app. It executes in a different process with restricted permissions, for example it cannot access geolocation information.
369
370 The Notification Service Extension will receive the remote push notification silently, in background. It has a constrained period of time to do some processing: connect to the SIP service, get the instant message, decipher it, and at the end update the notification to fill it with sender identity and clear text message. The notification is then displayed normally by iOS.
371
372 When pressed, the main application is launched and can display the conversation that relates to the received message. The app extension and the main app have a shared filesystem and messaging framework, with synchronisation mechanisms.
373
Simon Morlat 51.7 374 This solution is presented with details in the below section "Advertising instant messages with a Notification Service Extension". It is the one implemented in the Linphone application.
Simon Morlat 47.1 375 )))
376
377 === Advertising instant messages with a Notification Service Extension ===
378
Simon Morlat 51.7 379 This section describes a step by step procedure to implement state of art receiving of instant messages in a liblinphone based application, thanks to [[Notification Service Extension>>https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension]].
Simon Morlat 47.1 380
Simon Morlat 51.7 381 ==== Create the UNNotificationServiceExtension App Extension ====
382
383 A **Remote** push notification cannot resume an application to perform tasks in background. However it can do so with an app extension. An app extension is a process that is linked to the app but it doesn't share the same container (not the same process, not the same file system). The first step is then to create a UNNotificationServiceExtension.
384
385 Its goal is to catch the **Remote** push notification and modify the **user notification** before it is displayed. For this we need to connect to the server, get the new message then we can display the user notification.
386
387 In Xcode, in the “capabilities” menu, you must add the **App Group** capability for both the app and the app extension with the same App Group identifier: the //AppGroupId//. This identifier is used to reach the file system that is shared between the main App and the UNNotificationServiceExtension.
388
389 ==== Create a Shared Core in app ====
390
391 Since version 4.4, liblinphone has a new concept of **Shared Linphone Core** to be able to manage a LinphoneCore in both the app and the app extension. The goal of the Shared Cores is to be able to create several Cores at the same time with the same configuration while making sure that only one Shared Core can write to the configuration files and the databases at the same time.
392
393 There are two types of Shared Cores:
394 - The **Main Shared Core** is the Core owned by the application. It can stop the **Executor Shared Core** when it needs to start. It is the one that has the priority.
395 - The **Executor Shared Core** is the Core of the app extension. It can't start if another Shared Core is already running and it can be stopped by the **Main Shared Core **at any time.
396
397 The mutual exclusion between Shared Cores is handled by the liblinphone.
398
399 (% class="box infomessage" %)
400 (((
401 The liblinphone methods for creating the Shared Core exists in C and Swift. A newly written application will prefer to use Swift, while an already existing application written in Objective-C can use C.
402 )))
403
404 The Main Shared Core is to be created as follows:
405
Simon Morlat 51.9 406 1. Use// [[linphone_config_new_for_shared_core>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/Config.html#/s:10linphonesw6ConfigC16newForSharedCore10appGroupId14configFilename11factoryPathACSgSS_S2StFZ]](app_group_id, config_filename, factory_path)// taking your //AppGroupId// and a config filename (i.e. linphonerc). It computes the path in the shared memory for your config file. The factory config path works as for a normal LinphoneCore. It returns a **LinphoneConfig** object suitable to instantiate a Shared Core.
407 1. Use //[[linphone_factory_create_shared_core_with_config>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/Factory.html#/s:10linphonesw7FactoryC26createSharedCoreWithConfig6config13systemContext10appGroupId04mainE0AA0E0CAA0G0C_SvSgSSSbtKF]](factory, config, system_context, app_group_id, main_core)// to create the Shared Core. Set //main_core=TRUE// as we are creating a Main Shared Core for the app.
Simon Morlat 51.7 408
409 Note: linphone_factory_create_shared_core (factory, config_filename, factory_config_path, system_context, app_group_id, main_core) combines the two steps in a single function.
410
Simon Morlat 51.25 411 ==== Stop the Main Shared Core when going to background ====
Simon Morlat 51.7 412
Simon Morlat 51.25 413 The app extension will need to start an **Executor Shared Core** to get the new messages. To allow that, you need to stop the **Main Shared Core** each time the app goes in background. Reciprocally you need to start it again when the app goes to foreground.
Simon Morlat 51.7 414
Simon Morlat 51.25 415 * In //(void)applicationDidEnterBackground: (UIApplication *)application// : you need to call [[//linphone_core_stop//()>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/Core.html#/s:10linphonesw4CoreC4stopyyF]]
416 * In //(void)applicationWillEnterForeground: (UIApplication *)application// : you need to call [[linphone_core_start()>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/Core.html#/s:10linphonesw4CoreC5startyyKF]]
Simon Morlat 51.7 417
Simon Morlat 51.25 418 ==== Migrate all the configuration files to the shared file system (optional) ====
Simon Morlat 51.7 419
Simon Morlat 51.25 420 This section is applicable to apps using liblinphone before Xcode 11 arrived.
Simon Morlat 51.7 421
Simon Morlat 51.25 422 You have created a **Main Shared Core** instead of the usual **Core**. Now all the configuration files need to be moved to the **shared file system **in order to still have the SIP account information, the call logs, and the conversation history, etc.
Simon Morlat 51.7 423
424 For this we use these three functions:
425
426 - const char *linphone_factory_get_config_dir(LinphoneFactory *factory, void *context) that leads to /Library/Application Support/linphone/
427
428 - const char *linphone_factory_get_data_dir(LinphoneFactory *factory, void *context) that leads to /Library/Preferences/linphone/
429
430 - const char *linphone_factory_get_download_dir(LinphoneFactory *factory, void *context) that leads to /Library/Caches/
431
Simon Morlat 51.25 432 These functions return paths to the different iOS application configuration directories, depending on the context argument. If context = NULL, the returned path is in the application file system. If context = AppGroupId, the path leads to the file system shared between the app and the app extension.
Simon Morlat 51.7 433
Simon Morlat 51.25 434 ==== Set up the app extension to get the message ====
Simon Morlat 51.7 435
Simon Morlat 51.25 436 The function didReceive() of the **NotificationService** app extension is called when a **Remote** push notification is received. It has ~~30 seconds to get the message before the user notification is finally displayed by the system.
Simon Morlat 51.7 437
Simon Morlat 51.25 438 Please note that the push notification body must contain two string attributes (in its dictionary):
Simon Morlat 51.7 439
Simon Morlat 51.25 440 * call_id : the call_id of the SIP MESSAGE carrying the IM, when the push notification is related to a message.
441 * chat_room_addr : the SIP address of a chatroom in which the user is being INVITEd, when the push notification is related to an INVITE to join a user to chat room.
Simon Morlat 51.7 442
Simon Morlat 51.25 443 These are strong requirements. Flexisip proxy server of course takes care of adding these attributes since version 2.0.
Simon Morlat 51.7 444
Simon Morlat 51.25 445 An **Executor Shared Linphone Core** is required to retrieve the message. It is created as in step 2 but by setting main_core=FALSE. The **main shared core** running in the main application can stop the **Executor Shared Core** when the user puts the application in foreground.
Simon Morlat 51.7 446
Simon Morlat 51.25 447 These two methods are useful to perform the tasks of the app extension:
Simon Morlat 51.7 448
Simon Morlat 51.25 449 - [[LinphonePushNotificationMessage *linphone_core_get_new_message_from_callid(lc, call_id)>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/Core.html#/s:10linphonesw4CoreC23getNewMessageFromCallid6callIdAA016PushNotificationE0CSgSS_tF]] : to get the message from the //call_id// attribute embedded in the push notification payload.
Simon Morlat 51.7 450
Simon Morlat 51.25 451 - [[LinphoneChatRoom *linphone_core_get_new_chat_room_from_conf_addr(lc , chat_room_addr)>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/Core.html#/s:10linphonesw4CoreC26getNewChatRoomFromConfAddr04chatfI0AA0eF0CSgSS_tF]] : to get the new chat room from the //chat_room_addr// attribute embedded in the push notification. The app extension can use this information to indicate to the user that has been invited into a new chat room.
452
453 (% class="box infomessage" %)
454 (((
455 Don't call linphone_core_start() yourself. The above two functions will start the Shared Core if needed.
456 )))
457
458 Once ever of the two functions has returned, linphone_core_stop() must be called, in order to allow the core to perform additional actions that usually follow the receiving of a message, such as sending a delivery notification (IMDN), and finally release the shared core lock, to let other push notification to be processed by other notification service extension instances.
459
460 (% class="box infomessage" %)
461 (((
462 iOS launches a new instance of the app extension for every push notification received. Proper synchronisation between these multiple instances and the main shared core is performed by liblinphone internally.
463 )))
464
465 These two methods used by the app extension work when the main application is in background or in foreground. This means that the main application does not need to manage incoming messages. **The app extension will take in charge the notification of ALL the incoming messages to the user.**
466
467 If the application existed before XCode 11, it was probably user local notification to show IMs: this code needs to be removed now that the app extension is handling incoming IMs.
468
469 ==== Adding actions in user notifications: UNNotificationContentExtension ====
470
Simon Morlat 51.29 471 Linphone also uses the UNNotificationContentExtension app extension in addition of the UNNotificationServiceExtension, in order to implement the **reply** and **mark as seen** buttons.
Simon Morlat 51.7 472
Simon Morlat 51.29 473 The user notifications displayed by the NotificationService extension need a NotificationContent extension to add a **NotificationCategory **as well as some **actions** attached to this category. The categories and actions declared in the main app won't work when the app is in background.
Simon Morlat 51.7 474
Simon Morlat 51.29 475 If the app is in foreground, the NotificationContent extension won't be able to start a Shared Core so the app will need to handle the actions.
Simon Morlat 51.7 476
Simon Morlat 51.29 477 To summarize, the notification categories and attached actions must be declared in both the NotificationContent extension and the main app, despite the Notification Service Extension is centralizing the incoming push notifications and the display of the IM.
Simon Morlat 51.7 478
Simon Morlat 51.29 479 ==== Update Contact URI parameters ====
Simon Morlat 51.7 480
Quentin Arguillère 68.1 481 **This section is only relevant if you did not enable Core.pushEnabled in your app and wish to implement push notification management yourself. If that is not the case, have a look over [[here>>https://wiki.linphone.org/xwiki/wiki/public/view/Lib/Features/Liblinphone%205.0%20new%20features%20and%20how%20to%20use%20them/#HiOS]] to see how to use the linphone sdk push management.**
482 \\In order to get push notifications, the iOS app must send its push token(s) to the SIP server.
Simon Morlat 51.7 483
484 These push paramers are sent in the Contact URI parameter in the REGISTER.
485
Simon Morlat 51.29 486 We recommend the syntax and principles of [[RFC8599 - Push Notification with the Session Initiation Protocol>>https://tools.ietf.org/html/rfc8599]], on top of which we had unfortunately to plug extension to cover the specificities of iOS since version 13, because the app will actually need two different push notification tokens (one for Callkit, one for Remote Notifications).
Simon Morlat 51.7 487
Simon Morlat 51.29 488 The extensions we made are summarized by this ABNF grammar:
489
Simon Morlat 51.7 490 * "pn-prid=“ token [ “:” service ] *( “&” token “:” service )
491 * "pn-param=" TeamId ”.” BundleId ”.” service *( “&” service )
492 * "pn-provider=apns"
493 * service = "voip" / "remote"
494 * token = <voip push token or remote push token>
495
496 Example with voip and remote push token:
497
498 * pn-prid=00fc13adff78512:voip&c11292f7b74733d:remote
499 * pn-param=DEF123GHIJ.org.linphone.phone.voip&remote
500 * pn-provider=apns
501
502 Example only with remote push token:
503
504 * pn-prid=c11292f7b74733d:remote
505 * pn-param=DEF123GHIJ.org.linphone.phone.remote
506 * pn-provider=apns
507
Simon Morlat 51.29 508 The "voip" service stands for PushKit kind of notifications, while the "remote" service is for the basic "Remote Notification" system.
Simon Morlat 51.7 509
Simon Morlat 51.31 510 These parameters must be added at application to the ProxyConfig object using [[//linphone_proxy_config_set_contact_uri_parameters//()>>https://www.linphone.org/snapshots/docs/liblinphone/swift/Classes/ProxyConfig.html#/s:10linphonesw11ProxyConfigC20contactUriParametersSSvp]].
Simon Morlat 51.7 511
Simon Morlat 51.31 512 ==== Bugs ====
Simon Morlat 51.7 513
514 It appears that in iOS < 12, if the UNNotificationServiceExtension takes more than 1 second to start, it is killed by the system. Once it has successfully started once, the process is kept in cache and works every time. It is removed from the cache after ~~30 minutes without any push notification.
515
516 When the app extension fails to start, the push notification can't be caught to retrieve the message. We used the "loc-key" argument of the push notification JSON payload to put a key. This key is an entry in our translation files. So, when the app extension fails to start, we will display a localized message similar to "You have received a new message".
517
518 This limitation is not documented by Apple and we haven't been able to reproduce it in iOS versions >= 12.
519
Simon Morlat 51.31 520 ==== Beyond the scene ====
521
Simon Morlat 52.2 522 [[This page>>doc:.Shared cores.WebHome]] describes the internal mechanisms used by liblinphone for Shared Core synchronisation.
Simon Morlat 51.31 523
Admin Admin 13.1 524 = Handling liblinphone log =
525
526 In order to see liblinphone logs in your IOS app (for example in your Xcode console) follow these steps :
527
528 Put in your code at the launching of the app :
529
530 {{code language="Objective-C"}}
Admin Admin 22.2 531 linphone_core_set_log_handler(your_log_handler);
Admin Admin 13.1 532 {{/code}}
533
Elisa Nectoux 36.1 534 This will make the liblinphone logs to call your_log_handler and so be processed as a log from your app.
Admin Admin 13.1 535
Admin Admin 14.1 536 You can set the liblinphone log level by using the functions documented [[here>>http://linphone.org/docs/liblinphone/group__initializing.html#ga685b79d92af0184dbeac46df14bf4705]].
Admin Admin 13.1 537
Admin Admin 14.1 538 == IOS log handler for liblinphone ==
Admin Admin 13.1 539
Elisa Nectoux 36.1 540 Once you have set your log handler, you need to process liblinphone log in order to incorporate them into your app logs.
Admin Admin 14.1 541
Elisa Nectoux 36.1 542 Here is a short example of how to manage liblinphone log into an IOS app :
Admin Admin 14.1 543
544 {{code language="Objective-C"}}
Admin Admin 22.2 545 void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args) {
546 NSString *format = [[NSString alloc] initWithUTF8String:fmt];
Admin Admin 15.1 547 NSString *formatedString = [[NSString alloc] initWithFormat:format arguments:args];
548 NSString *lvl;
Admin Admin 14.1 549
Admin Admin 15.1 550 if (!domain)
551 domain = "lib";
552 // since \r are interpreted like \n, avoid double new lines when logging network packets (belle-sip)
553 // output format is like: I/ios/some logs. We truncate domain to **exactly** DOMAIN_SIZE characters to have
554 // fixed-length aligned logs
555 switch (lev) {
556 case ORTP_FATAL:
557 lvl = @"Fatal";
558 break;
559 case ORTP_ERROR:
560 lvl = @"Error";
561 break;
562 case ORTP_WARNING:
563 lvl = @"Warning";
564 break;
565 case ORTP_MESSAGE:
566 lvl = @"Message";
567 break;
568 case ORTP_DEBUG:
569 lvl = @"Debug";
570 break;
571 case ORTP_TRACE:
572 lvl = @"Trace";
573 break;
574 case ORTP_LOGLEV_END:
575 return;
576 }
577 if ([formatedString containsString:@"\n"]) {
578 NSArray *myWords = [[formatedString stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"]
579 componentsSeparatedByString:@"\n"];
580 for (int i = 0; i < myWords.count; i++) {
581 NSString *tab = i > 0 ? @"\t" : @"";
582 if (((NSString *)myWords[i]).length > 0) {
583 NSLog(@"[%@] %@%@", lvl, tab, (NSString *)myWords[i]);
584 }
585 }
586 } else {
587 NSLog(@"[%@] %@", lvl, [formatedString stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"]);
588 }
Admin Admin 22.2 589 }
Admin Admin 13.1 590 {{/code}}
591
Admin Admin 14.1 592
Danmei Chen 50.1 593
Admin Admin 13.1 594 )))
SandrineAvakian 12.1 595
596 (% class="col-xs-12 col-sm-4" %)
597 (((
598 {{box title="**Contents**"}}
Danmei Chen 56.1 599 {{toc/}}
SandrineAvakian 12.1 600 {{/box}}
601 )))
602 )))