@siteed/expo-audio-studio 2.12.3 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -1
- package/android/build.gradle +11 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/net/siteed/audiostream/AudioDeviceManager.kt +266 -42
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +55 -1
- package/app.plugin.js +3 -1
- package/build/cjs/AudioDeviceManager.js +225 -40
- package/build/cjs/AudioDeviceManager.js.map +1 -1
- package/build/cjs/hooks/useAudioDevices.js +30 -5
- package/build/cjs/hooks/useAudioDevices.js.map +1 -1
- package/build/cjs/useAudioRecorder.js +52 -8
- package/build/cjs/useAudioRecorder.js.map +1 -1
- package/build/esm/AudioDeviceManager.js +225 -40
- package/build/esm/AudioDeviceManager.js.map +1 -1
- package/build/esm/hooks/useAudioDevices.js +31 -6
- package/build/esm/hooks/useAudioDevices.js.map +1 -1
- package/build/esm/useAudioRecorder.js +53 -9
- package/build/esm/useAudioRecorder.js.map +1 -1
- package/build/types/AudioDeviceManager.d.ts +78 -2
- package/build/types/AudioDeviceManager.d.ts.map +1 -1
- package/build/types/hooks/useAudioDevices.d.ts +1 -0
- package/build/types/hooks/useAudioDevices.d.ts.map +1 -1
- package/build/types/useAudioRecorder.d.ts.map +1 -1
- package/ios/AudioDeviceManager.swift +21 -9
- package/ios/ExpoAudioStreamModule.swift +33 -1
- package/package.json +8 -6
- package/plugin/build/index.cjs +194 -0
- package/plugin/build/index.d.cts +1 -0
- package/plugin/build/index.js +7 -6
- package/plugin/src/index.ts +8 -8
- package/src/AudioDeviceManager.ts +286 -59
- package/src/hooks/useAudioDevices.ts +39 -6
- package/src/useAudioRecorder.tsx +102 -9
package/src/useAudioRecorder.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/useAudioRecorder.ts
|
|
2
2
|
import { EventSubscription, Platform } from 'expo-modules-core'
|
|
3
|
-
import { useCallback, useEffect, useReducer, useRef } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useReducer, useRef, useId } from 'react'
|
|
4
4
|
|
|
5
5
|
import { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'
|
|
6
|
+
import { audioDeviceManager } from './AudioDeviceManager'
|
|
6
7
|
import {
|
|
7
8
|
AudioDataEvent,
|
|
8
9
|
AudioRecording,
|
|
@@ -157,6 +158,10 @@ export function useAudioRecorder({
|
|
|
157
158
|
audioWorkletUrl,
|
|
158
159
|
featuresExtratorUrl,
|
|
159
160
|
}: UseAudioRecorderProps = {}): UseAudioRecorderState {
|
|
161
|
+
// Initialize AudioDeviceManager with logger (once)
|
|
162
|
+
if (logger) {
|
|
163
|
+
audioDeviceManager.setLogger(logger)
|
|
164
|
+
}
|
|
160
165
|
const [state, dispatch] = useReducer(audioRecorderReducer, {
|
|
161
166
|
isRecording: false,
|
|
162
167
|
isPaused: false,
|
|
@@ -200,6 +205,9 @@ export function useAudioRecorder({
|
|
|
200
205
|
|
|
201
206
|
const recordingConfigRef = useRef<RecordingConfig | null>(null)
|
|
202
207
|
|
|
208
|
+
// Generate unique instance ID for debugging
|
|
209
|
+
const instanceId = useId().replace(/:/g, '').slice(0, 5)
|
|
210
|
+
|
|
203
211
|
const handleAudioAnalysis = useCallback(
|
|
204
212
|
async ({
|
|
205
213
|
analysis,
|
|
@@ -469,7 +477,12 @@ export function useAudioRecorder({
|
|
|
469
477
|
|
|
470
478
|
analysisRef.current = { ...defaultAnalysis } // Reset analysis data
|
|
471
479
|
fullAnalysisRef.current = { ...defaultAnalysis }
|
|
472
|
-
const {
|
|
480
|
+
const {
|
|
481
|
+
onAudioStream,
|
|
482
|
+
onRecordingInterrupted,
|
|
483
|
+
onAudioAnalysis,
|
|
484
|
+
...options
|
|
485
|
+
} = recordingOptions
|
|
473
486
|
const { enableProcessing } = options
|
|
474
487
|
|
|
475
488
|
const maxRecentDataDuration = 10000 // TODO compute maxRecentDataDuration based on screen dimensions
|
|
@@ -518,7 +531,12 @@ export function useAudioRecorder({
|
|
|
518
531
|
|
|
519
532
|
analysisRef.current = { ...defaultAnalysis } // Reset analysis data
|
|
520
533
|
fullAnalysisRef.current = { ...defaultAnalysis }
|
|
521
|
-
const {
|
|
534
|
+
const {
|
|
535
|
+
onAudioStream,
|
|
536
|
+
onRecordingInterrupted,
|
|
537
|
+
onAudioAnalysis,
|
|
538
|
+
...options
|
|
539
|
+
} = recordingOptions
|
|
522
540
|
|
|
523
541
|
// Store onAudioStream for later use when recording starts
|
|
524
542
|
if (typeof onAudioStream === 'function') {
|
|
@@ -546,6 +564,8 @@ export function useAudioRecorder({
|
|
|
546
564
|
analysisListenerRef.current = null
|
|
547
565
|
}
|
|
548
566
|
onAudioStreamRef.current = null
|
|
567
|
+
|
|
568
|
+
// Note: We deliberately DON'T clear recordingConfigRef here to preserve interruption callback
|
|
549
569
|
logger?.debug(`recording stopped`, stopResult)
|
|
550
570
|
dispatch({ type: 'STOP' })
|
|
551
571
|
return stopResult
|
|
@@ -603,31 +623,104 @@ export function useAudioRecorder({
|
|
|
603
623
|
|
|
604
624
|
useEffect(() => {
|
|
605
625
|
// Add event subscription for recording interruptions
|
|
606
|
-
logger?.debug(
|
|
626
|
+
logger?.debug(
|
|
627
|
+
`Setting up recording interruption listener [${instanceId}]`
|
|
628
|
+
)
|
|
607
629
|
|
|
608
630
|
const subscription = addRecordingInterruptionListener((event) => {
|
|
609
|
-
logger?.debug(
|
|
631
|
+
logger?.debug(
|
|
632
|
+
`[${instanceId}] Received recording interruption event:`,
|
|
633
|
+
event
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
// Handle device disconnection for UI updates
|
|
637
|
+
if (event.reason === 'deviceDisconnected') {
|
|
638
|
+
logger?.debug(
|
|
639
|
+
`[${instanceId}] Device disconnected - temporarily hiding last device from UI`
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
// Get current device list before the native layer updates
|
|
643
|
+
const currentDevices = audioDeviceManager.getRawDevices()
|
|
644
|
+
|
|
645
|
+
// Wait a moment for native layer to update, then compare
|
|
646
|
+
setTimeout(async () => {
|
|
647
|
+
try {
|
|
648
|
+
// Get updated devices without notifying yet
|
|
649
|
+
const updatedDevices =
|
|
650
|
+
await audioDeviceManager.getAvailableDevices({
|
|
651
|
+
refresh: true,
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
// Find missing devices by comparing lists
|
|
655
|
+
const missingDevices = currentDevices.filter(
|
|
656
|
+
(oldDevice) =>
|
|
657
|
+
!updatedDevices.some(
|
|
658
|
+
(newDevice) => newDevice.id === oldDevice.id
|
|
659
|
+
)
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
if (missingDevices.length > 0) {
|
|
663
|
+
// Mark all missing devices as disconnected (silently)
|
|
664
|
+
missingDevices.forEach((missingDevice) => {
|
|
665
|
+
logger?.debug(
|
|
666
|
+
`[${instanceId}] Confirmed disconnected device: ${missingDevice.name} (${missingDevice.id})`
|
|
667
|
+
)
|
|
668
|
+
audioDeviceManager.markDeviceAsDisconnected(
|
|
669
|
+
missingDevice.id,
|
|
670
|
+
false
|
|
671
|
+
)
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Notify listeners once with the final filtered state
|
|
676
|
+
audioDeviceManager.notifyListeners()
|
|
677
|
+
} catch (error) {
|
|
678
|
+
logger?.warn(
|
|
679
|
+
`[${instanceId}] Error in delayed device disconnection handling:`,
|
|
680
|
+
error
|
|
681
|
+
)
|
|
682
|
+
}
|
|
683
|
+
}, 500) // 500ms delay to let native layer update
|
|
684
|
+
} else if (event.reason === 'deviceConnected') {
|
|
685
|
+
// Device reconnected - force refresh to show it immediately
|
|
686
|
+
logger?.debug(
|
|
687
|
+
`[${instanceId}] Device connected, forcing refresh`
|
|
688
|
+
)
|
|
689
|
+
audioDeviceManager.forceRefreshDevices()
|
|
690
|
+
}
|
|
610
691
|
|
|
611
692
|
// Check if we have a callback configured
|
|
693
|
+
logger?.debug(
|
|
694
|
+
`[${instanceId}] recordingConfigRef.current exists:`,
|
|
695
|
+
!!recordingConfigRef.current
|
|
696
|
+
)
|
|
697
|
+
|
|
612
698
|
if (recordingConfigRef.current?.onRecordingInterrupted) {
|
|
613
699
|
try {
|
|
700
|
+
logger?.debug(
|
|
701
|
+
`[${instanceId}] Calling recording interruption callback`
|
|
702
|
+
)
|
|
614
703
|
recordingConfigRef.current.onRecordingInterrupted(event)
|
|
615
704
|
} catch (error) {
|
|
616
705
|
logger?.error(
|
|
617
|
-
|
|
706
|
+
`[${instanceId}] Error in recording interruption callback:`,
|
|
618
707
|
error
|
|
619
708
|
)
|
|
620
709
|
}
|
|
621
710
|
} else {
|
|
622
|
-
logger?.debug(
|
|
711
|
+
logger?.debug(
|
|
712
|
+
`[${instanceId}] No recording interruption callback configured`
|
|
713
|
+
)
|
|
623
714
|
}
|
|
624
715
|
})
|
|
625
716
|
|
|
626
717
|
return () => {
|
|
627
|
-
logger?.debug(
|
|
718
|
+
logger?.debug(
|
|
719
|
+
`[${instanceId}] Removing recording interruption listener`
|
|
720
|
+
)
|
|
628
721
|
subscription.remove()
|
|
629
722
|
}
|
|
630
|
-
}, []) //
|
|
723
|
+
}, [instanceId, logger]) // Include instanceId and logger in dependencies
|
|
631
724
|
|
|
632
725
|
return {
|
|
633
726
|
prepareRecording,
|