// // ViewController.swift // Runner // // Created by Mohammad Aljammal & Elham Rababah on 23/6/20. // Copyright © 2020 The Chromium Authors. All rights reserved. // import UIKit import OpenTok import Alamofire import AADraggableView class VideoCallViewController: UIViewController { var session: OTSession? var publisher: OTPublisher? var subscriber: OTSubscriber? var kApiKey:String = "" var kSessionId:String = "" var kToken:String = "" var VC_ID: Int = 0 var TokenID: String = "" var generalid : String = "" var DoctorId: Int = 0 var baseUrl:String = "" var callBack: ICallProtocol? var timer = Timer() var seconds = 55 var isUserConnect : Bool = false var onRectFloat:((Bool)->Void)? = nil var onCircleFloat:((Bool)->Void)? = nil var onCallConnect:(()->Void)? = nil var onCallDisconnect:(()->Void)? = nil @IBOutlet weak var lblRemoteUsername: UILabel! // Bottom Actions @IBOutlet weak var videoMuteBtn: UIButton! @IBOutlet weak var micMuteBtn: UIButton! @IBOutlet weak var camSwitchBtn: UIButton! @IBOutlet var minimizeConstraint: [NSLayoutConstraint]! @IBOutlet var maximisedConstraint: [NSLayoutConstraint]! @IBOutlet weak var btnMinimize: UIButton! @IBOutlet weak var hideVideoBtn: UIButton! var localVideoDraggable:AADraggableView? @IBOutlet weak var controlButtons: UIView! @IBOutlet weak var remoteVideoMutedIndicator: UIImageView! @IBOutlet weak var localVideoMutedBg: UIView! @IBOutlet weak var btnScreenTap: UIButton! @IBOutlet weak var localVideoContainer: UIView! @IBOutlet weak var topBar: UIView! @IBOutlet weak var lblCallDuration: UILabel! @IBOutlet weak var fullVideoView: UIView! @IBOutlet weak var smallVideoView: UIView!{ didSet{ smallVideoView.layer.borderColor = UIColor.white.cgColor localVideoDraggable = smallVideoView?.superview as? AADraggableView localVideoDraggable?.reposition = .edgesOnly } } override func viewDidLoad() { super.viewDidLoad() localVideoDraggable?.respectedView = localVideoContainer } @objc func click(gesture:UIGestureRecognizer){ gesture.view?.removeFromSuperview() } @IBAction func btnOnScreenTapped(_ sender: Any) { if(hideVideoBtn.isSelected){ circleFloatBtnTapped(hideVideoBtn) }else if(btnMinimize.isSelected){ btnMinimizeTapped(btnMinimize) } } @IBAction func btnSwipeVideoTapped(_ sender: Any) { // let smallVdoRender = smallVideoView.subviews.first // let fullVdoRender = fullVideoView.subviews.first // if let vdo = smallVdoRender{ // fullVideoView.addSubview(vdo) // } // if let vdo = fullVdoRender{ // smallVideoView.addSubview(vdo) // } // // layoutVideoRenderViews() } @IBAction func didClickMuteButton(_ sender: UIButton) { sender.isSelected = !sender.isSelected publisher!.publishAudio = !sender.isSelected } @IBAction func didClickSpeakerButton(_ sender: UIButton) { sender.isSelected = !sender.isSelected subscriber?.subscribeToAudio = !sender.isSelected } @IBAction func didClickVideoMuteButton(_ sender: UIButton) { sender.isSelected = !sender.isSelected if publisher!.publishVideo { publisher!.publishVideo = false } else { publisher!.publishVideo = true } smallVideoView.isHidden = sender.isSelected localVideoMutedBg.isHidden = !sender.isSelected } @IBAction func didClickSwitchCameraButton(_ sender: UIButton) { sender.isSelected = !sender.isSelected if sender.isSelected { publisher!.cameraPosition = AVCaptureDevice.Position.front } else { publisher!.cameraPosition = AVCaptureDevice.Position.back } } @IBAction func hangUp(_ sender: UIButton) { callBack?.sessionDone(res:["callResponse":"CallEnd"]) sessionDisconnect() } @IBAction func circleFloatBtnTapped(_ sender: UIButton) { sender.isSelected = !sender.isSelected onCircleFloat?(sender.isSelected) topBar.isHidden = sender.isSelected controlButtons.isHidden = sender.isSelected smallVideoView.isHidden = sender.isSelected self.publisher?.view?.layoutIfNeeded() } @IBAction func btnMinimizeTapped(_ sender: UIButton) { minimizeVideoState(state: !sender.isSelected) btnScreenTap.isHidden = !sender.isSelected } func minimizeVideoState(state:Bool){ btnMinimize.isSelected = state onRectFloat?(state) NSLayoutConstraint.activate(state ? minimizeConstraint : maximisedConstraint) NSLayoutConstraint.deactivate(state ? maximisedConstraint : minimizeConstraint) localVideoDraggable?.enable(!state) lblRemoteUsername.isHidden = state hideVideoBtn.isHidden = !state lblCallDuration.superview?.isHidden = !hideVideoBtn.isHidden UIView.animate(withDuration: 0.5) { self.videoMuteBtn.isHidden = state self.micMuteBtn.isHidden = state self.camSwitchBtn.isHidden = state self.layoutVideoRenderViews() } } func layoutVideoRenderViews(){ if let publisherVdoSize = publisher?.view?.superview?.bounds.size{ publisher?.view?.frame = CGRect(x: 0, y: 0, width: publisherVdoSize.width, height: publisherVdoSize.height) } if let subscriberVdoSize = subscriber?.view?.superview?.bounds.size{ subscriber?.view?.frame = CGRect(x: 0, y: 0, width: subscriberVdoSize.width, height: subscriberVdoSize.height) } } var durationTimer:Timer?; func startUpdateCallDuration(){ var seconds = 0 durationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in seconds = seconds+1 let durationSegments = (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60) let hours = String(format: "%02d", durationSegments.0) let mins = String(format: "%02d", durationSegments.1) let secs = String(format: "%02d", durationSegments.2) let durationString = "\(mins):\(secs)" self.lblCallDuration.text = durationString } } func start(params:VideoCallRequestParameters){ lblRemoteUsername.text = params.patientName ?? "- - -" btnScreenTap.isHidden = true hideVideoBtn.isHidden = true self.kApiKey = params.apiKey ?? "" self.kSessionId = params.sessionId ?? "" self.kToken = params.token ?? "" self.VC_ID = params.vcId ?? 0 self.generalid = params.generalId ?? "" self.TokenID = params.tokenId ?? "" self.DoctorId = params.doctorId ?? 0 self.baseUrl = params.baseUrl ?? "" askForMicrophonePermission() requestCameraPermissionsIfNeeded() hideVideoMuted() setupSession() } private func changeCallStatus(callStatus:Int){ let URL_USER_REGISTER = baseUrl+"LiveCareApi/DoctorApp/ChangeCallStatus" let headers: HTTPHeaders = ["Content-Type":"application/json","Accept":"application/json",] let parameters = [ "CallStatus":callStatus, "VC_ID": VC_ID, "TokenID": TokenID, "generalid": generalid, "DoctorId" : DoctorId , ] as [String : Any] AF.request(URL_USER_REGISTER, method: .post,parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON{ response in if let result = response.value { let jsonData = result as! NSObject let resultVal = jsonData.value(forKey: "Result") print(resultVal as Any) } } } private func getSessionStatus() { let URL_USER_REGISTER = baseUrl+"LiveCareApi/DoctorApp/GetSessionStatus" let headers: HTTPHeaders = [ "Content-Type":"application/json", "Accept":"application/json", ] let parameters = [ "VC_ID": VC_ID, "TokenID": TokenID, "generalid": generalid, "DoctorId" : DoctorId , ] as [String : Any] AF.request(URL_USER_REGISTER, method: .post,parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON{ response in if self.isUserConnect { } else { if let result = response.value { let jsonData = result as! NSObject if((jsonData.value(forKey: "SessionStatus")) as! Int == 2 || (jsonData.value(forKey: "SessionStatus")) as! Int == 3) { //jsonData let jsonObject: [String: Any] = [ "sessionStatus": result , "callResponse": "CallNotRespond", ] self.callBack?.sessionNotResponded(res: jsonObject) } } self.sessionDisconnect(); self.timer.invalidate() } } } // MARK: -Microphone Camera and Permission Request func askForMicrophonePermission() { switch AVAudioSession.sharedInstance().recordPermission { case AVAudioSession.RecordPermission.granted: break case AVAudioSession.RecordPermission.denied: break case AVAudioSession.RecordPermission.undetermined: // This is the initial state before a user has made any choice // You can use this spot to request permission here if you want AVAudioSession.sharedInstance().requestRecordPermission({ granted in // Check for granted }) default: break } } func notifyUserOfCameraAccessDenial() { // display a useful message asking the user to grant permissions from within Settings > Privacy > Camera } func sessionDisconnect() { changeCallStatus(callStatus: 16) if (session != nil) { print("disconnecting....") session!.disconnect(nil) dismiss(animated: true) } dismiss(animated: true) onCallDisconnect?() durationTimer?.invalidate() } func requestCameraPermissionsIfNeeded() { // check camera authorization status let authStatus: AVAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) switch authStatus { case .authorized: break // camera authorized // do camera intensive stuff case .notDetermined: // request authorization AVCaptureDevice.requestAccess(for: .video, completionHandler: { granted in DispatchQueue.main.async(execute: { if granted { // do camera intensive stuff } else { self.notifyUserOfCameraAccessDenial() } }) }) case .restricted, .denied: DispatchQueue.main.async(execute: { self.notifyUserOfCameraAccessDenial() }) default: break } } func hideVideoMuted() { remoteVideoMutedIndicator.isHidden = true localVideoMutedBg.isHidden = true } func setupSession() { //setup one time session if (session != nil) { session = nil } session = OTSession( apiKey: kApiKey, sessionId: kSessionId, delegate: self) var error: OTError? session!.connect(withToken: kToken,error: &error) } func connectToAnOpenTokSession() { session = OTSession(apiKey: kApiKey, sessionId: kSessionId, delegate: self) var error: OTError? session?.connect(withToken: kToken, error: &error) if error != nil { print(error!) } } func showAlert(_ string: String?) { // show alertview on main UI DispatchQueue.main.async(execute: { let alertVC = UIAlertController( title: "OTError", message: string, preferredStyle: .alert) self.present(alertVC, animated: true) }) } @objc func updateTimer(){ seconds -= 1 //This will decrement(count down)the seconds. print(seconds) if seconds == 0 { getSessionStatus() } } } extension VideoCallViewController: OTSessionDelegate { func sessionDidConnect(_ session: OTSession) { print("The client connected to the OpenTok session.") timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(VideoCallViewController.updateTimer)), userInfo: nil, repeats: true) setupPublisher() } func setupPublisher() { let settings = OTPublisherSettings() settings.name = UIDevice.current.name publisher = OTPublisher(delegate: self, settings: settings) var error: OTError? = nil session!.publish(publisher!, error: &error) if error != nil { showAlert(error?.localizedDescription) } publisher?.view?.tag = 11 publisher?.view?.layer.cornerRadius = 5 publisher?.view?.clipsToBounds = true smallVideoView.addSubview((publisher?.view)!) layoutVideoRenderViews() } func sessionDidDisconnect(_ session: OTSession) { print("The client disconnected from the OpenTok session.") } func session(_ session: OTSession, didFailWithError error: OTError) { changeCallStatus(callStatus: 16) print("The client failed to connect to the OpenTok session: \(error).") } func session( _ session: OTSession, connectionDestroyed connection: OTConnection ) { if subscriber?.stream!.connection.connectionId == connection.connectionId { cleanupSubscriber() } sessionDisconnect() } func session(_ session: OTSession, streamCreated stream: OTStream) { subscriber = OTSubscriber(stream: stream, delegate: self) guard let subscriber = subscriber else { return } var error: OTError? session.subscribe(subscriber, error: &error) guard error == nil else { print(error!) return } guard let subscriberView = subscriber.view else { return } subscriberView.tag = 22 fullVideoView.addSubview(subscriberView) layoutVideoRenderViews() startUpdateCallDuration() onCallConnect?() } func setupSubscribe(_ stream: OTStream?) { subscriber = OTSubscriber(stream: stream!, delegate: self) var error: OTError? = nil session!.subscribe(subscriber!, error: &error) if error != nil { showAlert(error!.localizedDescription) } } func session(_ session: OTSession, streamDestroyed stream: OTStream) { if subscriber?.stream?.streamId == stream.streamId { cleanupSubscriber() } print("A stream was destroyed in the session.") } func cleanupSubscriber() { subscriber?.view!.removeFromSuperview() subscriber = nil } func session( _ session: OTSession?, connectionCreated connection: OTConnection? ) { // startTimer(callDuration, warningDuration) if let connectionId = connection?.connectionId { print("session connectionCreated (\(connectionId))") } changeCallStatus(callStatus: 3) isUserConnect = true timer.invalidate() } } extension VideoCallViewController: OTPublisherDelegate { func publisher(_ publisher: OTPublisherKit, didFailWithError error: OTError) { print("The publisher failed: \(error)") } } extension VideoCallViewController: OTSubscriberDelegate { public func subscriberDidConnect(toStream subscriber: OTSubscriberKit) { print("The subscriber did connect to the stream.") } public func subscriber(_ subscriber: OTSubscriberKit, didFailWithError error: OTError) { print("The subscriber failed to connect to the stream.") } }