@edkimmel/expo-audio-stream 0.2.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.
Files changed (75) hide show
  1. package/.eslintrc.js +5 -0
  2. package/.yarnrc.yml +8 -0
  3. package/NATIVE_EVENTS.md +270 -0
  4. package/README.md +289 -0
  5. package/android/build.gradle +92 -0
  6. package/android/src/main/AndroidManifest.xml +4 -0
  7. package/android/src/main/java/expo/modules/audiostream/AudioDataEncoder.kt +178 -0
  8. package/android/src/main/java/expo/modules/audiostream/AudioEffectsManager.kt +107 -0
  9. package/android/src/main/java/expo/modules/audiostream/AudioPlaybackManager.kt +651 -0
  10. package/android/src/main/java/expo/modules/audiostream/AudioRecorderManager.kt +509 -0
  11. package/android/src/main/java/expo/modules/audiostream/Constants.kt +21 -0
  12. package/android/src/main/java/expo/modules/audiostream/EventSender.kt +7 -0
  13. package/android/src/main/java/expo/modules/audiostream/ExpoAudioStreamView.kt +7 -0
  14. package/android/src/main/java/expo/modules/audiostream/ExpoPlayAudioStreamModule.kt +280 -0
  15. package/android/src/main/java/expo/modules/audiostream/PermissionUtils.kt +16 -0
  16. package/android/src/main/java/expo/modules/audiostream/RecordingConfig.kt +60 -0
  17. package/android/src/main/java/expo/modules/audiostream/SoundConfig.kt +46 -0
  18. package/android/src/main/java/expo/modules/audiostream/pipeline/AudioPipeline.kt +685 -0
  19. package/android/src/main/java/expo/modules/audiostream/pipeline/JitterBuffer.kt +227 -0
  20. package/android/src/main/java/expo/modules/audiostream/pipeline/PipelineIntegration.kt +315 -0
  21. package/app.plugin.js +1 -0
  22. package/build/ExpoPlayAudioStreamModule.d.ts +3 -0
  23. package/build/ExpoPlayAudioStreamModule.d.ts.map +1 -0
  24. package/build/ExpoPlayAudioStreamModule.js +5 -0
  25. package/build/ExpoPlayAudioStreamModule.js.map +1 -0
  26. package/build/events.d.ts +36 -0
  27. package/build/events.d.ts.map +1 -0
  28. package/build/events.js +25 -0
  29. package/build/events.js.map +1 -0
  30. package/build/index.d.ts +125 -0
  31. package/build/index.d.ts.map +1 -0
  32. package/build/index.js +222 -0
  33. package/build/index.js.map +1 -0
  34. package/build/pipeline/index.d.ts +81 -0
  35. package/build/pipeline/index.d.ts.map +1 -0
  36. package/build/pipeline/index.js +140 -0
  37. package/build/pipeline/index.js.map +1 -0
  38. package/build/pipeline/types.d.ts +132 -0
  39. package/build/pipeline/types.d.ts.map +1 -0
  40. package/build/pipeline/types.js +5 -0
  41. package/build/pipeline/types.js.map +1 -0
  42. package/build/types.d.ts +221 -0
  43. package/build/types.d.ts.map +1 -0
  44. package/build/types.js +10 -0
  45. package/build/types.js.map +1 -0
  46. package/expo-module.config.json +9 -0
  47. package/ios/AudioPipeline.swift +562 -0
  48. package/ios/AudioUtils.swift +356 -0
  49. package/ios/ExpoPlayAudioStream.podspec +27 -0
  50. package/ios/ExpoPlayAudioStreamModule.swift +436 -0
  51. package/ios/ExpoPlayAudioStreamView.swift +7 -0
  52. package/ios/JitterBuffer.swift +208 -0
  53. package/ios/Logger.swift +7 -0
  54. package/ios/Microphone.swift +221 -0
  55. package/ios/MicrophoneDataDelegate.swift +4 -0
  56. package/ios/PipelineIntegration.swift +214 -0
  57. package/ios/RecordingResult.swift +10 -0
  58. package/ios/RecordingSettings.swift +11 -0
  59. package/ios/SharedAudioEngine.swift +484 -0
  60. package/ios/SoundConfig.swift +45 -0
  61. package/ios/SoundPlayer.swift +408 -0
  62. package/ios/SoundPlayerDelegate.swift +7 -0
  63. package/package.json +49 -0
  64. package/plugin/build/index.d.ts +5 -0
  65. package/plugin/build/index.js +28 -0
  66. package/plugin/src/index.ts +53 -0
  67. package/plugin/tsconfig.json +9 -0
  68. package/plugin/tsconfig.tsbuildinfo +1 -0
  69. package/src/ExpoPlayAudioStreamModule.ts +5 -0
  70. package/src/events.ts +66 -0
  71. package/src/index.ts +359 -0
  72. package/src/pipeline/index.ts +216 -0
  73. package/src/pipeline/types.ts +169 -0
  74. package/src/types.ts +270 -0
  75. package/tsconfig.json +9 -0
@@ -0,0 +1,169 @@
1
+ // ────────────────────────────────────────────────────────────────────────────
2
+ // Native Audio Pipeline — V3 TypeScript Types
3
+ // ────────────────────────────────────────────────────────────────────────────
4
+
5
+ import { PlaybackMode } from "../types";
6
+
7
+ // ── Connect ─────────────────────────────────────────────────────────────────
8
+
9
+ /** Options passed to `connectPipeline()`. */
10
+ export interface ConnectPipelineOptions {
11
+ /** Sample rate in Hz (default 24000). */
12
+ sampleRate?: number;
13
+ /** Number of channels — 1 = mono, 2 = stereo (default 1). */
14
+ channelCount?: number;
15
+ /**
16
+ * How many ms of audio to accumulate in the jitter buffer before the
17
+ * priming gate opens and audio begins playing (default 80).
18
+ */
19
+ targetBufferMs?: number;
20
+ /**
21
+ * Playback mode hint for native optimizations. Affects thread priority and
22
+ */
23
+ playbackMode?: PlaybackMode;
24
+ }
25
+
26
+ /** Result returned from a successful `connectPipeline()` call. */
27
+ export interface ConnectPipelineResult {
28
+ sampleRate: number;
29
+ channelCount: number;
30
+ targetBufferMs: number;
31
+ /**
32
+ * Frame size in samples derived from the device HAL's
33
+ * `AudioTrack.getMinBufferSize()`. Useful for understanding the write
34
+ * granularity on the native side.
35
+ */
36
+ frameSizeSamples: number;
37
+ }
38
+
39
+ // ── Push Audio ──────────────────────────────────────────────────────────────
40
+
41
+ /** Options passed to `pushPipelineAudio()` / `pushPipelineAudioSync()`. */
42
+ export interface PushPipelineAudioOptions {
43
+ /** Base64-encoded PCM 16-bit signed LE audio data. */
44
+ audio: string;
45
+ /** Conversation turn identifier. */
46
+ turnId: string;
47
+ /** True if this is the first chunk of a new turn (resets jitter buffer). */
48
+ isFirstChunk?: boolean;
49
+ /** True if this is the final chunk of the current turn (marks end-of-stream). */
50
+ isLastChunk?: boolean;
51
+ }
52
+
53
+ // ── Invalidate Turn ─────────────────────────────────────────────────────────
54
+
55
+ /** Options passed to `invalidatePipelineTurn()`. */
56
+ export interface InvalidatePipelineTurnOptions {
57
+ /** The new turn identifier — stale audio for the old turn is discarded. */
58
+ turnId: string;
59
+ }
60
+
61
+ // ── State ───────────────────────────────────────────────────────────────────
62
+
63
+ /**
64
+ * Pipeline states reported via `PipelineStateChanged` events.
65
+ *
66
+ * - `idle` — connected but no audio flowing
67
+ * - `connecting` — AudioTrack being created, focus being requested
68
+ * - `streaming` — actively receiving and playing audio
69
+ * - `draining` — end-of-stream marked, playing remaining buffer
70
+ * - `error` — unrecoverable error (zombie, write failure, etc.)
71
+ */
72
+ export type PipelineState =
73
+ | 'idle'
74
+ | 'connecting'
75
+ | 'streaming'
76
+ | 'draining'
77
+ | 'error';
78
+
79
+ // ── Events ──────────────────────────────────────────────────────────────────
80
+
81
+ /** Payload for `PipelineStateChanged`. */
82
+ export interface PipelineStateChangedEvent {
83
+ state: PipelineState;
84
+ }
85
+
86
+ /** Payload for `PipelinePlaybackStarted`. */
87
+ export interface PipelinePlaybackStartedEvent {
88
+ turnId: string;
89
+ }
90
+
91
+ /** Payload for `PipelineError`. */
92
+ export interface PipelineErrorEvent {
93
+ code: string;
94
+ message: string;
95
+ }
96
+
97
+ /** Payload for `PipelineZombieDetected`. */
98
+ export interface PipelineZombieDetectedEvent {
99
+ playbackHead: number;
100
+ stalledMs: number;
101
+ }
102
+
103
+ /** Payload for `PipelineUnderrun`. */
104
+ export interface PipelineUnderrunEvent {
105
+ count: number;
106
+ }
107
+
108
+ /** Payload for `PipelineDrained`. */
109
+ export interface PipelineDrainedEvent {
110
+ turnId: string;
111
+ }
112
+
113
+ /** Payload for `PipelineAudioFocusLost` (empty — presence is the signal). */
114
+ export type PipelineAudioFocusLostEvent = Record<string, never>;
115
+
116
+ /** Payload for `PipelineAudioFocusResumed` (empty — presence is the signal). */
117
+ export type PipelineAudioFocusResumedEvent = Record<string, never>;
118
+
119
+ /**
120
+ * Map of all pipeline event names to their payload types.
121
+ * Used with `Pipeline.subscribe<K>()` for type-safe event subscriptions.
122
+ */
123
+ export interface PipelineEventMap {
124
+ PipelineStateChanged: PipelineStateChangedEvent;
125
+ PipelinePlaybackStarted: PipelinePlaybackStartedEvent;
126
+ PipelineError: PipelineErrorEvent;
127
+ PipelineZombieDetected: PipelineZombieDetectedEvent;
128
+ PipelineUnderrun: PipelineUnderrunEvent;
129
+ PipelineDrained: PipelineDrainedEvent;
130
+ PipelineAudioFocusLost: PipelineAudioFocusLostEvent;
131
+ PipelineAudioFocusResumed: PipelineAudioFocusResumedEvent;
132
+ }
133
+
134
+ /** Union of all pipeline event name strings. */
135
+ export type PipelineEventName = keyof PipelineEventMap;
136
+
137
+ // ── Telemetry ───────────────────────────────────────────────────────────────
138
+
139
+ /** Jitter buffer telemetry counters. */
140
+ export interface PipelineBufferTelemetry {
141
+ /** Current buffer level in milliseconds. */
142
+ bufferMs: number;
143
+ /** Current buffer level in samples. */
144
+ bufferSamples: number;
145
+ /** Whether the priming gate has opened. */
146
+ primed: boolean;
147
+ /** Total samples written by the producer since last reset. */
148
+ totalWritten: number;
149
+ /** Total samples read by the consumer since last reset. */
150
+ totalRead: number;
151
+ /** Number of underrun events. */
152
+ underrunCount: number;
153
+ /** Peak buffer level in samples. */
154
+ peakLevel: number;
155
+ }
156
+
157
+ /** Full pipeline telemetry snapshot. */
158
+ export interface PipelineTelemetry extends PipelineBufferTelemetry {
159
+ /** Current pipeline state. */
160
+ state: PipelineState;
161
+ /** Total pushAudio/pushAudioSync calls since connect. */
162
+ totalPushCalls: number;
163
+ /** Total bytes pushed since connect. */
164
+ totalPushBytes: number;
165
+ /** Total write-loop iterations since connect. */
166
+ totalWriteLoops: number;
167
+ /** Current turn identifier. */
168
+ turnId: string;
169
+ }
package/src/types.ts ADDED
@@ -0,0 +1,270 @@
1
+ export type RecordingEncodingType =
2
+ | 'pcm_32bit'
3
+ | 'pcm_16bit'
4
+ | 'pcm_8bit';
5
+ export type SampleRate = 16000 | 24000 | 44100 | 48000;
6
+ export type BitDepth = 8 | 16 | 32;
7
+
8
+ export const PlaybackModes = {
9
+ REGULAR: 'regular',
10
+ VOICE_PROCESSING: 'voiceProcessing',
11
+ CONVERSATION: 'conversation',
12
+ } as const;
13
+ /**
14
+ * Defines different playback modes for audio processing
15
+ */
16
+ export type PlaybackMode =
17
+ (typeof PlaybackModes)[keyof typeof PlaybackModes];
18
+
19
+ /**
20
+ * Configuration for audio playback settings
21
+ */
22
+ export interface SoundConfig {
23
+ /**
24
+ * The sample rate for audio playback in Hz
25
+ */
26
+ sampleRate?: SampleRate;
27
+
28
+ /**
29
+ * The playback mode (regular, voiceProcessing, or conversation)
30
+ */
31
+ playbackMode?: PlaybackMode;
32
+
33
+ /**
34
+ * When true, resets to default configuration regardless of other parameters
35
+ */
36
+ useDefault?: boolean;
37
+
38
+ /**
39
+ * Enable jitter buffering for audio streams
40
+ */
41
+ enableBuffering?: boolean;
42
+
43
+ /**
44
+ * Automatically enable buffering based on network conditions
45
+ */
46
+ autoBuffer?: boolean;
47
+
48
+ /**
49
+ * Configuration for the jitter buffer when enableBuffering is true
50
+ */
51
+ bufferConfig?: Partial<IAudioBufferConfig>;
52
+ }
53
+
54
+ /**
55
+ * Configuration for buffered audio streaming
56
+ */
57
+ export interface BufferedStreamConfig {
58
+ /**
59
+ * Turn ID for queue management
60
+ */
61
+ turnId: string;
62
+
63
+ /**
64
+ * Audio encoding format
65
+ */
66
+ encoding?: Encoding;
67
+
68
+ /**
69
+ * Buffer configuration options
70
+ */
71
+ bufferConfig?: Partial<IAudioBufferConfig>;
72
+
73
+ /**
74
+ * Callback for buffer health updates
75
+ */
76
+ onBufferHealth?: (metrics: IBufferHealthMetrics) => void;
77
+ }
78
+
79
+ export const EncodingTypes = {
80
+ PCM_F32LE: 'pcm_f32le',
81
+ PCM_S16LE: 'pcm_s16le',
82
+ } as const;
83
+
84
+ /**
85
+ * Defines different encoding formats for audio data
86
+ */
87
+ export type Encoding =
88
+ (typeof EncodingTypes)[keyof typeof EncodingTypes];
89
+
90
+ /**
91
+ * Smart buffering mode options
92
+ */
93
+ export type SmartBufferMode =
94
+ | 'conservative'
95
+ | 'balanced'
96
+ | 'aggressive'
97
+ | 'adaptive';
98
+
99
+ /**
100
+ * Network condition indicators for smart buffering
101
+ */
102
+ export interface NetworkConditions {
103
+ latency?: number; // Round-trip time in ms
104
+ jitter?: number; // Network jitter in ms
105
+ packetLoss?: number; // Packet loss percentage (0-100)
106
+ bandwidth?: number; // Available bandwidth estimate
107
+ }
108
+
109
+ /**
110
+ * Smart buffering configuration
111
+ */
112
+ export interface SmartBufferConfig {
113
+ mode: SmartBufferMode;
114
+ networkConditions?: NetworkConditions;
115
+ adaptiveThresholds?: {
116
+ highLatencyMs?: number; // Threshold to enable aggressive buffering
117
+ highJitterMs?: number; // Threshold to increase buffer size
118
+ packetLossPercent?: number; // Threshold to enable buffering
119
+ };
120
+ }
121
+
122
+ export interface StartRecordingResult {
123
+ fileUri: string;
124
+ mimeType: string;
125
+ channels?: number;
126
+ bitDepth?: BitDepth;
127
+ sampleRate?: SampleRate;
128
+ }
129
+
130
+ export interface AudioDataEvent {
131
+ data: string | Float32Array;
132
+ data16kHz?: string | Float32Array;
133
+ position: number;
134
+ fileUri: string;
135
+ eventDataSize: number;
136
+ totalSize: number;
137
+ soundLevel?: number;
138
+ }
139
+
140
+ export interface RecordingConfig {
141
+ sampleRate?: SampleRate; // Sample rate for recording
142
+ channels?: 1 | 2; // 1 or 2 (MONO or STEREO)
143
+ encoding?: RecordingEncodingType; // Encoding type for the recording
144
+ interval?: number; // Interval in milliseconds at which to emit recording data
145
+
146
+ // Optional parameters for audio processing
147
+ enableProcessing?: boolean; // Boolean to enable/disable audio processing (default is false)
148
+ pointsPerSecond?: number; // Number of data points to extract per second of audio (default is 1000)
149
+ onAudioStream?: (event: AudioDataEvent) => Promise<void>; // Callback function to handle audio stream
150
+ }
151
+
152
+ export interface Chunk {
153
+ text: string;
154
+ timestamp: [number, number | null];
155
+ }
156
+
157
+ export interface TranscriberData {
158
+ id: string;
159
+ isBusy: boolean;
160
+ text: string;
161
+ startTime: number;
162
+ endTime: number;
163
+ chunks: Chunk[];
164
+ }
165
+
166
+ export interface AudioRecording {
167
+ fileUri: string;
168
+ filename: string;
169
+ durationMs: number;
170
+ size: number;
171
+ channels: number;
172
+ bitDepth: BitDepth;
173
+ sampleRate: SampleRate;
174
+ mimeType: string;
175
+ transcripts?: TranscriberData[];
176
+ wavPCMData?: Float32Array; // Full PCM data for the recording in WAV format (only on web, for native use the fileUri)
177
+ }
178
+
179
+ // Audio Jitter Buffer Types
180
+
181
+ /**
182
+ * Configuration for audio buffer management
183
+ */
184
+ export interface IAudioBufferConfig {
185
+ targetBufferMs: number; // Target buffer size in milliseconds
186
+ minBufferMs: number; // Minimum buffer size before underrun handling
187
+ maxBufferMs: number; // Maximum buffer size before overrun handling
188
+ frameIntervalMs: number; // Expected frame interval in milliseconds
189
+ }
190
+
191
+ /**
192
+ * Audio payload for playback containing base64 encoded audio data
193
+ */
194
+ export interface IAudioPlayPayload {
195
+ audioData: string; // Base64 encoded PCM audio data
196
+ isFirst?: boolean; // True if this is the first chunk in a stream
197
+ isFinal?: boolean; // True if this is the final chunk in a stream
198
+ }
199
+
200
+ /**
201
+ * Processed audio frame with metadata
202
+ */
203
+ export interface IAudioFrame {
204
+ sequenceNumber: number; // Sequential frame number
205
+ data: IAudioPlayPayload; // Original audio payload
206
+ duration: number; // Estimated frame duration in milliseconds
207
+ timestamp: number; // Frame timestamp when processed
208
+ }
209
+
210
+ /**
211
+ * Buffer health states for quality monitoring
212
+ */
213
+ export type BufferHealthState =
214
+ | 'idle'
215
+ | 'healthy'
216
+ | 'degraded'
217
+ | 'critical';
218
+
219
+ /**
220
+ * Comprehensive buffer health and quality metrics
221
+ */
222
+ export interface IBufferHealthMetrics {
223
+ currentBufferMs: number; // Current buffer level in milliseconds
224
+ targetBufferMs: number; // Target buffer level in milliseconds
225
+ underrunCount: number; // Total number of buffer underruns
226
+ overrunCount: number; // Total number of buffer overruns
227
+ averageJitter: number; // Average network jitter in milliseconds
228
+ bufferHealthState: BufferHealthState; // Current buffer health assessment
229
+ adaptiveAdjustmentsCount: number; // Number of adaptive adjustments made
230
+ }
231
+
232
+ /**
233
+ * Interface for audio buffer management
234
+ */
235
+ export interface IAudioBufferManager {
236
+ enqueueFrames(audioData: IAudioPlayPayload): void;
237
+ startPlayback(): void;
238
+ stopPlayback(): void;
239
+ isPlaying(): boolean;
240
+ getHealthMetrics(): IBufferHealthMetrics;
241
+ updateConfig(config: Partial<IAudioBufferConfig>): void;
242
+ applyAdaptiveAdjustments(): void;
243
+ destroy(): void;
244
+ getCurrentBufferMs(): number;
245
+ }
246
+
247
+ /**
248
+ * Interface for frame processing
249
+ */
250
+ export interface IFrameProcessor {
251
+ parseChunk(payload: IAudioPlayPayload): IAudioFrame[];
252
+ reset(): void;
253
+ }
254
+
255
+ /**
256
+ * Interface for quality monitoring
257
+ */
258
+ export interface IQualityMonitor {
259
+ recordFrameArrival(timestamp: number): void;
260
+ recordUnderrun(): void;
261
+ recordOverrun(): void;
262
+ updateBufferLevel(bufferMs: number): void;
263
+ getMetrics(): IBufferHealthMetrics;
264
+ getBufferHealthState(
265
+ isPlaying: boolean,
266
+ currentLatencyMs: number
267
+ ): BufferHealthState;
268
+ getRecommendedAdjustment(): number;
269
+ reset(): void;
270
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ // @generated by expo-module-scripts
2
+ {
3
+ "extends": "expo-module-scripts/tsconfig.base",
4
+ "compilerOptions": {
5
+ "outDir": "./build"
6
+ },
7
+ "include": ["./src"],
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"]
9
+ }