@qusaieilouti99/call-manager 0.1.78 → 0.1.79
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/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallManager.kt +11 -0
- package/ios/AudioManager.swift +285 -0
- package/ios/CallEngine.swift +635 -0
- package/ios/CallInfo.swift +66 -0
- package/ios/CallKitManager.swift +238 -0
- package/ios/CallManager.swift +145 -3
- package/ios/VoIPTokenManager.swift +158 -0
- package/lib/module/CallManager.nitro.js.map +1 -1
- package/lib/typescript/src/CallManager.nitro.d.ts +2 -0
- package/lib/typescript/src/CallManager.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridCallManagerSpec.cpp +20 -0
- package/nitrogen/generated/android/c++/JHybridCallManagerSpec.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/qusaieilouti99/callmanager/Func_void_std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/qusaieilouti99/callmanager/HybridCallManagerSpec.kt +13 -0
- package/nitrogen/generated/android/qusaieilouti99_callmanagerOnLoad.cpp +2 -0
- package/nitrogen/generated/ios/CallManager-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/CallManager-Swift-Cxx-Bridge.hpp +22 -0
- package/nitrogen/generated/ios/c++/HybridCallManagerSpecSwift.hpp +14 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridCallManagerSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridCallManagerSpec_cxx.swift +31 -0
- package/nitrogen/generated/shared/c++/HybridCallManagerSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridCallManagerSpec.hpp +2 -0
- package/package.json +1 -1
- package/src/CallManager.nitro.ts +8 -0
|
@@ -97,4 +97,15 @@ class CallManager : HybridCallManagerSpec() {
|
|
|
97
97
|
ensureInitialized()
|
|
98
98
|
CallEngine.setMuted(callId, muted)
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
override fun updateDisplayCallInformation(callId: String, callerName: String): Unit {
|
|
102
|
+
// do nothing for now
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
override fun registerVoIPTokenListener(listener: (payload: String) -> Unit): () -> Unit
|
|
106
|
+
Log.d(TAG, "registerVoIPTokenListener called")
|
|
107
|
+
return {
|
|
108
|
+
Log.d(TAG, "registerVoIPTokenListener removed.")
|
|
109
|
+
}
|
|
110
|
+
}
|
|
100
111
|
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import AVFoundation
|
|
3
|
+
import OSLog
|
|
4
|
+
|
|
5
|
+
protocol AudioManagerDelegate: AnyObject {
|
|
6
|
+
func audioManager(_ manager: AudioManager, didChangeRoute routeInfo: AudioRoutesInfo)
|
|
7
|
+
func audioManager(_ manager: AudioManager, didChangeDevices routeInfo: AudioRoutesInfo)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class AudioManager: NSObject {
|
|
11
|
+
private let logger = Logger(subsystem: "com.qusaieilouti99.callmanager", category: "AudioManager")
|
|
12
|
+
private weak var delegate: AudioManagerDelegate?
|
|
13
|
+
private var audioSession: AVAudioSession
|
|
14
|
+
private var lastRouteInfo: AudioRoutesInfo?
|
|
15
|
+
|
|
16
|
+
init(delegate: AudioManagerDelegate) {
|
|
17
|
+
self.delegate = delegate
|
|
18
|
+
self.audioSession = AVAudioSession.sharedInstance()
|
|
19
|
+
super.init()
|
|
20
|
+
|
|
21
|
+
logger.info("🔊 AudioManager initializing...")
|
|
22
|
+
setupNotifications()
|
|
23
|
+
logger.info("🔊 ✅ AudioManager initialized successfully")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
deinit {
|
|
27
|
+
logger.info("🔊 AudioManager deinitializing...")
|
|
28
|
+
NotificationCenter.default.removeObserver(self)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// MARK: - Setup
|
|
32
|
+
private func setupNotifications() {
|
|
33
|
+
logger.info("🔊 Setting up audio session notifications...")
|
|
34
|
+
|
|
35
|
+
NotificationCenter.default.addObserver(
|
|
36
|
+
self,
|
|
37
|
+
selector: #selector(audioRouteChanged),
|
|
38
|
+
name: AVAudioSession.routeChangeNotification,
|
|
39
|
+
object: nil
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
NotificationCenter.default.addObserver(
|
|
43
|
+
self,
|
|
44
|
+
selector: #selector(audioSessionInterrupted),
|
|
45
|
+
name: AVAudioSession.interruptionNotification,
|
|
46
|
+
object: nil
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
logger.info("🔊 ✅ Audio session notifications setup completed")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// MARK: - Public Methods
|
|
53
|
+
func configureForIncomingCall() {
|
|
54
|
+
logger.info("🔊 Configuring audio session for incoming call...")
|
|
55
|
+
|
|
56
|
+
do {
|
|
57
|
+
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .allowBluetoothA2DP])
|
|
58
|
+
try audioSession.setActive(true)
|
|
59
|
+
logger.info("🔊 ✅ Audio session configured for incoming call")
|
|
60
|
+
} catch {
|
|
61
|
+
logger.error("🔊 ❌ Failed to configure audio session for incoming call: \(error.localizedDescription)")
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func configureForOutgoingCall(isVideo: Bool) {
|
|
66
|
+
logger.info("🔊 Configuring audio session for outgoing call (video: \(isVideo))...")
|
|
67
|
+
|
|
68
|
+
do {
|
|
69
|
+
let options: AVAudioSession.CategoryOptions = isVideo ?
|
|
70
|
+
[.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker] :
|
|
71
|
+
[.allowBluetooth, .allowBluetoothA2DP]
|
|
72
|
+
|
|
73
|
+
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: options)
|
|
74
|
+
try audioSession.setActive(true)
|
|
75
|
+
logger.info("🔊 ✅ Audio session configured for outgoing call (video: \(isVideo))")
|
|
76
|
+
} catch {
|
|
77
|
+
logger.error("🔊 ❌ Failed to configure audio session for outgoing call: \(error.localizedDescription)")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func configureForActiveCall(isVideo: Bool) {
|
|
82
|
+
logger.info("🔊 Configuring audio session for active call (video: \(isVideo))...")
|
|
83
|
+
|
|
84
|
+
do {
|
|
85
|
+
let options: AVAudioSession.CategoryOptions = isVideo ?
|
|
86
|
+
[.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker] :
|
|
87
|
+
[.allowBluetooth, .allowBluetoothA2DP]
|
|
88
|
+
|
|
89
|
+
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: options)
|
|
90
|
+
try audioSession.setActive(true)
|
|
91
|
+
logger.info("🔊 ✅ Audio session configured for active call (video: \(isVideo))")
|
|
92
|
+
} catch {
|
|
93
|
+
logger.error("🔊 ❌ Failed to configure audio session for active call: \(error.localizedDescription)")
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func getAudioDevices() -> AudioRoutesInfo {
|
|
98
|
+
logger.debug("🔊 Getting available audio devices...")
|
|
99
|
+
|
|
100
|
+
let currentRoute = audioSession.currentRoute
|
|
101
|
+
var devices: [String] = ["Earpiece", "Speaker"]
|
|
102
|
+
|
|
103
|
+
logger.debug("🔊 Current route inputs: \(currentRoute.inputs.map { $0.portType.rawValue })")
|
|
104
|
+
logger.debug("🔊 Current route outputs: \(currentRoute.outputs.map { $0.portType.rawValue })")
|
|
105
|
+
|
|
106
|
+
// Check for available inputs/outputs
|
|
107
|
+
if let availableInputs = audioSession.availableInputs {
|
|
108
|
+
logger.debug("🔊 Available inputs: \(availableInputs.map { $0.portType.rawValue })")
|
|
109
|
+
|
|
110
|
+
for input in availableInputs {
|
|
111
|
+
switch input.portType {
|
|
112
|
+
case .bluetoothHFP, .bluetoothA2DP, .bluetoothLE:
|
|
113
|
+
if !devices.contains("Bluetooth") {
|
|
114
|
+
devices.append("Bluetooth")
|
|
115
|
+
logger.debug("🔊 Added Bluetooth device")
|
|
116
|
+
}
|
|
117
|
+
case .headphones, .headsetMic, .wiredHeadphones:
|
|
118
|
+
if !devices.contains("Headset") {
|
|
119
|
+
devices.append("Headset")
|
|
120
|
+
logger.debug("🔊 Added Headset device")
|
|
121
|
+
}
|
|
122
|
+
default:
|
|
123
|
+
logger.debug("🔊 Other input type: \(input.portType.rawValue)")
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Determine current route
|
|
130
|
+
let currentRouteString = getCurrentAudioRoute()
|
|
131
|
+
|
|
132
|
+
let routeInfo = AudioRoutesInfo(devices: devices, currentRoute: currentRouteString)
|
|
133
|
+
lastRouteInfo = routeInfo
|
|
134
|
+
|
|
135
|
+
logger.info("🔊 Available audio devices: \(devices), current: \(currentRouteString)")
|
|
136
|
+
return routeInfo
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func setAudioRoute(_ route: String) {
|
|
140
|
+
logger.info("🔊 Setting audio route to: \(route)")
|
|
141
|
+
|
|
142
|
+
let previousRoute = getCurrentAudioRoute()
|
|
143
|
+
logger.debug("🔊 Previous route: \(previousRoute)")
|
|
144
|
+
|
|
145
|
+
do {
|
|
146
|
+
switch route {
|
|
147
|
+
case "Speaker":
|
|
148
|
+
logger.debug("🔊 Overriding to speaker...")
|
|
149
|
+
try audioSession.overrideOutputAudioPort(.speaker)
|
|
150
|
+
case "Earpiece":
|
|
151
|
+
logger.debug("🔊 Overriding to earpiece...")
|
|
152
|
+
try audioSession.overrideOutputAudioPort(.none)
|
|
153
|
+
case "Bluetooth":
|
|
154
|
+
logger.debug("🔊 Setting to Bluetooth (system managed)...")
|
|
155
|
+
try audioSession.overrideOutputAudioPort(.none)
|
|
156
|
+
// Bluetooth routing is handled automatically by the system
|
|
157
|
+
case "Headset":
|
|
158
|
+
logger.debug("🔊 Setting to Headset (system managed)...")
|
|
159
|
+
try audioSession.overrideOutputAudioPort(.none)
|
|
160
|
+
// Headset routing is handled automatically by the system
|
|
161
|
+
default:
|
|
162
|
+
logger.warning("🔊 ⚠️ Unknown audio route: \(route)")
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let newRoute = getCurrentAudioRoute()
|
|
167
|
+
logger.info("🔊 Audio route changed: \(previousRoute) → \(newRoute)")
|
|
168
|
+
|
|
169
|
+
if previousRoute != newRoute {
|
|
170
|
+
notifyRouteChange()
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
logger.error("🔊 ❌ Failed to set audio route to \(route): \(error.localizedDescription)")
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func setMuted(_ muted: Bool) {
|
|
178
|
+
logger.info("🔊 Mute state changed to: \(muted)")
|
|
179
|
+
// Note: Muting is typically handled by CallKit automatically
|
|
180
|
+
// This is here for consistency with Android implementation
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
func cleanup() {
|
|
184
|
+
logger.info("🔊 Cleaning up audio session...")
|
|
185
|
+
|
|
186
|
+
do {
|
|
187
|
+
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
|
|
188
|
+
logger.info("🔊 ✅ Audio session deactivated successfully")
|
|
189
|
+
} catch {
|
|
190
|
+
logger.error("🔊 ❌ Failed to deactivate audio session: \(error.localizedDescription)")
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// MARK: - Private Methods
|
|
195
|
+
private func getCurrentAudioRoute() -> String {
|
|
196
|
+
let currentRoute = audioSession.currentRoute
|
|
197
|
+
|
|
198
|
+
for output in currentRoute.outputs {
|
|
199
|
+
let routeType = output.portType
|
|
200
|
+
logger.debug("🔊 Checking output port: \(routeType.rawValue)")
|
|
201
|
+
|
|
202
|
+
switch routeType {
|
|
203
|
+
case .bluetoothHFP, .bluetoothA2DP, .bluetoothLE:
|
|
204
|
+
return "Bluetooth"
|
|
205
|
+
case .builtInSpeaker:
|
|
206
|
+
return "Speaker"
|
|
207
|
+
case .headphones, .headsetMic, .wiredHeadphones:
|
|
208
|
+
return "Headset"
|
|
209
|
+
case .builtInReceiver:
|
|
210
|
+
return "Earpiece"
|
|
211
|
+
default:
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
logger.debug("🔊 No specific route found, defaulting to Earpiece")
|
|
217
|
+
return "Earpiece" // Default fallback
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private func notifyRouteChange() {
|
|
221
|
+
logger.debug("🔊 Notifying delegate about route change...")
|
|
222
|
+
let routeInfo = getAudioDevices()
|
|
223
|
+
delegate?.audioManager(self, didChangeRoute: routeInfo)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private func notifyDeviceChange() {
|
|
227
|
+
logger.debug("🔊 Notifying delegate about device change...")
|
|
228
|
+
let routeInfo = getAudioDevices()
|
|
229
|
+
delegate?.audioManager(self, didChangeDevices: routeInfo)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// MARK: - Notification Handlers
|
|
233
|
+
@objc private func audioRouteChanged(notification: Notification) {
|
|
234
|
+
logger.info("🔊 Audio route changed notification received")
|
|
235
|
+
|
|
236
|
+
guard let userInfo = notification.userInfo,
|
|
237
|
+
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
|
238
|
+
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
|
|
239
|
+
logger.warning("🔊 ⚠️ Could not parse route change reason")
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
logger.info("🔊 Route change reason: \(reason)")
|
|
244
|
+
|
|
245
|
+
switch reason {
|
|
246
|
+
case .newDeviceAvailable, .oldDeviceUnavailable:
|
|
247
|
+
logger.info("🔊 Audio device availability changed: \(reason)")
|
|
248
|
+
notifyDeviceChange()
|
|
249
|
+
case .override, .categoryChange:
|
|
250
|
+
logger.info("🔊 Audio route override or category change: \(reason)")
|
|
251
|
+
notifyRouteChange()
|
|
252
|
+
default:
|
|
253
|
+
logger.info("🔊 Other audio route change reason: \(reason)")
|
|
254
|
+
notifyRouteChange()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@objc private func audioSessionInterrupted(notification: Notification) {
|
|
259
|
+
logger.info("🔊 Audio session interrupted notification received")
|
|
260
|
+
|
|
261
|
+
guard let userInfo = notification.userInfo,
|
|
262
|
+
let interruptionTypeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
263
|
+
let interruptionType = AVAudioSession.InterruptionType(rawValue: interruptionTypeValue) else {
|
|
264
|
+
logger.warning("🔊 ⚠️ Could not parse interruption type")
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
switch interruptionType {
|
|
269
|
+
case .began:
|
|
270
|
+
logger.info("🔊 Audio session interruption began")
|
|
271
|
+
case .ended:
|
|
272
|
+
logger.info("🔊 Audio session interruption ended")
|
|
273
|
+
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
|
|
274
|
+
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
|
275
|
+
if options.contains(.shouldResume) {
|
|
276
|
+
logger.info("🔊 Should resume audio session")
|
|
277
|
+
// Audio session will be reactivated by CallKit
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
@unknown default:
|
|
281
|
+
logger.warning("🔊 ⚠️ Unknown interruption type")
|
|
282
|
+
break
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|