IOS技术分享| ARCallPlus 开源项目

Posted anyRTC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IOS技术分享| ARCallPlus 开源项目相关的知识,希望对你有一定的参考价值。

ARCallPlus 简介

ARCallPlus 是 anyRTC 开源的音视频通话项目,同时支持iosandroid、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开源下载地址

以上是关于IOS技术分享| ARCallPlus 开源项目的主要内容,如果未能解决你的问题,请参考以下文章

IOS技术分享| ARCallPlus 开源项目

IOS技术分享| ARCallPlus 开源项目

IOS技术分享| anyLive 开源项目

学习Coding-iOS开源项目日志

Android技术分享| anyLive 开源项目

iOS-一个弹出菜单动画视图开源项目分享