> For the complete documentation index, see [llms.txt](https://docs.etelecom.vn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.etelecom.vn/tich-hop-api/tong-dai/voip-sdk/ios.md).

# iOS

## I. Thiết lập XCode project <a href="#i-thiet-lap-xcode-project" id="i-thiet-lap-xcode-project"></a>

***Bước 1:*** Tải portsipSDK tại [đây](https://www.portsip.com/download-portsip-voip-sdk/)

***Bước 2:*** Khai báo permission sử dụng Microphone, Camera (để gọi video) trong file `Info.plist`<br>

<figure><img src="/files/QcSMAEndRdcb9TGzOwPa" alt=""><figcaption></figcaption></figure>

***Bước 3:*** Add **các** thư viện, frameworks này vào **TARGETS → Tên App trong XCode**

* Các thư viện VideoToolbox, MetalKit, GLKit, libresolv.tbd, libc++.tbd thì được XCode hỗ trợ sẵn, chỉ việc bấm dấu "+" và search tên thư viện rồi add vào.
* `PortSIPVoIPSDK.framework` nằm trong thư mục `iOSSample` được giải nén từ file `.zip` download ở bước 1.

![https://i.imgur.com/bpfVyaC.png](https://i.imgur.com/bpfVyaC.png)

chú ý **TARGETS** không phải *PROJECT*

***Bước 4:*** Trong `Build Settings` → `Linking` → `Other Linker Flags`: thêm vào giá trị `-ObjC`

![https://i.imgur.com/HTkxsp5.png](https://i.imgur.com/HTkxsp5.png)

***Tài liệu tham khảo***: <https://www.portsip.com/docs/sdk/manual/portsip-voip-sdk-user-manual-ios.pdf>

{% hint style="info" %}
Những ví dụ từ mục II trở đi đều áp dụng trong chỉ 1 file duy nhất: **AppDelegate.swift**
{% endhint %}

## II. Khởi tạo SDK <a href="#ii-khoi-tao-sdk" id="ii-khoi-tao-sdk"></a>

***Bước 1***: Khai báo biến global `let portsipSDK: PortSIPSDK! = PortSIPSDK()` . Dùng để gọi các API của PortsipSDK.

***Bước 2:*** Mở file `Runner-Bridging-Header.h`, import thêm dòng này vào `#import <PortSIPVoIPSDK/PortSIPVoIPSDK.h>`

***Bước 3:***  Phải implement interface: `**PortSIPEventDelegate` .\*\* trong Class `AppDelegate`

{% code overflow="wrap" %}

```javascript
func initPortsipSDK() {

  portsipSDK.unInitialize()

  portsipSDK.delegate = self
  portsipSDK.enableCallKit(true)

  portsipSDK.initialize(
    TRANSPORT_TCP, localIP: "0.0.0.0", localSIPPort: Int32(10002), loglevel: PORTSIP_LOG_NONE, logPath: "",
    maxLine: 8, agent: "PortSIP SDK for IOS", audioDeviceLayer: 0, videoDeviceLayer: 0,
    tlsCertificatesRootPath: "", tlsCipherList: "", verifyTLSCertificate: false)

  // những cấu hình liên quan đến Audio
  portsipSDK.addAudioCodec(AUDIOCODEC_OPUS)
  portsipSDK.addAudioCodec(AUDIOCODEC_G729)
  portsipSDK.addAudioCodec(AUDIOCODEC_PCMA)
  portsipSDK.addAudioCodec(AUDIOCODEC_PCMU)
  portsipSDK.setAudioSamples(20, maxPtime: 60)

  // những cấu hình liên quan đến Video
  portsipSDK.addVideoCodec(VIDEO_CODEC_H264)
  portsipSDK.addVideoCodec(VIDEO_CODEC_H263_1998)
  portsipSDK.addVideoCodec(VIDEO_CODEC_VP8)
  portsipSDK.addVideoCodec(VIDEO_CODEC_VP9)
  portsipSDK.setVideoBitrate(-1, bitrateKbps: 512)
  portsipSDK.setVideoFrameRate(-1, frameRate: 20)
  portsipSDK.setVideoResolution(480, height: 640)
  portsipSDK.setVideoNackStatus(true)

  portsipSDK.setInstanceId(UIDevice.current.identifierForVendor?.uuidString)

  portsipSDK.setLicenseKey("PORTSIP_UC_LICENSE")
  
}
```

{% endcode %}

Hàm này nên được gọi chung lúc với đăng nhập máy nhánh.

## III. Đăng nhập máy nhánh (extension) <a href="#iii-dang-nhap-may-nhanh-extension" id="iii-dang-nhap-may-nhanh-extension"></a>

{% code overflow="wrap" %}

```javascript
// Giải thích:
// username: extension_number (ví dụ: 1234, 5678, ...)
// password: extension_password (ví dụ: abcDefg123, xzyqerTT11, ...)
// domain: tenant_domain (ví dụ: voip.example.com, ...)

func registerPortsip(username: String, password: String, domain: String) {
  
  portsipSDK.removeUser()
  var code = portsipSDK.setUser(
    username, displayName: username, authName: username, password: password, userDomain: domain,
    sipServer: "sip.etelecom.vn", sipServerPort: Int32(5063),
    stunServer: "", stunServerPort: 0, outboundServer: "", outboundServerPort: 0)
  
  if code != 0 {
    return
  }
  
  portsipSDK.unRegisterServer()
  code = portsipSDK.registerServer(90, retryTimes: 0)
  
}
```

{% endcode %}

Nên có 1 biến `portsipRegistered` dùng để lưu trạng thái đã register với portsip thành công hay chưa. Nếu đã register thành công thì chỉ cần gọi API `refreshRegistration()` , không cần qua 2 bước `initPortsipSDK()` và `registerPortsip()` bên trên.

```javascript
portsipSDK.refreshRegistration(0)
```

Nếu muốn register lại từ đầu thì phải **Khởi tạo SDK** lại như **mục II**.

⇒ Khi đăng nhập thành công: sự kiện `onRegisterSuccess()` xảy ra.

⇒ Ngược lại, khi không thành công thì sự kiện `onRegisterFailure()` xảy ra.

## IV. Đăng xuất máy nhánh (extension) <a href="#iv-dang-xuat-may-nhanh-extension" id="iv-dang-xuat-may-nhanh-extension"></a>

```javascript
portsipSDK.removeUser()
portsipSDK.unRegisterServer()
portsipSDK.unInitialize()
```

## V. Thực hiện cuộc gọi (outgoing call) <a href="#v-thuc-hien-cuoc-goi-outgoing-call" id="v-thuc-hien-cuoc-goi-outgoing-call"></a>

***Để thực hiện cuộc gọi:***

{% code overflow="wrap" %}

```javascript
_sessionID = portsipSDK.call(callee: String, sendSdp: Bool, videoCall: Bool)
// _sessionID là 1 biến global, khi một cuộc gọi bất kì được tạo ra thì sẽ có 1 session, biến _sessionID này sẽ lưu lại id của session đó.

// callee là số điện thoại user muốn gọi đến.
// sendSdp: có truyền lên Session Description Protocol hay không. Thường sẽ để bằng true
// videoCall: có thực hiện video call hay không.
```

{% endcode %}

***Khi người nhận cuộc gọi chấp nhận cuộc gọi:***

sự kiện `onInviteAnswered()` xảy ra.

```javascript
func onInviteAnswered(
  _ sessionId: Int,
  callerDisplayName: UnsafeMutablePointer<Int8>!,
  caller: UnsafeMutablePointer<Int8>!,
  calleeDisplayName: UnsafeMutablePointer<Int8>!,
  callee: UnsafeMutablePointer<Int8>!,
  audioCodecs: UnsafeMutablePointer<Int8>!,
  videoCodecs: UnsafeMutablePointer<Int8>!,
  existsAudio: Bool,
  existsVideo: Bool,
  sipMessage: UnsafeMutablePointer<Int8>!
) {}
```

***Khi người nhận cuộc gọi từ chối cuộc gọi/cuộc gọi không được bắt máy/hay vì 1 lý do nào đó mà cuộc gọi không thành công:***

sự kiện `onInviteFailure()` xảy ra.

```
func onInviteFailure(
  _ sessionId: Int,
  reason: UnsafeMutablePointer<Int8>!,
  code: Int32,
  sipMessage: UnsafeMutablePointer<Int8>!
) {}-
```

***Chủ động kết thúc cuộc gọi:***

```
portsipSDK.hangUp(sessionId: Int)
```

## VI. Nhận cuộc gọi (incoming call) <a href="#vi-nhan-cuoc-goi-incoming-call" id="vi-nhan-cuoc-goi-incoming-call"></a>

Cần áp dụng Portsip Mobile Push để ứng dụng có thể nhận cuộc gọi trong các trường hợp không active:

* Người dùng không ở màn hình ứng dụng
* Khoá màn hình
* Kill app

### 6.1. Thiết lập mobile push <a href="#id-6-1-thiet-lap-mobile-push" id="id-6-1-thiet-lap-mobile-push"></a>

Để sử dụng mobile push, cần khai báo ứng dụng iOS với hệ thống portsip thông qua các bước:

* Tạo certificate cho VoIP Push và APN Push cho app iOS
* Tạo ra 1 `Apple Certificate file` và `Apple Private key file (no password)` (2 file `.pem`)
* Gửi cho eTelecom 2 file này để tạo thêm Mobile Push

Xem chi tiết tại: [https://www.portsip.com/ios-mobile-push-portsip-pbx](https://support.portsip.com/pbx_v12/push-notifications/implement-the-push-notifications-in-native-ios-app-with-portsip-pbx-12.x) mục **4,5 và 9**

### 6.2. Hiện thực <a href="#id-6-2-hien-thuc" id="id-6-2-hien-thuc"></a>

***Khi có một cuộc gọi đến máy nhánh:***

Sự kiện `onInviteIncoming()` xảy ra.

```
func onInviteIncoming(
  _ sessionId: Int,
  callerDisplayName: UnsafeMutablePointer<Int8>!,
  caller: UnsafeMutablePointer<Int8>!,
  calleeDisplayName: UnsafeMutablePointer<Int8>!,
  callee: UnsafeMutablePointer<Int8>!,
  audioCodecs: UnsafeMutablePointer<Int8>!,
  videoCodecs: UnsafeMutablePointer<Int8>!,
  existsAudio: Bool,
  existsVideo: Bool,
  sipMessage: UnsafeMutablePointer<Int8>!
) {
}
```

***Để nhận được cuộc gọi***:  cần phải sử dụng Portsip Mobile Push, các service của iOS như CallKit, ForceBackground, PushKit.

* Liên hệ eTelecom để tạo thêm Mobile Push
* Bật những tính năng này trên XCode:

![https://i.imgur.com/IH0QC9H.png](https://i.imgur.com/IH0QC9H.png)

Tham khảo về CallKit: [https://developer.apple.com/documentation/callkit(opens new window)](https://developer.apple.com/documentation/callkit)

Khai báo các biến global sau đây:

* `voipRegistry: PKPushRegistry! = PKPushRegistry(queue: DispatchQueue.main)` . Dùng để khai báo về việc nhận notification từ Portsip Mobile Push.
* `_VoIPPushToken: String = ""` . Token dùng để nhận notification VoIP.
* `_APNsPushToken: String = ""` . Token dùng để nhận notification chung.
* `_cxProvider: CXProvider!` . Dùng để hiển thị popup cuộc gọi đến
* `_cxCallController: CXCallController = CXCallController()` . Dùng để thao tác với các cuộc gọi CallKit như ngắt máy, bắt máy, ...

class `AppDelegate` implement thêm các interface sau: `UIApplicationDelegate`, `PKPushRegistryDelegate`, `CXProviderDelegate`

class `AppDelegate` import thêm các service sau: `PushKit`, `UserNotifications`, `CallKit`

Phải có tối thiểu 2 hàm `application()` ở file `AppDelegate.swift`

* 1 hàm application mặc định. Tại đây, enable CallKit (để có thể nhận cuộc gọi, nghe-gọi khi ở background mode), PushNotification (để sử dụng Portsip Mobile Push), ForceBackground (để app chạy ở chế độ background)

{% code overflow="wrap" %}

```javascript
func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
  UserDefaults.standard.register(defaults: ["CallKit": true])
  UserDefaults.standard.register(defaults: ["PushNotification": true])
  UserDefaults.standard.register(defaults: ["ForceBackground": true])
}
```

{% endcode %}

* 1 hàm application dùng để lấy device token dùng cho việc nhận notification

{% code overflow="wrap" %}

```javascript
func application(
  _ application: UIApplication,
  didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {

  let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
  _APNsPushToken = tokenParts.joined()

  updatePushStatusToSipServer() **// khai báo Header dùng để gọi API Register với Portsip. Sẽ nói ở bước 9.**

}
```

{% endcode %}

Phải có tối thiểu 2 hàm `pushRegistry()` ở file `AppDelegate.swift`

* 1 hàm pushRegistry dùng để lấy token dùng cho việc nhận notification VoIP

{% code overflow="wrap" %}

```javascript
func pushRegistry(_: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for _: PKPushType) {

  let tokenParts = pushCredentials.token.map { data in String(format: "%02.2hhx", data) }
  _VoIPPushToken = tokenParts.joined()
  
  updatePushStatusToSipServer() **// khai báo Header dùng để gọi API Register với Portsip. Sẽ nói ở bước 9.**

}
```

{% endcode %}

* 1 hàm pushRegistry dùng để hứng notification VoIP từ Portsip Mobile Push. Khi có cuộc gọi đến, thì portsip sẽ bắn notification về cho thiết bị của người dùng.

{% code overflow="wrap" %}

```javascript
func pushRegistry(_: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for _: PKPushType) {

  **// Nếu không muốn nhận notification VoIP khi app đang active thì check điều kiện này.**
  if UIApplication.shared.applicationState == .active {
    return
  }
  processPushMessageFromPortPBX(payload.dictionaryPayload, completion: {}) **// Dùng CallKit để thông báo cuộc gọi đến. Sẽ nói ở bước 10.**

}
```

{% endcode %}

Khai báo Header với \_VoIPPushToken và \_APNsPushToken trước khi gọi API `Register` hay `RefreshRegistration` của portsipSDK (mục II bước 1).

{% code overflow="wrap" %}

```javascript
func updatePushStatusToSipServer() {

  if _VoIPPushToken.isEmpty || _APNsPushToken.isEmpty {
    return
  }
  
  **portsipSDK.clearAddedSipMessageHeaders()**

  let bundleIdentifier: String = Bundle.main.bundleIdentifier!
  let token = "\(_VoIPPushToken)|\(_APNsPushToken)"
  let pushMessage = "device-os=ios;device-uid=\(token);allow-call-push=true;allow-message-push=true;app-id=\(bundleIdentifier)"

  **portsipSDK.addSipMessageHeader(-1, methodName: "REGISTER", msgType: 1, headerName: "x-p-push", headerValue: pushMessage)**

}
```

{% endcode %}

Dùng CallKit để thông báo cuộc gọi đến, có thể hiện thực như sau:

{% code overflow="wrap" %}

```javascript
func processPushMessageFromPortPBX(_ dictionaryPayload: [AnyHashable: Any], completion: () -> Void) {
  /* dictionaryPayload JSON Format
   Payload: {
   "message_id" = "96854b5d-9d0b-4644-af6d-8d97798d9c5b";
   "msg_content" = "Received a call.";
   "msg_title" = "Received a new call";
   "msg_type" = "call";// im message is "im"
   "x-push-id" = "pvqxCpo-j485AYo9J1cP5A..";
   "send_from" = "102";
   "send_to" = "sip:105@portsip.com";
   }
   */
  
  let parsedObject = dictionaryPayload
  let pushId = dictionaryPayload["x-push-id"]
  
  // _callUUID là 1 biến global để dùng trong việc thông báo cuộc gọi đến bằng CallKit
  if pushId != nil {
    let uuidStr = pushId as! String
    _callUUID = UUID(uuidString: uuidStr)
  }
  if _callUUID == nil {
    return
  }
  
  let handle = CXHandle(type: .generic, value: _callerParsed)
  let update = CXCallUpdate()
  update.remoteHandle = handle
  update.supportsDTMF = false
  update.hasVideo = false
  update.supportsGrouping = false
  update.supportsUngrouping = false
  update.supportsHolding = false
    
  let infoDic = Bundle.main.infoDictionary!
  let localizedName = infoDic["CFBundleName"] as! String
    
  let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)
  providerConfiguration.supportsVideo = true
  providerConfiguration.maximumCallsPerCallGroup = 1
  providerConfiguration.supportedHandleTypes = [.phoneNumber]
    
  _cxProvider = CXProvider(configuration: providerConfiguration)
  _cxProvider.setDelegate(self, queue: DispatchQueue.main)
    
  _cxProvider.reportNewIncomingCall(with: _callUUID, update: update, completion: {})

}
```

{% endcode %}

Một số thao tác user có thể thực hiện khi có cuộc gọi từ CallKit: Chấp nhận (bắt máy), Từ chối, Ngắt máy (sau khi đã chấp nhận), ...

{% code overflow="wrap" %}

```javascript
// 1. Khi người dùng bấm vào nút Chấp nhận cuộc gọi ✅
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
  
  if portsipSDK.answerCall(sessionId: Int, videoCall: Bool) {
    action.fulfill()
  } else {
    action.fail()
  }

}

// 2. Khi người dùng bấm từ chối/kết thúc cuộc gọi ❌
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
  
  if (_sessionID <= 0) { 
    action.fulfill()
    return
  }
  
  var _result = false
  if (***cuộc gọi đã được chấp nhận và đang diễn ra***) {
    _result = portsipSDK.hangUp(sessionId: Int)
  } else {
    // code nên có giá trị là 486
    _result = portsipSDK.rejectCall(sessionId: Int, code: Int)
  }
  
  if (_result) {
    action.fulfill()
  } else {
    action.fail()
  }
}

// 3. Khi cuộc gọi bắt đầu, startAudio để mở microphone và speaker.
func provider(_: CXProvider, didActivate _: AVAudioSession) {
  portsipSDK.startAudio()
}

// 4. Khi cuộc gọi kết thúc, stopAudio để tắt microphone và speaker.
func provider(_: CXProvider, didDeactivate _: AVAudioSession) {
  portsipSDK.stopAudio()
}
```

{% endcode %}

Khi cuộc gọi kết thúc, để tắt màn hình gọi điện của ***CallKit*** đi:

{% code overflow="wrap" %}

```javascript
func reportClosedCall() {
  
  if _callUUID != nil {
    let endCallAction = CXEndCallAction(call: _callUUID)
    
    let transaction = CXTransaction(action: endCallAction)
    
    _cxCallController = CXCallController()
    
    _cxCallController.request(transaction) { error in
      if let error = error {
        print("Error requesting transaction: \(error)")
      } else {
        print("Requested transaction successfully")
      }
    }
  }
  
}
```

{% endcode %}

Khi người dùng tắt app, tắt màn hình điện thoại hoặc kill app thì cần phải giữ cho portsipSDK awake, để ứng dụng luôn sẵn sàng nhận cuộc gọi đến.

{% code overflow="wrap" %}

```javascript
func applicationDidEnterBackground(_: UIApplication) {
  portsipSDK.startKeepAwake()
}

// Khi người dùng mở app lại thì ngưng việc giữ cho portsipSDK awake đi.
func applicationWillEnterForeground(_: UIApplication) {
  portsipSDK.stopKeepAwake()
}
```

{% endcode %}

## VII. Cuộc gọi video <a href="#vii-cuoc-goi-video" id="vii-cuoc-goi-video"></a>

Đầu tiên, ta tạo 1 file swift (ví dụ: VideoCallViewController.swift) kế thừa `UIViewController`. File này sẽ được link với 1 `ViewController` được tạo ra qua các bước `2 và 3`.

```swift
class VideoCallViewController : UIViewController {
  // do something...
}
```

Open file `Main.storyboard` dưới dạng `Interface Builder`

![](https://i.imgur.com/O8bMZVN.png)

Tạo 1 ViewController (ví dụ: VideoCallViewController). Bấm dấu "+" phía góc phải-trên của màn hình xCode ⇒ search từ "View Controller" ⇒ Double Click hoặc Enter.

![https://i.imgur.com/RREQOjd.png](https://i.imgur.com/RREQOjd.png)

⇒ Ta sẽ có 1 ViewController như thế này:

![https://i.imgur.com/X6qFex9.png](https://i.imgur.com/X6qFex9.png)

Tại màn hình này, ở phần `Custom Class` ở bên phải, ta tìm và chọn class VideoCallViewController được tạo trước ở bước `1`.

![https://i.imgur.com/y5uogL4.png](https://i.imgur.com/y5uogL4.png)

Sau đó, ở phần `Identity`, mục `Storyboard ID` ta tuỳ ý đặt cho ViewController này 1 ID, đồng thời check vào checkbox `Use Storyboard ID` bên dưới.

![https://i.imgur.com/xH8PYhV.png](https://i.imgur.com/xH8PYhV.png)

⇒ Thử Open file `Main.storyboard` dưới dạng `Source Code`, ta sẽ thấy đoạn code như sau:

![https://i.imgur.com/jewveS8.png](https://i.imgur.com/jewveS8.png)

Trong ViewController này, ta tiến hành dựng các component cần thiết (1 vùng để chứa RemoteVideo - tức video từ phía khách hàng, 1 vùng để chứa LocalVideo - tức video của chính bạn, 1 button để Kết thúc cuộc gọi, các button như Bật/Tắt loa ngoài, chuyển đổi Camera trước và sau, ...)

* Tạo 2 connection outlets, dùng để gắn video vào 2 vùng RemoteVideo và LocalVideo. Cách gắn sẽ trình bày ở bước sau.
* Ở đoạn code demo bên dưới, chỉ bao gồm 1 vùng để chứa RemoteVideo, 1 vùng để chứa LocalVideo, 1 button để Kết thúc cuộc gọi.

![https://i.imgur.com/vA7KFzh.png](https://i.imgur.com/vA7KFzh.png)

Trong file `VideoCallViewController.swift` (đã tạo ở bước 1), khai báo các biến sau:

{% code overflow="wrap" %}

```swift
// dùng để gán portsipSDK ở AppDelegate.swift vào VideoCallViewController.swift => dùng để gọi API Portsip.
var portSIPSDK: PortSIPSDK!

// dùng để gán sessionId ở AppDelegate.swift vào VideoCallViewController.swift => khi gắn video vào vùng RemoteVideo thì cần có sessionId.
var sessionId: Int = 0
```

{% endcode %}

Trong file `AppDelegate.swift`:

* Khai báo thêm biến `var videoCallView: VideoCallViewController!`.
* Khi đăng nhập máy nhánh thành công, sự kiện `onRegisterSuccess()` xảy ra, trong sự kiện này, khởi tạo các giá trị cho VideoCallView:

{% code overflow="wrap" %}

```swift
func onRegisterSuccess(
  _ reason: UnsafeMutablePointer<Int8>!,
  statusCode: Int32,
  sipMessage: UnsafeMutablePointer<Int8>!
) {
  
  let storyboard = UIStoryboard(name: "Main", bundle: nil)
  videoCallView = storyboard?.instantiateViewController(withIdentifier: "VideoCallViewController") as! VideoCallViewController

  // giao diện cuộc gọi video sẽ có dạng full màn hình.
  videoCallView.modalPresentationStyle = .fullScreen

  // gán portsipSDK của AppDelegate vào portsipSDK của VideoCallViewController => 2 biến này phải bằng nhau thì gọi các api của Portsip mới không bị lỗi.
  videoCallView.portsipSDK = portsipSDK
  
}
```

{% endcode %}

Thực hiện cuộc gọi video, gọi hàm `call()` với tham số `videoCall` = true

{% code overflow="wrap" %}

```javascript
_sessionID = portsipSDK.call(phoneNumber, sendSdp: true, videoCall: true)
// _sessionID là 1 biến global, khi một cuộc gọi bất kì được tạo ra thì sẽ có 1 session, biến _sessionID này sẽ lưu lại id của session đó.

// phoneNumber là số điện thoại user muốn gọi đến.
// sendSdp: có truyền lên Session Description Protocol hay không.
// videoCall: có thực hiện video call hay không.
```

{% endcode %}

⇒ Khi người được gọi nhấc máy: sự kiện `onInviteAnswered()` xảy ra.

{% code overflow="wrap" %}

```swift
func onInviteAnswered(
  _ sessionId: Int,
  callerDisplayName: UnsafeMutablePointer<Int8>!,
  caller: UnsafeMutablePointer<Int8>!,
  calleeDisplayName: UnsafeMutablePointer<Int8>!,
  callee: UnsafeMutablePointer<Int8>!,
  audioCodecs: UnsafeMutablePointer<Int8>!,
  videoCodecs: UnsafeMutablePointer<Int8>!,
  existsAudio: Bool,
  existsVideo: Bool,
  sipMessage: UnsafeMutablePointer<Int8>!
) {
  // gán sessionId của videoCallView
  videoCallView.sessionId = sessionId

  // TODO: mở view VideoCallViewController lên. 
  // Có nhiều cách để mở một UIViewController lên, trong ví dụ này, ta dùng cách 1 UIViewController mở 1 UIViewController khác lên bằng hàm present()

  // RootViewController là ViewController có ID được gán vào attribute initialViewController ở thẻ document trong Main.storyboard (xem hình bên dưới)
  let rootController = window?.rootViewController as? RootViewController
  
  rootController.present(videoCallView, animated: true, completion: nil)
}
```

{% endcode %}

![https://i.imgur.com/pKXYbGW.png](https://i.imgur.com/pKXYbGW.png)

Trong file `VideoCallViewController.swift`:

* Khai báo thêm 2 biến

```swift
// Đây là 2 outlet ta tạo ra ở bước 4
@IBOutlet var remoteVideo: PortSIPVideoRenderView!
@IBOutlet var localVideo: PortSIPVideoRenderView!
```

* Khi view được init lần đầu tiên, ta phải gọi hàm initVideoRender() cho remoteVideo và localVideo.

```swift
override func viewDidLoad() {
  localVideo.initVideoRender()
  remoteVideo.initVideoRender()
}
```

* Khi view đã hiển thị, gắn video cho localVideo và remoteVideo.

{% code overflow="wrap" %}

```swift
override func viewDidAppear(_ animated: Bool) {
  // localVideo - video của chính bạn
  portsipSDK.setLocalVideoWindow(localVideo)
  portsipSDK.displayLocalVideo(true, mirror: true)

  // remoteVideo - video của khách hàng
  portsipSDK.setRemoteVideoWindow(sessionId, remoteVideoWindow: remoteVideo)
}
```

{% endcode %}

* Khi bấm vào nút Kết thúc cuộc gọi:

```swift
@IBAction func hangup(_ sender: Any) {
  // Tắt view videoCall đi.
  dismiss(animated: true, completion: nil)

  portsipSDK.hangUp(sessionId)
}
```

* Khi view đã tắt, gỡ video hiện tại ra khỏi localVideo và remoteVideo.

{% code overflow="wrap" %}

```swift
override func viewDidDisappear(_ animated: Bool) {
  portsipSDK.setLocalVideoWindow(nil)
  portsipSDK.displayLocalVideo(false, mirror: false)

  portsipSDK.setRemoteVideoWindow(sessionId, remoteVideoWindow: nil)
}
```

{% endcode %}

Khi có cuộc gọi đến, sự kiện `onInviteIncoming()` xảy ra

{% code overflow="wrap" %}

```swift
func onInviteIncoming(
  _ sessionId: Int,
  callerDisplayName: UnsafeMutablePointer<Int8>!,
  caller: UnsafeMutablePointer<Int8>!,
  calleeDisplayName: UnsafeMutablePointer<Int8>!,
  callee: UnsafeMutablePointer<Int8>!,
  audioCodecs: UnsafeMutablePointer<Int8>!,
  videoCodecs: UnsafeMutablePointer<Int8>!,
  existsAudio: Bool,
  existsVideo: Bool,
  sipMessage: UnsafeMutablePointer<Int8>!
) {
  // existsVideo để cho biết cuộc gọi hiện tại có phải là videoCall hay không.
}
```

{% endcode %}

⇒ Khi bạn nhấc máy:

{% code overflow="wrap" %}

```swift
let code = portsipSDK.answerCall(_sessionID, videoCall: isVideoCall)
  
if code != 0 {
  return
}

if (isVideoCall) {
  // tương tự như khi thực hiện cuộc gọi ra.
  videoCallView.sessionId = _sessionID
  let rootController = window?.rootViewController as? RootViewController
  
  rootController.present(videoCallView, animated: true, completion: nil)
}
```

{% endcode %}

<br>
