mirror of https://github.com/oxen-io/session-ios
Refactor CallVC
parent
4dd218daf6
commit
662fc945e2
@ -1,215 +1,215 @@
|
|||||||
import UIKit
|
//import UIKit
|
||||||
import AVFoundation
|
//import AVFoundation
|
||||||
import WebRTC
|
//import WebRTC
|
||||||
|
//
|
||||||
final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, WebSocketDelegate {
|
//final class CallVC : UIViewController, CameraCaptureDelegate, CallManagerDelegate, WebSocketDelegate {
|
||||||
private var webSocket: WebSocket?
|
// private var webSocket: WebSocket?
|
||||||
private let videoCallVC = VideoCallVC()
|
// private let videoCallVC = VideoCallVC()
|
||||||
let videoCapturer: RTCVideoCapturer = RTCCameraVideoCapturer(delegate: CallManager.shared.localVideoSource)
|
// let videoCapturer: RTCVideoCapturer = RTCCameraVideoCapturer(delegate: CallManager.shared.localVideoSource)
|
||||||
private var messageQueue: [String] = []
|
// private var messageQueue: [String] = []
|
||||||
private var isConnected = false {
|
// private var isConnected = false {
|
||||||
didSet {
|
// didSet {
|
||||||
let title = isConnected ? "Leave" : "Join"
|
// let title = isConnected ? "Leave" : "Join"
|
||||||
joinOrLeaveButton.setTitle(title, for: UIControl.State.normal)
|
// joinOrLeaveButton.setTitle(title, for: UIControl.State.normal)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
private var currentRoomInfo: RoomInfo?
|
// private var currentRoomInfo: RoomInfo?
|
||||||
|
//
|
||||||
var isInitiator: Bool {
|
// var isInitiator: Bool {
|
||||||
return currentRoomInfo?.isInitiator == true
|
// return currentRoomInfo?.isInitiator == true
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// MARK: UI Components
|
// // MARK: UI Components
|
||||||
private lazy var previewView: UIImageView = {
|
// private lazy var previewView: UIImageView = {
|
||||||
return UIImageView()
|
// return UIImageView()
|
||||||
}()
|
// }()
|
||||||
|
//
|
||||||
private lazy var containerView: UIView = {
|
// private lazy var containerView: UIView = {
|
||||||
return UIView()
|
// return UIView()
|
||||||
}()
|
// }()
|
||||||
|
//
|
||||||
private lazy var joinOrLeaveButton: UIButton = {
|
// private lazy var joinOrLeaveButton: UIButton = {
|
||||||
let result = UIButton()
|
// let result = UIButton()
|
||||||
result.setTitle("Join", for: UIControl.State.normal)
|
// result.setTitle("Join", for: UIControl.State.normal)
|
||||||
result.addTarget(self, action: #selector(joinOrLeave), for: UIControl.Event.touchUpInside)
|
// result.addTarget(self, action: #selector(joinOrLeave), for: UIControl.Event.touchUpInside)
|
||||||
return result
|
// return result
|
||||||
}()
|
// }()
|
||||||
|
//
|
||||||
private lazy var roomNumberTextField: UITextField = {
|
// private lazy var roomNumberTextField: UITextField = {
|
||||||
let result = UITextField()
|
// let result = UITextField()
|
||||||
result.set(.width, to: 120)
|
// result.set(.width, to: 120)
|
||||||
result.set(.height, to: 40)
|
// result.set(.height, to: 40)
|
||||||
result.backgroundColor = UIColor.white.withAlphaComponent(0.1)
|
// result.backgroundColor = UIColor.white.withAlphaComponent(0.1)
|
||||||
return result
|
// return result
|
||||||
}()
|
// }()
|
||||||
|
//
|
||||||
private lazy var infoTextView: UITextView = {
|
// private lazy var infoTextView: UITextView = {
|
||||||
let result = UITextView()
|
// let result = UITextView()
|
||||||
result.backgroundColor = UIColor.white.withAlphaComponent(0.1)
|
// result.backgroundColor = UIColor.white.withAlphaComponent(0.1)
|
||||||
return result
|
// return result
|
||||||
}()
|
// }()
|
||||||
|
//
|
||||||
// MARK: Lifecycle
|
// // MARK: Lifecycle
|
||||||
override func viewDidLoad() {
|
// override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
// super.viewDidLoad()
|
||||||
touch(CallManager.shared)
|
// touch(CallManager.shared)
|
||||||
CallManager.shared.delegate = self
|
// CallManager.shared.delegate = self
|
||||||
CameraManager.shared.delegate = self
|
// CameraManager.shared.delegate = self
|
||||||
CameraManager.shared.prepare()
|
// CameraManager.shared.prepare()
|
||||||
addChild(videoCallVC)
|
// addChild(videoCallVC)
|
||||||
containerView.addSubview(videoCallVC.view)
|
// containerView.addSubview(videoCallVC.view)
|
||||||
videoCallVC.view.translatesAutoresizingMaskIntoConstraints = false
|
// videoCallVC.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
videoCallVC.view.pin(to: containerView)
|
// videoCallVC.view.pin(to: containerView)
|
||||||
view.addSubview(containerView)
|
// view.addSubview(containerView)
|
||||||
containerView.pin(to: view)
|
// containerView.pin(to: view)
|
||||||
view.addSubview(joinOrLeaveButton)
|
// view.addSubview(joinOrLeaveButton)
|
||||||
joinOrLeaveButton.translatesAutoresizingMaskIntoConstraints = false
|
// joinOrLeaveButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
joinOrLeaveButton.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view)
|
// joinOrLeaveButton.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view)
|
||||||
view.addSubview(roomNumberTextField)
|
// view.addSubview(roomNumberTextField)
|
||||||
roomNumberTextField.translatesAutoresizingMaskIntoConstraints = false
|
// roomNumberTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
roomNumberTextField.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.left ], to: view)
|
// roomNumberTextField.pin([ UIView.VerticalEdge.top, UIView.HorizontalEdge.left ], to: view)
|
||||||
view.addSubview(infoTextView)
|
// view.addSubview(infoTextView)
|
||||||
infoTextView.translatesAutoresizingMaskIntoConstraints = false
|
// infoTextView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
infoTextView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view)
|
// infoTextView.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view)
|
||||||
infoTextView.center(.vertical, in: view)
|
// infoTextView.center(.vertical, in: view)
|
||||||
infoTextView.set(.height, to: 200)
|
// infoTextView.set(.height, to: 200)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
// override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
// super.viewDidAppear(animated)
|
||||||
CameraManager.shared.start()
|
// CameraManager.shared.start()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
// override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
// super.viewWillDisappear(animated)
|
||||||
CameraManager.shared.stop()
|
// CameraManager.shared.stop()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// MARK: Interaction
|
// // MARK: Interaction
|
||||||
@objc private func joinOrLeave() {
|
// @objc private func joinOrLeave() {
|
||||||
guard let roomID = roomNumberTextField.text, !roomID.isEmpty else { return }
|
// guard let roomID = roomNumberTextField.text, !roomID.isEmpty else { return }
|
||||||
if isConnected {
|
// if isConnected {
|
||||||
disconnect()
|
// disconnect()
|
||||||
} else {
|
// } else {
|
||||||
isConnected = true
|
// isConnected = true
|
||||||
TestCallServer.join(roomID: roomID).done2 { [weak self] info in
|
// TestCallServer.join(roomID: roomID).done2 { [weak self] info in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.log("Successfully joined room.")
|
// self.log("Successfully joined room.")
|
||||||
self.currentRoomInfo = info
|
// self.currentRoomInfo = info
|
||||||
if let messages = info.messages {
|
// if let messages = info.messages {
|
||||||
self.handle(messages)
|
// self.handle(messages)
|
||||||
}
|
// }
|
||||||
let webSocket = WebSocket(url: URL(string: info.wssURL)!)
|
// let webSocket = WebSocket(url: URL(string: info.wssURL)!)
|
||||||
webSocket.delegate = self
|
// webSocket.delegate = self
|
||||||
self.webSocket = webSocket
|
// self.webSocket = webSocket
|
||||||
}.catch2 { [weak self] error in
|
// }.catch2 { [weak self] error in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.isConnected = false
|
// self.isConnected = false
|
||||||
self.log("Couldn't join room due to error: \(error).")
|
// self.log("Couldn't join room due to error: \(error).")
|
||||||
SNLog("Couldn't join room due to error: \(error).")
|
// SNLog("Couldn't join room due to error: \(error).")
|
||||||
}
|
// }
|
||||||
roomNumberTextField.resignFirstResponder()
|
// roomNumberTextField.resignFirstResponder()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private func disconnect() {
|
// private func disconnect() {
|
||||||
guard let info = currentRoomInfo else { return }
|
// guard let info = currentRoomInfo else { return }
|
||||||
TestCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in
|
// TestCallServer.leave(roomID: info.roomID, userID: info.clientID).done2 { [weak self] in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.log("Disconnected.")
|
// self.log("Disconnected.")
|
||||||
}
|
// }
|
||||||
let message = [ "type": "bye" ]
|
// let message = [ "type": "bye" ]
|
||||||
guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return }
|
// guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return }
|
||||||
webSocket?.send(data)
|
// webSocket?.send(data)
|
||||||
webSocket?.delegate = nil
|
// webSocket?.delegate = nil
|
||||||
webSocket = nil
|
// webSocket = nil
|
||||||
currentRoomInfo = nil
|
// currentRoomInfo = nil
|
||||||
isConnected = false
|
// isConnected = false
|
||||||
CallManager.shared.endCall()
|
// CallManager.shared.endCall()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// MARK: Message Handling
|
// // MARK: Message Handling
|
||||||
func handle(_ messages: [String]) {
|
// func handle(_ messages: [String]) {
|
||||||
messageQueue.append(contentsOf: messages)
|
// messageQueue.append(contentsOf: messages)
|
||||||
drainMessageQueue()
|
// drainMessageQueue()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func drainMessageQueue() {
|
// func drainMessageQueue() {
|
||||||
guard isConnected else { return }
|
// guard isConnected else { return }
|
||||||
for message in messageQueue {
|
// for message in messageQueue {
|
||||||
handle(message)
|
// handle(message)
|
||||||
}
|
// }
|
||||||
messageQueue.removeAll()
|
// messageQueue.removeAll()
|
||||||
CallManager.shared.drainMessageQueue()
|
// CallManager.shared.drainICECandidateQueue()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func handle(_ message: String) {
|
// func handle(_ message: String) {
|
||||||
let signalingMessage = SignalingMessage.from(message: message)
|
// let signalingMessage = SignalingMessage.from(message: message)
|
||||||
switch signalingMessage {
|
// switch signalingMessage {
|
||||||
case .candidate(let candidate):
|
// case .candidate(let candidate):
|
||||||
CallManager.shared.handleCandidateMessage(candidate)
|
// CallManager.shared.handleCandidateMessage(candidate)
|
||||||
log("Candidate received.")
|
// log("Candidate received.")
|
||||||
case .answer(let answer):
|
// case .answer(let answer):
|
||||||
CallManager.shared.handleRemoteDescription(answer)
|
// CallManager.shared.handleRemoteDescription(answer)
|
||||||
log("Answer received.")
|
// log("Answer received.")
|
||||||
case .offer(let offer):
|
// case .offer(let offer):
|
||||||
CallManager.shared.handleRemoteDescription(offer)
|
// CallManager.shared.handleRemoteDescription(offer)
|
||||||
log("Offer received.")
|
// log("Offer received.")
|
||||||
case .bye:
|
// case .bye:
|
||||||
disconnect()
|
// disconnect()
|
||||||
default:
|
// default:
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// MARK: Streaming
|
// // MARK: Streaming
|
||||||
func webSocketDidConnect(_ webSocket: WebSocket) {
|
// func webSocketDidConnect(_ webSocket: WebSocket) {
|
||||||
guard let info = currentRoomInfo else { return }
|
// guard let info = currentRoomInfo else { return }
|
||||||
log("Connected to web socket.")
|
// log("Connected to web socket.")
|
||||||
let message = [
|
// let message = [
|
||||||
"cmd": "register",
|
// "cmd": "register",
|
||||||
"roomid": info.roomID,
|
// "roomid": info.roomID,
|
||||||
"clientid": info.clientID
|
// "clientid": info.clientID
|
||||||
]
|
// ]
|
||||||
guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return }
|
// guard let data = try? JSONSerialization.data(withJSONObject: message, options: [.prettyPrinted]) else { return }
|
||||||
webSocket.send(data)
|
// webSocket.send(data)
|
||||||
if isInitiator {
|
// if isInitiator {
|
||||||
CallManager.shared.initiateCall().retainUntilComplete()
|
// CallManager.shared.initiateCall().retainUntilComplete()
|
||||||
}
|
// }
|
||||||
drainMessageQueue()
|
// drainMessageQueue()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func webSocket(_ webSocket: WebSocket, didReceive message: String) {
|
// func webSocket(_ webSocket: WebSocket, didReceive message: String) {
|
||||||
log("Received data from web socket.")
|
// log("Received data from web socket.")
|
||||||
handle(message)
|
// handle(message)
|
||||||
CallManager.shared.drainMessageQueue()
|
// CallManager.shared.drainICECandidateQueue()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func webSocketDidDisconnect(_ webSocket: WebSocket) {
|
// func webSocketDidDisconnect(_ webSocket: WebSocket) {
|
||||||
webSocket.delegate = nil
|
// webSocket.delegate = nil
|
||||||
log("Disconnecting from web socket.")
|
// log("Disconnecting from web socket.")
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func callManager(_ callManager: CallManager, sendData data: Data) {
|
// func callManager(_ callManager: CallManager, sendData data: Data) {
|
||||||
guard let info = currentRoomInfo else { return }
|
// guard let info = currentRoomInfo else { return }
|
||||||
TestCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete()
|
// TestCallServer.send(data, roomID: info.roomID, userID: info.clientID).retainUntilComplete()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// MARK: Camera
|
// // MARK: Camera
|
||||||
func captureVideoOutput(sampleBuffer: CMSampleBuffer) {
|
// func captureVideoOutput(sampleBuffer: CMSampleBuffer) {
|
||||||
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
|
// guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
|
||||||
let ciImage = CIImage(cvImageBuffer: pixelBuffer)
|
// let ciImage = CIImage(cvImageBuffer: pixelBuffer)
|
||||||
let image = UIImage(ciImage: ciImage)
|
// let image = UIImage(ciImage: ciImage)
|
||||||
DispatchQueue.main.async { [weak self] in
|
// DispatchQueue.main.async { [weak self] in
|
||||||
self?.previewView.image = image
|
// self?.previewView.image = image
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// MARK: Logging
|
// // MARK: Logging
|
||||||
private func log(_ string: String) {
|
// private func log(_ string: String) {
|
||||||
DispatchQueue.main.async { [weak self] in
|
// DispatchQueue.main.async { [weak self] in
|
||||||
guard let self = self else { return }
|
// guard let self = self else { return }
|
||||||
self.infoTextView.text = self.infoTextView.text + "\n" + string
|
// self.infoTextView.text = self.infoTextView.text + "\n" + string
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import WebRTC
|
||||||
|
|
||||||
|
extension CallVCV2 : CameraManagerDelegate {
|
||||||
|
|
||||||
|
func handleVideoOutputCaptured(sampleBuffer: CMSampleBuffer) {
|
||||||
|
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
|
||||||
|
let rtcPixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer)
|
||||||
|
let timestamp = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
|
||||||
|
let timestampNs = Int64(timestamp * 1000000000)
|
||||||
|
let frame = RTCVideoFrame(buffer: rtcPixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs)
|
||||||
|
frame.timeStamp = Int32(timestamp)
|
||||||
|
callManager.handleLocalFrameCaptured(frame)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import WebRTC
|
||||||
|
|
||||||
|
extension CallVCV2 : CallManagerDelegate {
|
||||||
|
|
||||||
|
/// Invoked by `CallManager` upon initiating or accepting a call. This method sends the SDP to the other
|
||||||
|
/// party before streaming starts.
|
||||||
|
func sendSDP(_ sdp: RTCSessionDescription) {
|
||||||
|
guard let room = room else { return }
|
||||||
|
let json = [
|
||||||
|
"type" : RTCSessionDescription.string(for: sdp.type),
|
||||||
|
"sdp" : sdp.sdp
|
||||||
|
]
|
||||||
|
guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return }
|
||||||
|
print("[Calls] Sending SDP to test call server: \(json).")
|
||||||
|
TestCallServer.send(data, roomID: room.roomID, userID: room.clientID).retainUntilComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invoked when the peer connection has generated an ICE candidate.
|
||||||
|
func sendICECandidate(_ candidate: RTCIceCandidate) {
|
||||||
|
guard let room = room else { return }
|
||||||
|
let json = [
|
||||||
|
"type" : "candidate",
|
||||||
|
"label" : "\(candidate.sdpMLineIndex)",
|
||||||
|
"id" : candidate.sdpMid,
|
||||||
|
"candidate" : candidate.sdp
|
||||||
|
]
|
||||||
|
guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return }
|
||||||
|
print("[Calls] Sending ICE candidate to test call server: \(json).")
|
||||||
|
TestCallServer.send(data, roomID: room.roomID, userID: room.clientID).retainUntilComplete()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
extension CallVCV2 : WebSocketDelegate {
|
||||||
|
|
||||||
|
func webSocketDidConnect(_ webSocket: WebSocket) {
|
||||||
|
guard let room = room else { return }
|
||||||
|
let json = [
|
||||||
|
"cmd" : "register",
|
||||||
|
"roomid" : room.roomID,
|
||||||
|
"clientid" : room.clientID
|
||||||
|
]
|
||||||
|
guard let data = try? JSONSerialization.data(withJSONObject: json, options: [ .prettyPrinted ]) else { return }
|
||||||
|
print("[Calls] Web socket connected. Sending: \(json).")
|
||||||
|
webSocket.send(data)
|
||||||
|
print("[Calls] Is initiator: \(room.isInitiator).")
|
||||||
|
if room.isInitiator {
|
||||||
|
callManager.initiateCall().retainUntilComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func webSocketDidDisconnect(_ webSocket: WebSocket) {
|
||||||
|
webSocket.delegate = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func webSocket(_ webSocket: WebSocket, didReceive message: String) {
|
||||||
|
print("[Calls] Message received through web socket: \(message).")
|
||||||
|
handle([ message ])
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import WebRTC
|
||||||
|
|
||||||
|
final class CallVCV2 : UIViewController {
|
||||||
|
let roomID = "37923672512" // NOTE: You need to change this every time to ensure the room isn't full
|
||||||
|
var room: RoomInfo?
|
||||||
|
var socket: WebSocket?
|
||||||
|
|
||||||
|
lazy var callManager: CallManager = {
|
||||||
|
let result = CallManager()
|
||||||
|
result.delegate = self
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var cameraManager: CameraManager = {
|
||||||
|
let result = CameraManager()
|
||||||
|
result.delegate = self
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var videoCapturer: RTCVideoCapturer = {
|
||||||
|
return RTCCameraVideoCapturer(delegate: callManager.localVideoSource)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: Lifecycle
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
setUpViewHierarchy()
|
||||||
|
cameraManager.prepare()
|
||||||
|
touch(videoCapturer)
|
||||||
|
autoConnectToTestRoom()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUpViewHierarchy() {
|
||||||
|
// Create video views
|
||||||
|
let localVideoView = RTCMTLVideoView()
|
||||||
|
localVideoView.contentMode = .scaleAspectFill
|
||||||
|
let remoteVideoView = RTCMTLVideoView()
|
||||||
|
remoteVideoView.contentMode = .scaleAspectFill
|
||||||
|
// Set up stack view
|
||||||
|
let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ])
|
||||||
|
stackView.axis = .vertical
|
||||||
|
stackView.distribution = .fillEqually
|
||||||
|
stackView.alignment = .fill
|
||||||
|
view.addSubview(stackView)
|
||||||
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stackView.pin(to: view)
|
||||||
|
// Attach video views
|
||||||
|
callManager.attachLocalRenderer(localVideoView)
|
||||||
|
callManager.attachRemoteRenderer(remoteVideoView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
cameraManager.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
cameraManager.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: General
|
||||||
|
func autoConnectToTestRoom() {
|
||||||
|
// Connect to a random test room
|
||||||
|
TestCallServer.join(roomID: roomID).done2 { [weak self] room in
|
||||||
|
print("[Calls] Connected to test room.")
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.room = room
|
||||||
|
if let messages = room.messages {
|
||||||
|
self.handle(messages)
|
||||||
|
}
|
||||||
|
let socket = WebSocket(url: URL(string: room.wssURL)!)
|
||||||
|
socket.delegate = self
|
||||||
|
socket.connect()
|
||||||
|
self.socket = socket
|
||||||
|
}.catch2 { error in
|
||||||
|
SNLog("Couldn't join room due to error: \(error).")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(_ messages: [String]) {
|
||||||
|
print("[Calls] Handling messages:")
|
||||||
|
messages.forEach { print("[Calls] \($0)") }
|
||||||
|
messages.forEach { message in
|
||||||
|
let signalingMessage = SignalingMessage.from(message: message)
|
||||||
|
switch signalingMessage {
|
||||||
|
case .candidate(let candidate): callManager.handleCandidateMessage(candidate)
|
||||||
|
case .answer(let answer): callManager.handleRemoteDescription(answer)
|
||||||
|
case .offer(let offer): callManager.handleRemoteDescription(offer)
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callManager.drainICECandidateQueue()
|
||||||
|
}
|
||||||
|
}
|
@ -1,60 +1,60 @@
|
|||||||
import UIKit
|
//import UIKit
|
||||||
import AVFoundation
|
//import AVFoundation
|
||||||
import WebRTC
|
//import WebRTC
|
||||||
|
//
|
||||||
final class VideoCallVC : UIViewController {
|
//final class VideoCallVC : UIViewController {
|
||||||
private var localVideoView = UIView()
|
// private var localVideoView = UIView()
|
||||||
private var remoteVideoView = UIView()
|
// private var remoteVideoView = UIView()
|
||||||
|
//
|
||||||
override func viewDidLoad() {
|
// override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
// super.viewDidLoad()
|
||||||
setUpViewHierarchy()
|
// setUpViewHierarchy()
|
||||||
CameraManager.shared.delegate = self
|
// CameraManager.shared.delegate = self
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private func setUpViewHierarchy() {
|
// private func setUpViewHierarchy() {
|
||||||
// Create video views
|
// // Create video views
|
||||||
#if arch(arm64)
|
// #if arch(arm64)
|
||||||
// Use Metal
|
// // Use Metal
|
||||||
let localRenderer = RTCMTLVideoView(frame: self.localVideoView.frame)
|
// let localRenderer = RTCMTLVideoView(frame: self.localVideoView.frame)
|
||||||
localRenderer.contentMode = .scaleAspectFill
|
// localRenderer.contentMode = .scaleAspectFill
|
||||||
let remoteRenderer = RTCMTLVideoView(frame: self.remoteVideoView.frame)
|
// let remoteRenderer = RTCMTLVideoView(frame: self.remoteVideoView.frame)
|
||||||
remoteRenderer.contentMode = .scaleAspectFill
|
// remoteRenderer.contentMode = .scaleAspectFill
|
||||||
#else
|
// #else
|
||||||
// Use OpenGLES
|
// // Use OpenGLES
|
||||||
let localRenderer = RTCEAGLVideoView(frame: self.localVideoView.frame)
|
// let localRenderer = RTCEAGLVideoView(frame: self.localVideoView.frame)
|
||||||
let remoteRenderer = RTCEAGLVideoView(frame: self.remoteVideoView.frame)
|
// let remoteRenderer = RTCEAGLVideoView(frame: self.remoteVideoView.frame)
|
||||||
#endif
|
// #endif
|
||||||
// Set up stack view
|
// // Set up stack view
|
||||||
let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ])
|
// let stackView = UIStackView(arrangedSubviews: [ localVideoView, remoteVideoView ])
|
||||||
stackView.axis = .vertical
|
// stackView.axis = .vertical
|
||||||
stackView.distribution = .fillEqually
|
// stackView.distribution = .fillEqually
|
||||||
stackView.alignment = .fill
|
// stackView.alignment = .fill
|
||||||
view.addSubview(stackView)
|
// view.addSubview(stackView)
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
// stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackView.pin(to: view)
|
// stackView.pin(to: view)
|
||||||
// Attach video views
|
// // Attach video views
|
||||||
CallManager.shared.attachLocalRenderer(localRenderer)
|
// CallManager.shared.attachLocalRenderer(localRenderer)
|
||||||
CallManager.shared.attachRemoteRenderer(remoteRenderer)
|
// CallManager.shared.attachRemoteRenderer(remoteRenderer)
|
||||||
localVideoView.addSubview(localRenderer)
|
// localVideoView.addSubview(localRenderer)
|
||||||
localRenderer.translatesAutoresizingMaskIntoConstraints = false
|
// localRenderer.translatesAutoresizingMaskIntoConstraints = false
|
||||||
localRenderer.pin(to: localVideoView)
|
// localRenderer.pin(to: localVideoView)
|
||||||
remoteVideoView.addSubview(remoteRenderer)
|
// remoteVideoView.addSubview(remoteRenderer)
|
||||||
remoteRenderer.translatesAutoresizingMaskIntoConstraints = false
|
// remoteRenderer.translatesAutoresizingMaskIntoConstraints = false
|
||||||
remoteRenderer.pin(to: remoteVideoView)
|
// remoteRenderer.pin(to: remoteVideoView)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// MARK: Camera
|
//// MARK: Camera
|
||||||
extension VideoCallVC : CameraCaptureDelegate {
|
//extension VideoCallVC : CameraCaptureDelegate {
|
||||||
|
//
|
||||||
func captureVideoOutput(sampleBuffer: CMSampleBuffer) {
|
// func captureVideoOutput(sampleBuffer: CMSampleBuffer) {
|
||||||
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
|
// guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
|
||||||
let rtcpixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer)
|
// let rtcpixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer)
|
||||||
let timestamp = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
|
// let timestamp = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
|
||||||
let timestampNs = Int64(timestamp * 1000000000)
|
// let timestampNs = Int64(timestamp * 1000000000)
|
||||||
let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs)
|
// let videoFrame = RTCVideoFrame(buffer: rtcpixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: timestampNs)
|
||||||
videoFrame.timeStamp = Int32(timestamp)
|
// videoFrame.timeStamp = Int32(timestamp)
|
||||||
CallManager.shared.handleLocalFrameCaptured(videoFrame)
|
// CallManager.shared.handleLocalFrameCaptured(videoFrame)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
Loading…
Reference in New Issue