preface
Clubhouse is a new social networking application that provides real-time audio chat and interaction, creating the possibility for users to break down the barriers of information dissemination and interpersonal connection caused by social barriers. Clubhouse, often nicknamed “Silicon Valley’s Hottest start-up,” markets itself as an “exclusive” and “alternative” social network that attracts a variety of celebrities and people who just want to talk to each other.
App Store download address
Github open source download address
The development environment
- Development tools: Xcode12 real machine run
- Development language: Swift
- The SDK: ARtcKit_iOS
Results show
The core framework
platform :ios, '9.0'
use_frameworks!
target 'anyHouse-iOS' do#anyRTC pod 'ARtcKit_iOS', '~ > 4.1.4.1'#anyRTC Real-Time messaging pod'ARtmKit_iOS', '~ > 1.0.1.4'
end
Copy the code
Project file directory structure
Functional Directory:
Main:
①ARMainViewController: home page, room list;
②ARMineViewController: mine, including change nickname, privacy protocol, version information, etc.
(3) ARCreateRoomViewController: create room, including creating public/private rooms, add topics.
Audio:
①ARAudioViewController: voice room, including voice chat, up and down mic and other functions;
②ARMicViewController: request list;
ARReportViewController: report function.
Detailed description of some functional modules of the project
Login, my, home page
- Home page
override func viewDidLoad(a) {
super.viewDidLoad()
// Do any additional setup after loading the view.
let avatar = Int(UserDefaults.string(forKey: .avatar) ?? "1")! - 1
avatarButton.setImage(UIImage(named: headListArr![avatar] as! String), for: .normal)
let arr = UserDefaults.standard.array(forKey: blacklistIdentifier)
arr?.count ?? 0 > 0 ? (blackList.addObjects(from: arr!)) : nil
tableView.tableFooterView = UIView()
tableView.separatorStyle = .none
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 120
tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: {
[weak self() - >Void in
guard let weakself = self else {return}
weakself.index = 1
weakself.requestRoomList()
})
}
@objc func createPlaceholder(a) {
placeholderView.showPlaceholderView(self.tableView, placeholderImageName: "icon_add", placeholderTitle: "Try a drop-down refresh or create a room.") {
self.tableView.mj_header?.beginRefreshing()
}
placeholderView.backgroundColor = UIColor.clear
}
Copy the code
Create rooms and add topics
- Create room logic:
@IBAction func didClickTopicButton(_ sender: Any) {
passwordTextField.resignFirstResponder()
let alertVc = ARAlertTextViewController(title: "Add topic\n ", message: "Like funny things that happen around you.", preferredStyle: .alert)
alertVc.updateTextView(text: topic)
let cancelAction = UIAlertAction (title: "Cancel" , style: .cancel , handler: nil )
let okAction = UIAlertAction (title: "Set the topic" , style: . default , handler: {
action in
if !self.stringAllIsEmpty(string: alertVc.textView.text) {
self.topic = alertVc.textView.text ?? ""
self.updateTopic()
}
})
alertVc.addAction(cancelAction)
alertVc.addAction(okAction)
present(alertVc, animated: true, completion: nil)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) {
alertVc.textView.becomeFirstResponder()
}
}
func updateTopic(a) {
if isPrivate = = 0 {
(topic.count = = 0) ? (topicLabel.text = publicText) : (topicLabel.text = String(format: "% @ :" % @ "", publicText,topic))
} else {
(topic.count = = 0) ? (topicLabel.text = passwordText) : (topicLabel.text = String(format: "% @ :" % @ "", passwordText,topic))
}
}
@IBAction func didClickButton(_ sender: UIButton) {
if sender.tag ! = isPrivate {
isPrivate = sender.tag
passwordTextField.resignFirstResponder()
updateTopic()
if isPrivate = = 0 {
/ / public
passwordView.isHidden = true
padding.constant = 0
publicButton.backgroundColor = UIColor(hexString: "#DFE2EE")
passwordButton.backgroundColor = UIColor.white
} else {
/ / private
passwordView.isHidden = false
padding.constant = 47
passwordButton.backgroundColor = UIColor(hexString: "#DFE2EE")
publicButton.backgroundColor = UIColor.white
}
}
}
Copy the code
- Rewrite UIAlertController implementation to add topic:
class ARAlertTextViewController : UIAlertController.UITextViewDelegate {
public var textView : UITextView!
private var tipLabel: UILabel!
override init(nibName nibNameOrNil: String? .bundle nibBundleOrNil: Bundle?). {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
let contentView = UIView(a)let controller = UIViewController()
controller.view = contentView
textView = UITextView()
textView.delegate = self
textView.layer.masksToBounds = true
textView.layer.cornerRadius = 5
contentView.addSubview(textView)
textView.snp.makeConstraints({ (make) in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 15, bottom: 16, right: 15))
})
tipLabel = UILabel.init()
tipLabel.text = "60 characters left to enter."
tipLabel.textColor = UIColor(hexString: "# 999999")
tipLabel.font = UIFont.init(name: "PingFang SC", size: 12)
tipLabel.textAlignment = .center
textView.addSubview(tipLabel)
tipLabel.snp.makeConstraints({ (make) in
make.bottom.equalTo(textView.snp_bottom).offset(80)
make.centerX.equalToSuperview()
make.width.equalTo(100)
make.height.equalTo(15)})//super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.setValue(controller, forKey: "contentViewController")}func updateTextView(text: String!). {
textView.text = text
tipLabel.text = String(format: "Left to enter %d characters".60 - text.count)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")}func textViewDidChange(_ textView: UITextView) {
if textView.text?.count ?? 0 > 60 {
textView.text = String(textView.text.prefix(60))
}
tipLabel.text = String(format: "Left to enter %d characters".60 - textView.text.count)
}
}
Copy the code
Voice room, interactive connection
- Core code:
func initializeEngine(a) {
// init ARtcEngineKit
rtcKit = ARtcEngineKit.sharedEngine(withAppId: UserDefaults.string(forKey: .appId)!, delegate: self)
rtcKit.setAudioProfile(.musicHighQuality, scenario: .gameStreaming)
// Enable audio AI noise reduction
let dic1: NSDictionary = ["Cmd": "SetAudioAiNoise"."Enable": 1]
rtcKit.setParameters(getJSONStringFromDictionary(dictionary: dic1))
rtcKit.setChannelProfile(.liveBroadcasting)
if infoModel!.isBroadcaster {
rtcKit.setClientRole(.broadcaster)
}
rtcKit.enableAudioVolumeIndication(500, smooth: 3, report_vad: true)
//init ARtmKit
rtmEngine = ARtmKit.init(appId: UserDefaults.string(forKey: .appId)!, delegate: self)
rtmEngine.login(byToken: infoModel?.rtmToken, user: UserDefaults.string(forKey: .uid) ?? "0") {[weak self](errorCode) in
self?.rtmChannel = self?.rtmEngine.createChannel(withId: (self?.infoModel?.roomId)!, delegate: self)
self?.rtmChannel?.join(completion: { (errorCode) in}}})Copy the code
- Audio test
Callbacks that indicate who is talking in the channel, the volume of the speaker, and whether the local user is talking
func rtcEngine(_ engine: ARtcEngineKit.reportAudioVolumeIndicationOfSpeakers speakers: [ARtcAudioVolumeInfo].totalVolume: Int) {
for speakInfo in speakers {
if speakInfo.volume > 3 {
for index in 0..<modelArr[0].count {
let micModel = modelArr[0][index]
if speakInfo.uid = = micModel.uid || (speakInfo.uid = = "0" && micModel.uid = = UserDefaults.string(forKey: .uid)){
let indexPath: NSIndexPath = NSIndexPath(row: index, section: 0)
let cell: ARAudioViewCell? = collectionView.cellForItem(at: indexPath as IndexPath) as? ARAudioViewCell
cell?.startAnimation()
break
}
}
}
}
}
Copy the code
- Fluctuation of wheat
private func becomBroadcaster(role: ARClientRole) {
// Switch roles
rtcKit.setClientRole(role)
if role = = .audience {
/ / the wheat
audioButton.isHidden = true
audioButton.isSelected = false
micButton.isHidden = false
micButton.isSelected = false
rtcKit.enableLocalAudio(true)
for index in 0..<modelArr[0].count {
let micModel = modelArr[0][index]
if micModel.uid = = UserDefaults.string(forKey: .uid) {
modelArr[0].remove(at: index)
modelArr[1].append(micModel)
collectionView.reloadData()
break}}Drop.down("You have become an audience.", state: .color(UIColor(hexString: "#4BAB63")), duration: 1)}else {
/ / on wheat
audioButton.isHidden = false
micButton.isHidden = true}}Copy the code
Protocol, shielding, reporting functions
- In order to cope with the Apple audit mechanism, the protocol, shielding, reporting and other functional modules are added.
RTM signaling
Json: key =action value Int
For example: {“action”:1} toID: send object
Key | Value | instructions | http |
---|---|---|---|
action userName avatar |
1 userName(String) 1(Int) |
Show of hands (toID as moderator) | updateUserStatus status =1 |
action | 2 | Invite the audience to the stage (toID is the audience) | updateUserStatus status =-1 |
action | 3 userName(String) |
Audience refused invitation (toID as host) | updateUserStatus status = 0 |
action userName |
4 | Agree to invite (toID as host) | updateUserStatus status =2 |
action | 5 | The moderator turns off the speaker’s microphone (toID for the listener) | |
action | 6 | The moderator sets the speaker as the listener (down) (toID is the listener) | updateUserStatus status =0 |
action | 7 | Cancel the show of hands (toID as moderator) | updateUserStatus status = 0 |
action | 8 | Moderator leaves normally (Sending channel messages) | leaveRoom |
action userName avatar |
9 userName(String) 1(Int) |
Join RTM channel to send personal information (Sending channel messages) |
Join channel Sends channel messages for others to display
{“avatar”:1,userName:”lili”}
conclusion
This project has not completely restored the ClubHouse, and there are still some bugs and function points to be improved. For your information only, welcome to fork. You are welcome to point out issues if you have any shortcomings.
Finally, post the Github open source download address. If you feel good, I hope to click star~