This repository has been archived by the owner on Jun 26, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 24
/
ProviderDelegate.swift
272 lines (217 loc) · 10.5 KB
/
ProviderDelegate.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information
Abstract:
CallKit provider delegate class, which conforms to CXProviderDelegate protocol
*/
import Foundation
import UIKit
import CallKit
import AVFoundation
import OpenTok
final class ProviderDelegate: NSObject, CXProviderDelegate {
let callManager: SpeakerboxCallManager
private let provider: CXProvider
init(callManager: SpeakerboxCallManager) {
self.callManager = callManager
provider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
/// The app's provider configuration, representing its CallKit capabilities
static var providerConfiguration: CXProviderConfiguration {
let localizedName = NSLocalizedString("CallKitDemo", comment: "Name of application")
let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)
providerConfiguration.supportsVideo = false
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber]
providerConfiguration.iconTemplateImageData = #imageLiteral(resourceName: "IconMask").pngData()
providerConfiguration.ringtoneSound = "Ringtone.caf"
return providerConfiguration
}
// MARK: Incoming Calls
/// Use CXProvider to report the incoming call to the system
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
// pre-heat the AVAudioSession
//OTAudioDeviceManager.setAudioDevice(OTDefaultAudioDevice.sharedInstance())
// Report the incoming call to the system
provider.reportNewIncomingCall(with: uuid, update: update) { error in
/*
Only add incoming call to the app's list of calls if the call was allowed (i.e. there was no error)
since calls may be "denied" for various legitimate reasons. See CXErrorCodeIncomingCallError.
*/
if error == nil {
let call = SpeakerboxCall(uuid: uuid)
call.handle = handle
self.callManager.addCall(call)
}
completion?(error as NSError?)
}
}
// MARK: CXProviderDelegate
func providerDidReset(_ provider: CXProvider) {
print("Provider did reset")
/*
End any ongoing calls if the provider resets, and remove them from the app's list of calls,
since they are no longer valid.
*/
}
var outgoingCall: SpeakerboxCall?
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
// Create & configure an instance of SpeakerboxCall, the app's model class representing the new outgoing call.
let call = SpeakerboxCall(uuid: action.callUUID, isOutgoing: true)
call.handle = action.handle.value
/*
Configure the audio session, but do not start call audio here, since it must be done once
the audio session has been activated by the system after having its priority elevated.
*/
// https://forums.developer.apple.com/thread/64544
// we can't configure the audio session here for the case of launching it from locked screen
// instead, we have to pre-heat the AVAudioSession by configuring as early as possible, didActivate do not get called otherwise
// please look for * pre-heat the AVAudioSession *
configureAudioSession()
/*
Set callback blocks for significant events in the call's lifecycle, so that the CXProvider may be updated
to reflect the updated state.
*/
call.hasStartedConnectingDidChange = { [weak self] in
self?.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate)
}
call.hasConnectedDidChange = { [weak self] in
self?.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectDate)
}
self.outgoingCall = call
// Signal to the system that the action has been successfully performed.
action.fulfill()
}
var answerCall: SpeakerboxCall?
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
/*
Configure the audio session, but do not start call audio here, since it must be done once
the audio session has been activated by the system after having its priority elevated.
*/
// https://forums.developer.apple.com/thread/64544
// we can't configure the audio session here for the case of launching it from locked screen
// instead, we have to pre-heat the AVAudioSession by configuring as early as possible, didActivate do not get called otherwise
// please look for * pre-heat the AVAudioSession *
configureAudioSession()
self.answerCall = call
// Signal to the system that the action has been successfully performed.
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
// Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
// Trigger the call to be ended via the underlying network service.
call.endCall()
// Signal to the system that the action has been successfully performed.
action.fulfill()
// Remove the ended call from the app's list of calls.
callManager.removeCall(call)
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
// Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
// Update the SpeakerboxCall's underlying hold state.
call.isOnHold = action.isOnHold
// Stop or start audio in response to holding or unholding the call.
call.isMuted = call.isOnHold
// Signal to the system that the action has been successfully performed.
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
// Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
call.isMuted = action.isMuted
// Signal to the system that the action has been successfully performed.
action.fulfill()
}
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
print("Timed out \(#function)")
// React to the action timeout if necessary, such as showing an error UI.
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("Received \(#function)")
// If we are returning from a hold state
if answerCall?.hasConnected ?? false {
//configureAudioSession()
// See more details on how this works in the OTDefaultAudioDevice.m method handleInterruptionEvent
sendFakeAudioInterruptionNotificationToStartAudioResources();
return
}
if outgoingCall?.hasConnected ?? false {
//configureAudioSession()
// See more details on how this works in the OTDefaultAudioDevice.m method handleInterruptionEvent
sendFakeAudioInterruptionNotificationToStartAudioResources()
return
}
// Start call audio media, now that the audio session has been activated after having its priority boosted.
outgoingCall?.startCall(withAudioSession: audioSession) { [weak self] success in
guard let outgoingCall = self?.outgoingCall else { return }
if success {
self?.callManager.addCall(outgoingCall)
self?.outgoingCall?.startAudio()
} else {
self?.callManager.end(call: outgoingCall)
}
}
answerCall?.answerCall(withAudioSession: audioSession) { success in
if success {
self.answerCall?.startAudio()
}
}
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
print("Received \(#function)")
/*
Restart any non-call related audio now that the app's audio session has been
de-activated after having its priority restored to normal.
*/
if outgoingCall?.isOnHold ?? false || answerCall?.isOnHold ?? false {
print("Call is on hold. Do not terminate any call")
return
}
outgoingCall?.endCall()
outgoingCall = nil
answerCall?.endCall()
answerCall = nil
callManager.removeAllCalls()
}
func sendFakeAudioInterruptionNotificationToStartAudioResources() {
var userInfo = Dictionary<AnyHashable, Any>()
let interrupttioEndedRaw = AVAudioSession.InterruptionType.ended.rawValue
userInfo[AVAudioSessionInterruptionTypeKey] = interrupttioEndedRaw
NotificationCenter.default.post(name: AVAudioSession.interruptionNotification, object: self, userInfo: userInfo)
}
func configureAudioSession() {
// See https://forums.developer.apple.com/thread/64544
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSession.Category.playAndRecord, mode: .default)
try session.setActive(true)
try session.setMode(AVAudioSession.Mode.voiceChat)
try session.setPreferredSampleRate(44100.0)
try session.setPreferredIOBufferDuration(0.005)
} catch {
print(error)
}
}
}