@siteed/audio-studio 3.2.0 → 3.2.1-beta.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/README.md +30 -1
- package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +142 -12
- package/android/src/main/java/net/siteed/audiostudio/AudioRecordingService.kt +1 -1
- package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +5 -4
- package/android/src/main/java/net/siteed/audiostudio/Constants.kt +2 -1
- package/android/src/main/java/net/siteed/audiostudio/RecordingActionReceiver.kt +1 -1
- package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +5 -1
- package/build/cjs/AudioRecorder.provider.js +3 -37
- package/build/cjs/AudioRecorder.provider.js.map +1 -1
- package/build/cjs/AudioStudio.types.js.map +1 -1
- package/build/cjs/AudioStudio.web.js +125 -13
- package/build/cjs/AudioStudio.web.js.map +1 -1
- package/build/cjs/AudioStudioModule.js +6 -1
- package/build/cjs/AudioStudioModule.js.map +1 -1
- package/build/cjs/events.js +4 -0
- package/build/cjs/events.js.map +1 -1
- package/build/cjs/index.js +3 -1
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/useAudioRecorder.js +139 -4
- package/build/cjs/useAudioRecorder.js.map +1 -1
- package/build/esm/AudioRecorder.provider.js +3 -4
- package/build/esm/AudioRecorder.provider.js.map +1 -1
- package/build/esm/AudioStudio.types.js.map +1 -1
- package/build/esm/AudioStudio.web.js +125 -13
- package/build/esm/AudioStudio.web.js.map +1 -1
- package/build/esm/AudioStudioModule.js +6 -1
- package/build/esm/AudioStudioModule.js.map +1 -1
- package/build/esm/events.js +3 -0
- package/build/esm/events.js.map +1 -1
- package/build/esm/index.js +1 -0
- package/build/esm/index.js.map +1 -1
- package/build/esm/useAudioRecorder.js +140 -5
- package/build/esm/useAudioRecorder.js.map +1 -1
- package/build/types/AudioStudio.types.d.ts +44 -1
- package/build/types/AudioStudio.types.d.ts.map +1 -1
- package/build/types/AudioStudio.web.d.ts +17 -1
- package/build/types/AudioStudio.web.d.ts.map +1 -1
- package/build/types/AudioStudioModule.d.ts.map +1 -1
- package/build/types/events.d.ts +2 -1
- package/build/types/events.d.ts.map +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/useAudioRecorder.d.ts +2 -0
- package/build/types/useAudioRecorder.d.ts.map +1 -1
- package/ios/AudioStreamManager.swift +103 -9
- package/ios/AudioStreamManagerDelegate.swift +1 -0
- package/ios/AudioStudio.podspec +1 -1
- package/ios/AudioStudioModule.swift +6 -0
- package/ios/RecordingSettings.swift +48 -43
- package/package.json +163 -163
- package/plugin/tsconfig.json +8 -2
- package/src/AudioStudio.types.ts +48 -1
- package/src/AudioStudio.web.ts +152 -13
- package/src/AudioStudioModule.ts +6 -1
- package/src/events.ts +13 -1
- package/src/index.ts +1 -0
- package/src/useAudioRecorder.tsx +182 -2
- package/scripts/README.md +0 -58
|
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useReducer, useRef, useId } from 'react';
|
|
|
4
4
|
import { audioDeviceManager } from './AudioDeviceManager';
|
|
5
5
|
import AudioStudioModule from './AudioStudioModule';
|
|
6
6
|
import { validateRecordingConfig } from './constants/platformLimitations';
|
|
7
|
-
import { addAudioAnalysisListener, addAudioEventListener, addRecordingInterruptionListener, } from './events';
|
|
7
|
+
import { addAudioAnalysisListener, addAudioEventListener, addMaxDurationReachedListener, addRecordingInterruptionListener, } from './events';
|
|
8
8
|
import { cleanNativeOptions } from './utils/cleanNativeOptions';
|
|
9
9
|
const defaultAnalysis = {
|
|
10
10
|
segmentDurationMs: 100,
|
|
@@ -35,6 +35,8 @@ function audioRecorderReducer(state, action) {
|
|
|
35
35
|
size: 0,
|
|
36
36
|
compression: undefined,
|
|
37
37
|
analysisData: defaultAnalysis,
|
|
38
|
+
maxDurationMs: undefined,
|
|
39
|
+
maxDurationReached: false,
|
|
38
40
|
};
|
|
39
41
|
case 'STOP':
|
|
40
42
|
return {
|
|
@@ -45,6 +47,8 @@ function audioRecorderReducer(state, action) {
|
|
|
45
47
|
size: 0,
|
|
46
48
|
compression: undefined,
|
|
47
49
|
analysisData: undefined,
|
|
50
|
+
// Preserve max-duration state after stop so UI and agentic
|
|
51
|
+
// validation can explain why recording ended. START resets it.
|
|
48
52
|
};
|
|
49
53
|
case 'PAUSE':
|
|
50
54
|
return { ...state, isPaused: true, isRecording: false };
|
|
@@ -69,9 +73,17 @@ function audioRecorderReducer(state, action) {
|
|
|
69
73
|
format: action.payload.compression.format,
|
|
70
74
|
}
|
|
71
75
|
: undefined,
|
|
76
|
+
maxDurationMs: action.payload.maxDurationMs,
|
|
77
|
+
maxDurationReached: action.payload.maxDurationReached,
|
|
72
78
|
};
|
|
73
79
|
return newState;
|
|
74
80
|
}
|
|
81
|
+
case 'MAX_DURATION_REACHED':
|
|
82
|
+
return {
|
|
83
|
+
...state,
|
|
84
|
+
maxDurationMs: action.payload.maxDurationMs,
|
|
85
|
+
maxDurationReached: true,
|
|
86
|
+
};
|
|
75
87
|
case 'UPDATE_ANALYSIS':
|
|
76
88
|
return {
|
|
77
89
|
...state,
|
|
@@ -96,6 +108,8 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
96
108
|
size: 0,
|
|
97
109
|
compression: undefined,
|
|
98
110
|
analysisData: undefined,
|
|
111
|
+
maxDurationMs: undefined,
|
|
112
|
+
maxDurationReached: false,
|
|
99
113
|
});
|
|
100
114
|
const startResultRef = useRef(null);
|
|
101
115
|
const analysisListenerRef = useRef(null);
|
|
@@ -120,8 +134,11 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
120
134
|
durationMs: 0,
|
|
121
135
|
size: 0,
|
|
122
136
|
compression: undefined,
|
|
137
|
+
maxDurationMs: undefined,
|
|
138
|
+
maxDurationReached: false,
|
|
123
139
|
});
|
|
124
140
|
const recordingConfigRef = useRef(null);
|
|
141
|
+
const maxDurationHandledRef = useRef(false);
|
|
125
142
|
// Generate unique instance ID for debugging
|
|
126
143
|
const instanceId = useId().replace(/:/g, '').slice(0, 5);
|
|
127
144
|
const handleAudioAnalysis = useCallback(async ({ analysis, visualizationDuration, }) => {
|
|
@@ -287,10 +304,104 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
287
304
|
logger?.error(`Error processing audio event:`, error);
|
|
288
305
|
}
|
|
289
306
|
}, []);
|
|
307
|
+
const handleMaxDurationReached = useCallback(async (event) => {
|
|
308
|
+
if (maxDurationHandledRef.current) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
maxDurationHandledRef.current = true;
|
|
312
|
+
const config = recordingConfigRef.current;
|
|
313
|
+
const callbackEvent = {
|
|
314
|
+
...event,
|
|
315
|
+
autoStopped: event.autoStopped || !!config?.autoStopOnMaxDuration,
|
|
316
|
+
};
|
|
317
|
+
stateRef.current.maxDurationMs = callbackEvent.maxDurationMs;
|
|
318
|
+
stateRef.current.maxDurationReached = true;
|
|
319
|
+
dispatch({
|
|
320
|
+
type: 'MAX_DURATION_REACHED',
|
|
321
|
+
payload: callbackEvent,
|
|
322
|
+
});
|
|
323
|
+
try {
|
|
324
|
+
config?.onMaxDurationReached?.(callbackEvent);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
logger?.error(`Error in max duration callback:`, error);
|
|
328
|
+
}
|
|
329
|
+
const finishStoppedState = () => {
|
|
330
|
+
if (analysisListenerRef.current) {
|
|
331
|
+
analysisListenerRef.current.remove();
|
|
332
|
+
analysisListenerRef.current = null;
|
|
333
|
+
}
|
|
334
|
+
onAudioStreamRef.current = null;
|
|
335
|
+
stateRef.current.isRecording = false;
|
|
336
|
+
stateRef.current.isPaused = false;
|
|
337
|
+
dispatch({ type: 'STOP' });
|
|
338
|
+
};
|
|
339
|
+
const waitForPlatformAutoStop = async () => {
|
|
340
|
+
const timeoutMs = 3000;
|
|
341
|
+
const startedAt = Date.now();
|
|
342
|
+
let lastStatus;
|
|
343
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
344
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
345
|
+
try {
|
|
346
|
+
const currentStatus = audioStudio.status();
|
|
347
|
+
lastStatus = currentStatus;
|
|
348
|
+
if (!currentStatus.isRecording &&
|
|
349
|
+
!currentStatus.isPaused) {
|
|
350
|
+
finishStoppedState();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
logger?.warn(`Error checking status after max duration auto-stop:`, error);
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (lastStatus &&
|
|
360
|
+
(lastStatus.isRecording || lastStatus.isPaused)) {
|
|
361
|
+
try {
|
|
362
|
+
await audioStudio.stopRecording();
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
logger?.warn(`Error completing max duration auto-stop fallback:`, error);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// At this point platform-owned auto-stop did not settle cleanly.
|
|
369
|
+
// Clear hook state so the UI does not stay stuck as recording.
|
|
370
|
+
finishStoppedState();
|
|
371
|
+
};
|
|
372
|
+
// Only the original event tells us whether the platform already
|
|
373
|
+
// owns auto-stop. Keep stream callbacks alive until status confirms
|
|
374
|
+
// stop completion so native final audio flushes can still reach JS.
|
|
375
|
+
if (event.autoStopped && stateRef.current.isRecording) {
|
|
376
|
+
await waitForPlatformAutoStop();
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (config?.autoStopOnMaxDuration &&
|
|
380
|
+
!event.autoStopped &&
|
|
381
|
+
stateRef.current.isRecording) {
|
|
382
|
+
try {
|
|
383
|
+
await audioStudio.stopRecording();
|
|
384
|
+
finishStoppedState();
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
logger?.error(`Error auto-stopping on max duration:`, error);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}, [audioStudio, dispatch, logger]);
|
|
290
391
|
const checkStatus = useCallback(async () => {
|
|
291
392
|
try {
|
|
292
393
|
const status = audioStudio.status();
|
|
293
394
|
logger?.debug(`Status: paused: ${status.isPaused} isRecording: ${status.isRecording} durationMs: ${status.durationMs} size: ${status.size}`, status.compression);
|
|
395
|
+
if (status.maxDurationReached === true &&
|
|
396
|
+
status.maxDurationMs != null &&
|
|
397
|
+
!stateRef.current.maxDurationReached) {
|
|
398
|
+
await handleMaxDurationReached({
|
|
399
|
+
durationMs: status.durationMs,
|
|
400
|
+
maxDurationMs: status.maxDurationMs,
|
|
401
|
+
overrunMs: Math.max(0, status.durationMs - status.maxDurationMs),
|
|
402
|
+
autoStopped: false,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
294
405
|
// Only dispatch if values actually changed
|
|
295
406
|
if (status.isRecording !== stateRef.current.isRecording ||
|
|
296
407
|
status.isPaused !== stateRef.current.isPaused) {
|
|
@@ -305,16 +416,24 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
305
416
|
});
|
|
306
417
|
}
|
|
307
418
|
if (status.durationMs !== stateRef.current.durationMs ||
|
|
308
|
-
status.size !== stateRef.current.size
|
|
419
|
+
status.size !== stateRef.current.size ||
|
|
420
|
+
status.maxDurationMs !== stateRef.current.maxDurationMs ||
|
|
421
|
+
status.maxDurationReached !==
|
|
422
|
+
stateRef.current.maxDurationReached) {
|
|
309
423
|
stateRef.current.durationMs = status.durationMs;
|
|
310
424
|
stateRef.current.size = status.size;
|
|
311
425
|
stateRef.current.compression = status.compression;
|
|
426
|
+
stateRef.current.maxDurationMs = status.maxDurationMs;
|
|
427
|
+
stateRef.current.maxDurationReached =
|
|
428
|
+
status.maxDurationReached ?? false;
|
|
312
429
|
dispatch({
|
|
313
430
|
type: 'UPDATE_STATUS',
|
|
314
431
|
payload: {
|
|
315
432
|
durationMs: status.durationMs,
|
|
316
433
|
size: status.size,
|
|
317
434
|
compression: status.compression,
|
|
435
|
+
maxDurationMs: status.maxDurationMs,
|
|
436
|
+
maxDurationReached: status.maxDurationReached,
|
|
318
437
|
},
|
|
319
438
|
});
|
|
320
439
|
}
|
|
@@ -322,7 +441,7 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
322
441
|
catch (error) {
|
|
323
442
|
logger?.error(`Error getting status:`, error);
|
|
324
443
|
}
|
|
325
|
-
}, [audioStudio, logger]);
|
|
444
|
+
}, [audioStudio, handleMaxDurationReached, logger]);
|
|
326
445
|
// Update ref when state changes
|
|
327
446
|
useEffect(() => {
|
|
328
447
|
stateRef.current = {
|
|
@@ -331,6 +450,8 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
331
450
|
durationMs: state.durationMs,
|
|
332
451
|
size: state.size,
|
|
333
452
|
compression: state.compression,
|
|
453
|
+
maxDurationMs: state.maxDurationMs,
|
|
454
|
+
maxDurationReached: state.maxDurationReached ?? false,
|
|
334
455
|
};
|
|
335
456
|
}, [
|
|
336
457
|
state.isRecording,
|
|
@@ -338,6 +459,8 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
338
459
|
state.durationMs,
|
|
339
460
|
state.size,
|
|
340
461
|
state.compression,
|
|
462
|
+
state.maxDurationMs,
|
|
463
|
+
state.maxDurationReached,
|
|
341
464
|
]);
|
|
342
465
|
const startRecording = useCallback(async (recordingOptions) => {
|
|
343
466
|
// Validate the encoding configuration
|
|
@@ -356,10 +479,11 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
356
479
|
encoding: validationResult.encoding,
|
|
357
480
|
};
|
|
358
481
|
recordingConfigRef.current = validatedOptions;
|
|
482
|
+
maxDurationHandledRef.current = false;
|
|
359
483
|
logger?.debug(`start recording with validated config`, validatedOptions);
|
|
360
484
|
analysisRef.current = { ...defaultAnalysis }; // Reset analysis data
|
|
361
485
|
fullAnalysisRef.current = { ...defaultAnalysis };
|
|
362
|
-
const { onAudioStream, onRecordingInterrupted, onAudioAnalysis, keepFullAnalysis: _keepFullAnalysis, ...options } = validatedOptions;
|
|
486
|
+
const { onAudioStream, onRecordingInterrupted, onMaxDurationReached, onAudioAnalysis, keepFullAnalysis: _keepFullAnalysis, ...options } = validatedOptions;
|
|
363
487
|
const { enableProcessing } = options;
|
|
364
488
|
const maxRecentDataDuration = 10000; // TODO compute maxRecentDataDuration based on screen dimensions
|
|
365
489
|
if (typeof onAudioStream === 'function') {
|
|
@@ -396,7 +520,7 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
396
520
|
logger?.debug(`preparing recording`, recordingOptions);
|
|
397
521
|
analysisRef.current = { ...defaultAnalysis }; // Reset analysis data
|
|
398
522
|
fullAnalysisRef.current = { ...defaultAnalysis };
|
|
399
|
-
const { onAudioStream, onRecordingInterrupted, onAudioAnalysis, keepFullAnalysis: _keepFullAnalysis, ...options } = recordingOptions;
|
|
523
|
+
const { onAudioStream, onRecordingInterrupted, onMaxDurationReached, onAudioAnalysis, keepFullAnalysis: _keepFullAnalysis, ...options } = recordingOptions;
|
|
400
524
|
// Store onAudioStream for later use when recording starts
|
|
401
525
|
if (typeof onAudioStream === 'function') {
|
|
402
526
|
onAudioStreamRef.current = onAudioStream;
|
|
@@ -430,6 +554,7 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
430
554
|
onAudioStreamRef.current = null;
|
|
431
555
|
// Note: We deliberately DON'T clear recordingConfigRef here to preserve interruption callback
|
|
432
556
|
logger?.debug(`recording stopped`, stopResult);
|
|
557
|
+
maxDurationHandledRef.current = false;
|
|
433
558
|
dispatch({ type: 'STOP' });
|
|
434
559
|
return stopResult;
|
|
435
560
|
}, [dispatch]);
|
|
@@ -445,6 +570,14 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
445
570
|
dispatch({ type: 'RESUME' });
|
|
446
571
|
return resumeResult;
|
|
447
572
|
}, [dispatch]);
|
|
573
|
+
useEffect(() => {
|
|
574
|
+
const subscription = addMaxDurationReachedListener(async (event) => {
|
|
575
|
+
await handleMaxDurationReached(event);
|
|
576
|
+
});
|
|
577
|
+
return () => {
|
|
578
|
+
subscription.remove();
|
|
579
|
+
};
|
|
580
|
+
}, [handleMaxDurationReached]);
|
|
448
581
|
useEffect(() => {
|
|
449
582
|
let intervalId;
|
|
450
583
|
if (state.isRecording || state.isPaused) {
|
|
@@ -542,6 +675,8 @@ export function useAudioRecorder({ logger, audioWorkletUrl, featuresExtratorUrl,
|
|
|
542
675
|
size: state.size,
|
|
543
676
|
compression: state.compression,
|
|
544
677
|
analysisData: state.analysisData,
|
|
678
|
+
maxDurationMs: state.maxDurationMs,
|
|
679
|
+
maxDurationReached: state.maxDurationReached,
|
|
545
680
|
};
|
|
546
681
|
}
|
|
547
682
|
//# sourceMappingURL=useAudioRecorder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecorder.js","sourceRoot":"","sources":["../../src/useAudioRecorder.tsx"],"names":[],"mappings":"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","sourcesContent":["// src/useAudioRecorder.ts\nimport { EventSubscription, Platform } from 'expo-modules-core'\nimport { useCallback, useEffect, useReducer, useRef, useId } from 'react'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { audioDeviceManager } from './AudioDeviceManager'\nimport {\n AudioDataEvent,\n AudioRecording,\n AudioStreamStatus,\n CompressionInfo,\n ConsoleLike,\n RecordingConfig,\n StartRecordingResult,\n} from './AudioStudio.types'\nimport AudioStudioModule from './AudioStudioModule'\nimport { validateRecordingConfig } from './constants/platformLimitations'\nimport {\n addAudioAnalysisListener,\n addAudioEventListener,\n AudioEventPayload,\n addRecordingInterruptionListener,\n} from './events'\nimport { cleanNativeOptions } from './utils/cleanNativeOptions'\n\nexport interface UseAudioRecorderProps {\n logger?: ConsoleLike\n audioWorkletUrl?: string\n featuresExtratorUrl?: string\n}\n\nexport interface UseAudioRecorderState {\n prepareRecording: (_: RecordingConfig) => Promise<void>\n startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>\n stopRecording: () => Promise<AudioRecording>\n pauseRecording: () => Promise<void>\n resumeRecording: () => Promise<void>\n isRecording: boolean\n isPaused: boolean\n durationMs: number\n size: number\n compression?: CompressionInfo\n analysisData?: AudioAnalysis\n}\n\ninterface RecorderReducerState {\n isRecording: boolean\n isPaused: boolean\n durationMs: number\n size: number\n compression?: CompressionInfo\n analysisData?: AudioAnalysis\n}\n\ntype RecorderAction =\n | { type: 'START' | 'STOP' | 'PAUSE' | 'RESUME' }\n | {\n type: 'UPDATE_RECORDING_STATE'\n payload: {\n isRecording: boolean\n isPaused: boolean\n }\n }\n | {\n type: 'UPDATE_STATUS'\n payload: {\n durationMs: number\n size: number\n compression?: CompressionInfo\n }\n }\n | { type: 'UPDATE_ANALYSIS'; payload: AudioAnalysis }\n\nconst defaultAnalysis: AudioAnalysis = {\n segmentDurationMs: 100,\n bitDepth: 32,\n numberOfChannels: 1,\n durationMs: 0,\n sampleRate: 44100,\n samples: 0,\n dataPoints: [],\n rmsRange: {\n min: Number.POSITIVE_INFINITY,\n max: Number.NEGATIVE_INFINITY,\n },\n amplitudeRange: {\n min: Number.POSITIVE_INFINITY,\n max: Number.NEGATIVE_INFINITY,\n },\n extractionTimeMs: 0,\n}\n\nfunction audioRecorderReducer(\n state: RecorderReducerState,\n action: RecorderAction\n): RecorderReducerState {\n switch (action.type) {\n case 'START':\n return {\n ...state,\n isRecording: true,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined,\n analysisData: defaultAnalysis,\n }\n case 'STOP':\n return {\n ...state,\n isRecording: false,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined,\n analysisData: undefined,\n }\n case 'PAUSE':\n return { ...state, isPaused: true, isRecording: false }\n case 'RESUME':\n return { ...state, isPaused: false, isRecording: true }\n case 'UPDATE_RECORDING_STATE':\n return {\n ...state,\n isPaused: action.payload.isPaused,\n isRecording: action.payload.isRecording,\n }\n case 'UPDATE_STATUS': {\n const newState = {\n ...state,\n durationMs: action.payload.durationMs,\n size: action.payload.size,\n compression: action.payload.compression\n ? {\n size: action.payload.compression.size,\n mimeType: action.payload.compression.mimeType,\n bitrate: action.payload.compression.bitrate,\n format: action.payload.compression.format,\n }\n : undefined,\n }\n return newState\n }\n case 'UPDATE_ANALYSIS':\n return {\n ...state,\n analysisData: action.payload,\n }\n default:\n return state\n }\n}\n\ninterface HandleAudioAnalysisProps {\n analysis: AudioAnalysis\n visualizationDuration: number\n}\n\nfunction shouldKeepFullAnalysis(config?: RecordingConfig | null): boolean {\n return config?.keepFullAnalysis !== false\n}\n\nexport function useAudioRecorder({\n logger,\n audioWorkletUrl,\n featuresExtratorUrl,\n}: UseAudioRecorderProps = {}): UseAudioRecorderState {\n // Initialize AudioDeviceManager with logger (once)\n if (logger) {\n audioDeviceManager.setLogger(logger)\n }\n const [state, dispatch] = useReducer(audioRecorderReducer, {\n isRecording: false,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined,\n analysisData: undefined,\n })\n\n const startResultRef = useRef<StartRecordingResult | null>(null)\n\n const analysisListenerRef = useRef<EventSubscription | null>(null)\n // analysisRef is the current analysis data (last 10 seconds by default)\n const analysisRef = useRef<AudioAnalysis>({ ...defaultAnalysis })\n // fullAnalysisRef is the full analysis data (all data points)\n const fullAnalysisRef = useRef<AudioAnalysis>({\n ...defaultAnalysis,\n })\n\n // Instantiate the module for web with URLs\n const audioStudio =\n Platform.OS === 'web'\n ? AudioStudioModule({\n audioWorkletUrl,\n featuresExtratorUrl,\n logger,\n })\n : AudioStudioModule\n\n const onAudioStreamRef = useRef<\n ((_: AudioDataEvent) => Promise<void>) | null\n >(null)\n\n const stateRef = useRef({\n isRecording: false,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined as CompressionInfo | undefined,\n })\n\n const recordingConfigRef = useRef<RecordingConfig | null>(null)\n\n // Generate unique instance ID for debugging\n const instanceId = useId().replace(/:/g, '').slice(0, 5)\n\n const handleAudioAnalysis = useCallback(\n async ({\n analysis,\n visualizationDuration,\n }: HandleAudioAnalysisProps) => {\n const savedAnalysisData = analysisRef.current || {\n ...defaultAnalysis,\n }\n\n const maxDuration = visualizationDuration\n\n logger?.debug(\n `[handleAudioAnalysis] Received audio analysis: maxDuration=${maxDuration} analysis.dataPoints=${analysis.dataPoints.length} analysisData.dataPoints=${savedAnalysisData.dataPoints.length}`\n )\n\n // Combine data points\n const combinedDataPoints = [\n ...savedAnalysisData.dataPoints,\n ...analysis.dataPoints,\n ]\n\n const keepFullAnalysis = shouldKeepFullAnalysis(\n recordingConfigRef.current\n )\n const fullCombinedDataPoints = keepFullAnalysis\n ? [\n ...(fullAnalysisRef.current?.dataPoints ?? []),\n ...analysis.dataPoints,\n ]\n : undefined\n\n // Calculate the new duration\n // The number of segments is based on how many segments of segmentDurationMs can fit in visualizationDuration\n const numberOfSegments = Math.ceil(\n visualizationDuration / analysis.segmentDurationMs\n )\n // maxDataPoints should be the number of data points, not milliseconds\n const maxDataPoints = numberOfSegments\n\n logger?.debug(\n `[handleAudioAnalysis] Combined data points before trimming: numberOfSegments=${numberOfSegments} visualizationDuration=${visualizationDuration} combinedDataPointsLength=${combinedDataPoints.length} vs maxDataPoints=${maxDataPoints}`\n )\n\n // Trim data points to keep within the maximum number of data points\n if (combinedDataPoints.length > maxDataPoints) {\n combinedDataPoints.splice(\n 0,\n combinedDataPoints.length - maxDataPoints\n )\n }\n\n // Keep the full data points when requested for stopRecording().analysisData.\n if (keepFullAnalysis && fullCombinedDataPoints) {\n fullAnalysisRef.current = {\n ...fullAnalysisRef.current,\n dataPoints: fullCombinedDataPoints,\n }\n fullAnalysisRef.current.durationMs =\n fullCombinedDataPoints.length * analysis.segmentDurationMs\n }\n savedAnalysisData.dataPoints = combinedDataPoints\n savedAnalysisData.bitDepth =\n analysis.bitDepth || savedAnalysisData.bitDepth\n savedAnalysisData.durationMs =\n combinedDataPoints.length * analysis.segmentDurationMs\n\n // Update amplitude range\n const newMin = Math.min(\n savedAnalysisData.amplitudeRange.min,\n analysis.amplitudeRange.min\n )\n const newMax = Math.max(\n savedAnalysisData.amplitudeRange.max,\n analysis.amplitudeRange.max\n )\n\n savedAnalysisData.amplitudeRange = {\n min: newMin,\n max: newMax,\n }\n if (keepFullAnalysis) {\n fullAnalysisRef.current.amplitudeRange = {\n min: newMin,\n max: newMax,\n }\n }\n\n logger?.debug(\n `[handleAudioAnalysis] Updated analysis data: durationMs=${savedAnalysisData.durationMs}`,\n { dataPoints: savedAnalysisData.dataPoints.length }\n )\n\n // Call the onAudioAnalysis callback if it exists in the recording config\n if (recordingConfigRef.current?.onAudioAnalysis) {\n recordingConfigRef.current\n .onAudioAnalysis(analysis)\n .catch((error) => {\n logger?.warn(`Error processing audio analysis:`, error)\n })\n }\n\n // Update the ref\n analysisRef.current = savedAnalysisData\n\n // Dispatch the updated analysis data to state to trigger re-render\n dispatch({\n type: 'UPDATE_ANALYSIS',\n payload: { ...savedAnalysisData },\n })\n },\n [dispatch]\n )\n\n const handleAudioEvent = useCallback(\n async (eventData: AudioEventPayload) => {\n const {\n fileUri,\n deltaSize,\n totalSize,\n lastEmittedSize,\n position,\n streamUuid,\n encoded,\n pcmFloat32,\n mimeType,\n buffer,\n compression,\n } = eventData\n logger?.debug(`[handleAudioEvent] Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n position,\n mimeType,\n lastEmittedSize,\n streamUuid,\n encodedLength: encoded?.length,\n compression,\n })\n if (deltaSize === 0) {\n // Ignore packet with no data\n return\n }\n try {\n // Coming from native ( ios / android ) otherwise buffer is set\n if (Platform.OS !== 'web') {\n const compressionPayload =\n compression && startResultRef.current?.compression\n ? {\n data: compression.data,\n size: compression.totalSize,\n mimeType:\n startResultRef.current.compression\n ?.mimeType,\n bitrate:\n startResultRef.current.compression\n ?.bitrate,\n format: startResultRef.current.compression\n ?.format,\n }\n : undefined\n if (pcmFloat32 != null) {\n // Android new arch delivers Float32Array; iOS delivers number[] — normalize both\n const float32 =\n pcmFloat32 instanceof Float32Array\n ? pcmFloat32\n : new Float32Array(pcmFloat32 as number[])\n onAudioStreamRef.current?.({\n data: float32,\n streamFormat: 'float32',\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n compression: compressionPayload,\n })\n } else {\n if (!encoded) {\n logger?.error(`Encoded audio data is missing`)\n throw new Error('Encoded audio data is missing')\n }\n onAudioStreamRef.current?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n compression: compressionPayload,\n })\n }\n } else if (buffer) {\n // Coming from web\n const webEvent: AudioDataEvent = {\n data: buffer,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n compression:\n compression && startResultRef.current?.compression\n ? {\n data: compression.data,\n size: compression.totalSize,\n mimeType:\n startResultRef.current.compression\n ?.mimeType,\n bitrate:\n startResultRef.current.compression\n ?.bitrate,\n format: startResultRef.current.compression\n ?.format,\n }\n : undefined,\n }\n onAudioStreamRef.current?.(webEvent)\n logger?.debug(\n `[handleAudioEvent] Audio data sent to onAudioStream`,\n webEvent\n )\n }\n } catch (error) {\n logger?.error(`Error processing audio event:`, error)\n }\n },\n []\n )\n\n const checkStatus = useCallback(async () => {\n try {\n const status: AudioStreamStatus = audioStudio.status()\n logger?.debug(\n `Status: paused: ${status.isPaused} isRecording: ${status.isRecording} durationMs: ${status.durationMs} size: ${status.size}`,\n status.compression\n )\n\n // Only dispatch if values actually changed\n if (\n status.isRecording !== stateRef.current.isRecording ||\n status.isPaused !== stateRef.current.isPaused\n ) {\n stateRef.current.isRecording = status.isRecording\n stateRef.current.isPaused = status.isPaused\n dispatch({\n type: 'UPDATE_RECORDING_STATE',\n payload: {\n isRecording: status.isRecording,\n isPaused: status.isPaused,\n },\n })\n }\n\n if (\n status.durationMs !== stateRef.current.durationMs ||\n status.size !== stateRef.current.size\n ) {\n stateRef.current.durationMs = status.durationMs\n stateRef.current.size = status.size\n stateRef.current.compression = status.compression\n dispatch({\n type: 'UPDATE_STATUS',\n payload: {\n durationMs: status.durationMs,\n size: status.size,\n compression: status.compression,\n },\n })\n }\n } catch (error) {\n logger?.error(`Error getting status:`, error)\n }\n }, [audioStudio, logger]) // Only depend on audioStudio and logger\n\n // Update ref when state changes\n useEffect(() => {\n stateRef.current = {\n isRecording: state.isRecording,\n isPaused: state.isPaused,\n durationMs: state.durationMs,\n size: state.size,\n compression: state.compression,\n }\n }, [\n state.isRecording,\n state.isPaused,\n state.durationMs,\n state.size,\n state.compression,\n ])\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n // Validate the encoding configuration\n const validationResult = validateRecordingConfig({\n encoding: recordingOptions.encoding,\n })\n\n // Log warnings if any\n if (validationResult.warnings.length > 0) {\n validationResult.warnings.forEach((warning) => {\n logger?.warn(warning)\n })\n }\n\n // Update recording options with validated values\n const validatedOptions = {\n ...recordingOptions,\n encoding: validationResult.encoding,\n }\n\n recordingConfigRef.current = validatedOptions\n logger?.debug(\n `start recording with validated config`,\n validatedOptions\n )\n\n analysisRef.current = { ...defaultAnalysis } // Reset analysis data\n fullAnalysisRef.current = { ...defaultAnalysis }\n const {\n onAudioStream,\n onRecordingInterrupted,\n onAudioAnalysis,\n keepFullAnalysis: _keepFullAnalysis,\n ...options\n } = validatedOptions\n const { enableProcessing } = options\n\n const maxRecentDataDuration = 10000 // TODO compute maxRecentDataDuration based on screen dimensions\n if (typeof onAudioStream === 'function') {\n onAudioStreamRef.current = onAudioStream\n } else {\n logger?.warn(`onAudioStream is not a function`, onAudioStream)\n onAudioStreamRef.current = null\n }\n // Strip undefined values and functions that can't cross the native bridge\n const cleanOptions = cleanNativeOptions(options)\n const startResult: StartRecordingResult =\n await audioStudio.startRecording(cleanOptions)\n dispatch({ type: 'START' })\n\n startResultRef.current = startResult\n\n if (enableProcessing) {\n logger?.debug(`Enabling audio analysis listener`)\n const listener = addAudioAnalysisListener(\n async (analysisData) => {\n try {\n await handleAudioAnalysis({\n analysis: analysisData,\n visualizationDuration: maxRecentDataDuration,\n })\n } catch (error) {\n logger?.warn(\n `Error processing audio analysis:`,\n error\n )\n }\n }\n )\n\n analysisListenerRef.current = listener\n }\n\n return startResult\n },\n [handleAudioAnalysis, dispatch]\n )\n\n const prepareRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n recordingConfigRef.current = recordingOptions\n logger?.debug(`preparing recording`, recordingOptions)\n\n analysisRef.current = { ...defaultAnalysis } // Reset analysis data\n fullAnalysisRef.current = { ...defaultAnalysis }\n const {\n onAudioStream,\n onRecordingInterrupted,\n onAudioAnalysis,\n keepFullAnalysis: _keepFullAnalysis,\n ...options\n } = recordingOptions\n\n // Store onAudioStream for later use when recording starts\n if (typeof onAudioStream === 'function') {\n onAudioStreamRef.current = onAudioStream\n } else {\n logger?.warn(`onAudioStream is not a function`, onAudioStream)\n onAudioStreamRef.current = null\n }\n\n // Strip undefined values and functions that can't cross the native bridge\n const cleanOptions = cleanNativeOptions(options)\n // Call the native prepareRecording method\n await audioStudio.prepareRecording(cleanOptions)\n logger?.debug(`recording prepared successfully`)\n },\n []\n )\n\n const stopRecording = useCallback(async () => {\n logger?.debug(`stoping recording`)\n\n const stopResult: AudioRecording = await audioStudio.stopRecording()\n if (shouldKeepFullAnalysis(recordingConfigRef.current)) {\n stopResult.analysisData = fullAnalysisRef.current\n } else {\n // `keepFullAnalysis` is a hook-level retention policy. If a platform\n // starts returning native analysisData in the future, keep opt-out\n // semantics explicit and avoid leaking a full history here.\n delete stopResult.analysisData\n }\n\n if (analysisListenerRef.current) {\n analysisListenerRef.current.remove()\n analysisListenerRef.current = null\n }\n onAudioStreamRef.current = null\n\n // Note: We deliberately DON'T clear recordingConfigRef here to preserve interruption callback\n logger?.debug(`recording stopped`, stopResult)\n dispatch({ type: 'STOP' })\n return stopResult\n }, [dispatch])\n\n const pauseRecording = useCallback(async () => {\n logger?.debug(`pause recording`)\n const pauseResult = await audioStudio.pauseRecording()\n dispatch({ type: 'PAUSE' })\n return pauseResult\n }, [dispatch])\n\n const resumeRecording = useCallback(async () => {\n logger?.debug(`resume recording`)\n const resumeResult = await audioStudio.resumeRecording()\n dispatch({ type: 'RESUME' })\n return resumeResult\n }, [dispatch])\n\n useEffect(() => {\n let intervalId: ReturnType<typeof setInterval> | undefined\n\n if (state.isRecording || state.isPaused) {\n // Immediately check status when starting\n checkStatus()\n\n // Start interval\n intervalId = setInterval(checkStatus, 1000)\n }\n\n return () => {\n if (intervalId) {\n clearInterval(intervalId)\n intervalId = undefined\n }\n }\n }, [checkStatus, state.isRecording, state.isPaused])\n\n useEffect(() => {\n logger?.debug(`Registering audio event listener`)\n const subscribeAudio = addAudioEventListener(handleAudioEvent)\n\n logger?.debug(\n `Subscribed to audio event listener and analysis listener`,\n {\n subscribeAudio,\n }\n )\n\n return () => {\n logger?.debug(`Removing audio event listener`)\n subscribeAudio.remove()\n }\n }, [handleAudioEvent, handleAudioAnalysis])\n\n useEffect(() => {\n // Add event subscription for recording interruptions\n logger?.debug(\n `Setting up recording interruption listener [${instanceId}]`\n )\n\n const subscription = addRecordingInterruptionListener((event) => {\n logger?.debug(\n `[${instanceId}] Received recording interruption event:`,\n event\n )\n\n // Handle device disconnection for UI updates\n if (event.reason === 'deviceDisconnected') {\n logger?.debug(\n `[${instanceId}] Device disconnected - temporarily hiding last device from UI`\n )\n\n // Get current device list before the native layer updates\n const currentDevices = audioDeviceManager.getRawDevices()\n\n // Wait a moment for native layer to update, then compare\n setTimeout(async () => {\n try {\n // Get updated devices without notifying yet\n const updatedDevices =\n await audioDeviceManager.getAvailableDevices({\n refresh: true,\n })\n\n // Find missing devices by comparing lists\n const missingDevices = currentDevices.filter(\n (oldDevice) =>\n !updatedDevices.some(\n (newDevice) => newDevice.id === oldDevice.id\n )\n )\n\n if (missingDevices.length > 0) {\n // Mark all missing devices as disconnected (silently)\n missingDevices.forEach((missingDevice) => {\n logger?.debug(\n `[${instanceId}] Confirmed disconnected device: ${missingDevice.name} (${missingDevice.id})`\n )\n audioDeviceManager.markDeviceAsDisconnected(\n missingDevice.id,\n false\n )\n })\n }\n\n // Notify listeners once with the final filtered state\n audioDeviceManager.notifyListeners()\n } catch (error) {\n logger?.warn(\n `[${instanceId}] Error in delayed device disconnection handling:`,\n error\n )\n }\n }, 500) // 500ms delay to let native layer update\n } else if (event.reason === 'deviceConnected') {\n // Device reconnected - force refresh to show it immediately\n logger?.debug(\n `[${instanceId}] Device connected, forcing refresh`\n )\n audioDeviceManager.forceRefreshDevices()\n }\n\n // Check if we have a callback configured\n logger?.debug(\n `[${instanceId}] recordingConfigRef.current exists:`,\n !!recordingConfigRef.current\n )\n\n if (recordingConfigRef.current?.onRecordingInterrupted) {\n try {\n logger?.debug(\n `[${instanceId}] Calling recording interruption callback`\n )\n recordingConfigRef.current.onRecordingInterrupted(event)\n } catch (error) {\n logger?.error(\n `[${instanceId}] Error in recording interruption callback:`,\n error\n )\n }\n } else {\n logger?.debug(\n `[${instanceId}] No recording interruption callback configured`\n )\n }\n })\n\n return () => {\n logger?.debug(\n `[${instanceId}] Removing recording interruption listener`\n )\n subscription.remove()\n }\n }, [instanceId, logger]) // Include instanceId and logger in dependencies\n\n return {\n prepareRecording,\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n isPaused: state.isPaused,\n isRecording: state.isRecording,\n durationMs: state.durationMs,\n size: state.size,\n compression: state.compression,\n analysisData: state.analysisData,\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useAudioRecorder.js","sourceRoot":"","sources":["../../src/useAudioRecorder.tsx"],"names":[],"mappings":"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","sourcesContent":["// src/useAudioRecorder.ts\nimport { EventSubscription, Platform } from 'expo-modules-core'\nimport { useCallback, useEffect, useReducer, useRef, useId } from 'react'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { audioDeviceManager } from './AudioDeviceManager'\nimport {\n AudioDataEvent,\n AudioRecording,\n AudioStreamStatus,\n CompressionInfo,\n ConsoleLike,\n MaxDurationReachedEvent,\n RecordingConfig,\n StartRecordingResult,\n} from './AudioStudio.types'\nimport AudioStudioModule from './AudioStudioModule'\nimport { validateRecordingConfig } from './constants/platformLimitations'\nimport {\n addAudioAnalysisListener,\n addAudioEventListener,\n AudioEventPayload,\n addMaxDurationReachedListener,\n addRecordingInterruptionListener,\n} from './events'\nimport { cleanNativeOptions } from './utils/cleanNativeOptions'\n\nexport interface UseAudioRecorderProps {\n logger?: ConsoleLike\n audioWorkletUrl?: string\n featuresExtratorUrl?: string\n}\n\nexport interface UseAudioRecorderState {\n prepareRecording: (_: RecordingConfig) => Promise<void>\n startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>\n stopRecording: () => Promise<AudioRecording>\n pauseRecording: () => Promise<void>\n resumeRecording: () => Promise<void>\n isRecording: boolean\n isPaused: boolean\n durationMs: number\n size: number\n compression?: CompressionInfo\n analysisData?: AudioAnalysis\n maxDurationMs?: number\n maxDurationReached?: boolean\n}\n\ninterface RecorderReducerState {\n isRecording: boolean\n isPaused: boolean\n durationMs: number\n size: number\n compression?: CompressionInfo\n analysisData?: AudioAnalysis\n maxDurationMs?: number\n maxDurationReached?: boolean\n}\n\ntype RecorderAction =\n | { type: 'START' | 'STOP' | 'PAUSE' | 'RESUME' }\n | {\n type: 'UPDATE_RECORDING_STATE'\n payload: {\n isRecording: boolean\n isPaused: boolean\n }\n }\n | {\n type: 'UPDATE_STATUS'\n payload: {\n durationMs: number\n size: number\n compression?: CompressionInfo\n maxDurationMs?: number\n maxDurationReached?: boolean\n }\n }\n | {\n type: 'MAX_DURATION_REACHED'\n payload: MaxDurationReachedEvent\n }\n | { type: 'UPDATE_ANALYSIS'; payload: AudioAnalysis }\n\nconst defaultAnalysis: AudioAnalysis = {\n segmentDurationMs: 100,\n bitDepth: 32,\n numberOfChannels: 1,\n durationMs: 0,\n sampleRate: 44100,\n samples: 0,\n dataPoints: [],\n rmsRange: {\n min: Number.POSITIVE_INFINITY,\n max: Number.NEGATIVE_INFINITY,\n },\n amplitudeRange: {\n min: Number.POSITIVE_INFINITY,\n max: Number.NEGATIVE_INFINITY,\n },\n extractionTimeMs: 0,\n}\n\nfunction audioRecorderReducer(\n state: RecorderReducerState,\n action: RecorderAction\n): RecorderReducerState {\n switch (action.type) {\n case 'START':\n return {\n ...state,\n isRecording: true,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined,\n analysisData: defaultAnalysis,\n maxDurationMs: undefined,\n maxDurationReached: false,\n }\n case 'STOP':\n return {\n ...state,\n isRecording: false,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined,\n analysisData: undefined,\n // Preserve max-duration state after stop so UI and agentic\n // validation can explain why recording ended. START resets it.\n }\n case 'PAUSE':\n return { ...state, isPaused: true, isRecording: false }\n case 'RESUME':\n return { ...state, isPaused: false, isRecording: true }\n case 'UPDATE_RECORDING_STATE':\n return {\n ...state,\n isPaused: action.payload.isPaused,\n isRecording: action.payload.isRecording,\n }\n case 'UPDATE_STATUS': {\n const newState = {\n ...state,\n durationMs: action.payload.durationMs,\n size: action.payload.size,\n compression: action.payload.compression\n ? {\n size: action.payload.compression.size,\n mimeType: action.payload.compression.mimeType,\n bitrate: action.payload.compression.bitrate,\n format: action.payload.compression.format,\n }\n : undefined,\n maxDurationMs: action.payload.maxDurationMs,\n maxDurationReached: action.payload.maxDurationReached,\n }\n return newState\n }\n case 'MAX_DURATION_REACHED':\n return {\n ...state,\n maxDurationMs: action.payload.maxDurationMs,\n maxDurationReached: true,\n }\n case 'UPDATE_ANALYSIS':\n return {\n ...state,\n analysisData: action.payload,\n }\n default:\n return state\n }\n}\n\ninterface HandleAudioAnalysisProps {\n analysis: AudioAnalysis\n visualizationDuration: number\n}\n\nfunction shouldKeepFullAnalysis(config?: RecordingConfig | null): boolean {\n return config?.keepFullAnalysis !== false\n}\n\nexport function useAudioRecorder({\n logger,\n audioWorkletUrl,\n featuresExtratorUrl,\n}: UseAudioRecorderProps = {}): UseAudioRecorderState {\n // Initialize AudioDeviceManager with logger (once)\n if (logger) {\n audioDeviceManager.setLogger(logger)\n }\n const [state, dispatch] = useReducer(audioRecorderReducer, {\n isRecording: false,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined,\n analysisData: undefined,\n maxDurationMs: undefined,\n maxDurationReached: false,\n })\n\n const startResultRef = useRef<StartRecordingResult | null>(null)\n\n const analysisListenerRef = useRef<EventSubscription | null>(null)\n // analysisRef is the current analysis data (last 10 seconds by default)\n const analysisRef = useRef<AudioAnalysis>({ ...defaultAnalysis })\n // fullAnalysisRef is the full analysis data (all data points)\n const fullAnalysisRef = useRef<AudioAnalysis>({\n ...defaultAnalysis,\n })\n\n // Instantiate the module for web with URLs\n const audioStudio =\n Platform.OS === 'web'\n ? AudioStudioModule({\n audioWorkletUrl,\n featuresExtratorUrl,\n logger,\n })\n : AudioStudioModule\n\n const onAudioStreamRef = useRef<\n ((_: AudioDataEvent) => Promise<void>) | null\n >(null)\n\n const stateRef = useRef({\n isRecording: false,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined as CompressionInfo | undefined,\n maxDurationMs: undefined as number | undefined,\n maxDurationReached: false,\n })\n\n const recordingConfigRef = useRef<RecordingConfig | null>(null)\n const maxDurationHandledRef = useRef(false)\n\n // Generate unique instance ID for debugging\n const instanceId = useId().replace(/:/g, '').slice(0, 5)\n\n const handleAudioAnalysis = useCallback(\n async ({\n analysis,\n visualizationDuration,\n }: HandleAudioAnalysisProps) => {\n const savedAnalysisData = analysisRef.current || {\n ...defaultAnalysis,\n }\n\n const maxDuration = visualizationDuration\n\n logger?.debug(\n `[handleAudioAnalysis] Received audio analysis: maxDuration=${maxDuration} analysis.dataPoints=${analysis.dataPoints.length} analysisData.dataPoints=${savedAnalysisData.dataPoints.length}`\n )\n\n // Combine data points\n const combinedDataPoints = [\n ...savedAnalysisData.dataPoints,\n ...analysis.dataPoints,\n ]\n\n const keepFullAnalysis = shouldKeepFullAnalysis(\n recordingConfigRef.current\n )\n const fullCombinedDataPoints = keepFullAnalysis\n ? [\n ...(fullAnalysisRef.current?.dataPoints ?? []),\n ...analysis.dataPoints,\n ]\n : undefined\n\n // Calculate the new duration\n // The number of segments is based on how many segments of segmentDurationMs can fit in visualizationDuration\n const numberOfSegments = Math.ceil(\n visualizationDuration / analysis.segmentDurationMs\n )\n // maxDataPoints should be the number of data points, not milliseconds\n const maxDataPoints = numberOfSegments\n\n logger?.debug(\n `[handleAudioAnalysis] Combined data points before trimming: numberOfSegments=${numberOfSegments} visualizationDuration=${visualizationDuration} combinedDataPointsLength=${combinedDataPoints.length} vs maxDataPoints=${maxDataPoints}`\n )\n\n // Trim data points to keep within the maximum number of data points\n if (combinedDataPoints.length > maxDataPoints) {\n combinedDataPoints.splice(\n 0,\n combinedDataPoints.length - maxDataPoints\n )\n }\n\n // Keep the full data points when requested for stopRecording().analysisData.\n if (keepFullAnalysis && fullCombinedDataPoints) {\n fullAnalysisRef.current = {\n ...fullAnalysisRef.current,\n dataPoints: fullCombinedDataPoints,\n }\n fullAnalysisRef.current.durationMs =\n fullCombinedDataPoints.length * analysis.segmentDurationMs\n }\n savedAnalysisData.dataPoints = combinedDataPoints\n savedAnalysisData.bitDepth =\n analysis.bitDepth || savedAnalysisData.bitDepth\n savedAnalysisData.durationMs =\n combinedDataPoints.length * analysis.segmentDurationMs\n\n // Update amplitude range\n const newMin = Math.min(\n savedAnalysisData.amplitudeRange.min,\n analysis.amplitudeRange.min\n )\n const newMax = Math.max(\n savedAnalysisData.amplitudeRange.max,\n analysis.amplitudeRange.max\n )\n\n savedAnalysisData.amplitudeRange = {\n min: newMin,\n max: newMax,\n }\n if (keepFullAnalysis) {\n fullAnalysisRef.current.amplitudeRange = {\n min: newMin,\n max: newMax,\n }\n }\n\n logger?.debug(\n `[handleAudioAnalysis] Updated analysis data: durationMs=${savedAnalysisData.durationMs}`,\n { dataPoints: savedAnalysisData.dataPoints.length }\n )\n\n // Call the onAudioAnalysis callback if it exists in the recording config\n if (recordingConfigRef.current?.onAudioAnalysis) {\n recordingConfigRef.current\n .onAudioAnalysis(analysis)\n .catch((error) => {\n logger?.warn(`Error processing audio analysis:`, error)\n })\n }\n\n // Update the ref\n analysisRef.current = savedAnalysisData\n\n // Dispatch the updated analysis data to state to trigger re-render\n dispatch({\n type: 'UPDATE_ANALYSIS',\n payload: { ...savedAnalysisData },\n })\n },\n [dispatch]\n )\n\n const handleAudioEvent = useCallback(\n async (eventData: AudioEventPayload) => {\n const {\n fileUri,\n deltaSize,\n totalSize,\n lastEmittedSize,\n position,\n streamUuid,\n encoded,\n pcmFloat32,\n mimeType,\n buffer,\n compression,\n } = eventData\n logger?.debug(`[handleAudioEvent] Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n position,\n mimeType,\n lastEmittedSize,\n streamUuid,\n encodedLength: encoded?.length,\n compression,\n })\n if (deltaSize === 0) {\n // Ignore packet with no data\n return\n }\n try {\n // Coming from native ( ios / android ) otherwise buffer is set\n if (Platform.OS !== 'web') {\n const compressionPayload =\n compression && startResultRef.current?.compression\n ? {\n data: compression.data,\n size: compression.totalSize,\n mimeType:\n startResultRef.current.compression\n ?.mimeType,\n bitrate:\n startResultRef.current.compression\n ?.bitrate,\n format: startResultRef.current.compression\n ?.format,\n }\n : undefined\n if (pcmFloat32 != null) {\n // Android new arch delivers Float32Array; iOS delivers number[] — normalize both\n const float32 =\n pcmFloat32 instanceof Float32Array\n ? pcmFloat32\n : new Float32Array(pcmFloat32 as number[])\n onAudioStreamRef.current?.({\n data: float32,\n streamFormat: 'float32',\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n compression: compressionPayload,\n })\n } else {\n if (!encoded) {\n logger?.error(`Encoded audio data is missing`)\n throw new Error('Encoded audio data is missing')\n }\n onAudioStreamRef.current?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n compression: compressionPayload,\n })\n }\n } else if (buffer) {\n // Coming from web\n const webEvent: AudioDataEvent = {\n data: buffer,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n compression:\n compression && startResultRef.current?.compression\n ? {\n data: compression.data,\n size: compression.totalSize,\n mimeType:\n startResultRef.current.compression\n ?.mimeType,\n bitrate:\n startResultRef.current.compression\n ?.bitrate,\n format: startResultRef.current.compression\n ?.format,\n }\n : undefined,\n }\n onAudioStreamRef.current?.(webEvent)\n logger?.debug(\n `[handleAudioEvent] Audio data sent to onAudioStream`,\n webEvent\n )\n }\n } catch (error) {\n logger?.error(`Error processing audio event:`, error)\n }\n },\n []\n )\n\n const handleMaxDurationReached = useCallback(\n async (event: MaxDurationReachedEvent) => {\n if (maxDurationHandledRef.current) {\n return\n }\n\n maxDurationHandledRef.current = true\n const config = recordingConfigRef.current\n const callbackEvent: MaxDurationReachedEvent = {\n ...event,\n autoStopped:\n event.autoStopped || !!config?.autoStopOnMaxDuration,\n }\n\n stateRef.current.maxDurationMs = callbackEvent.maxDurationMs\n stateRef.current.maxDurationReached = true\n dispatch({\n type: 'MAX_DURATION_REACHED',\n payload: callbackEvent,\n })\n\n try {\n config?.onMaxDurationReached?.(callbackEvent)\n } catch (error) {\n logger?.error(`Error in max duration callback:`, error)\n }\n\n const finishStoppedState = () => {\n if (analysisListenerRef.current) {\n analysisListenerRef.current.remove()\n analysisListenerRef.current = null\n }\n onAudioStreamRef.current = null\n stateRef.current.isRecording = false\n stateRef.current.isPaused = false\n dispatch({ type: 'STOP' })\n }\n\n const waitForPlatformAutoStop = async () => {\n const timeoutMs = 3000\n const startedAt = Date.now()\n let lastStatus: AudioStreamStatus | undefined\n\n while (Date.now() - startedAt < timeoutMs) {\n await new Promise((resolve) => setTimeout(resolve, 50))\n try {\n const currentStatus: AudioStreamStatus =\n audioStudio.status()\n lastStatus = currentStatus\n if (\n !currentStatus.isRecording &&\n !currentStatus.isPaused\n ) {\n finishStoppedState()\n return\n }\n } catch (error) {\n logger?.warn(\n `Error checking status after max duration auto-stop:`,\n error\n )\n break\n }\n }\n\n if (\n lastStatus &&\n (lastStatus.isRecording || lastStatus.isPaused)\n ) {\n try {\n await audioStudio.stopRecording()\n } catch (error) {\n logger?.warn(\n `Error completing max duration auto-stop fallback:`,\n error\n )\n }\n }\n // At this point platform-owned auto-stop did not settle cleanly.\n // Clear hook state so the UI does not stay stuck as recording.\n finishStoppedState()\n }\n\n // Only the original event tells us whether the platform already\n // owns auto-stop. Keep stream callbacks alive until status confirms\n // stop completion so native final audio flushes can still reach JS.\n if (event.autoStopped && stateRef.current.isRecording) {\n await waitForPlatformAutoStop()\n return\n }\n\n if (\n config?.autoStopOnMaxDuration &&\n !event.autoStopped &&\n stateRef.current.isRecording\n ) {\n try {\n await audioStudio.stopRecording()\n finishStoppedState()\n } catch (error) {\n logger?.error(`Error auto-stopping on max duration:`, error)\n }\n }\n },\n [audioStudio, dispatch, logger]\n )\n\n const checkStatus = useCallback(async () => {\n try {\n const status: AudioStreamStatus = audioStudio.status()\n logger?.debug(\n `Status: paused: ${status.isPaused} isRecording: ${status.isRecording} durationMs: ${status.durationMs} size: ${status.size}`,\n status.compression\n )\n\n if (\n status.maxDurationReached === true &&\n status.maxDurationMs != null &&\n !stateRef.current.maxDurationReached\n ) {\n await handleMaxDurationReached({\n durationMs: status.durationMs,\n maxDurationMs: status.maxDurationMs,\n overrunMs: Math.max(\n 0,\n status.durationMs - status.maxDurationMs\n ),\n autoStopped: false,\n })\n }\n\n // Only dispatch if values actually changed\n if (\n status.isRecording !== stateRef.current.isRecording ||\n status.isPaused !== stateRef.current.isPaused\n ) {\n stateRef.current.isRecording = status.isRecording\n stateRef.current.isPaused = status.isPaused\n dispatch({\n type: 'UPDATE_RECORDING_STATE',\n payload: {\n isRecording: status.isRecording,\n isPaused: status.isPaused,\n },\n })\n }\n\n if (\n status.durationMs !== stateRef.current.durationMs ||\n status.size !== stateRef.current.size ||\n status.maxDurationMs !== stateRef.current.maxDurationMs ||\n status.maxDurationReached !==\n stateRef.current.maxDurationReached\n ) {\n stateRef.current.durationMs = status.durationMs\n stateRef.current.size = status.size\n stateRef.current.compression = status.compression\n stateRef.current.maxDurationMs = status.maxDurationMs\n stateRef.current.maxDurationReached =\n status.maxDurationReached ?? false\n dispatch({\n type: 'UPDATE_STATUS',\n payload: {\n durationMs: status.durationMs,\n size: status.size,\n compression: status.compression,\n maxDurationMs: status.maxDurationMs,\n maxDurationReached: status.maxDurationReached,\n },\n })\n }\n } catch (error) {\n logger?.error(`Error getting status:`, error)\n }\n }, [audioStudio, handleMaxDurationReached, logger])\n\n // Update ref when state changes\n useEffect(() => {\n stateRef.current = {\n isRecording: state.isRecording,\n isPaused: state.isPaused,\n durationMs: state.durationMs,\n size: state.size,\n compression: state.compression,\n maxDurationMs: state.maxDurationMs,\n maxDurationReached: state.maxDurationReached ?? false,\n }\n }, [\n state.isRecording,\n state.isPaused,\n state.durationMs,\n state.size,\n state.compression,\n state.maxDurationMs,\n state.maxDurationReached,\n ])\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n // Validate the encoding configuration\n const validationResult = validateRecordingConfig({\n encoding: recordingOptions.encoding,\n })\n\n // Log warnings if any\n if (validationResult.warnings.length > 0) {\n validationResult.warnings.forEach((warning) => {\n logger?.warn(warning)\n })\n }\n\n // Update recording options with validated values\n const validatedOptions = {\n ...recordingOptions,\n encoding: validationResult.encoding,\n }\n\n recordingConfigRef.current = validatedOptions\n maxDurationHandledRef.current = false\n logger?.debug(\n `start recording with validated config`,\n validatedOptions\n )\n\n analysisRef.current = { ...defaultAnalysis } // Reset analysis data\n fullAnalysisRef.current = { ...defaultAnalysis }\n const {\n onAudioStream,\n onRecordingInterrupted,\n onMaxDurationReached,\n onAudioAnalysis,\n keepFullAnalysis: _keepFullAnalysis,\n ...options\n } = validatedOptions\n const { enableProcessing } = options\n\n const maxRecentDataDuration = 10000 // TODO compute maxRecentDataDuration based on screen dimensions\n if (typeof onAudioStream === 'function') {\n onAudioStreamRef.current = onAudioStream\n } else {\n logger?.warn(`onAudioStream is not a function`, onAudioStream)\n onAudioStreamRef.current = null\n }\n // Strip undefined values and functions that can't cross the native bridge\n const cleanOptions = cleanNativeOptions(options)\n const startResult: StartRecordingResult =\n await audioStudio.startRecording(cleanOptions)\n dispatch({ type: 'START' })\n\n startResultRef.current = startResult\n\n if (enableProcessing) {\n logger?.debug(`Enabling audio analysis listener`)\n const listener = addAudioAnalysisListener(\n async (analysisData) => {\n try {\n await handleAudioAnalysis({\n analysis: analysisData,\n visualizationDuration: maxRecentDataDuration,\n })\n } catch (error) {\n logger?.warn(\n `Error processing audio analysis:`,\n error\n )\n }\n }\n )\n\n analysisListenerRef.current = listener\n }\n\n return startResult\n },\n [handleAudioAnalysis, dispatch]\n )\n\n const prepareRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n recordingConfigRef.current = recordingOptions\n logger?.debug(`preparing recording`, recordingOptions)\n\n analysisRef.current = { ...defaultAnalysis } // Reset analysis data\n fullAnalysisRef.current = { ...defaultAnalysis }\n const {\n onAudioStream,\n onRecordingInterrupted,\n onMaxDurationReached,\n onAudioAnalysis,\n keepFullAnalysis: _keepFullAnalysis,\n ...options\n } = recordingOptions\n\n // Store onAudioStream for later use when recording starts\n if (typeof onAudioStream === 'function') {\n onAudioStreamRef.current = onAudioStream\n } else {\n logger?.warn(`onAudioStream is not a function`, onAudioStream)\n onAudioStreamRef.current = null\n }\n\n // Strip undefined values and functions that can't cross the native bridge\n const cleanOptions = cleanNativeOptions(options)\n // Call the native prepareRecording method\n await audioStudio.prepareRecording(cleanOptions)\n logger?.debug(`recording prepared successfully`)\n },\n []\n )\n\n const stopRecording = useCallback(async () => {\n logger?.debug(`stoping recording`)\n\n const stopResult: AudioRecording = await audioStudio.stopRecording()\n if (shouldKeepFullAnalysis(recordingConfigRef.current)) {\n stopResult.analysisData = fullAnalysisRef.current\n } else {\n // `keepFullAnalysis` is a hook-level retention policy. If a platform\n // starts returning native analysisData in the future, keep opt-out\n // semantics explicit and avoid leaking a full history here.\n delete stopResult.analysisData\n }\n\n if (analysisListenerRef.current) {\n analysisListenerRef.current.remove()\n analysisListenerRef.current = null\n }\n onAudioStreamRef.current = null\n\n // Note: We deliberately DON'T clear recordingConfigRef here to preserve interruption callback\n logger?.debug(`recording stopped`, stopResult)\n maxDurationHandledRef.current = false\n dispatch({ type: 'STOP' })\n return stopResult\n }, [dispatch])\n\n const pauseRecording = useCallback(async () => {\n logger?.debug(`pause recording`)\n const pauseResult = await audioStudio.pauseRecording()\n dispatch({ type: 'PAUSE' })\n return pauseResult\n }, [dispatch])\n\n const resumeRecording = useCallback(async () => {\n logger?.debug(`resume recording`)\n const resumeResult = await audioStudio.resumeRecording()\n dispatch({ type: 'RESUME' })\n return resumeResult\n }, [dispatch])\n\n useEffect(() => {\n const subscription = addMaxDurationReachedListener(async (event) => {\n await handleMaxDurationReached(event)\n })\n\n return () => {\n subscription.remove()\n }\n }, [handleMaxDurationReached])\n\n useEffect(() => {\n let intervalId: ReturnType<typeof setInterval> | undefined\n\n if (state.isRecording || state.isPaused) {\n // Immediately check status when starting\n checkStatus()\n\n // Start interval\n intervalId = setInterval(checkStatus, 1000)\n }\n\n return () => {\n if (intervalId) {\n clearInterval(intervalId)\n intervalId = undefined\n }\n }\n }, [checkStatus, state.isRecording, state.isPaused])\n\n useEffect(() => {\n logger?.debug(`Registering audio event listener`)\n const subscribeAudio = addAudioEventListener(handleAudioEvent)\n\n logger?.debug(\n `Subscribed to audio event listener and analysis listener`,\n {\n subscribeAudio,\n }\n )\n\n return () => {\n logger?.debug(`Removing audio event listener`)\n subscribeAudio.remove()\n }\n }, [handleAudioEvent, handleAudioAnalysis])\n\n useEffect(() => {\n // Add event subscription for recording interruptions\n logger?.debug(\n `Setting up recording interruption listener [${instanceId}]`\n )\n\n const subscription = addRecordingInterruptionListener((event) => {\n logger?.debug(\n `[${instanceId}] Received recording interruption event:`,\n event\n )\n\n // Handle device disconnection for UI updates\n if (event.reason === 'deviceDisconnected') {\n logger?.debug(\n `[${instanceId}] Device disconnected - temporarily hiding last device from UI`\n )\n\n // Get current device list before the native layer updates\n const currentDevices = audioDeviceManager.getRawDevices()\n\n // Wait a moment for native layer to update, then compare\n setTimeout(async () => {\n try {\n // Get updated devices without notifying yet\n const updatedDevices =\n await audioDeviceManager.getAvailableDevices({\n refresh: true,\n })\n\n // Find missing devices by comparing lists\n const missingDevices = currentDevices.filter(\n (oldDevice) =>\n !updatedDevices.some(\n (newDevice) => newDevice.id === oldDevice.id\n )\n )\n\n if (missingDevices.length > 0) {\n // Mark all missing devices as disconnected (silently)\n missingDevices.forEach((missingDevice) => {\n logger?.debug(\n `[${instanceId}] Confirmed disconnected device: ${missingDevice.name} (${missingDevice.id})`\n )\n audioDeviceManager.markDeviceAsDisconnected(\n missingDevice.id,\n false\n )\n })\n }\n\n // Notify listeners once with the final filtered state\n audioDeviceManager.notifyListeners()\n } catch (error) {\n logger?.warn(\n `[${instanceId}] Error in delayed device disconnection handling:`,\n error\n )\n }\n }, 500) // 500ms delay to let native layer update\n } else if (event.reason === 'deviceConnected') {\n // Device reconnected - force refresh to show it immediately\n logger?.debug(\n `[${instanceId}] Device connected, forcing refresh`\n )\n audioDeviceManager.forceRefreshDevices()\n }\n\n // Check if we have a callback configured\n logger?.debug(\n `[${instanceId}] recordingConfigRef.current exists:`,\n !!recordingConfigRef.current\n )\n\n if (recordingConfigRef.current?.onRecordingInterrupted) {\n try {\n logger?.debug(\n `[${instanceId}] Calling recording interruption callback`\n )\n recordingConfigRef.current.onRecordingInterrupted(event)\n } catch (error) {\n logger?.error(\n `[${instanceId}] Error in recording interruption callback:`,\n error\n )\n }\n } else {\n logger?.debug(\n `[${instanceId}] No recording interruption callback configured`\n )\n }\n })\n\n return () => {\n logger?.debug(\n `[${instanceId}] Removing recording interruption listener`\n )\n subscription.remove()\n }\n }, [instanceId, logger]) // Include instanceId and logger in dependencies\n\n return {\n prepareRecording,\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n isPaused: state.isPaused,\n isRecording: state.isRecording,\n durationMs: state.durationMs,\n size: state.size,\n compression: state.compression,\n analysisData: state.analysisData,\n maxDurationMs: state.maxDurationMs,\n maxDurationReached: state.maxDurationReached,\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AudioAnalysis, AudioFeaturesOptions, DecodingConfig } from './AudioAnalysis/AudioAnalysis.types';
|
|
2
|
-
import { AudioAnalysisEvent } from './events';
|
|
2
|
+
import type { AudioAnalysisEvent } from './events';
|
|
3
3
|
export interface CompressionInfo {
|
|
4
4
|
/** Size of the compressed audio data in bytes */
|
|
5
5
|
size: number;
|
|
@@ -29,6 +29,10 @@ export interface AudioStreamStatus {
|
|
|
29
29
|
mimeType: string;
|
|
30
30
|
/** Information about audio compression if enabled */
|
|
31
31
|
compression?: CompressionInfo;
|
|
32
|
+
/** Configured maximum active recording duration in milliseconds, if enabled */
|
|
33
|
+
maxDurationMs?: number;
|
|
34
|
+
/** Whether the current recording session has reached the configured maximum duration */
|
|
35
|
+
maxDurationReached?: boolean;
|
|
32
36
|
}
|
|
33
37
|
interface AudioDataEventBase {
|
|
34
38
|
/** Current position in the audio stream in bytes */
|
|
@@ -169,6 +173,18 @@ export interface StartRecordingResult {
|
|
|
169
173
|
compressedFileUri: string;
|
|
170
174
|
};
|
|
171
175
|
}
|
|
176
|
+
export interface MaxDurationReachedEvent {
|
|
177
|
+
/** Active recording duration that triggered the event, in milliseconds */
|
|
178
|
+
durationMs: number;
|
|
179
|
+
/** Configured active recording duration limit, in milliseconds */
|
|
180
|
+
maxDurationMs: number;
|
|
181
|
+
/** Amount by which timer delivery exceeded the limit, in milliseconds */
|
|
182
|
+
overrunMs: number;
|
|
183
|
+
/** Active stream identifier when available */
|
|
184
|
+
streamUuid?: string;
|
|
185
|
+
/** Whether the recorder was configured to stop automatically after this event */
|
|
186
|
+
autoStopped: boolean;
|
|
187
|
+
}
|
|
172
188
|
export interface AudioSessionConfig {
|
|
173
189
|
/**
|
|
174
190
|
* Audio session category that defines the audio behavior
|
|
@@ -396,6 +412,27 @@ export interface RecordingConfig {
|
|
|
396
412
|
autoResumeAfterInterruption?: boolean;
|
|
397
413
|
/** Optional callback to handle recording interruptions */
|
|
398
414
|
onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void;
|
|
415
|
+
/**
|
|
416
|
+
* Maximum cumulative active recording duration, in milliseconds.
|
|
417
|
+
*
|
|
418
|
+
* Paused time does not count. Set to undefined, 0, or a negative value to disable.
|
|
419
|
+
*/
|
|
420
|
+
maxDurationMs?: number;
|
|
421
|
+
/**
|
|
422
|
+
* Stop recording automatically when maxDurationMs is reached.
|
|
423
|
+
*
|
|
424
|
+
* Defaults to false. The MaxDurationReached event is emitted before the stop request.
|
|
425
|
+
* The automatic stop result is not returned to onMaxDurationReached; use the
|
|
426
|
+
* event and stream callbacks for immediate UI updates.
|
|
427
|
+
*/
|
|
428
|
+
autoStopOnMaxDuration?: boolean;
|
|
429
|
+
/**
|
|
430
|
+
* Optional callback invoked when maxDurationMs is reached.
|
|
431
|
+
*
|
|
432
|
+
* If autoStopOnMaxDuration is true, this callback is invoked before the
|
|
433
|
+
* recorder finishes stopping. The final stop result is not passed here.
|
|
434
|
+
*/
|
|
435
|
+
onMaxDurationReached?: (_: MaxDurationReachedEvent) => void;
|
|
399
436
|
/** Optional directory path where output files will be saved */
|
|
400
437
|
outputDirectory?: string;
|
|
401
438
|
/** Optional filename for the recording (uses UUID if not provided) */
|
|
@@ -597,10 +634,16 @@ export interface UseAudioRecorderState {
|
|
|
597
634
|
size: number;
|
|
598
635
|
/** Information about compression if enabled */
|
|
599
636
|
compression?: CompressionInfo;
|
|
637
|
+
/** Configured maximum active recording duration in milliseconds, if enabled */
|
|
638
|
+
maxDurationMs?: number;
|
|
639
|
+
/** Whether the current recording session has reached the configured maximum duration */
|
|
640
|
+
maxDurationReached?: boolean;
|
|
600
641
|
/** Analysis data for the recording if processing was enabled */
|
|
601
642
|
analysisData?: AudioAnalysis;
|
|
602
643
|
/** Optional callback to handle recording interruptions */
|
|
603
644
|
onRecordingInterrupted?: (_: RecordingInterruptionEvent) => void;
|
|
645
|
+
/** Optional callback invoked when maxDurationMs is reached */
|
|
646
|
+
onMaxDurationReached?: (_: MaxDurationReachedEvent) => void;
|
|
604
647
|
}
|
|
605
648
|
/**
|
|
606
649
|
* Represents an event emitted during the trimming process to report progress.
|