Last active
April 25, 2019 16:59
-
-
Save saroar/0e7aa0e0e6122786ebbecb617e78d0bf to your computer and use it in GitHub Desktop.
CallManager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CallManager.swift | |
// AddaIOS | |
// | |
// Created by Alif on 30/1/19. | |
// Copyright © 2019 Alif. All rights reserved. | |
// | |
import UIKit | |
import CallKit | |
import Starscream | |
import WebRTC | |
@available(iOS 10.0, *) | |
class CallManager { | |
var uuid = "" | |
var callerID = "" | |
var receiverID = "" | |
var receiverName = "" | |
var mobileNumber = "" | |
var receiverAvatar = PerfectLocalAuth.avatarUrl | |
var videoTrack = false | |
var isCallActive = false | |
var isOutGoingCall = false | |
static let unansweredTimeoutInterval: TimeInterval = 60 | |
private var unansweredTimer: Timer? | |
private var socket = WebSocketTracker() | |
private var webRTCClient: WebRTCClient! | |
struct Static { | |
fileprivate static var instance: CallManager? | |
} | |
class var sharedInstance: CallManager { | |
if Static.instance == nil { | |
Static.instance = CallManager() | |
} | |
return Static.instance! | |
} | |
func dispose() { | |
log.info(#line, "Disposed Singleton instance") | |
endCall() | |
webRTCClient.disconnect() | |
// isMuted = false | |
// usesSpeaker = false | |
delay(1.0) { self.socket.disconnect() } | |
CallManager.Static.instance = nil | |
} | |
private let queue = DispatchQueue(label: "me.adda.call-manager") | |
private let ringtonePlayer = try? AVAudioPlayer( | |
contentsOf: Bundle.main.url(forResource: "incoming", withExtension: "wav")! | |
) | |
var callsChangedHandler: (() -> Void)? | |
private(set) var calls = [Call]() | |
private let callController = CXCallController() | |
// var isMuted = false { | |
// didSet { | |
// guard webRTCClient.iceConnectionState == .connected else { | |
// return | |
// } | |
// webRTCClient.isMuted = isMuted | |
// } | |
// } | |
// | |
// var usesSpeaker = false { | |
// didSet { | |
// guard webRTCClient.iceConnectionState == .connected else { | |
// return | |
// } | |
// | |
// queue.async { | |
// try? AVAudioSession.sharedInstance().overrideOutputAudioPort(self.portOverride) | |
// } | |
// } | |
// } | |
init() { | |
self.webRTCClient = WebRTCClient() | |
self.webRTCClient.delegate = self | |
NotificationCenter.default.addObserver( | |
self, | |
selector: #selector(audioSessionRouteChange(_:)), | |
name: AVAudioSession.routeChangeNotification, | |
object: nil | |
) | |
socket.addListener(self) | |
if socket.isConnected() { | |
socket.disconnect() | |
} | |
_ = socket.connect(link: "call", protocolName: "call") | |
_ = CallerAndReceiverID.instent.callerIDAndReceiverID( | |
isOutGoingCall: isOutGoingCall, | |
callerID: callerID, | |
receiverID: receiverID | |
) | |
} | |
deinit { | |
log.info(#line, "Deinit from CallManager") | |
NotificationCenter.default.removeObserver(self) | |
} | |
lazy var storyBoard: UIStoryboard = UIStoryboard(name: "Contacts", bundle: nil) | |
// swiftlint:disable force_cast | |
lazy var callVC: CallVC = { | |
return storyBoard.instantiateViewController(withIdentifier: "CallVC") as! CallVC | |
}() | |
func moveToCallingScreen() { | |
if let window = AppDelegate.shared.window, | |
let rootViewController = window.rootViewController { | |
var currentController = rootViewController | |
while let presentedController = currentController.presentedViewController { | |
currentController = presentedController | |
} | |
delay(1.5) { | |
UIApplication.shared.endIgnoringInteractionEvents() | |
currentController.modalTransitionStyle = .flipHorizontal | |
currentController.showDetailViewController(self.callVC, sender: Any.self) | |
//currentController.present(self.callVC, animated: true, completion: nil) | |
self.invalidateUnansweredTimeoutTimerAndSetNil() | |
} | |
} | |
} | |
func endCall() { | |
guard let uuid = UUID(uuidString: uuid) else { log.info(#line, "missing uuid"); return } | |
let call = Call(uuid: uuid, callerID: "", receiverID: "", mobileNumber: "", receiverAvatar: "", firstname: "", hasVideo: false, isOutgoing: false) | |
end(call) | |
guard let addaCallVC = storyBoard.instantiateViewController(withIdentifier: "AddaCallVC") as? AddaCallVC else { | |
log.error(#line, "AddaCallVC cant find fatalError") | |
fatalError("AddaCallVC cant find") | |
} | |
guard let callVC = storyBoard.instantiateViewController(withIdentifier: "CallVC") as? CallVC else { | |
log.error(#line, "CallVC cant find fatalError") | |
fatalError("CallVC cant find") | |
} | |
if let window = AppDelegate.shared.window, | |
let rootViewController = window.rootViewController { | |
var currentController = rootViewController | |
while let presentedController = currentController.presentedViewController { | |
currentController = presentedController | |
} | |
delay(0.5) { | |
UIApplication.shared.endIgnoringInteractionEvents() | |
if currentController.nibName == addaCallVC.nibName { | |
currentController.dismiss(animated: true, completion: nil) | |
} | |
if currentController.nibName == callVC.nibName { | |
currentController.dismiss(animated: true, completion: nil) | |
} | |
} | |
} | |
} | |
// MARK: Actions | |
func answerCall() { | |
self.webRTCClient.answer { (localSdp) in | |
let sessionDescription = RTCSessionDescription( | |
type: .answer, | |
sdp: localSdp.sdp | |
) | |
let sdp = SDP.init(sdp: sessionDescription.sdp) | |
let receiver = CallSignaling(callerID: CallerAndReceiverID.instent.callerID, receiverID: CallerAndReceiverID.instent.receiverID, isOutgoing: true, calling: true, type: SignalType.answer.rawValue, sessionDescription: sdp) | |
self.socket.send(receiver.jsonString!) | |
} | |
} | |
func hangupCall() { | |
let callSignalMsg = CallSignaling( | |
uuid: uuid, | |
userName: PerfectLocalAuth.username, | |
callerID: CallerAndReceiverID.instent.callerID, | |
receiverID: CallerAndReceiverID.instent.receiverID, | |
receiverAvatar: receiverAvatar, | |
type: SignalType.leave.rawValue) | |
self.socket.send(callSignalMsg.jsonString!) | |
// if webRTCClient.isConnected { | |
// webRTCClient.disconnect() | |
// } | |
} | |
@objc private func audioSessionRouteChange(_ notification: Notification) { | |
// guard call != nil else { | |
// return | |
// } | |
// try? AVAudioSession.sharedInstance().overrideOutputAudioPort(portOverride) | |
} | |
} | |
extension CallManager { | |
// MARK: Call Managment | |
static let CallsChangeNotification = Notification.Name("CallManagerCallsChangedNotification") | |
func setupTimeoutTimer() { | |
let timer = Timer( | |
timeInterval: CallManager.unansweredTimeoutInterval, | |
target: self, | |
selector: #selector(self.unansweredTimeout), | |
userInfo: nil, | |
repeats: false) | |
RunLoop.main.add(timer, forMode: .default) | |
self.unansweredTimer = timer | |
} | |
private func invalidateUnansweredTimeoutTimerAndSetNil() { | |
unansweredTimer?.invalidate() | |
unansweredTimer = nil | |
} | |
@objc private func unansweredTimeout() { | |
// guard let call = call, !call.hasReceivedRemoteAnswer else { | |
// return | |
// } | |
endCall() | |
webRTCClient.disconnect() | |
//isMuted = false | |
queue.async { | |
self.ringtonePlayer?.stop() | |
let call = Call.init( | |
uuid: UUID(uuidString: self.uuid)!, | |
callerID: CallerAndReceiverID.instent.callerID, | |
receiverID: CallerAndReceiverID.instent.receiverID, | |
mobileNumber: "", | |
receiverAvatar: self.receiverAvatar, | |
firstname: PerfectLocalAuth.realname(), | |
hasVideo: self.videoTrack, | |
isOutgoing: self.isOutGoingCall) | |
call.end() | |
self.end(call) | |
} | |
} | |
private func clean() { | |
webRTCClient.disconnect() | |
// call = nil | |
// isMuted = false | |
// usesSpeaker = false | |
invalidateUnansweredTimeoutTimerAndSetNil() | |
// performSynchronouslyOnMainThread { | |
// view.dismiss() | |
// } | |
} | |
func startCall(uuid: String, handle: String, videoEnabled: Bool) { | |
// pre-heat the AVAudioSession | |
configureAudioSession() | |
let handle = CXHandle(type: .phoneNumber, value: handle) | |
let startCallAction = CXStartCallAction(call: UUID(uuidString: uuid)!, handle: handle) | |
startCallAction.isVideo = videoEnabled | |
let transaction = CXTransaction() | |
transaction.addAction(startCallAction) | |
requestTransaction(transaction) | |
} | |
func setHeld(call: Call, onHold: Bool) { | |
let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: onHold) | |
let transaction = CXTransaction() | |
transaction.addAction(setHeldCallAction) | |
requestTransaction(transaction) | |
} | |
func requestTransaction(_ transaction: CXTransaction) { | |
callController.request(transaction) { error in | |
if let error = error { | |
log.error("Error requesting transaction: \(error.localizedDescription)") | |
} else { | |
log.info("Requested transaction successfully") | |
} | |
} | |
} | |
func callWithUUID(uuid: UUID) -> Call? { | |
guard let index = calls.index(where: { $0.uuid == uuid }) else { | |
return nil | |
} | |
return calls[index] | |
} | |
func add(_ call: Call) { | |
log.info(#line, "add \(call.uuid)") | |
calls.append(call) | |
call.stateDidChange = { [weak self] in | |
guard let strongSelf = self else { return } | |
strongSelf.callsChangedHandler?() | |
strongSelf.postCallChangeNotification() | |
} | |
callsChangedHandler?() | |
} | |
func end(_ call: Call) { | |
log.info(#line, "end \(call.uuid)") | |
let endCallAction = CXEndCallAction(call: call.uuid) | |
let transaction = CXTransaction(action: endCallAction) | |
requestTransaction(transaction) | |
postCallChangeNotification() | |
webRTCClient.disconnect() | |
} | |
func remove(_ call: Call) { | |
guard let index = calls.index(where: { $0 === call}) else { | |
return | |
} | |
calls.remove(at: index) | |
callsChangedHandler?() | |
postCallChangeNotification() | |
webRTCClient.disconnect() | |
} | |
func removeAllCalls() { | |
calls.removeAll() | |
callsChangedHandler?() | |
postCallChangeNotification() | |
} | |
private func postCallChangeNotification() { | |
NotificationCenter.default.post( | |
name: type(of: self).CallsChangeNotification, object: self | |
) | |
} | |
func playRingtone(usesSpeaker: Bool) { | |
if usesSpeaker { | |
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: []) | |
} else { | |
try? AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat, options: []) | |
} | |
ringtonePlayer?.play() | |
} | |
} | |
extension CallManager: WebSocketEventsListener { | |
func webSocketEventsListenerDidConnect(_ webSocketTrackerService: WebSocketTracker) { | |
_ = CallerAndReceiverID.instent.callerIDAndReceiverID( | |
isOutGoingCall: isOutGoingCall, | |
callerID: callerID, | |
receiverID: receiverID | |
) | |
let signalingMessage = CallSignaling( | |
uuid: uuid, | |
userName: PerfectLocalAuth.username, | |
callerID: CallerAndReceiverID.instent.callerID, | |
receiverID: CallerAndReceiverID.instent.receiverID, | |
receiverAvatar: receiverAvatar, | |
mobileNumber: mobileNumber, | |
hasVideoAndDataChannel: videoTrack, | |
calling: false, | |
isReceiverReadyToPickUpCall: false, | |
type: SignalType.connect.rawValue) | |
socket.sendData(signalingMessage.jsonData!) | |
print(#line, "after connet send all information to caller \(signalingMessage.jsonString!)") | |
} | |
func webSocketEventsListenerDidDisconnect(_ webSocketTrackerService: WebSocketTracker, didDisconnectError: Error?) { | |
log.info("DidDisconnect", context: didDisconnectError.debugDescription) | |
guard let uuid = UUID(uuidString: uuid) else { log.info(#line, "missing uuid"); return } | |
let call = Call.init( | |
uuid: uuid, | |
callerID: CallerAndReceiverID.instent.callerID, | |
receiverID: CallerAndReceiverID.instent.receiverID, | |
mobileNumber: mobileNumber, | |
receiverAvatar: receiverAvatar, | |
firstname: PerfectLocalAuth.realname(), | |
hasVideo: videoTrack, | |
isOutgoing: false) | |
call.end() | |
self.end(call) | |
} | |
func webSocketEventsListenerDidRecieveError(_ webSocketTrackerService: WebSocketTracker, didRecieveError: Error) { | |
log.info("DidRecieveError", context: didRecieveError.localizedDescription) | |
} | |
func webSocketEventsListenerDidReceiveMessage(_ webSocketTrackerService: WebSocketTracker, didRecieveMessage text: String) { | |
print(#line, "read text: \(text)") | |
guard let data = text.data(using: .utf8) else { | |
print(#line, "Wrong encoding for received message") | |
return | |
} | |
do { | |
let signalingMessage = try JSONDecoder().decode(CallSignaling.self, from: data) | |
switch signalingMessage.type { | |
case SignalType.connect.rawValue: | |
print(#line, "connect") | |
let signalingMessage = CallSignaling( | |
uuid: uuid, | |
userName: PerfectLocalAuth.username, | |
callerID: CallerAndReceiverID.instent.callerID, | |
receiverID: CallerAndReceiverID.instent.receiverID, | |
receiverAvatar: receiverAvatar, | |
mobileNumber: mobileNumber, | |
hasVideoAndDataChannel: videoTrack, | |
calling: false, | |
isReceiverReadyToPickUpCall: true, | |
type: SignalType.call.rawValue) | |
socket.send(signalingMessage.jsonString!) | |
case SignalType.call.rawValue: | |
print(#line, "calling to \(receiverName)") | |
case SignalType.offer.rawValue: | |
let offerSDP = RTCSessionDescription( | |
type: .offer, | |
sdp: signalingMessage.sessionDescription!.sdp | |
) | |
self.webRTCClient.set(remoteSdp: offerSDP) { (error) in | |
debugPrint(#line, error?.localizedDescription ?? "") | |
} | |
case SignalType.answer.rawValue: | |
let answerSDP = RTCSessionDescription( | |
type: .answer, | |
sdp: signalingMessage.sessionDescription!.sdp | |
) | |
self.webRTCClient.set(remoteSdp: answerSDP) { (error) in | |
debugPrint(#line, error?.localizedDescription ?? "") | |
} | |
case SignalType.candidate.rawValue: | |
let iceCandidate = signalingMessage.iceCandidate! | |
let candidate = RTCIceCandidate( | |
sdp: iceCandidate.sdp, | |
sdpMLineIndex: iceCandidate.sdpMLineIndex, | |
sdpMid: iceCandidate.sdpMid | |
) | |
self.webRTCClient.set(remoteCandidate: candidate) | |
print(#line, "iceCandidate") | |
case SignalType.leave.rawValue: | |
print(#line, "leave") | |
default: | |
debugPrint("Error in connection") | |
} | |
} catch { | |
debugPrint(#line, " decoded \(error.localizedDescription) ") | |
} | |
} | |
func webSocketEventsListenerDidReceiveData(_ webSocketTrackerService: WebSocketTracker, didRecieveMessage data: Data) { | |
log.info("didRecieveMessage", context: data) | |
} | |
func sendMessage(_ message: String) { } | |
func register(_ response: String) { } | |
} | |
extension CallManager: WebRTCClientDelegate { | |
func webRTCClient(_ client: WebRTCClient, didDiscoverLocalCandidate candidate: RTCIceCandidate) { | |
print("discovered local candidate") | |
let candidate = CallCandidate(from: candidate) | |
let candidateMsg = CallSignaling( | |
callerID: CallerAndReceiverID.instent.callerID, | |
receiverID: CallerAndReceiverID.instent.receiverID, | |
type: SignalType.candidate.rawValue, | |
iceCandidate: candidate) | |
self.socket.send(candidateMsg.jsonString!) | |
} | |
func webRTCClient(_ client: WebRTCClient, didChangeConnectionState state: RTCIceConnectionState) { | |
let textColor: UIColor | |
switch state { | |
case .connected, .completed: | |
textColor = .green | |
case .disconnected: | |
textColor = .orange | |
if let callVC = UIStoryboard(name: "Contacts", bundle: nil) | |
.instantiateViewController(withIdentifier: "CallVC") as? CallVC { | |
if let window = AppDelegate.shared.window, let rootViewController = window.rootViewController { | |
var currentController = rootViewController | |
while let presentedController = currentController.presentedViewController { | |
currentController = presentedController | |
} | |
delay(0.5) { | |
UIApplication.shared.endIgnoringInteractionEvents() | |
if currentController.nibName == callVC.nibName { | |
currentController.dismiss(animated: true, completion: nil) | |
} | |
} | |
} | |
} | |
case .failed, .closed: | |
textColor = .red | |
case .new, .checking, .count: | |
textColor = .black | |
@unknown default: | |
textColor = .black | |
} | |
} | |
func webRTCClient(_ client: WebRTCClient, didReceiveData data: Data) { | |
// DispatchQueue.main.async { | |
// let message = String(data: data, encoding: .utf8) ?? "(Binary: \(data.count) bytes)" | |
// let alert = UIAlertController(title: "Message from WebRTC", message: message, preferredStyle: .alert) | |
// alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) | |
// self.present(alert, animated: true, completion: nil) | |
// } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment