397 lines
13 KiB
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
|
|
}
|
|
}
|
|
}
|
|
}
|