@siteed/expo-audio-stream 2.1.0 → 2.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 (187) hide show
  1. package/README.md +40 -222
  2. package/build/index.d.ts +11 -15
  3. package/build/index.js +44 -14
  4. package/package.json +49 -110
  5. package/src/index.ts +18 -32
  6. package/CHANGELOG.md +0 -206
  7. package/android/build.gradle +0 -105
  8. package/android/src/main/AndroidManifest.xml +0 -27
  9. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -166
  10. package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
  11. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -131
  12. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -103
  13. package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -435
  14. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -2235
  15. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -1437
  16. package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -152
  17. package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +0 -1099
  18. package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -21
  19. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
  20. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -739
  21. package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
  22. package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
  23. package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -70
  24. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -59
  25. package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
  26. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -205
  27. package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
  28. package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
  29. package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
  30. package/android/src/main/res/drawable/ic_microphone.xml +0 -13
  31. package/android/src/main/res/drawable/ic_pause.xml +0 -10
  32. package/android/src/main/res/drawable/ic_play.xml +0 -10
  33. package/android/src/main/res/drawable/ic_stop.xml +0 -10
  34. package/android/src/main/res/layout/notification_recording.xml +0 -37
  35. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
  36. package/app.plugin.js +0 -1
  37. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -179
  38. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
  39. package/build/AudioAnalysis/AudioAnalysis.types.js +0 -3
  40. package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  41. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +0 -68
  42. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
  43. package/build/AudioAnalysis/extractAudioAnalysis.js +0 -203
  44. package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  45. package/build/AudioAnalysis/extractAudioData.d.ts +0 -3
  46. package/build/AudioAnalysis/extractAudioData.d.ts.map +0 -1
  47. package/build/AudioAnalysis/extractAudioData.js +0 -5
  48. package/build/AudioAnalysis/extractAudioData.js.map +0 -1
  49. package/build/AudioAnalysis/extractMelSpectrogram.d.ts +0 -14
  50. package/build/AudioAnalysis/extractMelSpectrogram.d.ts.map +0 -1
  51. package/build/AudioAnalysis/extractMelSpectrogram.js +0 -85
  52. package/build/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
  53. package/build/AudioAnalysis/extractPreview.d.ts +0 -11
  54. package/build/AudioAnalysis/extractPreview.d.ts.map +0 -1
  55. package/build/AudioAnalysis/extractPreview.js +0 -25
  56. package/build/AudioAnalysis/extractPreview.js.map +0 -1
  57. package/build/AudioAnalysis/extractWaveform.d.ts +0 -8
  58. package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  59. package/build/AudioAnalysis/extractWaveform.js +0 -11
  60. package/build/AudioAnalysis/extractWaveform.js.map +0 -1
  61. package/build/AudioRecorder.provider.d.ts +0 -11
  62. package/build/AudioRecorder.provider.d.ts.map +0 -1
  63. package/build/AudioRecorder.provider.js +0 -37
  64. package/build/AudioRecorder.provider.js.map +0 -1
  65. package/build/ExpoAudioStream.native.d.ts +0 -3
  66. package/build/ExpoAudioStream.native.d.ts.map +0 -1
  67. package/build/ExpoAudioStream.native.js +0 -6
  68. package/build/ExpoAudioStream.native.js.map +0 -1
  69. package/build/ExpoAudioStream.types.d.ts +0 -532
  70. package/build/ExpoAudioStream.types.d.ts.map +0 -1
  71. package/build/ExpoAudioStream.types.js +0 -2
  72. package/build/ExpoAudioStream.types.js.map +0 -1
  73. package/build/ExpoAudioStream.web.d.ts +0 -59
  74. package/build/ExpoAudioStream.web.d.ts.map +0 -1
  75. package/build/ExpoAudioStream.web.js +0 -285
  76. package/build/ExpoAudioStream.web.js.map +0 -1
  77. package/build/ExpoAudioStreamModule.d.ts +0 -3
  78. package/build/ExpoAudioStreamModule.d.ts.map +0 -1
  79. package/build/ExpoAudioStreamModule.js +0 -693
  80. package/build/ExpoAudioStreamModule.js.map +0 -1
  81. package/build/WebRecorder.web.d.ts +0 -119
  82. package/build/WebRecorder.web.d.ts.map +0 -1
  83. package/build/WebRecorder.web.js +0 -436
  84. package/build/WebRecorder.web.js.map +0 -1
  85. package/build/constants.d.ts +0 -11
  86. package/build/constants.d.ts.map +0 -1
  87. package/build/constants.js +0 -14
  88. package/build/constants.js.map +0 -1
  89. package/build/events.d.ts +0 -26
  90. package/build/events.d.ts.map +0 -1
  91. package/build/events.js +0 -21
  92. package/build/events.js.map +0 -1
  93. package/build/index.d.ts.map +0 -1
  94. package/build/index.js.map +0 -1
  95. package/build/trimAudio.d.ts +0 -25
  96. package/build/trimAudio.d.ts.map +0 -1
  97. package/build/trimAudio.js +0 -67
  98. package/build/trimAudio.js.map +0 -1
  99. package/build/useAudioRecorder.d.ts +0 -21
  100. package/build/useAudioRecorder.d.ts.map +0 -1
  101. package/build/useAudioRecorder.js +0 -427
  102. package/build/useAudioRecorder.js.map +0 -1
  103. package/build/utils/BlobFix.d.ts +0 -9
  104. package/build/utils/BlobFix.d.ts.map +0 -1
  105. package/build/utils/BlobFix.js +0 -498
  106. package/build/utils/BlobFix.js.map +0 -1
  107. package/build/utils/audioProcessing.d.ts +0 -24
  108. package/build/utils/audioProcessing.d.ts.map +0 -1
  109. package/build/utils/audioProcessing.js +0 -133
  110. package/build/utils/audioProcessing.js.map +0 -1
  111. package/build/utils/concatenateBuffers.d.ts +0 -8
  112. package/build/utils/concatenateBuffers.d.ts.map +0 -1
  113. package/build/utils/concatenateBuffers.js +0 -21
  114. package/build/utils/concatenateBuffers.js.map +0 -1
  115. package/build/utils/convertPCMToFloat32.d.ts +0 -13
  116. package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
  117. package/build/utils/convertPCMToFloat32.js +0 -120
  118. package/build/utils/convertPCMToFloat32.js.map +0 -1
  119. package/build/utils/encodingToBitDepth.d.ts +0 -5
  120. package/build/utils/encodingToBitDepth.d.ts.map +0 -1
  121. package/build/utils/encodingToBitDepth.js +0 -13
  122. package/build/utils/encodingToBitDepth.js.map +0 -1
  123. package/build/utils/getWavFileInfo.d.ts +0 -26
  124. package/build/utils/getWavFileInfo.d.ts.map +0 -1
  125. package/build/utils/getWavFileInfo.js +0 -92
  126. package/build/utils/getWavFileInfo.js.map +0 -1
  127. package/build/utils/writeWavHeader.d.ts +0 -49
  128. package/build/utils/writeWavHeader.d.ts.map +0 -1
  129. package/build/utils/writeWavHeader.js +0 -91
  130. package/build/utils/writeWavHeader.js.map +0 -1
  131. package/build/workers/InlineFeaturesExtractor.web.d.ts +0 -2
  132. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
  133. package/build/workers/InlineFeaturesExtractor.web.js +0 -828
  134. package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
  135. package/build/workers/inlineAudioWebWorker.web.d.ts +0 -2
  136. package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
  137. package/build/workers/inlineAudioWebWorker.web.js +0 -157
  138. package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
  139. package/expo-module.config.json +0 -9
  140. package/ios/AudioAnalysisData.swift +0 -74
  141. package/ios/AudioNotificationManager.swift +0 -135
  142. package/ios/AudioProcessingHelpers.swift +0 -743
  143. package/ios/AudioProcessor.swift +0 -1313
  144. package/ios/AudioStreamError.swift +0 -7
  145. package/ios/AudioStreamManager.swift +0 -1708
  146. package/ios/AudioStreamManagerDelegate.swift +0 -16
  147. package/ios/DataPoint.swift +0 -54
  148. package/ios/DecodingConfig.swift +0 -47
  149. package/ios/ExpoAudioStream.podspec +0 -27
  150. package/ios/ExpoAudioStreamModule.swift +0 -805
  151. package/ios/FFT.swift +0 -62
  152. package/ios/Features.swift +0 -95
  153. package/ios/Logger.swift +0 -7
  154. package/ios/NotificationExtension.swift +0 -15
  155. package/ios/RecordingResult.swift +0 -22
  156. package/ios/RecordingSettings.swift +0 -265
  157. package/ios/WaveformExtractor.swift +0 -105
  158. package/plugin/build/index.d.ts +0 -21
  159. package/plugin/build/index.js +0 -191
  160. package/plugin/src/index.ts +0 -278
  161. package/plugin/tsconfig.json +0 -10
  162. package/plugin/tsconfig.tsbuildinfo +0 -1
  163. package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -202
  164. package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -333
  165. package/src/AudioAnalysis/extractAudioData.ts +0 -6
  166. package/src/AudioAnalysis/extractMelSpectrogram.ts +0 -144
  167. package/src/AudioAnalysis/extractPreview.ts +0 -34
  168. package/src/AudioAnalysis/extractWaveform.ts +0 -22
  169. package/src/AudioRecorder.provider.tsx +0 -54
  170. package/src/ExpoAudioStream.native.ts +0 -6
  171. package/src/ExpoAudioStream.types.ts +0 -641
  172. package/src/ExpoAudioStream.web.ts +0 -359
  173. package/src/ExpoAudioStreamModule.ts +0 -967
  174. package/src/WebRecorder.web.ts +0 -580
  175. package/src/constants.ts +0 -18
  176. package/src/events.ts +0 -60
  177. package/src/trimAudio.ts +0 -90
  178. package/src/useAudioRecorder.tsx +0 -620
  179. package/src/utils/BlobFix.ts +0 -559
  180. package/src/utils/audioProcessing.ts +0 -205
  181. package/src/utils/concatenateBuffers.ts +0 -24
  182. package/src/utils/convertPCMToFloat32.ts +0 -170
  183. package/src/utils/encodingToBitDepth.ts +0 -18
  184. package/src/utils/getWavFileInfo.ts +0 -132
  185. package/src/utils/writeWavHeader.ts +0 -114
  186. package/src/workers/InlineFeaturesExtractor.web.tsx +0 -827
  187. package/src/workers/inlineAudioWebWorker.web.tsx +0 -156
@@ -1,580 +0,0 @@
1
- // packages/expo-audio-stream/src/WebRecorder.web.ts
2
-
3
- import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'
4
- import { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'
5
- import {
6
- EmitAudioAnalysisFunction,
7
- EmitAudioEventFunction,
8
- } from './ExpoAudioStream.web'
9
- import { encodingToBitDepth } from './utils/encodingToBitDepth'
10
- import { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web'
11
- import { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web'
12
-
13
- interface AudioWorkletEvent {
14
- data: {
15
- command: string
16
- recordedData?: Float32Array
17
- sampleRate?: number
18
- }
19
- }
20
-
21
- interface AudioFeaturesEvent {
22
- data: {
23
- command: string
24
- result: AudioAnalysis
25
- }
26
- }
27
-
28
- const DEFAULT_WEB_BITDEPTH = 32
29
- const DEFAULT_SEGMENT_DURATION_MS = 100
30
- const DEFAULT_WEB_INTERVAL = 500
31
- const DEFAULT_WEB_NUMBER_OF_CHANNELS = 1
32
-
33
- const TAG = 'WebRecorder'
34
-
35
- export class WebRecorder {
36
- private audioContext: AudioContext
37
- private audioWorkletNode!: AudioWorkletNode
38
- private featureExtractorWorker?: Worker
39
- private source: MediaStreamAudioSourceNode
40
- private emitAudioEventCallback: EmitAudioEventFunction
41
- private emitAudioAnalysisCallback: EmitAudioAnalysisFunction
42
- private config: RecordingConfig
43
- private position: number = 0
44
- private numberOfChannels: number // Number of audio channels
45
- private bitDepth: number // Bit depth of the audio
46
- private exportBitDepth: number // Bit depth of the audio
47
- private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events
48
- private packetCount: number = 0
49
- private logger?: ConsoleLike
50
- private compressedMediaRecorder: MediaRecorder | null = null
51
- private compressedChunks: Blob[] = []
52
- private compressedSize: number = 0
53
- private pendingCompressedChunk: Blob | null = null
54
- private readonly wavMimeType = 'audio/wav'
55
- private dataPointIdCounter: number = 0 // Add this property to track the counter
56
-
57
- /**
58
- * Initializes a new WebRecorder instance for audio recording and processing
59
- * @param audioContext - The AudioContext to use for recording
60
- * @param source - The MediaStreamAudioSourceNode providing the audio input
61
- * @param recordingConfig - Configuration options for the recording
62
- * @param emitAudioEventCallback - Callback function for audio data events
63
- * @param emitAudioAnalysisCallback - Callback function for audio analysis events
64
- * @param logger - Optional logger for debugging information
65
- */
66
- constructor({
67
- audioContext,
68
- source,
69
- recordingConfig,
70
- emitAudioEventCallback,
71
- emitAudioAnalysisCallback,
72
- logger,
73
- }: {
74
- audioContext: AudioContext
75
- source: MediaStreamAudioSourceNode
76
- recordingConfig: RecordingConfig
77
- emitAudioEventCallback: EmitAudioEventFunction
78
- emitAudioAnalysisCallback: EmitAudioAnalysisFunction
79
- logger?: ConsoleLike
80
- }) {
81
- this.audioContext = audioContext
82
- this.source = source
83
- this.emitAudioEventCallback = emitAudioEventCallback
84
- this.emitAudioAnalysisCallback = emitAudioAnalysisCallback
85
- this.config = recordingConfig
86
- this.logger = logger
87
-
88
- const audioContextFormat = this.checkAudioContextFormat({
89
- sampleRate: this.audioContext.sampleRate,
90
- })
91
- this.logger?.debug('Initialized WebRecorder with config:', {
92
- sampleRate: audioContextFormat.sampleRate,
93
- bitDepth: audioContextFormat.bitDepth,
94
- numberOfChannels: audioContextFormat.numberOfChannels,
95
- })
96
-
97
- this.bitDepth = audioContextFormat.bitDepth
98
- this.numberOfChannels =
99
- audioContextFormat.numberOfChannels ||
100
- DEFAULT_WEB_NUMBER_OF_CHANNELS // Default to 1 if not available
101
- this.exportBitDepth =
102
- encodingToBitDepth({
103
- encoding: recordingConfig.encoding ?? 'pcm_32bit',
104
- }) ||
105
- audioContextFormat.bitDepth ||
106
- DEFAULT_WEB_BITDEPTH
107
-
108
- this.audioAnalysisData = {
109
- amplitudeRange: { min: 0, max: 0 },
110
- rmsRange: { min: 0, max: 0 },
111
- dataPoints: [],
112
- durationMs: 0,
113
- samples: 0,
114
- bitDepth: this.bitDepth,
115
- numberOfChannels: this.numberOfChannels,
116
- sampleRate: this.config.sampleRate || this.audioContext.sampleRate,
117
- segmentDurationMs:
118
- this.config.segmentDurationMs ?? DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms segments
119
- }
120
-
121
- if (recordingConfig.enableProcessing) {
122
- this.initFeatureExtractorWorker()
123
- }
124
-
125
- // Initialize compressed recording if enabled
126
- if (recordingConfig.compression?.enabled) {
127
- this.initializeCompressedRecorder()
128
- }
129
- }
130
-
131
- /**
132
- * Initializes the audio worklet using an inline script
133
- * Creates and connects the audio processing pipeline
134
- */
135
- async init() {
136
- try {
137
- // Create and use inline audio worklet
138
- const blob = new Blob([InlineAudioWebWorker], {
139
- type: 'application/javascript',
140
- })
141
- const url = URL.createObjectURL(blob)
142
- await this.audioContext.audioWorklet.addModule(url)
143
-
144
- this.audioWorkletNode = new AudioWorkletNode(
145
- this.audioContext,
146
- 'recorder-processor'
147
- )
148
-
149
- this.audioWorkletNode.port.onmessage = async (
150
- event: AudioWorkletEvent
151
- ) => {
152
- const command = event.data.command
153
- if (command !== 'newData') return
154
-
155
- const pcmBufferFloat = event.data.recordedData
156
- if (!pcmBufferFloat) {
157
- this.logger?.warn('Received empty audio buffer', event)
158
- return
159
- }
160
-
161
- // Process data in smaller chunks and emit immediately
162
- const chunkSize = this.audioContext.sampleRate * 2 // Reduce to 2 seconds chunks
163
- const sampleRate =
164
- event.data.sampleRate ?? this.audioContext.sampleRate
165
- const duration = pcmBufferFloat.length / sampleRate
166
-
167
- // Calculate bytes per sample based on bit depth
168
- const bytesPerSample = this.bitDepth / 8
169
-
170
- // Emit chunks without storing them
171
- for (let i = 0; i < pcmBufferFloat.length; i += chunkSize) {
172
- const chunk = pcmBufferFloat.slice(i, i + chunkSize)
173
- const chunkPosition = this.position + i / sampleRate
174
-
175
- // Calculate byte positions and samples
176
- const startPosition = Math.floor(i * bytesPerSample)
177
- const endPosition = Math.floor(
178
- (i + chunk.length) * bytesPerSample
179
- )
180
- const samples = chunk.length // Number of samples in this chunk
181
-
182
- // Process features if enabled
183
- if (
184
- this.config.enableProcessing &&
185
- this.featureExtractorWorker
186
- ) {
187
- this.featureExtractorWorker.postMessage({
188
- command: 'process',
189
- channelData: chunk,
190
- sampleRate,
191
- segmentDurationMs:
192
- this.config.segmentDurationMs ??
193
- DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms
194
- bitDepth: this.bitDepth,
195
- fullAudioDurationMs: chunkPosition * 1000,
196
- numberOfChannels: this.numberOfChannels,
197
- features: this.config.features,
198
- intervalAnalysis: this.config.intervalAnalysis,
199
- startPosition,
200
- endPosition,
201
- samples,
202
- })
203
- }
204
-
205
- // Emit chunk immediately
206
- this.emitAudioEventCallback({
207
- data: chunk,
208
- position: chunkPosition,
209
- compression: this.pendingCompressedChunk
210
- ? {
211
- data: this.pendingCompressedChunk,
212
- size: this.pendingCompressedChunk.size,
213
- totalSize: this.compressedSize,
214
- mimeType: 'audio/webm',
215
- format: 'opus',
216
- bitrate:
217
- this.config.compression?.bitrate ??
218
- 128000,
219
- }
220
- : undefined,
221
- })
222
- }
223
-
224
- this.position += duration
225
- this.pendingCompressedChunk = null
226
- }
227
-
228
- this.logger?.debug(
229
- `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`,
230
- this.config
231
- )
232
- this.audioWorkletNode.port.postMessage({
233
- command: 'init',
234
- recordSampleRate: this.audioContext.sampleRate,
235
- exportSampleRate:
236
- this.config.sampleRate ?? this.audioContext.sampleRate,
237
- bitDepth: this.bitDepth,
238
- exportBitDepth: this.exportBitDepth,
239
- channels: this.numberOfChannels,
240
- interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,
241
- // enableLogging: !!this.logger,
242
- })
243
-
244
- // Connect the source to the AudioWorkletNode and start recording
245
- this.source.connect(this.audioWorkletNode)
246
- this.audioWorkletNode.connect(this.audioContext.destination)
247
- } catch (error) {
248
- console.error(`[${TAG}] Failed to initialize WebRecorder`, error)
249
- }
250
- }
251
-
252
- /**
253
- * Initializes the feature extractor worker for audio analysis
254
- * Creates an inline worker from a blob for audio feature extraction
255
- */
256
- initFeatureExtractorWorker() {
257
- try {
258
- const blob = new Blob([InlineFeaturesExtractor], {
259
- type: 'application/javascript',
260
- })
261
- const url = URL.createObjectURL(blob)
262
- this.featureExtractorWorker = new Worker(url)
263
- this.featureExtractorWorker.onmessage =
264
- this.handleFeatureExtractorMessage.bind(this)
265
- this.featureExtractorWorker.onerror = (error) => {
266
- console.error(`[${TAG}] Feature extractor worker error:`, error)
267
- }
268
- this.logger?.log(
269
- 'Feature extractor worker initialized successfully'
270
- )
271
- } catch (error) {
272
- console.error(
273
- `[${TAG}] Failed to initialize feature extractor worker`,
274
- error
275
- )
276
- }
277
- }
278
-
279
- /**
280
- * Processes audio analysis results from the feature extractor worker
281
- * Updates the audio analysis data and emits events
282
- * @param event - The event containing audio analysis results
283
- */
284
- handleFeatureExtractorMessage(event: AudioFeaturesEvent) {
285
- if (event.data.command === 'features') {
286
- const segmentResult = event.data.result
287
-
288
- // Update the dataPointIdCounter based on the last ID received
289
- if (
290
- segmentResult.dataPoints &&
291
- segmentResult.dataPoints.length > 0
292
- ) {
293
- const lastDataPoint =
294
- segmentResult.dataPoints[
295
- segmentResult.dataPoints.length - 1
296
- ]
297
- if (lastDataPoint && typeof lastDataPoint.id === 'number') {
298
- this.dataPointIdCounter = Math.max(
299
- this.dataPointIdCounter,
300
- lastDataPoint.id + 1
301
- )
302
- }
303
- }
304
-
305
- this.logger?.debug('[WebRecorder] Raw segment result:', {
306
- dataPointsLength: segmentResult.dataPoints.length,
307
- durationMs: segmentResult.durationMs,
308
- sampleRate: segmentResult.sampleRate,
309
- amplitudeRange: segmentResult.amplitudeRange,
310
- })
311
-
312
- // Ensure consistent sample rate in the result
313
- segmentResult.sampleRate =
314
- this.config.sampleRate || this.audioContext.sampleRate
315
-
316
- // Update the full audio analysis data with proper range merging
317
- this.audioAnalysisData.dataPoints.push(...segmentResult.dataPoints)
318
- this.audioAnalysisData.durationMs += segmentResult.durationMs
319
-
320
- // Make sure the sample rate is consistent
321
- this.audioAnalysisData.sampleRate = segmentResult.sampleRate
322
-
323
- // Properly merge amplitude ranges
324
- if (segmentResult.amplitudeRange) {
325
- if (!this.audioAnalysisData.amplitudeRange) {
326
- this.audioAnalysisData.amplitudeRange = {
327
- ...segmentResult.amplitudeRange,
328
- }
329
- } else {
330
- this.audioAnalysisData.amplitudeRange = {
331
- min: Math.min(
332
- this.audioAnalysisData.amplitudeRange.min,
333
- segmentResult.amplitudeRange.min
334
- ),
335
- max: Math.max(
336
- this.audioAnalysisData.amplitudeRange.max,
337
- segmentResult.amplitudeRange.max
338
- ),
339
- }
340
- }
341
- }
342
-
343
- // Properly merge RMS ranges
344
- if (segmentResult.rmsRange) {
345
- if (!this.audioAnalysisData.rmsRange) {
346
- this.audioAnalysisData.rmsRange = {
347
- ...segmentResult.rmsRange,
348
- }
349
- } else {
350
- this.audioAnalysisData.rmsRange = {
351
- min: Math.min(
352
- this.audioAnalysisData.rmsRange.min,
353
- segmentResult.rmsRange.min
354
- ),
355
- max: Math.max(
356
- this.audioAnalysisData.rmsRange.max,
357
- segmentResult.rmsRange.max
358
- ),
359
- }
360
- }
361
- }
362
-
363
- this.logger?.debug('features event segmentResult', segmentResult)
364
- this.logger?.debug(
365
- `features event audioAnalysisData duration=${this.audioAnalysisData.durationMs}`,
366
- this.audioAnalysisData
367
- )
368
- this.emitAudioAnalysisCallback(segmentResult)
369
-
370
- this.logger?.debug('[WebRecorder] Updated audioAnalysisData:', {
371
- dataPointsLength: this.audioAnalysisData.dataPoints.length,
372
- durationMs: this.audioAnalysisData.durationMs,
373
- sampleRate: this.audioAnalysisData.sampleRate,
374
- amplitudeRange: this.audioAnalysisData.amplitudeRange,
375
- })
376
- }
377
- }
378
-
379
- /**
380
- * Resets the data point ID counter
381
- * Used when starting a new recording
382
- */
383
- resetDataPointCounter() {
384
- this.dataPointIdCounter = 0
385
-
386
- // Reset the counter in the worker
387
- if (this.featureExtractorWorker) {
388
- this.featureExtractorWorker.postMessage({
389
- command: 'resetCounter',
390
- startCounterFrom: 0,
391
- })
392
- }
393
- }
394
-
395
- /**
396
- * Starts the audio recording process
397
- * Connects the audio nodes and begins capturing audio data
398
- */
399
- start() {
400
- this.source.connect(this.audioWorkletNode)
401
- this.audioWorkletNode.connect(this.audioContext.destination)
402
- this.packetCount = 0
403
-
404
- // Reset the counter when starting a new recording
405
- this.resetDataPointCounter()
406
-
407
- if (this.compressedMediaRecorder) {
408
- this.compressedMediaRecorder.start(this.config.interval ?? 1000)
409
- }
410
- }
411
-
412
- /**
413
- * Stops the audio recording process and returns the recorded data
414
- * @returns Promise resolving to an object containing PCM data and optional compressed blob
415
- */
416
- async stop(): Promise<{ pcmData: Float32Array; compressedBlob?: Blob }> {
417
- try {
418
- if (this.compressedMediaRecorder) {
419
- this.compressedMediaRecorder.stop()
420
- return {
421
- pcmData: new Float32Array(), // Return empty array since we're streaming
422
- compressedBlob: new Blob(this.compressedChunks, {
423
- type: 'audio/webm;codecs=opus',
424
- }),
425
- }
426
- }
427
- return { pcmData: new Float32Array() }
428
- } finally {
429
- this.cleanup()
430
- // Reset the chunks array
431
- this.compressedChunks = []
432
- this.compressedSize = 0
433
- this.pendingCompressedChunk = null
434
- }
435
- }
436
-
437
- /**
438
- * Cleans up resources when recording is stopped
439
- * Closes audio context and disconnects nodes
440
- */
441
- private cleanup() {
442
- if (this.audioContext) {
443
- this.audioContext.close()
444
- }
445
- if (this.audioWorkletNode) {
446
- this.audioWorkletNode.disconnect()
447
- }
448
- if (this.source) {
449
- this.source.disconnect()
450
- }
451
- this.stopMediaStreamTracks()
452
- }
453
-
454
- /**
455
- * Pauses the audio recording process
456
- * Disconnects audio nodes and pauses the media recorder
457
- */
458
- pause() {
459
- this.source.disconnect(this.audioWorkletNode) // Disconnect the source from the AudioWorkletNode
460
- this.audioWorkletNode.disconnect(this.audioContext.destination) // Disconnect the AudioWorkletNode from the destination
461
- this.audioWorkletNode.port.postMessage({ command: 'pause' })
462
- this.compressedMediaRecorder?.pause()
463
- }
464
-
465
- /**
466
- * Stops all media stream tracks to release hardware resources
467
- * Ensures recording indicators (like microphone icon) are turned off
468
- */
469
- stopMediaStreamTracks() {
470
- // Stop all audio tracks to stop the recording icon
471
- const tracks = this.source.mediaStream.getTracks()
472
- tracks.forEach((track) => track.stop())
473
- }
474
-
475
- /**
476
- * Determines the audio format capabilities of the current audio context
477
- * @param sampleRate - The sample rate to check
478
- * @returns Object containing format information (sample rate, bit depth, channels)
479
- */
480
- private checkAudioContextFormat({ sampleRate }: { sampleRate: number }) {
481
- // Create a silent AudioBuffer
482
- const frameCount = sampleRate * 1.0 // 1 second buffer
483
- const audioBuffer = this.audioContext.createBuffer(
484
- 1,
485
- frameCount,
486
- sampleRate
487
- )
488
-
489
- // Check the format
490
- const channelData = audioBuffer.getChannelData(0)
491
- const bitDepth = channelData.BYTES_PER_ELEMENT * 8 // 4 bytes per element means 32-bit
492
-
493
- return {
494
- sampleRate: audioBuffer.sampleRate,
495
- bitDepth,
496
- numberOfChannels: audioBuffer.numberOfChannels,
497
- }
498
- }
499
-
500
- /**
501
- * Resumes a paused recording
502
- * Reconnects audio nodes and resumes the media recorder
503
- */
504
- resume() {
505
- this.source.connect(this.audioWorkletNode)
506
- this.audioWorkletNode.connect(this.audioContext.destination)
507
- this.audioWorkletNode.port.postMessage({ command: 'resume' })
508
- this.compressedMediaRecorder?.resume()
509
- }
510
-
511
- /**
512
- * Initializes the compressed media recorder if compression is enabled
513
- * Sets up event handlers for compressed audio data
514
- */
515
- private initializeCompressedRecorder() {
516
- try {
517
- const mimeType = 'audio/webm;codecs=opus'
518
- if (!MediaRecorder.isTypeSupported(mimeType)) {
519
- this.logger?.warn(
520
- 'Opus compression not supported in this browser'
521
- )
522
- return
523
- }
524
-
525
- this.compressedMediaRecorder = new MediaRecorder(
526
- this.source.mediaStream,
527
- {
528
- mimeType,
529
- audioBitsPerSecond:
530
- this.config.compression?.bitrate ?? 128000,
531
- }
532
- )
533
-
534
- this.compressedMediaRecorder.ondataavailable = (event) => {
535
- if (event.data.size > 0) {
536
- this.compressedChunks.push(event.data)
537
- this.compressedSize += event.data.size
538
- this.pendingCompressedChunk = event.data
539
- }
540
- }
541
- } catch (error) {
542
- this.logger?.error(
543
- 'Failed to initialize compressed recorder:',
544
- error
545
- )
546
- }
547
- }
548
-
549
- /**
550
- * Processes features if enabled
551
- */
552
- processFeatures(
553
- chunk: Float32Array,
554
- sampleRate: number,
555
- chunkPosition: number,
556
- startPosition: number,
557
- endPosition: number,
558
- samples: number
559
- ) {
560
- if (this.config.enableProcessing && this.featureExtractorWorker) {
561
- this.featureExtractorWorker.postMessage({
562
- command: 'process',
563
- channelData: chunk,
564
- sampleRate,
565
- segmentDurationMs:
566
- this.config.segmentDurationMs ??
567
- DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms
568
- bitDepth: this.bitDepth,
569
- fullAudioDurationMs: chunkPosition * 1000,
570
- numberOfChannels: this.numberOfChannels,
571
- features: this.config.features,
572
- intervalAnalysis: this.config.intervalAnalysis,
573
- startPosition,
574
- endPosition,
575
- samples,
576
- startCounterFrom: this.dataPointIdCounter, // Pass the current counter value
577
- })
578
- }
579
- }
580
- }
package/src/constants.ts DELETED
@@ -1,18 +0,0 @@
1
- // packages/expo-audio-stream/src/constants.ts
2
- import { Platform } from 'react-native'
3
-
4
- import { BitDepth, SampleRate } from './ExpoAudioStream.types'
5
-
6
- export const isWeb = Platform.OS === 'web'
7
- export const DEBUG_NAMESPACE = 'expo-audio-stream'
8
-
9
- // Constants for identifying chunks in a WAV file
10
- export const RIFF_HEADER = 0x52494646 // "RIFF"
11
- export const WAVE_HEADER = 0x57415645 // "WAVE"
12
- export const FMT_CHUNK_ID = 0x666d7420 // "fmt "
13
- export const DATA_CHUNK_ID = 0x64617461 // "data"
14
- export const INFO_CHUNK_ID = 0x494e464f // "INFO"
15
-
16
- // Default values
17
- export const DEFAULT_SAMPLE_RATE: SampleRate = 16000
18
- export const DEFAULT_BIT_DEPTH: BitDepth = 32
package/src/events.ts DELETED
@@ -1,60 +0,0 @@
1
- // packages/expo-audio-stream/src/events.ts
2
-
3
- import { LegacyEventEmitter, type EventSubscription } from 'expo-modules-core'
4
-
5
- import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'
6
- import { RecordingInterruptionEvent } from './ExpoAudioStream.types'
7
- import ExpoAudioStreamModule from './ExpoAudioStreamModule'
8
-
9
- const emitter = new LegacyEventEmitter(ExpoAudioStreamModule)
10
-
11
- // Internal event payload from native module
12
- export interface AudioEventPayload {
13
- encoded?: string
14
- buffer?: Float32Array
15
- fileUri: string
16
- lastEmittedSize: number
17
- position: number
18
- deltaSize: number
19
- totalSize: number
20
- mimeType: string
21
- streamUuid: string
22
- compression?: {
23
- data?: string | Blob // Base64 (native) or Float32Array (web) encoded compressed data chunk
24
- position: number
25
- eventDataSize: number
26
- totalSize: number
27
- }
28
- }
29
-
30
- export function addAudioEventListener(
31
- listener: (event: AudioEventPayload) => Promise<void>
32
- ): EventSubscription {
33
- return emitter.addListener<AudioEventPayload>('AudioData', listener)
34
- }
35
-
36
- // Only aliasing the AudioAnalysis type for the event payload
37
- export interface AudioAnalysisEvent extends AudioAnalysis {}
38
-
39
- export function addAudioAnalysisListener(
40
- listener: (event: AudioAnalysisEvent) => Promise<void>
41
- ): EventSubscription {
42
- return emitter.addListener<AudioAnalysisEvent>('AudioAnalysis', listener)
43
- }
44
-
45
- export function addRecordingInterruptionListener(
46
- listener: (event: RecordingInterruptionEvent) => void
47
- ): EventSubscription {
48
- // Add debug logging
49
- console.debug('Adding recording interruption listener')
50
-
51
- const subscription = emitter.addListener<RecordingInterruptionEvent>(
52
- 'onRecordingInterrupted', // Make sure this matches the native event name
53
- (event) => {
54
- console.debug('Recording interruption event received:', event)
55
- listener(event)
56
- }
57
- )
58
-
59
- return subscription
60
- }