@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.
- package/CHANGELOG.md +36 -1
- package/build/ExpoAudioStream.types.d.ts +1 -14
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +5 -3
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +52 -57
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/WebRecorder.web.d.ts +7 -5
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +150 -197
- package/build/WebRecorder.web.js.map +1 -1
- package/build/useAudioRecorder.d.ts +2 -2
- package/build/useAudioRecorder.d.ts.map +1 -1
- package/build/useAudioRecorder.js.map +1 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts +1 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -1
- package/build/workers/inlineAudioWebWorker.web.js +65 -160
- package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
- package/ios/AudioStreamManager.swift +39 -18
- package/package.json +1 -1
- package/src/ExpoAudioStream.types.ts +1 -15
- package/src/ExpoAudioStream.web.ts +55 -58
- package/src/WebRecorder.web.ts +182 -228
- package/src/useAudioRecorder.tsx +1 -2
- package/src/workers/inlineAudioWebWorker.web.tsx +65 -160
|
@@ -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: (
|
|
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(
|
|
227
|
+
async stopRecording(): Promise<AudioRecording> {
|
|
222
228
|
if (!this.customRecorder) {
|
|
223
229
|
throw new Error('Recorder is not initialized')
|
|
224
230
|
}
|
|
225
231
|
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
243
|
+
let compression: AudioRecording['compression']
|
|
244
|
+
let fileUri = `${this.streamUuid}.${this.extension}`
|
|
245
|
+
let mimeType = `audio/${this.extension}`
|
|
250
246
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|