@qusaieilouti99/call-manager 0.1.80 → 0.1.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/AudioManager.swift +49 -51
- package/ios/CallEngine.swift +120 -59
- package/ios/CallInfo.swift +2 -2
- package/ios/CallKitManager.swift +99 -65
- package/ios/CallManager.swift +149 -144
- package/ios/VoIPTokenManager.swift +55 -47
- package/nitrogen/generated/android/c++/JFunc_void_CallEventType_std__string.hpp +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +1 -1
- package/nitrogen/generated/android/c++/JHybridCallManagerSpec.cpp +6 -1
- package/nitrogen/generated/android/c++/JHybridCallManagerSpec.hpp +1 -0
- package/nitrogen/generated/ios/c++/HybridCallManagerSpecSwift.hpp +3 -1
- package/nitrogen/generated/ios/swift/HybridCallManagerSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridCallManagerSpec_cxx.swift +10 -1
- package/nitrogen/generated/shared/c++/AudioRoutesInfo.hpp +1 -1
- package/package.json +168 -168
package/ios/AudioManager.swift
CHANGED
|
@@ -5,6 +5,8 @@ import OSLog
|
|
|
5
5
|
protocol AudioManagerDelegate: AnyObject {
|
|
6
6
|
func audioManager(_ manager: AudioManager, didChangeRoute routeInfo: AudioRoutesInfo)
|
|
7
7
|
func audioManager(_ manager: AudioManager, didChangeDevices routeInfo: AudioRoutesInfo)
|
|
8
|
+
func audioManagerDidActivateAudioSession(_ manager: AudioManager)
|
|
9
|
+
func audioManagerDidDeactivateAudioSession(_ manager: AudioManager)
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
class AudioManager {
|
|
@@ -55,55 +57,33 @@ class AudioManager {
|
|
|
55
57
|
logger.info("🔊 ✅ Audio session notifications setup completed")
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
logger.info("🔊 Configuring audio session for incoming call...")
|
|
60
|
+
// MARK: - Audio Session Configuration for CallKit
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
try audioSession.setActive(true)
|
|
64
|
-
logger.info("🔊 ✅ Audio session configured for incoming call")
|
|
65
|
-
} catch {
|
|
66
|
-
logger.error("🔊 ❌ Failed to configure audio session for incoming call: \(error.localizedDescription)")
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
func configureForOutgoingCall(isVideo: Bool) {
|
|
71
|
-
logger.info("🔊 Configuring audio session for outgoing call (video: \(isVideo))...")
|
|
62
|
+
func configureAudioSession(forCallType isVideo: Bool, isIncoming: Bool) {
|
|
63
|
+
logger.info("🔊 Configuring audio session: isVideo=\(isVideo), isIncoming=\(isIncoming)...")
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: options)
|
|
79
|
-
try audioSession.setActive(true)
|
|
80
|
-
logger.info("🔊 ✅ Audio session configured for outgoing call (video: \(isVideo))")
|
|
81
|
-
} catch {
|
|
82
|
-
logger.error("🔊 ❌ Failed to configure audio session for outgoing call: \(error.localizedDescription)")
|
|
65
|
+
let options: AVAudioSession.CategoryOptions
|
|
66
|
+
if isVideo {
|
|
67
|
+
options = [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker]
|
|
68
|
+
} else {
|
|
69
|
+
options = [.allowBluetooth, .allowBluetoothA2DP]
|
|
83
70
|
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
func configureForActiveCall(isVideo: Bool) {
|
|
87
|
-
logger.info("🔊 Configuring audio session for active call (video: \(isVideo))...")
|
|
88
71
|
|
|
89
72
|
do {
|
|
90
|
-
let options: AVAudioSession.CategoryOptions = isVideo ?
|
|
91
|
-
[.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker] :
|
|
92
|
-
[.allowBluetooth, .allowBluetoothA2DP]
|
|
93
|
-
|
|
94
73
|
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: options)
|
|
95
|
-
|
|
96
|
-
logger.info("🔊 ✅ Audio session configured for active call (video: \(isVideo))")
|
|
74
|
+
logger.info("🔊 ✅ Audio session category/mode/options set for call")
|
|
97
75
|
} catch {
|
|
98
|
-
logger.error("🔊 ❌ Failed to configure audio session
|
|
76
|
+
logger.error("🔊 ❌ Failed to configure audio session: \(error.localizedDescription)")
|
|
99
77
|
}
|
|
100
78
|
}
|
|
101
79
|
|
|
80
|
+
// MARK: - Audio Route Management
|
|
81
|
+
|
|
102
82
|
func getAudioDevices() -> AudioRoutesInfo {
|
|
103
83
|
logger.debug("🔊 Getting available audio devices...")
|
|
104
84
|
|
|
105
85
|
let currentRoute = audioSession.currentRoute
|
|
106
|
-
var devices: [String] = ["Earpiece", "Speaker"]
|
|
86
|
+
var devices: [String] = ["Earpiece", "Speaker"] // Default options
|
|
107
87
|
|
|
108
88
|
logger.debug("🔊 Current route inputs: \(currentRoute.inputs.map { $0.portType.rawValue })")
|
|
109
89
|
logger.debug("🔊 Current route outputs: \(currentRoute.outputs.map { $0.portType.rawValue })")
|
|
@@ -118,7 +98,7 @@ class AudioManager {
|
|
|
118
98
|
devices.append("Bluetooth")
|
|
119
99
|
logger.debug("🔊 Added Bluetooth device")
|
|
120
100
|
}
|
|
121
|
-
case .headphones, .headsetMic
|
|
101
|
+
case .headphones, .headsetMic:
|
|
122
102
|
if !devices.contains("Headset") {
|
|
123
103
|
devices.append("Headset")
|
|
124
104
|
logger.debug("🔊 Added Headset device")
|
|
@@ -151,13 +131,10 @@ class AudioManager {
|
|
|
151
131
|
logger.debug("🔊 Overriding to speaker...")
|
|
152
132
|
try audioSession.overrideOutputAudioPort(.speaker)
|
|
153
133
|
case "Earpiece":
|
|
154
|
-
logger.debug("🔊 Overriding to earpiece...")
|
|
155
|
-
try audioSession.overrideOutputAudioPort(.none)
|
|
156
|
-
case "Bluetooth":
|
|
157
|
-
logger.debug("🔊 Setting to Bluetooth (system managed)...")
|
|
134
|
+
logger.debug("🔊 Overriding to earpiece (built-in receiver)...")
|
|
158
135
|
try audioSession.overrideOutputAudioPort(.none)
|
|
159
|
-
case "Headset":
|
|
160
|
-
logger.debug("🔊 Setting to Headset (system managed)...")
|
|
136
|
+
case "Bluetooth", "Headset":
|
|
137
|
+
logger.debug("🔊 Setting to Bluetooth/Headset (system managed via .none)...")
|
|
161
138
|
try audioSession.overrideOutputAudioPort(.none)
|
|
162
139
|
default:
|
|
163
140
|
logger.warning("🔊 ⚠️ Unknown audio route: \(route)")
|
|
@@ -179,17 +156,32 @@ class AudioManager {
|
|
|
179
156
|
logger.info("🔊 Mute state changed to: \(muted)")
|
|
180
157
|
}
|
|
181
158
|
|
|
182
|
-
|
|
183
|
-
logger.info("🔊 Cleaning up audio session...")
|
|
159
|
+
// MARK: - Audio Session Activation/Deactivation from CallKit
|
|
184
160
|
|
|
161
|
+
func activateAudioSession() {
|
|
162
|
+
logger.info("🔊 Audio session activation requested by CallKit...")
|
|
163
|
+
do {
|
|
164
|
+
try audioSession.setActive(true)
|
|
165
|
+
logger.info("🔊 ✅ Audio session activated successfully")
|
|
166
|
+
delegate?.audioManagerDidActivateAudioSession(self)
|
|
167
|
+
} catch {
|
|
168
|
+
logger.error("🔊 ❌ Failed to activate audio session: \(error.localizedDescription)")
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func deactivateAudioSession() {
|
|
173
|
+
logger.info("🔊 Audio session deactivation requested by CallKit...")
|
|
185
174
|
do {
|
|
186
175
|
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
|
|
187
176
|
logger.info("🔊 ✅ Audio session deactivated successfully")
|
|
177
|
+
delegate?.audioManagerDidDeactivateAudioSession(self)
|
|
188
178
|
} catch {
|
|
189
179
|
logger.error("🔊 ❌ Failed to deactivate audio session: \(error.localizedDescription)")
|
|
190
180
|
}
|
|
191
181
|
}
|
|
192
182
|
|
|
183
|
+
// MARK: - Internal Helper Methods
|
|
184
|
+
|
|
193
185
|
private func getCurrentAudioRoute() -> String {
|
|
194
186
|
let currentRoute = audioSession.currentRoute
|
|
195
187
|
|
|
@@ -202,11 +194,12 @@ class AudioManager {
|
|
|
202
194
|
return "Bluetooth"
|
|
203
195
|
case .builtInSpeaker:
|
|
204
196
|
return "Speaker"
|
|
205
|
-
case .headphones, .headsetMic
|
|
197
|
+
case .headphones, .headsetMic:
|
|
206
198
|
return "Headset"
|
|
207
199
|
case .builtInReceiver:
|
|
208
200
|
return "Earpiece"
|
|
209
201
|
default:
|
|
202
|
+
logger.debug("🔊 Unhandled output type: \(routeType.rawValue)")
|
|
210
203
|
continue
|
|
211
204
|
}
|
|
212
205
|
}
|
|
@@ -227,6 +220,8 @@ class AudioManager {
|
|
|
227
220
|
self.delegate?.audioManager(self, didChangeDevices: routeInfo)
|
|
228
221
|
}
|
|
229
222
|
|
|
223
|
+
// MARK: - Notification Handlers
|
|
224
|
+
|
|
230
225
|
private func handleAudioRouteChanged(notification: Notification) {
|
|
231
226
|
logger.info("🔊 Audio route changed notification received")
|
|
232
227
|
|
|
@@ -237,17 +232,20 @@ class AudioManager {
|
|
|
237
232
|
return
|
|
238
233
|
}
|
|
239
234
|
|
|
240
|
-
logger.info("🔊 Route change reason: \(reason)")
|
|
235
|
+
logger.info("🔊 Route change reason: \(reason.rawValue)")
|
|
241
236
|
|
|
242
237
|
switch reason {
|
|
243
|
-
case .newDeviceAvailable, .oldDeviceUnavailable:
|
|
244
|
-
logger.info("🔊 Audio device availability changed: \(reason)")
|
|
238
|
+
case AVAudioSession.RouteChangeReason.newDeviceAvailable, AVAudioSession.RouteChangeReason.oldDeviceUnavailable:
|
|
239
|
+
logger.info("🔊 Audio device availability changed: \(reason.rawValue)") // Use .rawValue
|
|
245
240
|
notifyDeviceChange()
|
|
246
|
-
case .override, .categoryChange:
|
|
247
|
-
logger.info("🔊 Audio route override or category change: \(reason)")
|
|
241
|
+
case AVAudioSession.RouteChangeReason.override, AVAudioSession.RouteChangeReason.categoryChange:
|
|
242
|
+
logger.info("🔊 Audio route override or category change: \(reason.rawValue)") // Use .rawValue
|
|
243
|
+
notifyRouteChange()
|
|
244
|
+
case AVAudioSession.RouteChangeReason.wakeFromSleep, AVAudioSession.RouteChangeReason.noSuitableRouteForCategory:
|
|
245
|
+
logger.info("🔊 Session recovered or no suitable route: \(reason.rawValue)") // Use .rawValue
|
|
248
246
|
notifyRouteChange()
|
|
249
247
|
default:
|
|
250
|
-
logger.info("🔊 Other audio route change reason: \(reason)")
|
|
248
|
+
logger.info("🔊 Other audio route change reason: \(reason.rawValue)") // Use .rawValue
|
|
251
249
|
notifyRouteChange()
|
|
252
250
|
}
|
|
253
251
|
}
|
package/ios/CallEngine.swift
CHANGED
|
@@ -40,6 +40,7 @@ public class CallEngine {
|
|
|
40
40
|
|
|
41
41
|
callKitManager = CallKitManager(delegate: self)
|
|
42
42
|
audioManager = AudioManager(delegate: self)
|
|
43
|
+
VoIPTokenManager.shared.setupPushKit()
|
|
43
44
|
|
|
44
45
|
isInitialized = true
|
|
45
46
|
logger.info("🚀 ✅ CallEngine initialized successfully")
|
|
@@ -54,7 +55,8 @@ public class CallEngine {
|
|
|
54
55
|
logger.debug("🚀 Getting current call state...")
|
|
55
56
|
let callsArray = self.activeCalls.values.map { $0.toJSONObject() }
|
|
56
57
|
do {
|
|
57
|
-
|
|
58
|
+
// Line 466 (previously)
|
|
59
|
+
let jsonData = try JSONSerialization.data(withJSONObject: callsArray as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
|
|
58
60
|
let result = String(data: jsonData, encoding: .utf8) ?? "[]"
|
|
59
61
|
logger.debug("🚀 Current call state: \(result)")
|
|
60
62
|
return result
|
|
@@ -85,16 +87,17 @@ public class CallEngine {
|
|
|
85
87
|
return
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
if
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
if !canMakeMultipleCalls {
|
|
91
|
+
if let activeCall = self.activeCalls.values.first(where: { $0.state == .active || $0.state == .held }) {
|
|
92
|
+
logger.warning("📞 ⚠️ Active call (\(activeCall.callId)) exists when receiving incoming call. Auto-rejecting: \(callId)")
|
|
93
|
+
rejectIncomingCallCollision(callId: callId, reason: "Another call is already active")
|
|
94
|
+
return
|
|
95
|
+
}
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
if
|
|
98
|
+
if canMakeMultipleCalls {
|
|
96
99
|
for call in self.activeCalls.values where call.state == .active {
|
|
97
|
-
logger.info("📞 Holding existing active call: \(call.callId)")
|
|
100
|
+
logger.info("📞 Holding existing active call: \(call.callId) before new incoming call")
|
|
98
101
|
holdCallInternal(callId: call.callId, heldBySystem: false)
|
|
99
102
|
}
|
|
100
103
|
}
|
|
@@ -111,6 +114,8 @@ public class CallEngine {
|
|
|
111
114
|
self.currentCallId = callId
|
|
112
115
|
logger.info("📞 Call added to active calls. Total: \(self.activeCalls.count)")
|
|
113
116
|
|
|
117
|
+
audioManager?.configureAudioSession(forCallType: callType == "Video", isIncoming: true)
|
|
118
|
+
|
|
114
119
|
self.callKitManager?.reportIncomingCall(callInfo: callInfo) { [weak self] error in
|
|
115
120
|
guard let self = self else { return }
|
|
116
121
|
if let error = error {
|
|
@@ -118,7 +123,6 @@ public class CallEngine {
|
|
|
118
123
|
self.endCallInternal(callId: callId)
|
|
119
124
|
} else {
|
|
120
125
|
self.logger.info("📞 ✅ Successfully reported incoming call for \(callId)")
|
|
121
|
-
self.audioManager?.configureForIncomingCall()
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
}
|
|
@@ -145,9 +149,9 @@ public class CallEngine {
|
|
|
145
149
|
return
|
|
146
150
|
}
|
|
147
151
|
|
|
148
|
-
if
|
|
152
|
+
if canMakeMultipleCalls {
|
|
149
153
|
for call in self.activeCalls.values where call.state == .active {
|
|
150
|
-
logger.info("📞 Holding existing active call: \(call.callId)")
|
|
154
|
+
logger.info("📞 Holding existing active call: \(call.callId) before new outgoing call")
|
|
151
155
|
holdCallInternal(callId: call.callId, heldBySystem: false)
|
|
152
156
|
}
|
|
153
157
|
}
|
|
@@ -164,14 +168,15 @@ public class CallEngine {
|
|
|
164
168
|
self.currentCallId = callId
|
|
165
169
|
logger.info("📞 Call added to active calls. Total: \(self.activeCalls.count)")
|
|
166
170
|
|
|
171
|
+
audioManager?.configureAudioSession(forCallType: callType == "Video", isIncoming: false)
|
|
172
|
+
|
|
167
173
|
self.callKitManager?.startOutgoingCall(callInfo: callInfo) { [weak self] error in
|
|
168
174
|
guard let self = self else { return }
|
|
169
175
|
if let error = error {
|
|
170
176
|
self.logger.error("📞 ❌ Failed to start outgoing call: \(error.localizedDescription)")
|
|
171
177
|
self.endCallInternal(callId: callId)
|
|
172
178
|
} else {
|
|
173
|
-
self.logger.info("📞 ✅ Successfully
|
|
174
|
-
self.audioManager?.configureForOutgoingCall(isVideo: callType == "Video")
|
|
179
|
+
self.logger.info("📞 ✅ Successfully initiated outgoing call for \(callId) via CallKit")
|
|
175
180
|
}
|
|
176
181
|
}
|
|
177
182
|
}
|
|
@@ -194,9 +199,9 @@ public class CallEngine {
|
|
|
194
199
|
return
|
|
195
200
|
}
|
|
196
201
|
|
|
197
|
-
if
|
|
202
|
+
if canMakeMultipleCalls {
|
|
198
203
|
for call in self.activeCalls.values where call.state == .active {
|
|
199
|
-
logger.info("📞 Holding existing active call: \(call.callId)")
|
|
204
|
+
logger.info("📞 Holding existing active call: \(call.callId) before new direct active call")
|
|
200
205
|
holdCallInternal(callId: call.callId, heldBySystem: false)
|
|
201
206
|
}
|
|
202
207
|
}
|
|
@@ -213,9 +218,10 @@ public class CallEngine {
|
|
|
213
218
|
self.currentCallId = callId
|
|
214
219
|
logger.info("📞 Call added as ACTIVE. Total: \(self.activeCalls.count)")
|
|
215
220
|
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
audioManager?.configureAudioSession(forCallType: callType == "Video", isIncoming: false)
|
|
222
|
+
audioManager?.activateAudioSession()
|
|
218
223
|
|
|
224
|
+
emitOutgoingCallAnsweredWithMetadata(callId: callId)
|
|
219
225
|
logger.info("📞 ✅ Call \(callId) started as ACTIVE")
|
|
220
226
|
}
|
|
221
227
|
|
|
@@ -226,7 +232,19 @@ public class CallEngine {
|
|
|
226
232
|
|
|
227
233
|
public func answerCall(callId: String) {
|
|
228
234
|
logger.info("📞 Local party answering: \(callId)")
|
|
229
|
-
|
|
235
|
+
if let uuid = UUID(uuidString: callId) {
|
|
236
|
+
let answerAction = CXAnswerCallAction(call: uuid)
|
|
237
|
+
let transaction = CXTransaction(action: answerAction)
|
|
238
|
+
callKitManager?.callController.request(transaction) { error in
|
|
239
|
+
if let error = error {
|
|
240
|
+
self.logger.error("📞 ❌ Failed to request CallKit answer: \(error.localizedDescription)")
|
|
241
|
+
} else {
|
|
242
|
+
self.logger.info("📞 ✅ Requested CallKit to answer call \(callId)")
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
logger.warning("📞 ⚠️ Invalid UUID for callId: \(callId)")
|
|
247
|
+
}
|
|
230
248
|
}
|
|
231
249
|
|
|
232
250
|
private func coreCallAnswered(callId: String, isLocalAnswer: Bool) {
|
|
@@ -244,11 +262,11 @@ public class CallEngine {
|
|
|
244
262
|
|
|
245
263
|
logger.info("📞 Call state updated: \(previousState.stringValue) → \(CallState.active.stringValue)")
|
|
246
264
|
|
|
247
|
-
|
|
265
|
+
audioManager?.configureAudioSession(forCallType: callInfo.callType == "Video", isIncoming: false)
|
|
248
266
|
|
|
249
267
|
if !canMakeMultipleCalls {
|
|
250
|
-
for call in self.activeCalls.values where call.callId != callId && call.state == .active {
|
|
251
|
-
logger.info("📞 Holding other active call: \(call.callId)")
|
|
268
|
+
for call in self.activeCalls.values where call.callId != callId && (call.state == .active || call.state == .incoming) {
|
|
269
|
+
logger.info("📞 Holding other active/incoming call: \(call.callId)")
|
|
252
270
|
holdCallInternal(callId: call.callId, heldBySystem: false)
|
|
253
271
|
}
|
|
254
272
|
}
|
|
@@ -282,19 +300,22 @@ public class CallEngine {
|
|
|
282
300
|
private func holdCallInternal(callId: String, heldBySystem: Bool) {
|
|
283
301
|
logger.info("📞 Holding call internally: callId=\(callId), heldBySystem=\(heldBySystem)")
|
|
284
302
|
|
|
285
|
-
guard var callInfo = self.activeCalls[callId]
|
|
286
|
-
logger.warning("📞 ⚠️ Cannot hold call \(callId) - not
|
|
303
|
+
guard var callInfo = self.activeCalls[callId] else {
|
|
304
|
+
logger.warning("📞 ⚠️ Cannot hold call \(callId) - not found")
|
|
287
305
|
return
|
|
288
306
|
}
|
|
289
307
|
|
|
290
|
-
callInfo.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
self.callKitManager?.setCallOnHold(callId: callId, onHold: true)
|
|
295
|
-
emitEvent(.callHeld, data: ["callId": callId])
|
|
308
|
+
if callInfo.state == .active {
|
|
309
|
+
callInfo.updateState(.held)
|
|
310
|
+
callInfo.wasHeldBySystem = heldBySystem
|
|
311
|
+
self.activeCalls[callId] = callInfo
|
|
296
312
|
|
|
297
|
-
|
|
313
|
+
self.callKitManager?.setCallOnHold(callId: callId, onHold: true)
|
|
314
|
+
emitEvent(.callHeld, data: ["callId": callId])
|
|
315
|
+
logger.info("📞 ✅ Call \(callId) held successfully")
|
|
316
|
+
} else {
|
|
317
|
+
logger.warning("📞 ⚠️ Cannot hold call \(callId) from state \(callInfo.state.stringValue). Expected .active")
|
|
318
|
+
}
|
|
298
319
|
}
|
|
299
320
|
|
|
300
321
|
public func unholdCall(callId: String) {
|
|
@@ -304,19 +325,23 @@ public class CallEngine {
|
|
|
304
325
|
private func unholdCallInternal(callId: String, resumedBySystem: Bool) {
|
|
305
326
|
logger.info("📞 Unholding call internally: callId=\(callId), resumedBySystem=\(resumedBySystem)")
|
|
306
327
|
|
|
307
|
-
guard var callInfo = self.activeCalls[callId]
|
|
308
|
-
logger.warning("📞 ⚠️ Cannot unhold call \(callId) - not
|
|
328
|
+
guard var callInfo = self.activeCalls[callId] else {
|
|
329
|
+
logger.warning("📞 ⚠️ Cannot unhold call \(callId) - not found")
|
|
309
330
|
return
|
|
310
331
|
}
|
|
311
332
|
|
|
312
|
-
callInfo.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
self.callKitManager?.setCallOnHold(callId: callId, onHold: false)
|
|
317
|
-
emitEvent(.callUnheld, data: ["callId": callId])
|
|
333
|
+
if callInfo.state == .held {
|
|
334
|
+
callInfo.updateState(.active)
|
|
335
|
+
callInfo.wasHeldBySystem = false
|
|
336
|
+
self.activeCalls[callId] = callInfo
|
|
318
337
|
|
|
319
|
-
|
|
338
|
+
self.currentCallId = callId
|
|
339
|
+
self.callKitManager?.setCallOnHold(callId: callId, onHold: false)
|
|
340
|
+
emitEvent(.callUnheld, data: ["callId": callId])
|
|
341
|
+
logger.info("📞 ✅ Call \(callId) unheld successfully")
|
|
342
|
+
} else {
|
|
343
|
+
logger.warning("📞 ⚠️ Cannot unhold call \(callId) from state \(callInfo.state.stringValue). Expected .held")
|
|
344
|
+
}
|
|
320
345
|
}
|
|
321
346
|
|
|
322
347
|
public func setMuted(callId: String, muted: Bool) {
|
|
@@ -327,7 +352,8 @@ public class CallEngine {
|
|
|
327
352
|
return
|
|
328
353
|
}
|
|
329
354
|
|
|
330
|
-
self.
|
|
355
|
+
self.callKitManager?.setMuted(callId: callId, muted: muted)
|
|
356
|
+
|
|
331
357
|
let eventType: CallEventType = muted ? .callMuted : .callUnmuted
|
|
332
358
|
emitEvent(eventType, data: ["callId": callId])
|
|
333
359
|
logger.info("📞 ✅ Call \(callId) mute state changed to: \(muted)")
|
|
@@ -335,7 +361,21 @@ public class CallEngine {
|
|
|
335
361
|
|
|
336
362
|
public func endCall(callId: String) {
|
|
337
363
|
logger.info("📞 Ending call: \(callId)")
|
|
338
|
-
|
|
364
|
+
if let uuid = UUID(uuidString: callId) {
|
|
365
|
+
let endCallAction = CXEndCallAction(call: uuid)
|
|
366
|
+
let transaction = CXTransaction(action: endCallAction)
|
|
367
|
+
callKitManager?.callController.request(transaction) { error in
|
|
368
|
+
if let error = error {
|
|
369
|
+
self.logger.error("📞 ❌ Failed to request CallKit end call: \(error.localizedDescription)")
|
|
370
|
+
self.endCallInternal(callId: callId)
|
|
371
|
+
} else {
|
|
372
|
+
self.logger.info("📞 ✅ Requested CallKit to end call \(callId)")
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
logger.warning("📞 ⚠️ Invalid UUID for end call: \(callId)")
|
|
377
|
+
self.endCallInternal(callId: callId)
|
|
378
|
+
}
|
|
339
379
|
}
|
|
340
380
|
|
|
341
381
|
public func endAllCalls() {
|
|
@@ -344,22 +384,17 @@ public class CallEngine {
|
|
|
344
384
|
let callIds = Array(self.activeCalls.keys)
|
|
345
385
|
for callId in callIds {
|
|
346
386
|
logger.info("📞 Ending call: \(callId)")
|
|
347
|
-
|
|
387
|
+
endCall(callId: callId)
|
|
348
388
|
}
|
|
349
389
|
|
|
350
|
-
|
|
351
|
-
self.callMetadata.removeAll()
|
|
352
|
-
self.currentCallId = nil
|
|
353
|
-
|
|
354
|
-
self.audioManager?.cleanup()
|
|
355
|
-
logger.info("📞 ✅ All calls ended and cleanup completed")
|
|
390
|
+
logger.info("📞 ✅ All calls termination initiated")
|
|
356
391
|
}
|
|
357
392
|
|
|
358
393
|
private func endCallInternal(callId: String) {
|
|
359
394
|
logger.info("📞 Ending call internally: \(callId)")
|
|
360
395
|
|
|
361
396
|
guard var callInfo = self.activeCalls[callId] else {
|
|
362
|
-
logger.warning("📞 ⚠️ Call \(callId) not found in active calls")
|
|
397
|
+
logger.warning("📞 ⚠️ Call \(callId) not found in active calls (already ended?)")
|
|
363
398
|
return
|
|
364
399
|
}
|
|
365
400
|
|
|
@@ -368,15 +403,13 @@ public class CallEngine {
|
|
|
368
403
|
self.activeCalls.removeValue(forKey: callId)
|
|
369
404
|
|
|
370
405
|
if self.currentCallId == callId {
|
|
371
|
-
self.currentCallId = self.activeCalls.values.first { $0.state
|
|
406
|
+
self.currentCallId = self.activeCalls.values.first { $0.state == .active || $0.state == .held || $0.state == .dialing || $0.state == .incoming }?.callId
|
|
372
407
|
logger.info("📞 Current call ID updated to: \(self.currentCallId ?? "nil")")
|
|
373
408
|
}
|
|
374
409
|
|
|
375
|
-
self.callKitManager?.endCall(callId: callId)
|
|
376
|
-
|
|
377
410
|
if self.activeCalls.isEmpty {
|
|
378
|
-
logger.info("📞 No more active calls,
|
|
379
|
-
|
|
411
|
+
logger.info("📞 No more active calls, deactivating audio session")
|
|
412
|
+
audioManager?.deactivateAudioSession()
|
|
380
413
|
}
|
|
381
414
|
|
|
382
415
|
logger.info("📞 Notifying \(self.callEndListeners.count) internal call end listeners")
|
|
@@ -430,7 +463,7 @@ public class CallEngine {
|
|
|
430
463
|
if let handler = handler, !self.cachedEvents.isEmpty {
|
|
431
464
|
logger.info("📡 Emitting \(self.cachedEvents.count) cached events")
|
|
432
465
|
for (type, data) in self.cachedEvents {
|
|
433
|
-
logger.debug("📡 Emitting cached event: \(type)")
|
|
466
|
+
logger.debug("📡 Emitting cached event: \(type.rawValue)")
|
|
434
467
|
handler(type, data)
|
|
435
468
|
}
|
|
436
469
|
self.cachedEvents.removeAll()
|
|
@@ -439,18 +472,19 @@ public class CallEngine {
|
|
|
439
472
|
}
|
|
440
473
|
|
|
441
474
|
private func emitEvent(_ type: CallEventType, data: [String: Any]) {
|
|
442
|
-
logger.info("📡 Emitting event: \(type)")
|
|
475
|
+
logger.info("📡 Emitting event: \(type.rawValue)")
|
|
443
476
|
logger.debug("📡 Event data: \(data)")
|
|
444
477
|
|
|
445
478
|
do {
|
|
446
|
-
|
|
479
|
+
// Line 486 (previously)
|
|
480
|
+
let jsonData = try JSONSerialization.data(withJSONObject: data as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
|
|
447
481
|
let dataString = String(data: jsonData, encoding: .utf8) ?? "{}"
|
|
448
482
|
|
|
449
483
|
if let eventHandler = self.eventHandler {
|
|
450
484
|
logger.debug("📡 Calling event handler with data: \(dataString)")
|
|
451
485
|
eventHandler(type, dataString)
|
|
452
486
|
} else {
|
|
453
|
-
logger.info("📡 No event handler, caching event: \(type)")
|
|
487
|
+
logger.info("📡 No event handler, caching event: \(type.rawValue)")
|
|
454
488
|
self.cachedEvents.append((type, dataString))
|
|
455
489
|
}
|
|
456
490
|
} catch {
|
|
@@ -565,7 +599,7 @@ public class CallEngine {
|
|
|
565
599
|
extension CallEngine: CallKitManagerDelegate {
|
|
566
600
|
func callKitManager(_ manager: CallKitManager, didAnswerCall callId: String) {
|
|
567
601
|
logger.info("📲 CallKit delegate: answer call \(callId)")
|
|
568
|
-
|
|
602
|
+
coreCallAnswered(callId: callId, isLocalAnswer: true)
|
|
569
603
|
}
|
|
570
604
|
|
|
571
605
|
func callKitManager(_ manager: CallKitManager, didEndCall callId: String) {
|
|
@@ -586,6 +620,25 @@ extension CallEngine: CallKitManagerDelegate {
|
|
|
586
620
|
logger.info("📲 CallKit delegate: set muted \(callId), muted: \(muted)")
|
|
587
621
|
setMuted(callId: callId, muted: muted)
|
|
588
622
|
}
|
|
623
|
+
|
|
624
|
+
func callKitManager(_ manager: CallKitManager, didStartOutgoingCall callId: String) {
|
|
625
|
+
logger.info("📲 CallKit delegate: did start outgoing call \(callId)")
|
|
626
|
+
if var callInfo = activeCalls[callId], callInfo.state == .dialing {
|
|
627
|
+
callInfo.updateState(.active)
|
|
628
|
+
activeCalls[callId] = callInfo
|
|
629
|
+
logger.info("📞 Call \(callId) transitioned from DIALING to ACTIVE as reported by CallKit")
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
func callKitManager(_ manager: CallKitManager, didActivateAudioSession session: AVAudioSession) {
|
|
634
|
+
logger.info("📲 CallKit delegate: did activate audio session")
|
|
635
|
+
audioManager?.activateAudioSession()
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
func callKitManager(_ manager: CallKitManager, didDeactivateAudioSession session: AVAudioSession) {
|
|
639
|
+
logger.info("📲 CallKit delegate: did deactivate audio session")
|
|
640
|
+
audioManager?.deactivateAudioSession()
|
|
641
|
+
}
|
|
589
642
|
}
|
|
590
643
|
|
|
591
644
|
extension CallEngine: AudioManagerDelegate {
|
|
@@ -606,4 +659,12 @@ extension CallEngine: AudioManagerDelegate {
|
|
|
606
659
|
]
|
|
607
660
|
emitEvent(.audioDevicesChanged, data: eventData)
|
|
608
661
|
}
|
|
662
|
+
|
|
663
|
+
func audioManagerDidActivateAudioSession(_ manager: AudioManager) {
|
|
664
|
+
logger.info("🔊 Audio manager delegate: audio session activated")
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
func audioManagerDidDeactivateAudioSession(_ manager: AudioManager) {
|
|
668
|
+
logger.info("🔊 Audio manager delegate: audio session deactivated")
|
|
669
|
+
}
|
|
609
670
|
}
|
package/ios/CallInfo.swift
CHANGED
|
@@ -29,13 +29,13 @@ public struct CallInfo {
|
|
|
29
29
|
mutating func updateState(_ newState: CallState) {
|
|
30
30
|
let oldState = self.state
|
|
31
31
|
self.state = newState
|
|
32
|
-
Self.logger.info("📱 CallInfo state changed:
|
|
32
|
+
Self.logger.info("📱 CallInfo state changed: c\\ \(oldState.stringValue) → \(newState.stringValue)")
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
mutating func updateDisplayName(_ newName: String) {
|
|
36
36
|
let oldName = self.displayName
|
|
37
37
|
self.displayName = newName
|
|
38
|
-
Self.logger.info("📱 CallInfo display name updated:
|
|
38
|
+
Self.logger.info("📱 CallInfo display name updated: ca, '\(oldName)' → '\(newName)'")
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
func toJSONObject() -> [String: Any] {
|