@siteed/audio-studio 3.2.1-beta.1 → 3.2.1-beta.3
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 +22 -1
- package/README.md +12 -1
- package/build/cjs/AudioStudio.types.js.map +1 -1
- package/build/cjs/useAudioRecorder.js +115 -93
- package/build/cjs/useAudioRecorder.js.map +1 -1
- package/build/cjs/utils/nativeRecordingOptions.js +13 -0
- package/build/cjs/utils/nativeRecordingOptions.js.map +1 -0
- package/build/cjs/utils/nativeRecordingOptions.test.js +30 -0
- package/build/cjs/utils/nativeRecordingOptions.test.js.map +1 -0
- package/build/esm/AudioStudio.types.js.map +1 -1
- package/build/esm/useAudioRecorder.js +115 -93
- package/build/esm/useAudioRecorder.js.map +1 -1
- package/build/esm/utils/nativeRecordingOptions.js +10 -0
- package/build/esm/utils/nativeRecordingOptions.js.map +1 -0
- package/build/esm/utils/nativeRecordingOptions.test.js +28 -0
- package/build/esm/utils/nativeRecordingOptions.test.js.map +1 -0
- package/build/types/AudioStudio.types.d.ts +19 -5
- package/build/types/AudioStudio.types.d.ts.map +1 -1
- package/build/types/useAudioRecorder.d.ts +2 -1
- package/build/types/useAudioRecorder.d.ts.map +1 -1
- package/build/types/utils/nativeRecordingOptions.d.ts +28 -0
- package/build/types/utils/nativeRecordingOptions.d.ts.map +1 -0
- package/build/types/utils/nativeRecordingOptions.test.d.ts +2 -0
- package/build/types/utils/nativeRecordingOptions.test.d.ts.map +1 -0
- package/package.json +2 -1
- package/plugin/build/index.cjs +79 -47
- package/plugin/build/index.d.cts +15 -0
- package/plugin/build/index.js +79 -47
- package/plugin/src/index.test.ts +78 -0
- package/plugin/src/index.ts +141 -59
- package/plugin/tsconfig.json +6 -1
- package/src/AudioStudio.types.ts +27 -5
- package/src/useAudioRecorder.tsx +161 -126
- package/src/utils/nativeRecordingOptions.test.ts +29 -0
- package/src/utils/nativeRecordingOptions.ts +20 -0
package/src/useAudioRecorder.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ConsoleLike,
|
|
13
13
|
MaxDurationReachedEvent,
|
|
14
14
|
RecordingConfig,
|
|
15
|
+
RecordingStopReason,
|
|
15
16
|
StartRecordingResult,
|
|
16
17
|
} from './AudioStudio.types'
|
|
17
18
|
import AudioStudioModule from './AudioStudioModule'
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
addMaxDurationReachedListener,
|
|
24
25
|
addRecordingInterruptionListener,
|
|
25
26
|
} from './events'
|
|
26
|
-
import {
|
|
27
|
+
import { createNativeRecordingOptions } from './utils/nativeRecordingOptions'
|
|
27
28
|
|
|
28
29
|
export interface UseAudioRecorderProps {
|
|
29
30
|
logger?: ConsoleLike
|
|
@@ -45,6 +46,7 @@ export interface UseAudioRecorderState {
|
|
|
45
46
|
analysisData?: AudioAnalysis
|
|
46
47
|
maxDurationMs?: number
|
|
47
48
|
maxDurationReached?: boolean
|
|
49
|
+
lastRecordingReason?: RecordingStopReason
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
interface RecorderReducerState {
|
|
@@ -56,10 +58,17 @@ interface RecorderReducerState {
|
|
|
56
58
|
analysisData?: AudioAnalysis
|
|
57
59
|
maxDurationMs?: number
|
|
58
60
|
maxDurationReached?: boolean
|
|
61
|
+
lastRecordingReason?: RecordingStopReason
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
type RecorderAction =
|
|
62
|
-
| { type: 'START' | '
|
|
65
|
+
| { type: 'START' | 'PAUSE' | 'RESUME' }
|
|
66
|
+
| {
|
|
67
|
+
type: 'STOP'
|
|
68
|
+
payload: {
|
|
69
|
+
reason: RecordingStopReason
|
|
70
|
+
}
|
|
71
|
+
}
|
|
63
72
|
| {
|
|
64
73
|
type: 'UPDATE_RECORDING_STATE'
|
|
65
74
|
payload: {
|
|
@@ -102,6 +111,42 @@ const defaultAnalysis: AudioAnalysis = {
|
|
|
102
111
|
extractionTimeMs: 0,
|
|
103
112
|
}
|
|
104
113
|
|
|
114
|
+
function finiteOrZero(value: number): number {
|
|
115
|
+
return Number.isFinite(value) ? value : 0
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sanitizeSerializableValue<T>(value: T): T {
|
|
119
|
+
if (typeof value === 'number') {
|
|
120
|
+
return finiteOrZero(value) as T
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
return value.map((item) => sanitizeSerializableValue(item)) as T
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (value && typeof value === 'object') {
|
|
128
|
+
const sanitized: Record<string, unknown> = {}
|
|
129
|
+
|
|
130
|
+
for (const [key, nestedValue] of Object.entries(
|
|
131
|
+
value as Record<string, unknown>
|
|
132
|
+
)) {
|
|
133
|
+
sanitized[key] = sanitizeSerializableValue(nestedValue)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return sanitized as T
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return value
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function createSerializableAnalysis(analysis: AudioAnalysis): AudioAnalysis {
|
|
143
|
+
return sanitizeSerializableValue(analysis)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function createRecordingSnapshot(recording: AudioRecording): AudioRecording {
|
|
147
|
+
return sanitizeSerializableValue(recording)
|
|
148
|
+
}
|
|
149
|
+
|
|
105
150
|
function audioRecorderReducer(
|
|
106
151
|
state: RecorderReducerState,
|
|
107
152
|
action: RecorderAction
|
|
@@ -118,6 +163,7 @@ function audioRecorderReducer(
|
|
|
118
163
|
analysisData: defaultAnalysis,
|
|
119
164
|
maxDurationMs: undefined,
|
|
120
165
|
maxDurationReached: false,
|
|
166
|
+
lastRecordingReason: undefined,
|
|
121
167
|
}
|
|
122
168
|
case 'STOP':
|
|
123
169
|
return {
|
|
@@ -128,6 +174,7 @@ function audioRecorderReducer(
|
|
|
128
174
|
size: 0,
|
|
129
175
|
compression: undefined,
|
|
130
176
|
analysisData: undefined,
|
|
177
|
+
lastRecordingReason: action.payload.reason,
|
|
131
178
|
// Preserve max-duration state after stop so UI and agentic
|
|
132
179
|
// validation can explain why recording ended. START resets it.
|
|
133
180
|
}
|
|
@@ -202,6 +249,7 @@ export function useAudioRecorder({
|
|
|
202
249
|
analysisData: undefined,
|
|
203
250
|
maxDurationMs: undefined,
|
|
204
251
|
maxDurationReached: false,
|
|
252
|
+
lastRecordingReason: undefined,
|
|
205
253
|
})
|
|
206
254
|
|
|
207
255
|
const startResultRef = useRef<StartRecordingResult | null>(null)
|
|
@@ -240,6 +288,7 @@ export function useAudioRecorder({
|
|
|
240
288
|
|
|
241
289
|
const recordingConfigRef = useRef<RecordingConfig | null>(null)
|
|
242
290
|
const maxDurationHandledRef = useRef(false)
|
|
291
|
+
const stopFinalizationRef = useRef<Promise<AudioRecording> | null>(null)
|
|
243
292
|
|
|
244
293
|
// Generate unique instance ID for debugging
|
|
245
294
|
const instanceId = useId().replace(/:/g, '').slice(0, 5)
|
|
@@ -471,6 +520,83 @@ export function useAudioRecorder({
|
|
|
471
520
|
[]
|
|
472
521
|
)
|
|
473
522
|
|
|
523
|
+
const finalizeRecordingStop = useCallback(
|
|
524
|
+
async (reason: RecordingStopReason) => {
|
|
525
|
+
if (stopFinalizationRef.current) {
|
|
526
|
+
return stopFinalizationRef.current
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const finalizePromise = (async () => {
|
|
530
|
+
const nativeStopResult: AudioRecording | null =
|
|
531
|
+
await audioStudio.stopRecording()
|
|
532
|
+
|
|
533
|
+
if (!nativeStopResult) {
|
|
534
|
+
throw new Error('Failed to stop recording')
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const stopResult = createRecordingSnapshot(nativeStopResult)
|
|
538
|
+
|
|
539
|
+
if (shouldKeepFullAnalysis(recordingConfigRef.current)) {
|
|
540
|
+
stopResult.analysisData = createSerializableAnalysis(
|
|
541
|
+
fullAnalysisRef.current
|
|
542
|
+
)
|
|
543
|
+
} else {
|
|
544
|
+
// `keepFullAnalysis` is a hook-level retention policy. If a platform
|
|
545
|
+
// starts returning native analysisData in the future, keep opt-out
|
|
546
|
+
// semantics explicit and avoid leaking a full history here.
|
|
547
|
+
delete stopResult.analysisData
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (analysisListenerRef.current) {
|
|
551
|
+
analysisListenerRef.current.remove()
|
|
552
|
+
analysisListenerRef.current = null
|
|
553
|
+
}
|
|
554
|
+
onAudioStreamRef.current = null
|
|
555
|
+
|
|
556
|
+
stateRef.current.isRecording = false
|
|
557
|
+
stateRef.current.isPaused = false
|
|
558
|
+
|
|
559
|
+
// Note: We deliberately DON'T clear recordingConfigRef here to preserve callbacks.
|
|
560
|
+
logger?.debug(`recording stopped`, stopResult)
|
|
561
|
+
maxDurationHandledRef.current = false
|
|
562
|
+
dispatch({
|
|
563
|
+
type: 'STOP',
|
|
564
|
+
payload: { reason },
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
const stoppedCallback =
|
|
568
|
+
recordingConfigRef.current?.onRecordingStopped
|
|
569
|
+
if (stoppedCallback) {
|
|
570
|
+
try {
|
|
571
|
+
void Promise.resolve(
|
|
572
|
+
stoppedCallback(stopResult, reason)
|
|
573
|
+
).catch((error) => {
|
|
574
|
+
logger?.error(
|
|
575
|
+
`Error in recording stopped callback:`,
|
|
576
|
+
error
|
|
577
|
+
)
|
|
578
|
+
})
|
|
579
|
+
} catch (error) {
|
|
580
|
+
logger?.error(
|
|
581
|
+
`Error in recording stopped callback:`,
|
|
582
|
+
error
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return stopResult
|
|
588
|
+
})()
|
|
589
|
+
|
|
590
|
+
stopFinalizationRef.current = finalizePromise
|
|
591
|
+
try {
|
|
592
|
+
return await finalizePromise
|
|
593
|
+
} finally {
|
|
594
|
+
stopFinalizationRef.current = null
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
[audioStudio, dispatch, logger]
|
|
598
|
+
)
|
|
599
|
+
|
|
474
600
|
const handleMaxDurationReached = useCallback(
|
|
475
601
|
async (event: MaxDurationReachedEvent) => {
|
|
476
602
|
if (maxDurationHandledRef.current) {
|
|
@@ -498,84 +624,15 @@ export function useAudioRecorder({
|
|
|
498
624
|
logger?.error(`Error in max duration callback:`, error)
|
|
499
625
|
}
|
|
500
626
|
|
|
501
|
-
|
|
502
|
-
if (analysisListenerRef.current) {
|
|
503
|
-
analysisListenerRef.current.remove()
|
|
504
|
-
analysisListenerRef.current = null
|
|
505
|
-
}
|
|
506
|
-
onAudioStreamRef.current = null
|
|
507
|
-
stateRef.current.isRecording = false
|
|
508
|
-
stateRef.current.isPaused = false
|
|
509
|
-
dispatch({ type: 'STOP' })
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const waitForPlatformAutoStop = async () => {
|
|
513
|
-
const timeoutMs = 3000
|
|
514
|
-
const startedAt = Date.now()
|
|
515
|
-
let lastStatus: AudioStreamStatus | undefined
|
|
516
|
-
|
|
517
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
518
|
-
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
519
|
-
try {
|
|
520
|
-
const currentStatus: AudioStreamStatus =
|
|
521
|
-
audioStudio.status()
|
|
522
|
-
lastStatus = currentStatus
|
|
523
|
-
if (
|
|
524
|
-
!currentStatus.isRecording &&
|
|
525
|
-
!currentStatus.isPaused
|
|
526
|
-
) {
|
|
527
|
-
finishStoppedState()
|
|
528
|
-
return
|
|
529
|
-
}
|
|
530
|
-
} catch (error) {
|
|
531
|
-
logger?.warn(
|
|
532
|
-
`Error checking status after max duration auto-stop:`,
|
|
533
|
-
error
|
|
534
|
-
)
|
|
535
|
-
break
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
if (
|
|
540
|
-
lastStatus &&
|
|
541
|
-
(lastStatus.isRecording || lastStatus.isPaused)
|
|
542
|
-
) {
|
|
543
|
-
try {
|
|
544
|
-
await audioStudio.stopRecording()
|
|
545
|
-
} catch (error) {
|
|
546
|
-
logger?.warn(
|
|
547
|
-
`Error completing max duration auto-stop fallback:`,
|
|
548
|
-
error
|
|
549
|
-
)
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
// At this point platform-owned auto-stop did not settle cleanly.
|
|
553
|
-
// Clear hook state so the UI does not stay stuck as recording.
|
|
554
|
-
finishStoppedState()
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Only the original event tells us whether the platform already
|
|
558
|
-
// owns auto-stop. Keep stream callbacks alive until status confirms
|
|
559
|
-
// stop completion so native final audio flushes can still reach JS.
|
|
560
|
-
if (event.autoStopped && stateRef.current.isRecording) {
|
|
561
|
-
await waitForPlatformAutoStop()
|
|
562
|
-
return
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (
|
|
566
|
-
config?.autoStopOnMaxDuration &&
|
|
567
|
-
!event.autoStopped &&
|
|
568
|
-
stateRef.current.isRecording
|
|
569
|
-
) {
|
|
627
|
+
if (config?.autoStopOnMaxDuration && stateRef.current.isRecording) {
|
|
570
628
|
try {
|
|
571
|
-
await
|
|
572
|
-
finishStoppedState()
|
|
629
|
+
await finalizeRecordingStop('maxDuration')
|
|
573
630
|
} catch (error) {
|
|
574
631
|
logger?.error(`Error auto-stopping on max duration:`, error)
|
|
575
632
|
}
|
|
576
633
|
}
|
|
577
634
|
},
|
|
578
|
-
[
|
|
635
|
+
[dispatch, finalizeRecordingStop, logger]
|
|
579
636
|
)
|
|
580
637
|
|
|
581
638
|
const checkStatus = useCallback(async () => {
|
|
@@ -618,27 +675,38 @@ export function useAudioRecorder({
|
|
|
618
675
|
})
|
|
619
676
|
}
|
|
620
677
|
|
|
678
|
+
const statusMaxDurationReached = status.maxDurationReached ?? false
|
|
679
|
+
const preserveStoppedMaxDuration =
|
|
680
|
+
!status.isRecording &&
|
|
681
|
+
!status.isPaused &&
|
|
682
|
+
stateRef.current.maxDurationReached &&
|
|
683
|
+
!statusMaxDurationReached
|
|
684
|
+
const nextMaxDurationMs = preserveStoppedMaxDuration
|
|
685
|
+
? stateRef.current.maxDurationMs
|
|
686
|
+
: status.maxDurationMs
|
|
687
|
+
const nextMaxDurationReached = preserveStoppedMaxDuration
|
|
688
|
+
? true
|
|
689
|
+
: statusMaxDurationReached
|
|
690
|
+
|
|
621
691
|
if (
|
|
622
692
|
status.durationMs !== stateRef.current.durationMs ||
|
|
623
693
|
status.size !== stateRef.current.size ||
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
stateRef.current.maxDurationReached
|
|
694
|
+
nextMaxDurationMs !== stateRef.current.maxDurationMs ||
|
|
695
|
+
nextMaxDurationReached !== stateRef.current.maxDurationReached
|
|
627
696
|
) {
|
|
628
697
|
stateRef.current.durationMs = status.durationMs
|
|
629
698
|
stateRef.current.size = status.size
|
|
630
699
|
stateRef.current.compression = status.compression
|
|
631
|
-
stateRef.current.maxDurationMs =
|
|
632
|
-
stateRef.current.maxDurationReached =
|
|
633
|
-
status.maxDurationReached ?? false
|
|
700
|
+
stateRef.current.maxDurationMs = nextMaxDurationMs
|
|
701
|
+
stateRef.current.maxDurationReached = nextMaxDurationReached
|
|
634
702
|
dispatch({
|
|
635
703
|
type: 'UPDATE_STATUS',
|
|
636
704
|
payload: {
|
|
637
705
|
durationMs: status.durationMs,
|
|
638
706
|
size: status.size,
|
|
639
707
|
compression: status.compression,
|
|
640
|
-
maxDurationMs:
|
|
641
|
-
maxDurationReached:
|
|
708
|
+
maxDurationMs: nextMaxDurationMs,
|
|
709
|
+
maxDurationReached: nextMaxDurationReached,
|
|
642
710
|
},
|
|
643
711
|
})
|
|
644
712
|
}
|
|
@@ -697,15 +765,7 @@ export function useAudioRecorder({
|
|
|
697
765
|
|
|
698
766
|
analysisRef.current = { ...defaultAnalysis } // Reset analysis data
|
|
699
767
|
fullAnalysisRef.current = { ...defaultAnalysis }
|
|
700
|
-
const {
|
|
701
|
-
onAudioStream,
|
|
702
|
-
onRecordingInterrupted,
|
|
703
|
-
onMaxDurationReached,
|
|
704
|
-
onAudioAnalysis,
|
|
705
|
-
keepFullAnalysis: _keepFullAnalysis,
|
|
706
|
-
...options
|
|
707
|
-
} = validatedOptions
|
|
708
|
-
const { enableProcessing } = options
|
|
768
|
+
const { onAudioStream, enableProcessing } = validatedOptions
|
|
709
769
|
|
|
710
770
|
const maxRecentDataDuration = 10000 // TODO compute maxRecentDataDuration based on screen dimensions
|
|
711
771
|
if (typeof onAudioStream === 'function') {
|
|
@@ -714,8 +774,10 @@ export function useAudioRecorder({
|
|
|
714
774
|
logger?.warn(`onAudioStream is not a function`, onAudioStream)
|
|
715
775
|
onAudioStreamRef.current = null
|
|
716
776
|
}
|
|
717
|
-
// Strip
|
|
718
|
-
|
|
777
|
+
// Strip hook-only values and undefineds that can't cross the native bridge.
|
|
778
|
+
// autoStopOnMaxDuration stays hook-owned so finalization can expose
|
|
779
|
+
// the same AudioRecording result as a manual stop.
|
|
780
|
+
const cleanOptions = createNativeRecordingOptions(validatedOptions)
|
|
719
781
|
const startResult: StartRecordingResult =
|
|
720
782
|
await audioStudio.startRecording(cleanOptions)
|
|
721
783
|
dispatch({ type: 'START' })
|
|
@@ -755,14 +817,7 @@ export function useAudioRecorder({
|
|
|
755
817
|
|
|
756
818
|
analysisRef.current = { ...defaultAnalysis } // Reset analysis data
|
|
757
819
|
fullAnalysisRef.current = { ...defaultAnalysis }
|
|
758
|
-
const {
|
|
759
|
-
onAudioStream,
|
|
760
|
-
onRecordingInterrupted,
|
|
761
|
-
onMaxDurationReached,
|
|
762
|
-
onAudioAnalysis,
|
|
763
|
-
keepFullAnalysis: _keepFullAnalysis,
|
|
764
|
-
...options
|
|
765
|
-
} = recordingOptions
|
|
820
|
+
const { onAudioStream } = recordingOptions
|
|
766
821
|
|
|
767
822
|
// Store onAudioStream for later use when recording starts
|
|
768
823
|
if (typeof onAudioStream === 'function') {
|
|
@@ -772,8 +827,8 @@ export function useAudioRecorder({
|
|
|
772
827
|
onAudioStreamRef.current = null
|
|
773
828
|
}
|
|
774
829
|
|
|
775
|
-
// Strip
|
|
776
|
-
const cleanOptions =
|
|
830
|
+
// Strip hook-only values and undefineds that can't cross the native bridge.
|
|
831
|
+
const cleanOptions = createNativeRecordingOptions(recordingOptions)
|
|
777
832
|
// Call the native prepareRecording method
|
|
778
833
|
await audioStudio.prepareRecording(cleanOptions)
|
|
779
834
|
logger?.debug(`recording prepared successfully`)
|
|
@@ -783,29 +838,8 @@ export function useAudioRecorder({
|
|
|
783
838
|
|
|
784
839
|
const stopRecording = useCallback(async () => {
|
|
785
840
|
logger?.debug(`stoping recording`)
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (shouldKeepFullAnalysis(recordingConfigRef.current)) {
|
|
789
|
-
stopResult.analysisData = fullAnalysisRef.current
|
|
790
|
-
} else {
|
|
791
|
-
// `keepFullAnalysis` is a hook-level retention policy. If a platform
|
|
792
|
-
// starts returning native analysisData in the future, keep opt-out
|
|
793
|
-
// semantics explicit and avoid leaking a full history here.
|
|
794
|
-
delete stopResult.analysisData
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
if (analysisListenerRef.current) {
|
|
798
|
-
analysisListenerRef.current.remove()
|
|
799
|
-
analysisListenerRef.current = null
|
|
800
|
-
}
|
|
801
|
-
onAudioStreamRef.current = null
|
|
802
|
-
|
|
803
|
-
// Note: We deliberately DON'T clear recordingConfigRef here to preserve interruption callback
|
|
804
|
-
logger?.debug(`recording stopped`, stopResult)
|
|
805
|
-
maxDurationHandledRef.current = false
|
|
806
|
-
dispatch({ type: 'STOP' })
|
|
807
|
-
return stopResult
|
|
808
|
-
}, [dispatch])
|
|
841
|
+
return finalizeRecordingStop('manual')
|
|
842
|
+
}, [finalizeRecordingStop, logger])
|
|
809
843
|
|
|
810
844
|
const pauseRecording = useCallback(async () => {
|
|
811
845
|
logger?.debug(`pause recording`)
|
|
@@ -982,5 +1016,6 @@ export function useAudioRecorder({
|
|
|
982
1016
|
analysisData: state.analysisData,
|
|
983
1017
|
maxDurationMs: state.maxDurationMs,
|
|
984
1018
|
maxDurationReached: state.maxDurationReached,
|
|
1019
|
+
lastRecordingReason: state.lastRecordingReason,
|
|
985
1020
|
}
|
|
986
1021
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createNativeRecordingOptions } from './nativeRecordingOptions'
|
|
2
|
+
|
|
3
|
+
describe('createNativeRecordingOptions', () => {
|
|
4
|
+
it('keeps maxDurationMs native but leaves auto-stop finalization hook-owned', () => {
|
|
5
|
+
const nativeOptions = createNativeRecordingOptions({
|
|
6
|
+
maxDurationMs: 1500,
|
|
7
|
+
autoStopOnMaxDuration: true,
|
|
8
|
+
onMaxDurationReached: jest.fn(),
|
|
9
|
+
onRecordingStopped: jest.fn(),
|
|
10
|
+
onRecordingInterrupted: jest.fn(),
|
|
11
|
+
onAudioAnalysis: jest.fn(),
|
|
12
|
+
onAudioStream: jest.fn(),
|
|
13
|
+
keepFullAnalysis: true,
|
|
14
|
+
sampleRate: 16000,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
expect(nativeOptions).toMatchObject({
|
|
18
|
+
maxDurationMs: 1500,
|
|
19
|
+
sampleRate: 16000,
|
|
20
|
+
})
|
|
21
|
+
expect(nativeOptions).not.toHaveProperty('autoStopOnMaxDuration')
|
|
22
|
+
expect(nativeOptions).not.toHaveProperty('onMaxDurationReached')
|
|
23
|
+
expect(nativeOptions).not.toHaveProperty('onRecordingStopped')
|
|
24
|
+
expect(nativeOptions).not.toHaveProperty('onRecordingInterrupted')
|
|
25
|
+
expect(nativeOptions).not.toHaveProperty('onAudioAnalysis')
|
|
26
|
+
expect(nativeOptions).not.toHaveProperty('onAudioStream')
|
|
27
|
+
expect(nativeOptions).not.toHaveProperty('keepFullAnalysis')
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { RecordingConfig } from '../AudioStudio.types'
|
|
2
|
+
import { cleanNativeOptions } from './cleanNativeOptions'
|
|
3
|
+
|
|
4
|
+
export function createNativeRecordingOptions(recordingOptions: RecordingConfig) {
|
|
5
|
+
const {
|
|
6
|
+
onAudioStream,
|
|
7
|
+
onRecordingInterrupted,
|
|
8
|
+
onMaxDurationReached,
|
|
9
|
+
onRecordingStopped,
|
|
10
|
+
onAudioAnalysis,
|
|
11
|
+
keepFullAnalysis: _keepFullAnalysis,
|
|
12
|
+
// Keep hook-owned auto-stop out of the native bridge so the hook can
|
|
13
|
+
// reuse the same finalization path as manual stop and expose the
|
|
14
|
+
// resulting AudioRecording consistently.
|
|
15
|
+
autoStopOnMaxDuration: _autoStopOnMaxDuration,
|
|
16
|
+
...options
|
|
17
|
+
} = recordingOptions
|
|
18
|
+
|
|
19
|
+
return cleanNativeOptions(options)
|
|
20
|
+
}
|