IOS技術分享| ARCallPlus 開源專案(二)
ARCallPlus 簡介
ARCallPlus 是 anyRTC 開源的音視訊通話專案,同時支援iOS、Android、Web等平臺。上一篇我們介紹了ARUICalling 開源元件的封裝,本篇主要介紹如何通過 ARUICalling 元件來實現音視訊通話效果。
原始碼下載
三行程式碼、二十分鐘應用內構建,實現音視訊通話。本專案已上架App Store,歡迎下載體驗。
開發環境
-
開發工具:Xcode13 真機執行
-
開發語言:Objective-C、Swift
專案結構
示例 demo 目錄: - LoginViewController (登入) - RegisterViewController (註冊) - MainViewController (首頁) - CallingViewController(發起音視訊通話) - MineViewController (我的)
ARUICalling元件核心 API: - ARUILogin(登入 API) - ARUICalling(通話 API) - ARUICallingListerner(通話回撥)
元件整合
步驟一:匯入 ARUICalling 元件
通過 cocoapods 匯入元件,具體步驟如下:
- 在您的工程 Podfile 檔案同一級目錄下建立 ARUICalling 資料夾。
- 從 Github 下載程式碼,然後將 ARUICalling/iOS/ 目錄下的 Source、Resources 資料夾 和 ARUICalling.podspec 檔案拷貝到您在 步驟1 建立的 ARUICalling 資料夾下。
- 在您的 Podfile 檔案中新增以下依賴,之後執行 pod install 命令,完成匯入。 ```
:path => "指向ARUICalling.podspec的相對路徑"
pod 'ARUICalling', :path => "ARUICalling/ARUICalling.podspec", :subspecs => ["RTC"] ```
步驟二:配置許可權
-
使用音視訊功能,需要授權麥克風和攝像頭的使用許可權。
<key>NSCameraUsageDescription</key> <string>ARCallPlus請求訪問麥克風用於視訊通話?</string> <key>NSMicrophoneUsageDescription</key> <string>ARCallPlus請求訪問麥克風用於語音交流?</string>
-
推送許可權(可選)
步驟三:初始化元件
anyRTC 為 App 開發者簽發的 App ID。每個專案都應該有一個獨一無二的 App ID。如果你的開發包裡沒有 App ID,請從anyRTC官網(http://www.anyrtc.io)申請一個新的 App ID
``` /// 初始化 ARUILogin.initWithSdkAppID(AppID)
/// 登入
ARUILogin.login(localUserModel!) {
success()
print("Calling - login sucess")
} fail: { code in
failed(code.rawValue)
print("Calling - login fail")
}
```
步驟四:實現音視訊通話
/// 發起通話
ARUICalling.shareInstance().call(users: ["123"], type: .video)
/// 通話回撥
ARUICalling.shareInstance().setCallingListener(listener: self)
步驟五:離線推送(可選) 如果您的業務場景需要在 App 的程序被殺死後或者 App 退到後臺後,還可以正常接收到音視訊通話請求,就需要為 ARUICalling 元件增加推送功能,可參考demo中推送邏輯(極光推送為例)。
``` // MARK: - ARUICallingListerner
/// 推送事件回撥
/// @param userIDs 不線上的使用者id
/// @param type 通話型別:視訊\音訊
- (void)onPushToOfflineUser:(NSArray
示例程式碼
效果展示(註冊登入)
程式碼實現
``` /// 檢查是否登入 /// - Returns: 是否存在 func existLocalUserData() -> Bool { if let cacheData = UserDefaults.standard.object(forKey: localUserDataKey) as? Data { if let cacheUser = try? JSONDecoder().decode(LoginModel.self, from: cacheData) { localUserModel = cacheUser localUid = cacheUser.userId
/// 獲取 Authorization
exists(uid: localUid!) {
} failed: { error in
}
return true
}
}
return false
}
/// 查詢裝置資訊是否存在
/// - Parameters:
/// - uid: 使用者id
/// - success: 成功回撥
/// - failed: 失敗回撥
func exists(uid: String, success: @escaping ()->Void,
failed: @escaping (_ error: Int)->Void) {
ARNetWorkHepler.getResponseData("jpush/exists", parameters: ["uId": uid, "appId": AppID] as [String : AnyObject], headers: false) { [weak self] result in
let code = result["code"].rawValue as! Int
if code == 200 {
let model = LoginModel(jsonData: result["data"])
if model.device != 2 {
/// 相容異常問題
self?.register(uid: model.userId, nickName: model.userName, headUrl: model.headerUrl, success: {
success()
}, failed: { error in
failed(error)
})
} else {
self?.localUserModel = model
do {
let cacheData = try JSONEncoder().encode(model)
UserDefaults.standard.set(cacheData, forKey: localUserDataKey)
} catch {
print("Calling - Save Failed")
}
success()
}
} else {
failed(code)
}
} error: { error in
print("Calling - Exists Error")
self.receiveError(code: error)
}
}
/// 初始化裝置資訊
/// - Parameters:
/// - uid: 使用者id
/// - nickName: 使用者暱稱
/// - headUrl: 使用者頭像
/// - success: 成功回撥
/// - failed: 失敗回撥
func register(uid: String, nickName: String, headUrl: String,
success: @escaping ()->Void,
failed: @escaping (_ error: Int)->Void) {
ARNetWorkHepler.getResponseData("jpush/init", parameters: ["appId": AppID, "uId": uid, "device": 2, "headerImg": headUrl, "nickName": nickName] as [String : AnyObject], headers: false) { [weak self]result in
print("Calling - Server init Sucess")
let code = result["code"].rawValue as! Int
if code == 200 {
let model = LoginModel(jsonData: result["data"])
self?.localUserModel = model
do {
let cacheData = try JSONEncoder().encode(model)
UserDefaults.standard.set(cacheData, forKey: localUserDataKey)
} catch {
print("Calling - Save Failed")
}
success()
} else {
failed(code)
}
success()
} error: { error in
print("Calling - Server init Error")
self.receiveError(code: error)
}
}
/// 當前使用者登入
/// - Parameters:
/// - success: 成功回撥
/// - failed: 失敗回撥
@objc func loginRTM(success: @escaping ()->Void, failed: @escaping (_ error: NSInteger)->Void) {
ARUILogin.initWithSdkAppID(AppID)
ARUILogin.login(localUserModel!) {
success()
print("Calling - login sucess")
} fail: { code in
failed(code.rawValue)
print("Calling - login fail")
}
/// 配置極光別名
JPUSHService.setAlias(localUid, completion: { iResCode, iAlias, seq in
}, seq: 0)
}
```
效果展示(主頁我的)
程式碼實現
``` func setupUI() { addLoading() navigationItem.leftBarButtonItem = barButtonItem view.addSubview(bgImageView) view.addSubview(collectionView)
bgImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
func loginRtm() {
ProfileManager.shared.loginRTM { [weak self] in
guard let self = self else { return }
UIView.animate(withDuration: 0.8) {
self.loadingView.alpha = 0
} completion: { result in
self.loadingView.removeFromSuperview()
}
CallingManager.shared.addListener()
print("Calling - LoginRtm Sucess")
} failed: { [weak self] error in
guard let self = self else { return }
if error == 9 {
self.loadingView.removeFromSuperview()
self.refreshLoginState()
}
print("Calling - LoginRtm Fail")
}
}
var menus: [MenuItem] = [
MenuItem(imageName: "icon_lock", title: "隱私條例"),
MenuItem(imageName: "icon_log", title: "免責宣告"),
MenuItem(imageName: "icon_register", title: "anyRTC官網"),
MenuItem(imageName: "icon_time", title: "發版時間", subTitle: "2022.03.10"),
MenuItem(imageName: "icon_sdkversion", title: "SDK版本", subTitle: String(format: "V %@", "1.0.0")),
MenuItem(imageName: "icon_appversion", title: "軟體版本", subTitle: String(format: "V %@", Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! CVarArg))
]
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
view.backgroundColor = UIColor(hexString: "#F5F6FA")
navigationItem.leftBarButtonItem = barButtonItem
tableView.tableFooterView = UIView()
tableView.tableHeaderView = headView
tableView.tableHeaderView?.height = ARScreenHeight * 0.128
tableView.separatorColor = UIColor(hexString: "#DCDCDC")
}
```
效果展示(呼叫通話)
程式碼實現
```
@objc func sendCalling() {
CallingManager.shared.callingType = callType!
let type: ARUICallingType = (callType == .video || callType == .videos) ? .video : .audio
ARUICalling.shareInstance().call(users: selectedUsers!, type: type)
}
class CallingManager: NSObject {
@objc public static let shared = CallingManager()
private var callingVC = UIViewController()
public var callingType: CallingType = .audio
func addListener() {
ARUICalling.shareInstance().setCallingListener(listener: self)
ARUICalling.shareInstance().enableCustomViewRoute(enable: true)
}
}
extension CallingManager: ARUICallingListerner { func shouldShowOnCallView() -> Bool { /// 作為被叫是否拉起呼叫頁面,若為 false 直接 reject 通話 return true }
func callStart(userIDs: [String], type: ARUICallingType, role: ARUICallingRole, viewController: UIViewController?) {
print("Calling - callStart")
if let vc = viewController {
callingVC = vc;
vc.modalPresentationStyle = .fullScreen
let topVc = topViewController()
topVc.present(vc, animated: false, completion: nil)
}
}
func callEnd(userIDs: [String], type: ARUICallingType, role: ARUICallingRole, totalTime: Float) {
print("Calling - callEnd")
callingVC.dismiss(animated: true) {}
}
func onCallEvent(event: ARUICallingEvent, type: ARUICallingType, role: ARUICallingRole, message: String) {
print("Calling - onCallEvent event = \(event.rawValue) type = \(type.rawValue)")
if event == .callRemoteLogin {
ProfileManager.shared.removeAllData()
ARAlertActionSheet.showAlert(titleStr: "賬號異地登入", msgStr: nil, style: .alert, currentVC: topViewController(), cancelBtn: "確定", cancelHandler: { action in
ARUILogin.logout()
AppUtils.shared.showLoginController()
}, otherBtns: nil, otherHandler: nil)
}
}
}
```
推送模組
程式碼實現
```
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
///【註冊通知】通知回撥代理
let entity: JPUSHRegisterEntity = JPUSHRegisterEntity()
entity.types = NSInteger(UNAuthorizationOptions.alert.rawValue) |
NSInteger(UNAuthorizationOptions.sound.rawValue) |
NSInteger(UNAuthorizationOptions.badge.rawValue)
JPUSHService.register(forRemoteNotificationConfig: entity, delegate: self)
///【初始化sdk】
JPUSHService.setup(withOption: launchOptions, appKey: jpushAppKey, channel: channel, apsForProduction: isProduction)
changeBadgeNumber()
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
/// sdk註冊DeviceToken
JPUSHService.registerDeviceToken(deviceToken)
}
extension CallingManager: ARUICallingListerner {
func onPush(toOfflineUser userIDs: [String], type: ARUICallingType) {
print("Calling - toOfflineUser \(userIDs)")
ProfileManager.shared.processPush(userIDs: userIDs, type: callingType)
}
} /// 推送介面 /// - Parameters: /// - userIDs: 離線人員id /// - type: 呼叫型別( 0/1/2/3:p2p音訊呼叫/p2p視訊呼叫/群組音訊呼叫/群組視訊呼叫) func processPush(userIDs: [String], type: CallingType) { ARNetWorkHepler.getResponseData("jpush/processPush", parameters: ["caller": localUid as Any, "callee": userIDs, "callType": type.rawValue, "pushType": 0, "title": "ARCallPlus"] as [String : AnyObject], headers: true) { result in print("Calling - Offline Push Sucess == (result)") } error: { error in print("Calling - Offline Push Error") self.receiveError(code: error) } }
```
結束語
最後,ARCallPlus開源專案中還存在一些bug和待完善的功能點。有不足之處歡迎大家指出issues。最後再貼一下 Github開源下載地址。
- Android技術分享| ViewPager2離屏載入,實現抖音上下視訊滑動
- Android技術分享| Activity 過渡動畫 — 讓切換更加炫酷
- Linux下玩轉nginx系列(七)---nginx如何實現限流功能
- 技術分享| 如何部署安裝分散式序列號生成器系統
- web技術分享| 【地圖】實現自定義的軌跡回放
- 解決方案| 快對講綜合排程系統
- 實時訊息RTM| 多活架構中的資料一致性問題
- Android技術分享| Context淺析
- Android技術分享| Context淺析
- 螢幕共享的實現與應用
- 技術分析| 即時通訊和實時通訊的區別
- IOS技術分享| ARCallPlus 開源專案(二)
- Android技術分享| Android 中部分記憶體洩漏示例及解決方案
- Android技術分享| 安卓3行程式碼,實現整套音視訊通話功能
- 行業分析| 快對講Poc方案的優勢
- Android技術分享|【自定義View】實現Material Design的Loading效果
- IOS技術分享| ARCallPlus 開源專案(一)
- web技術分享| WebRTC控制攝像機平移、傾斜和縮放
- Android技術分享| anyLive 開源專案
- Android技術分享| 【Android 自定義View】多人視訊通話控制元件