IOS技術分享| ARCall視訊通話重構
阿新 • • 發佈:2021-10-28
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~