@siteed/expo-audio-stream 1.9.1 → 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 +20 -1
- package/ios/AudioStreamManager.swift +74 -42
- package/ios/RecordingSettings.swift +12 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,24 +8,31 @@ 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
|
+
|
|
11
14
|
## [1.9.1] - 2025-01-12
|
|
12
15
|
- ios potentially missing compressed file info ([88a628c](https://github.com/deeeed/expo-audio-stream/commit/88a628c35f2bfd626a2a5de1eb6950efd814619d))
|
|
13
16
|
|
|
17
|
+
|
|
14
18
|
## [1.9.0] - 2025-01-11
|
|
15
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))
|
|
16
20
|
|
|
17
21
|
|
|
22
|
+
|
|
18
23
|
## [1.8.0] - 2025-01-10
|
|
19
24
|
- feat(audio): implement audio compression support ([ff4e060](https://github.com/deeeed/expo-audio-stream/commit/ff4e060fef1061804c1cc0126d4344d2d50daa9a))
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
|
|
28
|
+
|
|
23
29
|
## [1.7.2] - 2025-01-07
|
|
24
30
|
- fix(audio-stream): correct WAV header handling in web audio recording ([9ba7de5](https://github.com/deeeed/expo-audio-stream/commit/9ba7de5b96ca4cc937dea261c80d3fda9c99e8f4))
|
|
25
31
|
|
|
26
32
|
|
|
27
33
|
|
|
28
34
|
|
|
35
|
+
|
|
29
36
|
## [1.7.1] - 2025-01-07
|
|
30
37
|
- update notification to avoid triggering new alerts (#71) ([32dcfc5](https://github.com/deeeed/expo-audio-stream/commit/32dcfc55daf3236babefc17016f329c177d466fd))
|
|
31
38
|
|
|
@@ -33,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
33
40
|
|
|
34
41
|
|
|
35
42
|
|
|
43
|
+
|
|
36
44
|
## [1.7.0] - 2025-01-05
|
|
37
45
|
- feat(playground): enhance app configuration and build setup for production deployment (#58) ([929d443](https://github.com/deeeed/expo-audio-stream/commit/929d443145378b1430d215db5c00b13758420e2b))
|
|
38
46
|
- chore(expo-audio-stream): release @siteed/expo-audio-stream@1.6.1 ([084e8ad](https://github.com/deeeed/expo-audio-stream/commit/084e8adb91da7874c9e608b55d9c7b2ffd7a8327))
|
|
@@ -46,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
46
54
|
|
|
47
55
|
|
|
48
56
|
|
|
57
|
+
|
|
49
58
|
## [1.6.1] - 2024-12-11
|
|
50
59
|
- chore(expo-audio-stream): remove git commit step from publish script ([4a772ce](https://github.com/deeeed/expo-audio-stream/commit/4a772ce93bb7405d9b8e981f46bdf8941a71ecfe))
|
|
51
60
|
- chore: more publishing automation ([3693021](https://github.com/deeeed/expo-audio-stream/commit/369302107f9dca9dddd8ae68e6214481a39976ac))
|
|
@@ -63,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
63
72
|
|
|
64
73
|
|
|
65
74
|
|
|
75
|
+
|
|
66
76
|
## [1.5.0] - 2024-12-10
|
|
67
77
|
- UNPUBLISHED because of a bug in the build system
|
|
68
78
|
|
|
@@ -73,6 +83,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
73
83
|
|
|
74
84
|
|
|
75
85
|
|
|
86
|
+
|
|
76
87
|
## [1.4.0] - 2024-12-05
|
|
77
88
|
- chore: remove unusded dependencies ([ad81dd5](https://github.com/deeeed/expo-audio-stream/commit/ad81dd560c93dd1d04995a323a4ae72d4de20f3e))
|
|
78
89
|
|
|
@@ -83,6 +94,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
83
94
|
|
|
84
95
|
|
|
85
96
|
|
|
97
|
+
|
|
86
98
|
## [1.3.1] - 2024-12-05
|
|
87
99
|
- feat(web): implement throttling and optimize event processing (#49) ([da28765](https://github.com/deeeed/expo-audio-stream/commit/da2876524c2c9d6e0a980fde40a0197b929d8a7f))
|
|
88
100
|
|
|
@@ -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.3.0] - 2024-11-28
|
|
97
110
|
### Added
|
|
98
111
|
- refactor(permissions): standardize permission status response structure across platforms (#44) ([7c9c800](https://github.com/deeeed/expo-audio-stream/commit/7c9c800d83b7cea3516643371484d5e1f3b99e4c))
|
|
@@ -108,6 +121,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
108
121
|
|
|
109
122
|
|
|
110
123
|
|
|
124
|
+
|
|
111
125
|
## [1.2.5] - 2024-11-12
|
|
112
126
|
### Added
|
|
113
127
|
- docs(license): add MIT license to all packages (6 files changed)
|
|
@@ -120,6 +134,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
120
134
|
|
|
121
135
|
|
|
122
136
|
|
|
137
|
+
|
|
123
138
|
## [1.2.4] - 2024-11-05
|
|
124
139
|
### Changed
|
|
125
140
|
- Android minimum audio interval set to 10ms.
|
|
@@ -135,6 +150,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
135
150
|
|
|
136
151
|
|
|
137
152
|
|
|
153
|
+
|
|
138
154
|
## [1.2.0] - 2024-10-24
|
|
139
155
|
### Added
|
|
140
156
|
- Feature: Keep device awake during recording with `keepAwake` option
|
|
@@ -150,6 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
150
166
|
|
|
151
167
|
|
|
152
168
|
|
|
169
|
+
|
|
153
170
|
## [1.1.17] - 2024-10-21
|
|
154
171
|
### Added
|
|
155
172
|
- Support bluetooth headset on ios
|
|
@@ -162,6 +179,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
162
179
|
|
|
163
180
|
|
|
164
181
|
|
|
182
|
+
|
|
165
183
|
## [1.0.0] - 2024-04-01
|
|
166
184
|
### Added
|
|
167
185
|
- Initial release of @siteed/expo-audio-stream.
|
|
@@ -172,7 +190,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
172
190
|
- Feature: Audio features extraction during recording.
|
|
173
191
|
- Feature: Consistent WAV PCM recording format across all platforms.
|
|
174
192
|
|
|
175
|
-
[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
|
|
176
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
|
|
177
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
|
|
178
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
|
|
@@ -410,13 +410,14 @@ class AudioStreamManager: NSObject {
|
|
|
410
410
|
"interval": emissionInterval
|
|
411
411
|
]
|
|
412
412
|
|
|
413
|
-
// Add compression info if enabled
|
|
413
|
+
// Add compression info if enabled
|
|
414
414
|
if settings.enableCompressedOutput,
|
|
415
415
|
let compressedURL = compressedFileURL,
|
|
416
416
|
FileManager.default.fileExists(atPath: compressedURL.path) {
|
|
417
417
|
do {
|
|
418
418
|
let compressedAttributes = try FileManager.default.attributesOfItem(atPath: compressedURL.path)
|
|
419
419
|
if let compressedSize = compressedAttributes[.size] as? Int64 {
|
|
420
|
+
Logger.debug("Compressed file status - Size: \(compressedSize)")
|
|
420
421
|
let compressionBundle: [String: Any] = [
|
|
421
422
|
"fileUri": compressedURL.absoluteString,
|
|
422
423
|
"mimeType": compressedFormat == "aac" ? "audio/aac" : "audio/opus",
|
|
@@ -558,34 +559,47 @@ class AudioStreamManager: NSObject {
|
|
|
558
559
|
AVSampleRateKey: settings.sampleRate,
|
|
559
560
|
AVNumberOfChannelsKey: settings.numberOfChannels,
|
|
560
561
|
AVEncoderBitRateKey: settings.compressedBitRate,
|
|
561
|
-
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
|
|
562
|
+
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue,
|
|
563
|
+
AVEncoderBitDepthHintKey: 16
|
|
562
564
|
]
|
|
563
565
|
|
|
564
|
-
|
|
566
|
+
Logger.debug("Initializing compressed recording with settings: \(compressedSettings)")
|
|
567
|
+
|
|
565
568
|
let tempDirectory = FileManager.default.temporaryDirectory
|
|
566
569
|
try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
|
|
567
570
|
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
.
|
|
571
|
-
|
|
572
|
-
if let url = compressedFileURL {
|
|
573
|
-
// Create empty file first
|
|
574
|
-
FileManager.default.createFile(atPath: url.path, contents: nil)
|
|
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
575
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
+
}
|
|
584
599
|
}
|
|
585
600
|
}
|
|
586
601
|
} catch {
|
|
587
602
|
Logger.debug("Failed to setup compressed recording: \(error)")
|
|
588
|
-
// Don't fail the entire recording if compression fails
|
|
589
603
|
compressedFileURL = nil
|
|
590
604
|
compressedRecorder = nil
|
|
591
605
|
}
|
|
@@ -1213,32 +1227,50 @@ class AudioStreamManager: NSObject {
|
|
|
1213
1227
|
var compressionInfo: [String: Any]? = nil
|
|
1214
1228
|
if settings.enableCompressedOutput, let compressedURL = compressedFileURL {
|
|
1215
1229
|
do {
|
|
1216
|
-
|
|
1217
|
-
if
|
|
1218
|
-
let
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
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")
|
|
1228
1268
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
compressionInfo = [
|
|
1233
|
-
"position": recordingTime * 1000, // Convert to milliseconds
|
|
1234
|
-
"fileUri": compressedURL.absoluteString,
|
|
1235
|
-
"eventDataSize": eventDataSize,
|
|
1236
|
-
"totalSize": compressedSize,
|
|
1237
|
-
"data": compressedData ?? ""
|
|
1238
|
-
]
|
|
1269
|
+
} else {
|
|
1270
|
+
Logger.debug("Compressed file does not exist at path: \(compressedURL.path)")
|
|
1239
1271
|
}
|
|
1240
1272
|
} catch {
|
|
1241
|
-
Logger.debug("
|
|
1273
|
+
Logger.debug("Error preparing compression info: \(error)")
|
|
1242
1274
|
}
|
|
1243
1275
|
}
|
|
1244
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
|
|