Appearance
Hướng dẫn sử dụng
Hướng dẫn sử dụng VBot iOS SDK.
Cấu hình dự án
Bật Voip trong dự án Xcode
Chọn Xcode Project → Capabilities
Thêm Background Modes và Push Notifications
Ở Background Modes, Bật Audio, AirPlay, and Picture in Picture | Voice over IP | Background Fetch | Remote Notifications
Mở tệp info.plist và thêm key sau
swift
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is necessary to be able to make calls.</string>Lưu ý:
Khi khởi chạy dự án mà Xcode trả về lỗi
“Sandbox: rsync.samba (13105) deny(1) file-write-create”
Thực hiện chỉnh sửa sau:
Trong Build Settings, tìm User Script Sandboxing: Chọn No
Sử dụng SDK
AppDelegate.swift
Khởi tạo
Trong hàm application didFinishLaunchingWithOptions, gọi hàm khởi tạo VBotPhone và khởi tạo voipRegistry:
swift
// Khởi tạo đầy đủ với các cấu hình tùy chọn
let config = VBotConfig(
supportPopupCall: false, // Mặc định: false. Cho phép hiển thị popup cuộc gọi.
includesCallsInRecents: true, // Mặc định: false. Cho phép lưu lịch sử cuộc gọi vào nhật ký cuộc gọi hệ thống qua CallKit.
iconTemplateImageData: UIImage(named: "callkit-icon")?.pngData(), // Ảnh icon hiển thị trên giao diện CallKit.
environment: .production, // Môi trường API. Mặc định là .production.
customBaseUrl: nil // URL API tùy chỉnh nếu muốn cấu hình thủ công (ghi đè cấu hình môi trường).
)
VBotPhone.sharedInstance.setup(with: config)
voipRegistry = PKPushRegistry(queue: .main)
voipRegistry!.desiredPushTypes = [.voIP]
voipRegistry!.delegate = selfTrong đó:
- supportPopupCall: Cho phép hoặc từ chối hiển thị popup cuộc gọi của SDK.
- includesCallsInRecents: Hiển thị lịch sử cuộc gọi trong app Điện thoại của iOS.
- iconTemplateImageData: Icon được hiển thị trong màn hình cuộc gọi CallKit.
- environment: Chỉ định môi trường kết nối API.
- customBaseUrl: Đường dẫn API URL tùy chọn nếu bạn muốn kết nối trực tiếp đến endpoint riêng. Ghi đè cấu hình từ
environment.
iOS History Call handle
Nếu trong VBotConfig có set includesCallsInRecents là true thì trong app Phone của iOS sẽ hiển thị lịch sử cuộc gọi.
Khi khách hàng tap vào 1 lịch sử cuộc gọi, app sẽ mở ra. Hãy dùng code sau để handle
swift
import Intents
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
switch userActivity.activityType {
case "INStartAudioCallIntent":
return handleStartCallIntent(
INStartAudioCallIntent.self,
userActivity: userActivity,
contacts: \.contacts,
)
case "INStartCallIntent":
if #available(iOS 13.0, *) {
return handleStartCallIntent(
INStartCallIntent.self,
userActivity: userActivity,
contacts: \.contacts,
)
} else {
return false
}
default:
return false
}
}
private func handleStartCallIntent<T: INIntent>(
_ intentType: T.Type,
userActivity: NSUserActivity,
contacts: KeyPath<T, [INPerson]?>,
) -> Bool {
let intent = userActivity.interaction?.intent
guard let intent = intent as? T else {
return false
}
if let person = intent[keyPath: contacts]?.first {
let displayName = person.displayName
let result = VBotPhone.sharedInstance.getCallIntentFromUserActivity(displayName)
// result này chứa name và number của người gọi. Từ đây app có thể dùng hàm startOutgoingCall để thực hiện cuộc gọi đi
}
return true
}Cuộc gọi đến
Thêm hàm lắng nghe sự kiện của PushKit (Thông báo cuộc gọi) và AppDelegate
swift
import PushKit
extension AppDelegate: PKPushRegistryDelegate {
nonisolated func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
guard let token = registry.pushToken(for: .voIP) else {
// Không lấy được token
return
}
let pushToken = token.map { String(format: "%.2hhx", $0) }.joined()
// Lưu token này lại, dùng khi connect account
// savePushToken(pushToken)
}
nonisolated func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
// Nếu payload có loại là .voIP thì gọi hàm startIncomingCall để khởi tạo cuộc gọi
if type == .voIP {
VBotPhone.sharedInstance.startIncomingCall(
payload: payload,
completion: completion
)
} else {
completion()
}
}
}Connect SDK
SWIFT
VBotPhone.sharedInstance.connect(token: token, pushkitToken: pushKitToken) { displayName, error in
if let error = error as NSError? {
// login error
return
}
// login successful
}| SIP | endedBy |
|---|---|
| 400–402, 405–408, 412–413, 416, 500 | server |
| 403, 409, 411, 486, 603 | callee |
| 404, 410, 414, 480, 502 | carrier |
| 415 | system |
| 487 | caller |
Trong đó:
- token: Token SDK của tài khoản VBot
- pushkitToken: Pushkit token đã lưu từ bước trước
- error: Lỗi trả về khi đăng nhập không thành công
Disconnect SDK
SWIFT
VBotPhone.sharedInstance.disconnect { error in
if let error = error as NSError? {
// logout error
return
}
// logout error
}Lấy danh sách hotline
SWIFT
VBotPhone.sharedInstance.getHotlines { hotlines, error in
if error != nil {
// get hotline error
return
}
// get hotline successful
}Gọi đi
swift
VBotPhone.sharedInstance.startOutgoingCall(
displayName: "Nguyễn Văn A",
number: "0901234567",
hotline: "1900xxxx",
externalCallId: "ext-call-123" // Mã định danh cuộc gọi từ hệ thống ngoài (Tùy chọn)
) { success, error in
if success {
// Bắt đầu cuộc gọi đi thành công
} else {
// Lỗi khởi tạo cuộc gọi đi (lỗi nằm trong biến error)
}
}Trong đó:
displayName: Tên người nhận hiển thị trên CallKit.
number: Số điện thoại cần gọi.
hotline: Số hotline sử dụng.
externalCallId (tùy chọn): Mã cuộc gọi từ hệ thống bên ngoài, dùng để liên kết dữ liệu cuộc gọi.
Giá trịexternalCallIdphải thỏa các điều kiện sau:- Độ dài tối đa: 32 ký tự.
- Ký tự hợp lệ: chữ thường (
a–z) và chữ số (0–9). - Không chứa ký tự đặc biệt, chữ in hoa hoặc khoảng trắng.
Gác máy
SWIFT
// Tắt call
VBotPhone.sharedInstance.endCall { error in
if let error = error as NSError? {
return
}
}Các hành động khác trong cuộc gọi
SWIFT
// Bật tắt mic
VBotPhone.sharedInstance.muteCall()
// Bật tắt loa ngoài
VBotPhone.sharedInstance.onOffSpeaker()Lắng nghe các sự kiện (Delegate)
Đăng ký nhận các sự kiện cuộc gọi:
swift
// Đăng ký nhận delegate
VBotPhone.sharedInstance.addDelegate(self)
// Hủy đăng ký nhận delegate
deinit {
VBotPhone.sharedInstance.removeDelegate(self)
}Các delegate method được cung cấp bởi VBotPhoneDelegate:
swift
protocol VBotPhoneDelegate {
// Trạng thái cuộc gọi thay đổi
func callStateChanged(state: VBotCallState)
// Cuộc gọi đi đã bắt đầu
func callStarted()
// Cuộc gọi đến được chấp nhận (Khi user chọn chấp nhận cuộc gọi)
func callAccepted()
// Cuộc gọi kết thúc, đi kèm nguyên nhân và bên kết thúc cuộc gọi
func callEnded(reason: VBotEndCallReason, endedBy: VBotCallEndParty)
// Trạng thái quyền truy cập microphone
func microphonePermission(status: AVAudioSession.RecordPermission)
// Trạng thái tắt/mở âm microphone thay đổi
func callMuteStateDidChange(muted: Bool)
// Nhận externalCallId (chỉ gọi 1 lần duy nhất khi bắt đầu cuộc gọi có chứa ID này, nếu không nil hoặc không rỗng)
func didReceiveExternalCallId(_ externalCallId: String)
// Yêu cầu hiển thị giao diện cuộc gọi
func showCallVC()
// Yêu cầu quay lại giao diện cuộc gọi
func returnToCallVC()
// Yêu cầu ẩn giao diện cuộc gọi
func hideCallVC()
// Mất kết nối mạng
func networkIsUnreachable()
// Kết nối mạng thay đổi
func internetConnectionChanged()
}Xem thêm
VBotEndCallReason
Enum nguyên nhân kết thúc cuộc gọi, nhận qua callEnded(reason:endedBy:). Kiểu Int (@objc), truy cập giá trị số qua reason.rawValue, tên ổn định qua reason.key và mô tả qua reason.description. endedBy là VBotCallEndParty (caller, callee, system, server, carrier, unknown). Callback cũ callEnded(reason:) vẫn tương thích ngược.
| Case | rawValue | Ý nghĩa |
|---|---|---|
normaly | 1000 | Cuộc gọi kết thúc bình thường |
busy | 1001 | Máy bận |
timeOut | 1004 | Hết thời gian chờ kết nối |
noPushToken | 1018 | Chưa đăng ký push notification |
notReadyForStartCall | 2002 | Chưa sẵn sàng để gọi đi / khởi tạo không thành công |
invalidPhoneNumber | 2004 | Số điện thoại không hợp lệ |
noDataFromServer | 2005 | Không có dữ liệu từ máy chủ |
endCallBeforeServerStartCall | 2006 | Cuộc gọi kết thúc khi chưa kết nối |
noCallCreated | 2007 | Lỗi khi khởi tạo cuộc gọi |
dataInvalid | 2008 | Dữ liệu không hợp lệ |
noVBotUser | 2009 | Không tìm thấy thông tin tài khoản |
authenticatedFailed | 2010 | Xác thực thất bại |
anotherCallInProgress | 2011 | Đang có cuộc gọi khác |
decline | 2013 | Từ chối cuộc gọi |
temporarilyUnavailable | 2014 | Không liên lạc được |
reportNewIncomingCallFailed | 2016 | Không thể tiếp nhận cuộc gọi đến |
alertDataNotFound | 2017 | Dữ liệu thông báo không hợp lệ |
setupEndpointFailed | 2019 | Khởi tạo dịch vụ gọi thất bại |
requestCallKitActionFailed | 2020 | Thực thi hành động cuộc gọi thất bại |
noAccount | 2022 | Tài khoản chưa được cấu hình |
incomingCallTimeout | 2023 | Cuộc gọi đến hết thời gian chờ |
incorrectInformation | 2024 | Thông tin không chính xác |
unauthenticated | 2025 | Chưa xác thực |
insufficientBalance | 2026 | Số dư không đủ |
recipientBlocksCalls | 2027 | Người nhận chặn cuộc gọi |
destinationNotFound | 2028 | Không tìm thấy số đích |
callIntervalNotAllowed | 2029 | Không được phép gọi trong khung giờ này |
memberNotActivated | 2030 | Thành viên chưa kích hoạt |
memberNotInProject | 2031 | Thành viên không thuộc dự án |
doNotDisturb | 2032 | Không làm phiền |
destinationGone | 2033 | Số đích không còn tồn tại |
recipientAbsent | 2034 | Người nhận vắng mặt |
packageExpired | 2035 | Gói cước đã hết hạn |
hotlineTelcoNotSupported | 2036 | Hotline không hỗ trợ nhà mạng |
telcoNotFound | 2037 | Không tìm thấy nhà mạng |
invalidParameter | 2038 | Tham số không hợp lệ |
projectExpired | 2039 | Dự án đã hết hạn |
callerCanceled | 2040 | Người gọi đã hủy |
connectionError | 2041 | Lỗi kết nối |
transmissionError | 2042 | Lỗi đường truyền |
unknownError | 9996 | Lỗi chưa xác định |
microphonePermissionDenied | 9999 | Chưa cấp quyền microphone |
swift
func callEnded(reason: VBotEndCallReason, endedBy: VBotCallEndParty) {
switch reason {
case .normaly:
// Cuộc gọi kết thúc bình thường
break
case .busy, .decline, .temporarilyUnavailable:
// Đầu bên kia không nhận cuộc gọi
break
default:
print("Cuộc gọi kết thúc: \(reason.key), bởi \(endedBy.key)")
}
}Bảng mã trên cũng dùng cho lỗi trả về từ các hàm SDK: lỗi là NSError với code trùng rawValue tương ứng.
swift
VBotPhone.sharedInstance.startOutgoingCall(
displayName: "Nguyễn Văn A",
number: "0901234567",
hotline: "1900xxxx"
) { success, error in
guard let error = error else { return }
if error.code == VBotEndCallReason.anotherCallInProgress.rawValue {
// Đang có cuộc gọi khác
}
print("Gọi đi thất bại: \(error.localizedDescription)")
}Đối chiếu với Android SDK
Android SDK dùng enum VBotEndCallReason với tên case và mã số giống hoàn toàn bảng trên, nên logic xử lý mã lỗi dùng chung được cho cả hai nền tảng.
Sử dụng với Objective-C
SDK tương thích hoàn toàn để sử dụng từ dự án Objective-C.
1. Import Module
Import module trong tệp .m hoặc .mm của bạn:
objc
@import VBotPhoneSDK;2. Khởi tạo SDK trong AppDelegate
objc
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSData *iconData = UIImagePNGRepresentation([UIImage imageNamed:@"callkit-icon"]);
VBotConfig *config = [[VBotConfig alloc] initWithSupportPopupCall:NO
includesCallsInRecents:YES
iconTemplateImageData:iconData
environment:VBotEnvironmentProduction
customBaseUrl:nil];
[[VBotPhone sharedInstance] setupWith:config];
return YES;
}3. Thực hiện cuộc gọi đi (Outgoing Call)
objc
[[VBotPhone sharedInstance] startOutgoingCallWithDisplayName:@"Nguyễn Văn A"
number:@"0901234567"
hotline:@"1900xxxx"
externalCallId:@"ext-call-123" // nil nếu không sử dụng
completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"Gọi đi thành công");
} else {
NSLog(@"Gọi đi thất bại với lỗi: %@", error.localizedDescription);
}
}];4. Nhận sự kiện cuộc gọi qua Delegate
objc
@interface ViewController () <VBotPhoneDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[VBotPhone sharedInstance] addDelegate:self];
}
- (void)dealloc {
[[VBotPhone sharedInstance] removeDelegate:self];
}
#pragma mark - VBotPhoneDelegate
- (void)callStateChangedWithState:(enum VBotCallState)state {
NSLog(@"Trạng thái cuộc gọi thay đổi: %ld", (long)state);
}
- (void)callStarted {
NSLog(@"Cuộc gọi đi đã bắt đầu");
}
- (void)callAccepted {
NSLog(@"Cuộc gọi đã được chấp nhận");
}
- (void)callEndedWithReason:(enum VBotEndCallReason)reason
endedBy:(enum VBotCallEndParty)endedBy {
NSLog(@"Cuộc gọi kết thúc: %@, bởi %@", reason.key, endedBy.key);
}
- (void)didReceiveExternalCallId:(NSString *)externalCallId {
NSLog(@"Nhận được External Call ID: %@", externalCallId);
}
@end