diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2.xcodeproj/project.pbxproj b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2.xcodeproj/project.pbxproj index 9517df66fa..8a12e2d243 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2.xcodeproj/project.pbxproj +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 3AEC29312995BF1400F59EA0 /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC29302995BF1400F59EA0 /* VideoToolbox.framework */; }; + 3AEC29332995BF4C00F59EA0 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AEC29322995BF4C00F59EA0 /* CoreMedia.framework */; }; C337417325BADEEE007785D7 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = C337417225BADEEE007785D7 /* Model.swift */; }; C351398125A5F70D00A090AA /* libg7221codec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C3B8521F25A5EC380037A8AB /* libg7221codec.a */; }; C351398225A5F70D00A090AA /* libgsmcodec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C3B8521A25A5EC380037A8AB /* libgsmcodec.a */; }; @@ -37,7 +39,6 @@ C3B8520325A5EA6C0037A8AB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3B8520225A5EA6C0037A8AB /* ViewController.swift */; }; C3B8520625A5EA6C0037A8AB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C3B8520425A5EA6C0037A8AB /* Main.storyboard */; }; C3B8520825A5EA6D0037A8AB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C3B8520725A5EA6D0037A8AB /* Assets.xcassets */; }; - C3B8520B25A5EA6D0037A8AB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C3B8520925A5EA6D0037A8AB /* LaunchScreen.storyboard */; }; C3B8524325A5EC940037A8AB /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3B8524125A5EC940037A8AB /* AVFoundation.framework */; }; C3B8524425A5EC940037A8AB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3B8524225A5EC940037A8AB /* Foundation.framework */; }; C3B8524725A5ED6D0037A8AB /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3B8524625A5ED6D0037A8AB /* AudioToolbox.framework */; }; @@ -45,6 +46,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 3AEC29302995BF1400F59EA0 /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; }; + 3AEC29322995BF4C00F59EA0 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; C337417225BADEEE007785D7 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; C374860725B84FA100FBE972 /* OutgoingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingViewController.swift; sourceTree = ""; }; C399A57025AF2721000F742A /* IncomingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingViewController.swift; sourceTree = ""; }; @@ -58,7 +61,6 @@ C3B8520225A5EA6C0037A8AB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; C3B8520525A5EA6C0037A8AB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; C3B8520725A5EA6D0037A8AB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - C3B8520A25A5EA6D0037A8AB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; C3B8520C25A5EA6D0037A8AB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C3B8521325A5EBF00037A8AB /* ios-swift-pjsua2-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ios-swift-pjsua2-Bridging-Header.h"; sourceTree = ""; }; C3B8521825A5EC380037A8AB /* libpjmedia-videodev.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libpjmedia-videodev.a"; path = "../PJSip-Build/pjproject/pjmedia/lib/libpjmedia-videodev.a"; sourceTree = ""; }; @@ -100,11 +102,13 @@ C351398625A5F70D00A090AA /* libpjmedia-audiodev.a in Frameworks */, C351398725A5F70D00A090AA /* libpjmedia-codec.a in Frameworks */, C351398825A5F70D00A090AA /* libpjmedia-videodev.a in Frameworks */, + 3AEC29312995BF1400F59EA0 /* VideoToolbox.framework in Frameworks */, C351398925A5F70D00A090AA /* libpjmedia.a in Frameworks */, C351398A25A5F70D00A090AA /* libpjnath.a in Frameworks */, C351398B25A5F70D00A090AA /* libpjsip-simple.a in Frameworks */, C351398C25A5F70D00A090AA /* libpjsip-ua.a in Frameworks */, C351398D25A5F70D00A090AA /* libpjsip.a in Frameworks */, + 3AEC29332995BF4C00F59EA0 /* CoreMedia.framework in Frameworks */, C351398E25A5F70D00A090AA /* libpjsua.a in Frameworks */, C351398F25A5F70D00A090AA /* libpjsua2.a in Frameworks */, C351399025A5F70D00A090AA /* libresample.a in Frameworks */, @@ -144,7 +148,6 @@ isa = PBXGroup; children = ( C3B8520425A5EA6C0037A8AB /* Main.storyboard */, - C3B8520925A5EA6D0037A8AB /* LaunchScreen.storyboard */, ); path = View; sourceTree = ""; @@ -185,6 +188,8 @@ C3B8521725A5EC380037A8AB /* Frameworks */ = { isa = PBXGroup; children = ( + 3AEC29322995BF4C00F59EA0 /* CoreMedia.framework */, + 3AEC29302995BF1400F59EA0 /* VideoToolbox.framework */, C3B8524625A5ED6D0037A8AB /* AudioToolbox.framework */, C3B8524125A5EC940037A8AB /* AVFoundation.framework */, C3B8524225A5EC940037A8AB /* Foundation.framework */, @@ -281,7 +286,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C3B8520B25A5EA6D0037A8AB /* LaunchScreen.storyboard in Resources */, C3B8520825A5EA6D0037A8AB /* Assets.xcassets in Resources */, C3B8520625A5EA6C0037A8AB /* Main.storyboard in Resources */, ); @@ -317,14 +321,6 @@ name = Main.storyboard; sourceTree = ""; }; - C3B8520925A5EA6D0037A8AB /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - C3B8520A25A5EA6D0037A8AB /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2.xcodeproj/xcshareddata/xcschemes/ios-swift-pjsua2.xcscheme b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2.xcodeproj/xcshareddata/xcschemes/ios-swift-pjsua2.xcscheme index 69418db9a9..a848d42b6d 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2.xcodeproj/xcshareddata/xcschemes/ios-swift-pjsua2.xcscheme +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2.xcodeproj/xcshareddata/xcschemes/ios-swift-pjsua2.xcscheme @@ -39,6 +39,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/AppDelegate.swift b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/AppDelegate.swift index e0c7294f62..c78669227d 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/AppDelegate.swift +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/AppDelegate.swift @@ -22,10 +22,20 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + + //Create Lib + CPPWrapper().createLibWrapper() + + //Listen incoming call via function pointer + CPPWrapper().incoming_call_wrapper(incoming_call_swift) + + //Listen incoming call via function pointer + CPPWrapper().acc_listener_wrapper(acc_listener_swift) + + CPPWrapper().update_video_wrapper(update_video_swift) + return true } diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/ActiveViewController.swift b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/ActiveViewController.swift index ffcc9851e8..ebbca76811 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/ActiveViewController.swift +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/ActiveViewController.swift @@ -19,7 +19,6 @@ import UIKit - class ActiveViewController: UIViewController { var activeCallId : String = "" @@ -27,6 +26,8 @@ class ActiveViewController: UIViewController { var holdFlag : Bool = false @IBOutlet weak var activeCallTitle: UILabel! + + @IBOutlet weak var videoView: UIView! override func viewDidLoad() { super.viewDidLoad() @@ -34,8 +35,14 @@ class ActiveViewController: UIViewController { } override func viewDidDisappear(_ animated: Bool) { - CPPWrapper().hangupCall(); - call_status_listener_swift(call_answer_code: 1); + } + + func updateVideo(vid_win: UIView!) { + videoView.addSubview(vid_win); + vid_win.center = videoView.center; + vid_win.frame = videoView.bounds; + vid_win.contentMode = .scaleAspectFit; + } @IBAction func hangupClick(_ sender: UIButton) { @@ -57,6 +64,5 @@ class ActiveViewController: UIViewController { holdFlag = !holdFlag } - } diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/IncomingViewController.swift b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/IncomingViewController.swift index ea6e9f47b5..990bf34574 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/IncomingViewController.swift +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/IncomingViewController.swift @@ -28,7 +28,7 @@ class IncomingViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - title = "Gelen Arama" + title = "Incoming Call" callTitle.text = incomingCallId CPPWrapper().call_listener_wrapper(call_status_listener_swift) diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/ViewController.swift b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/ViewController.swift index 62a725fb6d..11382796ef 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/ViewController.swift +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Controller/ViewController.swift @@ -19,7 +19,13 @@ import UIKit +var vc_inst: ViewController! = nil; +func acc_listener_swift(status: Bool) { + DispatchQueue.main.async () { + vc_inst.updateAccStatus(status: status); + } +} class ViewController: UIViewController { @@ -31,20 +37,38 @@ class ViewController: UIViewController { @IBOutlet weak var sipPortTField: UITextField! @IBOutlet weak var sipUsernameTField: UITextField! @IBOutlet weak var sipPasswordTField: UITextField! - + + @IBOutlet weak var loginButton: UIButton! + @IBOutlet weak var logoutButton: UIButton! + //Destination Uri to Making outgoing call @IBOutlet weak var sipDestinationUriTField: UITextField! + + var accStatus: Bool! + + func updateAccStatus(status: Bool) { + accStatus = status; + if (status) { + statusLabel.text = "Reg Status: REGISTERED" + loginButton.isEnabled = false; + logoutButton.isEnabled = true; + } else { + statusLabel.text = "Reg Status: NOT REGISTERED" + loginButton.isEnabled = true; + logoutButton.isEnabled = false; + } + } override func viewDidLoad() { super.viewDidLoad() - - - //Create Lib - CPPWrapper().createLibWrapper() - - //Listen incoming call via function pointer - CPPWrapper().incoming_call_wrapper(incoming_call_swift) - + + vc_inst = self; + + CPPWrapper().createAccountWrapper(sipUsernameTField.text, + sipPasswordTField.text, + sipIpTField.text, + sipPortTField.text) + //Done button to the keyboard sipIpTField.addDoneButtonOnKeyboard() sipPortTField.addDoneButtonOnKeyboard() @@ -52,28 +76,21 @@ class ViewController: UIViewController { sipPasswordTField.addDoneButtonOnKeyboard() sipDestinationUriTField.addDoneButtonOnKeyboard() } - - + //Refresh Button @IBAction func refreshStatus(_ sender: UIButton) { - if (CPPWrapper().registerStateInfoWrapper()){ - statusLabel.text = "Sip Status: REGISTERED" - }else { - statusLabel.text = "Sip Status: NOT REGISTERED" - } } - //Login Button @IBAction func loginClick(_ sender: UIButton) { //Check user already logged in. && Form is filled - if (CPPWrapper().registerStateInfoWrapper() == false - && !sipUsernameTField.text!.isEmpty - && !sipPasswordTField.text!.isEmpty - && !sipIpTField.text!.isEmpty - && !sipPortTField.text!.isEmpty){ - + if (//CPPWrapper().registerStateInfoWrapper() == false && + !sipUsernameTField.text!.isEmpty && + !sipPasswordTField.text!.isEmpty && + !sipIpTField.text!.isEmpty && + !sipPortTField.text!.isEmpty) + { //Register to the user CPPWrapper().createAccountWrapper( sipUsernameTField.text, @@ -81,7 +98,6 @@ class ViewController: UIViewController { sipIpTField.text, sipPortTField.text) - } else { let alert = UIAlertController(title: "SIP SETTINGS ERROR", message: "Please fill the form / Logout", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in @@ -101,43 +117,25 @@ class ViewController: UIViewController { })) self.present(alert, animated: true, completion: nil) } - - //Wait until register/unregister - sleep(2) - if (CPPWrapper().registerStateInfoWrapper()){ - statusLabel.text = "Sip Status: REGISTERED" - } else { - statusLabel.text = "Sip Status: NOT REGISTERED" - } - } //Logout Button @IBAction func logoutClick(_ sender: UIButton) { - /** Only unregister from an account. */ //Unregister CPPWrapper().unregisterAccountWrapper() - - //Wait until register/unregister - sleep(2) - if (CPPWrapper().registerStateInfoWrapper()){ - statusLabel.text = "Sip Status: REGISTERED" - } else { - statusLabel.text = "Sip Status: NOT REGISTERED" - } } //Call Button @IBAction func callClick(_ sender: UIButton) { - if(CPPWrapper().registerStateInfoWrapper() != false){ + if (accStatus) { let vcToPresent = self.storyboard!.instantiateViewController(withIdentifier: "outgoingCallVC") as! OutgoingViewController vcToPresent.outgoingCallId = sipDestinationUriTField.text ?? "" self.present(vcToPresent, animated: true, completion: nil) - }else { + } else { let alert = UIAlertController(title: "Outgoing Call Error", message: "Please register to be able to make call", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in switch action.style{ diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Info.plist b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Info.plist index 5b531f7b27..348e4910bc 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Info.plist +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Info.plist @@ -2,6 +2,8 @@ + NSCameraUsageDescription + Video Call CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -47,6 +49,7 @@ Main UIRequiredDeviceCapabilities + video-camera armv7 UISupportedInterfaceOrientations diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Model/Model.swift b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Model/Model.swift index 9cd976252a..e3d73c8f66 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Model/Model.swift +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/Model/Model.swift @@ -21,42 +21,53 @@ import UIKit func topMostController() -> UIViewController { var topController: UIViewController = (UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController!)! - while (topController.presentedViewController != nil) { - topController = topController.presentedViewController! - } - return topController + while (topController.presentedViewController != nil) { + topController = topController.presentedViewController! } + return topController +} +func incoming_call_swift() { + DispatchQueue.main.async () { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let vc = storyboard.instantiateViewController(withIdentifier: "viewController") + let topVC = topMostController() + let vcToPresent = vc.storyboard!.instantiateViewController(withIdentifier: "incomingCallVC") as! IncomingViewController + vcToPresent.incomingCallId = CPPWrapper().incomingCallInfoWrapper() + topVC.present(vcToPresent, animated: true, completion: nil) + } +} - func incoming_call_swift() { +func call_status_listener_swift ( call_answer_code: Int32) { + if (call_answer_code == 0) { + DispatchQueue.main.async () { + UIApplication.shared.windows.first {$0.isKeyWindow}?.rootViewController?.dismiss(animated: true, completion: nil) + } + } else if (call_answer_code == 1) { DispatchQueue.main.async () { let storyboard = UIStoryboard(name: "Main", bundle: nil) let vc = storyboard.instantiateViewController(withIdentifier: "viewController") - let topVC = topMostController() - let vcToPresent = vc.storyboard!.instantiateViewController(withIdentifier: "incomingCallVC") as! IncomingViewController - vcToPresent.incomingCallId = CPPWrapper().incomingCallInfoWrapper() - topVC.present(vcToPresent, animated: true, completion: nil) + let vcToPresent = vc.storyboard!.instantiateViewController(withIdentifier: "activeCallVC") as! ActiveViewController + if (vcToPresent.presentedViewController != nil) { + let topVC = topMostController() + vcToPresent.activeCallId = CPPWrapper().incomingCallInfoWrapper() + topVC.present(vcToPresent, animated: true, completion: nil) + } } } +} - - func call_status_listener_swift ( call_answer_code: Int32) { - if (call_answer_code == 0){ - DispatchQueue.main.async () { - UIApplication.shared.windows.first { $0.isKeyWindow}?.rootViewController?.dismiss(animated: true, completion: nil) - } - } - else if (call_answer_code == 1) { - DispatchQueue.main.async () { - let storyboard = UIStoryboard(name: "Main", bundle: nil) - let vc = storyboard.instantiateViewController(withIdentifier: "viewController") - let topVC = topMostController() - let vcToPresent = vc.storyboard!.instantiateViewController(withIdentifier: "activeCallVC") as! ActiveViewController - vcToPresent.activeCallId = CPPWrapper().incomingCallInfoWrapper() - topVC.present(vcToPresent, animated: true, completion: nil) - } - } - else { - print("ERROR CODE:") - } - } +func update_video_swift(window: UnsafeMutableRawPointer?) { + DispatchQueue.main.async () { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let vc = storyboard.instantiateViewController(withIdentifier: "viewController") + let activeVc = vc.storyboard!.instantiateViewController(withIdentifier: "activeCallVC") as! ActiveViewController + let topVC = topMostController() + activeVc.activeCallId = CPPWrapper().incomingCallInfoWrapper() + topVC.present(activeVc, animated: true, completion: nil) + let vid_view:UIView = + Unmanaged.fromOpaque(window!).takeUnretainedValue(); + activeVc.loadViewIfNeeded() + activeVc.updateVideo(vid_win: vid_view); + } +} diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/CustomPJSUA2.cpp b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/CustomPJSUA2.cpp index a95864590a..39051e3083 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/CustomPJSUA2.cpp +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/CustomPJSUA2.cpp @@ -23,172 +23,242 @@ using namespace pj; +class MyEndpoint : public Endpoint +{ +public: + virtual void onTimer(const OnTimerParam &prm); +}; + +// Subclass to extend the Account and get notifications etc. +class MyAccount : public Account +{ +public: + std::string dest_uri; + + MyAccount() {} + ~MyAccount() + { + // Invoke shutdown() first.. + shutdown(); + // ..before deleting any member objects. + } + + // This is getting for register status! + virtual void onRegState(OnRegStateParam &prm); + + // This is getting for incoming call (We can either answer or hangup the incoming call) + virtual void onIncomingCall(OnIncomingCallParam &iprm); +}; + +// Subclass to extend the Call and get notifications etc. +class MyCall : public Call +{ +public: + MyCall(Account &acc, int call_id = PJSUA_INVALID_ID) + : Call(acc, call_id) + { } + ~MyCall() + { } + + // Notification when call's state has changed. + virtual void onCallState(OnCallStateParam &prm); + + // Notification when call's media state has changed. + virtual void onCallMediaState(OnCallMediaStateParam &prm); +}; + +enum { + MAKE_CALL = 1, + ANSWER_CALL = 2, + HOLD_CALL = 3, + UNHOLD_CALL = 4, + HANGUP_CALL = 5 +}; + +Call *call = NULL; +Endpoint *ep = NULL; +MyAccount *acc = NULL; + // Listen swift code via function pointers void (*incomingCallPtr)() = 0; void (*callStatusListenerPtr)(int) = 0; - -/** - Dispatch queue to manage ios thread serially or concurrently on app's main thread - for more information please visit: - https://developer.apple.com/documentation/dispatch/dispatchqueue - */ -dispatch_queue_t queue; +void (*accStatusListenerPtr)(bool) = 0; +void (*updateVideoPtr)(void *) = 0; //Getter & Setter function std::string callerId; -bool registerState = false; -void setCallerId(std::string callerIdStr){ - callerId = callerIdStr; +void MyEndpoint::onTimer(const OnTimerParam &prm) +{ + /* IMPORTANT: + * We need to call PJSIP API from a separate thread since + * PJSIP API can potentially block the main/GUI thread. + * And make sure we don't use Apple's Dispatch / gcd since + * it's incompatible with POSIX threads. + * In this example, we take advantage of PJSUA2's timer thread + * to perform call operations. For a more complex application, + * it is recommended to create your own separate thread + * instead for this purpose. + */ + long code = (long) prm.userData; + if (code == MAKE_CALL) { + CallOpParam prm(true); // Use default call settings + prm.opt.videoCount = 1; + try { + call = new MyCall(*acc); + call->makeCall(acc->dest_uri, prm); + } catch(Error& err) { + std::cout << err.info() << std::endl; + } + } else if (code == ANSWER_CALL) { + CallOpParam op(true); + op.statusCode = PJSIP_SC_OK; + call->answer(op); + } else if (code == HOLD_CALL) { + if (call != NULL) { + CallOpParam op(true); + + try { + call->setHold(op); + } catch(Error& err) { + std::cout << "Hold error: " << err.info() << std::endl; + } + } + } else if (code == UNHOLD_CALL) { + if (call != NULL) { + CallOpParam op(true); + op.opt.flag = PJSUA_CALL_UNHOLD; + + try { + call->reinvite(op); + } catch(Error& err) { + std::cout << "Unhold/Reinvite error: " << err.info() << std::endl; + } + } + } else if (code == HANGUP_CALL) { + if (call != NULL) { + CallOpParam op(true); + op.statusCode = PJSIP_SC_DECLINE; + call->hangup(op); + delete call; + call = NULL; + } + } } -std::string getCallerId(){ - return callerId; +void MyAccount::onRegState(OnRegStateParam &prm) +{ + AccountInfo ai = getInfo(); + std::cout << (ai.regIsActive? "*** Register: code=" : "*** Unregister: code=") + << prm.code << std::endl; + accStatusListenerPtr(ai.regIsActive); } -void setRegisterState(bool registerStateBool){ - registerState = registerStateBool; +void MyAccount::onIncomingCall(OnIncomingCallParam &iprm) +{ + incomingCallPtr(); + call = new MyCall(*this, iprm.callId); } -bool getRegisterState(){ - return registerState; +void setCallerId(std::string callerIdStr) +{ + callerId = callerIdStr; } - -//Call object to manage call operations. -Call *call = NULL; - - -// Subclass to extend the Call and get notifications etc. -class MyCall : public Call +std::string getCallerId() { -public: - MyCall(Account &acc, int call_id = PJSUA_INVALID_ID) : Call(acc, call_id) - { } - ~MyCall() - { } + return callerId; +} - // Notification when call's state has changed. - virtual void onCallState(OnCallStateParam &prm){ - CallInfo ci = getInfo(); - if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { - callStatusListenerPtr(0); - - /* Delete the call */ - delete call; - call = NULL; - } - if (ci.state == PJSIP_INV_STATE_CONFIRMED) { - callStatusListenerPtr(1); - } - - setCallerId(ci.remoteUri); - - //Notify caller ID: - PJSua2 pjsua2; - pjsua2.incomingCallInfo(); +void MyCall::onCallState(OnCallStateParam &prm) +{ + CallInfo ci = getInfo(); + if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { + callStatusListenerPtr(0); + /* Delete the call */ + delete call; + call = NULL; + return; } + + setCallerId(ci.remoteUri); + + if (ci.state == PJSIP_INV_STATE_CONFIRMED) { + callStatusListenerPtr(1); + } + + //Notify caller ID: + PJSua2 pjsua2; + pjsua2.incomingCallInfo(); +} - // Notification when call's media state has changed. - virtual void onCallMediaState(OnCallMediaStateParam &prm){ - CallInfo ci = getInfo(); - // Iterate all the call medias - for (unsigned i = 0; i < ci.media.size(); i++) { - if (ci.media[i].type==PJMEDIA_TYPE_AUDIO && getMedia(i)) { +void MyCall::onCallMediaState(OnCallMediaStateParam &prm) +{ + CallInfo ci = getInfo(); + // Iterate all the call medias + for (unsigned i = 0; i < ci.media.size(); i++) { + if (ci.media[i].status == PJSUA_CALL_MEDIA_ACTIVE || + ci.media[i].status == PJSUA_CALL_MEDIA_REMOTE_HOLD) + { + if (ci.media[i].type==PJMEDIA_TYPE_AUDIO) { AudioMedia *aud_med = (AudioMedia *)getMedia(i); // Connect the call audio media to sound device AudDevManager& mgr = Endpoint::instance().audDevManager(); aud_med->startTransmit(mgr.getPlaybackDevMedia()); mgr.getCaptureDevMedia().startTransmit(*aud_med); + } else if (ci.media[i].type==PJMEDIA_TYPE_VIDEO) { + void *window = ci.media[i].videoWindow.getInfo().winHandle.handle.window; + updateVideoPtr(window); } } } -}; - - -// Subclass to extend the Account and get notifications etc. -class MyAccount : public Account { - public: - MyAccount() {} - ~MyAccount() - { - // Invoke shutdown() first.. - shutdown(); - // ..before deleting any member objects. - } - - - // This is getting for register status! - virtual void onRegState(OnRegStateParam &prm); - - // This is getting for incoming call (We can either answer or hangup the incoming call) - virtual void onIncomingCall(OnIncomingCallParam &iprm); -}; - - -//Creating objects -Endpoint *ep = new Endpoint; -MyAccount *acc = new MyAccount; - -void MyAccount::onRegState(OnRegStateParam &prm){ - AccountInfo ai = getInfo(); - std::cout << (ai.regIsActive? "*** Register: code=" : "*** Unregister: code=") << prm.code << std::endl; - PJSua2 pjsua2; - setRegisterState(ai.regIsActive); - pjsua2.registerStateInfo(); - -} - -void MyAccount::onIncomingCall(OnIncomingCallParam &iprm) { - incomingCallPtr(); - call = new MyCall(*this, iprm.callId); } - /** Create Lib with EpConfig */ -void PJSua2::createLib() { +void PJSua2::createLib() +{ + ep = new MyEndpoint; + try { ep->libCreate(); } catch (Error& err){ std::cout << "Startup error: " << err.info() << std::endl; } - + //LibInit try { EpConfig ep_cfg; ep->libInit( ep_cfg ); - } catch(Error& err) { std::cout << "Initialization error: " << err.info() << std::endl; } - - // Create SIP transport. Error handling sample is shown + + // Create SIP transport try { - TransportConfig tcfg; - tcfg.port = 5060; - TransportId tid = ep->transportCreate(PJSIP_TRANSPORT_UDP, tcfg); - + TransportConfig tcfg; + tcfg.port = 5060; + ep->transportCreate(PJSIP_TRANSPORT_UDP, tcfg); } catch(Error& err) { - std::cout << "Transport creation error: " << err.info() << std::endl; + std::cout << "Transport creation error: " << err.info() << std::endl; } // Start the library (worker threads etc) - try { ep->libStart(); + try { + ep->libStart(); } catch(Error& err) { - std::cout << "Startup error: " << err.info() << std::endl; + std::cout << "Startup error: " << err.info() << std::endl; } } - /** Delete lib */ -void PJSua2::deleteLib() { - +void PJSua2::deleteLib() +{ // Here we don't have anything else to do.. pj_thread_sleep(500); @@ -199,29 +269,31 @@ void PJSua2::deleteLib() { delete ep; } - /** Create Account via following config(string username, string password, string ip, string port) */ -void PJSua2::createAccount(std::string username, std::string password, std::string ip, std::string port) { - +void PJSua2::createAccount(std::string username, std::string password, + std::string registrar, std::string port) +{ // Configure an AccountConfig AccountConfig acfg; - acfg.idUri = "sip:" + username + "@" + ip + ":" + port; - acfg.regConfig.registrarUri = "sip:" + ip + ":" + port; + acfg.idUri = "sip:" + username + "@" + registrar + ":" + port;; + acfg.regConfig.registrarUri = "sip:" + registrar + ":" + port;; AuthCredInfo cred("digest", "*", username, 0, password); acfg.sipConfig.authCreds.push_back(cred); + + acfg.videoConfig.autoShowIncoming = true; + acfg.videoConfig.autoTransmitOutgoing = true; - - // TODO:: GET ID -1 IS EXPERIMENTAL, NOT SURE THAT, IT IS GOOD WAY TO CHECK ACC IS CREATED. FIX IT! - if(acc->getId() == -1){ + if (!acc) { // Create the account + acc = new MyAccount; try { acc->create(acfg); } catch(Error& err) { std::cout << "Account creation error: " << err.info() << std::endl; } - }else { + } else { // Modify the account try { //Update the registration @@ -231,121 +303,93 @@ void PJSua2::createAccount(std::string username, std::string password, std::stri std::cout << "Account modify error: " << err.info() << std::endl; } } - } - /** Unregister account */ -void PJSua2::unregisterAccount() { +void PJSua2::unregisterAccount() +{ acc->setRegistration(false); } - /** - Get register state true / false + Make outgoing call (string dest_uri) -> e.g. makeCall(sip:) */ -bool PJSua2::registerStateInfo(){ - return getRegisterState(); +void PJSua2::outgoingCall(std::string dest_uri) +{ + acc->dest_uri = dest_uri; + ep->utilTimerSchedule(0, (Token)MAKE_CALL); } - /** - Get caller id for incoming call, checks account currently registered (ai.regIsActive) + Answer incoming call */ -std::string PJSua2::incomingCallInfo() { - return getCallerId(); +void PJSua2::answerCall() +{ + ep->utilTimerSchedule(0, (Token)ANSWER_CALL); } - /** - Listener (When we have incoming call, this function pointer will notify swift.) + Hangup active call (Incoming/Outgoing/Active) */ -void PJSua2::incoming_call(void (* funcpntr)()){ - incomingCallPtr = funcpntr; +void PJSua2::hangupCall() +{ + ep->utilTimerSchedule(0, (Token)HANGUP_CALL); } - /** - Listener (When we have changes on the call state, this function pointer will notify swift.) + Hold the call */ -void PJSua2::call_listener(void (* funcpntr)(int)){ - callStatusListenerPtr = funcpntr; +void PJSua2::holdCall() +{ + ep->utilTimerSchedule(0, (Token)HOLD_CALL); } - /** - Answer incoming call + Unhold the call */ -void PJSua2::answerCall(){ - CallOpParam op; - op.statusCode = PJSIP_SC_OK; - call->answer(op); +void PJSua2::unholdCall() +{ + ep->utilTimerSchedule(0, (Token)UNHOLD_CALL); } - /** - Hangup active call (Incoming/Outgoing/Active) + Get caller id for incoming call, checks account currently registered (ai.regIsActive) */ -void PJSua2::hangupCall(){ - - if (call != NULL) { - CallOpParam op; - op.statusCode = PJSIP_SC_DECLINE; - call->hangup(op); - delete call; - call = NULL; - } +std::string PJSua2::incomingCallInfo() +{ + return getCallerId(); } /** - Hold the call + Listener (When we have incoming call, this function pointer will notify swift.) */ -void PJSua2::holdCall(){ - - if (call != NULL) { - CallOpParam op; - - try { - call->setHold(op); - } catch(Error& err) { - std::cout << "Hold error: " << err.info() << std::endl; - } - } - +void PJSua2::incoming_call(void (* funcpntr)()) +{ + incomingCallPtr = funcpntr; } /** - Unhold the call + Listener (When we have changes on the call state, this function pointer will notify swift.) */ -void PJSua2::unholdCall(){ - - if (call != NULL) { - - CallOpParam op; - op.opt.flag=PJSUA_CALL_UNHOLD; - - try { - call->reinvite(op); - } catch(Error& err) { - std::cout << "Unhold/Reinvite error: " << err.info() << std::endl; - } - } - +void PJSua2::call_listener(void (* funcpntr)(int)) +{ + callStatusListenerPtr = funcpntr; } + /** - Make outgoing call (string dest_uri) -> e.g. makeCall(sip:) + Listener (When we have changes on the acc reg state, this function pointer will notify swift.) */ -void PJSua2::outgoingCall(std::string dest_uri) { - CallOpParam prm(true); // Use default call settings - try { - call = new MyCall(*acc); - call->makeCall(dest_uri, prm); - } catch(Error& err) { - std::cout << err.info() << std::endl; - } +void PJSua2::acc_listener(void (* funcpntr)(bool)) +{ + accStatusListenerPtr = funcpntr; } - - +/** + Listener (When we have video, this function pointer will notify swift.) + */ +void PJSua2::update_video(void (*funcpntr)(void *)) +{ + updateVideoPtr = funcpntr; +} diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/CustomPJSUA2.hpp b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/CustomPJSUA2.hpp index 4fbc437975..6b993b994f 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/CustomPJSUA2.hpp +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/CustomPJSUA2.hpp @@ -18,7 +18,13 @@ */ #include + +// Ignore doxygen warning +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation" #include +#pragma clang diagnostic pop + #include /** @@ -38,35 +44,51 @@ class PJSua2{ */ void deleteLib(); - - //Account /** - Create Account via following config(string username, string password, string ip, string port) + Create Account via following config(string username, string password, string registrar, string port) */ - void createAccount(std::string username, std::string password, std::string ip, std::string port); + void createAccount(std::string username, std::string password, + std::string registrar, std::string port); /** Unregister account */ void unregisterAccount(); - - - //Register State Info + //Call /** - Get register state true / false + Answer incoming call */ - bool registerStateInfo(); + void answerCall(); + /** + Hangup active call (Incoming/Outgoing/Active) + */ + void hangupCall(); + + /** + Hold the call + */ + void holdCall(); + /** + unhold the call + */ + void unholdCall(); + /** + Make outgoing call (string dest_uri) -> e.g. makeCall(sip:) + */ + void outgoingCall(std::string dest_uri); + //Call Info /** Get caller id for incoming call, checks account currently registered (ai.regIsActive) */ std::string incomingCallInfo(); + //Listener /** Listener (When we have incoming call, this function pointer will notify swift.) */ @@ -76,31 +98,16 @@ class PJSua2{ Listener (When we have changes on the call state, this function pointer will notify swift.) */ void call_listener(void(*function)(int)); - - /** - Answer incoming call - */ - void answerCall(); - - /** - Hangup active call (Incoming/Outgoing/Active) - */ - void hangupCall(); /** - Hold the call - */ - void holdCall(); - - /** - unhold the call + Listener (When we have changes on the acc reg state, this function pointer will notify swift.) */ - void unholdCall(); - + void acc_listener(void (*function)(bool)); + /** - Make outgoing call (string dest_uri) -> e.g. makeCall(sip:) + Listener (When we have video, this function pointer will notify swift.) */ - void outgoingCall(std::string dest_uri); + void update_video(void (*function)(void *)); }; diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/wrapper.h b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/wrapper.h index 4634673dbc..c3e9aec084 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/wrapper.h +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/wrapper.h @@ -40,28 +40,46 @@ /** Create Account via following config(string username, string password, string ip, string port) */ --(void) createAccountWrapper :(NSString*) username :(NSString*) password :(NSString*) ip :(NSString*) port; +-(void) createAccountWrapper :(NSString*) username :(NSString*) password + :(NSString*) registrar :(NSString*) port; /** Unregister account */ -(void) unregisterAccountWrapper; +//Call +/** + Answer incoming call + */ +-(void) answerCallWrapper; +/** + Hangup active call (Incoming/Outgoing/Active) + */ +-(void) hangupCallWrapper; -//Register State Info - --(bool) registerStateInfoWrapper; +/** + Hold the call + */ +-(void) holdCallWrapper; +/** + unhold the call + */ +-(void) unholdCallWrapper; +/** + Make outgoing call (string dest_uri) -> e.g. makeCall(sip:) + */ +-(void) outgoingCallWrapper :(NSString*) dest_uri; -//Call /** Get caller id for incoming call, checks account currently registered (ai.regIsActive) */ -(NSString*) incomingCallInfoWrapper; - +//Listener /** Listener (When we have incoming call, this function pointer will notify swift.) (Runs swift code from C++) @@ -75,27 +93,16 @@ -(void) call_listener_wrapper: (void(*)(int))function; /** - Answer incoming call - */ --(void) answerCallWrapper; - -/** - Hangup active call (Incoming/Outgoing/Active) + Listener (When we have changes on the acc reg state, this function pointer will notify swift.) + (Runs swift code from C++) */ --(void) hangupCallWrapper; +-(void) acc_listener_wrapper: (void(*)(bool))function; /** - Hold the call + Listener (When we have video, this function pointer will notify swift.) + (Runs swift code from C++) */ --(void) holdCallWrapper; +-(void) update_video_wrapper: (void(*)(void *))function; -/** - unhold the call - */ --(void) unholdCallWrapper; -/** - Make outgoing call (string dest_uri) -> e.g. makeCall(sip:) - */ --(void) outgoingCallWrapper :(NSString*) dest_uri; @end diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/wrapper.mm b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/wrapper.mm index 78b2f322dc..7d83654ed3 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/wrapper.mm +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/PJSua2/wrapper.mm @@ -20,15 +20,12 @@ #import "wrapper.h" #import "CustomPJSUA2.hpp" - /** Create a object from .hpp class & wrapper to be able to use it via Swift */ @implementation CPPWrapper PJSua2 pjsua2; - - //Lib /** Create Lib with EpConfig @@ -45,20 +42,19 @@ -(void) deleteLibWrapper { pjsua2.deleteLib(); } - - //Account /** Create Account via following config(string username, string password, string ip, string port) */ --(void) createAccountWrapper :(NSString*) usernameNS :(NSString*) passwordNS :(NSString*) ipNS :(NSString*) portNS +-(void) createAccountWrapper :(NSString*) usernameNS :(NSString*) passwordNS + :(NSString*) registrarNS :(NSString*) portNS { std::string username = std::string([[usernameNS componentsSeparatedByString:@"*"][0] UTF8String]); std::string password = std::string([[passwordNS componentsSeparatedByString:@"*"][0] UTF8String]); - std::string ip = std::string([[ipNS componentsSeparatedByString:@"*"][0] UTF8String]); + std::string registrar = std::string([[registrarNS componentsSeparatedByString:@"*"][0] UTF8String]); std::string port = std::string([[portNS componentsSeparatedByString:@"*"][0] UTF8String]); - pjsua2.createAccount(username, password, ip, port); + pjsua2.createAccount(username, password, registrar, port); } /** @@ -68,17 +64,42 @@ -(void) unregisterAccountWrapper { return pjsua2.unregisterAccount(); } +/** + Answer incoming call + */ +- (void) answerCallWrapper { + pjsua2.answerCall(); +} +/** + Hangup active call (Incoming/Outgoing/Active) + */ +- (void) hangupCallWrapper { + pjsua2.hangupCall(); +} -//Register State Info /** - Get register state true / false + Hold the call */ --(bool) registerStateInfoWrapper { - return pjsua2.registerStateInfo(); +- (void) holdCallWrapper{ + pjsua2.holdCall(); } +/** + unhold the call + */ +- (void) unholdCallWrapper{ + pjsua2.unholdCall(); +} +/** + Make outgoing call (string dest_uri) -> e.g. makeCall(sip:) + */ +-(void) outgoingCallWrapper :(NSString*) dest_uriNS +{ + std::string dest_uri = std::string([[dest_uriNS componentsSeparatedByString:@"*"][0] UTF8String]); + pjsua2.outgoingCall(dest_uri); +} // Factory method to create NSString from C++ string /** @@ -104,41 +125,19 @@ - (void)call_listener_wrapper: (void(*)(int))function { } /** - Answer incoming call - */ -- (void) answerCallWrapper { - pjsua2.answerCall(); -} - -/** - Hangup active call (Incoming/Outgoing/Active) + Listener (When we have changes on the acc reg state, this function pointer will notify swift.) + (Runs swift code from C++) */ -- (void) hangupCallWrapper { - pjsua2.hangupCall(); +-(void) acc_listener_wrapper: (void(*)(bool))function { + pjsua2.acc_listener(function); } /** - Hold the call + Listener (When we have video, this function pointer will notify swift.) + (Runs swift code from C++) */ -- (void) holdCallWrapper{ - pjsua2.holdCall(); -} - -/** - unhold the call - */ -- (void) unholdCallWrapper{ - pjsua2.unholdCall(); -} - -/** - Make outgoing call (string dest_uri) -> e.g. makeCall(sip:) - */ --(void) outgoingCallWrapper :(NSString*) dest_uriNS -{ - std::string dest_uri = std::string([[dest_uriNS componentsSeparatedByString:@"*"][0] UTF8String]); - pjsua2.outgoingCall(dest_uri); +-(void) update_video_wrapper: (void(*)(void *))function { + pjsua2.update_video(function); } @end - diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/View/Base.lproj/LaunchScreen.storyboard b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/View/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index b655cdaa38..0000000000 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/View/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/View/Base.lproj/Main.storyboard b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/View/Base.lproj/Main.storyboard index 4fa1e9a448..dfadf4da81 100644 --- a/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/View/Base.lproj/Main.storyboard +++ b/pjsip-apps/src/pjsua2/ios-swift-pjsua2/ios-swift-pjsua2/View/Base.lproj/Main.storyboard @@ -1,8 +1,9 @@ - + - + + @@ -20,15 +21,15 @@ - - @@ -135,7 +136,7 @@ - + @@ -167,14 +168,16 @@ + + - - - + + + @@ -186,7 +189,7 @@ - + @@ -244,7 +247,7 @@ - + - + @@ -290,34 +293,25 @@ - + - + + + + + + @@ -332,6 +340,7 @@ + @@ -344,7 +353,7 @@ - +