mirror of https://github.com/jitsi/jitsi-meet
With this the RN component and the consumer app can share same CallKit provider, configuration, and enable to be part of multiple listeners of the CallKit flow events. The main driver of this is to enable the consumer app to be able to report an incoming call to the OS before loading the JitsiMeetView. Once the user answers the call, the app can instantiate a JitsiMeetView, pass the CallKit call UUIID, and the Jitsi Meet components will handle the connection and report back to CallKit that the call has been established.pull/2864/head
parent
520bb8bd22
commit
e5309a6482
@ -0,0 +1,63 @@ |
||||
/* |
||||
* Copyright @ 2018-present Atlassian Pty Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import AVKit |
||||
import CallKit |
||||
import Foundation |
||||
|
||||
@objc public protocol JMCallKitEventListener: NSObjectProtocol { |
||||
|
||||
@available(iOS 10.0, *) |
||||
@objc optional func providerDidReset() |
||||
|
||||
@available(iOS 10.0, *) |
||||
@objc optional func performAnswerCall(UUID: UUID) |
||||
|
||||
@available(iOS 10.0, *) |
||||
@objc optional func performEndCall(UUID: UUID) |
||||
|
||||
@available(iOS 10.0, *) |
||||
@objc optional func performSetMutedCall(UUID: UUID, isMuted: Bool) |
||||
|
||||
@available(iOS 10.0, *) |
||||
@objc optional func performStartCall(UUID: UUID, isVideo: Bool) |
||||
|
||||
@available(iOS 10.0, *) |
||||
@objc optional func providerDidActivateAudioSession(session: AVAudioSession) |
||||
|
||||
@available(iOS 10.0, *) |
||||
@objc optional func providerDidDeactivateAudioSession(session: AVAudioSession) |
||||
|
||||
@available(iOS 10.0, *) |
||||
@objc optional func providerTimedOutPerformingAction(action: CXAction) |
||||
} |
||||
|
||||
internal struct JMCallKitEventListenerWrapper: Hashable { |
||||
|
||||
public var hashValue: Int |
||||
|
||||
internal weak var listener: JMCallKitEventListener? |
||||
|
||||
public init(listener: JMCallKitEventListener) { |
||||
self.listener = listener |
||||
self.hashValue = listener.hash |
||||
} |
||||
|
||||
public static func ==(lhs: JMCallKitEventListenerWrapper, |
||||
rhs: JMCallKitEventListenerWrapper) -> Bool { |
||||
return lhs.hashValue == rhs.hashValue |
||||
} |
||||
} |
@ -0,0 +1,104 @@ |
||||
/* |
||||
* Copyright @ 2018-present Atlassian Pty Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import AVKit |
||||
import CallKit |
||||
import Foundation |
||||
|
||||
internal final class JMCallKitNotifier: NSObject, CXProviderDelegate { |
||||
|
||||
private var listeners = Set<JMCallKitEventListenerWrapper>() |
||||
|
||||
internal override init() {} |
||||
|
||||
// MARK: - Add/remove listeners |
||||
|
||||
func addListener(_ listener: JMCallKitEventListener) { |
||||
let wrapper = JMCallKitEventListenerWrapper(listener: listener) |
||||
objc_sync_enter(listeners) |
||||
listeners.insert(wrapper) |
||||
objc_sync_exit(listeners) |
||||
} |
||||
|
||||
func removeListener(_ listener: JMCallKitEventListener) { |
||||
let wrapper = JMCallKitEventListenerWrapper(listener: listener) |
||||
objc_sync_enter(listeners) |
||||
listeners.remove(wrapper) |
||||
objc_sync_exit(listeners) |
||||
} |
||||
|
||||
// MARK: - CXProviderDelegate |
||||
|
||||
func providerDidReset(_ provider: CXProvider) { |
||||
objc_sync_enter(listeners) |
||||
listeners.forEach { $0.listener?.providerDidReset?() } |
||||
objc_sync_exit(listeners) |
||||
} |
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { |
||||
objc_sync_enter(listeners) |
||||
listeners.forEach { $0.listener?.performAnswerCall?(UUID: action.callUUID) } |
||||
objc_sync_exit(listeners) |
||||
|
||||
action.fulfill() |
||||
} |
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXEndCallAction) { |
||||
objc_sync_enter(listeners) |
||||
listeners.forEach { $0.listener?.performEndCall?(UUID: action.callUUID) } |
||||
objc_sync_exit(listeners) |
||||
|
||||
action.fulfill() |
||||
} |
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) { |
||||
objc_sync_enter(listeners) |
||||
listeners.forEach { |
||||
$0.listener?.performSetMutedCall?(UUID: action.callUUID, |
||||
isMuted: action.isMuted) |
||||
} |
||||
objc_sync_exit(listeners) |
||||
|
||||
action.fulfill() |
||||
} |
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXStartCallAction) { |
||||
objc_sync_enter(listeners) |
||||
listeners.forEach { |
||||
$0.listener?.performStartCall?(UUID: action.callUUID, |
||||
isVideo: action.isVideo) |
||||
} |
||||
objc_sync_exit(listeners) |
||||
|
||||
action.fulfill() |
||||
} |
||||
|
||||
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { |
||||
objc_sync_enter(listeners) |
||||
listeners.forEach { |
||||
$0.listener?.providerDidActivateAudioSession?(session: audioSession) |
||||
} |
||||
objc_sync_exit(listeners) |
||||
} |
||||
|
||||
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { |
||||
objc_sync_enter(listeners) |
||||
listeners.forEach { |
||||
$0.listener?.providerDidDeactivateAudioSession?(session: audioSession) |
||||
} |
||||
objc_sync_exit(listeners) |
||||
} |
||||
} |
@ -0,0 +1,177 @@ |
||||
/* |
||||
* Copyright @ 2018-present Atlassian Pty Ltd |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import CallKit |
||||
import Foundation |
||||
|
||||
/// JitsiMeet CallKit proxy |
||||
@available(iOS 10.0, *) |
||||
@objc public final class JMCallKitProxy: NSObject { |
||||
|
||||
override private init() {} |
||||
|
||||
// MARK: - CallKit proxy |
||||
|
||||
internal static let cxProvider: CXProvider = { |
||||
let config = CXProviderConfiguration(localizedName: "") |
||||
let provider = CXProvider(configuration: config) |
||||
return provider |
||||
}() |
||||
|
||||
internal static let cxCallController: CXCallController = { |
||||
return CXCallController() |
||||
}() |
||||
|
||||
internal static let callKitNotifier: JMCallKitNotifier = { |
||||
return JMCallKitNotifier() |
||||
}() |
||||
|
||||
internal static var cxProviderConfiguration: CXProviderConfiguration? { |
||||
didSet { |
||||
guard let providerConfiguration = cxProviderConfiguration else { return } |
||||
cxProvider.configuration = providerConfiguration |
||||
cxProvider.setDelegate(callKitNotifier, queue: nil) |
||||
} |
||||
} |
||||
|
||||
/// Enables the proxy in between callkit and the consumers of the SDK |
||||
/// Default to enabled, set to false when you don't want to use callkit |
||||
@objc public static var enabled: Bool = true { |
||||
didSet { |
||||
if enabled == false { |
||||
cxProvider.setDelegate(nil, queue: nil) |
||||
} |
||||
} |
||||
} |
||||
|
||||
@objc public static func hasProviderBeenConfigurated() -> Bool { |
||||
return cxProviderConfiguration != nil |
||||
} |
||||
|
||||
@objc public static func configureCallKitProvider(localizedName: String, |
||||
ringtoneSound: String?, |
||||
iconTemplateImageData: Data?) { |
||||
let configuration = CXProviderConfiguration(localizedName: localizedName) |
||||
configuration.ringtoneSound = ringtoneSound |
||||
configuration.iconTemplateImageData = iconTemplateImageData |
||||
|
||||
configuration.maximumCallGroups = 1 |
||||
configuration.maximumCallsPerCallGroup = 1 |
||||
configuration.supportedHandleTypes = [CXHandle.HandleType.generic] |
||||
configuration.supportsVideo = true |
||||
cxProviderConfiguration = configuration |
||||
} |
||||
|
||||
@objc public static func addListener(_ listener: JMCallKitEventListener) { |
||||
callKitNotifier.addListener(listener) |
||||
} |
||||
|
||||
@objc public static func removeListener(_ listener: JMCallKitEventListener) { |
||||
callKitNotifier.removeListener(listener) |
||||
} |
||||
|
||||
@objc public static func hasActiveCallForUUID(_ callUUID: String) -> Bool { |
||||
let activeCallForUUID = cxCallController.callObserver.calls.first { |
||||
$0.uuid == UUID(uuidString: callUUID) |
||||
} |
||||
guard activeCallForUUID != nil else { return false } |
||||
return true |
||||
} |
||||
|
||||
@objc public static func reportNewIncomingCall(UUID: UUID, |
||||
handle: String?, |
||||
displayName: String?, |
||||
hasVideo: Bool, |
||||
completion: @escaping (Error?) -> Void) { |
||||
guard enabled else { return } |
||||
|
||||
let callUpdate = makeCXUpdate(handle: handle, |
||||
displayName: displayName, |
||||
hasVideo: hasVideo) |
||||
cxProvider.reportNewIncomingCall(with: UUID, |
||||
update: callUpdate, |
||||
completion: completion) |
||||
} |
||||
|
||||
@objc public static func reportCallUpdate(with UUID: UUID, |
||||
handle: String?, |
||||
displayName: String?, |
||||
hasVideo: Bool) { |
||||
guard enabled else { return } |
||||
|
||||
let callUpdate = makeCXUpdate(handle: handle, |
||||
displayName: displayName, |
||||
hasVideo: hasVideo) |
||||
cxProvider.reportCall(with: UUID, updated: callUpdate) |
||||
} |
||||
|
||||
@objc public static func reportCall(with UUID: UUID, |
||||
endedAt dateEnded: Date?, |
||||
reason endedReason: CXCallEndedReason) { |
||||
guard enabled else { return } |
||||
|
||||
cxProvider.reportCall(with: UUID, |
||||
endedAt: dateEnded, |
||||
reason: endedReason) |
||||
} |
||||
|
||||
@objc public static func reportOutgoingCall(with UUID: UUID, |
||||
startedConnectingAt dateStartedConnecting: Date?) { |
||||
guard enabled else { return } |
||||
|
||||
cxProvider.reportOutgoingCall(with: UUID, |
||||
startedConnectingAt: dateStartedConnecting) |
||||
} |
||||
|
||||
@objc public static func reportOutgoingCall(with UUID: UUID, |
||||
connectedAt dateConnected: Date?) { |
||||
guard enabled else { return } |
||||
|
||||
cxProvider.reportOutgoingCall(with: UUID, connectedAt: dateConnected) |
||||
} |
||||
|
||||
@objc public static func request(_ transaction: CXTransaction, |
||||
completion: @escaping (Error?) -> Swift.Void) { |
||||
guard enabled else { return } |
||||
|
||||
cxCallController.request(transaction, completion: completion) |
||||
} |
||||
|
||||
// MARK: - Callkit Proxy helpers |
||||
|
||||
private static func makeCXUpdate(handle: String?, |
||||
displayName: String?, |
||||
hasVideo: Bool) -> CXCallUpdate { |
||||
let update = CXCallUpdate() |
||||
update.supportsDTMF = false |
||||
update.supportsHolding = false |
||||
update.supportsGrouping = false |
||||
update.supportsUngrouping = false |
||||
update.hasVideo = hasVideo |
||||
|
||||
if let handle = handle { |
||||
update.remoteHandle = CXHandle(type: .generic, |
||||
value: handle) |
||||
} |
||||
|
||||
if let displayName = displayName { |
||||
update.localizedCallerName = displayName |
||||
} |
||||
|
||||
return update |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue