@siteed/expo-audio-stream 1.8.0 → 1.9.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.
@@ -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