1
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
版权声明: 本文为 InfoQ 作者【anyRTC开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/d4ba533c7ab99e0740e72706e】。文章转载请联系作者。
anyRTC开发者
关注
实时交互,万物互联! 2020.08.10 加入
实时交互,万物互联,全球实时互动云服务商领跑者!
评论