@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,279 +0,0 @@
1
- package net.siteed.audiostudio
2
-
3
- import org.junit.Test
4
- import org.junit.Assert.*
5
- import org.junit.Before
6
- import org.junit.After
7
- import java.io.ByteArrayOutputStream
8
- import java.io.File
9
- import java.nio.ByteBuffer
10
- import java.nio.ByteOrder
11
- import kotlin.test.assertNotNull
12
-
13
- class AudioFileHandlerTest {
14
- private lateinit var tempDir: File
15
- private lateinit var audioFileHandler: AudioFileHandler
16
-
17
- @Before
18
- fun setUp() {
19
- // Create a temporary directory for testing
20
- tempDir = File(System.getProperty("java.io.tmpdir"), "audio_test_${System.currentTimeMillis()}")
21
- tempDir.mkdirs()
22
- audioFileHandler = AudioFileHandler(tempDir)
23
- }
24
-
25
- @After
26
- fun tearDown() {
27
- // Clean up temporary directory
28
- tempDir.deleteRecursively()
29
- }
30
-
31
- @Test
32
- fun testWriteWavHeader_writesCorrectHeaderFormat() {
33
- // Given
34
- val outputStream = ByteArrayOutputStream()
35
- val sampleRate = 44100
36
- val channels = 2
37
- val bitDepth = 16
38
-
39
- // When
40
- audioFileHandler.writeWavHeader(outputStream, sampleRate, channels, bitDepth)
41
- val header = outputStream.toByteArray()
42
-
43
- // Then
44
- assertEquals("WAV header should be 44 bytes", 44, header.size)
45
-
46
- // Check RIFF header
47
- assertEquals("Should start with RIFF", "RIFF", String(header.sliceArray(0..3)))
48
- assertEquals("Should contain WAVE identifier", "WAVE", String(header.sliceArray(8..11)))
49
- assertEquals("Should contain fmt chunk", "fmt ", String(header.sliceArray(12..15)))
50
- assertEquals("Should contain data chunk", "data", String(header.sliceArray(36..39)))
51
-
52
- // Check audio format (PCM = 1)
53
- val audioFormat = ByteBuffer.wrap(header.sliceArray(20..21)).order(ByteOrder.LITTLE_ENDIAN).short
54
- assertEquals("Audio format should be 1 (PCM)", 1, audioFormat.toInt())
55
-
56
- // Check channels
57
- val headerChannels = ByteBuffer.wrap(header.sliceArray(22..23)).order(ByteOrder.LITTLE_ENDIAN).short
58
- assertEquals("Channels should match", channels, headerChannels.toInt())
59
-
60
- // Check sample rate
61
- val headerSampleRate = ByteBuffer.wrap(header.sliceArray(24..27)).order(ByteOrder.LITTLE_ENDIAN).int
62
- assertEquals("Sample rate should match", sampleRate, headerSampleRate)
63
-
64
- // Check bit depth
65
- val headerBitDepth = ByteBuffer.wrap(header.sliceArray(34..35)).order(ByteOrder.LITTLE_ENDIAN).short
66
- assertEquals("Bit depth should match", bitDepth, headerBitDepth.toInt())
67
- }
68
-
69
- @Test
70
- fun testWriteWavHeader_calculatesCorrectByteRate() {
71
- // Given
72
- val outputStream = ByteArrayOutputStream()
73
- val sampleRate = 48000
74
- val channels = 1
75
- val bitDepth = 24
76
- val expectedByteRate = sampleRate * channels * bitDepth / 8
77
-
78
- // When
79
- audioFileHandler.writeWavHeader(outputStream, sampleRate, channels, bitDepth)
80
- val header = outputStream.toByteArray()
81
-
82
- // Then
83
- val byteRate = ByteBuffer.wrap(header.sliceArray(28..31)).order(ByteOrder.LITTLE_ENDIAN).int
84
- assertEquals("Byte rate should be correctly calculated", expectedByteRate, byteRate)
85
- }
86
-
87
- @Test
88
- fun testCreateAudioFile_createsFileWithCorrectExtension() {
89
- // Given
90
- val extension = "wav"
91
-
92
- // When
93
- val file = audioFileHandler.createAudioFile(extension)
94
-
95
- // Then
96
- assertNotNull("File should not be null", file)
97
- assertTrue("File should exist", file.exists())
98
- assertTrue("File should have correct extension", file.name.endsWith(".$extension"))
99
- assertTrue("File should have correct prefix", file.name.startsWith("recording_"))
100
-
101
- // Clean up
102
- file.delete()
103
- }
104
-
105
- @Test
106
- fun testCreateAudioFile_createsUniqueFiles() {
107
- // Given
108
- val extension = "wav"
109
-
110
- // When
111
- val file1 = audioFileHandler.createAudioFile(extension)
112
- Thread.sleep(10) // Small delay to ensure different timestamps
113
- val file2 = audioFileHandler.createAudioFile(extension)
114
-
115
- // Then
116
- assertNotEquals("Files should have unique names", file1.name, file2.name)
117
- assertTrue("First file should exist", file1.exists())
118
- assertTrue("Second file should exist", file2.exists())
119
-
120
- // Clean up
121
- file1.delete()
122
- file2.delete()
123
- }
124
-
125
- @Test
126
- fun testDeleteFile_deletesExistingFile() {
127
- // Given
128
- val file = audioFileHandler.createAudioFile("wav")
129
- assertTrue("File should exist before deletion", file.exists())
130
-
131
- // When
132
- val result = audioFileHandler.deleteFile(file)
133
-
134
- // Then
135
- assertTrue("Delete should return true", result)
136
- assertFalse("File should not exist after deletion", file.exists())
137
- }
138
-
139
- @Test
140
- fun testDeleteFile_handlesNullFile() {
141
- // When
142
- val result = audioFileHandler.deleteFile(null)
143
-
144
- // Then
145
- assertFalse("Delete should return false for null file", result)
146
- }
147
-
148
- @Test
149
- fun testDeleteFile_handlesNonExistentFile() {
150
- // Given
151
- val nonExistentFile = File(tempDir, "non_existent.wav")
152
- assertFalse("File should not exist", nonExistentFile.exists())
153
-
154
- // When
155
- val result = audioFileHandler.deleteFile(nonExistentFile)
156
-
157
- // Then
158
- assertFalse("Delete should return false for non-existent file", result)
159
- }
160
-
161
- @Test
162
- fun testClearAudioStorage_deletesAllFiles() {
163
- // Given
164
- val file1 = audioFileHandler.createAudioFile("wav")
165
- val file2 = audioFileHandler.createAudioFile("mp3")
166
- val file3 = audioFileHandler.createAudioFile("aac")
167
-
168
- assertTrue("File 1 should exist", file1.exists())
169
- assertTrue("File 2 should exist", file2.exists())
170
- assertTrue("File 3 should exist", file3.exists())
171
-
172
- // When
173
- audioFileHandler.clearAudioStorage()
174
-
175
- // Then
176
- assertFalse("File 1 should be deleted", file1.exists())
177
- assertFalse("File 2 should be deleted", file2.exists())
178
- assertFalse("File 3 should be deleted", file3.exists())
179
- assertEquals("Directory should be empty", 0, tempDir.listFiles()?.size ?: 0)
180
- }
181
-
182
- @Test
183
- fun testUpdateWavHeader_updatesFileSizeCorrectly() {
184
- // Given
185
- val file = audioFileHandler.createAudioFile("wav")
186
- val outputStream = file.outputStream()
187
-
188
- // Write header
189
- audioFileHandler.writeWavHeader(outputStream, 44100, 2, 16)
190
-
191
- // Write some audio data (1 second of silence)
192
- val audioDataSize = 44100 * 2 * 2 // sampleRate * channels * bytesPerSample
193
- val audioData = ByteArray(audioDataSize)
194
- outputStream.write(audioData)
195
- outputStream.close()
196
-
197
- // When
198
- audioFileHandler.updateWavHeader(file)
199
-
200
- // Then
201
- val updatedHeader = file.inputStream().use { it.readNBytes(44) }
202
-
203
- // Check file size field (bytes 4-7)
204
- val fileSize = ByteBuffer.wrap(updatedHeader.sliceArray(4..7)).order(ByteOrder.LITTLE_ENDIAN).int
205
- assertEquals("File size should be data size + 36", audioDataSize + 36, fileSize)
206
-
207
- // Check data size field (bytes 40-43)
208
- val dataSize = ByteBuffer.wrap(updatedHeader.sliceArray(40..43)).order(ByteOrder.LITTLE_ENDIAN).int
209
- assertEquals("Data size should match audio data size", audioDataSize, dataSize)
210
-
211
- // Clean up
212
- file.delete()
213
- }
214
-
215
- @Test
216
- fun testLoadRealWavFile_readsHeaderCorrectly() {
217
- // Given - Load a real WAV file from test resources
218
- val resourceStream = javaClass.classLoader?.getResourceAsStream("jfk.wav")
219
- assertNotNull("Test resource jfk.wav should exist", resourceStream)
220
-
221
- val testFile = File(tempDir, "test_jfk.wav")
222
- resourceStream?.use { input ->
223
- testFile.outputStream().use { output ->
224
- input.copyTo(output)
225
- }
226
- }
227
-
228
- // When - Read the WAV header
229
- val header = testFile.inputStream().use { it.readNBytes(44) }
230
-
231
- // Then - Validate header structure
232
- assertEquals("Should start with RIFF", "RIFF", String(header.sliceArray(0..3)))
233
- assertEquals("Should contain WAVE identifier", "WAVE", String(header.sliceArray(8..11)))
234
- assertEquals("Should contain fmt chunk", "fmt ", String(header.sliceArray(12..15)))
235
-
236
- // Extract audio properties
237
- val channels = ByteBuffer.wrap(header.sliceArray(22..23)).order(ByteOrder.LITTLE_ENDIAN).short
238
- val sampleRate = ByteBuffer.wrap(header.sliceArray(24..27)).order(ByteOrder.LITTLE_ENDIAN).int
239
- val bitDepth = ByteBuffer.wrap(header.sliceArray(34..35)).order(ByteOrder.LITTLE_ENDIAN).short
240
-
241
- // JFK.wav is known to be mono, 16kHz, 16-bit
242
- assertEquals("JFK audio should be mono", 1, channels.toInt())
243
- assertEquals("JFK audio should be 16kHz", 16000, sampleRate)
244
- assertEquals("JFK audio should be 16-bit", 16, bitDepth.toInt())
245
-
246
- // Clean up
247
- testFile.delete()
248
- }
249
-
250
- @Test
251
- fun testProcessMultipleRealWavFiles() {
252
- // Test with different real WAV files to ensure compatibility
253
- val testFiles = listOf("jfk.wav", "recorder_hello_world.wav", "osr_us_000_0010_8k.wav")
254
-
255
- for (fileName in testFiles) {
256
- val resourceStream = javaClass.classLoader?.getResourceAsStream(fileName)
257
- if (resourceStream != null) {
258
- val testFile = File(tempDir, "test_$fileName")
259
- resourceStream.use { input ->
260
- testFile.outputStream().use { output ->
261
- input.copyTo(output)
262
- }
263
- }
264
-
265
- // Verify file was created and has content
266
- assertTrue("$fileName should exist", testFile.exists())
267
- assertTrue("$fileName should have more than just header", testFile.length() > 44)
268
-
269
- // Read and validate header
270
- val header = testFile.inputStream().use { it.readNBytes(44) }
271
- assertEquals("$fileName should have RIFF header", "RIFF", String(header.sliceArray(0..3)))
272
- assertEquals("$fileName should have WAVE format", "WAVE", String(header.sliceArray(8..11)))
273
-
274
- // Clean up
275
- testFile.delete()
276
- }
277
- }
278
- }
279
- }
@@ -1,249 +0,0 @@
1
- package net.siteed.audiostudio
2
-
3
- import org.junit.Test
4
- import org.junit.Assert.*
5
-
6
- /**
7
- * Unit tests for audio focus strategy configuration and logic.
8
- * These tests verify that the RecordingConfig correctly handles audioFocusStrategy
9
- * parameter and that the smart defaults work as expected.
10
- */
11
- class AudioFocusStrategyTest {
12
-
13
- @Test
14
- fun testRecordingConfigWithExplicitBackgroundStrategy() {
15
- val options = mapOf(
16
- "sampleRate" to 44100,
17
- "channels" to 1,
18
- "encoding" to "pcm_16bit",
19
- "android" to mapOf(
20
- "audioFocusStrategy" to "background"
21
- )
22
- )
23
-
24
- val result = RecordingConfig.fromMap(options)
25
- assertTrue("Config creation should succeed", result.isSuccess)
26
-
27
- val (config, _) = result.getOrThrow()
28
- assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
29
- }
30
-
31
- @Test
32
- fun testRecordingConfigWithExplicitInteractiveStrategy() {
33
- val options = mapOf(
34
- "sampleRate" to 44100,
35
- "channels" to 1,
36
- "encoding" to "pcm_16bit",
37
- "android" to mapOf(
38
- "audioFocusStrategy" to "interactive"
39
- )
40
- )
41
-
42
- val result = RecordingConfig.fromMap(options)
43
- assertTrue("Config creation should succeed", result.isSuccess)
44
-
45
- val (config, _) = result.getOrThrow()
46
- assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
47
- }
48
-
49
- @Test
50
- fun testRecordingConfigWithExplicitCommunicationStrategy() {
51
- val options = mapOf(
52
- "sampleRate" to 44100,
53
- "channels" to 1,
54
- "encoding" to "pcm_16bit",
55
- "android" to mapOf(
56
- "audioFocusStrategy" to "communication"
57
- )
58
- )
59
-
60
- val result = RecordingConfig.fromMap(options)
61
- assertTrue("Config creation should succeed", result.isSuccess)
62
-
63
- val (config, _) = result.getOrThrow()
64
- assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
65
- }
66
-
67
- @Test
68
- fun testRecordingConfigWithExplicitNoneStrategy() {
69
- val options = mapOf(
70
- "sampleRate" to 44100,
71
- "channels" to 1,
72
- "encoding" to "pcm_16bit",
73
- "android" to mapOf(
74
- "audioFocusStrategy" to "none"
75
- )
76
- )
77
-
78
- val result = RecordingConfig.fromMap(options)
79
- assertTrue("Config creation should succeed", result.isSuccess)
80
-
81
- val (config, _) = result.getOrThrow()
82
- assertEquals("Audio focus strategy should be none", "none", config.audioFocusStrategy)
83
- }
84
-
85
- @Test
86
- fun testRecordingConfigWithoutAudioFocusStrategy() {
87
- val options = mapOf(
88
- "sampleRate" to 44100,
89
- "channels" to 1,
90
- "encoding" to "pcm_16bit"
91
- )
92
-
93
- val result = RecordingConfig.fromMap(options)
94
- assertTrue("Config creation should succeed", result.isSuccess)
95
-
96
- val (config, _) = result.getOrThrow()
97
- assertNull("Audio focus strategy should be null when not specified", config.audioFocusStrategy)
98
- }
99
-
100
- @Test
101
- fun testRecordingConfigWithInvalidAudioFocusStrategy() {
102
- val options = mapOf(
103
- "sampleRate" to 44100,
104
- "channels" to 1,
105
- "encoding" to "pcm_16bit",
106
- "android" to mapOf(
107
- "audioFocusStrategy" to "invalid_strategy"
108
- )
109
- )
110
-
111
- val result = RecordingConfig.fromMap(options)
112
- assertTrue("Config creation should succeed even with invalid strategy", result.isSuccess)
113
-
114
- val (config, _) = result.getOrThrow()
115
- assertEquals("Invalid audio focus strategy should be preserved", "invalid_strategy", config.audioFocusStrategy)
116
- }
117
-
118
- @Test
119
- fun testRecordingConfigWithNullAudioFocusStrategy() {
120
- val options = mapOf(
121
- "sampleRate" to 44100,
122
- "channels" to 1,
123
- "encoding" to "pcm_16bit",
124
- "android" to mapOf(
125
- "audioFocusStrategy" to null
126
- )
127
- )
128
-
129
- val result = RecordingConfig.fromMap(options)
130
- assertTrue("Config creation should succeed", result.isSuccess)
131
-
132
- val (config, _) = result.getOrThrow()
133
- assertNull("Audio focus strategy should be null", config.audioFocusStrategy)
134
- }
135
-
136
- @Test
137
- fun testRecordingConfigKeepAwakeAndBackgroundStrategy() {
138
- val options = mapOf(
139
- "sampleRate" to 44100,
140
- "channels" to 1,
141
- "encoding" to "pcm_16bit",
142
- "keepAwake" to true,
143
- "android" to mapOf(
144
- "audioFocusStrategy" to "background"
145
- )
146
- )
147
-
148
- val result = RecordingConfig.fromMap(options)
149
- assertTrue("Config creation should succeed", result.isSuccess)
150
-
151
- val (config, _) = result.getOrThrow()
152
- assertTrue("keepAwake should be true", config.keepAwake)
153
- assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
154
- }
155
-
156
- @Test
157
- fun testRecordingConfigKeepAwakeFalseAndInteractiveStrategy() {
158
- val options = mapOf(
159
- "sampleRate" to 44100,
160
- "channels" to 1,
161
- "encoding" to "pcm_16bit",
162
- "keepAwake" to false,
163
- "android" to mapOf(
164
- "audioFocusStrategy" to "interactive"
165
- )
166
- )
167
-
168
- val result = RecordingConfig.fromMap(options)
169
- assertTrue("Config creation should succeed", result.isSuccess)
170
-
171
- val (config, _) = result.getOrThrow()
172
- assertFalse("keepAwake should be false", config.keepAwake)
173
- assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
174
- }
175
-
176
- @Test
177
- fun testRecordingConfigWithAutoResumeAndBackgroundStrategy() {
178
- val options = mapOf(
179
- "sampleRate" to 44100,
180
- "channels" to 1,
181
- "encoding" to "pcm_16bit",
182
- "autoResumeAfterInterruption" to true,
183
- "android" to mapOf(
184
- "audioFocusStrategy" to "background"
185
- )
186
- )
187
-
188
- val result = RecordingConfig.fromMap(options)
189
- assertTrue("Config creation should succeed", result.isSuccess)
190
-
191
- val (config, _) = result.getOrThrow()
192
- assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
193
- assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
194
- }
195
-
196
- @Test
197
- fun testRecordingConfigWithCommunicationStrategyAndSpeechSampleRate() {
198
- val options = mapOf(
199
- "sampleRate" to 16000, // Common speech sample rate
200
- "channels" to 1,
201
- "encoding" to "pcm_16bit",
202
- "android" to mapOf(
203
- "audioFocusStrategy" to "communication"
204
- )
205
- )
206
-
207
- val result = RecordingConfig.fromMap(options)
208
- assertTrue("Config creation should succeed", result.isSuccess)
209
-
210
- val (config, _) = result.getOrThrow()
211
- assertEquals("Sample rate should be 16000", 16000, config.sampleRate)
212
- assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
213
- }
214
-
215
- @Test
216
- fun testDefaultRecordingConfigValues() {
217
- val result = RecordingConfig.fromMap(null)
218
- assertTrue("Config creation should succeed with null input", result.isSuccess)
219
-
220
- val (config, _) = result.getOrThrow()
221
- assertNull("Default audio focus strategy should be null", config.audioFocusStrategy)
222
- assertTrue("Default keepAwake should be true", config.keepAwake)
223
- assertFalse("Default autoResumeAfterInterruption should be false", config.autoResumeAfterInterruption)
224
- }
225
-
226
- @Test
227
- fun testRecordingConfigCompleteAudioFocusConfiguration() {
228
- val options = mapOf(
229
- "sampleRate" to 44100,
230
- "channels" to 1,
231
- "encoding" to "pcm_16bit",
232
- "keepAwake" to true,
233
- "autoResumeAfterInterruption" to true,
234
- "showNotification" to true,
235
- "android" to mapOf(
236
- "audioFocusStrategy" to "background"
237
- )
238
- )
239
-
240
- val result = RecordingConfig.fromMap(options)
241
- assertTrue("Config creation should succeed", result.isSuccess)
242
-
243
- val (config, _) = result.getOrThrow()
244
- assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
245
- assertTrue("keepAwake should be true", config.keepAwake)
246
- assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
247
- assertTrue("showNotification should be true", config.showNotification)
248
- }
249
- }
@@ -1,151 +0,0 @@
1
- package net.siteed.audiostudio
2
-
3
- import org.junit.Test
4
- import org.junit.Assert.*
5
- import org.junit.Before
6
- import java.io.File
7
-
8
- class AudioFormatTest {
9
- private lateinit var tempDir: File
10
-
11
- @Before
12
- fun setUp() {
13
- tempDir = File(System.getProperty("java.io.tmpdir"), "audio_format_test_${System.currentTimeMillis()}")
14
- tempDir.mkdirs()
15
- }
16
-
17
- @Test
18
- fun testGetFileExtension_aacDefaultsToM4a() {
19
- // Given
20
- val config = RecordingConfig(
21
- output = OutputConfig(
22
- compressed = OutputConfig.CompressedOutput(
23
- enabled = true,
24
- format = "aac",
25
- preferRawStream = false
26
- )
27
- )
28
- )
29
-
30
- // When
31
- val file = createTestFile(config, isCompressed = true)
32
-
33
- // Then
34
- assertTrue("AAC without preferRawStream should produce .m4a", file.name.endsWith(".m4a"))
35
- }
36
-
37
- @Test
38
- fun testGetFileExtension_aacWithPreferRawStreamProducesAac() {
39
- // Given
40
- val config = RecordingConfig(
41
- output = OutputConfig(
42
- compressed = OutputConfig.CompressedOutput(
43
- enabled = true,
44
- format = "aac",
45
- preferRawStream = true
46
- )
47
- )
48
- )
49
-
50
- // When
51
- val file = createTestFile(config, isCompressed = true)
52
-
53
- // Then
54
- assertTrue("AAC with preferRawStream should produce .aac", file.name.endsWith(".aac"))
55
- }
56
-
57
- @Test
58
- fun testGetFileExtension_opusProducesOpus() {
59
- // Given
60
- val config = RecordingConfig(
61
- output = OutputConfig(
62
- compressed = OutputConfig.CompressedOutput(
63
- enabled = true,
64
- format = "opus"
65
- )
66
- )
67
- )
68
-
69
- // When
70
- val file = createTestFile(config, isCompressed = true)
71
-
72
- // Then
73
- assertTrue("Opus should produce .opus", file.name.endsWith(".opus"))
74
- }
75
-
76
- @Test
77
- fun testGetFileExtension_wavForUncompressed() {
78
- // Given
79
- val config = RecordingConfig()
80
-
81
- // When
82
- val file = createTestFile(config, isCompressed = false)
83
-
84
- // Then
85
- assertTrue("Uncompressed should produce .wav", file.name.endsWith(".wav"))
86
- }
87
-
88
- @Test
89
- fun testCompressedOutput_parseFromMap() {
90
- // Given
91
- val map = mapOf(
92
- "enabled" to true,
93
- "format" to "aac",
94
- "bitrate" to 192000,
95
- "preferRawStream" to true
96
- )
97
-
98
- // When
99
- val compressed = OutputConfig.CompressedOutput(
100
- enabled = map["enabled"] as Boolean,
101
- format = map["format"] as String,
102
- bitrate = map["bitrate"] as Int,
103
- preferRawStream = map["preferRawStream"] as Boolean
104
- )
105
-
106
- // Then
107
- assertTrue("enabled should be true", compressed.enabled)
108
- assertEquals("format should be aac", "aac", compressed.format)
109
- assertEquals("bitrate should be 192000", 192000, compressed.bitrate)
110
- assertTrue("preferRawStream should be true", compressed.preferRawStream)
111
- }
112
-
113
- @Test
114
- fun testCompressedOutput_defaultValues() {
115
- // When
116
- val compressed = OutputConfig.CompressedOutput()
117
-
118
- // Then
119
- assertFalse("enabled should default to false", compressed.enabled)
120
- assertEquals("format should default to aac", "aac", compressed.format)
121
- assertEquals("bitrate should default to 128000", 128000, compressed.bitrate)
122
- assertFalse("preferRawStream should default to false", compressed.preferRawStream)
123
- }
124
-
125
- /**
126
- * Helper function to simulate file creation logic from AudioRecorderManager
127
- */
128
- private fun createTestFile(config: RecordingConfig, isCompressed: Boolean): File {
129
- val baseFilename = config.filename?.let {
130
- it.substringBeforeLast('.', it)
131
- } ?: "test_recording"
132
-
133
- val extension = if (isCompressed) {
134
- when (config.output.compressed.format.lowercase()) {
135
- "aac" -> {
136
- if (config.output.compressed.preferRawStream) {
137
- "aac" // Raw AAC stream
138
- } else {
139
- "m4a" // M4A container (new default)
140
- }
141
- }
142
- "opus" -> "opus" // Opus in OGG container
143
- else -> config.output.compressed.format.lowercase()
144
- }
145
- } else {
146
- "wav"
147
- }
148
-
149
- return File(tempDir, "$baseFilename.$extension")
150
- }
151
- }