@edkimmel/expo-audio-stream 0.4.2 → 0.6.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 +97 -6
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/src/main/java/expo/modules/audiostream/AudioEffectsManager.kt +6 -11
- package/android/src/main/java/expo/modules/audiostream/AudioRecorderManager.kt +23 -6
- package/android/src/main/java/expo/modules/audiostream/CommunicationAudioManager.kt +155 -0
- package/android/src/main/java/expo/modules/audiostream/Constants.kt +1 -0
- package/android/src/main/java/expo/modules/audiostream/ExpoPlayAudioStreamModule.kt +17 -3
- package/android/src/main/java/expo/modules/audiostream/pipeline/AudioPipeline.kt +104 -11
- package/android/src/main/java/expo/modules/audiostream/pipeline/PipelineIntegration.kt +14 -0
- package/build/events.d.ts +26 -0
- package/build/events.d.ts.map +1 -1
- package/build/events.js +18 -0
- package/build/events.js.map +1 -1
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +19 -7
- 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 +14 -0
- package/build/pipeline/types.d.ts.map +1 -1
- package/build/pipeline/types.js.map +1 -1
- package/build/types.d.ts +21 -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 +43 -12
- package/ios/Microphone.swift +167 -120
- package/ios/MicrophoneDataDelegate.swift +10 -1
- package/ios/PipelineIntegration.swift +11 -1
- package/ios/SharedAudioEngine.swift +18 -0
- package/package.json +1 -2
- package/plugin/build/index.js +5 -0
- package/plugin/src/index.ts +5 -0
- package/src/events.ts +32 -0
- package/src/index.ts +27 -18
- package/src/pipeline/index.ts +17 -0
- package/src/pipeline/types.ts +15 -0
- package/src/types.ts +22 -0
|
@@ -103,6 +103,19 @@ export interface PipelineUnderrunEvent {
|
|
|
103
103
|
export interface PipelineDrainedEvent {
|
|
104
104
|
turnId: string;
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Payload for `PipelinePlaybackStopped`.
|
|
108
|
+
*
|
|
109
|
+
* Fired when the last sample physically leaves the speaker, approximately
|
|
110
|
+
* `outputLatencyMs` after `PipelineDrained` for the same turn. Pairs with
|
|
111
|
+
* `PipelinePlaybackStarted` (start-of-emission ↔ end-of-emission).
|
|
112
|
+
*
|
|
113
|
+
* Note: this is a physical-world milestone, distinct from `state: 'idle'`
|
|
114
|
+
* (the pipeline-state-machine value reported via `PipelineStateChanged`).
|
|
115
|
+
*/
|
|
116
|
+
export interface PipelinePlaybackStoppedEvent {
|
|
117
|
+
turnId: string;
|
|
118
|
+
}
|
|
106
119
|
/** Payload for `PipelineAudioFocusLost` (empty — presence is the signal). */
|
|
107
120
|
export type PipelineAudioFocusLostEvent = Record<string, never>;
|
|
108
121
|
/** Payload for `PipelineAudioFocusResumed` (empty — presence is the signal). */
|
|
@@ -121,6 +134,7 @@ export interface PipelineEventMap {
|
|
|
121
134
|
PipelineZombieDetected: PipelineZombieDetectedEvent;
|
|
122
135
|
PipelineUnderrun: PipelineUnderrunEvent;
|
|
123
136
|
PipelineDrained: PipelineDrainedEvent;
|
|
137
|
+
PipelinePlaybackStopped: PipelinePlaybackStoppedEvent;
|
|
124
138
|
PipelineAudioFocusLost: PipelineAudioFocusLostEvent;
|
|
125
139
|
PipelineAudioFocusResumed: PipelineAudioFocusResumedEvent;
|
|
126
140
|
PipelineFrequencyBands: PipelineFrequencyBandsEvent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8CAA8C;AAC9C,+EAA+E","sourcesContent":["// ────────────────────────────────────────────────────────────────────────────\n// Native Audio Pipeline — V3 TypeScript Types\n// ────────────────────────────────────────────────────────────────────────────\n\nimport { PlaybackMode, FrequencyBandConfig, FrequencyBands } from \"../types\";\n\n// ── Connect ─────────────────────────────────────────────────────────────────\n\n/**\n * How the pipeline's playback should coexist with other audio on the device.\n *\n * - `'mixWithOthers'` (default): plays alongside other apps without\n * interrupting them. On Android no audio focus is requested. Best for\n * sound effects and short clips.\n * - `'duckOthers'`: requests audio focus with ducking. Other apps lower\n * their volume but keep playing.\n * - `'doNotMix'`: requests exclusive audio focus. Other apps pause.\n */\nexport type PipelineAudioMode = 'mixWithOthers' | 'duckOthers' | 'doNotMix';\n\n/** Options passed to `connectPipeline()`. */\nexport interface ConnectPipelineOptions {\n /** Sample rate in Hz (default 24000). */\n sampleRate?: number;\n /** Number of channels — 1 = mono, 2 = stereo (default 1). */\n channelCount?: number;\n /**\n * How many ms of audio to accumulate in the jitter buffer before the\n * priming gate opens and audio begins playing (default 80).\n */\n targetBufferMs?: number;\n /**\n * Playback mode hint for native optimizations. Affects thread priority and\n */\n playbackMode?: PlaybackMode;\n /** Interval in ms for PipelineFrequencyBands events (default 100). */\n frequencyBandIntervalMs?: number;\n /** Optional frequency band crossover configuration. */\n frequencyBandConfig?: FrequencyBandConfig;\n /**\n * How pipeline playback should coexist with other apps' audio.\n * Default is `'mixWithOthers'` (matches expo-audio).\n *\n * Note: this is a **behavior change** vs. prior versions of this library,\n * which effectively used `'doNotMix'`. Pass `'doNotMix'` explicitly to\n * preserve that old behavior.\n */\n audioMode?: PipelineAudioMode;\n}\n\n/** Result returned from a successful `connectPipeline()` call. */\nexport interface ConnectPipelineResult {\n sampleRate: number;\n channelCount: number;\n targetBufferMs: number;\n /**\n * Frame size in samples derived from the device HAL's\n * `AudioTrack.getMinBufferSize()`. Useful for understanding the write\n * granularity on the native side.\n */\n frameSizeSamples: number;\n}\n\n// ── Push Audio ──────────────────────────────────────────────────────────────\n\n/** Options passed to `pushPipelineAudio()` / `pushPipelineAudioSync()`. */\nexport interface PushPipelineAudioOptions {\n /** Base64-encoded PCM 16-bit signed LE audio data. */\n audio: string;\n /** Conversation turn identifier. */\n turnId: string;\n /** True if this is the first chunk of a new turn (resets jitter buffer). */\n isFirstChunk?: boolean;\n /** True if this is the final chunk of the current turn (marks end-of-stream). */\n isLastChunk?: boolean;\n}\n\n// ── Invalidate Turn ─────────────────────────────────────────────────────────\n\n/** Options passed to `invalidatePipelineTurn()`. */\nexport interface InvalidatePipelineTurnOptions {\n /** The new turn identifier — stale audio for the old turn is discarded. */\n turnId: string;\n}\n\n// ── State ───────────────────────────────────────────────────────────────────\n\n/**\n * Pipeline states reported via `PipelineStateChanged` events.\n *\n * - `idle` — connected but no audio flowing\n * - `connecting` — AudioTrack being created, focus being requested\n * - `streaming` — actively receiving and playing audio\n * - `draining` — end-of-stream marked, playing remaining buffer\n * - `error` — unrecoverable error (zombie, write failure, etc.)\n */\nexport type PipelineState =\n | 'idle'\n | 'connecting'\n | 'streaming'\n | 'draining'\n | 'error';\n\n// ── Events ──────────────────────────────────────────────────────────────────\n\n/** Payload for `PipelineStateChanged`. */\nexport interface PipelineStateChangedEvent {\n state: PipelineState;\n}\n\n/** Payload for `PipelinePlaybackStarted`. */\nexport interface PipelinePlaybackStartedEvent {\n turnId: string;\n}\n\n/** Payload for `PipelineError`. */\nexport interface PipelineErrorEvent {\n code: string;\n message: string;\n}\n\n/** Payload for `PipelineZombieDetected`. */\nexport interface PipelineZombieDetectedEvent {\n playbackHead: number;\n stalledMs: number;\n}\n\n/** Payload for `PipelineUnderrun`. */\nexport interface PipelineUnderrunEvent {\n count: number;\n}\n\n/** Payload for `PipelineDrained`. */\nexport interface PipelineDrainedEvent {\n turnId: string;\n}\n\n/** Payload for `PipelineAudioFocusLost` (empty — presence is the signal). */\nexport type PipelineAudioFocusLostEvent = Record<string, never>;\n\n/** Payload for `PipelineAudioFocusResumed` (empty — presence is the signal). */\nexport type PipelineAudioFocusResumedEvent = Record<string, never>;\n\n/** Payload for `PipelineFrequencyBands`. */\nexport interface PipelineFrequencyBandsEvent extends FrequencyBands {}\n\n/**\n * Map of all pipeline event names to their payload types.\n * Used with `Pipeline.subscribe<K>()` for type-safe event subscriptions.\n */\nexport interface PipelineEventMap {\n PipelineStateChanged: PipelineStateChangedEvent;\n PipelinePlaybackStarted: PipelinePlaybackStartedEvent;\n PipelineError: PipelineErrorEvent;\n PipelineZombieDetected: PipelineZombieDetectedEvent;\n PipelineUnderrun: PipelineUnderrunEvent;\n PipelineDrained: PipelineDrainedEvent;\n PipelineAudioFocusLost: PipelineAudioFocusLostEvent;\n PipelineAudioFocusResumed: PipelineAudioFocusResumedEvent;\n PipelineFrequencyBands: PipelineFrequencyBandsEvent;\n}\n\n/** Union of all pipeline event name strings. */\nexport type PipelineEventName = keyof PipelineEventMap;\n\n// ── Telemetry ───────────────────────────────────────────────────────────────\n\n/** Jitter buffer telemetry counters. */\nexport interface PipelineBufferTelemetry {\n /** Current buffer level in milliseconds. */\n bufferMs: number;\n /** Current buffer level in samples. */\n bufferSamples: number;\n /** Whether the priming gate has opened. */\n primed: boolean;\n /** Total samples written by the producer since last reset. */\n totalWritten: number;\n /** Total samples read by the consumer since last reset. */\n totalRead: number;\n /** Number of underrun events. */\n underrunCount: number;\n /** Peak buffer level in samples. */\n peakLevel: number;\n}\n\n/** Full pipeline telemetry snapshot. */\nexport interface PipelineTelemetry extends PipelineBufferTelemetry {\n /** Current pipeline state. */\n state: PipelineState;\n /** Total pushAudio/pushAudioSync calls since connect. */\n totalPushCalls: number;\n /** Total bytes pushed since connect. */\n totalPushBytes: number;\n /** Total write-loop iterations since connect. */\n totalWriteLoops: number;\n /** Current turn identifier. */\n turnId: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8CAA8C;AAC9C,+EAA+E","sourcesContent":["// ────────────────────────────────────────────────────────────────────────────\n// Native Audio Pipeline — V3 TypeScript Types\n// ────────────────────────────────────────────────────────────────────────────\n\nimport { PlaybackMode, FrequencyBandConfig, FrequencyBands } from \"../types\";\n\n// ── Connect ─────────────────────────────────────────────────────────────────\n\n/**\n * How the pipeline's playback should coexist with other audio on the device.\n *\n * - `'mixWithOthers'` (default): plays alongside other apps without\n * interrupting them. On Android no audio focus is requested. Best for\n * sound effects and short clips.\n * - `'duckOthers'`: requests audio focus with ducking. Other apps lower\n * their volume but keep playing.\n * - `'doNotMix'`: requests exclusive audio focus. Other apps pause.\n */\nexport type PipelineAudioMode = 'mixWithOthers' | 'duckOthers' | 'doNotMix';\n\n/** Options passed to `connectPipeline()`. */\nexport interface ConnectPipelineOptions {\n /** Sample rate in Hz (default 24000). */\n sampleRate?: number;\n /** Number of channels — 1 = mono, 2 = stereo (default 1). */\n channelCount?: number;\n /**\n * How many ms of audio to accumulate in the jitter buffer before the\n * priming gate opens and audio begins playing (default 80).\n */\n targetBufferMs?: number;\n /**\n * Playback mode hint for native optimizations. Affects thread priority and\n */\n playbackMode?: PlaybackMode;\n /** Interval in ms for PipelineFrequencyBands events (default 100). */\n frequencyBandIntervalMs?: number;\n /** Optional frequency band crossover configuration. */\n frequencyBandConfig?: FrequencyBandConfig;\n /**\n * How pipeline playback should coexist with other apps' audio.\n * Default is `'mixWithOthers'` (matches expo-audio).\n *\n * Note: this is a **behavior change** vs. prior versions of this library,\n * which effectively used `'doNotMix'`. Pass `'doNotMix'` explicitly to\n * preserve that old behavior.\n */\n audioMode?: PipelineAudioMode;\n}\n\n/** Result returned from a successful `connectPipeline()` call. */\nexport interface ConnectPipelineResult {\n sampleRate: number;\n channelCount: number;\n targetBufferMs: number;\n /**\n * Frame size in samples derived from the device HAL's\n * `AudioTrack.getMinBufferSize()`. Useful for understanding the write\n * granularity on the native side.\n */\n frameSizeSamples: number;\n}\n\n// ── Push Audio ──────────────────────────────────────────────────────────────\n\n/** Options passed to `pushPipelineAudio()` / `pushPipelineAudioSync()`. */\nexport interface PushPipelineAudioOptions {\n /** Base64-encoded PCM 16-bit signed LE audio data. */\n audio: string;\n /** Conversation turn identifier. */\n turnId: string;\n /** True if this is the first chunk of a new turn (resets jitter buffer). */\n isFirstChunk?: boolean;\n /** True if this is the final chunk of the current turn (marks end-of-stream). */\n isLastChunk?: boolean;\n}\n\n// ── Invalidate Turn ─────────────────────────────────────────────────────────\n\n/** Options passed to `invalidatePipelineTurn()`. */\nexport interface InvalidatePipelineTurnOptions {\n /** The new turn identifier — stale audio for the old turn is discarded. */\n turnId: string;\n}\n\n// ── State ───────────────────────────────────────────────────────────────────\n\n/**\n * Pipeline states reported via `PipelineStateChanged` events.\n *\n * - `idle` — connected but no audio flowing\n * - `connecting` — AudioTrack being created, focus being requested\n * - `streaming` — actively receiving and playing audio\n * - `draining` — end-of-stream marked, playing remaining buffer\n * - `error` — unrecoverable error (zombie, write failure, etc.)\n */\nexport type PipelineState =\n | 'idle'\n | 'connecting'\n | 'streaming'\n | 'draining'\n | 'error';\n\n// ── Events ──────────────────────────────────────────────────────────────────\n\n/** Payload for `PipelineStateChanged`. */\nexport interface PipelineStateChangedEvent {\n state: PipelineState;\n}\n\n/** Payload for `PipelinePlaybackStarted`. */\nexport interface PipelinePlaybackStartedEvent {\n turnId: string;\n}\n\n/** Payload for `PipelineError`. */\nexport interface PipelineErrorEvent {\n code: string;\n message: string;\n}\n\n/** Payload for `PipelineZombieDetected`. */\nexport interface PipelineZombieDetectedEvent {\n playbackHead: number;\n stalledMs: number;\n}\n\n/** Payload for `PipelineUnderrun`. */\nexport interface PipelineUnderrunEvent {\n count: number;\n}\n\n/** Payload for `PipelineDrained`. */\nexport interface PipelineDrainedEvent {\n turnId: string;\n}\n\n/**\n * Payload for `PipelinePlaybackStopped`.\n *\n * Fired when the last sample physically leaves the speaker, approximately\n * `outputLatencyMs` after `PipelineDrained` for the same turn. Pairs with\n * `PipelinePlaybackStarted` (start-of-emission ↔ end-of-emission).\n *\n * Note: this is a physical-world milestone, distinct from `state: 'idle'`\n * (the pipeline-state-machine value reported via `PipelineStateChanged`).\n */\nexport interface PipelinePlaybackStoppedEvent {\n turnId: string;\n}\n\n/** Payload for `PipelineAudioFocusLost` (empty — presence is the signal). */\nexport type PipelineAudioFocusLostEvent = Record<string, never>;\n\n/** Payload for `PipelineAudioFocusResumed` (empty — presence is the signal). */\nexport type PipelineAudioFocusResumedEvent = Record<string, never>;\n\n/** Payload for `PipelineFrequencyBands`. */\nexport interface PipelineFrequencyBandsEvent extends FrequencyBands {}\n\n/**\n * Map of all pipeline event names to their payload types.\n * Used with `Pipeline.subscribe<K>()` for type-safe event subscriptions.\n */\nexport interface PipelineEventMap {\n PipelineStateChanged: PipelineStateChangedEvent;\n PipelinePlaybackStarted: PipelinePlaybackStartedEvent;\n PipelineError: PipelineErrorEvent;\n PipelineZombieDetected: PipelineZombieDetectedEvent;\n PipelineUnderrun: PipelineUnderrunEvent;\n PipelineDrained: PipelineDrainedEvent;\n PipelinePlaybackStopped: PipelinePlaybackStoppedEvent;\n PipelineAudioFocusLost: PipelineAudioFocusLostEvent;\n PipelineAudioFocusResumed: PipelineAudioFocusResumedEvent;\n PipelineFrequencyBands: PipelineFrequencyBandsEvent;\n}\n\n/** Union of all pipeline event name strings. */\nexport type PipelineEventName = keyof PipelineEventMap;\n\n// ── Telemetry ───────────────────────────────────────────────────────────────\n\n/** Jitter buffer telemetry counters. */\nexport interface PipelineBufferTelemetry {\n /** Current buffer level in milliseconds. */\n bufferMs: number;\n /** Current buffer level in samples. */\n bufferSamples: number;\n /** Whether the priming gate has opened. */\n primed: boolean;\n /** Total samples written by the producer since last reset. */\n totalWritten: number;\n /** Total samples read by the consumer since last reset. */\n totalRead: number;\n /** Number of underrun events. */\n underrunCount: number;\n /** Peak buffer level in samples. */\n peakLevel: number;\n}\n\n/** Full pipeline telemetry snapshot. */\nexport interface PipelineTelemetry extends PipelineBufferTelemetry {\n /** Current pipeline state. */\n state: PipelineState;\n /** Total pushAudio/pushAudioSync calls since connect. */\n totalPushCalls: number;\n /** Total bytes pushed since connect. */\n totalPushBytes: number;\n /** Total write-loop iterations since connect. */\n totalWriteLoops: number;\n /** Current turn identifier. */\n turnId: string;\n}\n"]}
|
package/build/types.d.ts
CHANGED
|
@@ -103,9 +103,30 @@ export interface RecordingConfig {
|
|
|
103
103
|
enableProcessing?: boolean;
|
|
104
104
|
pointsPerSecond?: number;
|
|
105
105
|
onAudioStream?: (event: AudioDataEvent) => Promise<void>;
|
|
106
|
+
/** Fired when the native layer reports a mid-recording error (e.g. system
|
|
107
|
+
* interruption like Siri or a phone call). The consumer should treat the
|
|
108
|
+
* recording session as terminated and clean up. */
|
|
109
|
+
onError?: (event: MicrophoneErrorEvent) => void;
|
|
106
110
|
/** Optional frequency band crossover configuration. */
|
|
107
111
|
frequencyBandConfig?: FrequencyBandConfig;
|
|
108
112
|
}
|
|
113
|
+
export interface MicrophoneErrorEvent {
|
|
114
|
+
code: string;
|
|
115
|
+
message: string;
|
|
116
|
+
/**
|
|
117
|
+
* True when the recording session has stopped and the caller must call
|
|
118
|
+
* stopMicrophone() and reconnect to resume. False for transient conditions
|
|
119
|
+
* where recording continues (READ_ERROR on iOS) or the library will
|
|
120
|
+
* auto-recover (INTERRUPTED + autoResuming: true).
|
|
121
|
+
*/
|
|
122
|
+
isFatal: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* True only when code is INTERRUPTED — the library is waiting for the
|
|
125
|
+
* system to return the audio session and will reinstall the tap
|
|
126
|
+
* automatically. Always false when isFatal is true.
|
|
127
|
+
*/
|
|
128
|
+
autoResuming: boolean;
|
|
129
|
+
}
|
|
109
130
|
export interface Chunk {
|
|
110
131
|
text: string;
|
|
111
132
|
timestamp: [number, number | null];
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAC7B,WAAW,GACX,WAAW,GACX,UAAU,CAAC;AACf,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;AAEnC,eAAO,MAAM,aAAa;;;;CAIhB,CAAC;AACX;;GAEG;AACH,MAAM,MAAM,YAAY,GACtB,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE3C;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC1D;AAED,eAAO,MAAM,aAAa;;;CAGhB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,QAAQ,GAClB,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAErD,mDAAmD;AACnD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2DAA2D;AAC3D,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,UAAU,GACV,YAAY,GACZ,UAAU,CAAC;AAEf;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,eAAe,CAAC;IACtB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,kBAAkB,CAAC,EAAE;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD;;uDAEmD;IACnD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChD,uDAAuD;IACvD,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAID;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,SAAS,GACT,UAAU,GACV,UAAU,CAAC;AAEf;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,wBAAwB,EAAE,MAAM,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAClD,aAAa,IAAI,IAAI,CAAC;IACtB,YAAY,IAAI,IAAI,CAAC;IACrB,SAAS,IAAI,OAAO,CAAC;IACrB,gBAAgB,IAAI,oBAAoB,CAAC;IACzC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC;IACxD,wBAAwB,IAAI,IAAI,CAAC;IACjC,OAAO,IAAI,IAAI,CAAC;IAChB,kBAAkB,IAAI,MAAM,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,WAAW,EAAE,CAAC;IACtD,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,cAAc,IAAI,IAAI,CAAC;IACvB,aAAa,IAAI,IAAI,CAAC;IACtB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,IAAI,oBAAoB,CAAC;IACnC,oBAAoB,CAClB,SAAS,EAAE,OAAO,EAClB,gBAAgB,EAAE,MAAM,GACvB,iBAAiB,CAAC;IACrB,wBAAwB,IAAI,MAAM,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf"}
|
package/build/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,OAAO,EAAE,SAAS;IAClB,gBAAgB,EAAE,iBAAiB;IACnC,YAAY,EAAE,cAAc;CACpB,CAAC;AAgCX,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,WAAW;CACd,CAAC","sourcesContent":["export type RecordingEncodingType =\n | 'pcm_32bit'\n | 'pcm_16bit'\n | 'pcm_8bit';\nexport type SampleRate = 16000 | 24000 | 44100 | 48000;\nexport type BitDepth = 8 | 16 | 32;\n\nexport const PlaybackModes = {\n REGULAR: 'regular',\n VOICE_PROCESSING: 'voiceProcessing',\n CONVERSATION: 'conversation',\n} as const;\n/**\n * Defines different playback modes for audio processing\n */\nexport type PlaybackMode =\n (typeof PlaybackModes)[keyof typeof PlaybackModes];\n\n/**\n * Configuration for buffered audio streaming\n */\nexport interface BufferedStreamConfig {\n /**\n * Turn ID for queue management\n */\n turnId: string;\n\n /**\n * Audio encoding format\n */\n encoding?: Encoding;\n\n /**\n * Buffer configuration options\n */\n bufferConfig?: Partial<IAudioBufferConfig>;\n\n /**\n * Callback for buffer health updates\n */\n onBufferHealth?: (metrics: IBufferHealthMetrics) => void;\n}\n\nexport const EncodingTypes = {\n PCM_F32LE: 'pcm_f32le',\n PCM_S16LE: 'pcm_s16le',\n} as const;\n\n/**\n * Defines different encoding formats for audio data\n */\nexport type Encoding =\n (typeof EncodingTypes)[keyof typeof EncodingTypes];\n\n/** RMS energy per frequency band, range [0, 1]. */\nexport interface FrequencyBands {\n low: number;\n mid: number;\n high: number;\n}\n\n/** Crossover frequency configuration for band analysis. */\nexport interface FrequencyBandConfig {\n /** Low/mid crossover in Hz (default 300). */\n lowCrossoverHz?: number;\n /** Mid/high crossover in Hz (default 2000). */\n highCrossoverHz?: number;\n}\n\n/**\n * Smart buffering mode options\n */\nexport type SmartBufferMode =\n | 'conservative'\n | 'balanced'\n | 'aggressive'\n | 'adaptive';\n\n/**\n * Network condition indicators for smart buffering\n */\nexport interface NetworkConditions {\n latency?: number; // Round-trip time in ms\n jitter?: number; // Network jitter in ms\n packetLoss?: number; // Packet loss percentage (0-100)\n bandwidth?: number; // Available bandwidth estimate\n}\n\n/**\n * Smart buffering configuration\n */\nexport interface SmartBufferConfig {\n mode: SmartBufferMode;\n networkConditions?: NetworkConditions;\n adaptiveThresholds?: {\n highLatencyMs?: number; // Threshold to enable aggressive buffering\n highJitterMs?: number; // Threshold to increase buffer size\n packetLossPercent?: number; // Threshold to enable buffering\n };\n}\n\nexport interface StartRecordingResult {\n fileUri: string;\n mimeType: string;\n channels?: number;\n bitDepth?: BitDepth;\n sampleRate?: SampleRate;\n}\n\nexport interface AudioDataEvent {\n data: string | Float32Array;\n data16kHz?: string | Float32Array;\n position: number;\n fileUri: string;\n eventDataSize: number;\n totalSize: number;\n soundLevel?: number;\n /** Frequency band RMS energy, present when recording is active. */\n frequencyBands?: FrequencyBands;\n}\n\nexport interface RecordingConfig {\n sampleRate?: SampleRate; // Sample rate for recording\n channels?: 1 | 2; // 1 or 2 (MONO or STEREO)\n encoding?: RecordingEncodingType; // Encoding type for the recording\n interval?: number; // Interval in milliseconds at which to emit recording data\n\n // Optional parameters for audio processing\n enableProcessing?: boolean; // Boolean to enable/disable audio processing (default is false)\n pointsPerSecond?: number; // Number of data points to extract per second of audio (default is 1000)\n onAudioStream?: (event: AudioDataEvent) => Promise<void>; // Callback function to handle audio stream\n /** Optional frequency band crossover configuration. */\n frequencyBandConfig?: FrequencyBandConfig;\n}\n\nexport interface Chunk {\n text: string;\n timestamp: [number, number | null];\n}\n\nexport interface TranscriberData {\n id: string;\n isBusy: boolean;\n text: string;\n startTime: number;\n endTime: number;\n chunks: Chunk[];\n}\n\nexport interface AudioRecording {\n fileUri: string;\n filename: string;\n durationMs: number;\n size: number;\n channels: number;\n bitDepth: BitDepth;\n sampleRate: SampleRate;\n mimeType: string;\n transcripts?: TranscriberData[];\n wavPCMData?: Float32Array; // Full PCM data for the recording in WAV format (only on web, for native use the fileUri)\n}\n\n// Audio Jitter Buffer Types\n\n/**\n * Configuration for audio buffer management\n */\nexport interface IAudioBufferConfig {\n targetBufferMs: number; // Target buffer size in milliseconds\n minBufferMs: number; // Minimum buffer size before underrun handling\n maxBufferMs: number; // Maximum buffer size before overrun handling\n frameIntervalMs: number; // Expected frame interval in milliseconds\n}\n\n/**\n * Audio payload for playback containing base64 encoded audio data\n */\nexport interface IAudioPlayPayload {\n audioData: string; // Base64 encoded PCM audio data\n isFirst?: boolean; // True if this is the first chunk in a stream\n isFinal?: boolean; // True if this is the final chunk in a stream\n}\n\n/**\n * Processed audio frame with metadata\n */\nexport interface IAudioFrame {\n sequenceNumber: number; // Sequential frame number\n data: IAudioPlayPayload; // Original audio payload\n duration: number; // Estimated frame duration in milliseconds\n timestamp: number; // Frame timestamp when processed\n}\n\n/**\n * Buffer health states for quality monitoring\n */\nexport type BufferHealthState =\n | 'idle'\n | 'healthy'\n | 'degraded'\n | 'critical';\n\n/**\n * Comprehensive buffer health and quality metrics\n */\nexport interface IBufferHealthMetrics {\n currentBufferMs: number; // Current buffer level in milliseconds\n targetBufferMs: number; // Target buffer level in milliseconds\n underrunCount: number; // Total number of buffer underruns\n overrunCount: number; // Total number of buffer overruns\n averageJitter: number; // Average network jitter in milliseconds\n bufferHealthState: BufferHealthState; // Current buffer health assessment\n adaptiveAdjustmentsCount: number; // Number of adaptive adjustments made\n}\n\n/**\n * Interface for audio buffer management\n */\nexport interface IAudioBufferManager {\n enqueueFrames(audioData: IAudioPlayPayload): void;\n startPlayback(): void;\n stopPlayback(): void;\n isPlaying(): boolean;\n getHealthMetrics(): IBufferHealthMetrics;\n updateConfig(config: Partial<IAudioBufferConfig>): void;\n applyAdaptiveAdjustments(): void;\n destroy(): void;\n getCurrentBufferMs(): number;\n}\n\n/**\n * Interface for frame processing\n */\nexport interface IFrameProcessor {\n parseChunk(payload: IAudioPlayPayload): IAudioFrame[];\n reset(): void;\n}\n\n/**\n * Interface for quality monitoring\n */\nexport interface IQualityMonitor {\n recordFrameArrival(timestamp: number): void;\n recordUnderrun(): void;\n recordOverrun(): void;\n updateBufferLevel(bufferMs: number): void;\n getMetrics(): IBufferHealthMetrics;\n getBufferHealthState(\n isPlaying: boolean,\n currentLatencyMs: number\n ): BufferHealthState;\n getRecommendedAdjustment(): number;\n reset(): void;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,OAAO,EAAE,SAAS;IAClB,gBAAgB,EAAE,iBAAiB;IACnC,YAAY,EAAE,cAAc;CACpB,CAAC;AAgCX,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,WAAW;CACd,CAAC","sourcesContent":["export type RecordingEncodingType =\n | 'pcm_32bit'\n | 'pcm_16bit'\n | 'pcm_8bit';\nexport type SampleRate = 16000 | 24000 | 44100 | 48000;\nexport type BitDepth = 8 | 16 | 32;\n\nexport const PlaybackModes = {\n REGULAR: 'regular',\n VOICE_PROCESSING: 'voiceProcessing',\n CONVERSATION: 'conversation',\n} as const;\n/**\n * Defines different playback modes for audio processing\n */\nexport type PlaybackMode =\n (typeof PlaybackModes)[keyof typeof PlaybackModes];\n\n/**\n * Configuration for buffered audio streaming\n */\nexport interface BufferedStreamConfig {\n /**\n * Turn ID for queue management\n */\n turnId: string;\n\n /**\n * Audio encoding format\n */\n encoding?: Encoding;\n\n /**\n * Buffer configuration options\n */\n bufferConfig?: Partial<IAudioBufferConfig>;\n\n /**\n * Callback for buffer health updates\n */\n onBufferHealth?: (metrics: IBufferHealthMetrics) => void;\n}\n\nexport const EncodingTypes = {\n PCM_F32LE: 'pcm_f32le',\n PCM_S16LE: 'pcm_s16le',\n} as const;\n\n/**\n * Defines different encoding formats for audio data\n */\nexport type Encoding =\n (typeof EncodingTypes)[keyof typeof EncodingTypes];\n\n/** RMS energy per frequency band, range [0, 1]. */\nexport interface FrequencyBands {\n low: number;\n mid: number;\n high: number;\n}\n\n/** Crossover frequency configuration for band analysis. */\nexport interface FrequencyBandConfig {\n /** Low/mid crossover in Hz (default 300). */\n lowCrossoverHz?: number;\n /** Mid/high crossover in Hz (default 2000). */\n highCrossoverHz?: number;\n}\n\n/**\n * Smart buffering mode options\n */\nexport type SmartBufferMode =\n | 'conservative'\n | 'balanced'\n | 'aggressive'\n | 'adaptive';\n\n/**\n * Network condition indicators for smart buffering\n */\nexport interface NetworkConditions {\n latency?: number; // Round-trip time in ms\n jitter?: number; // Network jitter in ms\n packetLoss?: number; // Packet loss percentage (0-100)\n bandwidth?: number; // Available bandwidth estimate\n}\n\n/**\n * Smart buffering configuration\n */\nexport interface SmartBufferConfig {\n mode: SmartBufferMode;\n networkConditions?: NetworkConditions;\n adaptiveThresholds?: {\n highLatencyMs?: number; // Threshold to enable aggressive buffering\n highJitterMs?: number; // Threshold to increase buffer size\n packetLossPercent?: number; // Threshold to enable buffering\n };\n}\n\nexport interface StartRecordingResult {\n fileUri: string;\n mimeType: string;\n channels?: number;\n bitDepth?: BitDepth;\n sampleRate?: SampleRate;\n}\n\nexport interface AudioDataEvent {\n data: string | Float32Array;\n data16kHz?: string | Float32Array;\n position: number;\n fileUri: string;\n eventDataSize: number;\n totalSize: number;\n soundLevel?: number;\n /** Frequency band RMS energy, present when recording is active. */\n frequencyBands?: FrequencyBands;\n}\n\nexport interface RecordingConfig {\n sampleRate?: SampleRate; // Sample rate for recording\n channels?: 1 | 2; // 1 or 2 (MONO or STEREO)\n encoding?: RecordingEncodingType; // Encoding type for the recording\n interval?: number; // Interval in milliseconds at which to emit recording data\n\n // Optional parameters for audio processing\n enableProcessing?: boolean; // Boolean to enable/disable audio processing (default is false)\n pointsPerSecond?: number; // Number of data points to extract per second of audio (default is 1000)\n onAudioStream?: (event: AudioDataEvent) => Promise<void>; // Callback function to handle audio stream\n /** Fired when the native layer reports a mid-recording error (e.g. system\n * interruption like Siri or a phone call). The consumer should treat the\n * recording session as terminated and clean up. */\n onError?: (event: MicrophoneErrorEvent) => void;\n /** Optional frequency band crossover configuration. */\n frequencyBandConfig?: FrequencyBandConfig;\n}\n\nexport interface MicrophoneErrorEvent {\n code: string;\n message: string;\n /**\n * True when the recording session has stopped and the caller must call\n * stopMicrophone() and reconnect to resume. False for transient conditions\n * where recording continues (READ_ERROR on iOS) or the library will\n * auto-recover (INTERRUPTED + autoResuming: true).\n */\n isFatal: boolean;\n /**\n * True only when code is INTERRUPTED — the library is waiting for the\n * system to return the audio session and will reinstall the tap\n * automatically. Always false when isFatal is true.\n */\n autoResuming: boolean;\n}\n\nexport interface Chunk {\n text: string;\n timestamp: [number, number | null];\n}\n\nexport interface TranscriberData {\n id: string;\n isBusy: boolean;\n text: string;\n startTime: number;\n endTime: number;\n chunks: Chunk[];\n}\n\nexport interface AudioRecording {\n fileUri: string;\n filename: string;\n durationMs: number;\n size: number;\n channels: number;\n bitDepth: BitDepth;\n sampleRate: SampleRate;\n mimeType: string;\n transcripts?: TranscriberData[];\n wavPCMData?: Float32Array; // Full PCM data for the recording in WAV format (only on web, for native use the fileUri)\n}\n\n// Audio Jitter Buffer Types\n\n/**\n * Configuration for audio buffer management\n */\nexport interface IAudioBufferConfig {\n targetBufferMs: number; // Target buffer size in milliseconds\n minBufferMs: number; // Minimum buffer size before underrun handling\n maxBufferMs: number; // Maximum buffer size before overrun handling\n frameIntervalMs: number; // Expected frame interval in milliseconds\n}\n\n/**\n * Audio payload for playback containing base64 encoded audio data\n */\nexport interface IAudioPlayPayload {\n audioData: string; // Base64 encoded PCM audio data\n isFirst?: boolean; // True if this is the first chunk in a stream\n isFinal?: boolean; // True if this is the final chunk in a stream\n}\n\n/**\n * Processed audio frame with metadata\n */\nexport interface IAudioFrame {\n sequenceNumber: number; // Sequential frame number\n data: IAudioPlayPayload; // Original audio payload\n duration: number; // Estimated frame duration in milliseconds\n timestamp: number; // Frame timestamp when processed\n}\n\n/**\n * Buffer health states for quality monitoring\n */\nexport type BufferHealthState =\n | 'idle'\n | 'healthy'\n | 'degraded'\n | 'critical';\n\n/**\n * Comprehensive buffer health and quality metrics\n */\nexport interface IBufferHealthMetrics {\n currentBufferMs: number; // Current buffer level in milliseconds\n targetBufferMs: number; // Target buffer level in milliseconds\n underrunCount: number; // Total number of buffer underruns\n overrunCount: number; // Total number of buffer overruns\n averageJitter: number; // Average network jitter in milliseconds\n bufferHealthState: BufferHealthState; // Current buffer health assessment\n adaptiveAdjustmentsCount: number; // Number of adaptive adjustments made\n}\n\n/**\n * Interface for audio buffer management\n */\nexport interface IAudioBufferManager {\n enqueueFrames(audioData: IAudioPlayPayload): void;\n startPlayback(): void;\n stopPlayback(): void;\n isPlaying(): boolean;\n getHealthMetrics(): IBufferHealthMetrics;\n updateConfig(config: Partial<IAudioBufferConfig>): void;\n applyAdaptiveAdjustments(): void;\n destroy(): void;\n getCurrentBufferMs(): number;\n}\n\n/**\n * Interface for frame processing\n */\nexport interface IFrameProcessor {\n parseChunk(payload: IAudioPlayPayload): IAudioFrame[];\n reset(): void;\n}\n\n/**\n * Interface for quality monitoring\n */\nexport interface IQualityMonitor {\n recordFrameArrival(timestamp: number): void;\n recordUnderrun(): void;\n recordOverrun(): void;\n updateBufferLevel(bufferMs: number): void;\n getMetrics(): IBufferHealthMetrics;\n getBufferHealthState(\n isPlaying: boolean,\n currentLatencyMs: number\n ): BufferHealthState;\n getRecommendedAdjustment(): number;\n reset(): void;\n}\n"]}
|
package/ios/AudioPipeline.swift
CHANGED
|
@@ -17,6 +17,7 @@ protocol PipelineListener: AnyObject {
|
|
|
17
17
|
func onZombieDetected(stalledMs: Int64)
|
|
18
18
|
func onUnderrun(count: Int)
|
|
19
19
|
func onDrained(turnId: String)
|
|
20
|
+
func onPlaybackStopped(turnId: String)
|
|
20
21
|
func onAudioFocusLost()
|
|
21
22
|
func onAudioFocusResumed()
|
|
22
23
|
func onFrequencyBands(low: Float, mid: Float, high: Float)
|
|
@@ -84,6 +85,10 @@ class AudioPipeline: SharedAudioEngineDelegate {
|
|
|
84
85
|
/// This prevents duplicate chains and stale callbacks from re-entering after a rebuild.
|
|
85
86
|
private var scheduleGeneration: Int = 0
|
|
86
87
|
|
|
88
|
+
/// Pending PlaybackStopped dispatch — cancelled on new turn / disconnect.
|
|
89
|
+
/// Always mutate from the main queue to avoid races with the drain timer.
|
|
90
|
+
private var pendingPlaybackStoppedWork: DispatchWorkItem?
|
|
91
|
+
|
|
87
92
|
// ── Timers ──────────────────────────────────────────────────────────
|
|
88
93
|
private var stateTimer: DispatchSourceTimer?
|
|
89
94
|
private var zombieTimer: DispatchSourceTimer?
|
|
@@ -122,7 +127,7 @@ class AudioPipeline: SharedAudioEngineDelegate {
|
|
|
122
127
|
// Connect / Disconnect
|
|
123
128
|
// ════════════════════════════════════════════════════════════════════
|
|
124
129
|
|
|
125
|
-
func connect() {
|
|
130
|
+
func connect() throws {
|
|
126
131
|
guard !running else {
|
|
127
132
|
Logger.debug("[\(AudioPipeline.TAG)] connect() called while already running — ignoring")
|
|
128
133
|
return
|
|
@@ -197,12 +202,17 @@ class AudioPipeline: SharedAudioEngineDelegate {
|
|
|
197
202
|
} catch {
|
|
198
203
|
Logger.debug("[\(AudioPipeline.TAG)] connect() failed: \(error)")
|
|
199
204
|
setState(.error)
|
|
200
|
-
listener?.onError(code: "CONNECT_FAILED", message: error.localizedDescription)
|
|
201
205
|
disconnect()
|
|
206
|
+
throw error
|
|
202
207
|
}
|
|
203
208
|
}
|
|
204
209
|
|
|
205
210
|
func disconnect() {
|
|
211
|
+
// Cancel any pending PlaybackStopped before tearing down.
|
|
212
|
+
// DispatchWorkItem.cancel is thread-safe; we may be on any queue here.
|
|
213
|
+
pendingPlaybackStoppedWork?.cancel()
|
|
214
|
+
pendingPlaybackStoppedWork = nil
|
|
215
|
+
|
|
206
216
|
running = false
|
|
207
217
|
// Invalidate all in-flight completion handlers before detaching.
|
|
208
218
|
scheduleGeneration += 1
|
|
@@ -365,6 +375,9 @@ class AudioPipeline: SharedAudioEngineDelegate {
|
|
|
365
375
|
lastReportedUnderrunCount = 0
|
|
366
376
|
setState(.streaming)
|
|
367
377
|
frequencyBandAnalyzer?.reset()
|
|
378
|
+
DispatchQueue.main.async { [weak self] in
|
|
379
|
+
self?.cancelPendingPlaybackStopped()
|
|
380
|
+
}
|
|
368
381
|
}
|
|
369
382
|
|
|
370
383
|
// ── Decode base64 → PCM shorts ──────────────────────────────────
|
|
@@ -407,6 +420,9 @@ class AudioPipeline: SharedAudioEngineDelegate {
|
|
|
407
420
|
lastReportedUnderrunCount = 0
|
|
408
421
|
setState(.idle)
|
|
409
422
|
frequencyBandAnalyzer?.reset()
|
|
423
|
+
DispatchQueue.main.async { [weak self] in
|
|
424
|
+
self?.cancelPendingPlaybackStopped()
|
|
425
|
+
}
|
|
410
426
|
}
|
|
411
427
|
|
|
412
428
|
// ════════════════════════════════════════════════════════════════════
|
|
@@ -433,6 +449,18 @@ class AudioPipeline: SharedAudioEngineDelegate {
|
|
|
433
449
|
]
|
|
434
450
|
}
|
|
435
451
|
|
|
452
|
+
/// Current platform output latency in milliseconds.
|
|
453
|
+
///
|
|
454
|
+
/// Reads `AVAudioSession.sharedInstance().outputLatency`. The value
|
|
455
|
+
/// reflects total HW output latency to the speaker and changes on
|
|
456
|
+
/// audio route changes (built-in vs. Bluetooth vs. wired).
|
|
457
|
+
///
|
|
458
|
+
/// Returns 0 if not running.
|
|
459
|
+
func outputLatencyMs() -> Double {
|
|
460
|
+
guard running else { return 0 }
|
|
461
|
+
return AVAudioSession.sharedInstance().outputLatency * 1000.0
|
|
462
|
+
}
|
|
463
|
+
|
|
436
464
|
// ════════════════════════════════════════════════════════════════════
|
|
437
465
|
// Scheduling loop
|
|
438
466
|
// ════════════════════════════════════════════════════════════════════
|
|
@@ -548,11 +576,48 @@ class AudioPipeline: SharedAudioEngineDelegate {
|
|
|
548
576
|
if buf.isDrained() && currentState == .draining {
|
|
549
577
|
if let tid = turnId {
|
|
550
578
|
listener?.onDrained(turnId: tid)
|
|
579
|
+
schedulePlaybackStopped(turnId: tid)
|
|
551
580
|
}
|
|
552
581
|
setState(.idle)
|
|
553
582
|
}
|
|
554
583
|
}
|
|
555
584
|
|
|
585
|
+
/// Schedule a `PlaybackStopped` event approximately `outputLatencyMs`
|
|
586
|
+
/// after `Drained`. Cancels any previously pending dispatch.
|
|
587
|
+
///
|
|
588
|
+
/// Approximation note: at drain detection, up to `PRE_SCHEDULE_COUNT`
|
|
589
|
+
/// pre-scheduled buffers may still be in the player node's chain — but
|
|
590
|
+
/// they read silence from the empty jitter buffer, so the last audible
|
|
591
|
+
/// sample stops emitting roughly `outputLatency` after this point.
|
|
592
|
+
private func schedulePlaybackStopped(turnId: String) {
|
|
593
|
+
pendingPlaybackStoppedWork?.cancel()
|
|
594
|
+
pendingPlaybackStoppedWork = nil
|
|
595
|
+
|
|
596
|
+
let latencyMs = outputLatencyMs()
|
|
597
|
+
let work = DispatchWorkItem { [weak self] in
|
|
598
|
+
guard let self = self else { return }
|
|
599
|
+
self.pendingPlaybackStoppedWork = nil
|
|
600
|
+
self.listener?.onPlaybackStopped(turnId: turnId)
|
|
601
|
+
}
|
|
602
|
+
pendingPlaybackStoppedWork = work
|
|
603
|
+
|
|
604
|
+
if latencyMs > 0 {
|
|
605
|
+
DispatchQueue.main.asyncAfter(
|
|
606
|
+
deadline: .now() + .milliseconds(Int(latencyMs.rounded())),
|
|
607
|
+
execute: work
|
|
608
|
+
)
|
|
609
|
+
} else {
|
|
610
|
+
DispatchQueue.main.async(execute: work)
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/// Cancel any pending PlaybackStopped dispatch. Call from turn-boundary
|
|
615
|
+
/// transitions (new pushAudio, invalidateTurn) and disconnect.
|
|
616
|
+
private func cancelPendingPlaybackStopped() {
|
|
617
|
+
pendingPlaybackStoppedWork?.cancel()
|
|
618
|
+
pendingPlaybackStoppedWork = nil
|
|
619
|
+
}
|
|
620
|
+
|
|
556
621
|
// ════════════════════════════════════════════════════════════════════
|
|
557
622
|
// Zombie detection
|
|
558
623
|
// ════════════════════════════════════════════════════════════════════
|
|
@@ -3,6 +3,7 @@ import AVFoundation
|
|
|
3
3
|
import ExpoModulesCore
|
|
4
4
|
|
|
5
5
|
let audioDataEvent: String = "AudioData"
|
|
6
|
+
let microphoneErrorEvent: String = "MicrophoneError"
|
|
6
7
|
let deviceReconnectedEvent: String = "DeviceReconnected"
|
|
7
8
|
|
|
8
9
|
|
|
@@ -17,6 +18,7 @@ public class ExpoPlayAudioStreamModule: Module, MicrophoneDataDelegate, Pipeline
|
|
|
17
18
|
if _microphone == nil {
|
|
18
19
|
_microphone = Microphone()
|
|
19
20
|
_microphone?.delegate = self
|
|
21
|
+
_microphone?.sharedAudioEngine = sharedAudioEngine
|
|
20
22
|
}
|
|
21
23
|
return _microphone!
|
|
22
24
|
}
|
|
@@ -41,6 +43,7 @@ public class ExpoPlayAudioStreamModule: Module, MicrophoneDataDelegate, Pipeline
|
|
|
41
43
|
// Defines event names that the module can send to JavaScript.
|
|
42
44
|
Events([
|
|
43
45
|
audioDataEvent,
|
|
46
|
+
microphoneErrorEvent,
|
|
44
47
|
deviceReconnectedEvent,
|
|
45
48
|
PipelineIntegration.EVENT_STATE_CHANGED,
|
|
46
49
|
PipelineIntegration.EVENT_PLAYBACK_STARTED,
|
|
@@ -48,12 +51,16 @@ public class ExpoPlayAudioStreamModule: Module, MicrophoneDataDelegate, Pipeline
|
|
|
48
51
|
PipelineIntegration.EVENT_ZOMBIE_DETECTED,
|
|
49
52
|
PipelineIntegration.EVENT_UNDERRUN,
|
|
50
53
|
PipelineIntegration.EVENT_DRAINED,
|
|
54
|
+
PipelineIntegration.EVENT_PLAYBACK_STOPPED,
|
|
51
55
|
PipelineIntegration.EVENT_AUDIO_FOCUS_LOST,
|
|
52
56
|
PipelineIntegration.EVENT_AUDIO_FOCUS_RESUMED,
|
|
53
57
|
PipelineIntegration.EVENT_FREQUENCY_BANDS,
|
|
54
58
|
])
|
|
55
59
|
|
|
56
60
|
AsyncFunction("destroy") { (promise: Promise) in
|
|
61
|
+
// Stop the microphone before tearing down the engine so the tap is
|
|
62
|
+
// cleanly removed and the capture callback cannot fire on a dead node.
|
|
63
|
+
self._microphone?.stopRecording(resolver: nil)
|
|
57
64
|
self._pipelineIntegration?.destroy()
|
|
58
65
|
self._pipelineIntegration = nil
|
|
59
66
|
self.sharedAudioEngine.teardown()
|
|
@@ -119,13 +126,17 @@ public class ExpoPlayAudioStreamModule: Module, MicrophoneDataDelegate, Pipeline
|
|
|
119
126
|
pointsPerSecond: nil
|
|
120
127
|
)
|
|
121
128
|
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
do {
|
|
130
|
+
if !isAudioSessionInitialized {
|
|
124
131
|
try ensureAudioSessionInitialized(settings: settings)
|
|
125
|
-
} catch {
|
|
126
|
-
promise.reject("ERROR", "Failed to init audio session \(error.localizedDescription)")
|
|
127
|
-
return
|
|
128
132
|
}
|
|
133
|
+
// Ensure the shared engine is configured so AEC is applied to mic audio.
|
|
134
|
+
if !self.sharedAudioEngine.isConfigured {
|
|
135
|
+
try self.sharedAudioEngine.configure(playbackMode: .conversation)
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
promise.reject("ERROR", "Failed to init audio session: \(error.localizedDescription)")
|
|
139
|
+
return
|
|
129
140
|
}
|
|
130
141
|
|
|
131
142
|
if let result = self.microphone.startRecording(settings: settings, intervalMilliseconds: interval, frequencyBandConfig: fbConfig) {
|
|
@@ -209,6 +220,15 @@ public class ExpoPlayAudioStreamModule: Module, MicrophoneDataDelegate, Pipeline
|
|
|
209
220
|
|
|
210
221
|
promise.resolve(result)
|
|
211
222
|
} catch {
|
|
223
|
+
// Reset session + engine state so a subsequent connect can recover.
|
|
224
|
+
// Without this, a partial failure (e.g. setActive denial after the
|
|
225
|
+
// iOS local-network permission prompt) leaves the session stuck and
|
|
226
|
+
// every retry fails the same way.
|
|
227
|
+
self._pipelineIntegration?.removeAsDelegate(from: self.sharedAudioEngine)
|
|
228
|
+
self._pipelineIntegration?.disconnect()
|
|
229
|
+
self.sharedAudioEngine.teardown()
|
|
230
|
+
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
|
231
|
+
self.isAudioSessionInitialized = false
|
|
212
232
|
promise.reject("PIPELINE_CONNECT_ERROR", error.localizedDescription)
|
|
213
233
|
}
|
|
214
234
|
}
|
|
@@ -248,6 +268,10 @@ public class ExpoPlayAudioStreamModule: Module, MicrophoneDataDelegate, Pipeline
|
|
|
248
268
|
Function("getPipelineState") { () -> String in
|
|
249
269
|
return self.pipelineIntegration.getState()
|
|
250
270
|
}
|
|
271
|
+
|
|
272
|
+
Function("getPipelineOutputLatencyMs") { () -> Double in
|
|
273
|
+
return self.pipelineIntegration.outputLatencyMs()
|
|
274
|
+
}
|
|
251
275
|
}
|
|
252
276
|
|
|
253
277
|
private func ensureAudioSessionInitialized(settings recordingSettings: RecordingSettings? = nil) throws {
|
|
@@ -320,13 +344,20 @@ public class ExpoPlayAudioStreamModule: Module, MicrophoneDataDelegate, Pipeline
|
|
|
320
344
|
sendEvent(audioDataEvent, eventBody)
|
|
321
345
|
}
|
|
322
346
|
|
|
323
|
-
func onMicrophoneError(_ error:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
"
|
|
327
|
-
"
|
|
328
|
-
|
|
329
|
-
|
|
347
|
+
func onMicrophoneError(_ error: MicrophoneErrorInfo) {
|
|
348
|
+
// Rich structured channel for new consumers
|
|
349
|
+
sendEvent(microphoneErrorEvent, [
|
|
350
|
+
"code": error.code,
|
|
351
|
+
"message": error.message,
|
|
352
|
+
"isFatal": error.isFatal,
|
|
353
|
+
"autoResuming": error.autoResuming,
|
|
354
|
+
])
|
|
355
|
+
// Backward-compat: keep the error variant on AudioData for existing consumers
|
|
356
|
+
sendEvent(audioDataEvent, [
|
|
357
|
+
"error": error.code,
|
|
358
|
+
"errorMessage": error.message,
|
|
359
|
+
"streamUuid": "",
|
|
360
|
+
])
|
|
330
361
|
}
|
|
331
362
|
|
|
332
363
|
func onDeviceReconnected(_ reason: AVAudioSession.RouteChangeReason) {
|