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
  • Cấu hình cho iOS
  • Cấu hình cho Android
  • Cấu hình đối với Flutter UI
  1. TÍCH HỢP API
  2. Tổng đài
  3. VoIP SDK

Flutter

PreviousWebNextAPI tổng đài

Last updated 1 year ago

Cấu hình cho iOS

Trong file AppDelegate.swift:

```swift
import UIKit
import Flutter
import PushKit
import UserNotifications
import PortSIPVoIPSDK

@UIApplicationMain class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate {
  
  private let channel = "name_channel"
  
  private var portsipService: PortsipService! = PortsipService()
  
  override 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])
  
    fController = window?.rootViewController as? FlutterViewController
    
    FlutterMethodChannel(name: channel, binaryMessenger: fController.binaryMessenger).setMethodCallHandler({
      [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
      // Note: this method is invoked on the UI thread.
      
      switch call.method {
      case "registerPortsip":
        let args = call.arguments as? [String: Any]
        let username = args?["username"] as? String
        let password = args?["password"] as? String
        let domain = args?["domain"] as? String
        let server = args?["sipServer"] as? String
        
        if username != nil && password != nil && domain != nil {
          let res = self?.registerPortSip(username: username!, password: password!, domain: domain!, sipServer: server!)
          if res == 0 || res == -60021 || res == -60095 || res == -60098 {
            result(Int32(res!))
          } else {
            result(FlutterError(code: "REGISTER FAILED", message: "Kết nối không thành công.", details: nil))
          }
        } else {
          result(FlutterError(code: "NOT AUTHENTICATED", message: "Username và password không đúng.", details: nil))
        }
        
      case "unregisterPortsip":
        if (self?.portsipService != nil) {
          self?.updatePushStatusToSipServer(willPush: false)
          self?.portsipService.unregisterPortsip()
        }
        
      case "callOut":
        let args = call.arguments as? [String: Any]
        let phoneNumber = args?["phoneNumber"] as? String
        let videoCall = args?["videoCall"] as? Bool
        
        if phoneNumber != nil {
          let res = self?.portsipService.callOut(phoneNumber: phoneNumber!, videoCall: videoCall ?? false)
          if res! {
            result(nil)
          } else {
            result(FlutterError(code: "CALL FAILED", message: "Không thể thực hiện cuộc gọi", details: nil))
          }
        }
      case "hangUp":
        let res = self?.portsipService.hangUp()
        if res! {
          result(nil)
        } else {
          result(FlutterError(code: "HANGUP FAILED", message: "Không thể kết thúc cuộc gọi", details: nil))
        }
      case "answerCall":
        let res = self?.portsipService.answerCall()
        if res! {
          result(nil)
        } else {
          result(FlutterError(code: "ANSWER_CALL FAILED", message: "Không thể trả lời cuộc gọi", details: nil))
        }
      case "rejectCall":
        let res = self?.portsipService.rejectCall()
        if res! {
          result(nil)
        } else {
          result(FlutterError(code: "REJECT_CALL FAILED", message: "Không thể từ chối cuộc gọi", details: nil))
        }
      case "hold":
        let res = self?.portsipService.hold()
        if res! {
          result(nil)
        } else {
          result(FlutterError(code: "HOLD_CALL FAILED", message: "Không thể giữ máy", details: nil))
        }
      case "unHold":
        let res = self?.portsipService.unHold()
        if res! {
          result(nil)
        } else {
          result(FlutterError(code: "UNHOLD_CALL FAILED", message: "Không thể tiếp tục cuộc gọi", details: nil))
        }
      case "speakerOn":
        self?.portsipService.speakerOn()
        result(nil)
      case "speakerOff":
        self?.portsipService.speakerOff()
        result(nil)
      case "microphoneOn":
        self?.portsipService.turnOnMicrophone()
        result(nil)
      case "microphoneOff":
        self?.portsipService.turnOffMicrophone()
        result(nil)
      case "frontCamera":
        self?.portsipService.switchToFrontCamera()
        result(nil)
      case "backCamera":
        self?.portsipService.switchToBackCamera()
        result(nil)
      case "cameraOn":
        self?.portsipService.turnOnCamera()
        result(nil)
      case "cameraOff":
        self?.portsipService.turnOffCamera()
        result(nil)
        
      default:
        result(FlutterMethodNotImplemented)
      }
    })
    
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
  
  private func registerPortSip(username: String, password: String, domain: String, sipServer: String) -> Int32 {
    PortsipService.fController = fController
    
    if !CallManager.portsipRegistered {
      CallManager.portsipExtension = username
      CallManager.portsipPassword = password
      CallManager.portsipDomain = domain
      CallManager.portsipServer = sipServer
      
      portsipService.registerPortsip()
    } else {
      portsipService.refreshRegistrationPortsip()
    }
    
    return 0
  }
  
}

```

Cấu hình cho Android

```kotlin

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.view.KeyEvent
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.portsip.PortSipErrorcode
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

@RequiresApi(Build.VERSION_CODES.O)
class MainActivity : FlutterActivity() {
  
  companion object {
    lateinit var shared: MainActivity
    const val channel = "name_channel"
  }
  
  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    
    shared = this
    PortsipService.engineF = flutterEngine
  
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler { call, result ->
      // Note: this method is invoked on the main thread.
      when (call.method) {
        "registerPortsip" -> {
          val username = call.argument<String>("username")
          val password = call.argument<String>("password")
          val domain = call.argument<String>("domain")
          val server = call.argument<String>("sipServer")
    
          if (username != null && password != null && domain != null && server != null) {
            val res = registerPortsip(username, password, domain, server)
            if (res == 0 || res == PortSipErrorcode.ECoreAlreadyRegistered ||
              res == PortSipErrorcode.ECoreAllowOnlyOneUser ||
              res == PortSipErrorcode.ECoreCreateTransportFailure
            ) {
              result.success(res)
            } else {
              result.error("REGISTER FAILED", "Kết nối không thành công.", null)
            }
          } else {
            result.error("NOT AUTHENTICATED", "Username và password không đúng.", null)
          }
        }
        "unregisterPortsip" -> {
          unregisterPortsip()
        }
        "callOut" -> {
          val phoneNumber = call.argument<String>("phoneNumber")
          val videoCall = call.argument<Boolean>("videoCall")

          if (phoneNumber != null) {
            val res = callOut(phoneNumber, videoCall ?: false)
            if (res) {
              result.success(null)
            } else {
              result.error("CALL FAILED", "Không thể thực hiện cuộc gọi", null)
            }
          }
        }
        "hangUp" -> {
          val res = hangUp()
          if (res) {
            result.success(null)
          } else {
            result.error("HANGUP FAILED", "Không thể kết thúc cuộc gọi", null)
          }
        }
        "answerCall" -> {
          val res = answerCall()
          if (res) {
            result.success(null)
          } else {
            result.error("ANSWER_CALL FAILED", "Không thể trả lời cuộc gọi", null)
          }
        }
        "rejectCall" -> {
          val res = rejectCall()
          if (res) {
            result.success(null)
          } else {
            result.error("REJECT_CALL FAILED", "Không thể từ chối cuộc gọi", null)
          }
        }
        "hold" -> {
          val res = hold()
          if (res) {
            result.success(null)
          } else {
            result.error("HOLD FAILED", "Không thể giữ máy", null)
          }
        }
        "unHold" -> {
          val res = unHold()
          if (res) {
            result.success(null)
          } else {
            result.error("UN_HOLD FAILED", "Không thể tiếp tục giữ máy", null)
          }
        }
        "speakerOn" -> {
          PortsipService.shared?.speakerOn()
          result.success(null)
        }
        "speakerOff" -> {
          PortsipService.shared?.speakerOff()
          result.success(null)
        }
        "microphoneOn" -> {
          PortsipService.shared?.turnOnMicrophone()
          result.success(null)
        }
        "microphoneOff" -> {
          PortsipService.shared?.turnOffMicrophone()
          result.success(null)
        }
        "frontCamera" -> {
          PortsipService.shared?.switchToFrontCamera()
          result.success(null)
        }
        "backCamera" -> {
          PortsipService.shared?.switchToBackCamera()
          result.success(null)
        }
        "cameraOn" -> {
          PortsipService.shared?.turnOnCamera()
          result.success(null)
        }
        "cameraOff" -> {
          PortsipService.shared?.turnOffCamera()
          result.success(null)
        }
        "finishDisposingCamera" -> {
            val intent = Intent(this, VideoCallActivity::class.java)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            startActivity(intent)
        }
        else -> {
          result.notImplemented()
        }
      }
    }
  }
  
  
  private fun registerPortsip(username: String, password: String, domain: String, server: String): Int {

    val sharedPref = getSharedPreferences("CallManager", MODE_PRIVATE) ?: return 0
    with(sharedPref.edit()) {
      putString(getString(R.string.call_manager_ps_extension), username)
      putString(getString(R.string.call_manager_ps_password), password)
      putString(getString(R.string.call_manager_ps_domain), domain)
      putString(getString(R.string.call_manager_ps_server), server)
      apply()
    }

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

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
      if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(
          activity, Manifest.permission.BLUETOOTH_CONNECT)
      ) {
        ActivityCompat.requestPermissions(
          activity,
          arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
          0
        )
      }
    }
    
    return 0

  }
  
  private fun unregisterPortsip() {
    if (PortsipService.shared != null) {
      PortsipService.shared!!.unregisterPortsip()
    }
  }
  
  private fun callOut(phoneNumber: String, videoCall: Boolean = false): Boolean {
    return PortsipService.shared?.callOut(phoneNumber, videoCall) ?: false
  }
  
  private fun hangUp(): Boolean {
    return PortsipService.shared?.hangUp() ?: false
  }
  
  private fun answerCall(): Boolean {
    return PortsipService.shared?.answerCall() ?: false
  }
  
  private fun rejectCall(): Boolean {
    return PortsipService.shared?.rejectCall() ?: false
  }
  
  private fun hold(): Boolean {
    return PortsipService.shared?.hold() ?: false
  }
  
  private fun unHold(): Boolean {
    return PortsipService.shared?.unHold() ?: false
  }
  
}

```

Cấu hình đối với Flutter UI

const CHANNEL = 'name_channel';
const platform = const MethodChannel(CHANNEL);

static Future<void> portsipRegister() async {
    try {
      await platform.invokeMethod("registerPortsip", {
        'username': extensionNumber,
        'password': extensionPassword,
        'domain': tenantDomain,
        'sipServer': sipServer
      });
    } on PlatformException catch (e) {
       throw e;
    }
  }
  

static Future<void> callOut(String phoneNumber, bool isVideoCall) async {
    try {
      await platform.invokeMethod("callOut", {
        'phoneNumber': phoneNumber, 'videoCall': isVideoCall
      });
    } on PlatformException catch(e) {
      throw e;
    }
  }
  
 static Future<void> portsipHangUp() async {
    try {
      await platform.invokeMethod("hangUp");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> portsipAnswer() async {
    try {
      await platform.invokeMethod("answerCall");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> portsipReject() async {
    try {
     await platform.invokeMethod("rejectCall");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> portsipHold() async {
    try {
      await platform.invokeMethod("hold");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> portsipUnHold() async {
    try {
      await platform.invokeMethod("unHold");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> portsipSpeakerOn() async {
    try {
      await platform.invokeMethod("speakerOn");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> portsipSpeakerOff() async {
    try {
      await platform.invokeMethod("speakerOff");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> microphoneOn() async {
    try {
      await platform.invokeMethod("microphoneOn");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> microphoneOff() async {
    try {
      await platform.invokeMethod("microphoneOff");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> switchToFrontCamera() async {
    try {
      await platform.invokeMethod("frontCamera");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> switchToBackCamera() async {
    try {
      await platform.invokeMethod("backCamera");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> cameraOn() async {
    try {
      await platform.invokeMethod("cameraOn");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static Future<void> cameraOff() async {
    try {
      await platform.invokeMethod("cameraOff");
    } on PlatformException catch (e) {
      throw e;
    }
  }

  static void hookNativeEvents(BuildContext context) {

    platform.setMethodCallHandler((call) async {
      try {
        switch (call.method) {
          case 'onRegisterFailure':

            String code = jsonDecode(call.arguments)["code"] ?? "";

            if (code != callsReader.portsipRegisterFailureCode) {
              //todo if onRegisterFailure
            }

            return;

          case 'callIn':
            String? caller = jsonDecode(call.arguments)["caller"];
            String callingNumber = caller!.split('sip:')[1].split('@')[0];

            bool? isVideoCall = jsonDecode(call.arguments)["isVideo"];

            portsipCallIn(
                phoneNumber: callingNumber,
                isVideoCall: isVideoCall == null ? false : isVideoCall
            );

            return;

          case 'callAnswered':
            if (call.arguments != null && call.arguments.length > 0) {
              String? caller = jsonDecode(call.arguments)["caller"];
            }

            return;

          case 'callEnded':
            //code here

            return;

          case 'sipMessageResponse':
            if (call.arguments == null || call.arguments.length == 0) {
              return;
            }

            String sipMessage = jsonDecode(call.arguments)['sipMessage'];
            String _splitMessage = sipMessage.split('X-Session-Id:')[1];
            if (_splitMessage.isEmpty) {
              return;
            }

            String xSessionId = _splitMessage.substring(0, 18);

            return;

          case 'disposeCamera':
            await callsReader.disposeCamera();

            try {
              await platform.invokeMethod("finishDisposingCamera");
            } on PlatformException catch (e) {
              
            }

            return;

        }
      } catch (e) {
        hookNativeEvents(context);
      }
      return;
    });
  }

Tham khảo thêm các function tại đây:

Tham khảo thêm các function tại đây:

https://docs.etelecom.vn/tong-dai/sdk/ios
https://docs.etelecom.vn/tong-dai/sdk/android
Platform channels