写点什么

IOS 技术分享| ARCall 视频通话重构

发布于: 刚刚

ARCall 简介

ARCall 是 anyRTC 开源的呼叫的示例项目,演示了如何通过 anyRTC 云服务,并配合 anyRTC RTC SDK、anyRTC RTM SDK,快速实现呼叫邀请通话的场景。


  • 一对一视频呼叫。

  • 一对一音频呼叫。

  • 多人音视频通话,最大支持 50 人同时通话。

  • 可运用自采集模块,加载第三方美颜库,实现美颜贴图功能。

  • 可对接第三方推送实现推送功能。

重构内容

九月份我们对 ARCall iOS 端项目进行了重构,开发语言由 Objective-C 变为 Swift,通话界面和逻辑处理进行深度优化,希望能给有需要的开发者带来帮助。

下载体验 & 源码下载

ARCall 开源项目支持多个平台,包含 Android、iOS、Web、uniapp



开发环境

  • 开发工具:Xcode12 真机运行

  • 开发语言:Swift

  • 实现:音视频通话。

项目结构

ARCall 实现了点对点呼叫、多人呼叫邀请,功能包含视频通话、语音通话、多人通话、sip 呼叫、手表呼叫、呼叫中邀请、大小屏切换、悬浮窗、AI 降噪、断网重连等等功能。


示例代码

效果展示
代码实现
    func initializeUI() {        self.navigationItem.leftBarButtonItem = createBarButtonItem(title: "", image: "icon_return_w")        calleeIdLabel.text = "您的呼叫ID:\(localUid)"                let setupButton = UIButton(type: .custom)        setupButton.setImage(UIImage(named: "icon_set"), for: .normal)        setupButton.frame = CGRect.init(x: 0, y: 0, width: 40, height: 40)        setupButton.addTarget(self, action: #selector(setup), for: .touchUpInside)        self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: setupButton)                if callWay == .single {            self.title = "点对点呼叫邀请"            mainCollectionView.removeFromSuperview()        } else {            self.title = "多人呼叫邀请"            titleLabel.text = "请输入对方的ID,可输入多个"        }                for objc in stackView.subviews {            let button = objc as? UIButton            button?.addTarget(self, action: #selector(didClickTextField), for: .touchUpInside)        }                self.view.addSubview(calleeIdTextField)        mainCollectionView.collectionViewLayout = flowLayout    }        @objc func didClickTextField() {        calleeIdTextField.becomeFirstResponder()    }        @objc func limitTextField() {                if calleeIdTextField.text?.count ?? 0 > 4 {            calleeIdTextField.text = String((calleeIdTextField.text?.prefix(4))!)        }        let calleeId = calleeIdTextField.text                let arr = stringToArr(str: calleeId)        for index in 0...3 {            var text = ""            (index < arr.count) ? (text = String(arr[index])) : nil            let button: UIButton = stackView.subviews[index] as! UIButton            button.setTitle(text, for: .normal)        }                if callWay == .single {            // 点对点呼叫            callButton.isEnabled = (calleeId?.count == 4)            callButton.isEnabled ? (callButton.setTitleColor(UIColor(hexString: "#40A3FB"), for: .normal)) : (callButton.setTitleColor(UIColor.lightGray, for: .normal))        } else {            // 多人呼叫            if calleeIdArr.count < 6 {                if calleeId?.count == 4 {                    if calleeIdArr.contains(calleeId!) == false {                        calleeIdArr.append(calleeId!)                        calleeIdTextField.text = ""                        if mainCollectionView.isHidden {                            mainCollectionView.isHidden = false                            changeConstraint(exist: true)                        }                        mainCollectionView.reloadData()                        callButton.isEnabled = true                        callButton.setTitleColor(UIColor(hexString: "#40A3FB"), for: .normal)                                                for index in 0...3 {                            let button: UIButton = stackView.subviews[index] as! UIButton                            button.setTitle("", for: .normal)                        }                    } else {                        showToast(text: "当前ID已存在,请重新输入", image: "icon_warning")                    }                }            }        }    }
复制代码
效果展示
代码实现
    private func acceptCallInvitation() {        // 接受呼叫邀请        if infoModel.callMode == .video {            view.insertSubview(remoteVideo, belowSubview: calledView)            view.insertSubview(localVideo, belowSubview: calledView)                        setVideoEncoderConfiguration()            let videoCanvas = ARtcVideoCanvas()            videoCanvas.view = localVideo.renderView            view.insertSubview(localVideo, belowSubview: calledView)            rtcKit.setupLocalVideo(videoCanvas)        }    }        private func acceptedInvitation(callMode: CallMode) {        // 对方已同意呼叫        if !isCallSucess {            callStateLabel.text = "接听中..."            playCallBell(isOpen: false)            isCallSucess.toggle()            selectCallMode(mode: callMode)            infoModel.callMode = callMode            acceptCallInvitation()            rtcKit.setClientRole(.broadcaster)                        // 兼容异常问题            leaveReason = .drop            dealWithException(time: 10)        }    }        private func switchAudioMode() {        // 切换音频呼叫        windowStatus = .audio        infoModel.callMode = .audio        selectCallMode(mode: .audio)        localVideo.removeFromSuperview()        remoteVideo.removeFromSuperview()                rtcKit.disableVideo()    }        private func selectCallMode(mode: CallMode) {        // 选择呼叫模式        calledView.isHidden = false        callingView.isHidden = true        hangupButton.isHidden = false                let isHidden = (mode == .video)        backView.isHidden = isHidden        switchAudioButton.isHidden = !isHidden        switchCameraButton.isHidden = !isHidden        audioButton.isHidden = isHidden        speakerButton.isHidden = isHidden    }
复制代码
效果展示
代码实现
    private func initializeEngine() {        // init ARtcEngineKit        rtcKit = ARtcEngineKit.sharedEngine(withAppId: AppID, delegate: self)        rtcKit.setChannelProfile(.liveBroadcasting)                switchAINoise()    }        private func joinChannel() {        rtcKit.joinChannel(byToken: nil, channelId: infoModel.channelId, uid: UserDefaults.string(forKey: .uid)) { (channel, uid, elapsed) in            print("joinChannel sucess")        }    }        @objc func switchAINoise() {        // AI 降噪        let dic = ["Cmd": "SetAudioAiNoise", "Enable": Default.bool(forKey: "noise") ? 1: 0] as [String : Any]        rtcKit.setParameters(getJSONStringFromDictionary(dictionary: dic as NSDictionary))    }        private func switchVideoSize() {        // 大小屏切换        if isCallSucess {            let frame = localVideo.frame            localVideo.frame = remoteVideo.frame            remoteVideo.frame = frame                        if localVideo.frame.width == ARScreenWidth {                view.insertSubview(localVideo, belowSubview: calledView)                view.insertSubview(remoteVideo, belowSubview: calledView)            } else {                view.insertSubview(remoteVideo, belowSubview: calledView)                view.insertSubview(localVideo, belowSubview: calledView)            }        }    }
复制代码
效果展示
代码实现
// MARK - videoLayout
extension ARGroupVideoController { func videoLayout() { videoArr.count <= 1 ? destroyGroupVc() : nil let count = videoArr.count let padding: CGFloat = 1.0 var warpCount: CGFloat = 2.0 let rate: CGFloat = 1.0 if count > 4 { if count <= 9 { warpCount = 3.0 } else if count <= 16 { warpCount = 4.0 } else { warpCount = 5.0 } } let itemWidth: CGFloat = (ARScreenWidth - padding * (warpCount - 1))/warpCount let itemHeight: CGFloat = itemWidth * rate let index = ceil(CGFloat(videoArr.count)/warpCount) containerConstraint.constant = itemHeight * index + (index - 1) * padding var lastView: UIView! for (index, object) in videoArr.enumerated() { let video = object as UIView containerView.insertSubview(video, at: 0) let rowCount: NSInteger = videoArr.count % NSInteger(warpCount) == 0 ? videoArr.count / NSInteger(warpCount) : videoArr.count / NSInteger(warpCount) + 1 let currentRow: NSInteger = index / NSInteger(warpCount) let currentColumn: NSInteger = index % NSInteger(warpCount) video.snp.remakeConstraints({ (make) in make.width.equalTo(itemWidth) make.height.equalTo(itemHeight)
if (currentRow == 0) { make.top.equalTo(containerView) } if (currentRow == rowCount - 1) { make.bottom.equalTo(containerView) } if currentRow != 0 && (currentRow != rowCount - 1) { if currentColumn == 0 { make.top.equalTo(lastView.snp_bottom).offset(padding) } else { make.bottom.equalTo(lastView.snp_bottom).offset(padding) } } if (currentColumn == 0) { make.left.equalTo(containerView); } if (currentColumn == Int(warpCount) - 1) { make.right.equalTo(containerView) } if currentColumn != 0 && (currentColumn != Int(warpCount) - 1) { make.left.equalTo(lastView.snp_right).offset(padding) } }) lastView = video } }}
复制代码

结束语

针对本次 ARCall-iOS 的重构,因时间有限项目中还存在一些 bug 和待完善的功能点。仅供参考,欢迎大家 fork。有不足之处欢迎大家指出 issues。最后再贴一下 Github开源下载地址


如果觉得不错,希望点个 star~



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

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

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

评论

发布
暂无评论
IOS技术分享| ARCall视频通话重构