Suggestions from CD for Async Call backs and Core object using @Published

feature/core_thread_prototypes_suggestions_cd_2
Christophe Deschamps 1 year ago
parent 8ee3e46dbc
commit 3f0264edef

@ -64,30 +64,38 @@ extension CallKitProviderDelegate: CXProviderDelegate {
func provider(_ provider: CXProvider, perform action: CXEndCallAction) { func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
NSLog("Callkit CXEndCallAction -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ") NSLog("Callkit CXEndCallAction -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ")
tutorialContext.$mCore.receive(on: coreQueue).sink { core in
do { do {
if (tutorialContext.mCall?.state != .End && tutorialContext.mCall?.state != .Released) { if (self.tutorialContext.mCall?.state != .End && self.tutorialContext.mCall?.state != .Released) {
try tutorialContext.mCall?.terminate() try self.tutorialContext.mCall?.terminate()
}
LinphoneAsyncHelper.postOnMainQueue {
self.tutorialContext.isCallRunning = false
self.tutorialContext.isCallIncoming = false
} }
} catch { NSLog(error.localizedDescription) } } catch { NSLog(error.localizedDescription) }
}.store(in: &cancellables)
tutorialContext.isCallRunning = false
tutorialContext.isCallIncoming = false
action.fulfill() action.fulfill()
} }
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
NSLog("Callkit CXAnswerCallAction -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ") NSLog("Callkit CXAnswerCallAction -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ")
tutorialContext.$mCore.receive(on: coreQueue).sink { core in
do { do {
// The audio stream is going to start shortly: the AVAudioSession must be configured now. // The audio stream is going to start shortly: the AVAudioSession must be configured now.
// It is worth to note that an application does not have permission to configure the // It is worth to note that an application does not have permission to configure the
// AVAudioSession outside of this delegate action while it is running in background, // AVAudioSession outside of this delegate action while it is running in background,
// which is usually the case in an incoming call scenario. // which is usually the case in an incoming call scenario.
tutorialContext.mCore.configureAudioSession(); core?.configureAudioSession();
try tutorialContext.mCall?.accept() try self.tutorialContext.mCall?.accept()
tutorialContext.isCallRunning = true LinphoneAsyncHelper.postOnMainQueue {
self.tutorialContext.isCallRunning = true
}
} catch { } catch {
print(error) print(error)
} }
}.store(in: &cancellables)
action.fulfill() action.fulfill()
} }
@ -109,15 +117,15 @@ extension CallKitProviderDelegate: CXProviderDelegate {
NSLog("Callkit didActivateaudiosession -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ") NSLog("Callkit didActivateaudiosession -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ")
// The linphone Core must be notified that CallKit has activated the AVAudioSession // The linphone Core must be notified that CallKit has activated the AVAudioSession
// in order to start streaming audio. // in order to start streaming audio.
tutorialContext.linphoneAsyncHelper.postOnCoreQueue { tutorialContext.$mCore.receive(on: coreQueue).sink { core in
self.tutorialContext.mCore.activateAudioSession(actived: true) core?.activateAudioSession(actived: true)
} }.store(in: &cancellables)
} }
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
// The linphone Core must be notified that CallKit has deactivated the AVAudioSession. // The linphone Core must be notified that CallKit has deactivated the AVAudioSession.
tutorialContext.linphoneAsyncHelper.postOnCoreQueue { tutorialContext.$mCore.receive(on: coreQueue).sink { core in
self.tutorialContext.mCore.activateAudioSession(actived: false) core?.activateAudioSession(actived: false)
} }.store(in: &cancellables)
} }
} }

@ -13,9 +13,8 @@ import Combine
class CallKitExampleContext : ObservableObject class CallKitExampleContext : ObservableObject
{ {
var mCore: Core! @Published var mCore: Core? = nil
var mAccount: Account? @Published var factory = Factory.Instance
var mIterateTimer : Timer!
@Published var coreVersion: String = Core.getVersion @Published var coreVersion: String = Core.getVersion
@Published var username : String = "quentindev" @Published var username : String = "quentindev"
@ -30,6 +29,10 @@ class CallKitExampleContext : ObservableObject
@Published var isSpeakerEnabled : Bool = false @Published var isSpeakerEnabled : Bool = false
@Published var isMicrophoneEnabled : Bool = false @Published var isMicrophoneEnabled : Bool = false
var cancellables = Set<AnyCancellable>()
var icancellables = Set<AnyCancellable>()
/* Async */ /* Async */
let linphoneAsyncHelper = LinphoneAsyncHelper() let linphoneAsyncHelper = LinphoneAsyncHelper()
@ -101,51 +104,41 @@ class CallKitExampleContext : ObservableObject
init() init()
{ {
LoggingService.Instance.logLevel = LogLevel.Debug LoggingService.Instance.logLevel = LogLevel.Debug
factory = Factory.Instance
// IMPORTANT : In this tutorial, we require the use of a core configuration file. $factory.receive(on: coreQueue).sink { factory in
// This way, once the registration is done, and until it is cleared, it will return to the LoggedIn state on launch.
// This allows us to have a functional call when the app was closed and is started by a VOIP push notification (incoming call
// We also need to enable "Push Notitifications" and "Background Mode - Voice Over IP"
linphoneAsyncHelper.postOnCoreQueue {
let factory = Factory.Instance
let configDir = factory.getConfigDir(context: nil) let configDir = factory.getConfigDir(context: nil)
let corePublisher = self.linphoneAsyncHelper.createLinphoneObjectWithPublisher(createAction: { do {
try factory.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil) let core = try factory.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil)
}) core.callkitEnabled = true
corePublisher core.pushNotificationEnabled = true
.postOnCoreQueue ( core.autoIterateEnabled = false
onError: { error in
NSLog("failed creating core \(error)")
},
receiveValue: { core in
self.mCore = core
// enabling push notifications management in the core
self.mCore.callkitEnabled = true
self.mCore.pushNotificationEnabled = true
self.mCore.autoIterateEnabled = false
self.addRegistrationStateCallBack(core: core) self.addRegistrationStateCallBack(core: core)
self.addCallStateChangedCallBack(core: core) self.addCallStateChangedCallBack(core: core)
try? core.start() try? core.start()
}) LinphoneAsyncHelper.postOnMainQueue {
.postOnMainQueue { core in self.mCore = core // @Publisher assignment can be done on main queue only
self.mIterateTimer = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { [weak self] timer in
self?.linphoneAsyncHelper.postOnCoreQueue {
core.iterate()
}
} }
} catch {
NSLog("failed creating core \(error)")
} }
}.store(in: &cancellables)
Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { _ in
self.icancellables.removeAll()
self.$mCore.receive(on: coreQueue).sink { core in
core?.iterate()
}.store(in: &self.icancellables)
} }
mProviderDelegate = CallKitProviderDelegate(context: self)
mProviderDelegate = CallKitProviderDelegate(context: self)
} }
func login() { func login() {
linphoneAsyncHelper.postOnCoreQueue { $factory.receive(on: coreQueue).sink { factory in
do { do {
var transport : TransportType var transport : TransportType
if (self.transportType == "TLS") { transport = TransportType.Tls } if (self.transportType == "TLS") { transport = TransportType.Tls }
@ -153,44 +146,42 @@ class CallKitExampleContext : ObservableObject
else { transport = TransportType.Udp } else { transport = TransportType.Udp }
let authInfo = try Factory.Instance.createAuthInfo(username: self.username, userid: "", passwd: self.passwd, ha1: "", realm: "", domain: self.domain) let authInfo = try Factory.Instance.createAuthInfo(username: self.username, userid: "", passwd: self.passwd, ha1: "", realm: "", domain: self.domain)
let accountParams = try self.mCore.createAccountParams() let accountParams = try self.mCore?.createAccountParams()
let identity = try Factory.Instance.createAddress(addr: String("sip:" + self.username + "@" + self.domain)) let identity = try Factory.Instance.createAddress(addr: String("sip:" + self.username + "@" + self.domain))
try! accountParams.setIdentityaddress(newValue: identity) try! accountParams?.setIdentityaddress(newValue: identity)
let address = try Factory.Instance.createAddress(addr: String("sip:" + self.domain)) let address = try Factory.Instance.createAddress(addr: String("sip:" + self.domain))
try address.setTransport(newValue: transport) try address.setTransport(newValue: transport)
try accountParams.setServeraddress(newValue: address) try accountParams?.setServeraddress(newValue: address)
accountParams.registerEnabled = true accountParams?.registerEnabled = true
// Enable push notifications on this account // Enable push notifications on this account
accountParams.pushNotificationAllowed = true accountParams?.pushNotificationAllowed = true
// We're in a sandbox application, so we must set the provider to "apns.dev" since it will be "apns" by default, which is used only for production apps // We're in a sandbox application, so we must set the provider to "apns.dev" since it will be "apns" by default, which is used only for production apps
accountParams.pushNotificationConfig?.provider = "apns.dev" accountParams?.pushNotificationConfig?.provider = "apns.dev"
self.mAccount = try self.mCore.createAccount(params: accountParams) let account = try self.mCore?.createAccount(params: accountParams!)
self.mCore.addAuthInfo(info: authInfo) self.mCore?.addAuthInfo(info: authInfo)
try self.mCore.addAccount(account: self.mAccount!) try self.mCore?.addAccount(account: account!)
self.mCore.defaultAccount = self.mAccount self.mCore?.defaultAccount = account
} catch { NSLog(error.localizedDescription) } } catch { NSLog(error.localizedDescription) }
} }.store(in: &cancellables)
} }
func unregister() func unregister() {
{ $mCore.receive(on: coreQueue).sink { core in
linphoneAsyncHelper.postOnCoreQueue { guard let account = core?.defaultAccount else {return}
if let account = self.mCore.defaultAccount {
let params = account.params let params = account.params
let clonedParams = params?.clone() let clonedParams = params?.clone()
clonedParams?.registerEnabled = false clonedParams?.registerEnabled = false
account.params = clonedParams account.params = clonedParams
} }.store(in: &cancellables)
}
} }
func delete() { func delete() {
linphoneAsyncHelper.postOnCoreQueue { $mCore.receive(on: coreQueue).sink { core in
if let account = self.mCore.defaultAccount { guard let account = core?.defaultAccount else {return}
self.mCore.removeAccount(account: account) self.mCore?.removeAccount(account: account)
self.mCore.clearAccounts() self.mCore?.clearAccounts()
self.mCore.clearAllAuthInfo() self.mCore?.clearAllAuthInfo()
} }.store(in: &cancellables)
}
} }
} }

@ -58,18 +58,20 @@ public class LinphoneObjectsPublisher <T> : Publisher {
public class LinphoneAsyncHelper { public class LinphoneAsyncHelper {
func postOnCoreQueue(lambda : @escaping ()->()) {
static func postOnCoreQueue(lambda : @escaping ()->()) {
coreQueue.async { coreQueue.async {
lambda() lambda()
} }
} }
func postOnMainQueue(lambda : @escaping()->()) { static func postOnMainQueue(lambda : @escaping()->()) {
DispatchQueue.main.async { DispatchQueue.main.async {
lambda() lambda()
} }
} }
/*
// Creates a publisher from the object created by the action passed as parameter // Creates a publisher from the object created by the action passed as parameter
// For example if passed a create core call this function will create the LinphoneObject Core on core queue, and created object will be published through the built publisher // For example if passed a create core call this function will create the LinphoneObject Core on core queue, and created object will be published through the built publisher
func createLinphoneObjectWithPublisher<LinphoneObject>(createAction:@escaping()throws -> LinphoneObject ) -> LinphoneObjectsPublisher<LinphoneObject> { func createLinphoneObjectWithPublisher<LinphoneObject>(createAction:@escaping()throws -> LinphoneObject ) -> LinphoneObjectsPublisher<LinphoneObject> {
@ -83,6 +85,7 @@ public class LinphoneAsyncHelper {
} }
return publisher return publisher
} }
*/
} }
@ -117,5 +120,83 @@ extension Core {
// ... // ...
} }
/*
extension Factory {
static var linphoneObjectsPublishers = [String:LinphoneObjectsPublisher<Factory>]()
var publisher: LinphoneObjectsPublisher<Factory> {
get {
let ref = String(format: "%p", unsafeBitCast(self, to: Int.self))
if (Factory.linphoneObjectsPublishers[ref] == nil) {
let publisher = LinphoneObjectsPublisher<Factory>()
Factory.linphoneObjectsPublishers[ref] = publisher
}
coreQueue.async {
Factory.linphoneObjectsPublishers[ref]!.passThroughSubject.send(self)
}
return Factory.linphoneObjectsPublishers[ref]!
}
set(newValue) {
let ref = String(format: "%p", unsafeBitCast(self, to: Int.self))
Factory.linphoneObjectsPublishers[ref] = newValue
}
}
}
extension LinphoneObject {
static var linphoneObjectsPublishers = [String:LinphoneObjectsPublisher<LinphoneObject>]()
private func refFromSelf() -> String {
return String(format: "%p", unsafeBitCast(self, to: Int.self))
}
var publisher: LinphoneObjectsPublisher<LinphoneObject> {
get {
let ref = refFromSelf()
if (LinphoneObject.linphoneObjectsPublishers[ref] == nil) {
let publisher = LinphoneObjectsPublisher<LinphoneObject>()
LinphoneObject.linphoneObjectsPublishers[ref] = publisher
}
coreQueue.async {
LinphoneObject.linphoneObjectsPublishers[ref]!.passThroughSubject.send(self)
}
return LinphoneObject.linphoneObjectsPublishers[ref]!
}
set(newValue) {
let ref = refFromSelf()
LinphoneObject.linphoneObjectsPublishers[ref] = newValue
}
}
}
// Could not find a way to put the below inside the generic LinphoneObject extension
extension Factory {
func postOnCoreQueue(lambda:@escaping ((Factory) -> Void)) {
publisher.postOnCoreQueue { result in
lambda(result as! Factory)
}
}
func postOnMainQueue(lambda:@escaping ((Factory) -> Void)) {
publisher.postOnMainQueue { result in
lambda(result as! Factory)
}
}
}
extension Core {
func postOnCoreQueue(lambda:@escaping ((Core) -> Void)) {
publisher.postOnCoreQueue { result in
lambda(result as! Core)
}
}
func postOnMainQueue(lambda:@escaping ((Core) -> Void)) {
publisher.postOnMainQueue { result in
lambda(result as! Core)
}
}
}
// .. add extensions to other needed objects, or have this generated in the wrapper
*/

Loading…
Cancel
Save