@edkimmel/expo-audio-stream 0.4.1 → 0.5.0
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/NATIVE_EVENTS.md +37 -0
- package/README.md +13 -0
- package/android/src/main/java/expo/modules/audiostream/ExpoPlayAudioStreamModule.kt +5 -0
- package/android/src/main/java/expo/modules/audiostream/pipeline/AudioPipeline.kt +152 -10
- package/android/src/main/java/expo/modules/audiostream/pipeline/PipelineIntegration.kt +16 -0
- package/build/events.d.ts +4 -0
- package/build/events.d.ts.map +1 -1
- package/build/events.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +8 -3
- package/build/index.js.map +1 -1
- package/build/pipeline/index.d.ts +14 -1
- package/build/pipeline/index.d.ts.map +1 -1
- package/build/pipeline/index.js +15 -0
- package/build/pipeline/index.js.map +1 -1
- package/build/pipeline/types.d.ts +34 -0
- package/build/pipeline/types.d.ts.map +1 -1
- package/build/pipeline/types.js.map +1 -1
- package/build/types.d.ts +8 -0
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/ios/AudioPipeline.swift +67 -2
- package/ios/ExpoPlayAudioStreamModule.swift +40 -0
- package/ios/Microphone.swift +116 -25
- package/ios/PipelineIntegration.swift +11 -1
- package/package.json +1 -2
- package/plugin/build/index.js +5 -0
- package/plugin/src/index.ts +5 -0
- package/src/events.ts +4 -0
- package/src/index.ts +12 -2
- package/src/pipeline/index.ts +17 -0
- package/src/pipeline/types.ts +36 -0
- package/src/types.ts +9 -0
package/NATIVE_EVENTS.md
CHANGED
|
@@ -200,6 +200,43 @@ The pipeline transitions from `draining` to `idle`.
|
|
|
200
200
|
|
|
201
201
|
---
|
|
202
202
|
|
|
203
|
+
### `PipelinePlaybackStopped`
|
|
204
|
+
|
|
205
|
+
Fired approximately `outputLatencyMs` after `PipelineDrained` for the same turn —
|
|
206
|
+
i.e., when the last sample physically leaves the speaker. Pairs symmetrically
|
|
207
|
+
with `PipelinePlaybackStarted` (start-of-emission ↔ end-of-emission).
|
|
208
|
+
|
|
209
|
+
**Distinct from `state: 'idle'`:** that's the pipeline-state-machine value
|
|
210
|
+
reported via `PipelineStateChanged`. `PipelinePlaybackStopped` is a
|
|
211
|
+
physical-world milestone about audible audio, not a state-machine transition.
|
|
212
|
+
|
|
213
|
+
| Field | Type | Notes |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `turnId` | `string` | The turn whose last sample just stopped emitting |
|
|
216
|
+
|
|
217
|
+
**Cancellation:** the scheduled dispatch is cancelled if a new turn starts,
|
|
218
|
+
`invalidatePipelineTurn` is called, or the pipeline disconnects before the
|
|
219
|
+
delay elapses. Consumers will not receive a late-arriving event mid-stream.
|
|
220
|
+
|
|
221
|
+
**Approximation:** the delay uses `outputLatencyMs` captured at drain time.
|
|
222
|
+
On iOS the value reflects `AVAudioSession.outputLatency` (total HW output
|
|
223
|
+
latency). On Android it uses `AudioTrack.getTimestamp()` to compute frames
|
|
224
|
+
in-flight, falling back to a HAL-buffer estimate if the timestamp call fails.
|
|
225
|
+
A route change during the latency window (e.g. switch to Bluetooth mid-tail)
|
|
226
|
+
is *not* re-measured — the captured value is used. The error is typically
|
|
227
|
+
single-digit milliseconds and is acceptable for VAD-gating use cases.
|
|
228
|
+
|
|
229
|
+
**Platform:** Android, iOS
|
|
230
|
+
|
|
231
|
+
**JS response:**
|
|
232
|
+
- Use this — not `PipelineDrained` — when you need to know when **mic-side
|
|
233
|
+
echo from the speaker is over**. Useful for voice-agent pipelines that
|
|
234
|
+
gate server-side VAD on the agent's own playback ending.
|
|
235
|
+
- If you do not care about the physical-world emission boundary, prefer
|
|
236
|
+
`PipelineDrained` (cheaper, fires earlier).
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
203
240
|
### `PipelineFrequencyBands`
|
|
204
241
|
|
|
205
242
|
Fired at the interval configured by `frequencyBandIntervalMs` during pipeline
|
package/README.md
CHANGED
|
@@ -79,6 +79,7 @@ const result = await Pipeline.connect({
|
|
|
79
79
|
channelCount: 1,
|
|
80
80
|
targetBufferMs: 80,
|
|
81
81
|
frequencyBandIntervalMs: 100, // optional: emit frequency bands every 100ms
|
|
82
|
+
audioMode: "mixWithOthers", // coexist with other apps (default)
|
|
82
83
|
});
|
|
83
84
|
|
|
84
85
|
// Subscribe to events
|
|
@@ -226,11 +227,23 @@ interface ConnectPipelineOptions {
|
|
|
226
227
|
sampleRate?: number; // default 24000
|
|
227
228
|
channelCount?: number; // default 1 (mono)
|
|
228
229
|
targetBufferMs?: number; // ms to buffer before priming gate opens (default 80)
|
|
230
|
+
playbackMode?: "voiceProcessing" | "conversation";
|
|
229
231
|
frequencyBandIntervalMs?: number; // emit PipelineFrequencyBands every N ms (omit to disable)
|
|
230
232
|
frequencyBandConfig?: FrequencyBandConfig; // crossover frequencies (optional)
|
|
233
|
+
audioMode?: "mixWithOthers" | "duckOthers" | "doNotMix"; // default "mixWithOthers"
|
|
231
234
|
}
|
|
232
235
|
```
|
|
233
236
|
|
|
237
|
+
#### `audioMode`
|
|
238
|
+
|
|
239
|
+
Controls how pipeline playback coexists with audio from other apps on the device. Default: `"mixWithOthers"` (matches expo-audio).
|
|
240
|
+
|
|
241
|
+
- **`"mixWithOthers"`** — plays alongside other apps without interrupting them. On Android no audio focus is requested; on iOS the session uses the `.mixWithOthers` category option. Best for sound effects and short clips.
|
|
242
|
+
- **`"duckOthers"`** — requests audio focus with ducking. Other apps lower their volume but keep playing.
|
|
243
|
+
- **`"doNotMix"`** — requests exclusive audio focus. Other apps pause.
|
|
244
|
+
|
|
245
|
+
> **Breaking change:** The default was effectively `"doNotMix"` in prior versions. If you rely on the previous behavior — where connecting the pipeline pauses other apps' audio — pass `audioMode: "doNotMix"` explicitly when calling `Pipeline.connect`.
|
|
246
|
+
|
|
234
247
|
### PushPipelineAudioOptions
|
|
235
248
|
|
|
236
249
|
```typescript
|
|
@@ -101,6 +101,7 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
|
|
|
101
101
|
PipelineIntegration.EVENT_ZOMBIE_DETECTED,
|
|
102
102
|
PipelineIntegration.EVENT_UNDERRUN,
|
|
103
103
|
PipelineIntegration.EVENT_DRAINED,
|
|
104
|
+
PipelineIntegration.EVENT_PLAYBACK_STOPPED,
|
|
104
105
|
PipelineIntegration.EVENT_AUDIO_FOCUS_LOST,
|
|
105
106
|
PipelineIntegration.EVENT_AUDIO_FOCUS_RESUMED,
|
|
106
107
|
PipelineIntegration.EVENT_FREQUENCY_BANDS
|
|
@@ -194,6 +195,10 @@ class ExpoPlayAudioStreamModule : Module(), EventSender {
|
|
|
194
195
|
pipelineIntegration.getState()
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
Function("getPipelineOutputLatencyMs") {
|
|
199
|
+
pipelineIntegration.outputLatencyMs()
|
|
200
|
+
}
|
|
201
|
+
|
|
197
202
|
}
|
|
198
203
|
private fun initializeManager() {
|
|
199
204
|
val androidContext =
|
|
@@ -49,6 +49,7 @@ interface PipelineListener {
|
|
|
49
49
|
fun onZombieDetected(playbackHead: Long, stalledMs: Long)
|
|
50
50
|
fun onUnderrun(count: Int)
|
|
51
51
|
fun onDrained(turnId: String)
|
|
52
|
+
fun onPlaybackStopped(turnId: String)
|
|
52
53
|
fun onAudioFocusLost()
|
|
53
54
|
fun onAudioFocusResumed()
|
|
54
55
|
fun onFrequencyBands(low: Float, mid: Float, high: Float)
|
|
@@ -58,6 +59,29 @@ interface PipelineListener {
|
|
|
58
59
|
// AudioPipeline
|
|
59
60
|
// ────────────────────────────────────────────────────────────────────────────
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Controls how pipeline playback coexists with audio from other apps.
|
|
64
|
+
* Mirrors the `PipelineAudioMode` TS type.
|
|
65
|
+
*/
|
|
66
|
+
enum class AudioMode {
|
|
67
|
+
/** No focus request — playback mixes freely with other audio. */
|
|
68
|
+
MIX_WITH_OTHERS,
|
|
69
|
+
|
|
70
|
+
/** Request transient focus with ducking — others lower volume but keep playing. */
|
|
71
|
+
DUCK_OTHERS,
|
|
72
|
+
|
|
73
|
+
/** Request exclusive focus — others pause. */
|
|
74
|
+
DO_NOT_MIX;
|
|
75
|
+
|
|
76
|
+
companion object {
|
|
77
|
+
fun fromString(value: String?): AudioMode = when (value) {
|
|
78
|
+
"duckOthers" -> DUCK_OTHERS
|
|
79
|
+
"doNotMix" -> DO_NOT_MIX
|
|
80
|
+
else -> MIX_WITH_OTHERS // default includes null, "mixWithOthers", and unknown
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
/**
|
|
62
86
|
* Core orchestrator for the native audio pipeline.
|
|
63
87
|
*
|
|
@@ -90,6 +114,7 @@ class AudioPipeline(
|
|
|
90
114
|
private val frequencyBandIntervalMs: Int = 100,
|
|
91
115
|
private val lowCrossoverHz: Float = 300f,
|
|
92
116
|
private val highCrossoverHz: Float = 2000f,
|
|
117
|
+
private val audioMode: AudioMode = AudioMode.MIX_WITH_OTHERS,
|
|
93
118
|
private val listener: PipelineListener
|
|
94
119
|
) {
|
|
95
120
|
companion object {
|
|
@@ -190,6 +215,13 @@ class AudioPipeline(
|
|
|
190
215
|
// ── Underrun debounce ───────────────────────────────────────────────
|
|
191
216
|
private var lastReportedUnderrunCount = 0
|
|
192
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Pending PlaybackStopped runnable scheduled on [mainHandler] — cancelled
|
|
220
|
+
* on new turn / invalidateTurn / disconnect. All mutations of this field
|
|
221
|
+
* MUST happen on the main thread to avoid races with the timer firing.
|
|
222
|
+
*/
|
|
223
|
+
private var pendingPlaybackStoppedRunnable: Runnable? = null
|
|
224
|
+
|
|
193
225
|
// ── Frequency band analysis ──────────────────────────────────────
|
|
194
226
|
private var frequencyBandAnalyzer: FrequencyBandAnalyzer? = null
|
|
195
227
|
private var frequencyBandExecutor: java.util.concurrent.ScheduledExecutorService? = null
|
|
@@ -203,6 +235,8 @@ class AudioPipeline(
|
|
|
203
235
|
val totalPushCalls = AtomicLong(0)
|
|
204
236
|
val totalPushBytes = AtomicLong(0)
|
|
205
237
|
val totalWriteLoops = AtomicLong(0)
|
|
238
|
+
/** Frames successfully written to AudioTrack (one frame per sample-time across all channels). */
|
|
239
|
+
private val framesWritten = AtomicLong(0)
|
|
206
240
|
|
|
207
241
|
// ════════════════════════════════════════════════════════════════════
|
|
208
242
|
// Connect / Disconnect
|
|
@@ -285,8 +319,8 @@ class AudioPipeline(
|
|
|
285
319
|
} catch (e: Exception) {
|
|
286
320
|
Log.e(TAG, "connect() failed", e)
|
|
287
321
|
setState(PipelineState.ERROR)
|
|
288
|
-
listener.onError("CONNECT_FAILED", e.message ?: "Unknown error")
|
|
289
322
|
disconnect()
|
|
323
|
+
throw e
|
|
290
324
|
}
|
|
291
325
|
}
|
|
292
326
|
|
|
@@ -297,6 +331,9 @@ class AudioPipeline(
|
|
|
297
331
|
* `WRITE_BLOCKING` call, then joins the thread.
|
|
298
332
|
*/
|
|
299
333
|
fun disconnect() {
|
|
334
|
+
// Cancel any pending PlaybackStopped dispatch before tearing down.
|
|
335
|
+
cancelPendingPlaybackStopped()
|
|
336
|
+
|
|
300
337
|
running.set(false)
|
|
301
338
|
|
|
302
339
|
// Stop zombie detection
|
|
@@ -383,6 +420,7 @@ class AudioPipeline(
|
|
|
383
420
|
pendingFlush.set(true)
|
|
384
421
|
setState(PipelineState.STREAMING)
|
|
385
422
|
frequencyBandAnalyzer?.reset()
|
|
423
|
+
cancelPendingPlaybackStopped()
|
|
386
424
|
}
|
|
387
425
|
|
|
388
426
|
// ── Decode base64 → PCM shorts ──────────────────────────────
|
|
@@ -427,6 +465,7 @@ class AudioPipeline(
|
|
|
427
465
|
lastReportedUnderrunCount = 0
|
|
428
466
|
setState(PipelineState.IDLE)
|
|
429
467
|
frequencyBandAnalyzer?.reset()
|
|
468
|
+
cancelPendingPlaybackStopped()
|
|
430
469
|
}
|
|
431
470
|
}
|
|
432
471
|
|
|
@@ -455,6 +494,37 @@ class AudioPipeline(
|
|
|
455
494
|
return bundle
|
|
456
495
|
}
|
|
457
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Current platform output latency in milliseconds — i.e., how long after
|
|
499
|
+
* a sample is written before it physically leaves the speaker.
|
|
500
|
+
*
|
|
501
|
+
* Uses [AudioTrack.getTimestamp] to compute frames still in flight, then
|
|
502
|
+
* converts to ms. Falls back to a conservative HAL-buffer estimate when
|
|
503
|
+
* the timestamp call returns false (notably during initial buffering or
|
|
504
|
+
* on bad audio routes).
|
|
505
|
+
*
|
|
506
|
+
* Returns 0 if the pipeline is not connected.
|
|
507
|
+
*/
|
|
508
|
+
fun outputLatencyMs(): Double {
|
|
509
|
+
val track = audioTrack ?: return 0.0
|
|
510
|
+
val ts = android.media.AudioTimestamp()
|
|
511
|
+
val ok = try {
|
|
512
|
+
track.getTimestamp(ts)
|
|
513
|
+
} catch (e: IllegalStateException) {
|
|
514
|
+
false
|
|
515
|
+
}
|
|
516
|
+
if (ok) {
|
|
517
|
+
val inFlight = framesWritten.get() - ts.framePosition
|
|
518
|
+
if (inFlight > 0) {
|
|
519
|
+
return (inFlight.toDouble() / sampleRate.toDouble()) * 1000.0
|
|
520
|
+
}
|
|
521
|
+
return 0.0
|
|
522
|
+
}
|
|
523
|
+
// Fallback: assume the HAL holds ~2× minBuffer worth of frames.
|
|
524
|
+
val fallbackFrames = (minBufferBytes / 2 / channelCount) * 2
|
|
525
|
+
return (fallbackFrames.toDouble() / sampleRate.toDouble()) * 1000.0
|
|
526
|
+
}
|
|
527
|
+
|
|
458
528
|
// ════════════════════════════════════════════════════════════════════
|
|
459
529
|
// Write loop (runs on MAX_PRIORITY thread)
|
|
460
530
|
// ════════════════════════════════════════════════════════════════════
|
|
@@ -510,6 +580,9 @@ class AudioPipeline(
|
|
|
510
580
|
listener.onError("WRITE_ERROR", "AudioTrack.write returned $errorName ($written)")
|
|
511
581
|
break
|
|
512
582
|
}
|
|
583
|
+
// Track frames written for output-latency computation.
|
|
584
|
+
// `written` is Int16-sample count; divide by channelCount for frames.
|
|
585
|
+
framesWritten.addAndGet((written / channelCount).toLong())
|
|
513
586
|
} catch (e: IllegalStateException) {
|
|
514
587
|
// Track was stopped/released — expected during disconnect
|
|
515
588
|
if (running.get()) {
|
|
@@ -537,7 +610,10 @@ class AudioPipeline(
|
|
|
537
610
|
|
|
538
611
|
// ── Drain detection ─────────────────────────────────────────
|
|
539
612
|
if (buf.isDrained() && state == PipelineState.DRAINING) {
|
|
540
|
-
currentTurnId?.let {
|
|
613
|
+
currentTurnId?.let { tid ->
|
|
614
|
+
listener.onDrained(tid)
|
|
615
|
+
schedulePlaybackStopped(tid)
|
|
616
|
+
}
|
|
541
617
|
setState(PipelineState.IDLE)
|
|
542
618
|
}
|
|
543
619
|
}
|
|
@@ -550,18 +626,46 @@ class AudioPipeline(
|
|
|
550
626
|
// ════════════════════════════════════════════════════════════════════
|
|
551
627
|
|
|
552
628
|
private fun requestAudioFocus() {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
629
|
+
when (audioMode) {
|
|
630
|
+
AudioMode.MIX_WITH_OTHERS -> {
|
|
631
|
+
// No focus request — we coexist silently with other apps.
|
|
632
|
+
// Mark as "has focus" so the write loop proceeds unconditionally.
|
|
633
|
+
hasAudioFocus.set(true)
|
|
634
|
+
audioFocusLost.set(false)
|
|
635
|
+
Log.d(TAG, "Audio focus skipped (mixWithOthers)")
|
|
636
|
+
}
|
|
637
|
+
AudioMode.DUCK_OTHERS -> {
|
|
638
|
+
val result = audioManager.requestAudioFocus(
|
|
639
|
+
focusChangeListener,
|
|
640
|
+
AudioManager.STREAM_MUSIC,
|
|
641
|
+
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
|
|
642
|
+
)
|
|
643
|
+
hasAudioFocus.set(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
|
|
644
|
+
if (!hasAudioFocus.get()) {
|
|
645
|
+
Log.w(TAG, "Audio focus request (duckOthers) denied")
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
AudioMode.DO_NOT_MIX -> {
|
|
649
|
+
val result = audioManager.requestAudioFocus(
|
|
650
|
+
focusChangeListener,
|
|
651
|
+
AudioManager.STREAM_MUSIC,
|
|
652
|
+
AudioManager.AUDIOFOCUS_GAIN
|
|
653
|
+
)
|
|
654
|
+
hasAudioFocus.set(result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
|
|
655
|
+
if (!hasAudioFocus.get()) {
|
|
656
|
+
Log.w(TAG, "Audio focus request (doNotMix) denied")
|
|
657
|
+
}
|
|
658
|
+
}
|
|
561
659
|
}
|
|
562
660
|
}
|
|
563
661
|
|
|
564
662
|
private fun abandonAudioFocus() {
|
|
663
|
+
if (audioMode == AudioMode.MIX_WITH_OTHERS) {
|
|
664
|
+
// No focus was ever requested — nothing to abandon.
|
|
665
|
+
hasAudioFocus.set(false)
|
|
666
|
+
audioFocusLost.set(false)
|
|
667
|
+
return
|
|
668
|
+
}
|
|
565
669
|
audioManager.abandonAudioFocus(focusChangeListener)
|
|
566
670
|
hasAudioFocus.set(false)
|
|
567
671
|
audioFocusLost.set(false)
|
|
@@ -720,6 +824,43 @@ class AudioPipeline(
|
|
|
720
824
|
// Internal helpers
|
|
721
825
|
// ════════════════════════════════════════════════════════════════════
|
|
722
826
|
|
|
827
|
+
/**
|
|
828
|
+
* Schedule a `PlaybackStopped` callback approximately [outputLatencyMs]
|
|
829
|
+
* milliseconds after `Drained`. Cancels any previously pending dispatch.
|
|
830
|
+
*
|
|
831
|
+
* Called from the write thread; the actual cancellation/scheduling and
|
|
832
|
+
* invocation happen on the main thread so we never race ourselves.
|
|
833
|
+
*/
|
|
834
|
+
private fun schedulePlaybackStopped(turnId: String) {
|
|
835
|
+
val latencyMs = outputLatencyMs().toLong()
|
|
836
|
+
mainHandler.post {
|
|
837
|
+
pendingPlaybackStoppedRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
838
|
+
|
|
839
|
+
val runnable = Runnable {
|
|
840
|
+
pendingPlaybackStoppedRunnable = null
|
|
841
|
+
listener.onPlaybackStopped(turnId)
|
|
842
|
+
}
|
|
843
|
+
pendingPlaybackStoppedRunnable = runnable
|
|
844
|
+
|
|
845
|
+
if (latencyMs > 0) {
|
|
846
|
+
mainHandler.postDelayed(runnable, latencyMs)
|
|
847
|
+
} else {
|
|
848
|
+
mainHandler.post(runnable)
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Cancel any pending PlaybackStopped dispatch. Posts to [mainHandler] so
|
|
855
|
+
* all mutations of [pendingPlaybackStoppedRunnable] are on one thread.
|
|
856
|
+
*/
|
|
857
|
+
private fun cancelPendingPlaybackStopped() {
|
|
858
|
+
mainHandler.post {
|
|
859
|
+
pendingPlaybackStoppedRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
860
|
+
pendingPlaybackStoppedRunnable = null
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
723
864
|
private fun setState(newState: PipelineState) {
|
|
724
865
|
if (state == newState) return
|
|
725
866
|
state = newState
|
|
@@ -735,6 +876,7 @@ class AudioPipeline(
|
|
|
735
876
|
totalPushCalls.set(0)
|
|
736
877
|
totalPushBytes.set(0)
|
|
737
878
|
totalWriteLoops.set(0)
|
|
879
|
+
framesWritten.set(0)
|
|
738
880
|
jitterBuffer?.resetTelemetry()
|
|
739
881
|
}
|
|
740
882
|
}
|
|
@@ -90,6 +90,7 @@ class PipelineIntegration(
|
|
|
90
90
|
const val EVENT_ZOMBIE_DETECTED = "PipelineZombieDetected"
|
|
91
91
|
const val EVENT_UNDERRUN = "PipelineUnderrun"
|
|
92
92
|
const val EVENT_DRAINED = "PipelineDrained"
|
|
93
|
+
const val EVENT_PLAYBACK_STOPPED = "PipelinePlaybackStopped"
|
|
93
94
|
const val EVENT_AUDIO_FOCUS_LOST = "PipelineAudioFocusLost"
|
|
94
95
|
const val EVENT_AUDIO_FOCUS_RESUMED = "PipelineAudioFocusResumed"
|
|
95
96
|
const val EVENT_FREQUENCY_BANDS = "PipelineFrequencyBands"
|
|
@@ -121,6 +122,7 @@ class PipelineIntegration(
|
|
|
121
122
|
val bandConfig = options["frequencyBandConfig"] as? Map<*, *>
|
|
122
123
|
val lowCrossoverHz = (bandConfig?.get("lowCrossoverHz") as? Number)?.toFloat() ?: 300f
|
|
123
124
|
val highCrossoverHz = (bandConfig?.get("highCrossoverHz") as? Number)?.toFloat() ?: 2000f
|
|
125
|
+
val audioMode = AudioMode.fromString(options["audioMode"] as? String)
|
|
124
126
|
|
|
125
127
|
pipeline = AudioPipeline(
|
|
126
128
|
context = context,
|
|
@@ -130,6 +132,7 @@ class PipelineIntegration(
|
|
|
130
132
|
frequencyBandIntervalMs = frequencyBandIntervalMs,
|
|
131
133
|
lowCrossoverHz = lowCrossoverHz,
|
|
132
134
|
highCrossoverHz = highCrossoverHz,
|
|
135
|
+
audioMode = audioMode,
|
|
133
136
|
listener = this
|
|
134
137
|
)
|
|
135
138
|
pipeline!!.connect()
|
|
@@ -245,6 +248,13 @@ class PipelineIntegration(
|
|
|
245
248
|
return pipeline?.getState()?.value ?: PipelineState.IDLE.value
|
|
246
249
|
}
|
|
247
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Current platform output latency in milliseconds. Returns 0 if not connected.
|
|
253
|
+
*/
|
|
254
|
+
fun outputLatencyMs(): Double {
|
|
255
|
+
return pipeline?.outputLatencyMs() ?: 0.0
|
|
256
|
+
}
|
|
257
|
+
|
|
248
258
|
/**
|
|
249
259
|
* Log AudioTrack health — called from the device callback to capture
|
|
250
260
|
* track state at the moment of a route change.
|
|
@@ -303,6 +313,12 @@ class PipelineIntegration(
|
|
|
303
313
|
})
|
|
304
314
|
}
|
|
305
315
|
|
|
316
|
+
override fun onPlaybackStopped(turnId: String) {
|
|
317
|
+
sendEvent(EVENT_PLAYBACK_STOPPED, Bundle().apply {
|
|
318
|
+
putString("turnId", turnId)
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
|
|
306
322
|
override fun onAudioFocusLost() {
|
|
307
323
|
sendEvent(EVENT_AUDIO_FOCUS_LOST, Bundle())
|
|
308
324
|
}
|
package/build/events.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export interface AudioEventPayload {
|
|
|
16
16
|
mid: number;
|
|
17
17
|
high: number;
|
|
18
18
|
};
|
|
19
|
+
/** Set by native when a mid-recording error occurs (interruption, read failure).
|
|
20
|
+
* When present, `encoded` is absent and the recording is no longer active. */
|
|
21
|
+
error?: string;
|
|
22
|
+
errorMessage?: string;
|
|
19
23
|
}
|
|
20
24
|
export declare const DeviceReconnectedReasons: {
|
|
21
25
|
readonly newDeviceAvailable: "newDeviceAvailable";
|
package/build/events.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/build/events.js.map
CHANGED
|
@@ -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;
|
|
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"]}
|
package/build/index.d.ts
CHANGED
|
@@ -73,5 +73,5 @@ export { AudioDataEvent, DeviceReconnectedReason, DeviceReconnectedEventPayload,
|
|
|
73
73
|
export type { EventSubscription } from "expo-modules-core";
|
|
74
74
|
export type { Subscription } from "./events";
|
|
75
75
|
export { Pipeline } from "./pipeline";
|
|
76
|
-
export type { ConnectPipelineOptions, ConnectPipelineResult, PushPipelineAudioOptions, InvalidatePipelineTurnOptions, PipelineState, PipelineEventMap, PipelineEventName, PipelineBufferTelemetry, PipelineTelemetry, PipelineStateChangedEvent, PipelinePlaybackStartedEvent, PipelineErrorEvent, PipelineZombieDetectedEvent, PipelineUnderrunEvent, PipelineDrainedEvent, PipelineAudioFocusLostEvent, PipelineAudioFocusResumedEvent, } from "./pipeline";
|
|
76
|
+
export type { ConnectPipelineOptions, ConnectPipelineResult, PushPipelineAudioOptions, InvalidatePipelineTurnOptions, PipelineState, PipelineEventMap, PipelineEventName, PipelineBufferTelemetry, PipelineTelemetry, PipelineStateChangedEvent, PipelinePlaybackStartedEvent, PipelineErrorEvent, PipelineZombieDetectedEvent, PipelineUnderrunEvent, PipelineDrainedEvent, PipelinePlaybackStoppedEvent, PipelineAudioFocusLostEvent, PipelineAudioFocusResumedEvent, } from "./pipeline";
|
|
77
77
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/build/index.js
CHANGED
|
@@ -19,10 +19,15 @@ export class ExpoPlayAudioStream {
|
|
|
19
19
|
static async startMicrophone(recordingConfig) {
|
|
20
20
|
let subscription;
|
|
21
21
|
try {
|
|
22
|
-
const { onAudioStream, ...options } = recordingConfig;
|
|
23
|
-
if (onAudioStream && typeof onAudioStream == "function")
|
|
22
|
+
const { onAudioStream, onError, ...options } = recordingConfig;
|
|
23
|
+
if ((onAudioStream && typeof onAudioStream == "function") ||
|
|
24
|
+
(onError && typeof onError == "function")) {
|
|
24
25
|
subscription = addAudioEventListener(async (event) => {
|
|
25
|
-
const { fileUri, deltaSize, totalSize, position, encoded, soundLevel, frequencyBands, } = event;
|
|
26
|
+
const { fileUri, deltaSize, totalSize, position, encoded, soundLevel, frequencyBands, error, errorMessage, } = event;
|
|
27
|
+
if (error) {
|
|
28
|
+
onError?.({ code: error, message: errorMessage ?? "" });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
26
31
|
if (!encoded) {
|
|
27
32
|
console.error(`[ExpoPlayAudioStream] Encoded audio data is missing`);
|
|
28
33
|
throw new Error("Encoded audio data is missing");
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\nimport ExpoPlayAudioStreamModule from \"./ExpoPlayAudioStreamModule\";\n\n// Type alias for backwards compatibility\ntype Subscription = EventSubscription;\nimport {\n AudioDataEvent,\n AudioRecording,\n RecordingConfig,\n StartRecordingResult,\n PlaybackMode,\n Encoding,\n EncodingTypes,\n FrequencyBands,\n PlaybackModes,\n // Audio jitter buffer types\n IAudioBufferConfig,\n IAudioPlayPayload,\n IAudioFrame,\n BufferHealthState,\n IBufferHealthMetrics,\n IAudioBufferManager,\n IFrameProcessor,\n IQualityMonitor,\n BufferedStreamConfig,\n SmartBufferConfig,\n SmartBufferMode,\n NetworkConditions,\n} from \"./types\";\n\nimport {\n addAudioEventListener,\n AudioEventPayload,\n AudioEvents,\n subscribeToEvent,\n DeviceReconnectedReason,\n DeviceReconnectedEventPayload,\n} from \"./events\";\n\nexport class ExpoPlayAudioStream {\n /**\n * Destroys the audio stream module, cleaning up all resources.\n * This should be called when the module is no longer needed.\n * It will reset all internal state and release audio resources.\n */\n static async destroy() {\n await ExpoPlayAudioStreamModule.destroy();\n }\n\n /**\n * Starts microphone streaming.\n * @param {RecordingConfig} recordingConfig - The recording configuration.\n * @returns {Promise<{recordingResult: StartRecordingResult, subscription: Subscription}>} A promise that resolves to an object containing the recording result and a subscription to audio events.\n * @throws {Error} If the recording fails to start.\n */\n static async startMicrophone(recordingConfig: RecordingConfig): Promise<{\n recordingResult: StartRecordingResult;\n subscription?: Subscription;\n }> {\n let subscription: Subscription | undefined;\n try {\n const { onAudioStream, ...options } = recordingConfig;\n\n if (onAudioStream && typeof onAudioStream == \"function\") {\n subscription = addAudioEventListener(\n async (event: AudioEventPayload) => {\n const {\n fileUri,\n deltaSize,\n totalSize,\n position,\n encoded,\n soundLevel,\n frequencyBands,\n } = event;\n if (!encoded) {\n console.error(\n `[ExpoPlayAudioStream] Encoded audio data is missing`\n );\n throw new Error(\"Encoded audio data is missing\");\n }\n onAudioStream?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n soundLevel,\n frequencyBands,\n });\n }\n );\n }\n\n const result = await ExpoPlayAudioStreamModule.startMicrophone(options);\n\n return { recordingResult: result, subscription };\n } catch (error) {\n console.error(error);\n subscription?.remove();\n throw new Error(`Failed to start recording: ${error}`);\n }\n }\n\n /**\n * Stops the current microphone streaming.\n * @returns {Promise<void>}\n * @throws {Error} If the microphone streaming fails to stop.\n */\n static async stopMicrophone(): Promise<AudioRecording | null> {\n try {\n return await ExpoPlayAudioStreamModule.stopMicrophone();\n } catch (error) {\n console.error(error);\n throw new Error(`Failed to stop mic stream: ${error}`);\n }\n }\n\n /**\n * Subscribes to audio events emitted during recording/streaming.\n * @param onMicrophoneStream - Callback function that will be called when audio data is received.\n * @returns {Subscription} A subscription object that can be used to unsubscribe from the events\n */\n static subscribeToAudioEvents(\n onMicrophoneStream: (event: AudioDataEvent) => Promise<void>\n ): Subscription {\n return addAudioEventListener(async (event: AudioEventPayload) => {\n const { fileUri, deltaSize, totalSize, position, encoded, soundLevel, frequencyBands } =\n event;\n if (!encoded) {\n console.error(`[ExpoPlayAudioStream] Encoded audio data is missing`);\n throw new Error(\"Encoded audio data is missing\");\n }\n onMicrophoneStream?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n soundLevel,\n frequencyBands,\n });\n });\n }\n\n /**\n * Subscribes to events emitted by the audio stream module, for advanced use cases.\n * @param eventName - The name of the event to subscribe to.\n * @param onEvent - Callback function that will be called when the event is emitted.\n * @returns {Subscription} A subscription object that can be used to unsubscribe from the events.\n */\n static subscribe<T extends unknown>(\n eventName: string,\n onEvent: (event: T | undefined) => Promise<void>\n ): Subscription {\n return subscribeToEvent(eventName, onEvent);\n }\n\n /**\n * Prompts the user to select the microphone mode.\n * @returns {Promise<void>}\n * @throws {Error} If the microphone mode fails to prompt.\n */\n static promptMicrophoneModes() {\n ExpoPlayAudioStreamModule.promptMicrophoneModes();\n }\n\n /**\n * Toggles the silence state of the microphone.\n * @returns {Promise<void>}\n * @throws {Error} If the microphone fails to toggle silence.\n */\n static toggleSilence(isSilent: boolean) {\n ExpoPlayAudioStreamModule.toggleSilence(isSilent);\n }\n\n /**\n * Requests microphone permission from the user.\n * @returns {Promise<{granted: boolean, canAskAgain?: boolean, status?: string}>} A promise that resolves to the permission result.\n */\n static async requestPermissionsAsync(): Promise<{\n granted: boolean;\n canAskAgain?: boolean;\n status?: string;\n }> {\n try {\n return await ExpoPlayAudioStreamModule.requestPermissionsAsync();\n } catch (error) {\n console.error(error);\n throw new Error(`Failed to request permissions: ${error}`);\n }\n }\n\n /**\n * Gets the current microphone permission status.\n * @returns {Promise<{granted: boolean, canAskAgain?: boolean, status?: string}>} A promise that resolves to the permission status.\n */\n static async getPermissionsAsync(): Promise<{\n granted: boolean;\n canAskAgain?: boolean;\n status?: string;\n }> {\n try {\n return await ExpoPlayAudioStreamModule.getPermissionsAsync();\n } catch (error) {\n console.error(error);\n throw new Error(`Failed to get permissions: ${error}`);\n }\n }\n}\n\nexport {\n AudioDataEvent,\n DeviceReconnectedReason,\n DeviceReconnectedEventPayload,\n AudioRecording,\n RecordingConfig,\n StartRecordingResult,\n AudioEvents,\n PlaybackMode,\n Encoding,\n EncodingTypes,\n FrequencyBands,\n PlaybackModes,\n // Audio jitter buffer types\n IAudioBufferConfig,\n IAudioPlayPayload,\n IAudioFrame,\n BufferHealthState,\n IBufferHealthMetrics,\n IAudioBufferManager,\n IFrameProcessor,\n IQualityMonitor,\n BufferedStreamConfig,\n SmartBufferConfig,\n SmartBufferMode,\n NetworkConditions,\n};\n\n// Re-export Subscription type for backwards compatibility\nexport type { EventSubscription } from \"expo-modules-core\";\nexport type { Subscription } from \"./events\";\n\n// Export native audio pipeline V3\nexport { Pipeline } from \"./pipeline\";\nexport type {\n ConnectPipelineOptions,\n ConnectPipelineResult,\n PushPipelineAudioOptions,\n InvalidatePipelineTurnOptions,\n PipelineState,\n PipelineEventMap,\n PipelineEventName,\n PipelineBufferTelemetry,\n PipelineTelemetry,\n PipelineStateChangedEvent,\n PipelinePlaybackStartedEvent,\n PipelineErrorEvent,\n PipelineZombieDetectedEvent,\n PipelineUnderrunEvent,\n PipelineDrainedEvent,\n PipelineAudioFocusLostEvent,\n PipelineAudioFocusResumedEvent,\n} from \"./pipeline\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,yBAAyB,MAAM,6BAA6B,CAAC;AAIpE,OAAO,EAOL,aAAa,EAEb,aAAa,GAcd,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,qBAAqB,EAErB,WAAW,EACX,gBAAgB,GAGjB,MAAM,UAAU,CAAC;AAElB,MAAM,OAAO,mBAAmB;IAC9B;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,OAAO;QAClB,MAAM,yBAAyB,CAAC,OAAO,EAAE,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,eAAgC;QAI3D,IAAI,YAAsC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,eAAe,CAAC;YAE/D,IACE,CAAC,aAAa,IAAI,OAAO,aAAa,IAAI,UAAU,CAAC;gBACrD,CAAC,OAAO,IAAI,OAAO,OAAO,IAAI,UAAU,CAAC,EACzC,CAAC;gBACD,YAAY,GAAG,qBAAqB,CAClC,KAAK,EAAE,KAAwB,EAAE,EAAE;oBACjC,MAAM,EACJ,OAAO,EACP,SAAS,EACT,SAAS,EACT,QAAQ,EACR,OAAO,EACP,UAAU,EACV,cAAc,EACd,KAAK,EACL,YAAY,GACb,GAAG,KAAK,CAAC;oBACV,IAAI,KAAK,EAAE,CAAC;wBACV,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,IAAI,EAAE,EAAE,CAAC,CAAC;wBACxD,OAAO;oBACT,CAAC;oBACD,IAAI,CAAC,OAAO,EAAE,CAAC;wBACb,OAAO,CAAC,KAAK,CACX,qDAAqD,CACtD,CAAC;wBACF,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;oBACnD,CAAC;oBACD,aAAa,EAAE,CAAC;wBACd,IAAI,EAAE,OAAO;wBACb,QAAQ;wBACR,OAAO;wBACP,aAAa,EAAE,SAAS;wBACxB,SAAS;wBACT,UAAU;wBACV,cAAc;qBACf,CAAC,CAAC;gBACL,CAAC,CACF,CAAC;YACJ,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,yBAAyB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YAExE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrB,YAAY,EAAE,MAAM,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,cAAc;QACzB,IAAI,CAAC;YACH,OAAO,MAAM,yBAAyB,CAAC,cAAc,EAAE,CAAC;QAC1D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,sBAAsB,CAC3B,kBAA4D;QAE5D,OAAO,qBAAqB,CAAC,KAAK,EAAE,KAAwB,EAAE,EAAE;YAC9D,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,GACpF,KAAK,CAAC;YACR,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;gBACrE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACnD,CAAC;YACD,kBAAkB,EAAE,CAAC;gBACnB,IAAI,EAAE,OAAO;gBACb,QAAQ;gBACR,OAAO;gBACP,aAAa,EAAE,SAAS;gBACxB,SAAS;gBACT,UAAU;gBACV,cAAc;aACf,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CACd,SAAiB,EACjB,OAAgD;QAEhD,OAAO,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,qBAAqB;QAC1B,yBAAyB,CAAC,qBAAqB,EAAE,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,aAAa,CAAC,QAAiB;QACpC,yBAAyB,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,uBAAuB;QAKlC,IAAI,CAAC;YACH,OAAO,MAAM,yBAAyB,CAAC,uBAAuB,EAAE,CAAC;QACnE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,kCAAkC,KAAK,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,mBAAmB;QAK9B,IAAI,CAAC;YACH,OAAO,MAAM,yBAAyB,CAAC,mBAAmB,EAAE,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;CACF;AAED,OAAO,EAOL,WAAW,EAGX,aAAa,EAEb,aAAa,GAcd,CAAC;AAMF,kCAAkC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC","sourcesContent":["import type { EventSubscription } from \"expo-modules-core\";\nimport ExpoPlayAudioStreamModule from \"./ExpoPlayAudioStreamModule\";\n\n// Type alias for backwards compatibility\ntype Subscription = EventSubscription;\nimport {\n AudioDataEvent,\n AudioRecording,\n RecordingConfig,\n StartRecordingResult,\n PlaybackMode,\n Encoding,\n EncodingTypes,\n FrequencyBands,\n PlaybackModes,\n // Audio jitter buffer types\n IAudioBufferConfig,\n IAudioPlayPayload,\n IAudioFrame,\n BufferHealthState,\n IBufferHealthMetrics,\n IAudioBufferManager,\n IFrameProcessor,\n IQualityMonitor,\n BufferedStreamConfig,\n SmartBufferConfig,\n SmartBufferMode,\n NetworkConditions,\n} from \"./types\";\n\nimport {\n addAudioEventListener,\n AudioEventPayload,\n AudioEvents,\n subscribeToEvent,\n DeviceReconnectedReason,\n DeviceReconnectedEventPayload,\n} from \"./events\";\n\nexport class ExpoPlayAudioStream {\n /**\n * Destroys the audio stream module, cleaning up all resources.\n * This should be called when the module is no longer needed.\n * It will reset all internal state and release audio resources.\n */\n static async destroy() {\n await ExpoPlayAudioStreamModule.destroy();\n }\n\n /**\n * Starts microphone streaming.\n * @param {RecordingConfig} recordingConfig - The recording configuration.\n * @returns {Promise<{recordingResult: StartRecordingResult, subscription: Subscription}>} A promise that resolves to an object containing the recording result and a subscription to audio events.\n * @throws {Error} If the recording fails to start.\n */\n static async startMicrophone(recordingConfig: RecordingConfig): Promise<{\n recordingResult: StartRecordingResult;\n subscription?: Subscription;\n }> {\n let subscription: Subscription | undefined;\n try {\n const { onAudioStream, onError, ...options } = recordingConfig;\n\n if (\n (onAudioStream && typeof onAudioStream == \"function\") ||\n (onError && typeof onError == \"function\")\n ) {\n subscription = addAudioEventListener(\n async (event: AudioEventPayload) => {\n const {\n fileUri,\n deltaSize,\n totalSize,\n position,\n encoded,\n soundLevel,\n frequencyBands,\n error,\n errorMessage,\n } = event;\n if (error) {\n onError?.({ code: error, message: errorMessage ?? \"\" });\n return;\n }\n if (!encoded) {\n console.error(\n `[ExpoPlayAudioStream] Encoded audio data is missing`\n );\n throw new Error(\"Encoded audio data is missing\");\n }\n onAudioStream?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n soundLevel,\n frequencyBands,\n });\n }\n );\n }\n\n const result = await ExpoPlayAudioStreamModule.startMicrophone(options);\n\n return { recordingResult: result, subscription };\n } catch (error) {\n console.error(error);\n subscription?.remove();\n throw new Error(`Failed to start recording: ${error}`);\n }\n }\n\n /**\n * Stops the current microphone streaming.\n * @returns {Promise<void>}\n * @throws {Error} If the microphone streaming fails to stop.\n */\n static async stopMicrophone(): Promise<AudioRecording | null> {\n try {\n return await ExpoPlayAudioStreamModule.stopMicrophone();\n } catch (error) {\n console.error(error);\n throw new Error(`Failed to stop mic stream: ${error}`);\n }\n }\n\n /**\n * Subscribes to audio events emitted during recording/streaming.\n * @param onMicrophoneStream - Callback function that will be called when audio data is received.\n * @returns {Subscription} A subscription object that can be used to unsubscribe from the events\n */\n static subscribeToAudioEvents(\n onMicrophoneStream: (event: AudioDataEvent) => Promise<void>\n ): Subscription {\n return addAudioEventListener(async (event: AudioEventPayload) => {\n const { fileUri, deltaSize, totalSize, position, encoded, soundLevel, frequencyBands } =\n event;\n if (!encoded) {\n console.error(`[ExpoPlayAudioStream] Encoded audio data is missing`);\n throw new Error(\"Encoded audio data is missing\");\n }\n onMicrophoneStream?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n soundLevel,\n frequencyBands,\n });\n });\n }\n\n /**\n * Subscribes to events emitted by the audio stream module, for advanced use cases.\n * @param eventName - The name of the event to subscribe to.\n * @param onEvent - Callback function that will be called when the event is emitted.\n * @returns {Subscription} A subscription object that can be used to unsubscribe from the events.\n */\n static subscribe<T extends unknown>(\n eventName: string,\n onEvent: (event: T | undefined) => Promise<void>\n ): Subscription {\n return subscribeToEvent(eventName, onEvent);\n }\n\n /**\n * Prompts the user to select the microphone mode.\n * @returns {Promise<void>}\n * @throws {Error} If the microphone mode fails to prompt.\n */\n static promptMicrophoneModes() {\n ExpoPlayAudioStreamModule.promptMicrophoneModes();\n }\n\n /**\n * Toggles the silence state of the microphone.\n * @returns {Promise<void>}\n * @throws {Error} If the microphone fails to toggle silence.\n */\n static toggleSilence(isSilent: boolean) {\n ExpoPlayAudioStreamModule.toggleSilence(isSilent);\n }\n\n /**\n * Requests microphone permission from the user.\n * @returns {Promise<{granted: boolean, canAskAgain?: boolean, status?: string}>} A promise that resolves to the permission result.\n */\n static async requestPermissionsAsync(): Promise<{\n granted: boolean;\n canAskAgain?: boolean;\n status?: string;\n }> {\n try {\n return await ExpoPlayAudioStreamModule.requestPermissionsAsync();\n } catch (error) {\n console.error(error);\n throw new Error(`Failed to request permissions: ${error}`);\n }\n }\n\n /**\n * Gets the current microphone permission status.\n * @returns {Promise<{granted: boolean, canAskAgain?: boolean, status?: string}>} A promise that resolves to the permission status.\n */\n static async getPermissionsAsync(): Promise<{\n granted: boolean;\n canAskAgain?: boolean;\n status?: string;\n }> {\n try {\n return await ExpoPlayAudioStreamModule.getPermissionsAsync();\n } catch (error) {\n console.error(error);\n throw new Error(`Failed to get permissions: ${error}`);\n }\n }\n}\n\nexport {\n AudioDataEvent,\n DeviceReconnectedReason,\n DeviceReconnectedEventPayload,\n AudioRecording,\n RecordingConfig,\n StartRecordingResult,\n AudioEvents,\n PlaybackMode,\n Encoding,\n EncodingTypes,\n FrequencyBands,\n PlaybackModes,\n // Audio jitter buffer types\n IAudioBufferConfig,\n IAudioPlayPayload,\n IAudioFrame,\n BufferHealthState,\n IBufferHealthMetrics,\n IAudioBufferManager,\n IFrameProcessor,\n IQualityMonitor,\n BufferedStreamConfig,\n SmartBufferConfig,\n SmartBufferMode,\n NetworkConditions,\n};\n\n// Re-export Subscription type for backwards compatibility\nexport type { EventSubscription } from \"expo-modules-core\";\nexport type { Subscription } from \"./events\";\n\n// Export native audio pipeline V3\nexport { Pipeline } from \"./pipeline\";\nexport type {\n ConnectPipelineOptions,\n ConnectPipelineResult,\n PushPipelineAudioOptions,\n InvalidatePipelineTurnOptions,\n PipelineState,\n PipelineEventMap,\n PipelineEventName,\n PipelineBufferTelemetry,\n PipelineTelemetry,\n PipelineStateChangedEvent,\n PipelinePlaybackStartedEvent,\n PipelineErrorEvent,\n PipelineZombieDetectedEvent,\n PipelineUnderrunEvent,\n PipelineDrainedEvent,\n PipelinePlaybackStoppedEvent,\n PipelineAudioFocusLostEvent,\n PipelineAudioFocusResumedEvent,\n} from \"./pipeline\";\n"]}
|
|
@@ -39,6 +39,19 @@ export declare class Pipeline {
|
|
|
39
39
|
static getState(): PipelineState;
|
|
40
40
|
/** Get a telemetry snapshot (buffer levels, counters, etc.). */
|
|
41
41
|
static getTelemetry(): PipelineTelemetry;
|
|
42
|
+
/**
|
|
43
|
+
* Query the platform's current output latency — i.e., how long after a
|
|
44
|
+
* sample is written to the native buffer before it actually leaves the
|
|
45
|
+
* speaker.
|
|
46
|
+
*
|
|
47
|
+
* Value can change mid-session, notably on audio route changes such as
|
|
48
|
+
* switching from built-in speaker to Bluetooth (Bluetooth typically adds
|
|
49
|
+
* 100+ ms). **Always query at the moment you care; do not cache.**
|
|
50
|
+
*
|
|
51
|
+
* Returns 0 if the pipeline is not connected or the platform cannot
|
|
52
|
+
* report a value.
|
|
53
|
+
*/
|
|
54
|
+
static getOutputLatencyMs(): number;
|
|
42
55
|
/**
|
|
43
56
|
* Subscribe to a specific pipeline event with full type safety.
|
|
44
57
|
*
|
|
@@ -77,5 +90,5 @@ export declare class Pipeline {
|
|
|
77
90
|
remove: () => void;
|
|
78
91
|
};
|
|
79
92
|
}
|
|
80
|
-
export type { ConnectPipelineOptions, ConnectPipelineResult, PushPipelineAudioOptions, InvalidatePipelineTurnOptions, PipelineState, PipelineEventMap, PipelineEventName, PipelineBufferTelemetry, PipelineTelemetry, PipelineStateChangedEvent, PipelinePlaybackStartedEvent, PipelineErrorEvent, PipelineZombieDetectedEvent, PipelineUnderrunEvent, PipelineDrainedEvent, PipelineAudioFocusLostEvent, PipelineAudioFocusResumedEvent, } from './types';
|
|
93
|
+
export type { ConnectPipelineOptions, ConnectPipelineResult, PushPipelineAudioOptions, InvalidatePipelineTurnOptions, PipelineState, PipelineEventMap, PipelineEventName, PipelineBufferTelemetry, PipelineTelemetry, PipelineStateChangedEvent, PipelinePlaybackStartedEvent, PipelineErrorEvent, PipelineZombieDetectedEvent, PipelineUnderrunEvent, PipelineDrainedEvent, PipelinePlaybackStoppedEvent, PipelineAudioFocusLostEvent, PipelineAudioFocusResumedEvent, } from './types';
|
|
81
94
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pipeline/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI3D,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,6BAA6B,EAC7B,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,qBAAa,QAAQ;IAKnB;;;;;;OAMG;WACU,OAAO,CAClB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,qBAAqB,CAAC;IAIjC;;;OAGG;WACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQxC;;;;;;OAMG;WACU,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxE;;;;;;OAMG;IACH,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO;IAQhE;;;OAGG;WACU,cAAc,CACzB,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC;IAQhB,oDAAoD;IACpD,MAAM,CAAC,QAAQ,IAAI,aAAa;IAIhC,gEAAgE;IAChE,MAAM,CAAC,YAAY,IAAI,iBAAiB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pipeline/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI3D,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,EACxB,6BAA6B,EAC7B,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,qBAAa,QAAQ;IAKnB;;;;;;OAMG;WACU,OAAO,CAClB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,qBAAqB,CAAC;IAIjC;;;OAGG;WACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQxC;;;;;;OAMG;WACU,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxE;;;;;;OAMG;IACH,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO;IAQhE;;;OAGG;WACU,cAAc,CACzB,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC;IAQhB,oDAAoD;IACpD,MAAM,CAAC,QAAQ,IAAI,aAAa;IAIhC,gEAAgE;IAChE,MAAM,CAAC,YAAY,IAAI,iBAAiB;IAIxC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,kBAAkB,IAAI,MAAM;IAQnC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,iBAAiB,EAC1C,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAC7D,iBAAiB;IAWpB;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CACZ,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAC3D;QAAE,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE;IAuBzB;;;;;;OAMG;IACH,MAAM,CAAC,YAAY,CACjB,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAC9C;QAAE,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE;CAmB1B;AAGD,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,SAAS,CAAC"}
|
package/build/pipeline/index.js
CHANGED
|
@@ -74,6 +74,21 @@ export class Pipeline {
|
|
|
74
74
|
static getTelemetry() {
|
|
75
75
|
return ExpoPlayAudioStreamModule.getPipelineTelemetry();
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Query the platform's current output latency — i.e., how long after a
|
|
79
|
+
* sample is written to the native buffer before it actually leaves the
|
|
80
|
+
* speaker.
|
|
81
|
+
*
|
|
82
|
+
* Value can change mid-session, notably on audio route changes such as
|
|
83
|
+
* switching from built-in speaker to Bluetooth (Bluetooth typically adds
|
|
84
|
+
* 100+ ms). **Always query at the moment you care; do not cache.**
|
|
85
|
+
*
|
|
86
|
+
* Returns 0 if the pipeline is not connected or the platform cannot
|
|
87
|
+
* report a value.
|
|
88
|
+
*/
|
|
89
|
+
static getOutputLatencyMs() {
|
|
90
|
+
return ExpoPlayAudioStreamModule.getPipelineOutputLatencyMs();
|
|
91
|
+
}
|
|
77
92
|
// ════════════════════════════════════════════════════════════════════════
|
|
78
93
|
// Event subscriptions
|
|
79
94
|
// ════════════════════════════════════════════════════════════════════════
|