@siteed/expo-audio-stream 1.9.0 → 1.9.2
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 +38 -1
- package/ios/AudioStreamManager.swift +93 -40
- package/ios/RecordingSettings.swift +12 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,24 +8,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
## [1.9.2] - 2025-01-12
|
|
12
|
+
- ios bitrate verification to prevent invalid values ([035a180](https://github.com/deeeed/expo-audio-stream/commit/035a1800833264edcc59724aaa8a2e12d5c78dc2))
|
|
13
|
+
|
|
14
|
+
## [1.9.1] - 2025-01-12
|
|
15
|
+
- ios potentially missing compressed file info ([88a628c](https://github.com/deeeed/expo-audio-stream/commit/88a628c35f2bfd626a2a5de1eb6950efd814619d))
|
|
16
|
+
|
|
17
|
+
|
|
11
18
|
## [1.9.0] - 2025-01-11
|
|
12
19
|
- feat(web-audio): optimize memory usage and streaming performance for web audio recording (#75) ([7b93e12](https://github.com/deeeed/expo-audio-stream/commit/7b93e12aae4bc0599b06b48ca34a60f65587fc75))
|
|
13
20
|
|
|
21
|
+
|
|
22
|
+
|
|
14
23
|
## [1.8.0] - 2025-01-10
|
|
15
24
|
- feat(audio): implement audio compression support ([ff4e060](https://github.com/deeeed/expo-audio-stream/commit/ff4e060fef1061804c1cc0126d4344d2d50daa9a))
|
|
16
25
|
|
|
17
26
|
|
|
27
|
+
|
|
28
|
+
|
|
18
29
|
## [1.7.2] - 2025-01-07
|
|
19
30
|
- fix(audio-stream): correct WAV header handling in web audio recording ([9ba7de5](https://github.com/deeeed/expo-audio-stream/commit/9ba7de5b96ca4cc937dea261c80d3fda9c99e8f4))
|
|
20
31
|
|
|
21
32
|
|
|
22
33
|
|
|
34
|
+
|
|
35
|
+
|
|
23
36
|
## [1.7.1] - 2025-01-07
|
|
24
37
|
- update notification to avoid triggering new alerts (#71) ([32dcfc5](https://github.com/deeeed/expo-audio-stream/commit/32dcfc55daf3236babefc17016f329c177d466fd))
|
|
25
38
|
|
|
26
39
|
|
|
27
40
|
|
|
28
41
|
|
|
42
|
+
|
|
43
|
+
|
|
29
44
|
## [1.7.0] - 2025-01-05
|
|
30
45
|
- feat(playground): enhance app configuration and build setup for production deployment (#58) ([929d443](https://github.com/deeeed/expo-audio-stream/commit/929d443145378b1430d215db5c00b13758420e2b))
|
|
31
46
|
- chore(expo-audio-stream): release @siteed/expo-audio-stream@1.6.1 ([084e8ad](https://github.com/deeeed/expo-audio-stream/commit/084e8adb91da7874c9e608b55d9c7b2ffd7a8327))
|
|
@@ -38,6 +53,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
38
53
|
|
|
39
54
|
|
|
40
55
|
|
|
56
|
+
|
|
57
|
+
|
|
41
58
|
## [1.6.1] - 2024-12-11
|
|
42
59
|
- chore(expo-audio-stream): remove git commit step from publish script ([4a772ce](https://github.com/deeeed/expo-audio-stream/commit/4a772ce93bb7405d9b8e981f46bdf8941a71ecfe))
|
|
43
60
|
- chore: more publishing automation ([3693021](https://github.com/deeeed/expo-audio-stream/commit/369302107f9dca9dddd8ae68e6214481a39976ac))
|
|
@@ -54,6 +71,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
54
71
|
|
|
55
72
|
|
|
56
73
|
|
|
74
|
+
|
|
75
|
+
|
|
57
76
|
## [1.5.0] - 2024-12-10
|
|
58
77
|
- UNPUBLISHED because of a bug in the build system
|
|
59
78
|
|
|
@@ -63,6 +82,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
63
82
|
|
|
64
83
|
|
|
65
84
|
|
|
85
|
+
|
|
86
|
+
|
|
66
87
|
## [1.4.0] - 2024-12-05
|
|
67
88
|
- chore: remove unusded dependencies ([ad81dd5](https://github.com/deeeed/expo-audio-stream/commit/ad81dd560c93dd1d04995a323a4ae72d4de20f3e))
|
|
68
89
|
|
|
@@ -72,6 +93,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
72
93
|
|
|
73
94
|
|
|
74
95
|
|
|
96
|
+
|
|
97
|
+
|
|
75
98
|
## [1.3.1] - 2024-12-05
|
|
76
99
|
- feat(web): implement throttling and optimize event processing (#49) ([da28765](https://github.com/deeeed/expo-audio-stream/commit/da2876524c2c9d6e0a980fde40a0197b929d8a7f))
|
|
77
100
|
|
|
@@ -81,6 +104,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
81
104
|
|
|
82
105
|
|
|
83
106
|
|
|
107
|
+
|
|
108
|
+
|
|
84
109
|
## [1.3.0] - 2024-11-28
|
|
85
110
|
### Added
|
|
86
111
|
- refactor(permissions): standardize permission status response structure across platforms (#44) ([7c9c800](https://github.com/deeeed/expo-audio-stream/commit/7c9c800d83b7cea3516643371484d5e1f3b99e4c))
|
|
@@ -95,6 +120,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
95
120
|
|
|
96
121
|
|
|
97
122
|
|
|
123
|
+
|
|
124
|
+
|
|
98
125
|
## [1.2.5] - 2024-11-12
|
|
99
126
|
### Added
|
|
100
127
|
- docs(license): add MIT license to all packages (6 files changed)
|
|
@@ -106,6 +133,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
106
133
|
|
|
107
134
|
|
|
108
135
|
|
|
136
|
+
|
|
137
|
+
|
|
109
138
|
## [1.2.4] - 2024-11-05
|
|
110
139
|
### Changed
|
|
111
140
|
- Android minimum audio interval set to 10ms.
|
|
@@ -120,6 +149,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
120
149
|
|
|
121
150
|
|
|
122
151
|
|
|
152
|
+
|
|
153
|
+
|
|
123
154
|
## [1.2.0] - 2024-10-24
|
|
124
155
|
### Added
|
|
125
156
|
- Feature: Keep device awake during recording with `keepAwake` option
|
|
@@ -134,6 +165,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
134
165
|
|
|
135
166
|
|
|
136
167
|
|
|
168
|
+
|
|
169
|
+
|
|
137
170
|
## [1.1.17] - 2024-10-21
|
|
138
171
|
### Added
|
|
139
172
|
- Support bluetooth headset on ios
|
|
@@ -145,6 +178,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
145
178
|
|
|
146
179
|
|
|
147
180
|
|
|
181
|
+
|
|
182
|
+
|
|
148
183
|
## [1.0.0] - 2024-04-01
|
|
149
184
|
### Added
|
|
150
185
|
- Initial release of @siteed/expo-audio-stream.
|
|
@@ -155,7 +190,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
155
190
|
- Feature: Audio features extraction during recording.
|
|
156
191
|
- Feature: Consistent WAV PCM recording format across all platforms.
|
|
157
192
|
|
|
158
|
-
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.9.
|
|
193
|
+
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.9.2...HEAD
|
|
194
|
+
[1.9.2]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.9.1...@siteed/expo-audio-stream@1.9.2
|
|
195
|
+
[1.9.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.9.0...@siteed/expo-audio-stream@1.9.1
|
|
159
196
|
[1.9.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.8.0...@siteed/expo-audio-stream@1.9.0
|
|
160
197
|
[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
|
|
161
198
|
[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
|
|
@@ -411,10 +411,13 @@ class AudioStreamManager: NSObject {
|
|
|
411
411
|
]
|
|
412
412
|
|
|
413
413
|
// Add compression info if enabled
|
|
414
|
-
if settings.enableCompressedOutput,
|
|
414
|
+
if settings.enableCompressedOutput,
|
|
415
|
+
let compressedURL = compressedFileURL,
|
|
416
|
+
FileManager.default.fileExists(atPath: compressedURL.path) {
|
|
415
417
|
do {
|
|
416
418
|
let compressedAttributes = try FileManager.default.attributesOfItem(atPath: compressedURL.path)
|
|
417
419
|
if let compressedSize = compressedAttributes[.size] as? Int64 {
|
|
420
|
+
Logger.debug("Compressed file status - Size: \(compressedSize)")
|
|
418
421
|
let compressionBundle: [String: Any] = [
|
|
419
422
|
"fileUri": compressedURL.absoluteString,
|
|
420
423
|
"mimeType": compressedFormat == "aac" ? "audio/aac" : "audio/opus",
|
|
@@ -550,23 +553,55 @@ class AudioStreamManager: NSObject {
|
|
|
550
553
|
|
|
551
554
|
// Setup compressed recording if enabled
|
|
552
555
|
if settings.enableCompressedOutput {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
.appendingPathComponent(UUID().uuidString)
|
|
563
|
-
.appendingPathExtension(settings.compressedFormat)
|
|
556
|
+
do {
|
|
557
|
+
let compressedSettings: [String: Any] = [
|
|
558
|
+
AVFormatIDKey: settings.compressedFormat == "aac" ? kAudioFormatMPEG4AAC : kAudioFormatOpus,
|
|
559
|
+
AVSampleRateKey: settings.sampleRate,
|
|
560
|
+
AVNumberOfChannelsKey: settings.numberOfChannels,
|
|
561
|
+
AVEncoderBitRateKey: settings.compressedBitRate,
|
|
562
|
+
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
|
|
563
|
+
AVEncoderBitDepthHintKey: 16
|
|
564
|
+
]
|
|
564
565
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
566
|
+
Logger.debug("Initializing compressed recording with settings: \(compressedSettings)")
|
|
567
|
+
|
|
568
|
+
let tempDirectory = FileManager.default.temporaryDirectory
|
|
569
|
+
try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
|
|
570
|
+
|
|
571
|
+
// Use the same UUID as the main recording
|
|
572
|
+
if let recordingUUID = recordingUUID {
|
|
573
|
+
compressedFileURL = tempDirectory.appendingPathComponent(recordingUUID.uuidString)
|
|
574
|
+
.appendingPathExtension(settings.compressedFormat)
|
|
575
|
+
|
|
576
|
+
if let url = compressedFileURL {
|
|
577
|
+
// Create empty file first
|
|
578
|
+
if FileManager.default.createFile(atPath: url.path, contents: nil) {
|
|
579
|
+
Logger.debug("Created empty file at: \(url.path)")
|
|
580
|
+
} else {
|
|
581
|
+
Logger.debug("Failed to create empty file at: \(url.path)")
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Then initialize recorder
|
|
585
|
+
compressedRecorder = try AVAudioRecorder(url: url, settings: compressedSettings)
|
|
586
|
+
if let recorder = compressedRecorder {
|
|
587
|
+
let prepared = recorder.prepareToRecord()
|
|
588
|
+
Logger.debug("Recorder prepared: \(prepared)")
|
|
589
|
+
|
|
590
|
+
let started = recorder.record()
|
|
591
|
+
Logger.debug("Recorder started: \(started)")
|
|
592
|
+
|
|
593
|
+
Logger.debug("Recorder current time: \(recorder.currentTime)")
|
|
594
|
+
|
|
595
|
+
compressedFormat = settings.compressedFormat
|
|
596
|
+
compressedBitRate = settings.compressedBitRate
|
|
597
|
+
Logger.debug("Compressed recording initialized - Format: \(compressedFormat), Bitrate: \(compressedBitRate)")
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
} catch {
|
|
602
|
+
Logger.debug("Failed to setup compressed recording: \(error)")
|
|
603
|
+
compressedFileURL = nil
|
|
604
|
+
compressedRecorder = nil
|
|
570
605
|
}
|
|
571
606
|
}
|
|
572
607
|
|
|
@@ -1192,32 +1227,50 @@ class AudioStreamManager: NSObject {
|
|
|
1192
1227
|
var compressionInfo: [String: Any]? = nil
|
|
1193
1228
|
if settings.enableCompressedOutput, let compressedURL = compressedFileURL {
|
|
1194
1229
|
do {
|
|
1195
|
-
|
|
1196
|
-
if
|
|
1197
|
-
let
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1230
|
+
// Ensure file exists and has data
|
|
1231
|
+
if FileManager.default.fileExists(atPath: compressedURL.path) {
|
|
1232
|
+
let compressedAttributes = try FileManager.default.attributesOfItem(atPath: compressedURL.path)
|
|
1233
|
+
if let compressedSize = compressedAttributes[.size] as? Int64 {
|
|
1234
|
+
let eventDataSize = compressedSize - lastEmittedCompressedSize
|
|
1235
|
+
|
|
1236
|
+
Logger.debug("Compressed file status - Total size: \(compressedSize), New data size: \(eventDataSize)")
|
|
1237
|
+
|
|
1238
|
+
// Read the new compressed data if there's new data
|
|
1239
|
+
var compressedData: String? = nil
|
|
1240
|
+
if eventDataSize > 0 {
|
|
1241
|
+
do {
|
|
1242
|
+
let fileHandle = try FileHandle(forReadingFrom: compressedURL)
|
|
1243
|
+
defer { fileHandle.closeFile() }
|
|
1244
|
+
|
|
1245
|
+
fileHandle.seek(toFileOffset: UInt64(lastEmittedCompressedSize))
|
|
1246
|
+
let data = fileHandle.readData(ofLength: Int(eventDataSize))
|
|
1247
|
+
compressedData = data.base64EncodedString()
|
|
1248
|
+
|
|
1249
|
+
Logger.debug("Read compressed data of size: \(data.count)")
|
|
1250
|
+
} catch {
|
|
1251
|
+
Logger.debug("Error reading compressed data: \(error)")
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
lastEmittedCompressedSize = compressedSize
|
|
1256
|
+
|
|
1257
|
+
compressionInfo = [
|
|
1258
|
+
"position": recordingTime * 1000, // Convert to milliseconds
|
|
1259
|
+
"fileUri": compressedURL.absoluteString,
|
|
1260
|
+
"eventDataSize": eventDataSize,
|
|
1261
|
+
"totalSize": compressedSize,
|
|
1262
|
+
"data": compressedData ?? ""
|
|
1263
|
+
]
|
|
1264
|
+
|
|
1265
|
+
Logger.debug("Compression info prepared: \(String(describing: compressionInfo))")
|
|
1266
|
+
} else {
|
|
1267
|
+
Logger.debug("Could not get compressed file size")
|
|
1207
1268
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
compressionInfo = [
|
|
1212
|
-
"position": recordingTime * 1000, // Convert to milliseconds
|
|
1213
|
-
"fileUri": compressedURL.absoluteString,
|
|
1214
|
-
"eventDataSize": eventDataSize,
|
|
1215
|
-
"totalSize": compressedSize,
|
|
1216
|
-
"data": compressedData ?? ""
|
|
1217
|
-
]
|
|
1269
|
+
} else {
|
|
1270
|
+
Logger.debug("Compressed file does not exist at path: \(compressedURL.path)")
|
|
1218
1271
|
}
|
|
1219
1272
|
} catch {
|
|
1220
|
-
Logger.debug("
|
|
1273
|
+
Logger.debug("Error preparing compression info: \(error)")
|
|
1221
1274
|
}
|
|
1222
1275
|
}
|
|
1223
1276
|
|
|
@@ -23,18 +23,25 @@ struct CompressedRecordingInfo {
|
|
|
23
23
|
var bitrate: Int
|
|
24
24
|
var format: String
|
|
25
25
|
|
|
26
|
-
static func validate(format: String, bitrate: Int) -> Result<
|
|
26
|
+
static func validate(format: String, bitrate: Int) -> Result<(String, Int), Error> {
|
|
27
27
|
// Validate format
|
|
28
28
|
guard ["aac", "opus"].contains(format.lowercased()) else {
|
|
29
29
|
return .failure(RecordingError.unsupportedFormat(format))
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// Adjust bitrate based on format
|
|
33
|
+
let adjustedBitrate: Int
|
|
34
|
+
if format.lowercased() == "aac" {
|
|
35
|
+
// Standard AAC bitrates (bps)
|
|
36
|
+
let standardAACBitrates = [32000, 48000, 64000, 96000, 128000, 160000, 192000, 256000, 320000]
|
|
37
|
+
adjustedBitrate = standardAACBitrates.min(by: { abs($0 - bitrate) < abs($1 - bitrate) }) ?? 128000
|
|
38
|
+
} else {
|
|
39
|
+
// For Opus, allow lower bitrates (especially good for voice)
|
|
40
|
+
// Typical Opus voice bitrates: 8-24 kbps, music: 32-128 kbps
|
|
41
|
+
adjustedBitrate = min(max(bitrate, 8000), 320000)
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
return .success(())
|
|
44
|
+
return .success((format, adjustedBitrate))
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
47
|
|