You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/Session/Calls/CallVC.swift

216 lines
7.7 KiB
Swift

3 years ago
import UIKit
import AVFoundation
import WebRTC
final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, WebSocketDelegate {
private var webSocket: WebSocket?
private let videoCallVC = VideoCallVC()
let videoCapturer: RTCVideoCapturer = RTCCameraVideoCapturer(delegate: CallManager.shared.localVideoSource)
private var messageQueue: [String] = []
private var isConnected = false {
didSet {
let title = isConnected ? "Leave" : "Join"
joinOrLeaveButton.setTitle(title, for: UIControl.State.normal)
}
}
private var currentRoomInfo: RoomInfo?
3 years ago
3 years ago
var isInitiator: Bool {
return currentRoomInfo?.isInitiator == true
3 years ago
}
3 years ago
// MARK: UI Components
private lazy var previewView: UIImageView = {
return UIImageView()
}()
private lazy var containerView: UIView = {
return UIView()
}()
private lazy var joinOrLeaveButton: UIButton = {
3 years ago
let result = UIButton()
result.setTitle("Join", for: UIControl.State.normal)
3 years ago
result.addTarget(self, action: #selector(joinOrLeave), for: UIControl.Event.touchUpInside)
3 years ago
return result
}()
private lazy var roomNumberTextField: UITextField = {
3 years ago
let result = UITextField()
result.set(.width, to: 120)
result.set(.height, to: 40)
result.backgroundColor = UIColor.white.withAlphaComponent(0.1)
return result
3 years ago
}()
private lazy var infoTextView: UITextView = {
3 years ago
let result = UITextView()
result.backgroundColor = UIColor.white.withAlphaComponent(0.1)
return result
3 years ago
}()
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
touch(CallManager.shared)
CallManager.shared.delegate = self
3 years ago
CameraManager.shared.delegate = self
CameraManager.shared.prepare()
addChild(videoCallVC)
containerView.addSubview(videoCallVC.view)
videoCallVC.view.translatesAutoresizingMaskIntoConstraints = false
videoCallVC.view.pin(to: containerView)
view.addSubview(containerView)
containerView.pin(to: view)
3 years ago
view.addSubview(joinOrLeaveButton)
joinOrLeaveButton.translatesAutoresizingMaskIntoConstraints = false
joinOrLeaveButton.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view)
view.addSubview(roomNumberTextField)
roomNumberTextField.translatesAutoresizingMaskIntoConstraints = false
roomNumberTextField.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.left ], to: view)
view.addSubview(infoTextView)
infoTextView.translatesAutoresizingMaskIntoConstraints = false
infoTextView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view)
infoTextView.center(.vertical, in: view)
infoTextView.set(.height, to: 200)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
3 years ago
CameraManager.shared.start()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
3 years ago
CameraManager.shared.stop()
}
// MARK: Interaction
@objc private func joinOrLeave() {
guard let roomID = roomNumberTextField.text, !roomID.isEmpty else { return }
if isConnected {
disconnect()
} else {
isConnected = true
TestCallServer.join(roomID: roomID).done2 { [weak self] info in
guard let self = self else { return }
self.log("Successfully joined room.")
self.currentRoomInfo = info
if let messages = info.messages {
self.handle(messages)
}
let webSocket = WebSocket(url: URL(string: info.wssURL)!)
webSocket.delegate = self
self.webSocket = webSocket
}.catch2 { [weak self] error in
guard let self = self else { return }
self.isConnected = false
self.log("Couldn't join room due to error: \(error).")
SNLog("Couldn't join room due to error: \(error).")
}
roomNumberTextField.resignFirstResponder()
}
}
private func disconnect() {
guard let info = currentRoomInfo else { return }
TestCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in
guard let self = self else { return }
self.log("Disconnected.")
}
let message = [ "type": "bye" ]
guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return }
webSocket?.send(data)
webSocket?.delegate = nil
webSocket = nil
currentRoomInfo = nil
isConnected = false
CallManager.shared.endCall()
}
// MARK: Message Handling
func handle(_ messages: [String]) {
messageQueue.append(contentsOf: messages)
drainMessageQueue()
}
func drainMessageQueue() {
guard isConnected else { return }
for message in messageQueue {
handle(message)
}
messageQueue.removeAll()
CallManager.shared.drainMessageQueue()
}
func handle(_ message: String) {
let signalingMessage = SignalingMessage.from(message: message)
switch signalingMessage {
case .candidate(let candidate):
CallManager.shared.handleCandidateMessage(candidate)
log("Candidate received.")
case .answer(let answer):
CallManager.shared.handleRemoteDescription(answer)
log("Answer received.")
case .offer(let offer):
CallManager.shared.handleRemoteDescription(offer)
log("Offer received.")
case .bye:
disconnect()
default:
break
}
}
3 years ago
// MARK: Streaming
func webSocketDidConnect(_ webSocket: WebSocket) {
3 years ago
guard let info = currentRoomInfo else { return }
log("Connected to web socket.")
let message = [
"cmd": "register",
"roomid": info.roomID,
"clientid": info.clientID
]
guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return }
webSocket.send(data)
3 years ago
if isInitiator {
CallManager.shared.initiateCall().retainUntilComplete()
}
drainMessageQueue()
}
func webSocket(_ webSocket: WebSocket, didReceive message: String) {
3 years ago
log("Received data from web socket.")
handle(message)
3 years ago
CallManager.shared.drainMessageQueue()
}
func webSocketDidDisconnect(_ webSocket: WebSocket) {
webSocket.delegate = nil
3 years ago
log("Disconnecting from web socket.")
}
3 years ago
func callManager(_ callManager: CallManager, sendData data: Data) {
3 years ago
guard let info = currentRoomInfo else { return }
TestCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete()
3 years ago
}
// MARK: Camera
func captureVideoOutput(sampleBuffer: CMSampleBuffer) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let ciImage = CIImage(cvImageBuffer: pixelBuffer)
let image = UIImage(ciImage: ciImage)
DispatchQueue.main.async { [weak self] in
self?.previewView.image = image
}
}
// MARK: Logging
private func log(_ string: String) {
3 years ago
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.infoTextView.text = self.infoTextView.text + "\n" + string
}
}
}