@siteed/expo-audio-studio 2.15.0 → 2.16.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,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
## [2.16.0] - 2025-07-27
|
|
12
|
+
### Changed
|
|
13
|
+
- feat(expo-audio-studio): optimize stop recording performance for long recording on android ([4553dc9](https://github.com/deeeed/expo-audio-stream/commit/4553dc9d2bd101a461f3f2eadfed63114f7d1b22))
|
|
14
|
+
- chore(expo-audio-studio): release @siteed/expo-audio-studio@2.15.0 ([1af374a](https://github.com/deeeed/expo-audio-stream/commit/1af374ada18ec2cd4edeb151fc0e91e54f783b9e))
|
|
11
15
|
## [2.15.0] - 2025-07-15
|
|
12
16
|
### Changed
|
|
13
17
|
- feat(android): add showPauseResumeActions option to notification config ([7456153](https://github.com/deeeed/expo-audio-stream/commit/7456153beb3f5041bd0199595b29d6b62c6b4c8f))
|
|
@@ -353,7 +357,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
353
357
|
- Feature: Audio features extraction during recording.
|
|
354
358
|
- Feature: Consistent WAV PCM recording format across all platforms.
|
|
355
359
|
|
|
356
|
-
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.
|
|
360
|
+
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.16.0...HEAD
|
|
361
|
+
[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
|
|
357
362
|
[2.15.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.14.9...@siteed/expo-audio-studio@2.15.0
|
|
358
363
|
[2.14.9]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.14.8...@siteed/expo-audio-studio@2.14.9
|
|
359
364
|
[2.14.8]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.14.7...@siteed/expo-audio-studio@2.14.8
|
|
@@ -921,6 +921,8 @@ class AudioRecorderManager(
|
|
|
921
921
|
}
|
|
922
922
|
|
|
923
923
|
fun stopRecording(promise: Promise) {
|
|
924
|
+
val stopStartTime = System.currentTimeMillis()
|
|
925
|
+
|
|
924
926
|
synchronized(audioRecordLock) {
|
|
925
927
|
if (!_isRecording.get()) {
|
|
926
928
|
LogUtils.e(CLASS_NAME, "Recording is not active")
|
|
@@ -933,7 +935,9 @@ class AudioRecorderManager(
|
|
|
933
935
|
var fileSize: Long = 0
|
|
934
936
|
|
|
935
937
|
try {
|
|
938
|
+
|
|
936
939
|
if (isPaused.get()) {
|
|
940
|
+
val readStartTime = System.currentTimeMillis()
|
|
937
941
|
val remainingData = ByteArray(bufferSizeInBytes)
|
|
938
942
|
val bytesRead = audioRecord?.read(remainingData, 0, bufferSizeInBytes) ?: -1
|
|
939
943
|
if (bytesRead > 0) {
|
|
@@ -942,6 +946,7 @@ class AudioRecorderManager(
|
|
|
942
946
|
}
|
|
943
947
|
|
|
944
948
|
if (recordingConfig.showNotification) {
|
|
949
|
+
val notificationStartTime = System.currentTimeMillis()
|
|
945
950
|
notificationManager.stopUpdates()
|
|
946
951
|
AudioRecordingService.stopService(context)
|
|
947
952
|
}
|
|
@@ -949,28 +954,23 @@ class AudioRecorderManager(
|
|
|
949
954
|
_isRecording.set(false)
|
|
950
955
|
isPrepared = false // Reset preparation state
|
|
951
956
|
|
|
952
|
-
//
|
|
953
|
-
//
|
|
954
|
-
val
|
|
955
|
-
|
|
956
|
-
} else {
|
|
957
|
-
0L
|
|
958
|
-
}
|
|
959
|
-
val estimatedFileSizeMB = (recordingDurationMs / 60000.0) * 5.0
|
|
960
|
-
val timeoutMs = maxOf(2000L, (estimatedFileSizeMB * 100).toLong()) // 100ms per MB, min 2 seconds
|
|
961
|
-
|
|
962
|
-
LogUtils.d(CLASS_NAME, "Waiting for recording thread to complete with timeout: ${timeoutMs}ms (estimated size: ${estimatedFileSizeMB}MB)")
|
|
957
|
+
// Use a reasonable fixed timeout for all cases
|
|
958
|
+
// The recording thread should exit quickly with non-blocking read
|
|
959
|
+
val timeoutMs = 2000L // 2 seconds should be more than enough
|
|
960
|
+
val threadJoinStartTime = System.currentTimeMillis()
|
|
963
961
|
recordingThread?.join(timeoutMs)
|
|
964
962
|
|
|
963
|
+
val finalReadStartTime = System.currentTimeMillis()
|
|
965
964
|
val audioData = ByteArray(bufferSizeInBytes)
|
|
966
965
|
val bytesRead = audioRecord?.read(audioData, 0, bufferSizeInBytes) ?: -1
|
|
967
|
-
LogUtils.d(CLASS_NAME, "Last Read $bytesRead bytes")
|
|
968
966
|
if (bytesRead > 0) {
|
|
967
|
+
val emitStartTime = System.currentTimeMillis()
|
|
969
968
|
emitAudioData(audioData.copyOfRange(0, bytesRead), bytesRead)
|
|
970
969
|
}
|
|
971
970
|
|
|
972
971
|
LogUtils.d(CLASS_NAME, "Stopping recording state = ${audioRecord?.state}")
|
|
973
972
|
if (audioRecord != null && audioRecord!!.state == AudioRecord.STATE_INITIALIZED) {
|
|
973
|
+
val audioStopStartTime = System.currentTimeMillis()
|
|
974
974
|
LogUtils.d(CLASS_NAME, "Stopping AudioRecord")
|
|
975
975
|
audioRecord!!.stop()
|
|
976
976
|
}
|
|
@@ -1003,6 +1003,7 @@ class AudioRecorderManager(
|
|
|
1003
1003
|
fileDuration
|
|
1004
1004
|
}
|
|
1005
1005
|
|
|
1006
|
+
val cleanupStartTime = System.currentTimeMillis()
|
|
1006
1007
|
cleanup()
|
|
1007
1008
|
} catch (e: IllegalStateException) {
|
|
1008
1009
|
LogUtils.e(CLASS_NAME, "Error reading from AudioRecord", e)
|
|
@@ -1015,16 +1016,25 @@ class AudioRecorderManager(
|
|
|
1015
1016
|
AudioProcessor.resetUniqueIdCounter()
|
|
1016
1017
|
audioProcessor.resetCumulativeAmplitudeRange()
|
|
1017
1018
|
|
|
1018
|
-
compressedRecorder
|
|
1019
|
-
|
|
1020
|
-
|
|
1019
|
+
if (compressedRecorder != null) {
|
|
1020
|
+
val compressedStopStartTime = System.currentTimeMillis()
|
|
1021
|
+
try {
|
|
1022
|
+
compressedRecorder?.stop()
|
|
1023
|
+
|
|
1024
|
+
val compressedReleaseStartTime = System.currentTimeMillis()
|
|
1025
|
+
compressedRecorder?.release()
|
|
1026
|
+
} catch (e: Exception) {
|
|
1027
|
+
LogUtils.e(CLASS_NAME, "Error stopping MediaRecorder: ${e.message}")
|
|
1028
|
+
}
|
|
1029
|
+
compressedRecorder = null
|
|
1021
1030
|
}
|
|
1022
|
-
compressedRecorder = null
|
|
1023
1031
|
|
|
1024
1032
|
// Log compressed file status if enabled - use actual file size for validation
|
|
1025
1033
|
if (recordingConfig.output.compressed.enabled) {
|
|
1034
|
+
val fileSizeStartTime = System.currentTimeMillis()
|
|
1026
1035
|
// Note: For compressed files, we need to get actual size as MediaRecorder handles the writing
|
|
1027
1036
|
// Use actual file size here for validation purposes only
|
|
1037
|
+
val compressedSizeStartTime = System.currentTimeMillis()
|
|
1028
1038
|
val compressedSize = compressedFile?.length() ?: 0
|
|
1029
1039
|
cachedCompressedFileSize = compressedSize // Update cache with final size
|
|
1030
1040
|
LogUtils.d(CLASS_NAME, "Compressed File validation - Size: $compressedSize bytes, Path: ${compressedFile?.absolutePath}")
|
|
@@ -1084,6 +1094,13 @@ class AudioRecorderManager(
|
|
|
1084
1094
|
) else null
|
|
1085
1095
|
)
|
|
1086
1096
|
}
|
|
1097
|
+
|
|
1098
|
+
// Log total stop duration if it's slow
|
|
1099
|
+
val stopDuration = System.currentTimeMillis() - stopStartTime
|
|
1100
|
+
if (stopDuration > 200) {
|
|
1101
|
+
LogUtils.w(CLASS_NAME, "Stop recording took ${stopDuration}ms - consider investigating")
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1087
1104
|
promise.resolve(result)
|
|
1088
1105
|
|
|
1089
1106
|
// Reset the timing variables
|
|
@@ -1399,7 +1416,12 @@ class AudioRecorderManager(
|
|
|
1399
1416
|
""".trimIndent())
|
|
1400
1417
|
|
|
1401
1418
|
// Recording loop
|
|
1419
|
+
var loopCount = 0
|
|
1402
1420
|
while (_isRecording.get() && !Thread.currentThread().isInterrupted) {
|
|
1421
|
+
loopCount++
|
|
1422
|
+
if (loopCount % 100 == 0) {
|
|
1423
|
+
LogUtils.d(CLASS_NAME, "Recording loop iteration $loopCount, _isRecording: ${_isRecording.get()}")
|
|
1424
|
+
}
|
|
1403
1425
|
if (isPaused.get()) {
|
|
1404
1426
|
Thread.sleep(100) // Add small delay when paused
|
|
1405
1427
|
continue
|
|
@@ -1416,7 +1438,8 @@ class AudioRecorderManager(
|
|
|
1416
1438
|
LogUtils.e(CLASS_NAME, "AudioRecord not initialized")
|
|
1417
1439
|
return@let -1
|
|
1418
1440
|
}
|
|
1419
|
-
|
|
1441
|
+
// Use non-blocking read mode to allow quick thread exit
|
|
1442
|
+
it.read(audioData, 0, bufferSizeInBytes, AudioRecord.READ_NON_BLOCKING).also { bytes ->
|
|
1420
1443
|
if (bytes < 0) {
|
|
1421
1444
|
LogUtils.e(CLASS_NAME, "AudioRecord read error: $bytes")
|
|
1422
1445
|
}
|
|
@@ -1486,6 +1509,9 @@ class AudioRecorderManager(
|
|
|
1486
1509
|
}
|
|
1487
1510
|
}
|
|
1488
1511
|
}
|
|
1512
|
+
} else if (bytesRead == 0) {
|
|
1513
|
+
// No data available yet, sleep briefly to avoid busy-waiting
|
|
1514
|
+
Thread.sleep(10)
|
|
1489
1515
|
}
|
|
1490
1516
|
}
|
|
1491
1517
|
} finally {
|
|
@@ -1673,7 +1699,14 @@ class AudioRecorderManager(
|
|
|
1673
1699
|
|
|
1674
1700
|
// Update the WAV header if needed
|
|
1675
1701
|
audioFile?.let { file ->
|
|
1676
|
-
|
|
1702
|
+
// Skip WAV header update if we're only doing compressed output
|
|
1703
|
+
if (::recordingConfig.isInitialized &&
|
|
1704
|
+
!recordingConfig.output.primary.enabled &&
|
|
1705
|
+
recordingConfig.output.compressed.enabled) {
|
|
1706
|
+
// Skip WAV header update for compressed-only recording
|
|
1707
|
+
} else {
|
|
1708
|
+
audioFileHandler.updateWavHeader(file)
|
|
1709
|
+
}
|
|
1677
1710
|
}
|
|
1678
1711
|
|
|
1679
1712
|
// Send event to notify that recording was stopped
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteed/expo-audio-studio",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.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",
|