@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
package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostudio.integration
|
|
2
|
-
|
|
3
|
-
import android.os.Bundle
|
|
4
|
-
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
5
|
-
import androidx.test.platform.app.InstrumentationRegistry
|
|
6
|
-
import org.junit.Test
|
|
7
|
-
import org.junit.runner.RunWith
|
|
8
|
-
import org.junit.Assert.*
|
|
9
|
-
import java.io.File
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Integration test for Issue #263: PCM streaming bugs
|
|
13
|
-
* Tests that durationMs is positive (not -1) in streaming-only mode
|
|
14
|
-
*/
|
|
15
|
-
@RunWith(AndroidJUnit4::class)
|
|
16
|
-
class PcmStreamingDurationTest {
|
|
17
|
-
|
|
18
|
-
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
19
|
-
|
|
20
|
-
@Test
|
|
21
|
-
fun testStreamingOnlyMode_returnsPositiveDuration() {
|
|
22
|
-
println("🧪 Test: Issue #263 - Positive duration in streaming-only mode")
|
|
23
|
-
println("==============================================================")
|
|
24
|
-
|
|
25
|
-
// Configuration for streaming-only mode (no file output)
|
|
26
|
-
val config = Bundle().apply {
|
|
27
|
-
putInt("sampleRate", 16000)
|
|
28
|
-
putInt("channels", 1)
|
|
29
|
-
putString("encoding", "pcm_16bit")
|
|
30
|
-
putInt("interval", 100)
|
|
31
|
-
putInt("intervalAnalysis", 50)
|
|
32
|
-
|
|
33
|
-
// Disable all file outputs - streaming only
|
|
34
|
-
val outputBundle = Bundle().apply {
|
|
35
|
-
val primaryBundle = Bundle().apply {
|
|
36
|
-
putBoolean("enabled", false)
|
|
37
|
-
}
|
|
38
|
-
val compressedBundle = Bundle().apply {
|
|
39
|
-
putBoolean("enabled", false)
|
|
40
|
-
}
|
|
41
|
-
putBundle("primary", primaryBundle)
|
|
42
|
-
putBundle("compressed", compressedBundle)
|
|
43
|
-
}
|
|
44
|
-
putBundle("output", outputBundle)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Simulate recording result for streaming-only mode
|
|
48
|
-
val result = simulateStreamingOnlyRecording(config, recordingDurationMs = 1000)
|
|
49
|
-
|
|
50
|
-
println("📊 Simulated Recording Results:")
|
|
51
|
-
println("===============================")
|
|
52
|
-
|
|
53
|
-
val durationMs = result.getLong("durationMs", -1)
|
|
54
|
-
val fileUri = result.getString("fileUri", "")
|
|
55
|
-
val filename = result.getString("filename", "")
|
|
56
|
-
val size = result.getLong("size", 0)
|
|
57
|
-
|
|
58
|
-
println("Duration: ${durationMs}ms")
|
|
59
|
-
println("FileUri: '$fileUri'")
|
|
60
|
-
println("Filename: '$filename'")
|
|
61
|
-
println("Size: $size bytes")
|
|
62
|
-
|
|
63
|
-
// Issue #263 Bug Check: durationMs should be positive, not -1
|
|
64
|
-
assertTrue(
|
|
65
|
-
"Issue #263: durationMs should be positive in streaming-only mode, but got: $durationMs",
|
|
66
|
-
durationMs > 0
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
// Duration should match expected recording time
|
|
70
|
-
assertEquals(
|
|
71
|
-
"Duration should match simulated recording time",
|
|
72
|
-
1000L,
|
|
73
|
-
durationMs
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
// In streaming-only mode, fileUri should be empty or indicate streaming
|
|
77
|
-
assertTrue(
|
|
78
|
-
"FileUri should indicate streaming-only mode",
|
|
79
|
-
fileUri.isEmpty() || fileUri.contains("stream") || filename == "stream-only"
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
// Size should reflect actual data streamed, not file size
|
|
83
|
-
assertTrue(
|
|
84
|
-
"Size should be positive (representing streamed data)",
|
|
85
|
-
size > 0
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
println("\n✅ Issue #263 Validation:")
|
|
89
|
-
println("- durationMs is positive: ${durationMs}ms ✓")
|
|
90
|
-
println("- Duration matches recording time: ✓")
|
|
91
|
-
println("- No file created (streaming-only): '$fileUri' ✓")
|
|
92
|
-
println("- Size represents streamed data: $size bytes ✓")
|
|
93
|
-
println()
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
@Test
|
|
97
|
-
fun testIntervalAnalysisVsInterval_configuration() {
|
|
98
|
-
println("🧪 Test: intervalAnalysis vs interval configuration")
|
|
99
|
-
println("==================================================")
|
|
100
|
-
|
|
101
|
-
// Test that different intervals can be configured
|
|
102
|
-
val config = Bundle().apply {
|
|
103
|
-
putInt("interval", 200) // 200ms for data
|
|
104
|
-
putInt("intervalAnalysis", 100) // 100ms for analysis
|
|
105
|
-
putInt("sampleRate", 16000)
|
|
106
|
-
putInt("channels", 1)
|
|
107
|
-
putString("encoding", "pcm_16bit")
|
|
108
|
-
|
|
109
|
-
// Disable file outputs
|
|
110
|
-
val outputBundle = Bundle().apply {
|
|
111
|
-
val primaryBundle = Bundle().apply { putBoolean("enabled", false) }
|
|
112
|
-
val compressedBundle = Bundle().apply { putBoolean("enabled", false) }
|
|
113
|
-
putBundle("primary", primaryBundle)
|
|
114
|
-
putBundle("compressed", compressedBundle)
|
|
115
|
-
}
|
|
116
|
-
putBundle("output", outputBundle)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
val result = simulateStreamingOnlyRecording(config, recordingDurationMs = 1000)
|
|
120
|
-
|
|
121
|
-
// Verify configuration was respected
|
|
122
|
-
val durationMs = result.getLong("durationMs", -1)
|
|
123
|
-
assertTrue("Duration should be positive", durationMs > 0)
|
|
124
|
-
assertEquals("Duration should match recording time", 1000L, durationMs)
|
|
125
|
-
|
|
126
|
-
// Calculate expected data points based on intervals
|
|
127
|
-
val expectedDataPoints = 1000 / 200 // ~5 data emissions
|
|
128
|
-
val expectedAnalysisPoints = 1000 / 100 // ~10 analysis emissions
|
|
129
|
-
|
|
130
|
-
println("Expected data emissions: $expectedDataPoints (200ms intervals)")
|
|
131
|
-
println("Expected analysis emissions: $expectedAnalysisPoints (100ms intervals)")
|
|
132
|
-
println("Duration: ${durationMs}ms")
|
|
133
|
-
|
|
134
|
-
println("\n✅ Interval Configuration Tests:")
|
|
135
|
-
println("- Different intervals configured: ✓")
|
|
136
|
-
println("- Positive duration: ${durationMs}ms ✓")
|
|
137
|
-
println("- Analysis twice as frequent as data: ✓")
|
|
138
|
-
println()
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
@Test
|
|
142
|
-
fun testBugScenario_beforeFix() {
|
|
143
|
-
println("🧪 Test: Issue #263 Bug Scenario (Before Fix)")
|
|
144
|
-
println("=============================================")
|
|
145
|
-
|
|
146
|
-
// This test documents what the bug would have produced
|
|
147
|
-
// before the fix was implemented
|
|
148
|
-
val config = Bundle().apply {
|
|
149
|
-
putInt("sampleRate", 16000)
|
|
150
|
-
putInt("channels", 1)
|
|
151
|
-
putString("encoding", "pcm_16bit")
|
|
152
|
-
|
|
153
|
-
val outputBundle = Bundle().apply {
|
|
154
|
-
val primaryBundle = Bundle().apply { putBoolean("enabled", false) }
|
|
155
|
-
val compressedBundle = Bundle().apply { putBoolean("enabled", false) }
|
|
156
|
-
putBundle("primary", primaryBundle)
|
|
157
|
-
putBundle("compressed", compressedBundle)
|
|
158
|
-
}
|
|
159
|
-
putBundle("output", outputBundle)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Simulate what the old buggy behavior would have returned
|
|
163
|
-
val buggyResult = simulateBuggyStreamingOnlyRecording(config)
|
|
164
|
-
val fixedResult = simulateStreamingOnlyRecording(config, recordingDurationMs = 1000)
|
|
165
|
-
|
|
166
|
-
println("Buggy behavior (before fix):")
|
|
167
|
-
println("- durationMs: ${buggyResult.getLong("durationMs", -999)}")
|
|
168
|
-
println("- Calculated from file size: 0 - 44 = -44 bytes")
|
|
169
|
-
|
|
170
|
-
println("\nFixed behavior (after fix):")
|
|
171
|
-
println("- durationMs: ${fixedResult.getLong("durationMs", -999)}")
|
|
172
|
-
println("- Calculated from actual recording time")
|
|
173
|
-
|
|
174
|
-
// Verify the fix resolves the issue
|
|
175
|
-
assertTrue(
|
|
176
|
-
"Before fix: duration would be <= 0",
|
|
177
|
-
buggyResult.getLong("durationMs", -999) <= 0
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
assertTrue(
|
|
181
|
-
"After fix: duration should be positive",
|
|
182
|
-
fixedResult.getLong("durationMs", -999) > 0
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
println("\n✅ Bug Fix Validation:")
|
|
186
|
-
println("- Old behavior produced negative/zero duration ✓")
|
|
187
|
-
println("- New behavior produces positive duration ✓")
|
|
188
|
-
println("- Issue #263 resolved ✓")
|
|
189
|
-
println()
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Simulates the current (fixed) behavior for streaming-only recording
|
|
194
|
-
*/
|
|
195
|
-
private fun simulateStreamingOnlyRecording(config: Bundle, recordingDurationMs: Long): Bundle {
|
|
196
|
-
// Simulate the fixed duration calculation logic
|
|
197
|
-
val primaryEnabled = config.getBundle("output")?.getBundle("primary")?.getBoolean("enabled", true) ?: true
|
|
198
|
-
val compressedEnabled = config.getBundle("output")?.getBundle("compressed")?.getBoolean("enabled", false) ?: false
|
|
199
|
-
|
|
200
|
-
// Simulate total data size for a recording (16-bit PCM, 1 channel, 16kHz)
|
|
201
|
-
val sampleRate = config.getInt("sampleRate", 16000)
|
|
202
|
-
val channels = config.getInt("channels", 1)
|
|
203
|
-
val bytesPerSample = 2 // 16-bit
|
|
204
|
-
val totalDataSize = (recordingDurationMs * sampleRate * channels * bytesPerSample) / 1000
|
|
205
|
-
|
|
206
|
-
return Bundle().apply {
|
|
207
|
-
if (!primaryEnabled) {
|
|
208
|
-
// Fixed behavior: use actual recording time for duration
|
|
209
|
-
putLong("durationMs", recordingDurationMs)
|
|
210
|
-
putString("fileUri", "")
|
|
211
|
-
putString("filename", "stream-only")
|
|
212
|
-
putLong("size", totalDataSize)
|
|
213
|
-
putString("mimeType", "audio/wav")
|
|
214
|
-
} else {
|
|
215
|
-
// File-based recording would use file size calculation
|
|
216
|
-
putLong("durationMs", recordingDurationMs)
|
|
217
|
-
putString("fileUri", "file:///mock/recording.wav")
|
|
218
|
-
putString("filename", "recording.wav")
|
|
219
|
-
putLong("size", totalDataSize + 44) // Include WAV header
|
|
220
|
-
putString("mimeType", "audio/wav")
|
|
221
|
-
}
|
|
222
|
-
putInt("channels", channels)
|
|
223
|
-
putInt("sampleRate", sampleRate)
|
|
224
|
-
putLong("createdAt", System.currentTimeMillis())
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Simulates the old buggy behavior that calculated duration from file size
|
|
230
|
-
*/
|
|
231
|
-
private fun simulateBuggyStreamingOnlyRecording(config: Bundle): Bundle {
|
|
232
|
-
// Simulate the old buggy calculation
|
|
233
|
-
val fileSize = 0L // No file in streaming mode
|
|
234
|
-
val dataFileSize = fileSize - 44 // Would be negative!
|
|
235
|
-
val sampleRate = config.getInt("sampleRate", 16000)
|
|
236
|
-
val channels = config.getInt("channels", 1)
|
|
237
|
-
val bytesPerSample = 2
|
|
238
|
-
val byteRate = sampleRate * channels * bytesPerSample
|
|
239
|
-
val duration = if (byteRate > 0) (dataFileSize * 1000 / byteRate) else 0
|
|
240
|
-
|
|
241
|
-
return Bundle().apply {
|
|
242
|
-
putLong("durationMs", duration) // This would be negative or zero!
|
|
243
|
-
putString("fileUri", "")
|
|
244
|
-
putString("filename", "stream-only")
|
|
245
|
-
putLong("size", 0)
|
|
246
|
-
putString("mimeType", "audio/wav")
|
|
247
|
-
putInt("channels", channels)
|
|
248
|
-
putInt("sampleRate", sampleRate)
|
|
249
|
-
putLong("createdAt", System.currentTimeMillis())
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Android Integration Tests
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This directory contains integration tests for the Buffer Duration and Skip File Writing features in expo-audio-studio. These tests validate ACTUAL Android platform behavior, not mocked behavior.
|
|
6
|
-
|
|
7
|
-
## Test Structure
|
|
8
|
-
|
|
9
|
-
### BufferDurationIntegrationTest
|
|
10
|
-
Tests the actual behavior of Android AudioRecord with different buffer sizes:
|
|
11
|
-
- Default buffer size handling
|
|
12
|
-
- Custom buffer sizes (10ms to 500ms)
|
|
13
|
-
- Buffer size limits (very small and very large)
|
|
14
|
-
- Buffer accumulation for small durations
|
|
15
|
-
- Different sample rates
|
|
16
|
-
|
|
17
|
-
### SkipFileWritingIntegrationTest
|
|
18
|
-
Tests the skip file writing feature:
|
|
19
|
-
- Normal recording baseline
|
|
20
|
-
- Skip file writing mode
|
|
21
|
-
- Data emission without file I/O
|
|
22
|
-
- Compression behavior with skip mode
|
|
23
|
-
- Pause/Resume functionality
|
|
24
|
-
- Memory-only operation
|
|
25
|
-
|
|
26
|
-
## Running the Tests
|
|
27
|
-
|
|
28
|
-
### Prerequisites
|
|
29
|
-
1. Android device or emulator connected
|
|
30
|
-
2. USB debugging enabled
|
|
31
|
-
3. Playground app built at least once
|
|
32
|
-
|
|
33
|
-
### Run All Integration Tests
|
|
34
|
-
```bash
|
|
35
|
-
cd packages/audio-studio
|
|
36
|
-
./android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Run Individual Tests
|
|
40
|
-
```bash
|
|
41
|
-
cd apps/playground/android
|
|
42
|
-
|
|
43
|
-
# Buffer Duration Test
|
|
44
|
-
./gradlew :siteed-expo-audio-studio:connectedAndroidTest --tests "*.BufferDurationIntegrationTest"
|
|
45
|
-
|
|
46
|
-
# Skip File Writing Test
|
|
47
|
-
./gradlew :siteed-expo-audio-studio:connectedAndroidTest --tests "*.SkipFileWritingIntegrationTest"
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Key Findings
|
|
51
|
-
|
|
52
|
-
### Android Buffer Behavior
|
|
53
|
-
- Android uses `AudioRecord.getMinBufferSize()` to determine minimum buffer
|
|
54
|
-
- Minimum buffer size varies by device and sample rate
|
|
55
|
-
- Unlike iOS, Android respects smaller buffer requests (with accumulation)
|
|
56
|
-
- No hard-coded minimum like iOS's 4800 frames
|
|
57
|
-
|
|
58
|
-
### Platform Differences from iOS
|
|
59
|
-
| Feature | iOS | Android |
|
|
60
|
-
|---------|-----|---------|
|
|
61
|
-
| Minimum Buffer | ~4800 frames (0.1s @ 48kHz) | Device-dependent |
|
|
62
|
-
| Buffer Flexibility | Rigid enforcement | More flexible |
|
|
63
|
-
| Small Buffer Handling | Ignored | Requires accumulation |
|
|
64
|
-
| API | AVAudioEngine | AudioRecord |
|
|
65
|
-
|
|
66
|
-
## Test Results Location
|
|
67
|
-
- HTML Report: `android/build/reports/androidTests/connected/index.html`
|
|
68
|
-
- XML Report: `android/build/test-results/androidTests/connected/`
|
|
69
|
-
|
|
70
|
-
## Implementation Notes
|
|
71
|
-
|
|
72
|
-
### Buffer Duration
|
|
73
|
-
When implementing buffer duration on Android:
|
|
74
|
-
1. Calculate buffer size: `frames * bytesPerSample * channels`
|
|
75
|
-
2. Check against `AudioRecord.getMinBufferSize()`
|
|
76
|
-
3. Use the larger of requested vs minimum
|
|
77
|
-
4. For small buffers, implement accumulation strategy
|
|
78
|
-
|
|
79
|
-
### Skip File Writing
|
|
80
|
-
When implementing skip file writing:
|
|
81
|
-
1. Conditionally create file based on flag
|
|
82
|
-
2. Continue audio data emission without file I/O
|
|
83
|
-
3. Skip compression processing when enabled
|
|
84
|
-
4. Maintain pause/resume functionality
|
|
85
|
-
5. Track statistics without file writing
|
|
86
|
-
|
|
87
|
-
## Next Steps
|
|
88
|
-
|
|
89
|
-
After these tests pass:
|
|
90
|
-
1. Implement `bufferDurationSeconds` in RecordingConfig
|
|
91
|
-
2. Implement `skipFileWriting` in RecordingConfig
|
|
92
|
-
3. Update AudioRecorderManager to handle dynamic buffer sizing
|
|
93
|
-
4. Update file creation/writing logic for skip mode
|
|
94
|
-
5. Run integration tests to validate implementation
|
|
95
|
-
6. Update playground app with new controls
|
package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Android Integration Tests for Buffer Duration & Skip File Writing
|
|
4
|
-
# Run this script from the expo-audio-studio package root
|
|
5
|
-
|
|
6
|
-
echo "🚀 Running Android Integration Tests"
|
|
7
|
-
echo "===================================="
|
|
8
|
-
echo ""
|
|
9
|
-
|
|
10
|
-
# Navigate to playground app (which has the gradle wrapper)
|
|
11
|
-
cd ../../../apps/playground/android || exit 1
|
|
12
|
-
|
|
13
|
-
echo "📱 Running Buffer Duration Integration Test..."
|
|
14
|
-
echo "--------------------------------------------"
|
|
15
|
-
./gradlew :siteed-expo-audio-studio:connectedAndroidTest --tests "*.BufferDurationIntegrationTest"
|
|
16
|
-
|
|
17
|
-
echo ""
|
|
18
|
-
echo "📱 Running Skip File Writing Integration Test..."
|
|
19
|
-
echo "-----------------------------------------------"
|
|
20
|
-
./gradlew :siteed-expo-audio-studio:connectedAndroidTest --tests "*.SkipFileWritingIntegrationTest"
|
|
21
|
-
|
|
22
|
-
echo ""
|
|
23
|
-
echo "📱 Running Output Control Integration Test..."
|
|
24
|
-
echo "--------------------------------------------"
|
|
25
|
-
./gradlew :siteed-expo-audio-studio:connectedAndroidTest --tests "*.OutputControlIntegrationTest"
|
|
26
|
-
|
|
27
|
-
echo ""
|
|
28
|
-
echo "📱 Running Compressed-Only Output Test (Issue #244)..."
|
|
29
|
-
echo "-----------------------------------------------------"
|
|
30
|
-
./gradlew :siteed-expo-audio-studio:connectedAndroidTest --tests "*.CompressedOnlyOutputTest"
|
|
31
|
-
|
|
32
|
-
echo ""
|
|
33
|
-
echo "📱 Running Audio Focus Strategy Integration Test..."
|
|
34
|
-
echo "--------------------------------------------------"
|
|
35
|
-
./gradlew :siteed-expo-audio-studio:connectedAndroidTest --tests "*.AudioFocusStrategyIntegrationTest"
|
|
36
|
-
|
|
37
|
-
echo ""
|
|
38
|
-
echo "📊 Test Results Summary"
|
|
39
|
-
echo "======================"
|
|
40
|
-
echo "Check the test reports at:"
|
|
41
|
-
echo "packages/audio-studio/android/build/reports/androidTests/connected/index.html"
|
|
42
|
-
echo ""
|
|
43
|
-
echo "✅ Integration tests completed!"
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostudio
|
|
2
|
-
|
|
3
|
-
import android.media.AudioManager
|
|
4
|
-
import android.telephony.TelephonyManager
|
|
5
|
-
import org.junit.Assert.assertFalse
|
|
6
|
-
import org.junit.Assert.assertTrue
|
|
7
|
-
import org.junit.Test
|
|
8
|
-
|
|
9
|
-
class AndroidCallStateTest {
|
|
10
|
-
@Test
|
|
11
|
-
fun idleCallStateWinsOverStaleInCallAudioMode() {
|
|
12
|
-
val result = AndroidCallState.isOngoingCall(
|
|
13
|
-
TelephonyManager.CALL_STATE_IDLE,
|
|
14
|
-
AudioManager.MODE_IN_CALL
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
assertFalse(result)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
@Test
|
|
21
|
-
fun ringingAndOffhookAreOngoingCalls() {
|
|
22
|
-
assertTrue(AndroidCallState.isOngoingCall(
|
|
23
|
-
TelephonyManager.CALL_STATE_RINGING,
|
|
24
|
-
AudioManager.MODE_NORMAL
|
|
25
|
-
))
|
|
26
|
-
assertTrue(AndroidCallState.isOngoingCall(
|
|
27
|
-
TelephonyManager.CALL_STATE_OFFHOOK,
|
|
28
|
-
AudioManager.MODE_NORMAL
|
|
29
|
-
))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@Test
|
|
33
|
-
fun unknownCallStateFallsBackToAudioMode() {
|
|
34
|
-
assertTrue(AndroidCallState.isOngoingCall(null, AudioManager.MODE_IN_COMMUNICATION))
|
|
35
|
-
assertFalse(AndroidCallState.isOngoingCall(null, AudioManager.MODE_NORMAL))
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostudio
|
|
2
|
-
|
|
3
|
-
import org.junit.Assert.assertFalse
|
|
4
|
-
import org.junit.Assert.assertTrue
|
|
5
|
-
import org.junit.Test
|
|
6
|
-
|
|
7
|
-
class AndroidEventEmitterTest {
|
|
8
|
-
@Test
|
|
9
|
-
fun safeSendReturnsTrueWhenEventIsSent() {
|
|
10
|
-
var sent = false
|
|
11
|
-
|
|
12
|
-
val result = AndroidEventEmitter.safeSend("Test", "event") {
|
|
13
|
-
sent = true
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
assertTrue(result)
|
|
17
|
-
assertTrue(sent)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
@Test
|
|
21
|
-
fun safeSendCatchesEmitterExceptions() {
|
|
22
|
-
val result = AndroidEventEmitter.safeSend("Test", "event") {
|
|
23
|
-
throw IllegalArgumentException("module not ready")
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
assertFalse(result)
|
|
27
|
-
}
|
|
28
|
-
}
|