ARCallPlus 简介

ARCallPlus 是 anyRTC 开源的音视频通话项目,一起支撑iOS、Android、Web等渠道。上一篇我们介绍了ARUICalling 开源组件的封装,本篇主要介绍如何经过 ARUICalling 组件来完成音视频通话作用。

源码下载

三行代码、二十分钟运用内构建,完成音视频通话。本项目已上架App Store,欢迎下载体会。

  • GitHub 开源地址
  • App Store 下载地址

开发环境

  • 开发工具:Xcode13 真机运行

  • 开发语言:Objective-C、Swift

项目结构

IOS技术分享| ARCallPlus 开源项目(二)

示例 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>

IOS技术分享| ARCallPlus 开源项目(二)

  • 推送权限(可选)
    IOS技术分享| ARCallPlus 开源项目(二)

过程三:初始化组件

anyRTC 为 App 开发者签发的 App ID。每个项目都应该有一个绝无仅有的 App ID。假如你的开发包里没有 App ID,请从anyRTC官网(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<NSString *> *)userIDs type:(ARUICallingType)type;

示例代码

作用展现(注册登录)

IOS技术分享| ARCallPlus 开源项目(二)

代码完成
        /// 查看是否登录
    /// - 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)
    }
作用展现(主页我的)

IOS技术分享| ARCallPlus 开源项目(二)

代码完成
    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")
    }
作用展现(呼叫通话)

IOS技术分享| ARCallPlus 开源项目(二)

代码完成

    @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开源下载地址。

Github开源下载地址。

IOS技术分享| ARCallPlus 开源项目(二)