@siteed/expo-audio-stream 2.0.1 → 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 (166) hide show
  1. package/README.md +46 -27
  2. package/build/index.d.ts +11 -12
  3. package/build/index.js +44 -10
  4. package/package.json +49 -110
  5. package/src/index.ts +18 -33
  6. package/CHANGELOG.md +0 -195
  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 -1936
  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 -138
  17. package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -20
  18. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
  19. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -509
  20. package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
  21. package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
  22. package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -70
  23. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -59
  24. package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
  25. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -205
  26. package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
  27. package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
  28. package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
  29. package/android/src/main/res/drawable/ic_microphone.xml +0 -13
  30. package/android/src/main/res/drawable/ic_pause.xml +0 -10
  31. package/android/src/main/res/drawable/ic_play.xml +0 -10
  32. package/android/src/main/res/drawable/ic_stop.xml +0 -10
  33. package/android/src/main/res/layout/notification_recording.xml +0 -37
  34. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
  35. package/app.plugin.js +0 -1
  36. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -144
  37. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
  38. package/build/AudioAnalysis/AudioAnalysis.types.js +0 -3
  39. package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  40. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +0 -78
  41. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
  42. package/build/AudioAnalysis/extractAudioAnalysis.js +0 -229
  43. package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  44. package/build/AudioAnalysis/extractWaveform.d.ts +0 -8
  45. package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  46. package/build/AudioAnalysis/extractWaveform.js +0 -11
  47. package/build/AudioAnalysis/extractWaveform.js.map +0 -1
  48. package/build/AudioRecorder.provider.d.ts +0 -11
  49. package/build/AudioRecorder.provider.d.ts.map +0 -1
  50. package/build/AudioRecorder.provider.js +0 -37
  51. package/build/AudioRecorder.provider.js.map +0 -1
  52. package/build/ExpoAudioStream.native.d.ts +0 -3
  53. package/build/ExpoAudioStream.native.d.ts.map +0 -1
  54. package/build/ExpoAudioStream.native.js +0 -6
  55. package/build/ExpoAudioStream.native.js.map +0 -1
  56. package/build/ExpoAudioStream.types.d.ts +0 -206
  57. package/build/ExpoAudioStream.types.d.ts.map +0 -1
  58. package/build/ExpoAudioStream.types.js +0 -2
  59. package/build/ExpoAudioStream.types.js.map +0 -1
  60. package/build/ExpoAudioStream.web.d.ts +0 -59
  61. package/build/ExpoAudioStream.web.d.ts.map +0 -1
  62. package/build/ExpoAudioStream.web.js +0 -285
  63. package/build/ExpoAudioStream.web.js.map +0 -1
  64. package/build/ExpoAudioStreamModule.d.ts +0 -3
  65. package/build/ExpoAudioStreamModule.d.ts.map +0 -1
  66. package/build/ExpoAudioStreamModule.js +0 -239
  67. package/build/ExpoAudioStreamModule.js.map +0 -1
  68. package/build/WebRecorder.web.d.ts +0 -119
  69. package/build/WebRecorder.web.d.ts.map +0 -1
  70. package/build/WebRecorder.web.js +0 -436
  71. package/build/WebRecorder.web.js.map +0 -1
  72. package/build/constants.d.ts +0 -11
  73. package/build/constants.d.ts.map +0 -1
  74. package/build/constants.js +0 -14
  75. package/build/constants.js.map +0 -1
  76. package/build/events.d.ts +0 -26
  77. package/build/events.d.ts.map +0 -1
  78. package/build/events.js +0 -21
  79. package/build/events.js.map +0 -1
  80. package/build/index.d.ts.map +0 -1
  81. package/build/index.js.map +0 -1
  82. package/build/useAudioRecorder.d.ts +0 -21
  83. package/build/useAudioRecorder.d.ts.map +0 -1
  84. package/build/useAudioRecorder.js +0 -427
  85. package/build/useAudioRecorder.js.map +0 -1
  86. package/build/utils/BlobFix.d.ts +0 -9
  87. package/build/utils/BlobFix.d.ts.map +0 -1
  88. package/build/utils/BlobFix.js +0 -498
  89. package/build/utils/BlobFix.js.map +0 -1
  90. package/build/utils/audioProcessing.d.ts +0 -24
  91. package/build/utils/audioProcessing.d.ts.map +0 -1
  92. package/build/utils/audioProcessing.js +0 -133
  93. package/build/utils/audioProcessing.js.map +0 -1
  94. package/build/utils/concatenateBuffers.d.ts +0 -8
  95. package/build/utils/concatenateBuffers.d.ts.map +0 -1
  96. package/build/utils/concatenateBuffers.js +0 -21
  97. package/build/utils/concatenateBuffers.js.map +0 -1
  98. package/build/utils/convertPCMToFloat32.d.ts +0 -13
  99. package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
  100. package/build/utils/convertPCMToFloat32.js +0 -120
  101. package/build/utils/convertPCMToFloat32.js.map +0 -1
  102. package/build/utils/encodingToBitDepth.d.ts +0 -5
  103. package/build/utils/encodingToBitDepth.d.ts.map +0 -1
  104. package/build/utils/encodingToBitDepth.js +0 -13
  105. package/build/utils/encodingToBitDepth.js.map +0 -1
  106. package/build/utils/getWavFileInfo.d.ts +0 -26
  107. package/build/utils/getWavFileInfo.d.ts.map +0 -1
  108. package/build/utils/getWavFileInfo.js +0 -92
  109. package/build/utils/getWavFileInfo.js.map +0 -1
  110. package/build/utils/writeWavHeader.d.ts +0 -49
  111. package/build/utils/writeWavHeader.d.ts.map +0 -1
  112. package/build/utils/writeWavHeader.js +0 -91
  113. package/build/utils/writeWavHeader.js.map +0 -1
  114. package/build/workers/InlineFeaturesExtractor.web.d.ts +0 -2
  115. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
  116. package/build/workers/InlineFeaturesExtractor.web.js +0 -828
  117. package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
  118. package/build/workers/inlineAudioWebWorker.web.d.ts +0 -2
  119. package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
  120. package/build/workers/inlineAudioWebWorker.web.js +0 -157
  121. package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
  122. package/expo-module.config.json +0 -9
  123. package/ios/AudioAnalysisData.swift +0 -74
  124. package/ios/AudioNotificationManager.swift +0 -135
  125. package/ios/AudioProcessingHelpers.swift +0 -743
  126. package/ios/AudioProcessor.swift +0 -858
  127. package/ios/AudioStreamError.swift +0 -7
  128. package/ios/AudioStreamManager.swift +0 -1708
  129. package/ios/AudioStreamManagerDelegate.swift +0 -16
  130. package/ios/DataPoint.swift +0 -54
  131. package/ios/DecodingConfig.swift +0 -47
  132. package/ios/ExpoAudioStream.podspec +0 -27
  133. package/ios/ExpoAudioStreamModule.swift +0 -698
  134. package/ios/FFT.swift +0 -62
  135. package/ios/Features.swift +0 -95
  136. package/ios/Logger.swift +0 -7
  137. package/ios/NotificationExtension.swift +0 -15
  138. package/ios/RecordingResult.swift +0 -22
  139. package/ios/RecordingSettings.swift +0 -265
  140. package/ios/WaveformExtractor.swift +0 -105
  141. package/plugin/build/index.d.ts +0 -21
  142. package/plugin/build/index.js +0 -191
  143. package/plugin/src/index.ts +0 -278
  144. package/plugin/tsconfig.json +0 -10
  145. package/plugin/tsconfig.tsbuildinfo +0 -1
  146. package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -165
  147. package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -370
  148. package/src/AudioAnalysis/extractWaveform.ts +0 -22
  149. package/src/AudioRecorder.provider.tsx +0 -54
  150. package/src/ExpoAudioStream.native.ts +0 -6
  151. package/src/ExpoAudioStream.types.ts +0 -329
  152. package/src/ExpoAudioStream.web.ts +0 -359
  153. package/src/ExpoAudioStreamModule.ts +0 -286
  154. package/src/WebRecorder.web.ts +0 -580
  155. package/src/constants.ts +0 -18
  156. package/src/events.ts +0 -60
  157. package/src/useAudioRecorder.tsx +0 -620
  158. package/src/utils/BlobFix.ts +0 -559
  159. package/src/utils/audioProcessing.ts +0 -205
  160. package/src/utils/concatenateBuffers.ts +0 -24
  161. package/src/utils/convertPCMToFloat32.ts +0 -170
  162. package/src/utils/encodingToBitDepth.ts +0 -18
  163. package/src/utils/getWavFileInfo.ts +0 -132
  164. package/src/utils/writeWavHeader.ts +0 -114
  165. package/src/workers/InlineFeaturesExtractor.web.tsx +0 -827
  166. package/src/workers/inlineAudioWebWorker.web.tsx +0 -156
@@ -1,329 +0,0 @@
1
- // packages/expo-audio-stream/src/ExpoAudioStream.types.ts
2
- import {
3
- AudioAnalysis,
4
- AudioFeaturesOptions,
5
- DecodingConfig,
6
- } from './AudioAnalysis/AudioAnalysis.types'
7
- import { AudioAnalysisEvent } from './events'
8
-
9
- export interface CompressionInfo {
10
- size: number
11
- mimeType: string
12
- bitrate: number
13
- format: string
14
- compressedFileUri?: string
15
- }
16
-
17
- export interface AudioStreamStatus {
18
- isRecording: boolean
19
- isPaused: boolean
20
- durationMs: number
21
- size: number
22
- interval: number
23
- intervalAnalysis: number
24
- mimeType: string
25
- compression?: CompressionInfo
26
- }
27
-
28
- export interface AudioDataEvent {
29
- data: string | Float32Array
30
- position: number
31
- fileUri: string
32
- eventDataSize: number
33
- totalSize: number
34
- compression?: CompressionInfo & {
35
- data?: string | Blob // Base64 (native) or Float32Array (web) encoded compressed data chunk
36
- }
37
- }
38
-
39
- export type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'
40
- export type SampleRate = 16000 | 44100 | 48000
41
- export type BitDepth = 8 | 16 | 32
42
- export type PCMFormat = `pcm_${BitDepth}bit`
43
-
44
- export type ConsoleLike = {
45
- log: (message: string, ...args: unknown[]) => void
46
- debug: (message: string, ...args: unknown[]) => void
47
- info: (message: string, ...args: unknown[]) => void
48
- warn: (message: string, ...args: unknown[]) => void
49
- error: (message: string, ...args: unknown[]) => void
50
- }
51
-
52
- export interface Chunk {
53
- text: string
54
- timestamp: [number, number | null]
55
- }
56
-
57
- export interface TranscriberData {
58
- id: string
59
- isBusy: boolean
60
- text: string
61
- startTime: number
62
- endTime: number
63
- chunks: Chunk[]
64
- }
65
-
66
- export interface AudioRecording {
67
- fileUri: string
68
- filename: string
69
- durationMs: number
70
- size: number
71
- mimeType: string
72
- channels: number
73
- bitDepth: BitDepth
74
- sampleRate: SampleRate
75
- createdAt?: number
76
- transcripts?: TranscriberData[]
77
- analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag
78
- compression?: CompressionInfo & {
79
- compressedFileUri: string
80
- }
81
- }
82
-
83
- export interface StartRecordingResult {
84
- fileUri: string
85
- mimeType: string
86
- channels?: number
87
- bitDepth?: BitDepth
88
- sampleRate?: SampleRate
89
- compression?: CompressionInfo & {
90
- compressedFileUri: string
91
- }
92
- }
93
-
94
- export interface AudioSessionConfig {
95
- category?:
96
- | 'Ambient'
97
- | 'SoloAmbient'
98
- | 'Playback'
99
- | 'Record'
100
- | 'PlayAndRecord'
101
- | 'MultiRoute'
102
- mode?:
103
- | 'Default'
104
- | 'VoiceChat'
105
- | 'VideoChat'
106
- | 'GameChat'
107
- | 'VideoRecording'
108
- | 'Measurement'
109
- | 'MoviePlayback'
110
- | 'SpokenAudio'
111
- categoryOptions?: (
112
- | 'MixWithOthers'
113
- | 'DuckOthers'
114
- | 'InterruptSpokenAudioAndMixWithOthers'
115
- | 'AllowBluetooth'
116
- | 'AllowBluetoothA2DP'
117
- | 'AllowAirPlay'
118
- | 'DefaultToSpeaker'
119
- )[]
120
- }
121
-
122
- export interface IOSConfig {
123
- audioSession?: AudioSessionConfig
124
- }
125
-
126
- // Add new type for interruption reasons
127
- export type RecordingInterruptionReason =
128
- | 'audioFocusLoss'
129
- | 'audioFocusGain'
130
- | 'phoneCall'
131
- | 'phoneCallEnded'
132
- | 'recordingStopped'
133
-
134
- // Add new interface for interruption events
135
- export interface RecordingInterruptionEvent {
136
- reason: RecordingInterruptionReason
137
- isPaused: boolean
138
- }
139
-
140
- export interface RecordingConfig {
141
- // Sample rate for recording (16000, 44100, or 48000 Hz)
142
- sampleRate?: SampleRate
143
-
144
- // Number of audio channels (1 for mono, 2 for stereo)
145
- channels?: 1 | 2
146
-
147
- // Encoding type for the recording (pcm_32bit, pcm_16bit, pcm_8bit)
148
- encoding?: EncodingType
149
-
150
- // Interval in milliseconds at which to emit recording data
151
- interval?: number
152
-
153
- // Interval in milliseconds at which to emit analysis data
154
- intervalAnalysis?: number
155
-
156
- // Keep the device awake while recording (default is false)
157
- keepAwake?: boolean
158
-
159
- // Show a notification during recording (default is false)
160
- showNotification?: boolean
161
-
162
- // Show waveform in the notification (Android only, when showNotification is true)
163
- showWaveformInNotification?: boolean
164
-
165
- // Configuration for the notification
166
- notification?: NotificationConfig
167
-
168
- // Enable audio processing (default is false)
169
- enableProcessing?: boolean
170
-
171
- // iOS-specific configuration
172
- ios?: IOSConfig
173
-
174
- // Duration of each segment in milliseconds (default: 100)
175
- segmentDurationMs?: number
176
-
177
- // Feature options to extract (default is empty)
178
- features?: AudioFeaturesOptions
179
-
180
- // Callback function to handle audio stream
181
- onAudioStream?: (_: AudioDataEvent) => Promise<void>
182
-
183
- // Callback function to handle audio features extraction results
184
- onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void>
185
-
186
- compression?: {
187
- enabled: boolean
188
- format: 'aac' | 'opus'
189
- bitrate?: number
190
- }
191
-
192
- // Whether to automatically resume recording after an interruption (default is false)
193
- autoResumeAfterInterruption?: boolean
194
-
195
- // Optional callback to handle recording interruptions
196
- onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void
197
-
198
- // Optional output configuration
199
- outputDirectory?: string // If not provided, uses default app directory
200
- filename?: string // If not provided, uses UUID
201
- }
202
-
203
- export interface NotificationConfig {
204
- // Title of the notification
205
- title?: string
206
-
207
- // Main text content of the notification
208
- text?: string
209
-
210
- // Icon to be displayed in the notification (resource name or URI)
211
- icon?: string
212
-
213
- // Android-specific notification configuration
214
- android?: {
215
- // Unique identifier for the notification channel
216
- channelId?: string
217
-
218
- // User-visible name of the notification channel
219
- channelName?: string
220
-
221
- // User-visible description of the notification channel
222
- channelDescription?: string
223
-
224
- // Unique identifier for this notification
225
- notificationId?: number
226
-
227
- // List of actions that can be performed from the notification
228
- actions?: NotificationAction[]
229
-
230
- // Configuration for the waveform visualization in the notification
231
- waveform?: WaveformConfig
232
-
233
- // Color of the notification LED (if device supports it)
234
- lightColor?: string
235
-
236
- // Priority of the notification (affects how it's displayed)
237
- priority?: 'min' | 'low' | 'default' | 'high' | 'max'
238
-
239
- // Accent color for the notification (used for the app icon and buttons)
240
- accentColor?: string
241
- }
242
-
243
- // iOS-specific notification configuration
244
- ios?: {
245
- // Identifier for the notification category (used for grouping similar notifications)
246
- categoryIdentifier?: string
247
- }
248
- }
249
-
250
- export interface NotificationAction {
251
- // Display title for the action
252
- title: string
253
-
254
- // Unique identifier for the action
255
- identifier: string
256
-
257
- // Icon to be displayed for the action (Android only)
258
- icon?: string
259
- }
260
-
261
- export interface WaveformConfig {
262
- color?: string // The color of the waveform (e.g., "#FFFFFF" for white)
263
- opacity?: number // Opacity of the waveform (0.0 - 1.0)
264
- strokeWidth?: number // Width of the waveform line (default: 1.5)
265
- style?: 'stroke' | 'fill' // Drawing style: "stroke" for outline, "fill" for solid
266
- mirror?: boolean // Whether to mirror the waveform (symmetrical display)
267
- height?: number // Height of the waveform view in dp (default: 64)
268
- }
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
-
317
- export interface UseAudioRecorderState {
318
- startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>
319
- stopRecording: () => Promise<AudioRecording | null>
320
- pauseRecording: () => Promise<void>
321
- resumeRecording: () => Promise<void>
322
- isRecording: boolean
323
- isPaused: boolean
324
- durationMs: number // Duration of the recording
325
- size: number // Size in bytes of the recorded audio
326
- compression?: CompressionInfo
327
- analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag
328
- onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void
329
- }
@@ -1,359 +0,0 @@
1
- // src/ExpoAudioStreamModule.web.ts
2
- import { LegacyEventEmitter } from 'expo-modules-core'
3
-
4
- import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'
5
- import {
6
- AudioRecording,
7
- AudioStreamStatus,
8
- BitDepth,
9
- ConsoleLike,
10
- RecordingConfig,
11
- StartRecordingResult,
12
- } from './ExpoAudioStream.types'
13
- import { WebRecorder } from './WebRecorder.web'
14
- import { AudioEventPayload } from './events'
15
- import { encodingToBitDepth } from './utils/encodingToBitDepth'
16
-
17
- export interface EmitAudioEventProps {
18
- data: Float32Array
19
- position: number
20
- compression?: {
21
- data: Blob
22
- size: number
23
- totalSize: number
24
- mimeType: string
25
- format: string
26
- bitrate: number
27
- }
28
- }
29
- export type EmitAudioEventFunction = (_: EmitAudioEventProps) => void
30
- export type EmitAudioAnalysisFunction = (_: AudioAnalysis) => void
31
-
32
- export interface ExpoAudioStreamWebProps {
33
- logger?: ConsoleLike
34
- audioWorkletUrl: string
35
- featuresExtratorUrl: string
36
- maxBufferSize?: number // Maximum number of chunks to keep in memory
37
- }
38
-
39
- export class ExpoAudioStreamWeb extends LegacyEventEmitter {
40
- customRecorder: WebRecorder | null
41
- audioChunks: Float32Array[]
42
- isRecording: boolean
43
- isPaused: boolean
44
- recordingStartTime: number
45
- pausedTime: number
46
- currentDurationMs: number
47
- currentSize: number
48
- currentInterval: number
49
- currentIntervalAnalysis: number
50
- lastEmittedSize: number
51
- lastEmittedTime: number
52
- lastEmittedCompressionSize: number
53
- lastEmittedAnalysisTime: number
54
- streamUuid: string | null
55
- extension: 'webm' | 'wav' = 'wav' // Default extension is 'wav'
56
- recordingConfig?: RecordingConfig
57
- bitDepth: BitDepth // Bit depth of the audio
58
- audioWorkletUrl: string
59
- featuresExtratorUrl: string
60
- logger?: ConsoleLike
61
- latestPosition: number = 0
62
- totalCompressedSize: number = 0
63
- private readonly maxBufferSize: number
64
-
65
- constructor({
66
- audioWorkletUrl,
67
- featuresExtratorUrl,
68
- logger,
69
- maxBufferSize = 100, // Default to storing last 100 chunks (1 chunk = 0.5 seconds)
70
- }: ExpoAudioStreamWebProps) {
71
- const mockNativeModule = {
72
- addListener: () => {
73
- // Not used on web
74
- },
75
- removeListeners: () => {
76
- // Not used on web
77
- },
78
- }
79
- super(mockNativeModule) // Pass the mock native module to the parent class
80
-
81
- this.logger = logger
82
- this.customRecorder = null
83
- this.audioChunks = []
84
- this.isRecording = false
85
- this.isPaused = false
86
- this.recordingStartTime = 0
87
- this.pausedTime = 0
88
- this.currentDurationMs = 0
89
- this.currentSize = 0
90
- this.bitDepth = 32 // Default
91
- this.currentInterval = 1000 // Default interval in ms
92
- this.currentIntervalAnalysis = 500 // Default analysis interval in ms
93
- this.lastEmittedSize = 0
94
- this.lastEmittedTime = 0
95
- this.latestPosition = 0
96
- this.lastEmittedCompressionSize = 0
97
- this.lastEmittedAnalysisTime = 0
98
- this.streamUuid = null // Initialize UUID on first recording start
99
- this.audioWorkletUrl = audioWorkletUrl
100
- this.featuresExtratorUrl = featuresExtratorUrl
101
- this.maxBufferSize = maxBufferSize
102
- }
103
-
104
- // Utility to handle user media stream
105
- async getMediaStream() {
106
- try {
107
- return await navigator.mediaDevices.getUserMedia({ audio: true })
108
- } catch (error) {
109
- this.logger?.error('Failed to get media stream:', error)
110
- throw error
111
- }
112
- }
113
-
114
- // Start recording with options
115
- async startRecording(
116
- recordingConfig: RecordingConfig = {}
117
- ): Promise<StartRecordingResult> {
118
- if (this.isRecording) {
119
- throw new Error('Recording is already in progress')
120
- }
121
-
122
- this.bitDepth = encodingToBitDepth({
123
- encoding: recordingConfig.encoding ?? 'pcm_32bit',
124
- })
125
-
126
- const audioContext = new (window.AudioContext ||
127
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
128
- // @ts-ignore - Allow webkitAudioContext for Safari
129
- window.webkitAudioContext)()
130
- const stream = await this.getMediaStream()
131
-
132
- const source = audioContext.createMediaStreamSource(stream)
133
-
134
- this.customRecorder = new WebRecorder({
135
- logger: this.logger,
136
- audioContext,
137
- source,
138
- recordingConfig,
139
- emitAudioEventCallback: ({
140
- data,
141
- position,
142
- compression,
143
- }: EmitAudioEventProps) => {
144
- // Keep only the latest chunks based on maxBufferSize
145
- this.audioChunks.push(new Float32Array(data))
146
- if (this.audioChunks.length > this.maxBufferSize) {
147
- this.audioChunks.shift() // Remove oldest chunk
148
- }
149
- this.currentSize += data.byteLength
150
- this.emitAudioEvent({ data, position, compression })
151
- this.lastEmittedTime = Date.now()
152
- this.lastEmittedSize = this.currentSize
153
- this.lastEmittedCompressionSize = compression?.size ?? 0
154
- },
155
- emitAudioAnalysisCallback: (audioAnalysisData: AudioAnalysis) => {
156
- this.logger?.log(`Emitted AudioAnalysis:`, audioAnalysisData)
157
- this.emit('AudioAnalysis', audioAnalysisData)
158
- },
159
- })
160
- await this.customRecorder.init()
161
- this.customRecorder.start()
162
-
163
- // // Set a timer to stop recording after 5 seconds
164
- // setTimeout(() => {
165
- // logger.log("AUTO Stopping recording");
166
- // this.customRecorder?.stopAndPlay();
167
- // this.isRecording = false;
168
- // }, 3000);
169
-
170
- this.isRecording = true
171
- this.recordingConfig = recordingConfig
172
- this.recordingStartTime = Date.now()
173
- this.pausedTime = 0
174
- this.isPaused = false
175
- this.lastEmittedSize = 0
176
- this.lastEmittedTime = 0
177
- this.lastEmittedCompressionSize = 0
178
- this.currentInterval = recordingConfig.interval ?? 1000
179
- this.currentIntervalAnalysis = recordingConfig.intervalAnalysis ?? 500
180
- this.lastEmittedAnalysisTime = Date.now()
181
-
182
- // Use custom filename if provided, otherwise fallback to timestamp
183
- if (recordingConfig.filename) {
184
- // Remove any existing extension from the filename
185
- this.streamUuid = recordingConfig.filename.replace(/\.[^/.]+$/, '')
186
- } else {
187
- this.streamUuid = Date.now().toString()
188
- }
189
-
190
- const fileUri = `${this.streamUuid}.${this.extension}`
191
- const streamConfig: StartRecordingResult = {
192
- fileUri,
193
- mimeType: `audio/${this.extension}`,
194
- bitDepth: this.bitDepth,
195
- channels: recordingConfig.channels ?? 1,
196
- sampleRate: recordingConfig.sampleRate ?? 44100,
197
- compression: recordingConfig.compression
198
- ? {
199
- ...recordingConfig.compression,
200
- bitrate: recordingConfig.compression?.bitrate ?? 128000,
201
- size: 0,
202
- mimeType: 'audio/webm',
203
- format: recordingConfig.compression?.format ?? 'opus',
204
- compressedFileUri: '',
205
- }
206
- : undefined,
207
- }
208
- return streamConfig
209
- }
210
-
211
- emitAudioEvent({ data, position, compression }: EmitAudioEventProps) {
212
- const fileUri = `${this.streamUuid}.${this.extension}`
213
- if (compression?.size) {
214
- this.lastEmittedCompressionSize = compression.size
215
- this.totalCompressedSize = compression.totalSize
216
- }
217
- this.latestPosition = position
218
- this.currentDurationMs = position * 1000 // Convert position (in seconds) to ms
219
-
220
- const audioEventPayload: AudioEventPayload = {
221
- fileUri,
222
- mimeType: `audio/${this.extension}`,
223
- lastEmittedSize: this.lastEmittedSize,
224
- deltaSize: data.byteLength,
225
- position,
226
- totalSize: this.currentSize,
227
- buffer: data,
228
- streamUuid: this.streamUuid ?? '',
229
- compression: compression
230
- ? {
231
- data: compression?.data,
232
- totalSize: this.totalCompressedSize,
233
- eventDataSize: compression?.size ?? 0,
234
- position,
235
- }
236
- : undefined,
237
- }
238
-
239
- this.emit('AudioData', audioEventPayload)
240
- }
241
-
242
- // Stop recording
243
- async stopRecording(): Promise<AudioRecording> {
244
- if (!this.customRecorder) {
245
- throw new Error('Recorder is not initialized')
246
- }
247
-
248
- this.logger?.debug('[Stop] Starting stop process')
249
- const startTime = performance.now()
250
-
251
- try {
252
- this.logger?.debug('[Stop] Stopping recorder')
253
- const { compressedBlob } = await this.customRecorder.stop()
254
-
255
- this.isRecording = false
256
- this.isPaused = false
257
- this.currentDurationMs = Date.now() - this.recordingStartTime
258
-
259
- let compression: AudioRecording['compression']
260
- let fileUri = `${this.streamUuid}.${this.extension}`
261
- let mimeType = `audio/${this.extension}`
262
-
263
- if (compressedBlob && this.recordingConfig?.compression?.enabled) {
264
- const compressedUri = URL.createObjectURL(compressedBlob)
265
- compression = {
266
- compressedFileUri: compressedUri,
267
- size: compressedBlob.size,
268
- mimeType: 'audio/webm',
269
- format: 'opus',
270
- bitrate: this.recordingConfig.compression.bitrate ?? 128000,
271
- }
272
- // Use compressed values when compression is enabled
273
- fileUri = compressedUri
274
- mimeType = 'audio/webm'
275
- }
276
-
277
- this.logger?.debug(
278
- `[Stop] Completed stop process in ${performance.now() - startTime}ms`,
279
- {
280
- durationMs: this.currentDurationMs,
281
- compressedSize: compression?.size,
282
- }
283
- )
284
-
285
- // Use the stored streamUuid (which contains our custom filename) for the final filename
286
- const filename = `${this.streamUuid}.${this.extension}`
287
- const result: AudioRecording = {
288
- fileUri,
289
- filename, // This will now use our custom filename
290
- bitDepth: this.bitDepth,
291
- createdAt: this.recordingStartTime,
292
- channels: this.recordingConfig?.channels ?? 1,
293
- sampleRate: this.recordingConfig?.sampleRate ?? 44100,
294
- durationMs: this.currentDurationMs,
295
- size: this.currentSize,
296
- mimeType,
297
- compression,
298
- }
299
-
300
- // Reset after creating the result
301
- this.streamUuid = null
302
-
303
- return result
304
- } catch (error) {
305
- this.logger?.error('[Stop] Error stopping recording:', error)
306
- throw error
307
- }
308
- }
309
-
310
- // Pause recording
311
- async pauseRecording() {
312
- if (!this.isRecording || this.isPaused) {
313
- throw new Error('Recording is not active or already paused')
314
- }
315
-
316
- if (this.customRecorder) {
317
- this.customRecorder.pause()
318
- }
319
- this.isPaused = true
320
- this.pausedTime = Date.now()
321
- }
322
-
323
- // Resume recording
324
- async resumeRecording() {
325
- if (!this.isPaused) {
326
- throw new Error('Recording is not paused')
327
- }
328
-
329
- if (this.customRecorder) {
330
- this.customRecorder.resume()
331
- }
332
- this.isPaused = false
333
- this.recordingStartTime += Date.now() - this.pausedTime
334
- }
335
-
336
- // Get current status
337
- status() {
338
- const status: AudioStreamStatus = {
339
- isRecording: this.isRecording,
340
- isPaused: this.isPaused,
341
- durationMs: this.currentDurationMs,
342
- size: this.currentSize,
343
- interval: this.currentInterval,
344
- intervalAnalysis: this.currentIntervalAnalysis,
345
- mimeType: `audio/${this.extension}`,
346
- compression: this.recordingConfig?.compression?.enabled
347
- ? {
348
- size: this.totalCompressedSize,
349
- mimeType: 'audio/webm',
350
- format: this.recordingConfig.compression.format ?? 'opus',
351
- bitrate:
352
- this.recordingConfig.compression.bitrate ?? 128000,
353
- compressedFileUri: `${this.streamUuid}.webm`,
354
- }
355
- : undefined,
356
- }
357
- return status
358
- }
359
- }