@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.
Files changed (26) hide show
  1. package/android/src/main/java/com/margelo/nitro/qusaieilouti99/callmanager/CallManager.kt +11 -0
  2. package/ios/AudioManager.swift +285 -0
  3. package/ios/CallEngine.swift +635 -0
  4. package/ios/CallInfo.swift +66 -0
  5. package/ios/CallKitManager.swift +238 -0
  6. package/ios/CallManager.swift +145 -3
  7. package/ios/VoIPTokenManager.swift +158 -0
  8. package/lib/module/CallManager.nitro.js.map +1 -1
  9. package/lib/typescript/src/CallManager.nitro.d.ts +2 -0
  10. package/lib/typescript/src/CallManager.nitro.d.ts.map +1 -1
  11. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +75 -0
  12. package/nitrogen/generated/android/c++/JHybridCallManagerSpec.cpp +20 -0
  13. package/nitrogen/generated/android/c++/JHybridCallManagerSpec.hpp +2 -0
  14. package/nitrogen/generated/android/kotlin/com/margelo/nitro/qusaieilouti99/callmanager/Func_void_std__string.kt +80 -0
  15. package/nitrogen/generated/android/kotlin/com/margelo/nitro/qusaieilouti99/callmanager/HybridCallManagerSpec.kt +13 -0
  16. package/nitrogen/generated/android/qusaieilouti99_callmanagerOnLoad.cpp +2 -0
  17. package/nitrogen/generated/ios/CallManager-Swift-Cxx-Bridge.cpp +8 -0
  18. package/nitrogen/generated/ios/CallManager-Swift-Cxx-Bridge.hpp +22 -0
  19. package/nitrogen/generated/ios/c++/HybridCallManagerSpecSwift.hpp +14 -0
  20. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  21. package/nitrogen/generated/ios/swift/HybridCallManagerSpec.swift +2 -0
  22. package/nitrogen/generated/ios/swift/HybridCallManagerSpec_cxx.swift +31 -0
  23. package/nitrogen/generated/shared/c++/HybridCallManagerSpec.cpp +2 -0
  24. package/nitrogen/generated/shared/c++/HybridCallManagerSpec.hpp +2 -0
  25. package/package.json +1 -1
  26. 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
+ }