eTelecom docs
Tiếng Việt
Tiếng Việt
  • HƯỚNG DẪN SỬ DỤNG
    • Trang quản trị cs.etelecom.vn
    • Trang quản trị dành cho Partner
    • Google Chrome Extension
    • App eTelecom
  • TÍCH HỢP API
    • Shop
      • Tài khoản
      • Sử dụng
    • Partner
      • Tài khoản
      • Sử dụng
    • Tổng đài
      • VoIP SDK
        • Android
        • iOS
        • Web
        • Flutter
      • API tổng đài
        • Partner lấy danh sách tổng đài
        • Partner tạo hotline
        • Partner lấy danh sách hotline
        • Partner kích hoạt tổng đài
        • Partner gán hotline vào tổng đài
        • Partner gỡ hotline ra khỏi tổng đài
        • Partner xóa hotline ra khỏi hệ thống
        • Tạo tổng đài
        • Lấy thông tin tổng đài
        • Lấy cấu hình tổng đài
        • Lấy danh sách hotline
        • Tạo nhân viên
        • Lấy danh sách nhân viên
        • Tạo máy nhánh
        • Lấy thông tin máy nhánh
        • Lấy danh sách máy nhánh
        • Gán máy nhánh cho nhân viên
        • Thay đổi hotline cho máy nhánh
        • Gỡ máy nhánh khỏi nhân viên
        • Lấy lịch sử cuộc gọi
    • Zalo Cloud
      • Hướng dẫn kết nối Zalo Cloud
      • Zalo Cloud API
        • Tạo liên kết uỷ quyền OA
        • Kết nối OA
        • Lấy refresh token
        • Lấy danh sách OA
        • Lấy thông tin OA
        • Cập nhật Webhook cho OA
        • Lấy danh sách người dùng
        • Gửi tin tư vấn dạng văn bản
        • Gửi tin Tư vấn theo mẫu yêu cầu thông tin người dùng
        • Gửi tin tư vấn dạng sticker
        • Gửi tin tư vấn dạng hình ảnh
        • Gửi tin tư vấn dạng file
        • Gửi tin giao dịch
        • Upload file
        • Gửi yêu cầu cấp quyền gọi
        • Kiểm tra khách hàng đã cấp quyền gọi
        • Lấy danh sách yêu cầu cấp quyền gọi
        • Tạo mẫu tin
        • Lấy thông tin chi tiết mẫu tin
        • Lấy danh sách mẫu tin
        • Khởi tạo Journey Token
        • Lấy thông tin chi tiết Journey Token
        • Lấy danh sách Journey Token
        • Lấy danh sách tin nhắn
        • Lấy thông tin chi tiết tin nhắn
        • Gửi ZNS
        • Gửi ZNS với Journey Token
  • SMS BRANDNAME
    • Hướng dẫn sử dụng
    • SMS API
      • Gửi SMS
      • Lấy trạng thái tin
      • Lấy thông tin chi tiết mẫu tin
Powered by GitBook
On this page
  • I. Thiết lập XCode project
  • II. Khởi tạo SDK
  • III. Đăng nhập máy nhánh (extension)
  • IV. Đăng xuất máy nhánh (extension)
  • V. Thực hiện cuộc gọi (outgoing call)
  • VI. Nhận cuộc gọi (incoming call)
  • 6.1. Thiết lập mobile push
  • 6.2. Hiện thực
  • VII. Cuộc gọi video
  1. TÍCH HỢP API
  2. Tổng đài
  3. VoIP SDK

iOS

PreviousAndroidNextWeb

Last updated 4 days ago

I. Thiết lập XCode project

Bước 1: Tải portsipSDK tại

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

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.

chú ý TARGETS không phải PROJECT

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

Những ví dụ từ mục II trở đi đều áp dụng trong chỉ 1 file duy nhất: AppDelegate.swift

II. Khởi tạo SDK

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

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")
  
}

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)

// 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)
  
}

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.

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)

portsipSDK.removeUser()
portsipSDK.unRegisterServer()
portsipSDK.unInitialize()

V. Thực hiện cuộc gọi (outgoing call)

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

_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.

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

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

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)

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

Để 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

6.2. Hiện thực

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:

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)

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])
}
  • 1 hàm application dùng để lấy device token dùng cho việc nhận notification

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.**

}

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

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.**

}
  • 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.

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.**

}

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).

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)**

}

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

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: {})

}

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), ...

// 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()
}

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

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")
      }
    }
  }
  
}

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.

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()
}

VII. Cuộc gọi video

Đầ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.

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

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

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.

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

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.

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.

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

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.

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

// 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

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:

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
  
}

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

_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.

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

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)
}

Trong file VideoCallViewController.swift:

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

// Đâ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.

override func viewDidLoad() {
  localVideo.initVideoRender()
  remoteVideo.initVideoRender()
}
  • Khi view đã hiển thị, gắn video cho localVideo và remoteVideo.

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)
}
  • Khi bấm vào nút Kết thúc cuộc gọi:

@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.

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

  portsipSDK.setRemoteVideoWindow(sessionId, remoteVideoWindow: nil)
}

Khi có cuộc gọi đến, 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>!
) {
  // existsVideo để cho biết cuộc gọi hiện tại có phải là videoCall hay không.
}

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

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)
}

https://i.imgur.com/bpfVyaC.png
https://i.imgur.com/HTkxsp5.png

Tài liệu tham khảo:

Xem chi tiết tại: mục 4,5 và 9

https://i.imgur.com/IH0QC9H.png

Tham khảo về CallKit:

https://i.imgur.com/RREQOjd.png
https://i.imgur.com/X6qFex9.png
https://i.imgur.com/y5uogL4.png
https://i.imgur.com/xH8PYhV.png
https://i.imgur.com/jewveS8.png
https://i.imgur.com/vA7KFzh.png
https://i.imgur.com/pKXYbGW.png
https://www.portsip.com/docs/sdk/manual/portsip-voip-sdk-user-manual-ios.pdf
https://www.portsip.com/ios-mobile-push-portsip-pbx
https://developer.apple.com/documentation/callkit(opens new window)
đây