Swiftgram/TelegramUI/CallKitIntergation.swift
2018-06-16 20:03:02 +03:00

235 lines
8.8 KiB
Swift

import Foundation
import CallKit
import AVFoundation
import Postbox
import SwiftSignalKit
final class CallKitIntegration {
private let providerDelegate: AnyObject
private let audioSessionActivePromise = ValuePromise<Bool>(false, ignoreRepeated: true)
var audioSessionActive: Signal<Bool, NoError> {
return self.audioSessionActivePromise.get()
}
init?(startCall: @escaping (UUID, String) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, audioSessionActivationChanged: @escaping (Bool) -> Void) {
if Locale.current.regionCode?.lowercased() == "cn" {
return nil
}
#if (arch(i386) || arch(x86_64)) && os(iOS)
return nil
#else
if #available(iOSApplicationExtension 10.0, *) {
self.providerDelegate = CallKitProviderDelegate(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, audioSessionActivationChanged: audioSessionActivationChanged)
} else {
return nil
}
#endif
}
func startCall(peerId: PeerId, displayTitle: String) {
if #available(iOSApplicationExtension 10.0, *) {
(self.providerDelegate as! CallKitProviderDelegate).startCall(peerId: peerId, displayTitle: displayTitle)
}
}
func answerCall(uuid: UUID) {
if #available(iOSApplicationExtension 10.0, *) {
(self.providerDelegate as! CallKitProviderDelegate).answerCall(uuid: uuid)
}
}
func dropCall(uuid: UUID) {
if #available(iOSApplicationExtension 10.0, *) {
(self.providerDelegate as! CallKitProviderDelegate).dropCall(uuid: uuid)
}
}
func reportIncomingCall(uuid: UUID, handle: String, displayTitle: String, completion: ((NSError?) -> Void)?) {
if #available(iOSApplicationExtension 10.0, *) {
(self.providerDelegate as! CallKitProviderDelegate).reportIncomingCall(uuid: uuid, handle: handle, displayTitle: displayTitle, completion: completion)
}
}
func reportOutgoingCallConnected(uuid: UUID, at date: Date) {
if #available(iOSApplicationExtension 10.0, *) {
(self.providerDelegate as! CallKitProviderDelegate).reportOutgoingCallConnected(uuid: uuid, at: date)
}
}
}
@available(iOSApplicationExtension 10.0, *)
class CallKitProviderDelegate: NSObject, CXProviderDelegate {
private let provider: CXProvider
private let callController = CXCallController()
private let startCall: (UUID, String) -> Signal<Bool, NoError>
private let answerCall: (UUID) -> Void
private let endCall: (UUID) -> Signal<Bool, NoError>
private let audioSessionActivationChanged: (Bool) -> Void
private let disposableSet = DisposableSet()
fileprivate let audioSessionActivePromise: ValuePromise<Bool>
init(audioSessionActivePromise: ValuePromise<Bool>, startCall: @escaping (UUID, String) -> Signal<Bool, NoError>, answerCall: @escaping (UUID) -> Void, endCall: @escaping (UUID) -> Signal<Bool, NoError>, audioSessionActivationChanged: @escaping (Bool) -> Void) {
self.audioSessionActivePromise = audioSessionActivePromise
self.startCall = startCall
self.answerCall = answerCall
self.endCall = endCall
self.audioSessionActivationChanged = audioSessionActivationChanged
self.provider = CXProvider(configuration: CallKitProviderDelegate.providerConfiguration)
super.init()
self.provider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration {
let providerConfiguration = CXProviderConfiguration(localizedName: "Telegram")
providerConfiguration.supportsVideo = false
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.maximumCallGroups = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber, .generic]
if let image = UIImage(bundleImageName: "Call/CallKitLogo") {
providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation(image)
}
return providerConfiguration
}
private func requestTransaction(_ transaction: CXTransaction, completion: ((Bool) -> Void)? = nil) {
self.callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction: \(error)")
}
completion?(error == nil)
}
}
func endCall(uuid: UUID) {
let endCallAction = CXEndCallAction(call: uuid)
let transaction = CXTransaction(action: endCallAction)
self.requestTransaction(transaction)
}
func dropCall(uuid: UUID) {
self.provider.reportCall(with: uuid, endedAt: nil, reason: CXCallEndedReason.remoteEnded)
}
func answerCall(uuid: UUID) {
}
func startCall(peerId: PeerId, displayTitle: String) {
let uuid = UUID()
let handle = CXHandle(type: .generic, value: "\(peerId.id)")
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
startCallAction.contactIdentifier = displayTitle
startCallAction.isVideo = false
let transaction = CXTransaction(action: startCallAction)
self.requestTransaction(transaction, completion: { _ in
let update = CXCallUpdate()
update.remoteHandle = handle
update.localizedCallerName = displayTitle
update.supportsHolding = false
update.supportsGrouping = false
update.supportsUngrouping = false
update.supportsDTMF = false
self.provider.reportCall(with: uuid, updated: update)
})
}
func reportIncomingCall(uuid: UUID, handle: String, displayTitle: String, completion: ((NSError?) -> Void)?) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle)
update.localizedCallerName = displayTitle
update.supportsHolding = false
update.supportsGrouping = false
update.supportsUngrouping = false
update.supportsDTMF = false
self.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in
completion?(error as NSError?)
})
}
func reportOutgoingCallConnecting(uuid: UUID, at date: Date) {
self.provider.reportOutgoingCall(with: uuid, startedConnectingAt: date)
}
func reportOutgoingCallConnected(uuid: UUID, at date: Date) {
self.provider.reportOutgoingCall(with: uuid, connectedAt: date)
}
func providerDidReset(_ provider: CXProvider) {
/*stopAudio()
for call in callManager.calls {
call.end()
}
callManager.removeAllCalls()*/
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
let disposable = MetaDisposable()
self.disposableSet.add(disposable)
disposable.set((self.startCall(action.callUUID, action.handle.value)
|> deliverOnMainQueue
|> afterDisposed { [weak self, weak disposable] in
if let strongSelf = self, let disposable = disposable {
strongSelf.disposableSet.remove(disposable)
}
}).start(next: { result in
if result {
action.fulfill()
} else {
action.fail()
}
}))
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
self.answerCall(action.callUUID)
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
let disposable = MetaDisposable()
self.disposableSet.add(disposable)
disposable.set((self.endCall(action.callUUID)
|> deliverOnMainQueue
|> afterDisposed { [weak self, weak disposable] in
if let strongSelf = self, let disposable = disposable {
strongSelf.disposableSet.remove(disposable)
}
}).start(next: { result in
if result {
action.fulfill(withDateEnded: Date())
} else {
action.fail()
}
}))
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
self.audioSessionActivationChanged(true)
self.audioSessionActivePromise.set(true)
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
self.audioSessionActivationChanged(false)
self.audioSessionActivePromise.set(false)
}
}