@siteed/expo-audio-stream 1.8.0 → 1.9.1

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.
@@ -69,7 +69,6 @@ export interface AudioRecording {
69
69
  bitDepth: BitDepth
70
70
  sampleRate: SampleRate
71
71
  transcripts?: TranscriberData[]
72
- wavPCMData?: Float32Array // Full PCM data for the recording in WAV format (only on web, for native use the fileUri)
73
72
  analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag
74
73
  compression?: CompressionInfo & {
75
74
  compressedFileUri: string
@@ -239,22 +238,9 @@ export interface WaveformConfig {
239
238
  height?: number // Height of the waveform view in dp (default: 64)
240
239
  }
241
240
 
242
- export interface WebRecordingOptions {
243
- /**
244
- * Web-specific option to skip the final audio data consolidation process.
245
- * When true, it will:
246
- * - Skip the time-consuming process of concatenating all audio chunks
247
- * - Return immediately with the compressed audio (if compression is enabled)
248
- * - Improve performance when stopping large recordings
249
- * - Useful when only the compressed audio is needed (e.g., when not using transcription)
250
- * @default false
251
- */
252
- skipFinalConsolidation?: boolean
253
- }
254
-
255
241
  export interface UseAudioRecorderState {
256
242
  startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>
257
- stopRecording: (options?: WebRecordingOptions) => Promise<AudioRecording | null>
243
+ stopRecording: () => Promise<AudioRecording | null>
258
244
  pauseRecording: () => Promise<void>
259
245
  resumeRecording: () => Promise<void>
260
246
  isRecording: boolean
@@ -9,12 +9,10 @@ import {
9
9
  ConsoleLike,
10
10
  RecordingConfig,
11
11
  StartRecordingResult,
12
- WebRecordingOptions,
13
12
  } from './ExpoAudioStream.types'
14
13
  import { WebRecorder } from './WebRecorder.web'
15
14
  import { AudioEventPayload } from './events'
16
15
  import { encodingToBitDepth } from './utils/encodingToBitDepth'
17
- import { writeWavHeader } from './utils/writeWavHeader'
18
16
 
19
17
  export interface EmitAudioEventProps {
20
18
  data: Float32Array
@@ -35,6 +33,7 @@ export interface ExpoAudioStreamWebProps {
35
33
  logger?: ConsoleLike
36
34
  audioWorkletUrl: string
37
35
  featuresExtratorUrl: string
36
+ maxBufferSize?: number // Maximum number of chunks to keep in memory
38
37
  }
39
38
 
40
39
  export class ExpoAudioStreamWeb extends LegacyEventEmitter {
@@ -59,11 +58,13 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
59
58
  logger?: ConsoleLike
60
59
  latestPosition: number = 0
61
60
  totalCompressedSize: number = 0
61
+ private readonly maxBufferSize: number
62
62
 
63
63
  constructor({
64
64
  audioWorkletUrl,
65
65
  featuresExtratorUrl,
66
66
  logger,
67
+ maxBufferSize = 100, // Default to storing last 100 chunks (1 chunk = 0.5 seconds)
67
68
  }: ExpoAudioStreamWebProps) {
68
69
  const mockNativeModule = {
69
70
  addListener: () => {
@@ -93,6 +94,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
93
94
  this.streamUuid = null // Initialize UUID on first recording start
94
95
  this.audioWorkletUrl = audioWorkletUrl
95
96
  this.featuresExtratorUrl = featuresExtratorUrl
97
+ this.maxBufferSize = maxBufferSize
96
98
  }
97
99
 
98
100
  // Utility to handle user media stream
@@ -134,7 +136,11 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
134
136
  position,
135
137
  compression,
136
138
  }: EmitAudioEventProps) => {
139
+ // Keep only the latest chunks based on maxBufferSize
137
140
  this.audioChunks.push(new Float32Array(data))
141
+ if (this.audioChunks.length > this.maxBufferSize) {
142
+ this.audioChunks.shift() // Remove oldest chunk
143
+ }
138
144
  this.currentSize += data.byteLength
139
145
  this.emitAudioEvent({ data, position, compression })
140
146
  this.lastEmittedTime = Date.now()
@@ -218,72 +224,63 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
218
224
  }
219
225
 
220
226
  // Stop recording
221
- async stopRecording(options?: WebRecordingOptions): Promise<AudioRecording> {
227
+ async stopRecording(): Promise<AudioRecording> {
222
228
  if (!this.customRecorder) {
223
229
  throw new Error('Recorder is not initialized')
224
230
  }
225
231
 
226
- // Create a promise to handle the PCM data processing
227
- return new Promise<AudioRecording>((resolve) => {
228
- // Use requestAnimationFrame to avoid blocking the UI
229
- requestAnimationFrame(() => {
230
- // Move the async work inside a self-executing async function
231
- (async () => {
232
- const { pcmData, compressedBlob } = await this.customRecorder!.stop(options)
232
+ this.logger?.debug('[Stop] Starting stop process')
233
+ const startTime = performance.now()
233
234
 
234
- this.logger?.debug(`Stopped recording`, pcmData)
235
- this.isRecording = false
236
- this.isPaused = false
237
- this.currentDurationMs = Date.now() - this.recordingStartTime
235
+ try {
236
+ this.logger?.debug('[Stop] Stopping recorder')
237
+ const { compressedBlob } = await this.customRecorder.stop()
238
238
 
239
- // Process in the next frame to avoid blocking
240
- requestAnimationFrame(() => {
241
- // Rest of the code remains the same
242
- const wavBuffer = writeWavHeader({
243
- buffer: pcmData.buffer,
244
- sampleRate: this.recordingConfig?.sampleRate ?? 44100,
245
- numChannels: this.recordingConfig?.channels ?? 1,
246
- bitDepth: this.bitDepth,
247
- })
239
+ this.isRecording = false
240
+ this.isPaused = false
241
+ this.currentDurationMs = Date.now() - this.recordingStartTime
248
242
 
249
- const cloneableBuffer = wavBuffer.slice(0)
243
+ let compression: AudioRecording['compression']
244
+ let fileUri = `${this.streamUuid}.${this.extension}`
245
+ let mimeType = `audio/${this.extension}`
250
246
 
251
- const blob = new Blob([cloneableBuffer], {
252
- type: `audio/${this.extension}`,
253
- })
254
- const fileUri = URL.createObjectURL(blob)
247
+ if (compressedBlob && this.recordingConfig?.compression?.enabled) {
248
+ const compressedUri = URL.createObjectURL(compressedBlob)
249
+ compression = {
250
+ compressedFileUri: compressedUri,
251
+ size: compressedBlob.size,
252
+ mimeType: 'audio/webm',
253
+ format: 'opus',
254
+ bitrate: this.recordingConfig.compression.bitrate ?? 128000,
255
+ }
256
+ // Use compressed values when compression is enabled
257
+ fileUri = compressedUri
258
+ mimeType = 'audio/webm'
259
+ }
255
260
 
256
- let compression: AudioRecording['compression']
257
- if (compressedBlob && this.recordingConfig?.compression?.enabled) {
258
- const compressedUri = URL.createObjectURL(compressedBlob)
259
- compression = {
260
- compressedFileUri: compressedUri,
261
- size: compressedBlob.size,
262
- mimeType: 'audio/webm',
263
- format: 'opus',
264
- bitrate: this.recordingConfig.compression.bitrate ?? 128000,
265
- }
266
- }
261
+ this.logger?.debug(
262
+ `[Stop] Completed stop process in ${performance.now() - startTime}ms`,
263
+ {
264
+ durationMs: this.currentDurationMs,
265
+ compressedSize: compression?.size,
266
+ }
267
+ )
267
268
 
268
- resolve({
269
- fileUri,
270
- filename: `${this.streamUuid}.${this.extension}`,
271
- wavPCMData: new Float32Array(cloneableBuffer),
272
- bitDepth: this.bitDepth,
273
- channels: this.recordingConfig?.channels ?? 1,
274
- sampleRate: this.recordingConfig?.sampleRate ?? 44100,
275
- durationMs: this.currentDurationMs,
276
- size: this.currentSize,
277
- mimeType: `audio/${this.extension}`,
278
- compression,
279
- })
280
- })
281
- })().catch((error) => {
282
- this.logger?.error('Error in stopRecording:', error)
283
- throw error
284
- })
285
- })
286
- })
269
+ return {
270
+ fileUri,
271
+ filename: `${this.streamUuid}.${this.extension}`,
272
+ bitDepth: this.bitDepth,
273
+ channels: this.recordingConfig?.channels ?? 1,
274
+ sampleRate: this.recordingConfig?.sampleRate ?? 44100,
275
+ durationMs: this.currentDurationMs,
276
+ size: this.currentSize,
277
+ mimeType,
278
+ compression,
279
+ }
280
+ } catch (error) {
281
+ this.logger?.error('[Stop] Error stopping recording:', error)
282
+ throw error
283
+ }
287
284
  }
288
285
 
289
286
  // Pause recording