@siteed/expo-audio-studio 2.16.2 → 2.18.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 CHANGED
@@ -8,6 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  ## [Unreleased]
9
9
 
10
10
 
11
+ ## [2.18.0] - 2025-08-01
12
+ ### Changed
13
+ - feat(expo-audio-studio): optimize buffer size on android to prevent oom ([32fcb9b](https://github.com/deeeed/expo-audio-stream/commit/32fcb9b0a965669b3a37c9860998ae46a1d26cd8))
14
+ - fix(expo-audio-studio): invalid paused duration on android ([c107258](https://github.com/deeeed/expo-audio-stream/commit/c107258054ebdbc733298c84b8d84b0f9f416e6e))
15
+ - chore(expo-audio-studio): release @siteed/expo-audio-studio@2.17.0 ([8a303b4](https://github.com/deeeed/expo-audio-stream/commit/8a303b4d96988b97604123d74daaa406d9ec517c))
16
+ ## [2.17.0] - 2025-07-31
17
+ ### Changed
18
+ - fix(expo-audio-studio): fix OutOfMemoryError by tracking stream position correctly ([b67e521](https://github.com/deeeed/expo-audio-stream/commit/b67e52142154d07873c5c1ec9c183d524d61e528))
19
+ - chore(expo-audio-studio): release @siteed/expo-audio-studio@2.16.2 ([c4291a8](https://github.com/deeeed/expo-audio-stream/commit/c4291a82cc740b4d4790c69ae7e7cc07f1e8fb1a))
11
20
  ## [2.16.2] - 2025-07-27
12
21
  ### Changed
13
22
  - chore(expo-audio-studio): release @siteed/expo-audio-studio@2.16.1 ([c9614d4](https://github.com/deeeed/expo-audio-stream/commit/c9614d4ebf87d73c3c5b2f7d6e60492fd5e45e64))
@@ -364,7 +373,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
364
373
  - Feature: Audio features extraction during recording.
365
374
  - Feature: Consistent WAV PCM recording format across all platforms.
366
375
 
367
- [unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.16.2...HEAD
376
+ [unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.18.0...HEAD
377
+ [2.18.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.17.0...@siteed/expo-audio-studio@2.18.0
378
+ [2.17.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.16.2...@siteed/expo-audio-studio@2.17.0
368
379
  [2.16.2]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.16.1...@siteed/expo-audio-studio@2.16.2
369
380
  [2.16.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.16.0...@siteed/expo-audio-studio@2.16.1
370
381
  [2.16.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.15.0...@siteed/expo-audio-studio@2.16.0
@@ -90,6 +90,7 @@ class AudioRecorderManager(
90
90
  private var pausedDuration = 0L
91
91
  private var lastEmittedSize = 0L
92
92
  private var lastEmittedCompressedSize = 0L
93
+ private var streamPosition = 0L // Track total bytes processed in the stream
93
94
  private val mainHandler = Handler(Looper.getMainLooper())
94
95
  private val audioRecordLock = Any()
95
96
  private var audioFileHandler: AudioFileHandler = AudioFileHandler(filesDir)
@@ -708,19 +709,29 @@ class AudioRecorderManager(
708
709
  )
709
710
 
710
711
  // Calculate buffer size based on bufferDurationSeconds if provided
711
- bufferSizeInBytes = recordingConfig.bufferDurationSeconds?.let { bufferDuration ->
712
+ var requestedBufferSize = recordingConfig.bufferDurationSeconds?.let { bufferDuration ->
712
713
  val bytesPerSample = when (recordingConfig.encoding) {
713
714
  "pcm_8bit" -> 1
714
715
  "pcm_16bit" -> 2
715
716
  "pcm_32bit" -> 4
716
717
  else -> 2
717
718
  }
718
- val requestedSize = (bufferDuration * recordingConfig.sampleRate *
719
- bytesPerSample * recordingConfig.channels).toInt()
720
- // Use the larger of requested size or minimum buffer size
721
- maxOf(requestedSize, minBufferSize)
719
+ (bufferDuration * recordingConfig.sampleRate * bytesPerSample * recordingConfig.channels).toInt()
722
720
  } ?: minBufferSize
723
721
 
722
+ LogUtils.d(CLASS_NAME, "Calculated minBufferSize: $minBufferSize bytes")
723
+ LogUtils.d(CLASS_NAME, "Requested buffer size: $requestedBufferSize bytes")
724
+
725
+ // Cap the buffer size to prevent OOM
726
+ val MAX_BUFFER_SIZE = 10485760 // 10MB
727
+ if (requestedBufferSize > MAX_BUFFER_SIZE) {
728
+ LogUtils.w(CLASS_NAME, "Requested buffer size $requestedBufferSize exceeds max limit of $MAX_BUFFER_SIZE, capping to max")
729
+ requestedBufferSize = MAX_BUFFER_SIZE
730
+ }
731
+
732
+ bufferSizeInBytes = maxOf(requestedBufferSize, minBufferSize)
733
+ LogUtils.d(CLASS_NAME, "Final bufferSizeInBytes: $bufferSizeInBytes (after capping and min check)")
734
+
724
735
  when {
725
736
  bufferSizeInBytes == AudioRecord.ERROR -> {
726
737
  LogUtils.e(CLASS_NAME, "Error getting minimum buffer size: ERROR")
@@ -942,6 +953,7 @@ class AudioRecorderManager(
942
953
  val bytesRead = audioRecord?.read(remainingData, 0, bufferSizeInBytes) ?: -1
943
954
  if (bytesRead > 0) {
944
955
  emitAudioData(remainingData.copyOfRange(0, bytesRead), bytesRead)
956
+ streamPosition += bytesRead // Update stream position for final data
945
957
  }
946
958
  }
947
959
 
@@ -966,6 +978,7 @@ class AudioRecorderManager(
966
978
  if (bytesRead > 0) {
967
979
  val emitStartTime = System.currentTimeMillis()
968
980
  emitAudioData(audioData.copyOfRange(0, bytesRead), bytesRead)
981
+ streamPosition += bytesRead // Update stream position for final data
969
982
  }
970
983
 
971
984
  LogUtils.d(CLASS_NAME, "Stopping recording state = ${audioRecord?.state}")
@@ -1250,7 +1263,14 @@ class AudioRecorderManager(
1250
1263
 
1251
1264
  // Use cached file size instead of file system call
1252
1265
  val fileSize = if (recordingConfig.output.primary.enabled) cachedPrimaryFileSize else 0L
1253
- val duration = if (!recordingConfig.output.primary.enabled) {
1266
+ val duration = if (isPaused.get()) {
1267
+ // Return frozen duration when paused using lastPauseTime
1268
+ if (lastPauseTime > 0) {
1269
+ lastPauseTime - recordingStartTime - pausedDuration
1270
+ } else {
1271
+ 0L
1272
+ }
1273
+ } else if (!recordingConfig.output.primary.enabled) {
1254
1274
  // For streaming-only mode, calculate duration from actual recording time
1255
1275
  val actualRecordingTime = if (recordingStartTime > 0) {
1256
1276
  System.currentTimeMillis() - recordingStartTime - pausedDuration
@@ -1420,7 +1440,7 @@ class AudioRecorderManager(
1420
1440
  while (_isRecording.get() && !Thread.currentThread().isInterrupted) {
1421
1441
  loopCount++
1422
1442
  if (loopCount % 100 == 0) {
1423
- LogUtils.d(CLASS_NAME, "Recording loop iteration $loopCount, isRecording: ${_isRecording.get()}")
1443
+ LogUtils.d(CLASS_NAME, "Recording loop iteration $loopCount, isRecording: ${_isRecording.get()}, accumulatedAudioSize: ${accumulatedAudioData.size()}, accumulatedAnalysisSize: ${accumulatedAnalysisData.size()}")
1424
1444
  }
1425
1445
  if (isPaused.get()) {
1426
1446
  Thread.sleep(100) // Add small delay when paused
@@ -1468,6 +1488,7 @@ class AudioRecorderManager(
1468
1488
  accumulatedAudioData.toByteArray(),
1469
1489
  accumulatedAudioData.size()
1470
1490
  )
1491
+ streamPosition += accumulatedAudioData.size() // Update stream position
1471
1492
  lastEmitTime = currentTime
1472
1493
  accumulatedAudioData.reset() // Clear the accumulator
1473
1494
  }
@@ -1549,9 +1570,15 @@ class AudioRecorderManager(
1549
1570
  val from = lastEmittedSize
1550
1571
  lastEmittedSize = fileSize
1551
1572
 
1552
- // Calculate position in milliseconds
1553
- val positionInMs =
1554
- (from * 1000) / (recordingConfig.sampleRate * recordingConfig.channels * (if (recordingConfig.encoding == "pcm_8bit") 8 else 16) / 8)
1573
+ // Calculate position in milliseconds using stream position
1574
+ val bytesPerSample = when (recordingConfig.encoding) {
1575
+ "pcm_8bit" -> 1
1576
+ "pcm_16bit" -> 2
1577
+ "pcm_32bit" -> 4
1578
+ else -> 2
1579
+ }
1580
+ val byteRate = recordingConfig.sampleRate * recordingConfig.channels * bytesPerSample
1581
+ val positionInMs = (streamPosition * 1000) / byteRate
1555
1582
 
1556
1583
  val compressionBundle = if (recordingConfig.output.compressed.enabled) {
1557
1584
  // For compressed files, we need to get actual size as MediaRecorder handles the writing
@@ -1700,6 +1727,7 @@ class AudioRecorderManager(
1700
1727
  totalRecordedTime = 0
1701
1728
  pausedDuration = 0
1702
1729
  lastEmittedSize = 0
1730
+ streamPosition = 0
1703
1731
  recordingStartTime = 0
1704
1732
 
1705
1733
  // Update the WAV header if needed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-studio",
3
- "version": "2.16.2",
3
+ "version": "2.18.0",
4
4
  "description": "Comprehensive audio processing library for React Native and Expo with recording, analysis, visualization, and streaming capabilities across iOS, Android, and web",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",