@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.
Files changed (58) hide show
  1. package/README.md +30 -1
  2. package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +142 -12
  3. package/android/src/main/java/net/siteed/audiostudio/AudioRecordingService.kt +1 -1
  4. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +5 -4
  5. package/android/src/main/java/net/siteed/audiostudio/Constants.kt +2 -1
  6. package/android/src/main/java/net/siteed/audiostudio/RecordingActionReceiver.kt +1 -1
  7. package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +5 -1
  8. package/build/cjs/AudioRecorder.provider.js +3 -37
  9. package/build/cjs/AudioRecorder.provider.js.map +1 -1
  10. package/build/cjs/AudioStudio.types.js.map +1 -1
  11. package/build/cjs/AudioStudio.web.js +125 -13
  12. package/build/cjs/AudioStudio.web.js.map +1 -1
  13. package/build/cjs/AudioStudioModule.js +6 -1
  14. package/build/cjs/AudioStudioModule.js.map +1 -1
  15. package/build/cjs/events.js +4 -0
  16. package/build/cjs/events.js.map +1 -1
  17. package/build/cjs/index.js +3 -1
  18. package/build/cjs/index.js.map +1 -1
  19. package/build/cjs/useAudioRecorder.js +139 -4
  20. package/build/cjs/useAudioRecorder.js.map +1 -1
  21. package/build/esm/AudioRecorder.provider.js +3 -4
  22. package/build/esm/AudioRecorder.provider.js.map +1 -1
  23. package/build/esm/AudioStudio.types.js.map +1 -1
  24. package/build/esm/AudioStudio.web.js +125 -13
  25. package/build/esm/AudioStudio.web.js.map +1 -1
  26. package/build/esm/AudioStudioModule.js +6 -1
  27. package/build/esm/AudioStudioModule.js.map +1 -1
  28. package/build/esm/events.js +3 -0
  29. package/build/esm/events.js.map +1 -1
  30. package/build/esm/index.js +1 -0
  31. package/build/esm/index.js.map +1 -1
  32. package/build/esm/useAudioRecorder.js +140 -5
  33. package/build/esm/useAudioRecorder.js.map +1 -1
  34. package/build/types/AudioStudio.types.d.ts +44 -1
  35. package/build/types/AudioStudio.types.d.ts.map +1 -1
  36. package/build/types/AudioStudio.web.d.ts +17 -1
  37. package/build/types/AudioStudio.web.d.ts.map +1 -1
  38. package/build/types/AudioStudioModule.d.ts.map +1 -1
  39. package/build/types/events.d.ts +2 -1
  40. package/build/types/events.d.ts.map +1 -1
  41. package/build/types/index.d.ts +1 -0
  42. package/build/types/index.d.ts.map +1 -1
  43. package/build/types/useAudioRecorder.d.ts +2 -0
  44. package/build/types/useAudioRecorder.d.ts.map +1 -1
  45. package/ios/AudioStreamManager.swift +103 -9
  46. package/ios/AudioStreamManagerDelegate.swift +1 -0
  47. package/ios/AudioStudio.podspec +1 -1
  48. package/ios/AudioStudioModule.swift +6 -0
  49. package/ios/RecordingSettings.swift +48 -43
  50. package/package.json +163 -163
  51. package/plugin/tsconfig.json +8 -2
  52. package/src/AudioStudio.types.ts +48 -1
  53. package/src/AudioStudio.web.ts +152 -13
  54. package/src/AudioStudioModule.ts +6 -1
  55. package/src/events.ts +13 -1
  56. package/src/index.ts +1 -0
  57. package/src/useAudioRecorder.tsx +182 -2
  58. 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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {}
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, message: String?, cause: Throwable?) {}
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, message: String?, cause: Throwable?) {}
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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, message: String?, cause: Throwable?) {
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 = __importStar(require("react"));
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 (<AudioRecorderContext.Provider value={audioRecorder}>
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uDAAuD;AACvD,+CAAwD;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,CAAC,oBAAoB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,CAChD;YAAA,CAAC,QAAQ,CACb;QAAA,EAAE,oBAAoB,CAAC,QAAQ,CAAC,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"]}
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"]}