@poolse/react-native-calls 0.4.3 → 0.4.4

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.
@@ -224,6 +224,7 @@ final class CallKitController: NSObject, CXProviderDelegate {
224
224
  }
225
225
 
226
226
  func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
227
+ pcTrace("CallKit answer action")
227
228
  guard let t = calls[action.callUUID], !t.ended else { action.fail(); return }
228
229
  let fromUs = t.answered
229
230
  t.answered = true
@@ -259,10 +260,12 @@ final class CallKitController: NSObject, CXProviderDelegate {
259
260
  }
260
261
 
261
262
  func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
263
+ pcTrace("CallKit didActivate \(pcSessionDescription(audioSession))")
262
264
  actions?.callKitAudioActivated(audioSession)
263
265
  }
264
266
 
265
267
  func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
268
+ pcTrace("CallKit didDeactivate")
266
269
  actions?.callKitAudioDeactivated(audioSession)
267
270
  }
268
271
  }
@@ -26,6 +26,21 @@ protocol MediaEvents: AnyObject {
26
26
  func onLocalCamera(on: Bool, facing: String?)
27
27
  }
28
28
 
29
+ /// Step-by-step trace of the audio path, in the device log as
30
+ /// "[PoolseCalls] trace …" — the order of activation, engine availability,
31
+ /// connect and publish is what cold-answer audio failures hinge on.
32
+ func pcTrace(_ message: @autoclosure () -> String) {
33
+ NSLog("[PoolseCalls] trace %@", message())
34
+ }
35
+
36
+ func pcSessionDescription(_ s: AVAudioSession = .sharedInstance()) -> String {
37
+ let outs = s.currentRoute.outputs.map { $0.portType.rawValue }.joined(separator: ",")
38
+ let ins = s.currentRoute.inputs.map { $0.portType.rawValue }.joined(separator: ",")
39
+ return "category=\(s.category.rawValue) mode=\(s.mode.rawValue) in=[\(ins)] out=[\(outs)] " +
40
+ "otherAudio=\(s.isOtherAudioPlaying) inputAvailable=\(s.isInputAvailable) " +
41
+ "record=\(s.recordPermission.rawValue)"
42
+ }
43
+
29
44
  /// The shipped MediaPort over LiveKit's Swift SDK — LiveKitMediaPort.kt
30
45
  /// for iOS. Joins with the server-minted token, publishes the mic (and
31
46
  /// the camera for video), tracks the roster and reports room transitions.
@@ -83,6 +98,7 @@ final class LiveKitMediaPort: NSObject, MediaPort {
83
98
  func prepareAudio() {
84
99
  guard !audioPrepared else { return }
85
100
  audioPrepared = true
101
+ pcTrace("prepareAudio (automatic config off, engine .none)")
86
102
  AudioManager.shared.audioSession.isAutomaticConfigurationEnabled = false
87
103
  do { try AudioManager.shared.setEngineAvailability(.none) } catch {
88
104
  NSLog("[PoolseCalls] setEngineAvailability(.none) failed: %@", "\(error)")
@@ -91,6 +107,7 @@ final class LiveKitMediaPort: NSObject, MediaPort {
91
107
 
92
108
  /// CallKit activated the session (or, without CallKit, we did).
93
109
  func audioActivated(_ session: AVAudioSession, media: String) {
110
+ pcTrace("audioActivated media=\(media) room=\(room != nil) connected=\(connectedRoom != nil) \(pcSessionDescription(session))")
94
111
  prepareAudio()
95
112
  audioActive = true
96
113
  if media != self.media && room == nil { self.media = media }
@@ -115,6 +132,7 @@ final class LiveKitMediaPort: NSObject, MediaPort {
115
132
  audioKicked != ObjectIdentifier(r), !mutedWanted else { return }
116
133
  guard r.localParticipant.isMicrophoneEnabled() else { return }
117
134
  audioKicked = ObjectIdentifier(r)
135
+ pcTrace("kick: cycling engine availability + mic")
118
136
  do {
119
137
  try AudioManager.shared.setEngineAvailability(.none)
120
138
  try AudioManager.shared.setEngineAvailability(.default)
@@ -134,6 +152,7 @@ final class LiveKitMediaPort: NSObject, MediaPort {
134
152
  } catch {
135
153
  NSLog("[PoolseCalls] mic cycle failed: %@", "\(error)")
136
154
  }
155
+ pcTrace("kick done enabled=\(r.localParticipant.isMicrophoneEnabled()) \(pcSessionDescription())")
137
156
  await MainActor.run { self?.reassertAudio() }
138
157
  }
139
158
  }
@@ -147,6 +166,7 @@ final class LiveKitMediaPort: NSObject, MediaPort {
147
166
  /// no-CallKit fallback) holds the session active.
148
167
  func reassertAudio(_ session: AVAudioSession = .sharedInstance()) {
149
168
  guard audioActive else { return }
169
+ pcTrace("reassertAudio before: \(pcSessionDescription(session))")
150
170
  let mode: AVAudioSession.Mode = media == "video" && speakerWanted != false ? .videoChat : .voiceChat
151
171
  if session.category != .playAndRecord || session.mode != mode {
152
172
  do {
@@ -163,6 +183,7 @@ final class LiveKitMediaPort: NSObject, MediaPort {
163
183
  }
164
184
 
165
185
  func audioDeactivated() {
186
+ pcTrace("audioDeactivated")
166
187
  audioActive = false
167
188
  guard audioPrepared else { return }
168
189
  do { try AudioManager.shared.setEngineAvailability(.none) } catch {
@@ -181,6 +202,7 @@ final class LiveKitMediaPort: NSObject, MediaPort {
181
202
  // MARK: - MediaPort
182
203
 
183
204
  func join(bundle: CallSessionBundle) {
205
+ pcTrace("join call=\(bundle.callId) media=\(bundle.media) audioActive=\(audioActive)")
184
206
  prepareAudio()
185
207
  if let joined = joinedCallId, joined != bundle.callId { resetWanted() }
186
208
  joinedCallId = bundle.callId
@@ -224,10 +246,13 @@ final class LiveKitMediaPort: NSObject, MediaPort {
224
246
  return
225
247
  }
226
248
  let muted = await MainActor.run { self.mutedWanted }
249
+ let activeNow = await MainActor.run { self.audioActive }
250
+ pcTrace("connected; publishing mic muted=\(muted) audioActive=\(activeNow)")
227
251
  do { try await r.localParticipant.setMicrophone(enabled: !muted) } catch {
228
252
  NSLog("[PoolseCalls] microphone publish failed: %@", "\(error)")
229
253
  }
230
254
  // Connected and publishing: make sure nothing changed the session
255
+ pcTrace("mic published enabled=\(r.localParticipant.isMicrophoneEnabled()) \(pcSessionDescription())")
231
256
  // while we connected (a cold answer boots JS in exactly this window),
232
257
  // then make sure the engine is actually carrying the mic.
233
258
  await MainActor.run {
@@ -375,18 +400,21 @@ final class LiveKitMediaPort: NSObject, MediaPort {
375
400
  /// An interruption (another app's audio, Siri) ended: the session may
376
401
  /// have been reconfigured; restore the call's.
377
402
  @objc private func interrupted(_ note: Notification) {
403
+ pcTrace("interruption \(note.userInfo ?? [:])")
378
404
  guard let raw = note.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
379
405
  AVAudioSession.InterruptionType(rawValue: raw) == .ended else { return }
380
406
  DispatchQueue.main.async { [weak self] in self?.reassertAudio() }
381
407
  }
382
408
 
383
409
  @objc private func mediaServicesReset(_ note: Notification) {
410
+ pcTrace("mediaServicesWereReset")
384
411
  DispatchQueue.main.async { [weak self] in self?.reassertAudio() }
385
412
  }
386
413
 
387
414
  @objc private func routeChanged(_ note: Notification) {
388
415
  let reason = (note.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt)
389
416
  .flatMap(AVAudioSession.RouteChangeReason.init(rawValue:))
417
+ pcTrace("routeChange reason=\(reason.map { String($0.rawValue) } ?? "?") \(pcSessionDescription())")
390
418
  DispatchQueue.main.async { [weak self] in
391
419
  guard let self = self, self.audioActive else { return }
392
420
  // Something reconfigured the shared session mid-call: put it back.
@@ -1,6 +1,7 @@
1
1
  import CallKit
2
2
  import Foundation
3
3
  import PushKit
4
+ import UIKit
4
5
 
5
6
  /// Native PushKit intake — the iOS twin of CallMessagingService. The
6
7
  /// registry is created at launch (PoolseCallsAppDelegateSubscriber →
@@ -59,6 +60,7 @@ final class VoipPush: NSObject, PKPushRegistryDelegate {
59
60
 
60
61
  func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload,
61
62
  for type: PKPushType, completion: @escaping () -> Void) {
63
+ pcTrace("VoIP push received appState=\(UIApplication.shared.applicationState.rawValue)")
62
64
  if type == .voIP { handle(payload.dictionaryPayload) }
63
65
  completion()
64
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolse/react-native-calls",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Native call core for poolse — Kotlin/Swift calling engine with a thin React Native bridge. Ring, answer and carry audio with the app killed and the phone locked.",
5
5
  "license": "MIT",
6
6
  "type": "module",