@poolse/react-native-calls 0.4.2 → 0.4.3

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.
@@ -55,6 +55,8 @@ final class LiveKitMediaPort: NSObject, MediaPort {
55
55
  private let surfaces = NSHashTable<LiveKitVideoSurface>.weakObjects()
56
56
  private var audioPrepared = false
57
57
  private var audioActive = false
58
+ /// The room whose audio engine has been kicked (once per room).
59
+ private var audioKicked: ObjectIdentifier?
58
60
 
59
61
  init(events: MediaEvents) {
60
62
  self.events = events
@@ -93,6 +95,47 @@ final class LiveKitMediaPort: NSObject, MediaPort {
93
95
  audioActive = true
94
96
  if media != self.media && room == nil { self.media = media }
95
97
  reassertAudio(session)
98
+ kickAudioIfReady()
99
+ }
100
+
101
+ /// Once per call, the first moment the session is active AND the mic is
102
+ /// published (in whichever order they happen): make LiveKit's audio
103
+ /// engine rebuild around the live mic.
104
+ ///
105
+ /// On a call answered with the app closed, the process is brand new and
106
+ /// LiveKit's engine never started for a mic published right after
107
+ /// CallKit made audio available — no capture, no playback, though the
108
+ /// session was correct (video, which needs no audio engine, worked). A
109
+ /// user-side mute → unmute cured it, because toggling the mic makes the
110
+ /// engine re-evaluate and start. This does the same, automatically: an
111
+ /// availability cycle first, then a mic off/on. A deliberate mute is
112
+ /// left alone. The remote hears at most a sub-second blip at connect.
113
+ private func kickAudioIfReady() {
114
+ guard audioActive, let r = room, connectedRoom == ObjectIdentifier(r),
115
+ audioKicked != ObjectIdentifier(r), !mutedWanted else { return }
116
+ guard r.localParticipant.isMicrophoneEnabled() else { return }
117
+ audioKicked = ObjectIdentifier(r)
118
+ do {
119
+ try AudioManager.shared.setEngineAvailability(.none)
120
+ try AudioManager.shared.setEngineAvailability(.default)
121
+ } catch {
122
+ NSLog("[PoolseCalls] engine availability cycle failed: %@", "\(error)")
123
+ }
124
+ Task { [weak self] in
125
+ try? await Task.sleep(nanoseconds: 250_000_000)
126
+ let still = await MainActor.run { () -> Bool in
127
+ guard let self = self else { return false }
128
+ return self.room === r && self.audioActive && !self.mutedWanted
129
+ }
130
+ guard still else { return }
131
+ do {
132
+ try await r.localParticipant.setMicrophone(enabled: false)
133
+ try await r.localParticipant.setMicrophone(enabled: true)
134
+ } catch {
135
+ NSLog("[PoolseCalls] mic cycle failed: %@", "\(error)")
136
+ }
137
+ await MainActor.run { self?.reassertAudio() }
138
+ }
96
139
  }
97
140
 
98
141
  /// Put the call's audio back the way the call needs it: play-and-record
@@ -185,8 +228,12 @@ final class LiveKitMediaPort: NSObject, MediaPort {
185
228
  NSLog("[PoolseCalls] microphone publish failed: %@", "\(error)")
186
229
  }
187
230
  // Connected and publishing: make sure nothing changed the session
188
- // while we connected (a cold answer boots JS in exactly this window).
189
- await MainActor.run { self.reassertAudio() }
231
+ // while we connected (a cold answer boots JS in exactly this window),
232
+ // then make sure the engine is actually carrying the mic.
233
+ await MainActor.run {
234
+ self.reassertAudio()
235
+ self.kickAudioIfReady()
236
+ }
190
237
  let camera = await MainActor.run { self.cameraWanted ?? wantsVideo }
191
238
  if camera { await self.enableCamera(r, true) }
192
239
  await MainActor.run {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolse/react-native-calls",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
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",