@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +356 -5
  2. package/android/src/main/java/net/siteed/audiostudio/AudioStreamDecoder.kt +306 -94
  3. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +39 -6
  4. package/build/cjs/errors/AudioStreamError.js +9 -0
  5. package/build/cjs/errors/AudioStreamError.js.map +1 -1
  6. package/build/cjs/errors/AudioStreamError.test.js +22 -1
  7. package/build/cjs/errors/AudioStreamError.test.js.map +1 -1
  8. package/build/cjs/streamAudioData.js +99 -32
  9. package/build/cjs/streamAudioData.js.map +1 -1
  10. package/build/cjs/utils/audioProcessing.js +14 -10
  11. package/build/cjs/utils/audioProcessing.js.map +1 -1
  12. package/build/esm/errors/AudioStreamError.js +9 -0
  13. package/build/esm/errors/AudioStreamError.js.map +1 -1
  14. package/build/esm/errors/AudioStreamError.test.js +22 -1
  15. package/build/esm/errors/AudioStreamError.test.js.map +1 -1
  16. package/build/esm/streamAudioData.js +99 -32
  17. package/build/esm/streamAudioData.js.map +1 -1
  18. package/build/esm/utils/audioProcessing.js +14 -10
  19. package/build/esm/utils/audioProcessing.js.map +1 -1
  20. package/build/types/errors/AudioStreamError.d.ts.map +1 -1
  21. package/build/types/streamAudioData.d.ts +5 -0
  22. package/build/types/streamAudioData.d.ts.map +1 -1
  23. package/build/types/utils/audioProcessing.d.ts +2 -2
  24. package/build/types/utils/audioProcessing.d.ts.map +1 -1
  25. package/ios/AudioStreamDecoder.swift +191 -100
  26. package/ios/AudioStudioModule.swift +48 -9
  27. package/package.json +163 -146
  28. package/scripts/README.md +58 -0
  29. package/src/errors/AudioStreamError.test.ts +29 -2
  30. package/src/errors/AudioStreamError.ts +14 -0
  31. package/src/streamAudioData.ts +146 -42
  32. package/src/utils/audioProcessing.ts +25 -14
  33. package/android/src/androidTest/assets/chorus.wav +0 -0
  34. package/android/src/androidTest/assets/jfk.wav +0 -0
  35. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  36. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  37. package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +0 -190
  38. package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +0 -197
  39. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +0 -487
  40. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +0 -250
  41. package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +0 -186
  42. package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +0 -332
  43. package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +0 -324
  44. package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +0 -253
  45. package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +0 -218
  46. package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +0 -120
  47. package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +0 -345
  48. package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +0 -340
  49. package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +0 -252
  50. package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +0 -95
  51. package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +0 -43
  52. package/android/src/test/java/net/siteed/audiostudio/AndroidCallStateTest.kt +0 -37
  53. package/android/src/test/java/net/siteed/audiostudio/AndroidEventEmitterTest.kt +0 -28
  54. package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +0 -279
  55. package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +0 -249
  56. package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +0 -151
  57. package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +0 -273
  58. package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +0 -140
  59. package/android/src/test/java/net/siteed/audiostudio/InterruptionAutoResumePolicyTest.kt +0 -49
  60. package/android/src/test/resources/chorus.wav +0 -0
  61. package/android/src/test/resources/generate_test_audio.py +0 -94
  62. package/android/src/test/resources/jfk.wav +0 -0
  63. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  64. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  65. package/ios/AudioStudioTests/AudioFileHandlerTests.swift +0 -338
  66. package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +0 -331
  67. package/ios/AudioStudioTests/AudioStreamDecoderTests.swift +0 -128
  68. package/ios/AudioStudioTests/AudioTestHelpers.swift +0 -130
  69. package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +0 -334
  70. package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +0 -105
  71. package/ios/AudioStudioTests/Info.plist +0 -22
  72. package/ios/AudioStudioTests/README.md +0 -39
  73. package/ios/AudioStudioTests/SimpleAudioTest.swift +0 -98
  74. package/ios/AudioStudioTests/TestAudioGenerator.swift +0 -75
  75. package/ios/tests/README.md +0 -41
  76. package/ios/tests/integration/buffer_and_fallback_test.swift +0 -178
  77. package/ios/tests/integration/buffer_duration_test.swift +0 -185
  78. package/ios/tests/integration/compressed_only_output_test.swift +0 -271
  79. package/ios/tests/integration/output_control_test.swift +0 -322
  80. package/ios/tests/integration/run_integration_tests.sh +0 -37
  81. package/ios/tests/opus_support_test_macos.swift +0 -154
  82. package/ios/tests/standalone/audio_processing_test.swift +0 -144
  83. package/ios/tests/standalone/audio_recording_test.swift +0 -277
  84. package/ios/tests/standalone/audio_streaming_test.swift +0 -249
  85. package/ios/tests/standalone/standalone_test.swift +0 -144
@@ -1,253 +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 Compressed-Only Output (Issue #244)
13
- *
14
- * This test validates that when primary output is disabled and compressed output
15
- * is enabled, the compressed file information is properly returned in the result.
16
- */
17
- @RunWith(AndroidJUnit4::class)
18
- class CompressedOnlyOutputTest {
19
-
20
- private val context = InstrumentationRegistry.getInstrumentation().targetContext
21
-
22
- @Test
23
- fun testCompressedOnlyOutput_AAC() {
24
- println("๐Ÿงช Test: Compressed-Only Output with AAC")
25
- println("---------------------------------------")
26
-
27
- // Configuration with primary disabled, compressed enabled
28
- val config = Bundle().apply {
29
- putInt("sampleRate", 44100)
30
- putInt("channels", 1)
31
- putString("encoding", "pcm_16bit")
32
-
33
- val outputBundle = Bundle().apply {
34
- val primaryBundle = Bundle().apply {
35
- putBoolean("enabled", false)
36
- }
37
- val compressedBundle = Bundle().apply {
38
- putBoolean("enabled", true)
39
- putString("format", "aac")
40
- putInt("bitrate", 128000)
41
- }
42
- putBundle("primary", primaryBundle)
43
- putBundle("compressed", compressedBundle)
44
- }
45
- putBundle("output", outputBundle)
46
- }
47
-
48
- // Simulate recording result
49
- val result = simulateRecording(config)
50
-
51
- // Verify compression info is present
52
- val compressionBundle = result.getBundle("compression")
53
- assertNotNull("Compression info should not be null", compressionBundle)
54
-
55
- // Verify compressed file URI is provided
56
- val compressedFileUri = compressionBundle?.getString("compressedFileUri")
57
- assertNotNull("Compressed file URI should not be null", compressedFileUri)
58
- assertNotEquals("Compressed file URI should not be empty", "", compressedFileUri)
59
-
60
- // Verify format and bitrate
61
- assertEquals("aac", compressionBundle?.getString("format"))
62
- assertEquals(128000, compressionBundle?.getInt("bitrate"))
63
-
64
- println("โœ… Compression info properly returned")
65
- println("โœ… Compressed file URI: $compressedFileUri")
66
- println("โœ… Format: ${compressionBundle?.getString("format")}")
67
- println()
68
- }
69
-
70
- @Test
71
- fun testCompressedOnlyOutput_Opus() {
72
- println("๐Ÿงช Test: Compressed-Only Output with Opus")
73
- println("----------------------------------------")
74
-
75
- val config = Bundle().apply {
76
- putInt("sampleRate", 48000)
77
- putInt("channels", 1)
78
- putString("encoding", "pcm_16bit")
79
-
80
- val outputBundle = Bundle().apply {
81
- val primaryBundle = Bundle().apply {
82
- putBoolean("enabled", false)
83
- }
84
- val compressedBundle = Bundle().apply {
85
- putBoolean("enabled", true)
86
- putString("format", "opus")
87
- putInt("bitrate", 64000)
88
- }
89
- putBundle("primary", primaryBundle)
90
- putBundle("compressed", compressedBundle)
91
- }
92
- putBundle("output", outputBundle)
93
- }
94
-
95
- val result = simulateRecording(config)
96
- val compressionBundle = result.getBundle("compression")
97
-
98
- assertNotNull("Compression info should not be null", compressionBundle)
99
- assertEquals("opus", compressionBundle?.getString("format"))
100
- assertEquals(64000, compressionBundle?.getInt("bitrate"))
101
-
102
- println("โœ… Opus compression properly configured")
103
- println()
104
- }
105
-
106
- @Test
107
- fun testFileAccessibility() {
108
- println("๐Ÿงช Test: Verify Compressed File Accessibility")
109
- println("--------------------------------------------")
110
-
111
- val config = Bundle().apply {
112
- putInt("sampleRate", 44100)
113
- putInt("channels", 1)
114
- putString("encoding", "pcm_16bit")
115
-
116
- val outputBundle = Bundle().apply {
117
- val primaryBundle = Bundle().apply {
118
- putBoolean("enabled", false)
119
- }
120
- val compressedBundle = Bundle().apply {
121
- putBoolean("enabled", true)
122
- putString("format", "aac")
123
- }
124
- putBundle("primary", primaryBundle)
125
- putBundle("compressed", compressedBundle)
126
- }
127
- putBundle("output", outputBundle)
128
- }
129
-
130
- val result = simulateRecording(config)
131
-
132
- // Check main result structure
133
- val fileUri = result.getString("fileUri", "")
134
- val filename = result.getString("filename", "")
135
-
136
- // Check compression structure
137
- val compressionBundle = result.getBundle("compression")
138
- val compressedUri = compressionBundle?.getString("compressedFileUri")
139
- val compressedSize = compressionBundle?.getLong("size", 0L) ?: 0L
140
-
141
- // When primary is disabled, we should have access to compressed file
142
- val hasAccessToCompressed = !compressedUri.isNullOrEmpty() || fileUri.isNotEmpty()
143
-
144
- assertTrue("Should have access to compressed file", hasAccessToCompressed)
145
- assertTrue("Compressed file should have size > 0", compressedSize > 0)
146
-
147
- println("โœ… Compressed file is accessible")
148
- println("โœ… File size reported: $compressedSize bytes")
149
- println()
150
- }
151
-
152
- @Test
153
- fun testBugScenario() {
154
- println("๐Ÿงช Test: Current Bug Scenario")
155
- println("-----------------------------")
156
-
157
- // This test demonstrates the current bug
158
- val config = Bundle().apply {
159
- putInt("sampleRate", 44100)
160
- putInt("channels", 1)
161
- putString("encoding", "pcm_16bit")
162
-
163
- val outputBundle = Bundle().apply {
164
- val primaryBundle = Bundle().apply {
165
- putBoolean("enabled", false)
166
- }
167
- val compressedBundle = Bundle().apply {
168
- putBoolean("enabled", true)
169
- putString("format", "aac")
170
- putInt("bitrate", 128000)
171
- }
172
- putBundle("primary", primaryBundle)
173
- putBundle("compressed", compressedBundle)
174
- }
175
- putBundle("output", outputBundle)
176
- }
177
-
178
- // Current buggy behavior
179
- val buggyResult = simulateBuggyRecording(config)
180
-
181
- // This is what currently happens - compression is null
182
- val compressionBundle = buggyResult.getBundle("compression")
183
-
184
- if (compressionBundle == null) {
185
- println("โŒ BUG CONFIRMED: Compression info is null when primary is disabled")
186
- println(" This prevents users from accessing the compressed file")
187
- } else {
188
- println("โœ… Bug appears to be fixed - compression info is present")
189
- }
190
- println()
191
- }
192
-
193
- /**
194
- * Simulates the expected correct behavior after fix
195
- */
196
- private fun simulateRecording(config: Bundle): Bundle {
197
- val outputConfig = config.getBundle("output")
198
- val primaryEnabled = outputConfig?.getBundle("primary")?.getBoolean("enabled", true) ?: true
199
- val compressedConfig = outputConfig?.getBundle("compressed")
200
- val compressedEnabled = compressedConfig?.getBoolean("enabled", false) ?: false
201
-
202
- return Bundle().apply {
203
- if (!primaryEnabled) {
204
- // Expected behavior after fix
205
- putString("fileUri", "")
206
- putString("filename", "stream-only")
207
- putLong("durationMs", 5000)
208
- putLong("size", 0)
209
- putString("mimeType", "audio/wav")
210
-
211
- // FIXED: Include compression info when compressed is enabled
212
- if (compressedEnabled) {
213
- val compressionBundle = Bundle().apply {
214
- putString("compressedFileUri", "file:///storage/emulated/0/Android/data/test/files/recording.aac")
215
- putString("format", compressedConfig?.getString("format") ?: "aac")
216
- putInt("bitrate", compressedConfig?.getInt("bitrate") ?: 128000)
217
- putLong("size", 40000)
218
- putString("mimeType", "audio/aac")
219
- }
220
- putBundle("compression", compressionBundle)
221
- }
222
- } else {
223
- // Normal behavior
224
- putString("fileUri", "file:///storage/emulated/0/Android/data/test/files/recording.wav")
225
- putString("filename", "recording.wav")
226
- putLong("durationMs", 5000)
227
- putLong("size", 240000)
228
- putString("mimeType", "audio/wav")
229
- }
230
- }
231
- }
232
-
233
- /**
234
- * Simulates the current buggy behavior
235
- */
236
- private fun simulateBuggyRecording(config: Bundle): Bundle {
237
- val outputConfig = config.getBundle("output")
238
- val primaryEnabled = outputConfig?.getBundle("primary")?.getBoolean("enabled", true) ?: true
239
-
240
- return Bundle().apply {
241
- if (!primaryEnabled) {
242
- // Current buggy behavior
243
- putString("fileUri", "")
244
- putString("filename", "stream-only")
245
- putLong("durationMs", 5000)
246
- putLong("size", 0)
247
- putString("mimeType", "audio/wav")
248
- // BUG: compression is null even when compressed output is enabled
249
- putBundle("compression", null)
250
- }
251
- }
252
- }
253
- }
@@ -1,218 +0,0 @@
1
- package net.siteed.audiostudio.integration
2
-
3
- import android.content.Context
4
- import androidx.test.ext.junit.runners.AndroidJUnit4
5
- import androidx.test.platform.app.InstrumentationRegistry
6
- import net.siteed.audiostudio.OutputConfig
7
- import net.siteed.audiostudio.RecordingConfig
8
- import org.junit.After
9
- import org.junit.Assert.*
10
- import org.junit.Before
11
- import org.junit.Test
12
- import org.junit.runner.RunWith
13
- import java.io.File
14
-
15
- /**
16
- * Integration test for device disconnection fallback behavior.
17
- * Tests various scenarios when audio devices are disconnected during recording.
18
- */
19
- @RunWith(AndroidJUnit4::class)
20
- class DeviceDisconnectionFallbackTest {
21
-
22
- private lateinit var context: Context
23
- private lateinit var testDir: File
24
-
25
- @Before
26
- fun setup() {
27
- context = InstrumentationRegistry.getInstrumentation().targetContext
28
-
29
- // Create test directory
30
- testDir = File(context.getExternalFilesDir(null), "fallback_test")
31
- testDir.mkdirs()
32
-
33
- // Clear any existing test files
34
- testDir.listFiles()?.forEach { it.delete() }
35
- }
36
-
37
- @After
38
- fun tearDown() {
39
- // Clean up test files
40
- testDir.listFiles()?.forEach { it.delete() }
41
- testDir.delete()
42
- }
43
-
44
- @Test
45
- fun testDeviceDisconnectionBehavior_Fallback() {
46
- println("๐Ÿงช Test: Device Disconnection with Fallback Behavior")
47
- println("--------------------------------------------------")
48
-
49
- // Create recording config with fallback behavior
50
- val config = RecordingConfig(
51
- sampleRate = 44100,
52
- channels = 1,
53
- encoding = "pcm_16bit",
54
- deviceDisconnectionBehavior = "fallback",
55
- output = OutputConfig(
56
- primary = OutputConfig.PrimaryOutput(
57
- enabled = true,
58
- format = "wav"
59
- )
60
- ),
61
- outputDirectory = testDir.absolutePath,
62
- filename = "fallback_test.wav"
63
- )
64
-
65
- // Verify device disconnection behavior is set
66
- assertEquals("Device disconnection behavior should be fallback",
67
- "fallback", config.deviceDisconnectionBehavior)
68
-
69
- println("โœ… RecordingConfig correctly configured with fallback behavior")
70
-
71
- // Note: Actual device disconnection simulation would require running the full
72
- // AudioStudioModule with device connection/disconnection events
73
- }
74
-
75
- @Test
76
- fun testDeviceDisconnectionBehavior_Pause() {
77
- println("๐Ÿงช Test: Device Disconnection with Pause Behavior")
78
- println("-----------------------------------------------")
79
-
80
- val config = RecordingConfig(
81
- sampleRate = 44100,
82
- channels = 1,
83
- encoding = "pcm_16bit",
84
- deviceDisconnectionBehavior = "pause",
85
- output = OutputConfig(
86
- primary = OutputConfig.PrimaryOutput(
87
- enabled = true,
88
- format = "wav"
89
- )
90
- ),
91
- outputDirectory = testDir.absolutePath,
92
- filename = "pause_test.wav"
93
- )
94
-
95
- // Verify device disconnection behavior is set to pause
96
- assertEquals("Device disconnection behavior should be pause",
97
- "pause", config.deviceDisconnectionBehavior)
98
-
99
- println("โœ… RecordingConfig correctly configured with pause behavior")
100
- }
101
-
102
- @Test
103
- fun testDefaultDeviceDisconnectionBehavior() {
104
- println("๐Ÿงช Test: Default Device Disconnection Behavior")
105
- println("--------------------------------------------")
106
-
107
- // Create config without specifying deviceDisconnectionBehavior
108
- val config = RecordingConfig(
109
- sampleRate = 44100,
110
- channels = 1,
111
- encoding = "pcm_16bit",
112
- output = OutputConfig(
113
- primary = OutputConfig.PrimaryOutput(
114
- enabled = true,
115
- format = "wav"
116
- )
117
- ),
118
- outputDirectory = testDir.absolutePath,
119
- filename = "default_test.wav"
120
- )
121
-
122
- // Default behavior should be null (handled as pause in implementation)
123
- assertNull("Default device disconnection behavior should be null",
124
- config.deviceDisconnectionBehavior)
125
-
126
- println("โœ… Default device disconnection behavior is null (treated as 'pause')")
127
- }
128
-
129
- @Test
130
- fun testInterruptionEventReasons() {
131
- println("๐Ÿงช Test: Interruption Event Reasons")
132
- println("----------------------------------")
133
-
134
- // Test that the expected event reasons are valid
135
- val fallbackReasons = listOf("deviceFallback", "deviceSwitchFailed")
136
- val pauseReasons = listOf("deviceDisconnected")
137
-
138
- println("๐Ÿ“‹ Expected reasons for fallback behavior:")
139
- fallbackReasons.forEach { reason ->
140
- println(" - $reason")
141
- assertNotNull("Reason should not be null", reason)
142
- assertTrue("Reason should not be empty", reason.isNotEmpty())
143
- }
144
-
145
- println("๐Ÿ“‹ Expected reasons for pause behavior:")
146
- pauseReasons.forEach { reason ->
147
- println(" - $reason")
148
- assertNotNull("Reason should not be null", reason)
149
- assertTrue("Reason should not be empty", reason.isNotEmpty())
150
- }
151
-
152
- println("โœ… All interruption event reasons are valid")
153
- }
154
-
155
- @Test
156
- fun testAllDeviceDisconnectionBehaviors() {
157
- println("๐Ÿงช Test: All Device Disconnection Behaviors")
158
- println("-----------------------------------------")
159
-
160
- val behaviors = listOf("fallback", "pause")
161
-
162
- behaviors.forEach { behavior ->
163
- val config = RecordingConfig(
164
- sampleRate = 44100,
165
- channels = 1,
166
- encoding = "pcm_16bit",
167
- deviceDisconnectionBehavior = behavior,
168
- output = OutputConfig(
169
- primary = OutputConfig.PrimaryOutput(
170
- enabled = true,
171
- format = "wav"
172
- )
173
- ),
174
- outputDirectory = testDir.absolutePath,
175
- filename = "${behavior}_test.wav"
176
- )
177
-
178
- // Verify configuration
179
- assertEquals("Device disconnection behavior should be $behavior",
180
- behavior, config.deviceDisconnectionBehavior)
181
-
182
- println("โœ… Behavior '$behavior' correctly configured")
183
- }
184
- }
185
-
186
- @Test
187
- fun testDeviceDisconnectionWithCompressedOutput() {
188
- println("๐Ÿงช Test: Device Disconnection with Compressed Output")
189
- println("--------------------------------------------------")
190
-
191
- // Test fallback behavior with compressed output enabled
192
- val config = RecordingConfig(
193
- sampleRate = 44100,
194
- channels = 1,
195
- encoding = "pcm_16bit",
196
- deviceDisconnectionBehavior = "fallback",
197
- output = OutputConfig(
198
- primary = OutputConfig.PrimaryOutput(
199
- enabled = false
200
- ),
201
- compressed = OutputConfig.CompressedOutput(
202
- enabled = true,
203
- format = "aac",
204
- bitrate = 128000
205
- )
206
- ),
207
- outputDirectory = testDir.absolutePath,
208
- filename = "compressed_fallback_test"
209
- )
210
-
211
- assertEquals("Device disconnection behavior should be fallback",
212
- "fallback", config.deviceDisconnectionBehavior)
213
- assertTrue("Compressed output should be enabled", config.output.compressed.enabled)
214
- assertFalse("Primary output should be disabled", config.output.primary.enabled)
215
-
216
- println("โœ… Fallback behavior configured with compressed-only output")
217
- }
218
- }
@@ -1,120 +0,0 @@
1
- package net.siteed.audiostudio.integration
2
-
3
- import android.os.Bundle
4
- import android.util.Log
5
- import androidx.test.ext.junit.runners.AndroidJUnit4
6
- import androidx.test.platform.app.InstrumentationRegistry
7
- import org.junit.Test
8
- import org.junit.runner.RunWith
9
- import org.junit.Assert.*
10
- import java.io.File
11
- import kotlin.math.abs
12
-
13
- /**
14
- * Integration test to validate event emission interval enforcement.
15
- *
16
- * This test verifies that the configured intervals respect platform
17
- * minimums to prevent excessive CPU usage.
18
- */
19
- @RunWith(AndroidJUnit4::class)
20
- class EventEmissionIntervalTest {
21
-
22
- private val TAG = "EventEmissionIntervalTest"
23
- private val context = InstrumentationRegistry.getInstrumentation().targetContext
24
-
25
- @Test
26
- fun testMinimumIntervalEnforcement() {
27
- println("๐Ÿงช Test: Minimum Interval Enforcement")
28
- println("------------------------------------")
29
-
30
- // Test cases for different requested intervals
31
- val testCases = listOf(
32
- 5 to 10, // 5ms should be clamped to MIN_INTERVAL (10ms)
33
- 10 to 10, // 10ms should remain 10ms
34
- 50 to 50, // 50ms should remain 50ms
35
- 100 to 100 // 100ms should remain 100ms
36
- )
37
-
38
- for ((requested, expected) in testCases) {
39
- val config = Bundle().apply {
40
- putInt("sampleRate", 48000)
41
- putInt("channels", 1)
42
- putLong("interval", requested.toLong())
43
- putLong("intervalAnalysis", requested.toLong())
44
- }
45
-
46
- // Parse the config to validate interval enforcement
47
- val configMap = bundleToMap(config)
48
- val result = net.siteed.audiostudio.RecordingConfig.fromMap(configMap)
49
-
50
- assertTrue("Config parsing should succeed", result.isSuccess)
51
- val (recordingConfig, _) = result.getOrNull()!!
52
-
53
- println("Requested interval: ${requested}ms")
54
- println("Actual interval: ${recordingConfig.interval}ms")
55
- println("Expected interval: ${expected}ms")
56
-
57
- assertEquals(
58
- "Interval should be clamped to minimum if below MIN_INTERVAL",
59
- expected.toLong(),
60
- recordingConfig.interval
61
- )
62
-
63
- assertEquals(
64
- "Analysis interval should be clamped to minimum if below MIN_INTERVAL",
65
- expected.toLong(),
66
- recordingConfig.intervalAnalysis
67
- )
68
-
69
- println("โœ“ Passed\n")
70
- }
71
- }
72
-
73
- @Test
74
- fun testIntervalConsistencyAcrossPlatforms() {
75
- println("๐Ÿงช Test: Platform Consistency Check")
76
- println("----------------------------------")
77
-
78
- // Document the expected behavior across platforms
79
- println("Expected behavior after fix:")
80
- println("- iOS: Minimum interval = 10ms")
81
- println("- Android: Minimum interval = 10ms (enforced)")
82
- println("")
83
-
84
- // Verify Android enforces the minimum
85
- val intervals = listOf(1, 5, 10, 50, 100)
86
- for (interval in intervals) {
87
- val config = Bundle().apply {
88
- putLong("interval", interval.toLong())
89
- putLong("intervalAnalysis", interval.toLong())
90
- }
91
-
92
- val configMap = bundleToMap(config)
93
- val result = net.siteed.audiostudio.RecordingConfig.fromMap(configMap)
94
- val (recordingConfig, _) = result.getOrNull()!!
95
-
96
- val expectedInterval = maxOf(10, interval).toLong()
97
- assertEquals(
98
- "Android should enforce MIN_INTERVAL of 10ms",
99
- expectedInterval,
100
- recordingConfig.interval
101
- )
102
-
103
- println("โœ“ Interval ${interval}ms -> ${recordingConfig.interval}ms")
104
- }
105
- }
106
-
107
- /**
108
- * Helper to convert Bundle to Map for RecordingConfig
109
- */
110
- private fun bundleToMap(bundle: Bundle): Map<String, Any?> {
111
- val map = mutableMapOf<String, Any?>()
112
- for (key in bundle.keySet()) {
113
- when (val value = bundle.get(key)) {
114
- is Bundle -> map[key] = bundleToMap(value)
115
- else -> map[key] = value
116
- }
117
- }
118
- return map
119
- }
120
- }