IOS技术分享| ARCallPlus 开源项目
Posted anyRTC
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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开源下载地址。
以上是关于IOS技术分享| ARCallPlus 开源项目的主要内容,如果未能解决你的问题,请参考以下文章