@siteed/expo-audio-stream 1.16.0 → 2.0.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 (77) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/README.md +1 -1
  3. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +68 -22
  4. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +24 -0
  5. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +836 -386
  6. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +134 -23
  7. package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +35 -29
  8. package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
  9. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +236 -96
  10. package/android/src/main/java/net/siteed/audiostream/FFT.kt +55 -0
  11. package/android/src/main/java/net/siteed/audiostream/Features.kt +49 -7
  12. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +4 -4
  13. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +55 -47
  14. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
  15. package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  16. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +60 -13
  17. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -1
  18. package/build/AudioAnalysis/extractAudioAnalysis.js +147 -162
  19. package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -1
  20. package/build/ExpoAudioStream.types.d.ts +49 -3
  21. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  22. package/build/ExpoAudioStream.types.js.map +1 -1
  23. package/build/ExpoAudioStream.web.d.ts +2 -0
  24. package/build/ExpoAudioStream.web.d.ts.map +1 -1
  25. package/build/ExpoAudioStream.web.js +8 -1
  26. package/build/ExpoAudioStream.web.js.map +1 -1
  27. package/build/ExpoAudioStreamModule.d.ts.map +1 -1
  28. package/build/ExpoAudioStreamModule.js +216 -12
  29. package/build/ExpoAudioStreamModule.js.map +1 -1
  30. package/build/WebRecorder.web.d.ts +67 -13
  31. package/build/WebRecorder.web.d.ts.map +1 -1
  32. package/build/WebRecorder.web.js +178 -173
  33. package/build/WebRecorder.web.js.map +1 -1
  34. package/build/index.d.ts +3 -3
  35. package/build/index.d.ts.map +1 -1
  36. package/build/index.js +2 -2
  37. package/build/index.js.map +1 -1
  38. package/build/useAudioRecorder.d.ts.map +1 -1
  39. package/build/useAudioRecorder.js +12 -8
  40. package/build/useAudioRecorder.js.map +1 -1
  41. package/build/utils/audioProcessing.d.ts +24 -0
  42. package/build/utils/audioProcessing.d.ts.map +1 -0
  43. package/build/utils/audioProcessing.js +133 -0
  44. package/build/utils/audioProcessing.js.map +1 -0
  45. package/build/workers/InlineFeaturesExtractor.web.d.ts +1 -1
  46. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -1
  47. package/build/workers/InlineFeaturesExtractor.web.js +692 -175
  48. package/build/workers/InlineFeaturesExtractor.web.js.map +1 -1
  49. package/build/workers/inlineAudioWebWorker.web.d.ts +1 -1
  50. package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -1
  51. package/build/workers/inlineAudioWebWorker.web.js +3 -2
  52. package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
  53. package/ios/AudioAnalysisData.swift +51 -16
  54. package/ios/AudioProcessingHelpers.swift +710 -26
  55. package/ios/AudioProcessor.swift +334 -185
  56. package/ios/AudioStreamManager.swift +66 -22
  57. package/ios/DataPoint.swift +25 -12
  58. package/ios/DecodingConfig.swift +47 -0
  59. package/ios/ExpoAudioStreamModule.swift +189 -104
  60. package/ios/FFT.swift +62 -0
  61. package/ios/Features.swift +24 -3
  62. package/ios/RecordingSettings.swift +9 -7
  63. package/package.json +2 -1
  64. package/plugin/build/index.d.ts +2 -0
  65. package/plugin/build/index.js +10 -3
  66. package/plugin/src/index.ts +10 -1
  67. package/src/AudioAnalysis/AudioAnalysis.types.ts +68 -52
  68. package/src/AudioAnalysis/extractAudioAnalysis.ts +223 -219
  69. package/src/ExpoAudioStream.types.ts +57 -7
  70. package/src/ExpoAudioStream.web.ts +8 -1
  71. package/src/ExpoAudioStreamModule.ts +255 -10
  72. package/src/WebRecorder.web.ts +231 -243
  73. package/src/index.ts +5 -3
  74. package/src/useAudioRecorder.tsx +14 -10
  75. package/src/utils/audioProcessing.ts +205 -0
  76. package/src/workers/InlineFeaturesExtractor.web.tsx +692 -175
  77. package/src/workers/inlineAudioWebWorker.web.tsx +3 -2
@@ -1,220 +1,239 @@
1
1
  // packages/expo-audio-stream/src/AudioAnalysis/extractAudioAnalysis.ts
2
- import { ConsoleLike } from '../ExpoAudioStream.types'
2
+ /**
3
+ * This module provides functions for extracting and analyzing audio data.
4
+ * - `extractAudioAnalysis`: For detailed analysis with customizable ranges and decoding options.
5
+ * - `extractWavAudioAnalysis`: For analyzing WAV files without decoding, preserving original PCM values.
6
+ * - `extractPreview`: For generating quick previews of audio waveforms, optimized for UI rendering.
7
+ */
8
+ import crc32 from 'crc-32'
9
+
10
+ import { ConsoleLike, ExtractAudioDataOptions } from '../ExpoAudioStream.types'
3
11
  import ExpoAudioStreamModule from '../ExpoAudioStreamModule'
4
12
  import { isWeb } from '../constants'
5
13
  import {
6
- AmplitudeAlgorithm,
7
14
  AudioAnalysis,
8
15
  AudioFeaturesOptions,
9
- AudioPreview,
16
+ DataPoint,
10
17
  DecodingConfig,
11
18
  PreviewOptions,
12
19
  } from './AudioAnalysis.types'
20
+ import { processAudioBuffer } from '../utils/audioProcessing'
13
21
  import { convertPCMToFloat32 } from '../utils/convertPCMToFloat32'
14
22
  import { getWavFileInfo, WavFileInfo } from '../utils/getWavFileInfo'
15
23
  import { InlineFeaturesExtractor } from '../workers/InlineFeaturesExtractor.web'
16
24
 
17
- export interface ExtractAudioAnalysisProps {
25
+ function calculateCRC32ForDataPoint(data: Float32Array): number {
26
+ // Convert float array to byte array for CRC32
27
+ const byteArray = new Uint8Array(data.length * 4)
28
+ const dataView = new DataView(byteArray.buffer)
29
+
30
+ for (let i = 0; i < data.length; i++) {
31
+ dataView.setFloat32(i * 4, data[i], true)
32
+ }
33
+
34
+ return crc32.buf(byteArray)
35
+ }
36
+
37
+ export interface ExtractWavAudioAnalysisProps {
18
38
  fileUri?: string // should provide either fileUri or arrayBuffer
19
39
  wavMetadata?: WavFileInfo
20
40
  arrayBuffer?: ArrayBuffer
21
41
  bitDepth?: number
22
- skipWavHeader?: boolean
23
42
  durationMs?: number
24
43
  sampleRate?: number
25
44
  numberOfChannels?: number
26
- algorithm?: AmplitudeAlgorithm
27
45
  position?: number // Optional number of bytes to skip. Default is 0
28
46
  length?: number // Optional number of bytes to read.
29
- pointsPerSecond?: number // Optional number of points per second. Use to reduce the number of points and compute the number of datapoints to return.
47
+ segmentDurationMs?: number // Optional number of points per second. Use to reduce the number of points and compute the number of datapoints to return.
30
48
  features?: AudioFeaturesOptions
31
49
  featuresExtratorUrl?: string
32
50
  logger?: ConsoleLike
33
51
  decodingOptions?: DecodingConfig
34
52
  }
35
53
 
36
- export interface ExtractAudioFromAnyFormatProps
37
- extends ExtractAudioAnalysisProps {
38
- mimeType?: string
54
+ // Define base options interface with common properties
55
+ interface BaseExtractOptions {
56
+ fileUri?: string
57
+ arrayBuffer?: ArrayBuffer
58
+ /**
59
+ * Duration of each analysis segment in milliseconds. Defaults to 100ms if not specified.
60
+ */
61
+ segmentDurationMs?: number
62
+ features?: AudioFeaturesOptions
39
63
  decodingOptions?: DecodingConfig
40
- startTime?: number // Add start time in milliseconds
41
- endTime?: number // Add end time in milliseconds
64
+ logger?: ConsoleLike
42
65
  }
43
66
 
44
- export async function extractAudioFromAnyFormat({
45
- fileUri,
46
- arrayBuffer,
47
- mimeType,
48
- decodingOptions,
49
- startTime,
50
- endTime,
51
- ...restProps
52
- }: ExtractAudioFromAnyFormatProps): Promise<AudioAnalysis> {
67
+ // Time-based range options
68
+ interface TimeRangeOptions extends BaseExtractOptions {
69
+ startTimeMs?: number
70
+ endTimeMs?: number
71
+ position?: never
72
+ length?: never
73
+ }
74
+
75
+ // Byte-based range options
76
+ interface ByteRangeOptions extends BaseExtractOptions {
77
+ position?: number
78
+ length?: number
79
+ startTimeMs?: never
80
+ endTimeMs?: never
81
+ }
82
+
83
+ /**
84
+ * Options for extracting audio analysis.
85
+ * - For time-based analysis, provide `startTimeMs` and `endTimeMs`.
86
+ * - For byte-based analysis, provide `position` and `length`.
87
+ * - Do not mix time and byte ranges.
88
+ */
89
+ export type ExtractAudioAnalysisProps = TimeRangeOptions | ByteRangeOptions
90
+
91
+ /**
92
+ * Extracts detailed audio analysis from the specified audio file or buffer.
93
+ * Supports either time-based or byte-based ranges for flexibility in analysis.
94
+ *
95
+ * @param props - The options for extraction, including file URI, ranges, and decoding settings.
96
+ * @returns A promise that resolves to the audio analysis data.
97
+ * @throws {Error} If both time and byte ranges are provided or if required parameters are missing.
98
+ */
99
+ export async function extractAudioAnalysis(
100
+ props: ExtractAudioAnalysisProps
101
+ ): Promise<AudioAnalysis> {
102
+ const {
103
+ fileUri,
104
+ arrayBuffer,
105
+ decodingOptions,
106
+ logger,
107
+ segmentDurationMs = 100,
108
+ features,
109
+ } = props
110
+
53
111
  if (isWeb) {
54
112
  try {
55
- // Get the audio data
56
- let audioBuffer: ArrayBuffer
57
- if (arrayBuffer) {
58
- audioBuffer = arrayBuffer
59
- } else if (fileUri) {
60
- const response = await fetch(fileUri)
61
- if (!response.ok) {
62
- throw new Error(
63
- `Failed to fetch fileUri: ${response.statusText}`
64
- )
65
- }
66
- audioBuffer = await response.arrayBuffer()
67
- } else {
68
- throw new Error(
69
- 'Either arrayBuffer or fileUri must be provided'
70
- )
71
- }
72
-
73
- // Create audio context with target sample rate if specified
113
+ // Create AudioContext here
74
114
  const audioContext = new (window.AudioContext ||
75
115
  (window as any).webkitAudioContext)({
76
- sampleRate: decodingOptions?.targetSampleRate,
77
- })
78
-
79
- // Decode the audio data
80
- const decodedAudioBuffer =
81
- await audioContext.decodeAudioData(audioBuffer)
82
-
83
- // Calculate the actual duration in milliseconds
84
- const fullDurationMs = decodedAudioBuffer.duration * 1000
85
- const effectiveDurationMs = endTime
86
- ? endTime - (startTime || 0)
87
- : fullDurationMs - (startTime || 0)
88
-
89
- // Create a new buffer for the selected range
90
- const rangeLength = decodedAudioBuffer.length
91
- const rangeBuffer = new AudioBuffer({
92
- length: rangeLength,
93
- numberOfChannels: decodedAudioBuffer.numberOfChannels,
94
- sampleRate: decodedAudioBuffer.sampleRate,
116
+ sampleRate: decodingOptions?.targetSampleRate ?? 16000,
95
117
  })
96
118
 
97
- // Copy the selected range
98
- for (
99
- let channel = 0;
100
- channel < decodedAudioBuffer.numberOfChannels;
101
- channel++
102
- ) {
103
- const channelData = decodedAudioBuffer.getChannelData(channel)
104
- const rangeData = channelData.slice(0, rangeLength)
105
- rangeBuffer.copyToChannel(rangeData, channel)
106
- }
107
-
108
- // Use the range buffer instead of the full buffer
109
- let processedBuffer = rangeBuffer
110
-
111
- // Get original properties
112
- const originalSampleRate = processedBuffer.sampleRate
113
- const length = processedBuffer.length
114
-
115
- // Determine target format
116
- const targetChannels =
117
- decodingOptions?.targetChannels ??
118
- processedBuffer.numberOfChannels
119
- const targetSampleRate =
120
- decodingOptions?.targetSampleRate ?? processedBuffer.sampleRate
121
-
122
- // Create offline context for resampling if needed
123
- if (targetSampleRate !== originalSampleRate) {
124
- const offlineCtx = new OfflineAudioContext(
125
- targetChannels,
126
- (length * targetSampleRate) / originalSampleRate,
127
- targetSampleRate
128
- )
129
- const source = offlineCtx.createBufferSource()
130
- source.buffer = processedBuffer
131
- source.connect(offlineCtx.destination)
132
- source.start()
133
- processedBuffer = await offlineCtx.startRendering()
134
- }
119
+ try {
120
+ const processedBuffer = await processAudioBuffer({
121
+ arrayBuffer,
122
+ fileUri,
123
+ targetSampleRate:
124
+ decodingOptions?.targetSampleRate ?? 16000,
125
+ targetChannels: decodingOptions?.targetChannels ?? 1,
126
+ normalizeAudio: decodingOptions?.normalizeAudio ?? false,
127
+ startTimeMs:
128
+ 'startTimeMs' in props ? props.startTimeMs : undefined,
129
+ endTimeMs:
130
+ 'endTimeMs' in props ? props.endTimeMs : undefined,
131
+ position: 'position' in props ? props.position : undefined,
132
+ length: 'length' in props ? props.length : undefined,
133
+ audioContext, // Pass the context we created
134
+ logger,
135
+ })
135
136
 
136
- // Convert to the desired format
137
- const newLength = processedBuffer.length
138
- let wavBuffer: Float32Array | Int16Array | Int8Array
139
-
140
- // Create appropriate buffer based on target bit depth
141
- switch (decodingOptions?.targetBitDepth) {
142
- case 16:
143
- wavBuffer = new Int16Array(newLength * targetChannels)
144
- break
145
- case 8:
146
- wavBuffer = new Int8Array(newLength * targetChannels)
147
- break
148
- case 32:
149
- default:
150
- wavBuffer = new Float32Array(newLength * targetChannels)
151
- break
152
- }
137
+ const channelData = processedBuffer.buffer.getChannelData(0)
153
138
 
154
- // Interleave channels and handle bit depth conversion
155
- const numChannels = Math.min(
156
- processedBuffer.numberOfChannels,
157
- targetChannels
158
- )
159
- for (let channel = 0; channel < numChannels; channel++) {
160
- const channelData = processedBuffer.getChannelData(channel)
161
- for (let i = 0; i < newLength; i++) {
162
- let sample = channelData[i]
163
-
164
- // Normalize if requested
165
- if (decodingOptions?.normalizeAudio) {
166
- sample = Math.max(-1, Math.min(1, sample))
139
+ // Create and initialize the worker
140
+ const blob = new Blob([InlineFeaturesExtractor], {
141
+ type: 'application/javascript',
142
+ })
143
+ const workerUrl = URL.createObjectURL(blob)
144
+ const worker = new Worker(workerUrl)
145
+
146
+ return new Promise((resolve, reject) => {
147
+ worker.onmessage = (event) => {
148
+ if (event.data.error) {
149
+ reject(new Error(event.data.error))
150
+ return
151
+ }
152
+
153
+ const result: AudioAnalysis = event.data.result
154
+ // Calculate CRC32 after worker completes if requested
155
+ if (features?.crc32) {
156
+ const samplesPerSegment = Math.floor(
157
+ (processedBuffer.sampleRate *
158
+ segmentDurationMs) /
159
+ 1000
160
+ )
161
+
162
+ result.dataPoints = result.dataPoints.map(
163
+ (point: DataPoint, index: number) => {
164
+ const startSample =
165
+ index * samplesPerSegment
166
+ const segmentData = channelData.slice(
167
+ startSample,
168
+ startSample + samplesPerSegment
169
+ )
170
+
171
+ return {
172
+ ...point,
173
+ features: {
174
+ ...point.features,
175
+ crc32: calculateCRC32ForDataPoint(
176
+ segmentData
177
+ ),
178
+ },
179
+ }
180
+ }
181
+ )
182
+ }
183
+
184
+ URL.revokeObjectURL(workerUrl)
185
+ worker.terminate()
186
+ resolve(result)
167
187
  }
168
188
 
169
- // Convert sample based on target bit depth
170
- if (decodingOptions?.targetBitDepth === 16) {
171
- sample = sample * 32767 // Convert to 16-bit range
172
- } else if (decodingOptions?.targetBitDepth === 8) {
173
- sample = sample * 127 // Convert to 8-bit range
189
+ worker.onerror = (error) => {
190
+ URL.revokeObjectURL(workerUrl)
191
+ worker.terminate()
192
+ reject(error)
174
193
  }
175
194
 
176
- wavBuffer[i * targetChannels + channel] = sample
177
- }
195
+ worker.postMessage({
196
+ channelData,
197
+ sampleRate: processedBuffer.sampleRate,
198
+ segmentDurationMs,
199
+ bitDepth: decodingOptions?.targetBitDepth ?? 32,
200
+ numberOfChannels: processedBuffer.channels,
201
+ // enableLogging: !!logger,
202
+ features,
203
+ })
204
+ })
205
+ } finally {
206
+ await audioContext.close()
178
207
  }
179
-
180
- // Pass the duration to extractAudioAnalysis
181
- return await extractAudioAnalysis({
182
- arrayBuffer: wavBuffer.buffer as ArrayBuffer,
183
- bitDepth: decodingOptions?.targetBitDepth ?? 32,
184
- skipWavHeader: true,
185
- sampleRate: targetSampleRate,
186
- numberOfChannels: targetChannels,
187
- durationMs: effectiveDurationMs,
188
- ...restProps,
189
- })
190
208
  } catch (error) {
191
- console.error('Failed to process audio:', error)
209
+ logger?.error('Failed to process audio:', error)
192
210
  throw error
193
211
  }
194
212
  } else {
195
- // For native platforms, pass through all options
196
- return await extractAudioAnalysis({
197
- fileUri,
198
- decodingOptions,
199
- ...restProps,
200
- })
213
+ return await ExpoAudioStreamModule.extractAudioAnalysis(props)
201
214
  }
202
215
  }
203
216
 
204
- export const extractAudioAnalysis = async ({
217
+ /**
218
+ * Analyzes WAV files without decoding, preserving original PCM values.
219
+ * Use this function when you need to ensure the analysis matches other software by avoiding any transformations.
220
+ *
221
+ * @param props - The options for WAV analysis, including file URI and range.
222
+ * @returns A promise that resolves to the audio analysis data.
223
+ */
224
+ export const extractRawWavAnalysis = async ({
205
225
  fileUri,
206
- pointsPerSecond = 20,
226
+ segmentDurationMs = 100, // Default to 100ms
207
227
  arrayBuffer,
208
228
  bitDepth,
209
- skipWavHeader = true,
210
229
  durationMs,
211
230
  sampleRate,
212
231
  numberOfChannels,
213
- algorithm = 'rms',
214
232
  features,
215
- featuresExtratorUrl,
216
233
  logger,
217
- }: ExtractAudioAnalysisProps): Promise<AudioAnalysis> => {
234
+ position = 0,
235
+ length,
236
+ }: ExtractWavAudioAnalysisProps): Promise<AudioAnalysis> => {
218
237
  if (isWeb) {
219
238
  if (!arrayBuffer && !fileUri) {
220
239
  throw new Error('Either arrayBuffer or fileUri must be provided')
@@ -237,7 +256,7 @@ export const extractAudioAnalysis = async ({
237
256
  // Create a new copy of the ArrayBuffer to avoid detachment issues
238
257
  const bufferCopy = arrayBuffer.slice(0)
239
258
  logger?.log(
240
- `extractAudioAnalysis skipWavHeader=${skipWavHeader} bitDepth=${bitDepth} len=${bufferCopy.byteLength}`,
259
+ `extractAudioAnalysis bitDepth=${bitDepth} len=${bufferCopy.byteLength}`,
241
260
  bufferCopy.slice(0, 100)
242
261
  )
243
262
 
@@ -258,25 +277,22 @@ export const extractAudioAnalysis = async ({
258
277
  } = await convertPCMToFloat32({
259
278
  buffer: arrayBuffer,
260
279
  bitDepth: actualBitDepth,
261
- skipWavHeader,
262
280
  })
263
281
  logger?.log(
264
- `extractAudioAnalysis skipWaveHeader=${skipWavHeader} convertPCMToFloat32 length=${channelData.length} range: [ ${min} :: ${max} ]`
282
+ `extractAudioAnalysis convertPCMToFloat32 length=${channelData.length} range: [ ${min} :: ${max} ]`
265
283
  )
266
284
 
285
+ // Apply position and length constraints to channelData if specified
286
+ const startIndex = position
287
+ const endIndex = length ? startIndex + length : channelData.length
288
+ const constrainedChannelData = channelData.slice(startIndex, endIndex)
289
+
267
290
  return new Promise((resolve, reject) => {
268
- let worker: Worker
269
- if (featuresExtratorUrl) {
270
- worker = new Worker(
271
- new URL(featuresExtratorUrl, window.location.href)
272
- )
273
- } else {
274
- const blob = new Blob([InlineFeaturesExtractor], {
275
- type: 'application/javascript',
276
- })
277
- const url = URL.createObjectURL(blob)
278
- worker = new Worker(url)
279
- }
291
+ const blob = new Blob([InlineFeaturesExtractor], {
292
+ type: 'application/javascript',
293
+ })
294
+ const url = URL.createObjectURL(blob)
295
+ const worker = new Worker(url)
280
296
 
281
297
  worker.onmessage = (event) => {
282
298
  resolve(event.data.result)
@@ -288,10 +304,10 @@ export const extractAudioAnalysis = async ({
288
304
 
289
305
  worker.postMessage({
290
306
  command: 'process',
291
- channelData,
307
+ channelData: constrainedChannelData,
292
308
  sampleRate,
293
- pointsPerSecond,
294
- algorithm,
309
+ segmentDurationMs,
310
+ logger,
295
311
  bitDepth,
296
312
  fullAudioDurationMs: durationMs,
297
313
  numberOfChannels,
@@ -303,64 +319,52 @@ export const extractAudioAnalysis = async ({
303
319
  }
304
320
  logger?.log(`extractAudioAnalysis`, {
305
321
  fileUri,
306
- pointsPerSecond,
307
- algorithm,
322
+ segmentDurationMs,
308
323
  })
309
324
  const res = await ExpoAudioStreamModule.extractAudioAnalysis({
310
325
  fileUri,
311
- pointsPerSecond,
312
- skipWavHeader,
313
- algorithm,
326
+ segmentDurationMs,
314
327
  features,
328
+ position,
329
+ length,
315
330
  })
316
331
  logger?.log(`extractAudioAnalysis`, res)
317
332
  return res
318
333
  }
319
334
  }
320
335
 
336
+ /**
337
+ * Generates a simplified preview of the audio waveform for quick visualization.
338
+ * Ideal for UI rendering with a specified number of points.
339
+ *
340
+ * @param options - The options for the preview, including file URI and time range.
341
+ * @returns A promise that resolves to the audio preview data.
342
+ */
321
343
  export async function extractPreview({
322
344
  fileUri,
323
- numberOfPoints,
324
- algorithm = 'rms',
325
- startTime,
326
- endTime,
345
+ numberOfPoints = 100,
346
+ startTimeMs = 0,
347
+ endTimeMs = 30000, // First 30 seconds
327
348
  decodingOptions,
328
- }: PreviewOptions): Promise<AudioPreview> {
329
- if (isWeb) {
330
- // For web, we can reuse the existing extractAudioFromAnyFormat with modified parameters
331
- const analysis = await extractAudioFromAnyFormat({
332
- fileUri,
333
- algorithm,
334
- decodingOptions,
335
- startTime, // Pass startTime
336
- endTime, // Pass endTime
337
- pointsPerSecond:
338
- (numberOfPoints ?? 100) /
339
- ((endTime ? endTime - (startTime || 0) : 1000) / 1000), // Adjust points per second calculation
340
- })
341
-
342
- // Convert AudioAnalysis to AudioPreview format and adjust duration
343
- return {
344
- pointsPerSecond: analysis.pointsPerSecond,
345
- durationMs: endTime
346
- ? endTime - (startTime || 0)
347
- : analysis.durationMs, // Use range duration if specified
348
- amplitudeRange: analysis.amplitudeRange,
349
- dataPoints: analysis.dataPoints.map((point) => ({
350
- id: point.id,
351
- amplitude: point.amplitude,
352
- startTime: point.startTime,
353
- endTime: point.endTime,
354
- })),
355
- }
356
- }
349
+ logger,
350
+ }: PreviewOptions): Promise<AudioAnalysis> {
351
+ const durationMs = endTimeMs - startTimeMs
352
+ const segmentDurationMs = Math.floor(durationMs / numberOfPoints)
357
353
 
358
- return await ExpoAudioStreamModule.extractPreview({
354
+ // Call extractAudioAnalysis with calculated parameters
355
+ const analysis = await extractAudioAnalysis({
359
356
  fileUri,
360
- numberOfPoints,
361
- algorithm,
362
- startTime,
363
- endTime,
357
+ startTimeMs,
358
+ endTimeMs,
359
+ logger,
360
+ segmentDurationMs,
364
361
  decodingOptions,
365
362
  })
363
+
364
+ // Transform the result into AudioPreview format
365
+ return analysis
366
366
  }
367
+
368
+ export const extractAudioData = async (props: ExtractAudioDataOptions) => {
369
+ return await ExpoAudioStreamModule.extractAudioData(props)
370
+ }
@@ -1,8 +1,8 @@
1
1
  // packages/expo-audio-stream/src/ExpoAudioStream.types.ts
2
2
  import {
3
- AmplitudeAlgorithm,
4
3
  AudioAnalysis,
5
4
  AudioFeaturesOptions,
5
+ DecodingConfig,
6
6
  } from './AudioAnalysis/AudioAnalysis.types'
7
7
  import { AudioAnalysisEvent } from './events'
8
8
 
@@ -20,6 +20,7 @@ export interface AudioStreamStatus {
20
20
  durationMs: number
21
21
  size: number
22
22
  interval: number
23
+ intervalAnalysis: number
23
24
  mimeType: string
24
25
  compression?: CompressionInfo
25
26
  }
@@ -38,10 +39,12 @@ export interface AudioDataEvent {
38
39
  export type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'
39
40
  export type SampleRate = 16000 | 44100 | 48000
40
41
  export type BitDepth = 8 | 16 | 32
42
+ export type PCMFormat = `pcm_${BitDepth}bit`
41
43
 
42
44
  export type ConsoleLike = {
43
45
  log: (message: string, ...args: unknown[]) => void
44
46
  debug: (message: string, ...args: unknown[]) => void
47
+ info: (message: string, ...args: unknown[]) => void
45
48
  warn: (message: string, ...args: unknown[]) => void
46
49
  error: (message: string, ...args: unknown[]) => void
47
50
  }
@@ -147,7 +150,10 @@ export interface RecordingConfig {
147
150
  // Interval in milliseconds at which to emit recording data
148
151
  interval?: number
149
152
 
150
- // Continue recording when app is in background (default is true)
153
+ // Interval in milliseconds at which to emit analysis data
154
+ intervalAnalysis?: number
155
+
156
+ // Keep the device awake while recording (default is false)
151
157
  keepAwake?: boolean
152
158
 
153
159
  // Show a notification during recording (default is false)
@@ -165,11 +171,8 @@ export interface RecordingConfig {
165
171
  // iOS-specific configuration
166
172
  ios?: IOSConfig
167
173
 
168
- // Number of data points to extract per second of audio (default is 1000)
169
- pointsPerSecond?: number
170
-
171
- // Algorithm to use for amplitude computation (default is "rms")
172
- algorithm?: AmplitudeAlgorithm
174
+ // Duration of each segment in milliseconds (default: 100)
175
+ segmentDurationMs?: number
173
176
 
174
177
  // Feature options to extract (default is empty)
175
178
  features?: AudioFeaturesOptions
@@ -264,6 +267,53 @@ export interface WaveformConfig {
264
267
  height?: number // Height of the waveform view in dp (default: 64)
265
268
  }
266
269
 
270
+ export interface ExtractAudioDataOptions {
271
+ fileUri: string
272
+ // Time-based range (mutually exclusive with byte-based range)
273
+ startTimeMs?: number
274
+ endTimeMs?: number
275
+ // Byte-based range (mutually exclusive with time-based range)
276
+ position?: number
277
+ length?: number
278
+ /** Include normalized audio data in [-1, 1] range */
279
+ includeNormalizedData?: boolean
280
+ /** Include base64 encoded string representation of the audio data */
281
+ includeBase64Data?: boolean
282
+ /** Include WAV header in the PCM data (makes it a valid WAV file) */
283
+ includeWavHeader?: boolean
284
+ /** Logger for debugging - can pass console directly. */
285
+ logger?: ConsoleLike
286
+ /** Compute the checksum of the pcm data */
287
+ computeChecksum?: boolean
288
+ /** Target config for the normalized audio (Android and Web) */
289
+ decodingOptions?: DecodingConfig
290
+ }
291
+
292
+ export interface ExtractedAudioData {
293
+ /** Raw PCM audio data */
294
+ pcmData: Uint8Array
295
+ /** Normalized audio data in [-1, 1] range (when includeNormalizedData is true) */
296
+ normalizedData?: Float32Array
297
+ /** Base64 encoded string representation of the audio data (when includeBase64Data is true) */
298
+ base64Data?: string
299
+ /** Sample rate in Hz (e.g., 44100, 48000) */
300
+ sampleRate: number
301
+ /** Number of audio channels (1 for mono, 2 for stereo) */
302
+ channels: number
303
+ /** Bits per sample (8, 16, or 32) */
304
+ bitDepth: BitDepth
305
+ /** Duration of the audio in milliseconds */
306
+ durationMs: number
307
+ /** PCM format identifier (e.g., "pcm_16bit") */
308
+ format: PCMFormat
309
+ /** Total number of audio samples per channel */
310
+ samples: number
311
+ /** Whether the pcmData includes a WAV header */
312
+ hasWavHeader?: boolean
313
+ /** CRC32 Checksum of pcm data */
314
+ checksum?: number
315
+ }
316
+
267
317
  export interface UseAudioRecorderState {
268
318
  startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>
269
319
  stopRecording: () => Promise<AudioRecording | null>