diff --git a/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitProviderDelegate.swift b/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitProviderDelegate.swift index 97fffc8..a22a7f4 100644 --- a/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitProviderDelegate.swift +++ b/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitProviderDelegate.swift @@ -64,30 +64,38 @@ extension CallKitProviderDelegate: CXProviderDelegate { func provider(_ provider: CXProvider, perform action: CXEndCallAction) { NSLog("Callkit CXEndCallAction -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ") - do { - if (tutorialContext.mCall?.state != .End && tutorialContext.mCall?.state != .Released) { - try tutorialContext.mCall?.terminate() - } - } catch { NSLog(error.localizedDescription) } + tutorialContext.$mCore.receive(on: coreQueue).sink { core in + do { + if (self.tutorialContext.mCall?.state != .End && self.tutorialContext.mCall?.state != .Released) { + try self.tutorialContext.mCall?.terminate() + } + LinphoneAsyncHelper.postOnMainQueue { + self.tutorialContext.isCallRunning = false + self.tutorialContext.isCallIncoming = false + } + } catch { NSLog(error.localizedDescription) } + }.store(in: &cancellables) - tutorialContext.isCallRunning = false - tutorialContext.isCallIncoming = false action.fulfill() } func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { NSLog("Callkit CXAnswerCallAction -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ") - do { - // 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 - // AVAudioSession outside of this delegate action while it is running in background, - // which is usually the case in an incoming call scenario. - tutorialContext.mCore.configureAudioSession(); - try tutorialContext.mCall?.accept() - tutorialContext.isCallRunning = true - } catch { - print(error) - } + tutorialContext.$mCore.receive(on: coreQueue).sink { core in + do { + // 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 + // AVAudioSession outside of this delegate action while it is running in background, + // which is usually the case in an incoming call scenario. + core?.configureAudioSession(); + try self.tutorialContext.mCall?.accept() + LinphoneAsyncHelper.postOnMainQueue { + self.tutorialContext.isCallRunning = true + } + } catch { + print(error) + } + }.store(in: &cancellables) action.fulfill() } @@ -109,15 +117,15 @@ extension CallKitProviderDelegate: CXProviderDelegate { NSLog("Callkit didActivateaudiosession -- Is main thread ? \(Thread.isMainThread ? "yes" : "no") ") // The linphone Core must be notified that CallKit has activated the AVAudioSession // in order to start streaming audio. - tutorialContext.linphoneAsyncHelper.postOnCoreQueue { - self.tutorialContext.mCore.activateAudioSession(actived: true) - } + tutorialContext.$mCore.receive(on: coreQueue).sink { core in + core?.activateAudioSession(actived: true) + }.store(in: &cancellables) } func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { // The linphone Core must be notified that CallKit has deactivated the AVAudioSession. - tutorialContext.linphoneAsyncHelper.postOnCoreQueue { - self.tutorialContext.mCore.activateAudioSession(actived: false) - } + tutorialContext.$mCore.receive(on: coreQueue).sink { core in + core?.activateAudioSession(actived: false) + }.store(in: &cancellables) } } diff --git a/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitTutorial.swift b/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitTutorial.swift index a0a144c..0a40ac2 100644 --- a/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitTutorial.swift +++ b/ios/swift/4-CallKitTutorial/CallKitTutorial/CallKitTutorial.swift @@ -13,9 +13,8 @@ import Combine class CallKitExampleContext : ObservableObject { - var mCore: Core! - var mAccount: Account? - var mIterateTimer : Timer! + @Published var mCore: Core? = nil + @Published var factory = Factory.Instance @Published var coreVersion: String = Core.getVersion @Published var username : String = "quentindev" @@ -30,6 +29,10 @@ class CallKitExampleContext : ObservableObject @Published var isSpeakerEnabled : Bool = false @Published var isMicrophoneEnabled : Bool = false + var cancellables = Set() + var icancellables = Set() + + /* Async */ let linphoneAsyncHelper = LinphoneAsyncHelper() @@ -101,51 +104,41 @@ class CallKitExampleContext : ObservableObject init() { LoggingService.Instance.logLevel = LogLevel.Debug - - // IMPORTANT : In this tutorial, we require the use of a core configuration file. - // 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 + factory = Factory.Instance + $factory.receive(on: coreQueue).sink { factory in let configDir = factory.getConfigDir(context: nil) - let corePublisher = self.linphoneAsyncHelper.createLinphoneObjectWithPublisher(createAction: { - try factory.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil) - }) - corePublisher - .postOnCoreQueue ( - 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.addCallStateChangedCallBack(core: core) - try? core.start() - }) - .postOnMainQueue { core in - self.mIterateTimer = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { [weak self] timer in - self?.linphoneAsyncHelper.postOnCoreQueue { - core.iterate() - } + do { + let core = try factory.createCore(configPath: "\(configDir)/MyConfig", factoryConfigPath: "", systemContext: nil) + core.callkitEnabled = true + core.pushNotificationEnabled = true + core.autoIterateEnabled = false + self.addRegistrationStateCallBack(core: core) + self.addCallStateChangedCallBack(core: core) + try? core.start() + LinphoneAsyncHelper.postOnMainQueue { + self.mCore = core // @Publisher assignment can be done on main queue only } + } 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() { - - linphoneAsyncHelper.postOnCoreQueue { + + $factory.receive(on: coreQueue).sink { factory in do { var transport : TransportType if (self.transportType == "TLS") { transport = TransportType.Tls } @@ -153,44 +146,42 @@ class CallKitExampleContext : ObservableObject else { transport = TransportType.Udp } 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)) - try! accountParams.setIdentityaddress(newValue: identity) + try! accountParams?.setIdentityaddress(newValue: identity) let address = try Factory.Instance.createAddress(addr: String("sip:" + self.domain)) try address.setTransport(newValue: transport) - try accountParams.setServeraddress(newValue: address) - accountParams.registerEnabled = true + try accountParams?.setServeraddress(newValue: address) + accountParams?.registerEnabled = true // 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 - accountParams.pushNotificationConfig?.provider = "apns.dev" - self.mAccount = try self.mCore.createAccount(params: accountParams) - self.mCore.addAuthInfo(info: authInfo) - try self.mCore.addAccount(account: self.mAccount!) - self.mCore.defaultAccount = self.mAccount + accountParams?.pushNotificationConfig?.provider = "apns.dev" + let account = try self.mCore?.createAccount(params: accountParams!) + self.mCore?.addAuthInfo(info: authInfo) + try self.mCore?.addAccount(account: account!) + self.mCore?.defaultAccount = account } catch { NSLog(error.localizedDescription) } - } + }.store(in: &cancellables) + } - func unregister() - { - linphoneAsyncHelper.postOnCoreQueue { - if let account = self.mCore.defaultAccount { - let params = account.params - let clonedParams = params?.clone() - clonedParams?.registerEnabled = false - account.params = clonedParams - } - } + func unregister() { + $mCore.receive(on: coreQueue).sink { core in + guard let account = core?.defaultAccount else {return} + let params = account.params + let clonedParams = params?.clone() + clonedParams?.registerEnabled = false + account.params = clonedParams + }.store(in: &cancellables) } func delete() { - linphoneAsyncHelper.postOnCoreQueue { - if let account = self.mCore.defaultAccount { - self.mCore.removeAccount(account: account) - self.mCore.clearAccounts() - self.mCore.clearAllAuthInfo() - } - } + $mCore.receive(on: coreQueue).sink { core in + guard let account = core?.defaultAccount else {return} + self.mCore?.removeAccount(account: account) + self.mCore?.clearAccounts() + self.mCore?.clearAllAuthInfo() + }.store(in: &cancellables) } } diff --git a/ios/swift/4-CallKitTutorial/CallKitTutorial/LinphoneAsyncHelper.swift b/ios/swift/4-CallKitTutorial/CallKitTutorial/LinphoneAsyncHelper.swift index f957f21..92f7a3d 100644 --- a/ios/swift/4-CallKitTutorial/CallKitTutorial/LinphoneAsyncHelper.swift +++ b/ios/swift/4-CallKitTutorial/CallKitTutorial/LinphoneAsyncHelper.swift @@ -58,18 +58,20 @@ public class LinphoneObjectsPublisher : Publisher { public class LinphoneAsyncHelper { - func postOnCoreQueue(lambda : @escaping ()->()) { + + static func postOnCoreQueue(lambda : @escaping ()->()) { coreQueue.async { lambda() } } - func postOnMainQueue(lambda : @escaping()->()) { + static func postOnMainQueue(lambda : @escaping()->()) { DispatchQueue.main.async { lambda() } } + /* // 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 func createLinphoneObjectWithPublisher(createAction:@escaping()throws -> LinphoneObject ) -> LinphoneObjectsPublisher { @@ -83,6 +85,7 @@ public class LinphoneAsyncHelper { } return publisher } + */ } @@ -117,5 +120,83 @@ extension Core { // ... } +/* +extension Factory { + static var linphoneObjectsPublishers = [String:LinphoneObjectsPublisher]() + var publisher: LinphoneObjectsPublisher { + get { + let ref = String(format: "%p", unsafeBitCast(self, to: Int.self)) + if (Factory.linphoneObjectsPublishers[ref] == nil) { + let publisher = LinphoneObjectsPublisher() + 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]() + private func refFromSelf() -> String { + return String(format: "%p", unsafeBitCast(self, to: Int.self)) + } + var publisher: LinphoneObjectsPublisher { + get { + let ref = refFromSelf() + if (LinphoneObject.linphoneObjectsPublishers[ref] == nil) { + let publisher = LinphoneObjectsPublisher() + 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 +*/ +