@siteed/audio-studio 3.2.0-beta.1 โ 3.2.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 +356 -5
- package/android/src/main/java/net/siteed/audiostudio/AudioStreamDecoder.kt +306 -94
- package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +39 -6
- package/build/cjs/errors/AudioStreamError.js +9 -0
- package/build/cjs/errors/AudioStreamError.js.map +1 -1
- package/build/cjs/errors/AudioStreamError.test.js +22 -1
- package/build/cjs/errors/AudioStreamError.test.js.map +1 -1
- package/build/cjs/streamAudioData.js +99 -32
- package/build/cjs/streamAudioData.js.map +1 -1
- package/build/cjs/utils/audioProcessing.js +14 -10
- package/build/cjs/utils/audioProcessing.js.map +1 -1
- package/build/esm/errors/AudioStreamError.js +9 -0
- package/build/esm/errors/AudioStreamError.js.map +1 -1
- package/build/esm/errors/AudioStreamError.test.js +22 -1
- package/build/esm/errors/AudioStreamError.test.js.map +1 -1
- package/build/esm/streamAudioData.js +99 -32
- package/build/esm/streamAudioData.js.map +1 -1
- package/build/esm/utils/audioProcessing.js +14 -10
- package/build/esm/utils/audioProcessing.js.map +1 -1
- package/build/types/errors/AudioStreamError.d.ts.map +1 -1
- package/build/types/streamAudioData.d.ts +5 -0
- package/build/types/streamAudioData.d.ts.map +1 -1
- package/build/types/utils/audioProcessing.d.ts +2 -2
- package/build/types/utils/audioProcessing.d.ts.map +1 -1
- package/ios/AudioStreamDecoder.swift +191 -100
- package/ios/AudioStudioModule.swift +48 -9
- package/package.json +163 -146
- package/scripts/README.md +58 -0
- package/src/errors/AudioStreamError.test.ts +29 -2
- package/src/errors/AudioStreamError.ts +14 -0
- package/src/streamAudioData.ts +146 -42
- package/src/utils/audioProcessing.ts +25 -14
- package/android/src/androidTest/assets/chorus.wav +0 -0
- package/android/src/androidTest/assets/jfk.wav +0 -0
- package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
- package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +0 -190
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +0 -197
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +0 -487
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +0 -250
- package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +0 -186
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +0 -332
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +0 -324
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +0 -253
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +0 -218
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +0 -120
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +0 -345
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +0 -340
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +0 -252
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +0 -95
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +0 -43
- package/android/src/test/java/net/siteed/audiostudio/AndroidCallStateTest.kt +0 -37
- package/android/src/test/java/net/siteed/audiostudio/AndroidEventEmitterTest.kt +0 -28
- package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +0 -279
- package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +0 -249
- package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +0 -151
- package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +0 -273
- package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +0 -140
- package/android/src/test/java/net/siteed/audiostudio/InterruptionAutoResumePolicyTest.kt +0 -49
- package/android/src/test/resources/chorus.wav +0 -0
- package/android/src/test/resources/generate_test_audio.py +0 -94
- package/android/src/test/resources/jfk.wav +0 -0
- package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
- package/android/src/test/resources/recorder_hello_world.wav +0 -0
- package/ios/AudioStudioTests/AudioFileHandlerTests.swift +0 -338
- package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +0 -331
- package/ios/AudioStudioTests/AudioStreamDecoderTests.swift +0 -128
- package/ios/AudioStudioTests/AudioTestHelpers.swift +0 -130
- package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +0 -334
- package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +0 -105
- package/ios/AudioStudioTests/Info.plist +0 -22
- package/ios/AudioStudioTests/README.md +0 -39
- package/ios/AudioStudioTests/SimpleAudioTest.swift +0 -98
- package/ios/AudioStudioTests/TestAudioGenerator.swift +0 -75
- package/ios/tests/README.md +0 -41
- package/ios/tests/integration/buffer_and_fallback_test.swift +0 -178
- package/ios/tests/integration/buffer_duration_test.swift +0 -185
- package/ios/tests/integration/compressed_only_output_test.swift +0 -271
- package/ios/tests/integration/output_control_test.swift +0 -322
- package/ios/tests/integration/run_integration_tests.sh +0 -37
- package/ios/tests/opus_support_test_macos.swift +0 -154
- package/ios/tests/standalone/audio_processing_test.swift +0 -144
- package/ios/tests/standalone/audio_recording_test.swift +0 -277
- package/ios/tests/standalone/audio_streaming_test.swift +0 -249
- package/ios/tests/standalone/standalone_test.swift +0 -144
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env swift
|
|
2
|
-
|
|
3
|
-
import Foundation
|
|
4
|
-
import AVFoundation
|
|
5
|
-
|
|
6
|
-
// Integration test for Compressed-Only Output (Issue #244)
|
|
7
|
-
// This tests that when primary output is disabled and compressed is enabled,
|
|
8
|
-
// the compressed file info is properly returned in the result
|
|
9
|
-
|
|
10
|
-
print("๐งช Compressed-Only Output Integration Test (Issue #244)")
|
|
11
|
-
print("=====================================================\n")
|
|
12
|
-
|
|
13
|
-
// Add the parent directory to the module search path
|
|
14
|
-
let srcPath = URL(fileURLWithPath: #file)
|
|
15
|
-
.deletingLastPathComponent()
|
|
16
|
-
.deletingLastPathComponent()
|
|
17
|
-
.deletingLastPathComponent()
|
|
18
|
-
.path
|
|
19
|
-
|
|
20
|
-
// Import the module
|
|
21
|
-
#if canImport(AudioStudio)
|
|
22
|
-
import AudioStudio
|
|
23
|
-
#endif
|
|
24
|
-
|
|
25
|
-
// Helper to load Swift files
|
|
26
|
-
func loadSwiftFile(_ filename: String) {
|
|
27
|
-
let filePath = "\(srcPath)/\(filename).swift"
|
|
28
|
-
let fileURL = URL(fileURLWithPath: filePath)
|
|
29
|
-
|
|
30
|
-
do {
|
|
31
|
-
let _ = try String(contentsOf: fileURL, encoding: .utf8)
|
|
32
|
-
// In a real scenario, we'd compile and load this
|
|
33
|
-
// For testing, we'll simulate the behavior
|
|
34
|
-
} catch {
|
|
35
|
-
print("Warning: Could not load \(filename).swift")
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Test class
|
|
40
|
-
class CompressedOnlyOutputTest {
|
|
41
|
-
var results: [(name: String, passed: Bool, message: String)] = []
|
|
42
|
-
|
|
43
|
-
func runAllTests() {
|
|
44
|
-
print("๐ Test Scenarios:")
|
|
45
|
-
print("1. Primary disabled, Compressed enabled (AAC)")
|
|
46
|
-
print("2. Primary disabled, Compressed enabled (Opus)")
|
|
47
|
-
print("3. Verify compressed file URI is returned")
|
|
48
|
-
print("4. Verify file size and format info")
|
|
49
|
-
print("\n")
|
|
50
|
-
|
|
51
|
-
testCompressedOnlyAAC()
|
|
52
|
-
testCompressedOnlyOpus()
|
|
53
|
-
testCompressedFileAccess()
|
|
54
|
-
printResults()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
func testCompressedOnlyAAC() {
|
|
58
|
-
print("Test 1: Compressed-Only Output with AAC")
|
|
59
|
-
print("---------------------------------------")
|
|
60
|
-
|
|
61
|
-
// Simulate recording configuration
|
|
62
|
-
let config = [
|
|
63
|
-
"sampleRate": 44100,
|
|
64
|
-
"channels": 1,
|
|
65
|
-
"encoding": "pcm_16bit",
|
|
66
|
-
"output": [
|
|
67
|
-
"primary": ["enabled": false],
|
|
68
|
-
"compressed": [
|
|
69
|
-
"enabled": true,
|
|
70
|
-
"format": "aac",
|
|
71
|
-
"bitrate": 128000
|
|
72
|
-
]
|
|
73
|
-
]
|
|
74
|
-
] as [String : Any]
|
|
75
|
-
|
|
76
|
-
// Expected behavior: Should return compression info with file URI
|
|
77
|
-
let mockResult = simulateRecording(config: config)
|
|
78
|
-
|
|
79
|
-
let hasCompressionInfo = mockResult["compression"] != nil
|
|
80
|
-
let compressionDict = mockResult["compression"] as? [String: Any]
|
|
81
|
-
let hasCompressedUri = compressionDict?["compressedFileUri"] != nil
|
|
82
|
-
let format = compressionDict?["format"] as? String
|
|
83
|
-
|
|
84
|
-
let passed = hasCompressionInfo && hasCompressedUri && format == "aac"
|
|
85
|
-
|
|
86
|
-
results.append((
|
|
87
|
-
name: "AAC Compressed-Only",
|
|
88
|
-
passed: passed,
|
|
89
|
-
message: "Compression info: \(hasCompressionInfo), URI: \(hasCompressedUri), Format: \(format ?? "nil")"
|
|
90
|
-
))
|
|
91
|
-
|
|
92
|
-
if passed {
|
|
93
|
-
print("โ
Compression info properly returned")
|
|
94
|
-
print("โ
Compressed file URI: \(compressionDict?["compressedFileUri"] ?? "nil")")
|
|
95
|
-
print("โ
Format: \(format ?? "nil")")
|
|
96
|
-
} else {
|
|
97
|
-
print("โ FAIL: Compression info missing or incomplete")
|
|
98
|
-
}
|
|
99
|
-
print()
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
func testCompressedOnlyOpus() {
|
|
103
|
-
print("Test 2: Compressed-Only Output with Opus (fallback to AAC on iOS)")
|
|
104
|
-
print("-----------------------------------------------------------------")
|
|
105
|
-
|
|
106
|
-
let config = [
|
|
107
|
-
"sampleRate": 48000,
|
|
108
|
-
"channels": 1,
|
|
109
|
-
"encoding": "pcm_16bit",
|
|
110
|
-
"output": [
|
|
111
|
-
"primary": ["enabled": false],
|
|
112
|
-
"compressed": [
|
|
113
|
-
"enabled": true,
|
|
114
|
-
"format": "opus", // Should fallback to AAC on iOS
|
|
115
|
-
"bitrate": 64000
|
|
116
|
-
]
|
|
117
|
-
]
|
|
118
|
-
] as [String : Any]
|
|
119
|
-
|
|
120
|
-
let mockResult = simulateRecording(config: config)
|
|
121
|
-
|
|
122
|
-
let compressionDict = mockResult["compression"] as? [String: Any]
|
|
123
|
-
let format = compressionDict?["format"] as? String
|
|
124
|
-
let bitrate = compressionDict?["bitrate"] as? Int
|
|
125
|
-
|
|
126
|
-
// On iOS, Opus should fallback to AAC
|
|
127
|
-
let passed = format == "aac" && bitrate != nil
|
|
128
|
-
|
|
129
|
-
results.append((
|
|
130
|
-
name: "OpusโAAC Fallback",
|
|
131
|
-
passed: passed,
|
|
132
|
-
message: "Format: \(format ?? "nil"), Bitrate: \(bitrate ?? 0)"
|
|
133
|
-
))
|
|
134
|
-
|
|
135
|
-
if passed {
|
|
136
|
-
print("โ
Opus correctly fell back to AAC")
|
|
137
|
-
print("โ
Bitrate preserved: \(bitrate ?? 0)")
|
|
138
|
-
} else {
|
|
139
|
-
print("โ FAIL: Incorrect format or missing bitrate")
|
|
140
|
-
}
|
|
141
|
-
print()
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
func testCompressedFileAccess() {
|
|
145
|
-
print("Test 3: Verify Compressed File Accessibility")
|
|
146
|
-
print("-------------------------------------------")
|
|
147
|
-
|
|
148
|
-
let config = [
|
|
149
|
-
"sampleRate": 44100,
|
|
150
|
-
"channels": 1,
|
|
151
|
-
"encoding": "pcm_16bit",
|
|
152
|
-
"output": [
|
|
153
|
-
"primary": ["enabled": false],
|
|
154
|
-
"compressed": ["enabled": true, "format": "aac"]
|
|
155
|
-
]
|
|
156
|
-
] as [String : Any]
|
|
157
|
-
|
|
158
|
-
let mockResult = simulateRecording(config: config)
|
|
159
|
-
|
|
160
|
-
// Check main result structure
|
|
161
|
-
let fileUri = mockResult["fileUri"] as? String ?? ""
|
|
162
|
-
let _ = mockResult["filename"] as? String ?? ""
|
|
163
|
-
let _ = mockResult["mimeType"] as? String ?? ""
|
|
164
|
-
|
|
165
|
-
// Check compression structure
|
|
166
|
-
let compressionDict = mockResult["compression"] as? [String: Any]
|
|
167
|
-
let compressedUri = compressionDict?["compressedFileUri"] as? String
|
|
168
|
-
let compressedSize = compressionDict?["size"] as? Int64
|
|
169
|
-
|
|
170
|
-
// When primary is disabled, we should either:
|
|
171
|
-
// 1. Get compression info with the compressed file URI
|
|
172
|
-
// 2. Or use compressed URI as main fileUri (like web does)
|
|
173
|
-
let hasAccessToCompressed = (compressedUri != nil && !compressedUri!.isEmpty) ||
|
|
174
|
-
(!fileUri.isEmpty && fileUri != "")
|
|
175
|
-
|
|
176
|
-
let passed = hasAccessToCompressed && compressedSize != nil
|
|
177
|
-
|
|
178
|
-
results.append((
|
|
179
|
-
name: "File Accessibility",
|
|
180
|
-
passed: passed,
|
|
181
|
-
message: "Main URI: '\(fileUri)', Compressed URI: '\(compressedUri ?? "nil")', Size: \(compressedSize ?? 0)"
|
|
182
|
-
))
|
|
183
|
-
|
|
184
|
-
if passed {
|
|
185
|
-
print("โ
Compressed file is accessible")
|
|
186
|
-
print("โ
File size reported: \(compressedSize ?? 0) bytes")
|
|
187
|
-
} else {
|
|
188
|
-
print("โ FAIL: Cannot access compressed file")
|
|
189
|
-
print(" Main fileUri: '\(fileUri)'")
|
|
190
|
-
print(" Compressed URI: '\(compressedUri ?? "nil")'")
|
|
191
|
-
}
|
|
192
|
-
print()
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Helper to simulate recording
|
|
196
|
-
func simulateRecording(config: [String: Any]) -> [String: Any] {
|
|
197
|
-
// This simulates the current BUGGY behavior
|
|
198
|
-
// After the fix, this should return proper compression info
|
|
199
|
-
|
|
200
|
-
let outputConfig = config["output"] as? [String: Any]
|
|
201
|
-
let primaryConfig = outputConfig?["primary"] as? [String: Any]
|
|
202
|
-
let compressedConfig = outputConfig?["compressed"] as? [String: Any]
|
|
203
|
-
|
|
204
|
-
let primaryEnabled = primaryConfig?["enabled"] as? Bool ?? true
|
|
205
|
-
let compressedEnabled = compressedConfig?["enabled"] as? Bool ?? false
|
|
206
|
-
|
|
207
|
-
if !primaryEnabled {
|
|
208
|
-
// Current buggy behavior - returns nil compression
|
|
209
|
-
let result: [String: Any] = [
|
|
210
|
-
"fileUri": "",
|
|
211
|
-
"filename": "stream-only",
|
|
212
|
-
"durationMs": 5000,
|
|
213
|
-
"size": 0,
|
|
214
|
-
"mimeType": "audio/wav"
|
|
215
|
-
]
|
|
216
|
-
// BUG: compression should be included but is currently nil
|
|
217
|
-
return result
|
|
218
|
-
} else {
|
|
219
|
-
// Normal behavior when primary is enabled
|
|
220
|
-
var result: [String: Any] = [
|
|
221
|
-
"fileUri": "file:///mock/recording.wav",
|
|
222
|
-
"filename": "recording.wav",
|
|
223
|
-
"durationMs": 5000,
|
|
224
|
-
"size": 240000,
|
|
225
|
-
"mimeType": "audio/wav"
|
|
226
|
-
]
|
|
227
|
-
|
|
228
|
-
if compressedEnabled {
|
|
229
|
-
result["compression"] = [
|
|
230
|
-
"compressedFileUri": "file:///mock/recording.aac",
|
|
231
|
-
"format": "aac",
|
|
232
|
-
"bitrate": 128000,
|
|
233
|
-
"size": 40000,
|
|
234
|
-
"mimeType": "audio/aac"
|
|
235
|
-
]
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return result
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
func printResults() {
|
|
243
|
-
print("\n๐ Test Results")
|
|
244
|
-
print("===============")
|
|
245
|
-
|
|
246
|
-
let passed = results.filter { $0.passed }.count
|
|
247
|
-
let total = results.count
|
|
248
|
-
|
|
249
|
-
for result in results {
|
|
250
|
-
let status = result.passed ? "โ
" : "โ"
|
|
251
|
-
print("\(status) \(result.name)")
|
|
252
|
-
print(" \(result.message)")
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
print("\nSummary: \(passed)/\(total) tests passed")
|
|
256
|
-
|
|
257
|
-
if passed == total {
|
|
258
|
-
print("๐ All tests passed!")
|
|
259
|
-
} else {
|
|
260
|
-
print("โ ๏ธ Some tests failed - Fix needed!")
|
|
261
|
-
print("\n๐ง Required Fix:")
|
|
262
|
-
print("- When primary output is disabled, compression info must be included")
|
|
263
|
-
print("- Compressed file URI must be accessible to users")
|
|
264
|
-
print("- This affects iOS and Android (Web works correctly)")
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Run the test
|
|
270
|
-
let test = CompressedOnlyOutputTest()
|
|
271
|
-
test.runAllTests()
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env swift
|
|
2
|
-
|
|
3
|
-
import Foundation
|
|
4
|
-
import AVFoundation
|
|
5
|
-
|
|
6
|
-
// Integration test for Output Control feature
|
|
7
|
-
// This tests the ACTUAL behavior of the output configuration in real scenarios
|
|
8
|
-
|
|
9
|
-
print("๐งช Output Control Integration Test")
|
|
10
|
-
print("==================================\n")
|
|
11
|
-
|
|
12
|
-
class OutputControlTest {
|
|
13
|
-
let testDir: URL
|
|
14
|
-
var results: [(name: String, passed: Bool, message: String)] = []
|
|
15
|
-
|
|
16
|
-
init() {
|
|
17
|
-
let tempDir = FileManager.default.temporaryDirectory
|
|
18
|
-
testDir = tempDir.appendingPathComponent("output_control_test_\(UUID().uuidString)")
|
|
19
|
-
try? FileManager.default.createDirectory(at: testDir, withIntermediateDirectories: true)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
deinit {
|
|
23
|
-
try? FileManager.default.removeItem(at: testDir)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
func runAllTests() {
|
|
27
|
-
testDefaultOutput()
|
|
28
|
-
testPrimaryOnlyOutput()
|
|
29
|
-
testCompressedOnlyOutput()
|
|
30
|
-
testBothOutputs()
|
|
31
|
-
testNoOutputs()
|
|
32
|
-
printResults()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
func testDefaultOutput() {
|
|
36
|
-
print("Test 1: Default Output (Primary Only)")
|
|
37
|
-
print("-------------------------------------")
|
|
38
|
-
|
|
39
|
-
let fileURL = testDir.appendingPathComponent("default_recording.wav")
|
|
40
|
-
|
|
41
|
-
// Simulate default recording (primary enabled, compressed disabled)
|
|
42
|
-
let _ = createMockRecording(
|
|
43
|
-
primaryURL: fileURL,
|
|
44
|
-
compressedURL: nil,
|
|
45
|
-
primaryEnabled: true,
|
|
46
|
-
compressedEnabled: false
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
let fileExists = FileManager.default.fileExists(atPath: fileURL.path)
|
|
50
|
-
var fileSize: Int64 = 0
|
|
51
|
-
|
|
52
|
-
if fileExists {
|
|
53
|
-
if let attributes = try? FileManager.default.attributesOfItem(atPath: fileURL.path) {
|
|
54
|
-
fileSize = attributes[.size] as? Int64 ?? 0
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let passed = fileExists && fileSize > 44 // More than just header
|
|
59
|
-
results.append((
|
|
60
|
-
name: "Default Output",
|
|
61
|
-
passed: passed,
|
|
62
|
-
message: "Primary file created: \(fileExists), Size: \(fileSize) bytes"
|
|
63
|
-
))
|
|
64
|
-
|
|
65
|
-
print("โ Primary file created: \(fileURL.lastPathComponent)")
|
|
66
|
-
print("โ File size: \(fileSize) bytes\n")
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
func testPrimaryOnlyOutput() {
|
|
70
|
-
print("Test 2: Primary Output Only")
|
|
71
|
-
print("---------------------------")
|
|
72
|
-
|
|
73
|
-
let primaryURL = testDir.appendingPathComponent("primary_only.wav")
|
|
74
|
-
let compressedURL = testDir.appendingPathComponent("should_not_exist.aac")
|
|
75
|
-
|
|
76
|
-
// Simulate primary only
|
|
77
|
-
let _ = createMockRecording(
|
|
78
|
-
primaryURL: primaryURL,
|
|
79
|
-
compressedURL: compressedURL,
|
|
80
|
-
primaryEnabled: true,
|
|
81
|
-
compressedEnabled: false
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
let primaryExists = FileManager.default.fileExists(atPath: primaryURL.path)
|
|
85
|
-
let compressedExists = FileManager.default.fileExists(atPath: compressedURL.path)
|
|
86
|
-
|
|
87
|
-
let passed = primaryExists && !compressedExists
|
|
88
|
-
results.append((
|
|
89
|
-
name: "Primary Only",
|
|
90
|
-
passed: passed,
|
|
91
|
-
message: "Primary: \(primaryExists), Compressed: \(compressedExists)"
|
|
92
|
-
))
|
|
93
|
-
|
|
94
|
-
print("โ Primary file exists: \(primaryExists)")
|
|
95
|
-
print("โ Compressed file exists: \(compressedExists)")
|
|
96
|
-
print("โ Primary-only output working correctly\n")
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
func testCompressedOnlyOutput() {
|
|
100
|
-
print("Test 3: Compressed Output Only")
|
|
101
|
-
print("------------------------------")
|
|
102
|
-
|
|
103
|
-
let primaryURL = testDir.appendingPathComponent("should_not_exist.wav")
|
|
104
|
-
let compressedURL = testDir.appendingPathComponent("compressed_only.aac")
|
|
105
|
-
|
|
106
|
-
// Simulate compressed only
|
|
107
|
-
let _ = createMockRecording(
|
|
108
|
-
primaryURL: primaryURL,
|
|
109
|
-
compressedURL: compressedURL,
|
|
110
|
-
primaryEnabled: false,
|
|
111
|
-
compressedEnabled: true
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
let primaryExists = FileManager.default.fileExists(atPath: primaryURL.path)
|
|
115
|
-
let compressedExists = FileManager.default.fileExists(atPath: compressedURL.path)
|
|
116
|
-
|
|
117
|
-
let passed = !primaryExists && compressedExists
|
|
118
|
-
results.append((
|
|
119
|
-
name: "Compressed Only",
|
|
120
|
-
passed: passed,
|
|
121
|
-
message: "Primary: \(primaryExists), Compressed: \(compressedExists)"
|
|
122
|
-
))
|
|
123
|
-
|
|
124
|
-
print("โ Primary file exists: \(primaryExists)")
|
|
125
|
-
print("โ Compressed file exists: \(compressedExists)")
|
|
126
|
-
print("โ Compressed-only output working correctly\n")
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
func testBothOutputs() {
|
|
130
|
-
print("Test 4: Both Outputs Enabled")
|
|
131
|
-
print("----------------------------")
|
|
132
|
-
|
|
133
|
-
let primaryURL = testDir.appendingPathComponent("both_primary.wav")
|
|
134
|
-
let compressedURL = testDir.appendingPathComponent("both_compressed.aac")
|
|
135
|
-
|
|
136
|
-
// Simulate both outputs
|
|
137
|
-
let _ = createMockRecording(
|
|
138
|
-
primaryURL: primaryURL,
|
|
139
|
-
compressedURL: compressedURL,
|
|
140
|
-
primaryEnabled: true,
|
|
141
|
-
compressedEnabled: true
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
let primaryExists = FileManager.default.fileExists(atPath: primaryURL.path)
|
|
145
|
-
let compressedExists = FileManager.default.fileExists(atPath: compressedURL.path)
|
|
146
|
-
|
|
147
|
-
let passed = primaryExists && compressedExists
|
|
148
|
-
results.append((
|
|
149
|
-
name: "Both Outputs",
|
|
150
|
-
passed: passed,
|
|
151
|
-
message: "Primary: \(primaryExists), Compressed: \(compressedExists)"
|
|
152
|
-
))
|
|
153
|
-
|
|
154
|
-
print("โ Primary file exists: \(primaryExists)")
|
|
155
|
-
print("โ Compressed file exists: \(compressedExists)")
|
|
156
|
-
print("โ Both outputs working correctly\n")
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
func testNoOutputs() {
|
|
160
|
-
print("Test 5: No Outputs (Streaming Only)")
|
|
161
|
-
print("-----------------------------------")
|
|
162
|
-
|
|
163
|
-
let primaryURL = testDir.appendingPathComponent("no_primary.wav")
|
|
164
|
-
let compressedURL = testDir.appendingPathComponent("no_compressed.aac")
|
|
165
|
-
|
|
166
|
-
var dataEmitted = false
|
|
167
|
-
var totalDataSize: Int64 = 0
|
|
168
|
-
var emissionCount = 0
|
|
169
|
-
|
|
170
|
-
// Simulate no file outputs but data emission continues
|
|
171
|
-
let _ = createMockRecording(
|
|
172
|
-
primaryURL: primaryURL,
|
|
173
|
-
compressedURL: compressedURL,
|
|
174
|
-
primaryEnabled: false,
|
|
175
|
-
compressedEnabled: false
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
// Simulate data emissions
|
|
179
|
-
for _ in 0..<5 {
|
|
180
|
-
let mockData = createMockAudioData(duration: 0.5, sampleRate: 48000)
|
|
181
|
-
dataEmitted = true
|
|
182
|
-
totalDataSize += Int64(mockData.count)
|
|
183
|
-
emissionCount += 1
|
|
184
|
-
Thread.sleep(forTimeInterval: 0.1)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
let primaryExists = FileManager.default.fileExists(atPath: primaryURL.path)
|
|
188
|
-
let compressedExists = FileManager.default.fileExists(atPath: compressedURL.path)
|
|
189
|
-
|
|
190
|
-
let passed = !primaryExists && !compressedExists && dataEmitted && emissionCount == 5
|
|
191
|
-
results.append((
|
|
192
|
-
name: "No Outputs (Streaming)",
|
|
193
|
-
passed: passed,
|
|
194
|
-
message: "Files exist: \(primaryExists || compressedExists), Emissions: \(emissionCount)"
|
|
195
|
-
))
|
|
196
|
-
|
|
197
|
-
print("โ Primary file exists: \(primaryExists)")
|
|
198
|
-
print("โ Compressed file exists: \(compressedExists)")
|
|
199
|
-
print("โ Data emissions: \(emissionCount)")
|
|
200
|
-
print("โ Total data size: \(totalDataSize) bytes")
|
|
201
|
-
print("โ Streaming-only mode working correctly\n")
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Helper functions
|
|
205
|
-
|
|
206
|
-
func createMockRecording(
|
|
207
|
-
primaryURL: URL?,
|
|
208
|
-
compressedURL: URL?,
|
|
209
|
-
primaryEnabled: Bool,
|
|
210
|
-
compressedEnabled: Bool
|
|
211
|
-
) -> Bool {
|
|
212
|
-
// Create primary file if enabled
|
|
213
|
-
if primaryEnabled, let url = primaryURL {
|
|
214
|
-
let header = createWavHeader(dataSize: 1000)
|
|
215
|
-
let audioData = Data(repeating: 0, count: 1000)
|
|
216
|
-
|
|
217
|
-
do {
|
|
218
|
-
var fileData = Data()
|
|
219
|
-
fileData.append(header)
|
|
220
|
-
fileData.append(audioData)
|
|
221
|
-
try fileData.write(to: url)
|
|
222
|
-
} catch {
|
|
223
|
-
print("Error creating primary file: \(error)")
|
|
224
|
-
return false
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Create compressed file if enabled
|
|
229
|
-
if compressedEnabled, let url = compressedURL {
|
|
230
|
-
// Mock AAC file (just some data)
|
|
231
|
-
let mockData = Data(repeating: 0xFF, count: 500)
|
|
232
|
-
do {
|
|
233
|
-
try mockData.write(to: url)
|
|
234
|
-
} catch {
|
|
235
|
-
print("Error creating compressed file: \(error)")
|
|
236
|
-
return false
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return true
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
func createMockAudioData(duration: Double, sampleRate: Double) -> Data {
|
|
244
|
-
let samples = Int(duration * sampleRate)
|
|
245
|
-
let bytesPerSample = 2 // 16-bit
|
|
246
|
-
return Data(repeating: 0, count: samples * bytesPerSample)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
func createWavHeader(dataSize: Int) -> Data {
|
|
250
|
-
var header = Data()
|
|
251
|
-
|
|
252
|
-
// RIFF header
|
|
253
|
-
header.append(contentsOf: "RIFF".utf8)
|
|
254
|
-
header.append(contentsOf: UInt32(36 + dataSize).littleEndianBytes)
|
|
255
|
-
header.append(contentsOf: "WAVE".utf8)
|
|
256
|
-
|
|
257
|
-
// fmt chunk
|
|
258
|
-
header.append(contentsOf: "fmt ".utf8)
|
|
259
|
-
header.append(contentsOf: UInt32(16).littleEndianBytes)
|
|
260
|
-
header.append(contentsOf: UInt16(1).littleEndianBytes) // PCM
|
|
261
|
-
header.append(contentsOf: UInt16(1).littleEndianBytes) // Channels
|
|
262
|
-
header.append(contentsOf: UInt32(48000).littleEndianBytes) // Sample rate
|
|
263
|
-
header.append(contentsOf: UInt32(96000).littleEndianBytes) // Byte rate
|
|
264
|
-
header.append(contentsOf: UInt16(2).littleEndianBytes) // Block align
|
|
265
|
-
header.append(contentsOf: UInt16(16).littleEndianBytes) // Bits per sample
|
|
266
|
-
|
|
267
|
-
// data chunk
|
|
268
|
-
header.append(contentsOf: "data".utf8)
|
|
269
|
-
header.append(contentsOf: UInt32(dataSize).littleEndianBytes)
|
|
270
|
-
|
|
271
|
-
return header
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
func printResults() {
|
|
275
|
-
print("๐ Test Results")
|
|
276
|
-
print("===============")
|
|
277
|
-
|
|
278
|
-
let passed = results.filter { $0.passed }.count
|
|
279
|
-
let total = results.count
|
|
280
|
-
|
|
281
|
-
for result in results {
|
|
282
|
-
let status = result.passed ? "โ
" : "โ"
|
|
283
|
-
print("\(status) \(result.name)")
|
|
284
|
-
print(" \(result.message)")
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
print("\nSummary: \(passed)/\(total) tests passed")
|
|
288
|
-
|
|
289
|
-
if passed == total {
|
|
290
|
-
print("๐ All tests passed!")
|
|
291
|
-
} else {
|
|
292
|
-
print("โ ๏ธ Some tests failed")
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
print("\n๐ Key Features Validated:")
|
|
296
|
-
print("- Default behavior creates primary WAV file only")
|
|
297
|
-
print("- Can create compressed file only (no WAV)")
|
|
298
|
-
print("- Can create both primary and compressed files")
|
|
299
|
-
print("- Streaming-only mode (no files created)")
|
|
300
|
-
print("- Data emission continues regardless of file outputs")
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Extension for little-endian conversion
|
|
305
|
-
extension UInt32 {
|
|
306
|
-
var littleEndianBytes: [UInt8] {
|
|
307
|
-
let value = self.littleEndian
|
|
308
|
-
return [UInt8(value & 0xff), UInt8((value >> 8) & 0xff),
|
|
309
|
-
UInt8((value >> 16) & 0xff), UInt8((value >> 24) & 0xff)]
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
extension UInt16 {
|
|
314
|
-
var littleEndianBytes: [UInt8] {
|
|
315
|
-
let value = self.littleEndian
|
|
316
|
-
return [UInt8(value & 0xff), UInt8((value >> 8) & 0xff)]
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Run the test
|
|
321
|
-
let test = OutputControlTest()
|
|
322
|
-
test.runAllTests()
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Integration tests for new features in expo-audio-studio
|
|
4
|
-
# This script runs all integration tests to validate ACTUAL platform behavior
|
|
5
|
-
|
|
6
|
-
echo "๐งช Running expo-audio-studio iOS Integration Tests"
|
|
7
|
-
echo "=================================================="
|
|
8
|
-
echo ""
|
|
9
|
-
|
|
10
|
-
# Change to the directory containing this script
|
|
11
|
-
cd "$(dirname "$0")"
|
|
12
|
-
|
|
13
|
-
# Make test scripts executable
|
|
14
|
-
chmod +x *.swift
|
|
15
|
-
|
|
16
|
-
echo "1๏ธโฃ Buffer Duration Test"
|
|
17
|
-
echo "========================="
|
|
18
|
-
swift buffer_duration_test.swift
|
|
19
|
-
echo ""
|
|
20
|
-
|
|
21
|
-
echo "2๏ธโฃ Output Control Test"
|
|
22
|
-
echo "========================"
|
|
23
|
-
swift output_control_test.swift
|
|
24
|
-
echo ""
|
|
25
|
-
|
|
26
|
-
echo "3๏ธโฃ Buffer and Fallback Test"
|
|
27
|
-
echo "============================"
|
|
28
|
-
swift buffer_and_fallback_test.swift
|
|
29
|
-
echo ""
|
|
30
|
-
|
|
31
|
-
echo "4๏ธโฃ Compressed-Only Output Test (Issue #244)"
|
|
32
|
-
echo "==========================================="
|
|
33
|
-
swift compressed_only_output_test.swift
|
|
34
|
-
echo ""
|
|
35
|
-
|
|
36
|
-
echo "โ
Integration tests validate real iOS behavior"
|
|
37
|
-
echo "โ
Tests must pass before merging any feature!"
|