@siteed/expo-audio-stream 1.7.2 → 1.8.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 +17 -1
- package/README.md +6 -1
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +39 -0
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +124 -12
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +26 -2
- package/build/AudioRecorder.provider.d.ts.map +1 -1
- package/build/AudioRecorder.provider.js +1 -0
- package/build/AudioRecorder.provider.js.map +1 -1
- package/build/ExpoAudioStream.types.d.ts +35 -1
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +14 -3
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +102 -38
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/WebRecorder.web.d.ts +11 -2
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +178 -43
- package/build/WebRecorder.web.js.map +1 -1
- package/build/events.d.ts +6 -0
- package/build/events.d.ts.map +1 -1
- package/build/events.js.map +1 -1
- package/build/useAudioRecorder.d.ts +3 -2
- package/build/useAudioRecorder.d.ts.map +1 -1
- package/build/useAudioRecorder.js +46 -5
- package/build/useAudioRecorder.js.map +1 -1
- package/ios/AudioStreamManager.swift +127 -8
- package/ios/AudioStreamManagerDelegate.swift +8 -2
- package/ios/ExpoAudioStreamModule.swift +61 -46
- package/ios/RecordingResult.swift +2 -0
- package/ios/RecordingSettings.swift +63 -3
- package/package.json +1 -1
- package/src/AudioRecorder.provider.tsx +1 -0
- package/src/ExpoAudioStream.types.ts +38 -1
- package/src/ExpoAudioStream.web.ts +114 -38
- package/src/WebRecorder.web.ts +210 -64
- package/src/events.ts +7 -0
- package/src/useAudioRecorder.tsx +70 -8
package/CHANGELOG.md
CHANGED
|
@@ -8,13 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
## [1.8.0] - 2025-01-10
|
|
12
|
+
- feat(audio): implement audio compression support ([ff4e060](https://github.com/deeeed/expo-audio-stream/commit/ff4e060fef1061804c1cc0126d4344d2d50daa9a))
|
|
13
|
+
|
|
11
14
|
## [1.7.2] - 2025-01-07
|
|
12
15
|
- fix(audio-stream): correct WAV header handling in web audio recording ([9ba7de5](https://github.com/deeeed/expo-audio-stream/commit/9ba7de5b96ca4cc937dea261c80d3fda9c99e8f4))
|
|
13
16
|
|
|
17
|
+
|
|
14
18
|
## [1.7.1] - 2025-01-07
|
|
15
19
|
- update notification to avoid triggering new alerts (#71) ([32dcfc5](https://github.com/deeeed/expo-audio-stream/commit/32dcfc55daf3236babefc17016f329c177d466fd))
|
|
16
20
|
|
|
17
21
|
|
|
22
|
+
|
|
18
23
|
## [1.7.0] - 2025-01-05
|
|
19
24
|
- feat(playground): enhance app configuration and build setup for production deployment (#58) ([929d443](https://github.com/deeeed/expo-audio-stream/commit/929d443145378b1430d215db5c00b13758420e2b))
|
|
20
25
|
- chore(expo-audio-stream): release @siteed/expo-audio-stream@1.6.1 ([084e8ad](https://github.com/deeeed/expo-audio-stream/commit/084e8adb91da7874c9e608b55d9c7b2ffd7a8327))
|
|
@@ -25,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
25
30
|
|
|
26
31
|
|
|
27
32
|
|
|
33
|
+
|
|
28
34
|
## [1.6.1] - 2024-12-11
|
|
29
35
|
- chore(expo-audio-stream): remove git commit step from publish script ([4a772ce](https://github.com/deeeed/expo-audio-stream/commit/4a772ce93bb7405d9b8e981f46bdf8941a71ecfe))
|
|
30
36
|
- chore: more publishing automation ([3693021](https://github.com/deeeed/expo-audio-stream/commit/369302107f9dca9dddd8ae68e6214481a39976ac))
|
|
@@ -39,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
39
45
|
|
|
40
46
|
|
|
41
47
|
|
|
48
|
+
|
|
42
49
|
## [1.5.0] - 2024-12-10
|
|
43
50
|
- UNPUBLISHED because of a bug in the build system
|
|
44
51
|
|
|
@@ -46,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
46
53
|
|
|
47
54
|
|
|
48
55
|
|
|
56
|
+
|
|
49
57
|
## [1.4.0] - 2024-12-05
|
|
50
58
|
- chore: remove unusded dependencies ([ad81dd5](https://github.com/deeeed/expo-audio-stream/commit/ad81dd560c93dd1d04995a323a4ae72d4de20f3e))
|
|
51
59
|
|
|
@@ -53,6 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
53
61
|
|
|
54
62
|
|
|
55
63
|
|
|
64
|
+
|
|
56
65
|
## [1.3.1] - 2024-12-05
|
|
57
66
|
- feat(web): implement throttling and optimize event processing (#49) ([da28765](https://github.com/deeeed/expo-audio-stream/commit/da2876524c2c9d6e0a980fde40a0197b929d8a7f))
|
|
58
67
|
|
|
@@ -60,6 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
60
69
|
|
|
61
70
|
|
|
62
71
|
|
|
72
|
+
|
|
63
73
|
## [1.3.0] - 2024-11-28
|
|
64
74
|
### Added
|
|
65
75
|
- refactor(permissions): standardize permission status response structure across platforms (#44) ([7c9c800](https://github.com/deeeed/expo-audio-stream/commit/7c9c800d83b7cea3516643371484d5e1f3b99e4c))
|
|
@@ -72,6 +82,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
72
82
|
|
|
73
83
|
|
|
74
84
|
|
|
85
|
+
|
|
75
86
|
## [1.2.5] - 2024-11-12
|
|
76
87
|
### Added
|
|
77
88
|
- docs(license): add MIT license to all packages (6 files changed)
|
|
@@ -81,6 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
81
92
|
|
|
82
93
|
|
|
83
94
|
|
|
95
|
+
|
|
84
96
|
## [1.2.4] - 2024-11-05
|
|
85
97
|
### Changed
|
|
86
98
|
- Android minimum audio interval set to 10ms.
|
|
@@ -93,6 +105,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
93
105
|
|
|
94
106
|
|
|
95
107
|
|
|
108
|
+
|
|
96
109
|
## [1.2.0] - 2024-10-24
|
|
97
110
|
### Added
|
|
98
111
|
- Feature: Keep device awake during recording with `keepAwake` option
|
|
@@ -105,6 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
105
118
|
|
|
106
119
|
|
|
107
120
|
|
|
121
|
+
|
|
108
122
|
## [1.1.17] - 2024-10-21
|
|
109
123
|
### Added
|
|
110
124
|
- Support bluetooth headset on ios
|
|
@@ -114,6 +128,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
114
128
|
|
|
115
129
|
|
|
116
130
|
|
|
131
|
+
|
|
117
132
|
## [1.0.0] - 2024-04-01
|
|
118
133
|
### Added
|
|
119
134
|
- Initial release of @siteed/expo-audio-stream.
|
|
@@ -124,7 +139,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
124
139
|
- Feature: Audio features extraction during recording.
|
|
125
140
|
- Feature: Consistent WAV PCM recording format across all platforms.
|
|
126
141
|
|
|
127
|
-
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.
|
|
142
|
+
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.8.0...HEAD
|
|
143
|
+
[1.8.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.7.2...@siteed/expo-audio-stream@1.8.0
|
|
128
144
|
[1.7.2]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.7.1...@siteed/expo-audio-stream@1.7.2
|
|
129
145
|
[1.7.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.7.0...@siteed/expo-audio-stream@1.7.1
|
|
130
146
|
[1.7.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.6.1...@siteed/expo-audio-stream@1.7.0
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<div align="center">
|
|
10
10
|
<h2 align="center">
|
|
11
11
|
<br />
|
|
12
|
-
<strong>Comprehensive library designed to facilitate real-time audio processing and streaming across iOS, Android, and web platforms.
|
|
12
|
+
<strong>Comprehensive library designed to facilitate real-time audio processing and streaming across iOS, Android, and web platforms, with support for dual-stream recording and audio compression.
|
|
13
13
|
<br />
|
|
14
14
|
<br />
|
|
15
15
|
<a href="https://deeeed.github.io/expo-audio-stream/playground/">
|
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
## Features
|
|
33
33
|
|
|
34
34
|
- Real-time audio streaming across iOS, Android, and web.
|
|
35
|
+
- Dual-stream recording capabilities:
|
|
36
|
+
- Simultaneous raw PCM and compressed audio recording
|
|
37
|
+
- Compression formats: OPUS or AAC
|
|
38
|
+
- Configurable bitrate for compressed audio
|
|
39
|
+
- Optimized storage for both high-quality and compressed formats
|
|
35
40
|
- Configurable intervals for audio buffer receipt.
|
|
36
41
|
- Automated microphone permissions setup in managed Expo projects.
|
|
37
42
|
- Background audio recording on iOS.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
package net.siteed.audiostream
|
|
2
2
|
|
|
3
|
+
import android.util.Log
|
|
3
4
|
import java.io.File
|
|
4
5
|
import java.io.IOException
|
|
5
6
|
import java.io.OutputStream
|
|
6
7
|
import java.io.RandomAccessFile
|
|
8
|
+
import java.util.UUID
|
|
7
9
|
|
|
8
10
|
class AudioFileHandler(private val filesDir: File) {
|
|
9
11
|
// Method to write WAV file header
|
|
@@ -89,4 +91,41 @@ class AudioFileHandler(private val filesDir: File) {
|
|
|
89
91
|
it.delete()
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
|
|
95
|
+
fun createAudioFile(extension: String): File {
|
|
96
|
+
val timestamp = System.currentTimeMillis()
|
|
97
|
+
val uuid = UUID.randomUUID().toString()
|
|
98
|
+
val filename = "recording_${timestamp}_${uuid}.${extension}"
|
|
99
|
+
|
|
100
|
+
return try {
|
|
101
|
+
File(filesDir, filename).apply {
|
|
102
|
+
parentFile?.mkdirs() // Create directories if they don't exist
|
|
103
|
+
createNewFile() // Create the file
|
|
104
|
+
}
|
|
105
|
+
} catch (e: Exception) {
|
|
106
|
+
Log.e(Constants.TAG, "Failed to create audio file", e)
|
|
107
|
+
throw e
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fun deleteFile(file: File?): Boolean {
|
|
112
|
+
return try {
|
|
113
|
+
if (file == null) {
|
|
114
|
+
Log.w(Constants.TAG, "Attempted to delete null file")
|
|
115
|
+
false
|
|
116
|
+
} else if (!file.exists()) {
|
|
117
|
+
Log.w(Constants.TAG, "File does not exist: ${file.absolutePath}")
|
|
118
|
+
false
|
|
119
|
+
} else {
|
|
120
|
+
val wasDeleted = file.delete()
|
|
121
|
+
if (!wasDeleted) {
|
|
122
|
+
Log.w(Constants.TAG, "Failed to delete file: ${file.absolutePath}")
|
|
123
|
+
}
|
|
124
|
+
wasDeleted
|
|
125
|
+
}
|
|
126
|
+
} catch (e: Exception) {
|
|
127
|
+
Log.e(Constants.TAG, "Error deleting file: ${file?.absolutePath}", e)
|
|
128
|
+
false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
92
131
|
}
|
|
@@ -45,6 +45,7 @@ class AudioRecorderManager(
|
|
|
45
45
|
private var lastPauseTime = 0L
|
|
46
46
|
private var pausedDuration = 0L
|
|
47
47
|
private var lastEmittedSize = 0L
|
|
48
|
+
private var lastEmittedCompressedSize = 0L
|
|
48
49
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
49
50
|
private val audioRecordLock = Any()
|
|
50
51
|
private var audioFileHandler: AudioFileHandler = AudioFileHandler(filesDir)
|
|
@@ -59,6 +60,9 @@ class AudioRecorderManager(
|
|
|
59
60
|
private var wasWakeLockEnabled = false
|
|
60
61
|
private val notificationManager = AudioNotificationManager.getInstance(context)
|
|
61
62
|
|
|
63
|
+
private var compressedRecorder: MediaRecorder? = null
|
|
64
|
+
private var compressedFile: File? = null
|
|
65
|
+
|
|
62
66
|
companion object {
|
|
63
67
|
@SuppressLint("StaticFieldLeak")
|
|
64
68
|
@Volatile
|
|
@@ -117,17 +121,39 @@ class AudioRecorderManager(
|
|
|
117
121
|
|
|
118
122
|
if (!initializeAudioRecord(promise)) return
|
|
119
123
|
|
|
124
|
+
if (recordingConfig.enableCompressedOutput && !initializeCompressedRecorder(
|
|
125
|
+
if (recordingConfig.compressedFormat == "aac") "aac" else "opus",
|
|
126
|
+
promise
|
|
127
|
+
)) return
|
|
128
|
+
|
|
120
129
|
if (!initializeRecordingResources(audioFormatInfo.fileExtension, promise)) return
|
|
121
130
|
|
|
122
131
|
if (!startRecordingProcess(promise)) return
|
|
123
132
|
|
|
124
|
-
//
|
|
133
|
+
// Start compressed recording if enabled
|
|
134
|
+
try {
|
|
135
|
+
compressedRecorder?.start()
|
|
136
|
+
} catch (e: Exception) {
|
|
137
|
+
Log.e(Constants.TAG, "Failed to start compressed recording", e)
|
|
138
|
+
cleanup()
|
|
139
|
+
promise.reject("COMPRESSED_START_FAILED", "Failed to start compressed recording", e)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Return success result with both file URIs
|
|
125
144
|
val result = bundleOf(
|
|
126
145
|
"fileUri" to audioFile?.toURI().toString(),
|
|
127
146
|
"channels" to recordingConfig.channels,
|
|
128
147
|
"bitDepth" to AudioFormatUtils.getBitDepth(recordingConfig.encoding),
|
|
129
148
|
"sampleRate" to recordingConfig.sampleRate,
|
|
130
|
-
"mimeType" to mimeType
|
|
149
|
+
"mimeType" to mimeType,
|
|
150
|
+
"compression" to if (compressedFile != null) bundleOf(
|
|
151
|
+
"mimeType" to if (recordingConfig.compressedFormat == "aac") "audio/aac" else "audio/opus",
|
|
152
|
+
"bitrate" to recordingConfig.compressedBitRate,
|
|
153
|
+
"format" to recordingConfig.compressedFormat,
|
|
154
|
+
"size" to 0,
|
|
155
|
+
"compressedFileUri" to compressedFile?.toURI().toString()
|
|
156
|
+
) else null
|
|
131
157
|
)
|
|
132
158
|
promise.resolve(result)
|
|
133
159
|
|
|
@@ -458,21 +484,30 @@ class AudioRecorderManager(
|
|
|
458
484
|
// Calculate duration based on the data size and byte rate
|
|
459
485
|
val duration = if (byteRate > 0) (dataFileSize * 1000 / byteRate) else 0
|
|
460
486
|
|
|
487
|
+
compressedRecorder?.apply {
|
|
488
|
+
stop()
|
|
489
|
+
release()
|
|
490
|
+
}
|
|
491
|
+
compressedRecorder = null
|
|
492
|
+
|
|
493
|
+
|
|
461
494
|
// Create result bundle
|
|
462
495
|
val result = bundleOf(
|
|
463
496
|
"fileUri" to audioFile?.toURI().toString(),
|
|
464
497
|
"filename" to audioFile?.name,
|
|
465
498
|
"durationMs" to duration,
|
|
466
499
|
"channels" to recordingConfig.channels,
|
|
467
|
-
"bitDepth" to
|
|
468
|
-
"pcm_8bit" -> 8
|
|
469
|
-
"pcm_16bit" -> 16
|
|
470
|
-
"pcm_32bit" -> 32
|
|
471
|
-
else -> 16 // Default to 16 if the encoding is not recognized
|
|
472
|
-
},
|
|
500
|
+
"bitDepth" to AudioFormatUtils.getBitDepth(recordingConfig.encoding),
|
|
473
501
|
"sampleRate" to recordingConfig.sampleRate,
|
|
474
502
|
"size" to fileSize,
|
|
475
|
-
"mimeType" to mimeType
|
|
503
|
+
"mimeType" to mimeType,
|
|
504
|
+
"compression" to if (compressedFile != null) bundleOf(
|
|
505
|
+
"size" to compressedFile?.length(),
|
|
506
|
+
"mimeType" to if (recordingConfig.compressedFormat == "aac") "audio/aac" else "audio/opus",
|
|
507
|
+
"bitrate" to recordingConfig.compressedBitRate,
|
|
508
|
+
"format" to recordingConfig.compressedFormat,
|
|
509
|
+
"compressedFileUri" to compressedFile?.toURI().toString()
|
|
510
|
+
) else null
|
|
476
511
|
)
|
|
477
512
|
promise.resolve(result)
|
|
478
513
|
|
|
@@ -560,15 +595,27 @@ class AudioRecorderManager(
|
|
|
560
595
|
}
|
|
561
596
|
|
|
562
597
|
// "audio/opus", "audio/aac" -> getCompressedAudioDuration(audioFile)
|
|
563
|
-
else ->
|
|
598
|
+
else -> totalRecordedTime
|
|
564
599
|
}
|
|
600
|
+
|
|
601
|
+
val compressionBundle = if (recordingConfig.enableCompressedOutput) {
|
|
602
|
+
bundleOf(
|
|
603
|
+
"size" to (compressedFile?.length() ?: 0),
|
|
604
|
+
"mimeType" to if (recordingConfig.compressedFormat == "aac") "audio/aac" else "audio/opus",
|
|
605
|
+
"bitrate" to recordingConfig.compressedBitRate,
|
|
606
|
+
"format" to recordingConfig.compressedFormat
|
|
607
|
+
)
|
|
608
|
+
} else null
|
|
609
|
+
|
|
610
|
+
|
|
565
611
|
return bundleOf(
|
|
566
612
|
"durationMs" to duration,
|
|
567
613
|
"isRecording" to isRecording.get(),
|
|
568
614
|
"isPaused" to isPaused.get(),
|
|
569
615
|
"mimeType" to mimeType,
|
|
570
616
|
"size" to totalDataSize,
|
|
571
|
-
"interval" to recordingConfig.interval
|
|
617
|
+
"interval" to recordingConfig.interval,
|
|
618
|
+
"compression" to compressionBundle
|
|
572
619
|
)
|
|
573
620
|
}
|
|
574
621
|
}
|
|
@@ -704,6 +751,36 @@ class AudioRecorderManager(
|
|
|
704
751
|
val positionInMs =
|
|
705
752
|
(from * 1000) / (recordingConfig.sampleRate * recordingConfig.channels * (if (recordingConfig.encoding == "pcm_8bit") 8 else 16) / 8)
|
|
706
753
|
|
|
754
|
+
val compressionBundle = if (recordingConfig.enableCompressedOutput) {
|
|
755
|
+
val compressedSize = compressedFile?.length() ?: 0
|
|
756
|
+
val eventDataSize = compressedSize - lastEmittedCompressedSize
|
|
757
|
+
|
|
758
|
+
// Read the new compressed data
|
|
759
|
+
val compressedData = if (eventDataSize > 0) {
|
|
760
|
+
try {
|
|
761
|
+
compressedFile?.inputStream()?.use { input ->
|
|
762
|
+
input.skip(lastEmittedCompressedSize)
|
|
763
|
+
val buffer = ByteArray(eventDataSize.toInt())
|
|
764
|
+
input.read(buffer)
|
|
765
|
+
audioDataEncoder.encodeToBase64(buffer)
|
|
766
|
+
}
|
|
767
|
+
} catch (e: Exception) {
|
|
768
|
+
Log.e(Constants.TAG, "Failed to read compressed data", e)
|
|
769
|
+
null
|
|
770
|
+
}
|
|
771
|
+
} else null
|
|
772
|
+
|
|
773
|
+
lastEmittedCompressedSize = compressedSize
|
|
774
|
+
|
|
775
|
+
bundleOf(
|
|
776
|
+
"position" to positionInMs,
|
|
777
|
+
"fileUri" to compressedFile?.toURI().toString(),
|
|
778
|
+
"eventDataSize" to eventDataSize,
|
|
779
|
+
"totalSize" to compressedSize,
|
|
780
|
+
"data" to compressedData
|
|
781
|
+
)
|
|
782
|
+
} else null
|
|
783
|
+
|
|
707
784
|
mainHandler.post {
|
|
708
785
|
try {
|
|
709
786
|
eventSender.sendExpoEvent(
|
|
@@ -715,7 +792,8 @@ class AudioRecorderManager(
|
|
|
715
792
|
"position" to positionInMs,
|
|
716
793
|
"mimeType" to mimeType,
|
|
717
794
|
"totalSize" to fileSize,
|
|
718
|
-
"streamUuid" to streamUuid
|
|
795
|
+
"streamUuid" to streamUuid,
|
|
796
|
+
"compression" to compressionBundle
|
|
719
797
|
)
|
|
720
798
|
)
|
|
721
799
|
} catch (e: Exception) {
|
|
@@ -787,4 +865,38 @@ class AudioRecorderManager(
|
|
|
787
865
|
releaseWakeLock()
|
|
788
866
|
}
|
|
789
867
|
}
|
|
868
|
+
|
|
869
|
+
private fun initializeCompressedRecorder(fileExtension: String, promise: Promise): Boolean {
|
|
870
|
+
try {
|
|
871
|
+
// Use the existing audioFileHandler instance
|
|
872
|
+
compressedFile = audioFileHandler.createAudioFile(fileExtension)
|
|
873
|
+
|
|
874
|
+
compressedRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
875
|
+
MediaRecorder(context)
|
|
876
|
+
} else {
|
|
877
|
+
@Suppress("DEPRECATION")
|
|
878
|
+
MediaRecorder()
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
compressedRecorder?.apply {
|
|
882
|
+
setAudioSource(MediaRecorder.AudioSource.MIC)
|
|
883
|
+
setOutputFormat(if (recordingConfig.compressedFormat == "aac")
|
|
884
|
+
MediaRecorder.OutputFormat.AAC_ADTS
|
|
885
|
+
else MediaRecorder.OutputFormat.OGG)
|
|
886
|
+
setAudioEncoder(if (recordingConfig.compressedFormat == "aac")
|
|
887
|
+
MediaRecorder.AudioEncoder.AAC
|
|
888
|
+
else MediaRecorder.AudioEncoder.OPUS)
|
|
889
|
+
setAudioChannels(recordingConfig.channels)
|
|
890
|
+
setAudioSamplingRate(recordingConfig.sampleRate)
|
|
891
|
+
setAudioEncodingBitRate(recordingConfig.compressedBitRate)
|
|
892
|
+
setOutputFile(compressedFile?.absolutePath)
|
|
893
|
+
prepare()
|
|
894
|
+
}
|
|
895
|
+
return true
|
|
896
|
+
} catch (e: Exception) {
|
|
897
|
+
Log.e(Constants.TAG, "Failed to initialize compressed recorder", e)
|
|
898
|
+
promise.reject("COMPRESSED_INIT_FAILED", "Failed to initialize compressed recorder", e)
|
|
899
|
+
return false
|
|
900
|
+
}
|
|
901
|
+
}
|
|
790
902
|
}
|
|
@@ -15,7 +15,10 @@ data class RecordingConfig(
|
|
|
15
15
|
val showNotification: Boolean = false,
|
|
16
16
|
val showWaveformInNotification: Boolean = false,
|
|
17
17
|
val notification: NotificationConfig = NotificationConfig(),
|
|
18
|
-
val features: Map<String, Boolean> = emptyMap()
|
|
18
|
+
val features: Map<String, Boolean> = emptyMap(),
|
|
19
|
+
val enableCompressedOutput: Boolean = false,
|
|
20
|
+
val compressedFormat: String = "opus",
|
|
21
|
+
val compressedBitRate: Int = 24000,
|
|
19
22
|
) {
|
|
20
23
|
companion object {
|
|
21
24
|
fun fromMap(options: Map<String, Any?>?): Result<Pair<RecordingConfig, AudioFormatInfo>> {
|
|
@@ -36,6 +39,24 @@ data class RecordingConfig(
|
|
|
36
39
|
val notificationMap = options.getTypedMap<Any?>("notification") { true }
|
|
37
40
|
val notificationConfig = NotificationConfig.fromMap(notificationMap)
|
|
38
41
|
|
|
42
|
+
// Parse compression config
|
|
43
|
+
val compressionMap = options.getTypedMap<Any?>("compression") { true }
|
|
44
|
+
val enableCompressedOutput = compressionMap["enabled"] as? Boolean ?: false
|
|
45
|
+
val compressedFormat = (compressionMap["format"] as? String)?.lowercase() ?: "aac"
|
|
46
|
+
val compressedBitRate = (compressionMap["bitrate"] as? Number)?.toInt() ?: 128000
|
|
47
|
+
|
|
48
|
+
// Validate bitrate if compression is enabled
|
|
49
|
+
if (enableCompressedOutput) {
|
|
50
|
+
when {
|
|
51
|
+
compressedBitRate < 8000 -> return Result.failure(
|
|
52
|
+
IllegalArgumentException("Bitrate must be at least 8000 bps")
|
|
53
|
+
)
|
|
54
|
+
compressedBitRate > 960000 -> return Result.failure(
|
|
55
|
+
IllegalArgumentException("Bitrate cannot exceed 960000 bps")
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
// Initialize the recording configuration
|
|
40
61
|
val tempRecordingConfig = RecordingConfig(
|
|
41
62
|
sampleRate = options.getNumberOrDefault("sampleRate", Constants.DEFAULT_SAMPLE_RATE),
|
|
@@ -49,7 +70,10 @@ data class RecordingConfig(
|
|
|
49
70
|
showNotification = options.getBooleanOrDefault("showNotification", false),
|
|
50
71
|
showWaveformInNotification = options.getBooleanOrDefault("showWaveformInNotification", false),
|
|
51
72
|
notification = notificationConfig,
|
|
52
|
-
features = features
|
|
73
|
+
features = features,
|
|
74
|
+
enableCompressedOutput = enableCompressedOutput,
|
|
75
|
+
compressedFormat = compressedFormat,
|
|
76
|
+
compressedBitRate = compressedBitRate
|
|
53
77
|
)
|
|
54
78
|
|
|
55
79
|
// Validate sample rate and channels
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioRecorder.provider.d.ts","sourceRoot":"","sources":["../src/AudioRecorder.provider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoC,MAAM,OAAO,CAAA;AAExD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAoB,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"AudioRecorder.provider.d.ts","sourceRoot":"","sources":["../src/AudioRecorder.provider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoC,MAAM,OAAO,CAAA;AAExD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAoB,MAAM,oBAAoB,CAAA;AAwB5E,UAAU,0BAA0B;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,MAAM,CAAC,EAAE,qBAAqB,CAAA;CACjC;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAUtE,CAAA;AAED,eAAO,MAAM,sBAAsB,6BAQlC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioRecorder.provider.js","sourceRoot":"","sources":["../src/AudioRecorder.provider.tsx"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAGxD,OAAO,EAAyB,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE5E,MAAM,WAAW,GAA0B;IACvC,WAAW,EAAE,KAAK;IAClB,QAAQ,EAAE,KAAK;IACf,UAAU,EAAE,CAAC;IACb,IAAI,EAAE,CAAC;IACP,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;CACJ,CAAA;AAED,MAAM,oBAAoB,GAAG,aAAa,CAAwB,WAAW,CAAC,CAAA;AAO9E,MAAM,CAAC,MAAM,qBAAqB,GAAyC,CAAC,EACxE,QAAQ,EACR,MAAM,GAAG,EAAE,GACd,EAAE,EAAE;IACD,MAAM,aAAa,GAAG,gBAAgB,CAAC,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;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,EAAE;IACvC,MAAM,OAAO,GAAG,UAAU,CAAC,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","sourcesContent":["// packages/expo-audio-stream/src/AudioRecorder.provider.tsx\nimport React, { createContext, useContext } from 'react'\n\nimport { UseAudioRecorderState } from './ExpoAudioStream.types'\nimport { UseAudioRecorderProps, useAudioRecorder } from './useAudioRecorder'\n\nconst initContext: UseAudioRecorderState = {\n isRecording: false,\n isPaused: false,\n durationMs: 0,\n size: 0,\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}\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,4DAA4D;AAC5D,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAGxD,OAAO,EAAyB,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;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;CACJ,CAAA;AAED,MAAM,oBAAoB,GAAG,aAAa,CAAwB,WAAW,CAAC,CAAA;AAO9E,MAAM,CAAC,MAAM,qBAAqB,GAAyC,CAAC,EACxE,QAAQ,EACR,MAAM,GAAG,EAAE,GACd,EAAE,EAAE;IACD,MAAM,aAAa,GAAG,gBAAgB,CAAC,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;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,EAAE;IACvC,MAAM,OAAO,GAAG,UAAU,CAAC,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","sourcesContent":["// packages/expo-audio-stream/src/AudioRecorder.provider.tsx\nimport React, { createContext, useContext } from 'react'\n\nimport { UseAudioRecorderState } from './ExpoAudioStream.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}\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,5 +1,11 @@
|
|
|
1
1
|
import { AmplitudeAlgorithm, AudioAnalysis, AudioFeaturesOptions } from './AudioAnalysis/AudioAnalysis.types';
|
|
2
2
|
import { AudioAnalysisEvent } from './events';
|
|
3
|
+
export interface CompressionInfo {
|
|
4
|
+
size: number;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
bitrate: number;
|
|
7
|
+
format: string;
|
|
8
|
+
}
|
|
3
9
|
export interface AudioStreamStatus {
|
|
4
10
|
isRecording: boolean;
|
|
5
11
|
isPaused: boolean;
|
|
@@ -7,6 +13,7 @@ export interface AudioStreamStatus {
|
|
|
7
13
|
size: number;
|
|
8
14
|
interval: number;
|
|
9
15
|
mimeType: string;
|
|
16
|
+
compression?: CompressionInfo;
|
|
10
17
|
}
|
|
11
18
|
export interface AudioDataEvent {
|
|
12
19
|
data: string | Float32Array;
|
|
@@ -14,6 +21,9 @@ export interface AudioDataEvent {
|
|
|
14
21
|
fileUri: string;
|
|
15
22
|
eventDataSize: number;
|
|
16
23
|
totalSize: number;
|
|
24
|
+
compression?: CompressionInfo & {
|
|
25
|
+
data?: string | Blob;
|
|
26
|
+
};
|
|
17
27
|
}
|
|
18
28
|
export type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit';
|
|
19
29
|
export type SampleRate = 16000 | 44100 | 48000;
|
|
@@ -48,6 +58,9 @@ export interface AudioRecording {
|
|
|
48
58
|
transcripts?: TranscriberData[];
|
|
49
59
|
wavPCMData?: Float32Array;
|
|
50
60
|
analysisData?: AudioAnalysis;
|
|
61
|
+
compression?: CompressionInfo & {
|
|
62
|
+
compressedFileUri: string;
|
|
63
|
+
};
|
|
51
64
|
}
|
|
52
65
|
export interface StartRecordingResult {
|
|
53
66
|
fileUri: string;
|
|
@@ -55,6 +68,9 @@ export interface StartRecordingResult {
|
|
|
55
68
|
channels?: number;
|
|
56
69
|
bitDepth?: BitDepth;
|
|
57
70
|
sampleRate?: SampleRate;
|
|
71
|
+
compression?: CompressionInfo & {
|
|
72
|
+
compressedFileUri: string;
|
|
73
|
+
};
|
|
58
74
|
}
|
|
59
75
|
export interface AudioSessionConfig {
|
|
60
76
|
category?: 'Ambient' | 'SoloAmbient' | 'Playback' | 'Record' | 'PlayAndRecord' | 'MultiRoute';
|
|
@@ -80,6 +96,11 @@ export interface RecordingConfig {
|
|
|
80
96
|
features?: AudioFeaturesOptions;
|
|
81
97
|
onAudioStream?: (_: AudioDataEvent) => Promise<void>;
|
|
82
98
|
onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void>;
|
|
99
|
+
compression?: {
|
|
100
|
+
enabled: boolean;
|
|
101
|
+
format: 'aac' | 'opus' | 'mp3';
|
|
102
|
+
bitrate?: number;
|
|
103
|
+
};
|
|
83
104
|
}
|
|
84
105
|
export interface NotificationConfig {
|
|
85
106
|
title?: string;
|
|
@@ -113,15 +134,28 @@ export interface WaveformConfig {
|
|
|
113
134
|
mirror?: boolean;
|
|
114
135
|
height?: number;
|
|
115
136
|
}
|
|
137
|
+
export interface WebRecordingOptions {
|
|
138
|
+
/**
|
|
139
|
+
* Web-specific option to skip the final audio data consolidation process.
|
|
140
|
+
* When true, it will:
|
|
141
|
+
* - Skip the time-consuming process of concatenating all audio chunks
|
|
142
|
+
* - Return immediately with the compressed audio (if compression is enabled)
|
|
143
|
+
* - Improve performance when stopping large recordings
|
|
144
|
+
* - Useful when only the compressed audio is needed (e.g., when not using transcription)
|
|
145
|
+
* @default false
|
|
146
|
+
*/
|
|
147
|
+
skipFinalConsolidation?: boolean;
|
|
148
|
+
}
|
|
116
149
|
export interface UseAudioRecorderState {
|
|
117
150
|
startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>;
|
|
118
|
-
stopRecording: () => Promise<AudioRecording | null>;
|
|
151
|
+
stopRecording: (options?: WebRecordingOptions) => Promise<AudioRecording | null>;
|
|
119
152
|
pauseRecording: () => Promise<void>;
|
|
120
153
|
resumeRecording: () => Promise<void>;
|
|
121
154
|
isRecording: boolean;
|
|
122
155
|
isPaused: boolean;
|
|
123
156
|
durationMs: number;
|
|
124
157
|
size: number;
|
|
158
|
+
compression?: CompressionInfo;
|
|
125
159
|
analysisData?: AudioAnalysis;
|
|
126
160
|
}
|
|
127
161
|
//# sourceMappingURL=ExpoAudioStream.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStream.types.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"AACA,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACvB,MAAM,qCAAqC,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAE7C,MAAM,WAAW,iBAAiB;IAC9B,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"ExpoAudioStream.types.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"","sourcesContent":["// packages/expo-audio-stream/src/ExpoAudioStream.types.ts\nimport {\n AmplitudeAlgorithm,\n AudioAnalysis,\n AudioFeaturesOptions,\n} from './AudioAnalysis/AudioAnalysis.types'\nimport { AudioAnalysisEvent } from './events'\n\nexport interface AudioStreamStatus {\n isRecording: boolean\n isPaused: boolean\n durationMs: number\n size: number\n interval: number\n mimeType: string\n}\n\nexport interface AudioDataEvent {\n data: string | Float32Array\n position: number\n fileUri: string\n eventDataSize: number\n totalSize: number\n}\n\nexport type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'\nexport type SampleRate = 16000 | 44100 | 48000\nexport type BitDepth = 8 | 16 | 32\n\nexport type ConsoleLike = {\n log: (message: string, ...args: unknown[]) => void\n debug: (message: string, ...args: unknown[]) => void\n warn: (message: string, ...args: unknown[]) => void\n error: (message: string, ...args: unknown[]) => void\n}\n\nexport interface Chunk {\n text: string\n timestamp: [number, number | null]\n}\n\nexport interface TranscriberData {\n id: string\n isBusy: boolean\n text: string\n startTime: number\n endTime: number\n chunks: Chunk[]\n}\n\nexport interface AudioRecording {\n fileUri: string\n filename: string\n durationMs: number\n size: number\n mimeType: string\n channels: number\n bitDepth: BitDepth\n sampleRate: SampleRate\n transcripts?: TranscriberData[]\n wavPCMData?: Float32Array // Full PCM data for the recording in WAV format (only on web, for native use the fileUri)\n analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag\n}\n\nexport interface StartRecordingResult {\n fileUri: string\n mimeType: string\n channels?: number\n bitDepth?: BitDepth\n sampleRate?: SampleRate\n}\n\nexport interface AudioSessionConfig {\n category?:\n | 'Ambient'\n | 'SoloAmbient'\n | 'Playback'\n | 'Record'\n | 'PlayAndRecord'\n | 'MultiRoute'\n mode?:\n | 'Default'\n | 'VoiceChat'\n | 'VideoChat'\n | 'GameChat'\n | 'VideoRecording'\n | 'Measurement'\n | 'MoviePlayback'\n | 'SpokenAudio'\n categoryOptions?: (\n | 'MixWithOthers'\n | 'DuckOthers'\n | 'InterruptSpokenAudioAndMixWithOthers'\n | 'AllowBluetooth'\n | 'AllowBluetoothA2DP'\n | 'AllowAirPlay'\n | 'DefaultToSpeaker'\n )[]\n}\n\nexport interface IOSConfig {\n audioSession?: AudioSessionConfig\n}\n\nexport interface RecordingConfig {\n // Sample rate for recording (16000, 44100, or 48000 Hz)\n sampleRate?: SampleRate\n\n // Number of audio channels (1 for mono, 2 for stereo)\n channels?: 1 | 2\n\n // Encoding type for the recording (pcm_32bit, pcm_16bit, pcm_8bit)\n encoding?: EncodingType\n\n // Interval in milliseconds at which to emit recording data\n interval?: number\n\n // Keep the device awake while recording (default is false)\n keepAwake?: boolean\n\n // Show a notification during recording (default is false)\n showNotification?: boolean\n\n // Show waveform in the notification (Android only, when showNotification is true)\n showWaveformInNotification?: boolean\n\n // Configuration for the notification\n notification?: NotificationConfig\n\n // Enable audio processing (default is false)\n enableProcessing?: boolean\n\n // iOS-specific configuration\n ios?: IOSConfig\n\n // Number of data points to extract per second of audio (default is 1000)\n pointsPerSecond?: number\n\n // Algorithm to use for amplitude computation (default is \"rms\")\n algorithm?: AmplitudeAlgorithm\n\n // Feature options to extract (default is empty)\n features?: AudioFeaturesOptions\n\n // Callback function to handle audio stream\n onAudioStream?: (_: AudioDataEvent) => Promise<void>\n\n // Callback function to handle audio features extraction results\n onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void>\n}\n\nexport interface NotificationConfig {\n // Title of the notification\n title?: string\n\n // Main text content of the notification\n text?: string\n\n // Icon to be displayed in the notification (resource name or URI)\n icon?: string\n\n // Android-specific notification configuration\n android?: {\n // Unique identifier for the notification channel\n channelId?: string\n\n // User-visible name of the notification channel\n channelName?: string\n\n // User-visible description of the notification channel\n channelDescription?: string\n\n // Unique identifier for this notification\n notificationId?: number\n\n // List of actions that can be performed from the notification\n actions?: NotificationAction[]\n\n // Configuration for the waveform visualization in the notification\n waveform?: WaveformConfig\n\n // Color of the notification LED (if device supports it)\n lightColor?: string\n\n // Priority of the notification (affects how it's displayed)\n priority?: 'min' | 'low' | 'default' | 'high' | 'max'\n\n // Accent color for the notification (used for the app icon and buttons)\n accentColor?: string\n }\n\n // iOS-specific notification configuration\n ios?: {\n // Identifier for the notification category (used for grouping similar notifications)\n categoryIdentifier?: string\n }\n}\n\nexport interface NotificationAction {\n // Display title for the action\n title: string\n\n // Unique identifier for the action\n identifier: string\n\n // Icon to be displayed for the action (Android only)\n icon?: string\n}\n\nexport interface WaveformConfig {\n color?: string // The color of the waveform (e.g., \"#FFFFFF\" for white)\n opacity?: number // Opacity of the waveform (0.0 - 1.0)\n strokeWidth?: number // Width of the waveform line (default: 1.5)\n style?: 'stroke' | 'fill' // Drawing style: \"stroke\" for outline, \"fill\" for solid\n mirror?: boolean // Whether to mirror the waveform (symmetrical display)\n height?: number // Height of the waveform view in dp (default: 64)\n}\n\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>\n stopRecording: () => Promise<AudioRecording | null>\n pauseRecording: () => Promise<void>\n resumeRecording: () => Promise<void>\n isRecording: boolean\n isPaused: boolean\n durationMs: number // Duration of the recording\n size: number // Size in bytes of the recorded audio\n analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"","sourcesContent":["// packages/expo-audio-stream/src/ExpoAudioStream.types.ts\nimport {\n AmplitudeAlgorithm,\n AudioAnalysis,\n AudioFeaturesOptions,\n} from './AudioAnalysis/AudioAnalysis.types'\nimport { AudioAnalysisEvent } from './events'\n\nexport interface CompressionInfo {\n size: number\n mimeType: string\n bitrate: number\n format: string\n}\n\nexport interface AudioStreamStatus {\n isRecording: boolean\n isPaused: boolean\n durationMs: number\n size: number\n interval: number\n mimeType: string\n compression?: CompressionInfo\n}\n\nexport interface AudioDataEvent {\n data: string | Float32Array\n position: number\n fileUri: string\n eventDataSize: number\n totalSize: number\n compression?: CompressionInfo & {\n data?: string | Blob // Base64 (native) or Float32Array (web) encoded compressed data chunk\n }\n}\n\nexport type EncodingType = 'pcm_32bit' | 'pcm_16bit' | 'pcm_8bit'\nexport type SampleRate = 16000 | 44100 | 48000\nexport type BitDepth = 8 | 16 | 32\n\nexport type ConsoleLike = {\n log: (message: string, ...args: unknown[]) => void\n debug: (message: string, ...args: unknown[]) => void\n warn: (message: string, ...args: unknown[]) => void\n error: (message: string, ...args: unknown[]) => void\n}\n\nexport interface Chunk {\n text: string\n timestamp: [number, number | null]\n}\n\nexport interface TranscriberData {\n id: string\n isBusy: boolean\n text: string\n startTime: number\n endTime: number\n chunks: Chunk[]\n}\n\nexport interface AudioRecording {\n fileUri: string\n filename: string\n durationMs: number\n size: number\n mimeType: string\n channels: number\n bitDepth: BitDepth\n sampleRate: SampleRate\n transcripts?: TranscriberData[]\n wavPCMData?: Float32Array // Full PCM data for the recording in WAV format (only on web, for native use the fileUri)\n analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag\n compression?: CompressionInfo & {\n compressedFileUri: string\n }\n}\n\nexport interface StartRecordingResult {\n fileUri: string\n mimeType: string\n channels?: number\n bitDepth?: BitDepth\n sampleRate?: SampleRate\n compression?: CompressionInfo & {\n compressedFileUri: string\n }\n}\n\nexport interface AudioSessionConfig {\n category?:\n | 'Ambient'\n | 'SoloAmbient'\n | 'Playback'\n | 'Record'\n | 'PlayAndRecord'\n | 'MultiRoute'\n mode?:\n | 'Default'\n | 'VoiceChat'\n | 'VideoChat'\n | 'GameChat'\n | 'VideoRecording'\n | 'Measurement'\n | 'MoviePlayback'\n | 'SpokenAudio'\n categoryOptions?: (\n | 'MixWithOthers'\n | 'DuckOthers'\n | 'InterruptSpokenAudioAndMixWithOthers'\n | 'AllowBluetooth'\n | 'AllowBluetoothA2DP'\n | 'AllowAirPlay'\n | 'DefaultToSpeaker'\n )[]\n}\n\nexport interface IOSConfig {\n audioSession?: AudioSessionConfig\n}\n\nexport interface RecordingConfig {\n // Sample rate for recording (16000, 44100, or 48000 Hz)\n sampleRate?: SampleRate\n\n // Number of audio channels (1 for mono, 2 for stereo)\n channels?: 1 | 2\n\n // Encoding type for the recording (pcm_32bit, pcm_16bit, pcm_8bit)\n encoding?: EncodingType\n\n // Interval in milliseconds at which to emit recording data\n interval?: number\n\n // Keep the device awake while recording (default is false)\n keepAwake?: boolean\n\n // Show a notification during recording (default is false)\n showNotification?: boolean\n\n // Show waveform in the notification (Android only, when showNotification is true)\n showWaveformInNotification?: boolean\n\n // Configuration for the notification\n notification?: NotificationConfig\n\n // Enable audio processing (default is false)\n enableProcessing?: boolean\n\n // iOS-specific configuration\n ios?: IOSConfig\n\n // Number of data points to extract per second of audio (default is 1000)\n pointsPerSecond?: number\n\n // Algorithm to use for amplitude computation (default is \"rms\")\n algorithm?: AmplitudeAlgorithm\n\n // Feature options to extract (default is empty)\n features?: AudioFeaturesOptions\n\n // Callback function to handle audio stream\n onAudioStream?: (_: AudioDataEvent) => Promise<void>\n\n // Callback function to handle audio features extraction results\n onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void>\n\n compression?: {\n enabled: boolean\n format: 'aac' | 'opus' | 'mp3'\n bitrate?: number\n }\n}\n\nexport interface NotificationConfig {\n // Title of the notification\n title?: string\n\n // Main text content of the notification\n text?: string\n\n // Icon to be displayed in the notification (resource name or URI)\n icon?: string\n\n // Android-specific notification configuration\n android?: {\n // Unique identifier for the notification channel\n channelId?: string\n\n // User-visible name of the notification channel\n channelName?: string\n\n // User-visible description of the notification channel\n channelDescription?: string\n\n // Unique identifier for this notification\n notificationId?: number\n\n // List of actions that can be performed from the notification\n actions?: NotificationAction[]\n\n // Configuration for the waveform visualization in the notification\n waveform?: WaveformConfig\n\n // Color of the notification LED (if device supports it)\n lightColor?: string\n\n // Priority of the notification (affects how it's displayed)\n priority?: 'min' | 'low' | 'default' | 'high' | 'max'\n\n // Accent color for the notification (used for the app icon and buttons)\n accentColor?: string\n }\n\n // iOS-specific notification configuration\n ios?: {\n // Identifier for the notification category (used for grouping similar notifications)\n categoryIdentifier?: string\n }\n}\n\nexport interface NotificationAction {\n // Display title for the action\n title: string\n\n // Unique identifier for the action\n identifier: string\n\n // Icon to be displayed for the action (Android only)\n icon?: string\n}\n\nexport interface WaveformConfig {\n color?: string // The color of the waveform (e.g., \"#FFFFFF\" for white)\n opacity?: number // Opacity of the waveform (0.0 - 1.0)\n strokeWidth?: number // Width of the waveform line (default: 1.5)\n style?: 'stroke' | 'fill' // Drawing style: \"stroke\" for outline, \"fill\" for solid\n mirror?: boolean // Whether to mirror the waveform (symmetrical display)\n height?: number // Height of the waveform view in dp (default: 64)\n}\n\nexport interface WebRecordingOptions {\n /**\n * Web-specific option to skip the final audio data consolidation process.\n * When true, it will:\n * - Skip the time-consuming process of concatenating all audio chunks\n * - Return immediately with the compressed audio (if compression is enabled)\n * - Improve performance when stopping large recordings\n * - Useful when only the compressed audio is needed (e.g., when not using transcription)\n * @default false\n */\n skipFinalConsolidation?: boolean\n}\n\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>\n stopRecording: (options?: WebRecordingOptions) => Promise<AudioRecording | null>\n pauseRecording: () => Promise<void>\n resumeRecording: () => Promise<void>\n isRecording: boolean\n isPaused: boolean\n durationMs: number // Duration of the recording\n size: number // Size in bytes of the recorded audio\n compression?: CompressionInfo\n analysisData?: AudioAnalysis // Analysis data for the recording depending on enableProcessing flag\n}\n"]}
|