@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
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Cross-platform audio recording, analysis, and processing for React Native and Ex
|
|
|
36
36
|
|
|
37
37
|
## Features
|
|
38
38
|
|
|
39
|
-
- **Recording** — real-time streaming, dual-stream (raw PCM + compressed), background recording, zero-latency start via `prepareRecording`
|
|
39
|
+
- **Recording** — real-time streaming, dual-stream (raw PCM + compressed), max active duration limits, background recording, zero-latency start via `prepareRecording`
|
|
40
40
|
- **Device management** — list/select input devices (Bluetooth, USB, wired), automatic fallback
|
|
41
41
|
- **Interruption handling** — auto pause/resume during phone calls
|
|
42
42
|
- **Audio analysis** — MFCC, spectral features, mel spectrogram, tempo, pitch, waveform preview
|
|
@@ -116,6 +116,35 @@ await startRecording({
|
|
|
116
116
|
})
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
### Max Active Recording Duration
|
|
120
|
+
|
|
121
|
+
Use `maxDurationMs` to cap cumulative active recording time. Paused time does
|
|
122
|
+
not count toward the limit. By default, the recorder emits
|
|
123
|
+
`onMaxDurationReached` and continues recording so your app can decide what to
|
|
124
|
+
do next. Set `autoStopOnMaxDuration: true` to stop automatically.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const {
|
|
128
|
+
startRecording,
|
|
129
|
+
maxDurationMs,
|
|
130
|
+
maxDurationReached,
|
|
131
|
+
} = useAudioRecorder()
|
|
132
|
+
|
|
133
|
+
await startRecording({
|
|
134
|
+
sampleRate: 16000,
|
|
135
|
+
channels: 1,
|
|
136
|
+
maxDurationMs: 60_000,
|
|
137
|
+
autoStopOnMaxDuration: true,
|
|
138
|
+
onMaxDurationReached: (event) => {
|
|
139
|
+
console.log('Reached recording limit:', event.maxDurationMs)
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
When auto-stop is enabled, the max-duration event is emitted before the stop
|
|
145
|
+
finishes. Use the event and stream callbacks for immediate UI updates, then use
|
|
146
|
+
normal recording state to observe that the recorder has stopped.
|
|
147
|
+
|
|
119
148
|
## Audio Analysis
|
|
120
149
|
|
|
121
150
|
For live analysis during recording, `useAudioRecorder` keeps a recent analysis
|
|
@@ -92,6 +92,12 @@ class AudioRecorderManager(
|
|
|
92
92
|
private var lastEmitTime = SystemClock.elapsedRealtime()
|
|
93
93
|
private var lastPauseTime = 0L
|
|
94
94
|
private var pausedDuration = 0L
|
|
95
|
+
private val maxDurationLock = Any()
|
|
96
|
+
private var maxDurationRunnable: Runnable? = null
|
|
97
|
+
private var maxDurationTargetMs = 0L
|
|
98
|
+
private var maxDurationAccumulatedActiveMs = 0L
|
|
99
|
+
private var maxDurationSegmentStartElapsed = 0L
|
|
100
|
+
private var maxDurationReached = false
|
|
95
101
|
private var lastEmittedSize = 0L
|
|
96
102
|
private var lastEmittedCompressedSize = 0L
|
|
97
103
|
private var streamPosition = 0L // Track total bytes processed in the stream
|
|
@@ -225,7 +231,7 @@ class AudioRecorderManager(
|
|
|
225
231
|
override fun resolve(value: Any?) {
|
|
226
232
|
LogUtils.d(CLASS_NAME, "🔄 Successfully reinitialized AudioRecord with new device")
|
|
227
233
|
}
|
|
228
|
-
override fun reject(code: String
|
|
234
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
229
235
|
LogUtils.e(CLASS_NAME, "🔄 Failed to reinitialize AudioRecord: $message")
|
|
230
236
|
}
|
|
231
237
|
})) {
|
|
@@ -237,7 +243,7 @@ class AudioRecorderManager(
|
|
|
237
243
|
"isPaused" to true
|
|
238
244
|
))
|
|
239
245
|
}
|
|
240
|
-
override fun reject(code: String
|
|
246
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {}
|
|
241
247
|
})
|
|
242
248
|
return
|
|
243
249
|
}
|
|
@@ -253,7 +259,7 @@ class AudioRecorderManager(
|
|
|
253
259
|
"isPaused" to true
|
|
254
260
|
))
|
|
255
261
|
}
|
|
256
|
-
override fun reject(code: String
|
|
262
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {}
|
|
257
263
|
})
|
|
258
264
|
return
|
|
259
265
|
}
|
|
@@ -297,7 +303,7 @@ class AudioRecorderManager(
|
|
|
297
303
|
"error" to e.message
|
|
298
304
|
))
|
|
299
305
|
}
|
|
300
|
-
override fun reject(code: String
|
|
306
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {}
|
|
301
307
|
})
|
|
302
308
|
}
|
|
303
309
|
}
|
|
@@ -417,7 +423,7 @@ class AudioRecorderManager(
|
|
|
417
423
|
"isPaused" to true
|
|
418
424
|
))
|
|
419
425
|
}
|
|
420
|
-
override fun reject(code: String
|
|
426
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
421
427
|
LogUtils.e(CLASS_NAME, "Failed to pause recording on phone call", cause)
|
|
422
428
|
}
|
|
423
429
|
})
|
|
@@ -444,7 +450,7 @@ class AudioRecorderManager(
|
|
|
444
450
|
"isPaused" to false
|
|
445
451
|
))
|
|
446
452
|
}
|
|
447
|
-
override fun reject(code: String
|
|
453
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
448
454
|
LogUtils.e(CLASS_NAME, "Failed to resume recording after phone call", cause)
|
|
449
455
|
}
|
|
450
456
|
})
|
|
@@ -650,6 +656,7 @@ class AudioRecorderManager(
|
|
|
650
656
|
"compressedFileUri" to compressedFile?.toURI().toString()
|
|
651
657
|
) else null
|
|
652
658
|
)
|
|
659
|
+
startMaxDurationTimer()
|
|
653
660
|
promise.resolve(result)
|
|
654
661
|
|
|
655
662
|
} catch (e: Exception) {
|
|
@@ -695,6 +702,123 @@ class AudioRecorderManager(
|
|
|
695
702
|
return isSupported
|
|
696
703
|
}
|
|
697
704
|
|
|
705
|
+
private fun getMaxDurationActiveMs(now: Long = SystemClock.elapsedRealtime()): Long {
|
|
706
|
+
return synchronized(maxDurationLock) {
|
|
707
|
+
if (maxDurationSegmentStartElapsed <= 0L) {
|
|
708
|
+
maxDurationAccumulatedActiveMs
|
|
709
|
+
} else {
|
|
710
|
+
maxDurationAccumulatedActiveMs + (now - maxDurationSegmentStartElapsed)
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
private fun startMaxDurationTimer() {
|
|
716
|
+
synchronized(maxDurationLock) {
|
|
717
|
+
maxDurationRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
718
|
+
maxDurationRunnable = null
|
|
719
|
+
maxDurationTargetMs = recordingConfig.maxDurationMs
|
|
720
|
+
maxDurationAccumulatedActiveMs = 0L
|
|
721
|
+
maxDurationSegmentStartElapsed = 0L
|
|
722
|
+
maxDurationReached = false
|
|
723
|
+
|
|
724
|
+
if (maxDurationTargetMs <= 0L) {
|
|
725
|
+
return
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
maxDurationSegmentStartElapsed = SystemClock.elapsedRealtime()
|
|
729
|
+
}
|
|
730
|
+
scheduleMaxDurationTimer()
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private fun scheduleMaxDurationTimer() {
|
|
734
|
+
val remainingMs = synchronized(maxDurationLock) {
|
|
735
|
+
if (
|
|
736
|
+
maxDurationTargetMs <= 0L ||
|
|
737
|
+
maxDurationReached ||
|
|
738
|
+
!_isRecording.get() ||
|
|
739
|
+
isPaused.get()
|
|
740
|
+
) {
|
|
741
|
+
return
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
maxDurationRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
745
|
+
(maxDurationTargetMs - getMaxDurationActiveMs()).coerceAtLeast(0L)
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
val runnable = Runnable { emitMaxDurationReached() }
|
|
749
|
+
synchronized(maxDurationLock) {
|
|
750
|
+
maxDurationRunnable = runnable
|
|
751
|
+
}
|
|
752
|
+
mainHandler.postDelayed(runnable, remainingMs)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
private fun pauseMaxDurationTimer() {
|
|
756
|
+
synchronized(maxDurationLock) {
|
|
757
|
+
maxDurationRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
758
|
+
maxDurationRunnable = null
|
|
759
|
+
if (maxDurationSegmentStartElapsed > 0L) {
|
|
760
|
+
maxDurationAccumulatedActiveMs = getMaxDurationActiveMs()
|
|
761
|
+
maxDurationSegmentStartElapsed = 0L
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private fun resumeMaxDurationTimer() {
|
|
767
|
+
synchronized(maxDurationLock) {
|
|
768
|
+
if (maxDurationTargetMs <= 0L || maxDurationReached) {
|
|
769
|
+
return
|
|
770
|
+
}
|
|
771
|
+
maxDurationSegmentStartElapsed = SystemClock.elapsedRealtime()
|
|
772
|
+
}
|
|
773
|
+
scheduleMaxDurationTimer()
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
private fun cancelMaxDurationTimer() {
|
|
777
|
+
synchronized(maxDurationLock) {
|
|
778
|
+
maxDurationRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
779
|
+
maxDurationRunnable = null
|
|
780
|
+
maxDurationSegmentStartElapsed = 0L
|
|
781
|
+
if (!maxDurationReached) {
|
|
782
|
+
maxDurationTargetMs = 0L
|
|
783
|
+
maxDurationAccumulatedActiveMs = 0L
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
private fun emitMaxDurationReached() {
|
|
789
|
+
val event = synchronized(maxDurationLock) {
|
|
790
|
+
if (maxDurationTargetMs <= 0L || maxDurationReached) {
|
|
791
|
+
return
|
|
792
|
+
}
|
|
793
|
+
if (!_isRecording.get() || isPaused.get()) {
|
|
794
|
+
return
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
val durationMs = getMaxDurationActiveMs()
|
|
798
|
+
maxDurationReached = true
|
|
799
|
+
maxDurationRunnable = null
|
|
800
|
+
bundleOf(
|
|
801
|
+
"durationMs" to durationMs,
|
|
802
|
+
"maxDurationMs" to maxDurationTargetMs,
|
|
803
|
+
"overrunMs" to (durationMs - maxDurationTargetMs).coerceAtLeast(0L),
|
|
804
|
+
"streamUuid" to streamUuid,
|
|
805
|
+
"autoStopped" to recordingConfig.autoStopOnMaxDuration,
|
|
806
|
+
)
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
eventSender.sendExpoEvent(Constants.MAX_DURATION_REACHED_EVENT_NAME, event)
|
|
810
|
+
if (recordingConfig.autoStopOnMaxDuration) {
|
|
811
|
+
stopRecording(object : Promise {
|
|
812
|
+
override fun resolve(value: Any?) {
|
|
813
|
+
LogUtils.d(CLASS_NAME, "Auto-stopped recording after maxDurationMs")
|
|
814
|
+
}
|
|
815
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
816
|
+
LogUtils.e(CLASS_NAME, "Failed to auto-stop recording after maxDurationMs: $message")
|
|
817
|
+
}
|
|
818
|
+
})
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
698
822
|
private fun checkPermissions(options: Map<String, Any?>, promise: Promise): Boolean {
|
|
699
823
|
if (!permissionUtils.checkRecordingPermission(enableBackgroundAudio)) {
|
|
700
824
|
promise.reject(
|
|
@@ -996,6 +1120,7 @@ class AudioRecorderManager(
|
|
|
996
1120
|
|
|
997
1121
|
fun stopRecording(promise: Promise) {
|
|
998
1122
|
val stopStartTime = System.currentTimeMillis()
|
|
1123
|
+
cancelMaxDurationTimer()
|
|
999
1124
|
|
|
1000
1125
|
synchronized(audioRecordLock) {
|
|
1001
1126
|
if (!_isRecording.get()) {
|
|
@@ -1226,7 +1351,7 @@ class AudioRecorderManager(
|
|
|
1226
1351
|
override fun resolve(value: Any?) {
|
|
1227
1352
|
LogUtils.d(CLASS_NAME, "⏺️ Successfully reinitialized AudioRecord for resumption")
|
|
1228
1353
|
}
|
|
1229
|
-
override fun reject(code: String
|
|
1354
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
1230
1355
|
LogUtils.e(CLASS_NAME, "⏺️ Failed to reinitialize AudioRecord: $message")
|
|
1231
1356
|
// We'll let the main try-catch handle this error
|
|
1232
1357
|
throw IllegalStateException("Failed to reinitialize AudioRecord: $message")
|
|
@@ -1246,6 +1371,7 @@ class AudioRecorderManager(
|
|
|
1246
1371
|
acquireWakeLock()
|
|
1247
1372
|
pausedDuration += System.currentTimeMillis() - lastPauseTime
|
|
1248
1373
|
isPaused.set(false)
|
|
1374
|
+
resumeMaxDurationTimer()
|
|
1249
1375
|
|
|
1250
1376
|
synchronized(audioRecordLock) {
|
|
1251
1377
|
// Double-check audioRecord is valid after potential reinitialization
|
|
@@ -1292,6 +1418,7 @@ class AudioRecorderManager(
|
|
|
1292
1418
|
lastPauseTime = System.currentTimeMillis()
|
|
1293
1419
|
isPaused.set(true)
|
|
1294
1420
|
pausedBySystemInterruption.set(isSystemInterruption)
|
|
1421
|
+
pauseMaxDurationTimer()
|
|
1295
1422
|
|
|
1296
1423
|
if (recordingConfig.showNotification) {
|
|
1297
1424
|
notificationManager.pauseUpdates()
|
|
@@ -1381,6 +1508,8 @@ class AudioRecorderManager(
|
|
|
1381
1508
|
"mimeType" to mimeType,
|
|
1382
1509
|
"size" to totalDataSize,
|
|
1383
1510
|
"interval" to recordingConfig.interval,
|
|
1511
|
+
"maxDurationMs" to if (recordingConfig.maxDurationMs > 0) recordingConfig.maxDurationMs else null,
|
|
1512
|
+
"maxDurationReached" to maxDurationReached,
|
|
1384
1513
|
"compression" to compressionBundle
|
|
1385
1514
|
)
|
|
1386
1515
|
}
|
|
@@ -1756,6 +1885,7 @@ class AudioRecorderManager(
|
|
|
1756
1885
|
}
|
|
1757
1886
|
|
|
1758
1887
|
fun cleanup() {
|
|
1888
|
+
cancelMaxDurationTimer()
|
|
1759
1889
|
synchronized(audioRecordLock) {
|
|
1760
1890
|
try {
|
|
1761
1891
|
if (_isRecording.get()) {
|
|
@@ -1936,7 +2066,7 @@ class AudioRecorderManager(
|
|
|
1936
2066
|
"isPaused" to true
|
|
1937
2067
|
))
|
|
1938
2068
|
}
|
|
1939
|
-
override fun reject(code: String
|
|
2069
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
1940
2070
|
LogUtils.e(CLASS_NAME, "Failed to pause recording on audio focus loss")
|
|
1941
2071
|
}
|
|
1942
2072
|
})
|
|
@@ -1959,7 +2089,7 @@ class AudioRecorderManager(
|
|
|
1959
2089
|
"isPaused" to false
|
|
1960
2090
|
))
|
|
1961
2091
|
}
|
|
1962
|
-
override fun reject(code: String
|
|
2092
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
1963
2093
|
LogUtils.e(CLASS_NAME, "Failed to resume recording on audio focus gain")
|
|
1964
2094
|
}
|
|
1965
2095
|
})
|
|
@@ -2006,7 +2136,7 @@ class AudioRecorderManager(
|
|
|
2006
2136
|
"isPaused" to true
|
|
2007
2137
|
))
|
|
2008
2138
|
}
|
|
2009
|
-
override fun reject(code: String
|
|
2139
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
2010
2140
|
LogUtils.e(CLASS_NAME, "Failed to pause recording on audio focus loss")
|
|
2011
2141
|
}
|
|
2012
2142
|
})
|
|
@@ -2033,7 +2163,7 @@ class AudioRecorderManager(
|
|
|
2033
2163
|
"isPaused" to false
|
|
2034
2164
|
))
|
|
2035
2165
|
}
|
|
2036
|
-
override fun reject(code: String
|
|
2166
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
2037
2167
|
LogUtils.e(CLASS_NAME, "Failed to resume recording on audio focus gain")
|
|
2038
2168
|
}
|
|
2039
2169
|
})
|
|
@@ -2139,7 +2269,7 @@ class AudioRecorderManager(
|
|
|
2139
2269
|
// Check permissions - create a dummy promise to avoid rejections
|
|
2140
2270
|
val dummyPromise = object : Promise {
|
|
2141
2271
|
override fun resolve(value: Any?) {}
|
|
2142
|
-
override fun reject(code: String
|
|
2272
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
2143
2273
|
LogUtils.e(CLASS_NAME, "Preparation error: $code - $message", cause)
|
|
2144
2274
|
}
|
|
2145
2275
|
}
|
|
@@ -101,7 +101,7 @@ class AudioRecordingService : Service() {
|
|
|
101
101
|
Log.d(Constants.TAG, "Successfully stopped recording on task removed")
|
|
102
102
|
cleanup()
|
|
103
103
|
}
|
|
104
|
-
override fun reject(code: String
|
|
104
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
105
105
|
Log.e(Constants.TAG, "Failed to stop recording on task removed: $message")
|
|
106
106
|
cleanup()
|
|
107
107
|
}
|
|
@@ -90,6 +90,7 @@ class AudioStudioModule : Module(), EventSender, AudioStreamDecoderDelegate {
|
|
|
90
90
|
Constants.AUDIO_EVENT_NAME,
|
|
91
91
|
Constants.AUDIO_ANALYSIS_EVENT_NAME,
|
|
92
92
|
Constants.RECORDING_INTERRUPTED_EVENT_NAME,
|
|
93
|
+
Constants.MAX_DURATION_REACHED_EVENT_NAME,
|
|
93
94
|
Constants.TRIM_PROGRESS_EVENT,
|
|
94
95
|
Constants.DEVICE_CHANGED_EVENT, // Add device changed event name
|
|
95
96
|
Constants.AUDIO_STREAM_CHUNK_EVENT,
|
|
@@ -252,7 +253,7 @@ class AudioStudioModule : Module(), EventSender, AudioStreamDecoderDelegate {
|
|
|
252
253
|
LogUtils.d(CLASS_NAME, "⏺️ resumeRecording completed successfully")
|
|
253
254
|
promise.resolve(value)
|
|
254
255
|
}
|
|
255
|
-
override fun reject(code: String
|
|
256
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
256
257
|
LogUtils.e(CLASS_NAME, "⏺️ resumeRecording failed: $code - $message", cause)
|
|
257
258
|
promise.reject(code, message, cause)
|
|
258
259
|
}
|
|
@@ -1294,7 +1295,7 @@ class AudioStudioModule : Module(), EventSender, AudioStreamDecoderDelegate {
|
|
|
1294
1295
|
"isPaused" to true
|
|
1295
1296
|
))
|
|
1296
1297
|
}
|
|
1297
|
-
override fun reject(code: String
|
|
1298
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
1298
1299
|
LogUtils.e(CLASS_NAME, "📱 Failed to pause recording after device disconnection: $message")
|
|
1299
1300
|
}
|
|
1300
1301
|
})
|
|
@@ -1314,7 +1315,7 @@ class AudioStudioModule : Module(), EventSender, AudioStreamDecoderDelegate {
|
|
|
1314
1315
|
"isPaused" to true
|
|
1315
1316
|
))
|
|
1316
1317
|
}
|
|
1317
|
-
override fun reject(code: String
|
|
1318
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
1318
1319
|
LogUtils.e(CLASS_NAME, "📱 Failed to pause recording after device disconnection: $message")
|
|
1319
1320
|
}
|
|
1320
1321
|
})
|
|
@@ -1336,7 +1337,7 @@ class AudioStudioModule : Module(), EventSender, AudioStreamDecoderDelegate {
|
|
|
1336
1337
|
"isPaused" to true
|
|
1337
1338
|
))
|
|
1338
1339
|
}
|
|
1339
|
-
override fun reject(code: String
|
|
1340
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
1340
1341
|
LogUtils.e(CLASS_NAME, "📱 Failed to pause recording after device disconnection: $message")
|
|
1341
1342
|
}
|
|
1342
1343
|
})
|
|
@@ -9,6 +9,7 @@ object Constants {
|
|
|
9
9
|
const val AUDIO_EVENT_NAME = "AudioData"
|
|
10
10
|
const val AUDIO_ANALYSIS_EVENT_NAME = "AudioAnalysis"
|
|
11
11
|
const val RECORDING_INTERRUPTED_EVENT_NAME = "onRecordingInterrupted"
|
|
12
|
+
const val MAX_DURATION_REACHED_EVENT_NAME = "MaxDurationReached"
|
|
12
13
|
const val TRIM_PROGRESS_EVENT = "TrimProgress"
|
|
13
14
|
const val DEVICE_CHANGED_EVENT = "deviceChangedEvent"
|
|
14
15
|
const val AUDIO_STREAM_CHUNK_EVENT = "AudioDataStreamChunk"
|
|
@@ -38,4 +39,4 @@ object Constants {
|
|
|
38
39
|
const val DEVICE_TYPE_WIRED_HEADPHONES = "wired_headphones"
|
|
39
40
|
const val DEVICE_TYPE_SPEAKER = "speaker"
|
|
40
41
|
const val DEVICE_TYPE_UNKNOWN = "unknown"
|
|
41
|
-
}
|
|
42
|
+
}
|
|
@@ -41,7 +41,7 @@ class RecordingActionReceiver : BroadcastReceiver() {
|
|
|
41
41
|
isProcessingAction.set(false)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
override fun reject(code: String
|
|
44
|
+
override fun reject(code: String?, message: String?, cause: Throwable?) {
|
|
45
45
|
Log.e("RecordingActionReceiver", "$action failed: $message", cause)
|
|
46
46
|
isProcessingAction.set(false)
|
|
47
47
|
}
|
|
@@ -67,6 +67,8 @@ data class RecordingConfig(
|
|
|
67
67
|
val audioFocusStrategy: String? = null,
|
|
68
68
|
val bufferDurationSeconds: Double? = null,
|
|
69
69
|
val streamFormat: String = "raw",
|
|
70
|
+
val maxDurationMs: Long = 0L,
|
|
71
|
+
val autoStopOnMaxDuration: Boolean = false,
|
|
70
72
|
) {
|
|
71
73
|
companion object {
|
|
72
74
|
fun fromMap(options: Map<String, Any?>?): Result<Pair<RecordingConfig, AudioFormatInfo>> {
|
|
@@ -160,6 +162,8 @@ data class RecordingConfig(
|
|
|
160
162
|
audioFocusStrategy = audioFocusStrategy,
|
|
161
163
|
bufferDurationSeconds = (options["bufferDurationSeconds"] as? Number)?.toDouble(),
|
|
162
164
|
streamFormat = options.getStringOrDefault("streamFormat", "raw"),
|
|
165
|
+
maxDurationMs = options.getNumberOrDefault("maxDurationMs", 0L),
|
|
166
|
+
autoStopOnMaxDuration = options.getBooleanOrDefault("autoStopOnMaxDuration", false),
|
|
163
167
|
)
|
|
164
168
|
|
|
165
169
|
// Validate sample rate and channels
|
|
@@ -256,4 +260,4 @@ data class AudioFormatInfo(
|
|
|
256
260
|
val format: Int,
|
|
257
261
|
val mimeType: String,
|
|
258
262
|
val fileExtension: String
|
|
259
|
-
)
|
|
263
|
+
)
|
|
@@ -1,41 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.useSharedAudioRecorder = exports.AudioRecorderProvider = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
37
5
|
// packages/audio-studio/src/AudioRecorder.provider.tsx
|
|
38
|
-
const react_1 =
|
|
6
|
+
const react_1 = require("react");
|
|
39
7
|
const useAudioRecorder_1 = require("./useAudioRecorder");
|
|
40
8
|
const initContext = {
|
|
41
9
|
isRecording: false,
|
|
@@ -62,9 +30,7 @@ const initContext = {
|
|
|
62
30
|
const AudioRecorderContext = (0, react_1.createContext)(initContext);
|
|
63
31
|
const AudioRecorderProvider = ({ children, config = {}, }) => {
|
|
64
32
|
const audioRecorder = (0, useAudioRecorder_1.useAudioRecorder)(config);
|
|
65
|
-
return (
|
|
66
|
-
{children}
|
|
67
|
-
</AudioRecorderContext.Provider>);
|
|
33
|
+
return ((0, jsx_runtime_1.jsx)(AudioRecorderContext.Provider, { value: audioRecorder, children: children }));
|
|
68
34
|
};
|
|
69
35
|
exports.AudioRecorderProvider = AudioRecorderProvider;
|
|
70
36
|
const useSharedAudioRecorder = () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioRecorder.provider.js","sourceRoot":"","sources":["../../src/AudioRecorder.provider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AudioRecorder.provider.js","sourceRoot":"","sources":["../../src/AudioRecorder.provider.tsx"],"names":[],"mappings":";;;;AAAA,uDAAuD;AACvD,iCAAwD;AAGxD,yDAA4E;AAE5E,MAAM,WAAW,GAA0B;IACvC,WAAW,EAAE,KAAK;IAClB,QAAQ,EAAE,KAAK;IACf,UAAU,EAAE,CAAC;IACb,IAAI,EAAE,CAAC;IACP,WAAW,EAAE,SAAS;IACtB,cAAc,EAAE,KAAK,IAAI,EAAE;QACvB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACtD,CAAC;IACD,aAAa,EAAE,KAAK,IAAI,EAAE;QACtB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACtD,CAAC;IACD,cAAc,EAAE,KAAK,IAAI,EAAE;QACvB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACtD,CAAC;IACD,eAAe,EAAE,KAAK,IAAI,EAAE;QACxB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACtD,CAAC;IACD,gBAAgB,EAAE,KAAK,IAAI,EAAE;QACzB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAA;IACtD,CAAC;CACJ,CAAA;AAED,MAAM,oBAAoB,GAAG,IAAA,qBAAa,EAAwB,WAAW,CAAC,CAAA;AAOvE,MAAM,qBAAqB,GAAyC,CAAC,EACxE,QAAQ,EACR,MAAM,GAAG,EAAE,GACd,EAAE,EAAE;IACD,MAAM,aAAa,GAAG,IAAA,mCAAgB,EAAC,MAAM,CAAC,CAAA;IAC9C,OAAO,CACH,uBAAC,oBAAoB,CAAC,QAAQ,IAAC,KAAK,EAAE,aAAa,YAC9C,QAAQ,GACmB,CACnC,CAAA;AACL,CAAC,CAAA;AAVY,QAAA,qBAAqB,yBAUjC;AAEM,MAAM,sBAAsB,GAAG,GAAG,EAAE;IACvC,MAAM,OAAO,GAAG,IAAA,kBAAU,EAAC,oBAAoB,CAAC,CAAA;IAChD,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACX,qEAAqE,CACxE,CAAA;IACL,CAAC;IACD,OAAO,OAAO,CAAA;AAClB,CAAC,CAAA;AARY,QAAA,sBAAsB,0BAQlC","sourcesContent":["// packages/audio-studio/src/AudioRecorder.provider.tsx\nimport React, { createContext, useContext } from 'react'\n\nimport { UseAudioRecorderState } from './AudioStudio.types'\nimport { UseAudioRecorderProps, useAudioRecorder } from './useAudioRecorder'\n\nconst initContext: UseAudioRecorderState = {\n isRecording: false,\n isPaused: false,\n durationMs: 0,\n size: 0,\n compression: undefined,\n startRecording: async () => {\n throw new Error('AudioRecorderProvider not found')\n },\n stopRecording: async () => {\n throw new Error('AudioRecorderProvider not found')\n },\n pauseRecording: async () => {\n throw new Error('AudioRecorderProvider not found')\n },\n resumeRecording: async () => {\n throw new Error('AudioRecorderProvider not found')\n },\n prepareRecording: async () => {\n throw new Error('AudioRecorderProvider not found')\n },\n}\n\nconst AudioRecorderContext = createContext<UseAudioRecorderState>(initContext)\n\ninterface AudioRecorderProviderProps {\n children: React.ReactNode\n config?: UseAudioRecorderProps\n}\n\nexport const AudioRecorderProvider: React.FC<AudioRecorderProviderProps> = ({\n children,\n config = {},\n}) => {\n const audioRecorder = useAudioRecorder(config)\n return (\n <AudioRecorderContext.Provider value={audioRecorder}>\n {children}\n </AudioRecorderContext.Provider>\n )\n}\n\nexport const useSharedAudioRecorder = () => {\n const context = useContext(AudioRecorderContext)\n if (!context) {\n throw new Error(\n 'useSharedAudioRecorder must be used within an AudioRecorderProvider'\n )\n }\n return context\n}\n"]}
|