写点什么

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

作者:anyRTC开发者
  • 2022 年 4 月 27 日
  • 本文字数:6655 字

    阅读完需:约 22 分钟

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 官网(https://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;
复制代码

示例代码

效果展示(注册登录)
代码实现
        /// 检查是否登录    /// - 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 开源下载地址。


Github开源下载地址



发布于: 刚刚阅读数: 3
用户头像

实时交互,万物互联! 2020.08.10 加入

实时交互,万物互联,全球实时互动云服务商领跑者!

评论

发布
暂无评论
IOS技术分享| ARCallPlus 开源项目(二)_ios_anyRTC开发者_InfoQ写作社区