Antidote/Antidote/CallCoordinator.swift

397 lines
13 KiB
Swift

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
import Foundation
import CallKit
protocol CallCoordinatorDelegate: class {
func callCoordinator(_ coordinator: CallCoordinator, notifyAboutBackgroundCallFrom caller: String, userInfo: String)
func callCoordinatorDidStartCall(_ coordinator: CallCoordinator)
func callCoordinatorDidFinishCall(_ coordinator: CallCoordinator)
}
private struct Constants {
static let DeclineAfterInterval = 1.5
}
private class ActiveCall {
var callToken: RLMNotificationToken?
fileprivate let call: OCTCall
fileprivate let navigation: UINavigationController
fileprivate var usingFrontCamera: Bool = true
init(call: OCTCall, navigation: UINavigationController) {
self.call = call
self.navigation = navigation
}
deinit {
callToken?.invalidate()
}
}
class CallCoordinator: NSObject {
weak var delegate: CallCoordinatorDelegate?
fileprivate let theme: Theme
fileprivate weak var presentingController: UIViewController!
fileprivate weak var submanagerCalls: OCTSubmanagerCalls!
fileprivate weak var submanagerObjects: OCTSubmanagerObjects!
fileprivate var providerdelegate: ProviderDelegate!
fileprivate let audioPlayer = AudioPlayer()
fileprivate var activeCall: ActiveCall? {
didSet {
switch (oldValue, activeCall) {
case (.none, .some):
delegate?.callCoordinatorDidStartCall(self)
case (.some, .none):
delegate?.callCoordinatorDidFinishCall(self)
default:
break
}
}
}
init(theme: Theme, presentingController: UIViewController, submanagerCalls: OCTSubmanagerCalls, submanagerObjects: OCTSubmanagerObjects) {
self.theme = theme
self.presentingController = presentingController
self.submanagerCalls = submanagerCalls
self.submanagerObjects = submanagerObjects
super.init()
// CALL:
print("cc:controler:init:01")
submanagerCalls.delegate = self
}
func callToChat(_ chat: OCTChat, enableVideo: Bool) {
// CALL:
print("cc:controler:callToChat:01")
do {
let call = try submanagerCalls.call(to: chat, enableAudio: true, enableVideo: enableVideo)
var nickname = String(localized: "contact_deleted")
if let friend = chat.friends.lastObject() as? OCTFriend {
nickname = friend.nickname
}
let controller = CallActiveController(theme: theme, callerName: nickname)
controller.delegate = self
// CALL:
print("cc:controler:callToChat:02")
startActiveCallWithCall(call, controller: controller)
}
catch let error as NSError {
handleErrorWithType(.callToChat, error: error)
}
}
func answerIncomingCallWithUserInfo(_ userInfo: String) {
// CALL:
print("cc:controler:answerIncomingCallWithUserInfo:01")
guard let activeCall = activeCall else { return }
guard activeCall.call.uniqueIdentifier == userInfo else { return }
guard activeCall.call.status == .ringing else { return }
answerCall(enableVideo: false)
}
}
extension CallCoordinator: CoordinatorProtocol {
func startWithOptions(_ options: CoordinatorOptions?) {
}
}
extension CallCoordinator: OCTSubmanagerCallDelegate {
func callSubmanager(_ callSubmanager: OCTSubmanagerCalls!, receive call: OCTCall!, audioEnabled: Bool, videoEnabled: Bool) {
guard activeCall == nil else {
// Currently we support only one call at a time
_ = try? submanagerCalls.send(.cancel, to: call)
return
}
let nickname = call.caller?.nickname ?? ""
// CALL: start incoming call
print("cc:controler:incoming_call:01")
if !UIApplication.isActive {
delegate?.callCoordinator(self, notifyAboutBackgroundCallFrom: nickname, userInfo: call.uniqueIdentifier)
// CALL: start incoming call
print("cc:controler:incoming_call:BG")
let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 0.1) {
AppDelegate.shared.displayIncomingCall(uuid: UUID(), handle: nickname, hasVideo: false) { _ in
UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
}
}
}
let controller = CallIncomingController(theme: theme, callerName: nickname)
controller.delegate = self
startActiveCallWithCall(call, controller: controller)
print("cc:controler:incoming_call:99")
}
}
extension CallCoordinator: CallIncomingControllerDelegate {
func callIncomingControllerDecline(_ controller: CallIncomingController) {
// CALL:
print("cc:controler:callIncomingControllerDecline:01")
declineCall(callWasRemoved: false)
}
func callIncomingControllerAnswerAudio(_ controller: CallIncomingController) {
// CALL:
print("cc:controler:callIncomingControllerAnswerAudio:01")
answerCall(enableVideo: false)
}
func callIncomingControllerAnswerVideo(_ controller: CallIncomingController) {
// CALL:
print("cc:controler:callIncomingControllerAnswerVideo:01")
answerCall(enableVideo: true)
}
}
extension CallCoordinator: CallActiveControllerDelegate {
func callActiveController(_ controller: CallActiveController, mute: Bool) {
submanagerCalls.enableMicrophone = !mute
}
func callActiveController(_ controller: CallActiveController, speaker: Bool) {
do {
try submanagerCalls.routeAudio(toSpeaker: speaker)
}
catch {
handleErrorWithType(.routeAudioToSpeaker)
controller.speaker = !speaker
}
}
func callActiveController(_ controller: CallActiveController, outgoingVideo: Bool) {
guard let activeCall = activeCall else {
assert(false, "This method should be called only if active call is non-nil")
return
}
do {
try submanagerCalls.enableVideoSending(outgoingVideo, for: activeCall.call)
}
catch {
handleErrorWithType(.enableVideoSending)
controller.outgoingVideo = !outgoingVideo
}
}
func callActiveControllerDecline(_ controller: CallActiveController) {
// CALL:
print("cc:controler:callActiveControllerDecline:02")
declineCall(callWasRemoved: false)
}
func callActiveControllerSwitchCamera(_ controller: CallActiveController) {
guard let activeCall = activeCall else {
assert(false, "This method should be called only if active call is non-nil")
return
}
do {
let front = !activeCall.usingFrontCamera
try submanagerCalls.switch(toCameraFront: front)
self.activeCall?.usingFrontCamera = front
}
catch {
handleErrorWithType(.callSwitchCamera)
}
}
}
extension CallCoordinator {
func declineCall(callWasRemoved wasRemoved: Bool) {
// CALL:
print("cc:controler:declineCall:01")
guard let activeCall = activeCall else {
// assert(false, "This method should be called only if active call is non-nil")
return
}
if !wasRemoved {
_ = try? submanagerCalls.send(.cancel, to: activeCall.call)
}
audioPlayer.stopAll()
if let controller = activeCall.navigation.topViewController as? CallBaseController {
controller.prepareForRemoval()
}
let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 0.1) {
AppDelegate.shared.endIncomingCalls()
UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
}
// self.providerdelegate.endIncomingCall()
let delayTime = DispatchTime.now() + Double(Int64(Constants.DeclineAfterInterval * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in
self?.presentingController.dismiss(animated: true, completion: nil)
self?.activeCall = nil
}
}
func startActiveCallWithCall(_ call: OCTCall, controller: CallBaseController) {
guard activeCall == nil else {
assert(false, "This method should be called only if there is no active call")
return
}
// CALL:
print("cc:controler:startActiveCallWithCall:01")
let navigation = UINavigationController(rootViewController: controller)
navigation.modalPresentationStyle = .overCurrentContext
navigation.isNavigationBarHidden = true
navigation.modalTransitionStyle = .crossDissolve
activeCall = ActiveCall(call: call, navigation: navigation)
let predicate = NSPredicate(format: "uniqueIdentifier == %@", call.uniqueIdentifier)
let results = submanagerObjects.calls(predicate: predicate)
activeCall!.callToken = results.addNotificationBlock { [unowned self] change in
switch change {
case .initial:
break
case .update(_, let deletions, _, let modifications):
if deletions.count > 0 {
self.declineCall(callWasRemoved: true)
}
else if modifications.count > 0 {
self.activeCallWasUpdated()
}
case .error(let error):
fatalError("\(error)")
}
}
presentingController.present(navigation, animated: true, completion: nil)
activeCallWasUpdated()
}
func answerCall(enableVideo: Bool) {
// CALL:
print("cc:controler:answerCall:01")
guard let activeCall = activeCall else {
// assert(false, "This method should be called only if active call is non-nil")
return
}
guard activeCall.call.status == .ringing else {
// assert(false, "Call status should be .Ringing")
return
}
do {
try submanagerCalls.answer(activeCall.call, enableAudio: true, enableVideo: enableVideo)
}
catch let error as NSError {
handleErrorWithType(.answerCall, error: error)
declineCall(callWasRemoved: false)
}
}
func activeCallWasUpdated() {
// CALL:
print("cc:controler:activeCallWasUpdated:01")
guard let activeCall = activeCall else {
assert(false, "This method should be called only if active call is non-nil")
return
}
switch activeCall.call.status {
case .ringing:
if !audioPlayer.isPlayingSound(.Ringtone) {
audioPlayer.playSound(.Ringtone, loop: true)
}
// no update for ringing status
return
case .dialing:
if !audioPlayer.isPlayingSound(.Calltone) {
audioPlayer.playSound(.Calltone, loop: true)
}
case .active:
if audioPlayer.isPlaying() {
audioPlayer.stopAll()
}
}
var activeController = activeCall.navigation.topViewController as? CallActiveController
if (activeController == nil) {
let nickname = activeCall.call.caller?.nickname ?? ""
activeController = CallActiveController(theme: theme, callerName: nickname)
activeController!.delegate = self
activeCall.navigation.setViewControllers([activeController!], animated: false)
}
switch activeCall.call.status {
case .ringing:
break
case .dialing:
activeController!.state = .reaching
case .active:
activeController!.state = .active(duration: activeCall.call.callDuration)
}
activeController!.outgoingVideo = activeCall.call.videoIsEnabled
if activeCall.call.videoIsEnabled {
if activeController!.videoPreviewLayer == nil {
submanagerCalls.getVideoCallPreview { [weak activeController] layer in
activeController?.videoPreviewLayer = layer
}
}
}
else {
if activeController!.videoPreviewLayer != nil {
activeController!.videoPreviewLayer = nil
}
}
if activeCall.call.friendSendingVideo {
if activeController!.videoFeed == nil {
activeController!.videoFeed = submanagerCalls.videoFeed()
}
}
else {
if activeController!.videoFeed != nil {
activeController!.videoFeed = nil
}
}
}
}