@edkimmel/expo-audio-stream 0.5.0 → 0.6.1

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 (34) hide show
  1. package/NATIVE_EVENTS.md +60 -6
  2. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  4. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  5. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  6. package/android/.gradle/8.9/gc.properties +0 -0
  7. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  8. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  9. package/android/.gradle/vcs-1/gc.properties +0 -0
  10. package/android/src/main/java/expo/modules/audiostream/AudioEffectsManager.kt +6 -11
  11. package/android/src/main/java/expo/modules/audiostream/AudioRecorderManager.kt +23 -6
  12. package/android/src/main/java/expo/modules/audiostream/CommunicationAudioManager.kt +155 -0
  13. package/android/src/main/java/expo/modules/audiostream/Constants.kt +1 -0
  14. package/android/src/main/java/expo/modules/audiostream/ExpoPlayAudioStreamModule.kt +12 -3
  15. package/android/src/main/java/expo/modules/audiostream/pipeline/AudioPipeline.kt +12 -9
  16. package/build/events.d.ts +22 -0
  17. package/build/events.d.ts.map +1 -1
  18. package/build/events.js +18 -0
  19. package/build/events.js.map +1 -1
  20. package/build/index.d.ts +2 -0
  21. package/build/index.d.ts.map +1 -1
  22. package/build/index.js +21 -10
  23. package/build/index.js.map +1 -1
  24. package/build/types.d.ts +13 -0
  25. package/build/types.d.ts.map +1 -1
  26. package/build/types.js.map +1 -1
  27. package/ios/ExpoPlayAudioStreamModule.swift +29 -12
  28. package/ios/Microphone.swift +153 -197
  29. package/ios/MicrophoneDataDelegate.swift +10 -1
  30. package/ios/SharedAudioEngine.swift +18 -0
  31. package/package.json +1 -1
  32. package/src/events.ts +28 -0
  33. package/src/index.ts +29 -26
  34. package/src/types.ts +13 -0
package/NATIVE_EVENTS.md CHANGED
@@ -9,8 +9,7 @@ including payload shapes, trigger conditions, and recommended JS responses.
9
9
 
10
10
  ### `AudioData`
11
11
 
12
- Emitted at the configured interval during microphone recording. Also doubles as
13
- the recording error channel.
12
+ Emitted at the configured interval during microphone recording.
14
13
 
15
14
  | Field | Type | Notes |
16
15
  |---|---|---|
@@ -27,19 +26,21 @@ the recording error channel.
27
26
 
28
27
  **Error variant** (same event name, different shape):
29
28
 
29
+ > Errors are also emitted on the richer `MicrophoneError` event (see below).
30
+ > This variant is kept for backward compatibility.
31
+
30
32
  | Field | Type | Notes |
31
33
  |---|---|---|
32
- | `error` | `string` | Error code: `READ_ERROR`, `RECORDING_CRASH` |
34
+ | `error` | `string` | Error code see `MicrophoneError` section for full table |
33
35
  | `errorMessage` | `string` | Human-readable description |
34
- | `streamUuid` | `string` | Stream that errored |
36
+ | `streamUuid` | `string` | Stream ID (Android only; `""` on iOS) |
35
37
 
36
38
  **Platform:** Android, iOS
37
39
 
38
40
  **JS response:**
39
41
  - Forward the base64 PCM to your STT pipeline or WebSocket.
40
42
  - Use `soundLevel` for VAD or UI visualisation.
41
- - Check for the `error` field before assuming the payload is audio data.
42
- On error, stop the conversation turn or retry.
43
+ - Prefer subscribing via `addMicrophoneErrorListener` it receives the structured `MicrophoneError` event with `isFatal` and `autoResuming` fields.
43
44
 
44
45
  ---
45
46
 
@@ -62,6 +63,59 @@ wired headset/headphones, USB headset).
62
63
 
63
64
  ---
64
65
 
66
+ ### `MicrophoneError`
67
+
68
+ Fired on every microphone error alongside the backward-compatible `AudioData`
69
+ error variant. Carries structured `isFatal` and `autoResuming` fields decided
70
+ by native code.
71
+
72
+ **OTA safe:** apps can subscribe to this event before the native binary
73
+ includes it — old native simply never emits it, so the listener is never
74
+ called. Apps using the `AudioData` error variant continue to work unchanged.
75
+
76
+ | Field | Type | Notes |
77
+ |---|---|---|
78
+ | `code` | `string` | Error code (see table below) |
79
+ | `message` | `string` | Human-readable description including any underlying system error |
80
+ | `isFatal` | `boolean` | `true` = recording has stopped; caller must call `stopMicrophone()` and reconnect |
81
+ | `autoResuming` | `boolean` | `true` = library will reinstall the tap automatically (`INTERRUPTED` only) |
82
+
83
+ **Error code reference:**
84
+
85
+ | Code | Platform | `isFatal` | `autoResuming` | Meaning | Recommended action |
86
+ |------|----------|-----------|----------------|---------|-------------------|
87
+ | `INTERRUPTED` | iOS | false | true | System interrupted the audio session | Show paused UI; do NOT stop — library auto-resumes |
88
+ | `RESUME_FAILED` | iOS | true | false | System allowed resume but engine restart failed | `stopMicrophone()` then reconnect |
89
+ | `RESTART_FAILED` | iOS | true | false | Route-change or engine-rebuild recovery failed | `stopMicrophone()` then reconnect |
90
+ | `ENGINE_DIED` | iOS | true | false | SharedAudioEngine exhausted all recovery | `stopMicrophone()` + `disconnectPipeline()` then reconnect both |
91
+ | `READ_ERROR` | iOS | false | false | Single empty buffer — transient, recording continues | Log for diagnostics; no action |
92
+ | `READ_ERROR` | Android | true | false | `AudioRecord.read()` failed 10× consecutively | `stopMicrophone()` then reconnect |
93
+ | `RECORDING_CRASH` | Android | true | false | Recording thread threw an unexpected exception | `stopMicrophone()` then reconnect |
94
+
95
+ **Platform:** Android, iOS
96
+
97
+ **JS response:**
98
+
99
+ ```typescript
100
+ import { addMicrophoneErrorListener } from "@edkimmel/expo-audio-stream"
101
+
102
+ const sub = addMicrophoneErrorListener((error) => {
103
+ if (error.isFatal) {
104
+ stopMicrophone()
105
+ if (error.code === "ENGINE_DIED") disconnectPipeline()
106
+ reconnect()
107
+ } else if (error.autoResuming) {
108
+ showPausedUI() // INTERRUPTED — library will reinstall tap automatically
109
+ }
110
+ // isFatal: false, autoResuming: false → READ_ERROR on iOS: log and ignore
111
+ })
112
+
113
+ // cleanup
114
+ sub.remove()
115
+ ```
116
+
117
+ ---
118
+
65
119
  ## Legacy Playback Events (AudioPlaybackManager)
66
120
 
67
121
  ### `SoundStarted`
File without changes
@@ -0,0 +1,2 @@
1
+ #Thu Jun 04 15:44:34 EDT 2026
2
+ gradle.version=8.9
File without changes
@@ -9,18 +9,13 @@ import android.util.Log
9
9
  /**
10
10
  * Manages hardware audio effects for voice recording.
11
11
  *
12
- * We use VOICE_RECOGNITION as our audio source. The Android CDD (Section 5.4)
13
- * mandates that this source delivers unprocessed audio:
14
- * [C-1-2] MUST disable noise reduction by default
15
- * [C-1-3] MUST disable automatic gain control by default
12
+ * We use VOICE_COMMUNICATION as our audio source. This source enables
13
+ * platform-managed AEC at the HAL level automatically. The explicit
14
+ * AcousticEchoCanceler effect here is belt-and-suspenders for devices
15
+ * where the platform does not apply it automatically.
16
16
  *
17
- * NS and AGC are therefore off by default to honor the spec. Enabling them
18
- * re-introduces the processing the CDD explicitly prohibits for this source
19
- * and can cause low-volume capture on many OEMs.
20
- *
21
- * AEC is the one effect the CDD permits for VOICE_RECOGNITION ("expects a
22
- * stream that has an echo cancellation effect if available"), so it is
23
- * enabled by default.
17
+ * NS and AGC remain opt-in VOICE_COMMUNICATION applies its own
18
+ * processing and additional effects can cause over-processing on some OEMs.
24
19
  */
25
20
  class AudioEffectsManager(
26
21
  /** Enable hardware noise suppressor. Default false — CDD 5.4 [C-1-2] prohibits it for VOICE_RECOGNITION. */
@@ -339,23 +339,29 @@ class AudioRecorderManager(
339
339
  consecutiveErrors++
340
340
  if (consecutiveErrors >= 10) {
341
341
  Log.e(Constants.TAG, "Too many consecutive read errors ($consecutiveErrors), stopping")
342
- emitRecordingError("READ_ERROR", "AudioRecord read failed after $consecutiveErrors consecutive errors")
342
+ emitRecordingError("READ_ERROR", "AudioRecord read failed after $consecutiveErrors consecutive errors", isFatal = true)
343
343
  break
344
344
  }
345
345
  }
346
346
  }
347
347
  } catch (e: Exception) {
348
348
  Log.e(Constants.TAG, "Recording thread crashed", e)
349
- emitRecordingError("RECORDING_CRASH", e.message ?: "Recording thread unexpected error")
349
+ emitRecordingError("RECORDING_CRASH", e.message ?: "Recording thread unexpected error", isFatal = true)
350
350
  }
351
351
  }
352
352
 
353
353
  /**
354
354
  * Sends a recording error event to JS so the caller can react.
355
355
  */
356
- private fun emitRecordingError(code: String, message: String) {
356
+ private fun emitRecordingError(
357
+ code: String,
358
+ message: String,
359
+ isFatal: Boolean,
360
+ autoResuming: Boolean = false
361
+ ) {
357
362
  mainHandler.post {
358
363
  try {
364
+ // Backward-compat: keep the error variant on AudioData for existing consumers
359
365
  eventSender.sendExpoEvent(
360
366
  Constants.AUDIO_EVENT_NAME, bundleOf(
361
367
  "error" to code,
@@ -363,6 +369,15 @@ class AudioRecorderManager(
363
369
  "streamUuid" to streamUuid
364
370
  )
365
371
  )
372
+ // Rich structured channel for new consumers
373
+ eventSender.sendExpoEvent(
374
+ Constants.MICROPHONE_ERROR_EVENT_NAME, bundleOf(
375
+ "code" to code,
376
+ "message" to message,
377
+ "isFatal" to isFatal,
378
+ "autoResuming" to autoResuming
379
+ )
380
+ )
366
381
  } catch (e: Exception) {
367
382
  Log.e(Constants.TAG, "Failed to send error event", e)
368
383
  }
@@ -479,9 +494,11 @@ class AudioRecorderManager(
479
494
  return null
480
495
  }
481
496
 
482
- // Use VOICE_RECOGNITION for far-field/speakerphone use higher mic gain,
483
- // no near-field gain reduction. AEC/NS/AGC are applied separately via AudioEffectsManager.
484
- val audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION
497
+ // VOICE_COMMUNICATION enables platform-managed AEC (echo cancellation happens at
498
+ // the HAL level, not just via the explicit AcousticEchoCanceler effect).
499
+ // VOICE_RECOGNITION intentionally bypasses platform AEC per the Android CDD,
500
+ // making hardware echo cancellation ineffective regardless of AudioEffectsManager.
501
+ val audioSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION
485
502
 
486
503
  val record = AudioRecord(
487
504
  audioSource,
@@ -0,0 +1,155 @@
1
+ package expo.modules.audiostream
2
+
3
+ import android.app.Activity
4
+ import android.media.AudioDeviceInfo
5
+ import android.media.AudioManager
6
+ import android.os.Build
7
+ import android.util.Log
8
+ import java.util.concurrent.atomic.AtomicInteger
9
+
10
+ /**
11
+ * Owns Android communication-mode lifecycle and output device routing for
12
+ * hands-free voice sessions (microphone + speaker playback with AEC).
13
+ *
14
+ * Responsibilities:
15
+ * - Set MODE_IN_COMMUNICATION before AudioRecord starts so the HAL echo
16
+ * reference path is active when AcousticEchoCanceler initializes.
17
+ * - Route output to the best available device: Bluetooth HFP > wired headset
18
+ * > built-in speaker. Re-routes automatically when devices connect or
19
+ * disconnect.
20
+ * - Bind hardware volume buttons to STREAM_VOICE_CALL while a session is
21
+ * active so the user's volume buttons control playback volume.
22
+ * - Reset everything when all callers have stopped so the phone's audio
23
+ * state is clean.
24
+ *
25
+ * Reference-counted: mic and pipeline each call startSession/stopSession
26
+ * independently. Communication mode stays active until both have stopped.
27
+ *
28
+ * Usage:
29
+ * cam.startSession(activity) // call before AudioRecord.startRecording() or pipeline connect
30
+ * cam.onDeviceChanged() // call from AudioDeviceCallback
31
+ * cam.stopSession(activity) // call after AudioRecord.stop() or pipeline disconnect
32
+ */
33
+ class CommunicationAudioManager(private val audioManager: AudioManager) {
34
+
35
+ private val refCount = AtomicInteger(0)
36
+
37
+ private val sessionActive get() = refCount.get() > 0
38
+
39
+ /**
40
+ * Start (or join) a voice session. Safe to call from multiple owners —
41
+ * communication mode is activated on the first call.
42
+ * Pass the current Activity so volume buttons bind to STREAM_VOICE_CALL.
43
+ */
44
+ fun startSession(activity: Activity? = null) {
45
+ if (refCount.getAndIncrement() == 0) {
46
+ audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
47
+ activity?.volumeControlStream = AudioManager.STREAM_VOICE_CALL
48
+ Log.d(TAG, "Session started — MODE_IN_COMMUNICATION")
49
+ }
50
+ applyBestRoute()
51
+ }
52
+
53
+ /**
54
+ * Release this owner's hold on the session. Communication mode is reset
55
+ * only when all owners have called stopSession.
56
+ * No-op if the session was already ended via forceReset.
57
+ */
58
+ fun stopSession(activity: Activity? = null) {
59
+ // Decrement only if currently positive — prevents post-forceReset calls
60
+ // from re-triggering cleanup (getAndUpdate returns the previous value).
61
+ val prev = refCount.getAndUpdate { if (it > 0) it - 1 else 0 }
62
+ if (prev <= 0) {
63
+ Log.d(TAG, "stopSession — already stopped, no-op")
64
+ return
65
+ }
66
+ val remaining = prev - 1
67
+ if (remaining == 0) {
68
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
69
+ audioManager.clearCommunicationDevice()
70
+ } else {
71
+ @Suppress("DEPRECATION")
72
+ audioManager.stopBluetoothSco()
73
+ @Suppress("DEPRECATION")
74
+ audioManager.isSpeakerphoneOn = false
75
+ }
76
+ audioManager.mode = AudioManager.MODE_NORMAL
77
+ activity?.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
78
+ Log.d(TAG, "Session ended — audio mode reset to NORMAL")
79
+ } else {
80
+ Log.d(TAG, "stopSession — $remaining owner(s) still active")
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Re-evaluate and apply the best available output route.
86
+ * Call this from AudioDeviceCallback whenever devices are added or removed.
87
+ */
88
+ fun onDeviceChanged() {
89
+ applyBestRoute()
90
+ }
91
+
92
+ /**
93
+ * Unconditionally reset all communication audio state regardless of ref count.
94
+ * Use in OnDestroy and explicit destroy flows where normal lifecycle won't run.
95
+ */
96
+ fun forceReset(activity: Activity? = null) {
97
+ refCount.set(0)
98
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
99
+ audioManager.clearCommunicationDevice()
100
+ } else {
101
+ @Suppress("DEPRECATION")
102
+ audioManager.stopBluetoothSco()
103
+ @Suppress("DEPRECATION")
104
+ audioManager.isSpeakerphoneOn = false
105
+ }
106
+ audioManager.mode = AudioManager.MODE_NORMAL
107
+ activity?.volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
108
+ Log.d(TAG, "forceReset — audio mode reset to NORMAL")
109
+ }
110
+
111
+ // ── Private ─────────────────────────────────────────────────────────────
112
+
113
+ private fun applyBestRoute() {
114
+ if (!sessionActive) return
115
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
116
+ applyBestRouteApi31()
117
+ } else {
118
+ applyBestRouteLegacy()
119
+ }
120
+ }
121
+
122
+ private fun applyBestRouteApi31() {
123
+ val devices = audioManager.availableCommunicationDevices
124
+ // Priority: BT HFP > wired headset > built-in speaker
125
+ val preferred = devices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
126
+ ?: devices.firstOrNull { it.type == AudioDeviceInfo.TYPE_WIRED_HEADSET }
127
+ ?: devices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }
128
+ if (preferred != null) {
129
+ audioManager.setCommunicationDevice(preferred)
130
+ Log.d(TAG, "Route → ${preferred.productName} (type=${preferred.type})")
131
+ } else {
132
+ Log.w(TAG, "No suitable communication device found")
133
+ }
134
+ }
135
+
136
+ @Suppress("DEPRECATION")
137
+ private fun applyBestRouteLegacy() {
138
+ // Detect a connected BT HFP device via the full device list (API 23+).
139
+ val allDevices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL)
140
+ val btSco = allDevices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
141
+ if (btSco != null) {
142
+ audioManager.startBluetoothSco()
143
+ audioManager.isSpeakerphoneOn = false
144
+ Log.d(TAG, "Route → Bluetooth SCO (${btSco.productName})")
145
+ } else {
146
+ audioManager.stopBluetoothSco()
147
+ audioManager.isSpeakerphoneOn = true
148
+ Log.d(TAG, "Route → built-in speaker")
149
+ }
150
+ }
151
+
152
+ companion object {
153
+ private const val TAG = "CommunicationAudioMgr"
154
+ }
155
+ }
@@ -2,6 +2,7 @@ package expo.modules.audiostream
2
2
 
3
3
  object Constants {
4
4
  const val AUDIO_EVENT_NAME = "AudioData"
5
+ const val MICROPHONE_ERROR_EVENT_NAME = "MicrophoneError"
5
6
  const val AUDIO_ANALYSIS_EVENT_NAME = "AudioAnalysis"
6
7
  const val DEVICE_RECONNECTED_EVENT_NAME = "DeviceReconnected"
7
8
  const val DEFAULT_SAMPLE_RATE = 16000 // Default sample rate for audio recording
@@ -26,6 +26,7 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
26
26
  private lateinit var audioRecorderManager: AudioRecorderManager
27
27
  private lateinit var audioManager: AudioManager
28
28
  private lateinit var pipelineIntegration: PipelineIntegration
29
+ private lateinit var communicationAudioManager: CommunicationAudioManager
29
30
 
30
31
  // Ensure callbacks are delivered on the main thread
31
32
  private val mainHandler by lazy { Handler(Looper.getMainLooper()) }
@@ -62,6 +63,7 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
62
63
  if (firstOfGroup?.isNotEmpty()==true) {
63
64
  val matched = firstOfGroup.map { "${it.productName} (type=${it.type})" }
64
65
  Log.d("ExpoAudioCallback", "AudioDeviceCallback ➜ ADDED (interesting): $matched")
66
+ communicationAudioManager.onDeviceChanged()
65
67
  pipelineIntegration.logAudioTrackHealth("device_added")
66
68
  val params = Bundle()
67
69
  params.putString("reason", "newDeviceAvailable")
@@ -79,6 +81,7 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
79
81
  if (lastOfGroup?.isNotEmpty() == true) {
80
82
  val matched = lastOfGroup.map { "${it.productName} (type=${it.type})" }
81
83
  Log.d("ExpoAudioCallback", "AudioDeviceCallback ➜ REMOVED (interesting): $matched")
84
+ communicationAudioManager.onDeviceChanged()
82
85
  pipelineIntegration.logAudioTrackHealth("device_removed")
83
86
  val params = Bundle()
84
87
  params.putString("reason", "oldDeviceUnavailable")
@@ -94,6 +97,7 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
94
97
 
95
98
  Events(
96
99
  Constants.AUDIO_EVENT_NAME,
100
+ Constants.MICROPHONE_ERROR_EVENT_NAME,
97
101
  Constants.DEVICE_RECONNECTED_EVENT_NAME,
98
102
  PipelineIntegration.EVENT_STATE_CHANGED,
99
103
  PipelineIntegration.EVENT_PLAYBACK_STARTED,
@@ -113,26 +117,27 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
113
117
 
114
118
  OnCreate {
115
119
  audioManager = appContext.reactContext?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
120
+ communicationAudioManager = CommunicationAudioManager(audioManager)
116
121
  audioManager.registerAudioDeviceCallback(audioCallCallback, mainHandler)
117
122
  }
118
123
 
119
124
  OnDestroy {
120
125
  reportedGroups.clear()
121
126
  audioManager.unregisterAudioDeviceCallback(audioCallCallback)
122
- // Module is being destroyed (app shutdown)
123
- // Just clean up resources without reinitialization
124
127
  pipelineIntegration.destroy()
125
128
  audioRecorderManager.release()
129
+ communicationAudioManager.forceReset(appContext.currentActivity)
126
130
  }
127
131
 
128
132
  AsyncFunction("destroy") { promise: Promise ->
129
- // User explicitly called destroy - clean up and reinitialize for reuse
130
133
  pipelineIntegration.destroy()
131
134
  audioRecorderManager.release()
135
+ communicationAudioManager.forceReset(appContext.currentActivity)
132
136
 
133
137
  // Reinitialize all managers so the module can be used again
134
138
  initializeManager()
135
139
  initializePipeline()
140
+ communicationAudioManager = CommunicationAudioManager(audioManager)
136
141
  promise.resolve(null)
137
142
  }
138
143
 
@@ -153,11 +158,13 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
153
158
  }
154
159
 
155
160
  AsyncFunction("startMicrophone") { options: Map<String, Any?>, promise: Promise ->
161
+ communicationAudioManager.startSession(appContext.currentActivity)
156
162
  audioRecorderManager.startRecording(options, promise)
157
163
  }
158
164
 
159
165
  AsyncFunction("stopMicrophone") { promise: Promise ->
160
166
  audioRecorderManager.stopRecording(promise)
167
+ communicationAudioManager.stopSession(appContext.currentActivity)
161
168
  }
162
169
 
163
170
  Function("toggleSilence") { isSilent: Boolean ->
@@ -168,6 +175,7 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
168
175
  // ── Native Audio Pipeline V3 ────────────────────────────────────
169
176
 
170
177
  AsyncFunction("connectPipeline") { options: Map<String, Any?>, promise: Promise ->
178
+ communicationAudioManager.startSession(appContext.currentActivity)
171
179
  pipelineIntegration.connect(options, promise)
172
180
  }
173
181
 
@@ -181,6 +189,7 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
181
189
 
182
190
  AsyncFunction("disconnectPipeline") { promise: Promise ->
183
191
  pipelineIntegration.disconnect(promise)
192
+ communicationAudioManager.stopSession(appContext.currentActivity)
184
193
  }
185
194
 
186
195
  AsyncFunction("invalidatePipelineTurn") { options: Map<String, Any?>, promise: Promise ->
@@ -90,8 +90,9 @@ enum class AudioMode {
90
90
  * **MAX_PRIORITY write thread** that loops `buffer.read() → track.write(BLOCKING)`.
91
91
  *
92
92
  * Key design points:
93
- * - AudioTrack uses **USAGE_MEDIA + CONTENT_TYPE_SPEECH** (not
94
- * VOICE_COMMUNICATION avoids earpiece routing).
93
+ * - AudioTrack uses **USAGE_VOICE_COMMUNICATION + CONTENT_TYPE_SPEECH** so its
94
+ * output feeds the hardware AEC echo reference path. Earpiece routing is
95
+ * prevented by CommunicationAudioManager (setCommunicationDevice/isSpeakerphoneOn).
95
96
  * - AudioTrack stays alive for the entire session, writing silence when idle.
96
97
  * This avoids 50–100 ms restart latency.
97
98
  * - Config is **immutable per session** — tear down and rebuild to change
@@ -129,7 +130,9 @@ class AudioPipeline(
129
130
  /** If playback head hasn't moved for this long, declare zombie. */
130
131
  private const val ZOMBIE_STALL_THRESHOLD_MS = 5000L
131
132
 
132
- /** Minimum volume level (0–15) enforced by VolumeGuard on STREAM_MUSIC. */
133
+ /** Minimum volume level (0–15) enforced by VolumeGuard on STREAM_VOICE_CALL.
134
+ * USAGE_VOICE_COMMUNICATION is required so the output feeds the hardware AEC
135
+ * echo reference path — USAGE_MEDIA does not. */
133
136
  private const val MIN_VOLUME_LEVEL = 1
134
137
  }
135
138
 
@@ -263,7 +266,7 @@ class AudioPipeline(
263
266
 
264
267
  // ── 2. AudioTrack ───────────────────────────────────────────
265
268
  val audioAttributes = AudioAttributes.Builder()
266
- .setUsage(AudioAttributes.USAGE_MEDIA)
269
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
267
270
  .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
268
271
  .build()
269
272
 
@@ -637,7 +640,7 @@ class AudioPipeline(
637
640
  AudioMode.DUCK_OTHERS -> {
638
641
  val result = audioManager.requestAudioFocus(
639
642
  focusChangeListener,
640
- AudioManager.STREAM_MUSIC,
643
+ AudioManager.STREAM_VOICE_CALL,
641
644
  AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
642
645
  )
643
646
  hasAudioFocus.set(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
@@ -648,7 +651,7 @@ class AudioPipeline(
648
651
  AudioMode.DO_NOT_MIX -> {
649
652
  val result = audioManager.requestAudioFocus(
650
653
  focusChangeListener,
651
- AudioManager.STREAM_MUSIC,
654
+ AudioManager.STREAM_VOICE_CALL,
652
655
  AudioManager.AUDIOFOCUS_GAIN
653
656
  )
654
657
  hasAudioFocus.set(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
@@ -743,12 +746,12 @@ class AudioPipeline(
743
746
  private fun installVolumeGuard() {
744
747
  volumeObserver = object : ContentObserver(mainHandler) {
745
748
  override fun onChange(selfChange: Boolean) {
746
- val current = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
749
+ val current = audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL)
747
750
  if (current < MIN_VOLUME_LEVEL) {
748
- Log.d(TAG, "VolumeGuard: raising STREAM_MUSIC from $current to $MIN_VOLUME_LEVEL")
751
+ Log.d(TAG, "VolumeGuard: raising STREAM_VOICE_CALL from $current to $MIN_VOLUME_LEVEL")
749
752
  try {
750
753
  audioManager.setStreamVolume(
751
- AudioManager.STREAM_MUSIC,
754
+ AudioManager.STREAM_VOICE_CALL,
752
755
  MIN_VOLUME_LEVEL,
753
756
  0 // no flags — silent raise
754
757
  )
package/build/events.d.ts CHANGED
@@ -32,8 +32,30 @@ export type DeviceReconnectedEventPayload = {
32
32
  };
33
33
  export declare const AudioEvents: {
34
34
  AudioData: string;
35
+ MicrophoneError: string;
35
36
  DeviceReconnected: string;
36
37
  };
37
38
  export declare function addAudioEventListener(listener: (event: AudioEventPayload) => Promise<void>): EventSubscription;
39
+ export interface MicrophoneErrorEventPayload {
40
+ code: string;
41
+ message: string;
42
+ isFatal: boolean;
43
+ autoResuming: boolean;
44
+ }
45
+ /**
46
+ * Subscribe to the dedicated MicrophoneError native event.
47
+ *
48
+ * OTA safe: if the running native binary predates this feature, the event is
49
+ * never emitted and this listener is never called. Apps that also subscribe to
50
+ * AudioData errors via addAudioEventListener continue to work unchanged.
51
+ *
52
+ * @example
53
+ * const sub = addMicrophoneErrorListener((e) => {
54
+ * if (e.isFatal) { stopMicrophone(); reconnect(); }
55
+ * else if (e.autoResuming) { showPausedUI(); }
56
+ * })
57
+ * // cleanup: sub.remove()
58
+ */
59
+ export declare function addMicrophoneErrorListener(listener: (event: MicrophoneErrorEventPayload) => void): EventSubscription;
38
60
  export declare function subscribeToEvent<T extends unknown>(eventName: string, listener: (event: T | undefined) => Promise<void>): EventSubscription;
39
61
  //# sourceMappingURL=events.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAM7C,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D;kFAC8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,wBAAwB;;;;CAI3B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAE3E,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,WAAW;;;CAGvB,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACpD,iBAAiB,CAEnB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,EAChD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,iBAAiB,CAEnB"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAM7C,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D;kFAC8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,wBAAwB;;;;CAI3B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAE3E,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,WAAW;;;;CAIvB,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACpD,iBAAiB,CAEnB;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,CAAC,KAAK,EAAE,2BAA2B,KAAK,IAAI,GACrD,iBAAiB,CAEnB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,EAChD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,iBAAiB,CAEnB"}
package/build/events.js CHANGED
@@ -9,11 +9,29 @@ export const DeviceReconnectedReasons = {
9
9
  };
10
10
  export const AudioEvents = {
11
11
  AudioData: "AudioData",
12
+ MicrophoneError: "MicrophoneError",
12
13
  DeviceReconnected: "DeviceReconnected",
13
14
  };
14
15
  export function addAudioEventListener(listener) {
15
16
  return emitter.addListener("AudioData", listener);
16
17
  }
18
+ /**
19
+ * Subscribe to the dedicated MicrophoneError native event.
20
+ *
21
+ * OTA safe: if the running native binary predates this feature, the event is
22
+ * never emitted and this listener is never called. Apps that also subscribe to
23
+ * AudioData errors via addAudioEventListener continue to work unchanged.
24
+ *
25
+ * @example
26
+ * const sub = addMicrophoneErrorListener((e) => {
27
+ * if (e.isFatal) { stopMicrophone(); reconnect(); }
28
+ * else if (e.autoResuming) { showPausedUI(); }
29
+ * })
30
+ * // cleanup: sub.remove()
31
+ */
32
+ export function addMicrophoneErrorListener(listener) {
33
+ return emitter.addListener("MicrophoneError", listener);
34
+ }
17
35
  export function subscribeToEvent(eventName, listener) {
18
36
  return emitter.addListener(eventName, listener);
19
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,OAAO,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAC;AAKzE,OAAO,yBAAyB,MAAM,6BAA6B,CAAC;AAEpE,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,yBAAyB,CAAC,CAAC;AAoB5D,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,oBAAoB,EAAE,sBAAsB;IAC5C,OAAO,EAAE,SAAS;CACV,CAAC;AASX,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS,EAAE,WAAW;IACtB,iBAAiB,EAAE,mBAAmB;CACvC,CAAC;AAEF,MAAM,UAAU,qBAAqB,CACnC,QAAqD;IAErD,OAAQ,OAAe,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,QAAiD;IAEjD,OAAQ,OAAe,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["// packages/expo-audio-stream/src/events.ts\n\nimport { EventEmitter, type EventSubscription } from \"expo-modules-core\";\n\n// Type alias for backwards compatibility\nexport type Subscription = EventSubscription;\n\nimport ExpoPlayAudioStreamModule from \"./ExpoPlayAudioStreamModule\";\n\nconst emitter = new EventEmitter(ExpoPlayAudioStreamModule);\n\nexport interface AudioEventPayload {\n encoded?: string;\n buffer?: Float32Array;\n fileUri: string;\n lastEmittedSize: number;\n position: number;\n deltaSize: number;\n totalSize: number;\n mimeType: string;\n streamUuid: string;\n soundLevel?: number;\n frequencyBands?: { low: number; mid: number; high: number };\n /** Set by native when a mid-recording error occurs (interruption, read failure).\n * When present, `encoded` is absent and the recording is no longer active. */\n error?: string;\n errorMessage?: string;\n}\n\nexport const DeviceReconnectedReasons = {\n newDeviceAvailable: \"newDeviceAvailable\",\n oldDeviceUnavailable: \"oldDeviceUnavailable\",\n unknown: \"unknown\",\n} as const;\n\nexport type DeviceReconnectedReason =\n (typeof DeviceReconnectedReasons)[keyof typeof DeviceReconnectedReasons];\n\nexport type DeviceReconnectedEventPayload = {\n reason: DeviceReconnectedReason;\n};\n\nexport const AudioEvents = {\n AudioData: \"AudioData\",\n DeviceReconnected: \"DeviceReconnected\",\n};\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(\"AudioData\", listener);\n}\n\nexport function subscribeToEvent<T extends unknown>(\n eventName: string,\n listener: (event: T | undefined) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(eventName, listener);\n}\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAE3C,OAAO,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAC;AAKzE,OAAO,yBAAyB,MAAM,6BAA6B,CAAC;AAEpE,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,yBAAyB,CAAC,CAAC;AAoB5D,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,oBAAoB,EAAE,sBAAsB;IAC5C,OAAO,EAAE,SAAS;CACV,CAAC;AASX,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,SAAS,EAAE,WAAW;IACtB,eAAe,EAAE,iBAAiB;IAClC,iBAAiB,EAAE,mBAAmB;CACvC,CAAC;AAEF,MAAM,UAAU,qBAAqB,CACnC,QAAqD;IAErD,OAAQ,OAAe,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC7D,CAAC;AASD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAsD;IAEtD,OAAQ,OAAe,CAAC,WAAW,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,QAAiD;IAEjD,OAAQ,OAAe,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["// packages/expo-audio-stream/src/events.ts\n\nimport { EventEmitter, type EventSubscription } from \"expo-modules-core\";\n\n// Type alias for backwards compatibility\nexport type Subscription = EventSubscription;\n\nimport ExpoPlayAudioStreamModule from \"./ExpoPlayAudioStreamModule\";\n\nconst emitter = new EventEmitter(ExpoPlayAudioStreamModule);\n\nexport interface AudioEventPayload {\n encoded?: string;\n buffer?: Float32Array;\n fileUri: string;\n lastEmittedSize: number;\n position: number;\n deltaSize: number;\n totalSize: number;\n mimeType: string;\n streamUuid: string;\n soundLevel?: number;\n frequencyBands?: { low: number; mid: number; high: number };\n /** Set by native when a mid-recording error occurs (interruption, read failure).\n * When present, `encoded` is absent and the recording is no longer active. */\n error?: string;\n errorMessage?: string;\n}\n\nexport const DeviceReconnectedReasons = {\n newDeviceAvailable: \"newDeviceAvailable\",\n oldDeviceUnavailable: \"oldDeviceUnavailable\",\n unknown: \"unknown\",\n} as const;\n\nexport type DeviceReconnectedReason =\n (typeof DeviceReconnectedReasons)[keyof typeof DeviceReconnectedReasons];\n\nexport type DeviceReconnectedEventPayload = {\n reason: DeviceReconnectedReason;\n};\n\nexport const AudioEvents = {\n AudioData: \"AudioData\",\n MicrophoneError: \"MicrophoneError\",\n DeviceReconnected: \"DeviceReconnected\",\n};\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(\"AudioData\", listener);\n}\n\nexport interface MicrophoneErrorEventPayload {\n code: string;\n message: string;\n isFatal: boolean;\n autoResuming: boolean;\n}\n\n/**\n * Subscribe to the dedicated MicrophoneError native event.\n *\n * OTA safe: if the running native binary predates this feature, the event is\n * never emitted and this listener is never called. Apps that also subscribe to\n * AudioData errors via addAudioEventListener continue to work unchanged.\n *\n * @example\n * const sub = addMicrophoneErrorListener((e) => {\n * if (e.isFatal) { stopMicrophone(); reconnect(); }\n * else if (e.autoResuming) { showPausedUI(); }\n * })\n * // cleanup: sub.remove()\n */\nexport function addMicrophoneErrorListener(\n listener: (event: MicrophoneErrorEventPayload) => void\n): EventSubscription {\n return (emitter as any).addListener(\"MicrophoneError\", listener);\n}\n\nexport function subscribeToEvent<T extends unknown>(\n eventName: string,\n listener: (event: T | undefined) => Promise<void>\n): EventSubscription {\n return (emitter as any).addListener(eventName, listener);\n}\n"]}
package/build/index.d.ts CHANGED
@@ -72,6 +72,8 @@ export declare class ExpoPlayAudioStream {
72
72
  export { AudioDataEvent, DeviceReconnectedReason, DeviceReconnectedEventPayload, AudioRecording, RecordingConfig, StartRecordingResult, AudioEvents, PlaybackMode, Encoding, EncodingTypes, FrequencyBands, PlaybackModes, IAudioBufferConfig, IAudioPlayPayload, IAudioFrame, BufferHealthState, IBufferHealthMetrics, IAudioBufferManager, IFrameProcessor, IQualityMonitor, BufferedStreamConfig, SmartBufferConfig, SmartBufferMode, NetworkConditions, };
73
73
  export type { EventSubscription } from "expo-modules-core";
74
74
  export type { Subscription } from "./events";
75
+ export { addMicrophoneErrorListener } from "./events";
76
+ export type { MicrophoneErrorEventPayload } from "./events";
75
77
  export { Pipeline } from "./pipeline";
76
78
  export type { ConnectPipelineOptions, ConnectPipelineResult, PushPipelineAudioOptions, InvalidatePipelineTurnOptions, PipelineState, PipelineEventMap, PipelineEventName, PipelineBufferTelemetry, PipelineTelemetry, PipelineStateChangedEvent, PipelinePlaybackStartedEvent, PipelineErrorEvent, PipelineZombieDetectedEvent, PipelineUnderrunEvent, PipelineDrainedEvent, PipelinePlaybackStoppedEvent, PipelineAudioFocusLostEvent, PipelineAudioFocusResumedEvent, } from "./pipeline";
77
79
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI3D,KAAK,YAAY,GAAG,iBAAiB,CAAC;AACtC,OAAO,EACL,cAAc,EACd,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,cAAc,EACd,aAAa,EAEb,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAGL,WAAW,EAEX,uBAAuB,EACvB,6BAA6B,EAC9B,MAAM,UAAU,CAAC;AAElB,qBAAa,mBAAmB;IAC9B;;;;OAIG;WACU,OAAO;IAIpB;;;;;OAKG;WACU,eAAe,CAAC,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC;QACtE,eAAe,EAAE,oBAAoB,CAAC;QACtC,YAAY,CAAC,EAAE,YAAY,CAAC;KAC7B,CAAC;IAuDF;;;;OAIG;WACU,cAAc,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAS7D;;;;OAIG;IACH,MAAM,CAAC,sBAAsB,CAC3B,kBAAkB,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAC3D,YAAY;IAoBf;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,OAAO,EAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/C,YAAY;IAIf;;;;OAIG;IACH,MAAM,CAAC,qBAAqB;IAI5B;;;;OAIG;IACH,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO;IAItC;;;OAGG;WACU,uBAAuB,IAAI,OAAO,CAAC;QAC9C,OAAO,EAAE,OAAO,CAAC;QACjB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IASF;;;OAGG;WACU,mBAAmB,IAAI,OAAO,CAAC;QAC1C,OAAO,EAAE,OAAO,CAAC;QACjB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CAQH;AAED,OAAO,EACL,cAAc,EACd,uBAAuB,EACvB,6BAA6B,EAC7B,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,cAAc,EACd,aAAa,EAEb,kBAAkB,EAClB,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,GAClB,CAAC;AAGF,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,6BAA6B,EAC7B,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,EACzB,4BAA4B,EAC5B,kBAAkB,EAClB,2BAA2B,EAC3B,qBAAqB,EACrB,oBAAoB,EACpB,4BAA4B,EAC5B,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}