> 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/android.md).

# Android

## I. Thiết lập Android Studio project <a href="#i-thiet-lap-android-studio-project" id="i-thiet-lap-android-studio-project"></a>

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

***Bước 2*****:** Giải nén file .zip vừa mới tải về, copy file `AndroidSample/SIPSample_AndroidStudio/SIPSample/libs/portsipvoipsdk.jar` vào thư mục `app/libs` của project Android

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

***Bước 3*****:** Compile Libs : Chuột phải vào file `portsipvoipsdk.jar` chọn `Add as Library...`

***Bước 4*****:** Khai báo các permission cần thiết trong file `AndroidManifest.xml`

{% code overflow="wrap" fullWidth="false" %}

```html
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">

  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
  <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
  <!-- Các permission liên quan đến internet. -->
  
  <uses-permission android:name="android.permission.WAKE_LOCK" />
  <!-- Cho phép ứng dụng có thể unlock điện thoại khi có cuộc gọi đến. -->
  <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
  <!-- Cho phép ứng dụng có thể mở giao diện fullscreen khi nhận thông báo cuộc gọi đến 
  lúc điện thoại đang ở trạng thái không active (khoá màn hình, kill app, không ở
  màn hình ứng dụng) -->
  <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
  <!-- Cho phép người dùng có thể thao tác khi máy đã ở trạng thái lock 
  (bấm chấp nhận/từ chối cuộc gọi). -->
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <!-- Cho phép sử dụng microphone. -->
  <uses-permission android:name="android.permission.CAMERA" />
  <!-- Cho phép sử dụng camera. -->
  <uses-permission android:name="android.permission.VIBRATE" />
  <!-- Cho phép ứng dụng điều khiển điện thoại rung khi có cuộc gọi đến. -->

</manifest>
```

{% endcode %}

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

{% hint style="info" %}
Những ví dụ từ mục II trở đi áp dụng cho 2 file: **MainActivity.kt** và **PortsipService.kt**
{% endhint %}

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

#### Ở file `PortsipService.kt`

* Khai báo biến global `val portsipSDK: PortSipSdk = PortSipSdk()` . Dùng để gọi các API của PortsipSDK.
* Class `PortsipService` phải implement interface: `**OnPortSIPEvent` .\*\*

```javascript
fun initialSDK() {
    portsipSDK.setOnPortSIPEvent(this)
    portsipSDK.CreateCallManager(application)
    
    portsipSDK.initialize(
      PortSipEnumDefine.ENUM_TRANSPORT_TCP, "0.0.0.0", 10002,
      PortSipEnumDefine.ENUM_LOG_LEVEL_NONE, "", 8, "PortSIP SDK for Android",
      0, 0, "", "", false, "")
    
    // những cấu hình liên quan đến Audio
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_AUDIOCODEC_OPUS)
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_AUDIOCODEC_G729)
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_AUDIOCODEC_PCMA)
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_AUDIOCODEC_PCMU)

    // những cấu hình liên quan đến Video
    portsipSDK.addVideoCodec(PortSipEnumDefine.ENUM_VIDEOCODEC_H264)
    portsipSDK.addVideoCodec(PortSipEnumDefine.ENUM_VIDEOCODEC_VP8)
    portsipSDK.addAudioCodec(PortSipEnumDefine.ENUM_VIDEOCODEC_VP9)
    portsipSDK.setVideoBitrate(-1, 512)
    portsipSDK.setVideoFrameRate(-1, 20)
    portsipSDK.setVideoResolution(480, 640)
    portsipSDK.setVideoNackStatus(true)
    
    // có thể đặt InstanceId là 1 constant bất kì.
    portsipSDK.setInstanceId("PORTSIP_INSTANCE_ID")
    
    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) <a href="#iii-dang-nhap-may-nhanh-extension" id="iii-dang-nhap-may-nhanh-extension"></a>

Đầu tiên, ở file ***`MainActivity.kt`***, ta phải khởi chạy `PortsipService` trước

<pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript"><strong>// cần lưu các data về extension (số máy nhánh, password, domain tenant, ...) vào storage của máy.
</strong>// sử dụng SharedPreferences
// Vì khi kill app, các data sẽ bị mất nên phải lưu xuống storage những thông tin này.

// Giải thích:
// số máy nhánh: (ví dụ: 1234, 5678, ...)
// password máy nhánh: (ví dụ: abcDefg123, xzyqerTT11, ...)
// domain tenant: (ví dụ: voip.example.com, ...)

val sharedPref = getSharedPreferences("CallManager", MODE_PRIVATE) ?: return 0
    with(sharedPref.edit()) {
      putString(getString(R.string.call_manager_ps_extension), số máy nhánh)
      putString(getString(R.string.call_manager_ps_password), password)
      putString(getString(R.string.call_manager_ps_domain), domain tenant)
      apply()
    }

Intent(this, PortsipService::class.java).also { intent ->
  startService(intent)
}

</code></pre>

Ở file ***`PortsipService.kt`***, sau khi `startService` như bước 1 thì `onStartCommand` của `PortsipService` được chạy, đăng nhập máy nhánh có thể được gọi lúc này, được hiện thực như ví dụ sau:

{% code overflow="wrap" %}

```javascript
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  registerPortsip()
  return super.onStartCommand(intent, flags, startId)
}

fun registerPortsip() {
  
  val sharedPref = getSharedPreferences("CallManager", MODE_PRIVATE)
  val psExtension = sharedPref.getString(getString(R.string.call_manager_ps_extension), "")
  val psPassword = sharedPref.getString(getString(R.string.call_manager_ps_password), "")
  val psDomain = sharedPref.getString(getString(R.string.call_manager_ps_domain), "")
  val pushMessage = sharedPref.getString(getString(R.string.call_manager_push_message), "") ?: ""

  // addSipMessageHeader dùng cho việc nhận cuộc gọi khi ứng dụng đang không active
  // ví dụ: tắt màn hình, kill app, ...
  // sẽ trình bày chi tiết ở mục VI.
  if (pushMessage.isNotEmpty()) {
    portsipSDK.clearAddedSipMessageHeaders()
    portsipSDK.addSipMessageHeader(-1, "REGISTER", 1, "x-p-push", pushMessage)
  }

  portsipSDK.removeUser()
  var code = portsipSDK.setUser(
    psExtension, psExtension, psExtension, psPassword, psDomain, "sip.etelecom.vn", 5063,
    "", 0, "", 0)
  
  if (code != PortSipErrorcode.ECoreErrorNone) {
    return
  }
  
  portsipSDK.unRegisterServer()
  code = portsipSDK.registerServer(90, 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 `initialSDK()` 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**.

**Có 2 event sẽ xảy ra khi đăng nhập máy nhánh:**

```javascript
// Đăng nhập máy nhánh thành công
fun onRegisterSuccess(reason: String?, code: Int, sipMessage: String?) {}

// Đăng nhập máy nhánh thất bại
fun onRegisterFailure(reason: String?, code: Int, sipMessage: String?) {}
```

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

Ở file ***`PortsipService.kt`***

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

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

Ở file `PortsipService.kt`

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

{% code overflow="wrap" %}

```javascript
_sessionID = portsipSDK.call(callee: String, sendSdp: Boolean, videoCall: Boolean)
// _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
fun onInviteAnswered(
  sessionId: Long,
  callerDisplayName: String,
  caller: String,
  calleeDisplayName: String,
  callee: String,
  audioCodecNames: String,
  videoCodecNames: String,
  existsAudio: Boolean,
  existsVideo: Boolean,
  sipMessage: String
) {}
```

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.

{% code overflow="wrap" %}

```javascript
fun onInviteFailure(sessionId: Long, reason: String?, code: Int, sipMessage: String?) {}
```

{% endcode %}

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

```javascript
portsipSDK.hangUp(sessionID: Long)
```

## 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 Mobile Push (push notification voip) để ứ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 Android với hệ thống portsip thông qua các bước:

* Lấy 2 thông tin: 1 Server Key và 1 Sender ID trên Firebase của Project.
* `applicationId` nằm trong file `build.gradle (Module: android.app)` ví dụ: vn.etelecom.app
* Gửi cho eTelecom 3 thông tin này để tạo thêm Mobile Push.

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

```javascript
onInviteIncoming(
  sessionId: Long,
  callerDisplayName: String,
  caller: String,
  calleeDisplayName: String,
  callee: String,
  audioCodecs: String,
  videoCodecs: String,
  existsAudio: Boolean,
  existsVideo: Boolean,
  sipMessage: String
) {
// do something...
// Ví dụ:
// start 1 Activity hiển thị thông tin cuộc gọi đến
}
```

* Tạo 1 service là `FirebaseService`, và khai báo nó ở `AndroidManifest.xml`

{% code title="FirebaseService.kt" overflow="wrap" %}

```javascript
class FirebaseService : FirebaseMessagingService() {

  companion object {
    const val ACTION_REFRESH_PUSH_TOKEN = "REFRESH_PUSH_TOKEN"
    const val ACTION_PROCESS_PUSH_PBX = "PROCESS_PUSH_PBX"
    const val PUSH_TOKEN = "PUSH_TOKEN"
  }

  override fun onCreate() {
    super.onCreate()
  }

  // Khi eTelecom bắn 1 notification về việc có cuộc gọi đến máy nhánh của bạn.
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    // trong biến data này sẽ gồm thông tin về caller, callee, có video call hay không, ...
    val data: Map<String, String> = remoteMessage.data

    if ***app đang ở trạng thái unactive (kill app, screen off, ...)*** {

      Intent(this, PortsipService::class.java).also { intent ->
        intent.action = ACTION_PROCESS_PUSH_PBX
        startService(intent)
      }

    }
  }

  // Khi token của Firebase được reset
  override fun onNewToken(newToken: String) {
    Intent(this, PortsipService::class.java).also { intent ->
      intent.action = ACTION_REFRESH_PUSH_TOKEN
      intent.putExtra(PUSH_TOKEN, newToken)
      startService(intent)
    }
  }

}
```

{% endcode %}

{% code title="AndroidManifest.xml" %}

```html
<service android:name=".FirebaseService" android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>
```

{% endcode %}

* `build.gradle(Project: android)`, thêm classpath sau đây:

```sh
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.3.8'
    }
}
```

* `build.gradle(Module: android.app)`, thêm dependencies như sau:

```sh
apply plugin: 'com.google.gms.google-services'

dependencies {
  implementation platform('com.google.firebase:firebase-bom:28.1.0')
  implementation 'com.google.firebase:firebase-messaging'
}
```

***Tạo notification channel:*** để dùng trong việc thông báo cuộc gọi đến. *Ta nên gọi hàm này cùng lúc với đăng nhập máy nhánh.*

{% code overflow="wrap" %}

```javascript
fun initNotification() {
  notiManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

  val callChannel = NotificationChannel(BACKGROUND_INCOMING_CALL_CHANNEL_ID, "Cuộc gọi đến", NotificationManager.IMPORTANCE_HIGH)
  callChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
  
  notiManager?.createNotificationChannel(callChannel)  
}
```

{% endcode %}

Khi `onStartCommand` của `PortsipService` được chạy, đã trình bày chi tiết ở mục III.

{% code overflow="wrap" %}

```javascript
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  if (intent != null) {
    // có thêm 2 tình huống cần xử lý bên dưới đây (bên cạnh tình huống đã được trình bày ở mục III.)
    // 1. Khi token của Firebase được refresh
    // 2. Khi kill app, hoặc tắt màn hình điện thoại, cuộc gọi đến sẽ được bắn qua Firebase.
  }
    
  return super.onStartCommand(intent, flags, startId)
}
```

{% endcode %}

* Khi token của Firebase được refresh:

```javascript
if (intent.action == FirebaseService.ACTION_REFRESH_PUSH_TOKEN) {
  firebasePushToken = intent.getStringExtra(FirebaseService.PUSH_TOKEN) ?: ""
  refreshPushToken(true)
  portsipSDK.refreshRegistration(0)
}
```

* Khi kill app, hoặc tắt màn hình điện thoại, cuộc gọi đến sẽ được bắn qua Firebase và sẽ xử lý tại đây:

```javascript
if (intent.action == FirebaseService.ACTION_PROCESS_PUSH_PBX) {

    // Đăng nhập máy nhánh lại, vì khi kill app thì trạng thái đăng nhập sẽ mất đi.
    registerPortsip()
    // Sau đó, hiển thị popup notification thông báo cuộc gọi đến.
    showPendingCallNotification()

}
```

* Khi khởi động ứng dụng, lần đầu tiên start của `PortsipService`, ta phải lắng nghe token của Firebase rồi đăng nhập máy nhánh sau.

{% code overflow="wrap" %}

```javascript
if (intent?.action == null || intent.action!!.isEmpty()) {
  FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
    if (!task.isSuccessful) {
      Log.w("FIREBASE", "Fetching FCM registration token failed", task.exception)
      return@OnCompleteListener
    }

    // Get new FCM registration token
    firebasePushToken = task.result ?: ""
    refreshPushToken(true)
    // Trường hợp Firebase init chậm, xảy ra sau khi đăng nhập máy nhánh thành công, ta sẽ cần refreshRegistration lại.
    portsipSDK.refreshRegistration(0)
  })

  registerPortsip()
}
```

{% endcode %}

* Hàm `refreshPushToken` có thể hiện thực như sau:

{% code overflow="wrap" %}

```javascript
fun refreshPushToken(willPush: Boolean) {
  if (firebasePushToken.isNotEmpty()) {
    portsipSDK.clearAddedSipMessageHeaders()
    val pushMessage = "device-os=android;device-uid=$firebasePushToken;allow-call-push=$willPush;allow-message-push=$willPush;app-id=$packageName"
    portsipSDK.addSipMessageHeader(-1, "REGISTER", 1, "x-p-push", pushMessage)

    // lưu pushMessage vào storage của máy
    val sharedPref = getSharedPreferences("CallManager", MODE_PRIVATE)
    with(sharedPref.edit()) {
      putString(getString(R.string.call_manager_push_message), pushMessage)
      apply()
    }
  }
}
```

{% endcode %}

* Hàm `showPendingNotification` có thể hiện thực như sau:

{% code overflow="wrap" %}

```javascript
fun showPendingCallNotification() {
    
  // Cần phải có 1 Activity để hiển thị view Cuộc gọi đến 
  // khi user bấm vào notification hoặc khi màn hình tắt.
  val fullScreenIntent = Intent(this, IncomingActivity::class.java)
  fullScreenIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK

  val fullScreenPendingIntent = PendingIntent.getActivity(this, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)
  
  val notification = Notification.Builder(this, BACKGROUND_INCOMING_CALL_CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_notification)
    .setColor(Color.argb(1, 47, 204, 112))
    .setContentTitle("Cuộc gọi đến từ")
    .setContentText(Html.fromHtml("<strong>${CallManager.callerParsed}</strong>", Html.FROM_HTML_MODE_LEGACY))
    .setOngoing(true)
    .setShowWhen(true)
    .setContentIntent(fullScreenPendingIntent)
    .setFullScreenIntent(fullScreenPendingIntent, true)
    .build()
  
  notiManager?.notify(BACKGROUND_INCOMING_CALL_NOTIFICATION, notification)
  
}
```

{% endcode %}

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

```javascript
//1. Chấp nhận cuộc gọi
portsipSDK.answerCall(sessionId: Long, videoCall: Boolean) {}

//2. Từ chối cuộc gọi
// code nên có giá trị là 486
portsipSDK.rejectCall(sessionId: Long, code: Int) {}

//3. Ngắt máy sau khi cuộc gọi đã được chấp nhận và đang diễn ra
portsipSDK.hangUp(sessionId: Long) {}
```

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

Cần có 1 *Activity* thể hiện cuộc gọi Video, ví dụ như sau:

{% code overflow="wrap" %}

```javascript
class VideoCallActivity: Activity(), View.OnClickListener {
  // video của user
  var localVideo
  // video của người đang đàm thoại với user
  var remoteVideo

  // khi cuộc gọi video được bắt đầu (1 trong caller hoặc callee chấp nhận cuộc gọi từ người còn lại)
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
  
    // phần giao diện của Activity này, được hiện thực trong file video_call_view.xml
    setContentView(R.layout.video_call_view)

    
    localVideo = findViewById(R.id.local_video_view)
    remoteVideo = findViewById(R.id.remote_video_view)

    portsipSDK.setRemoteVideoWindow(sessionId: Long, remoteVideo)
    portsipSDK.displayLocalVideo(true, true, localVideo)
    
    // nếu user tắt cam thì tham số send bằng false, ngược lại thì bằng true
    portsipSDK.sendVideo(sessionId: Long, send: Boolean)
  }

  // khi cuộc gọi kết thúc, Activity bị dismiss thì cần release các video này ra.
  override fun onDestroy() {
    super.onDestroy()

    portsipSDK.displayLocalVideo(false, false, null)
    localVideo.release()

    portsipSDK.setRemoteVideoWindow(sessionId: Long, null)
    remoteVideo.release()
  }
}
```

{% endcode %}

Nên dùng biến *portsipSDK* ở file `PortsipService.kt` để không xảy ra hiện tượng nhiều instance của 1 biến được tạo ra, sẽ phát sinh nhiều bug không mong muốn.

Giao diện của Activity bên trên, có thể hiện thực ở file `video_call_view.xml` như ví dụ sau:

{% code overflow="wrap" %}

```html
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:orientation="vertical"
        android:background="#EEEEEE"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

  <!-- Remote Video: video của người đang đàm thoại với user -->
  <LinearLayout
            android:id="@+id/remote_video_view_wrapper"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:background="#333"
            android:orientation="vertical">

        <com.portsip.PortSIPVideoRenderer
                android:id="@+id/remote_video_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
  </LinearLayout>

  <!-- Local Video: video của user -->
  <LinearLayout
            android:id="@+id/local_video_view_wrapper"
            android:layout_width="120dp"
            android:layout_height="166dp"
            android:layout_alignParentTop="true"
            android:layout_marginTop="70dp"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="22dp"
            android:clipChildren="true"
            android:background="@drawable/transparent_rounded_corner">

        <com.portsip.PortSIPVideoRenderer
                android:id="@+id/local_video_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
  </LinearLayout>

</FrameLayout>
```

{% endcode %}

***Gọi ra và nhận cuộc gọi đến***:

```javascript
// 1. Gọi ra, với videoCall bằng true
portsipSDK.call(callee: String, true, videoCall: Boolean)

// 2. Nhận cuộc gọi đến
onInviteIncoming(
  sessionId: Long,
  callerDisplayName: String,
  caller: String,
  calleeDisplayName: String,
  callee: String,
  audioCodecs: String,
  videoCodecs: String,
  existsAudio: Boolean,
  existsVideo: Boolean,
  sipMessage: String
) {
// existsVideo để cho biết cuộc gọi hiện tại có phải là videoCall hay không.
}
```
