@siteed/expo-audio-studio 2.10.6 โ†’ 2.12.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 (23) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/android/src/androidTest/java/net/siteed/audiostream/integration/AudioFocusStrategyIntegrationTest.kt +332 -0
  3. package/android/src/androidTest/java/net/siteed/audiostream/integration/DeviceDisconnectionFallbackTest.kt +218 -0
  4. package/android/src/androidTest/java/net/siteed/audiostream/integration/EventEmissionIntervalTest.kt +120 -0
  5. package/android/src/androidTest/java/net/siteed/audiostream/integration/M4aFormatTest.kt +345 -0
  6. package/android/src/androidTest/java/net/siteed/audiostream/integration/PcmStreamingDurationTest.kt +252 -0
  7. package/android/src/androidTest/java/net/siteed/audiostream/integration/run_integration_tests.sh +5 -0
  8. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +44 -32
  9. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +198 -22
  10. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +13 -4
  11. package/android/src/test/java/net/siteed/audiostream/AudioFocusStrategyTest.kt +249 -0
  12. package/android/src/test/java/net/siteed/audiostream/AudioFormatTest.kt +151 -0
  13. package/android/src/test/java/net/siteed/audiostream/DeviceDisconnectionFallbackUnitTest.kt +140 -0
  14. package/build/cjs/ExpoAudioStream.types.js.map +1 -1
  15. package/build/esm/ExpoAudioStream.types.js.map +1 -1
  16. package/build/types/ExpoAudioStream.types.d.ts +25 -2
  17. package/build/types/ExpoAudioStream.types.d.ts.map +1 -1
  18. package/ios/AudioStreamManager.swift +55 -43
  19. package/ios/ExpoAudioStudioTests/EventEmissionIntervalTests.swift +105 -0
  20. package/ios/tests/README.md +41 -0
  21. package/ios/tests/opus_support_test_macos.swift +154 -0
  22. package/package.json +2 -2
  23. package/src/ExpoAudioStream.types.ts +27 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  ## [Unreleased]
9
9
 
10
10
 
11
+ ## [2.12.0] - 2025-06-07
12
+ ### Changed
13
+ - fix(android): resolve PCM streaming duration calculation bug (Issue #263) (#265) ([a0c5500](https://github.com/deeeed/expo-audio-stream/commit/a0c550099fec9d6b0d486440819173d9d9275908))
14
+ - feat(expo-audio-studio): implement Android-only audioFocusStrategy (#264) ([cc77226](https://github.com/deeeed/expo-audio-stream/commit/cc7722605a5502a58b8236b610c9bdccf5f7f561))
15
+ - docs: fix comment formatting in OutputConfig interface for clarity ([07fac61](https://github.com/deeeed/expo-audio-stream/commit/07fac61245843c601709bb7576db6e48b2106cf7))
16
+ ## [2.11.0] - 2025-06-05
17
+ ### Changed
18
+ - refactor(expo-audio-studio): remove android/build.gradle and add device disconnection fallback tests ([36fe9a9](https://github.com/deeeed/expo-audio-stream/commit/36fe9a921505e136ea50406d4b664c597293ffd8))
19
+ - fix(expo-audio-studio): enforce 10ms minimum interval on both platforms (#262) ([035fc07](https://github.com/deeeed/expo-audio-stream/commit/035fc076334c169a2371527bad0ca60f222d10ee))
20
+ - fix(expo-audio-studio): add proper MediaCodec resource cleanup in AudioProcessor ([2b069b6](https://github.com/deeeed/expo-audio-stream/commit/2b069b6ae512f97fae80df1b8cb38bb3a14538e5))
21
+ - feat(expo-audio-studio): add audio format enhancement specification and tests ([ace22a2](https://github.com/deeeed/expo-audio-stream/commit/ace22a22cf6c94ec58ddd4f6cb44f77dd383d6bc))
22
+ - feat\!: Add M4A support with preferRawStream option (#261) ([c9faeb0](https://github.com/deeeed/expo-audio-stream/commit/c9faeb01cd5dcd7407f73a0f7e6d5822adb862a4))
11
23
  ## [2.10.6] - 2025-06-04
12
24
  ### Changed
13
25
  - fix(expo-audio-studio): prevent durationMs returning 0 on iOS (#244) (#260) ([595e5d5](https://github.com/deeeed/expo-audio-stream/commit/595e5d56991c9fa88c2fa4e39efb197916cb8b84))
@@ -282,7 +294,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
282
294
  - Feature: Audio features extraction during recording.
283
295
  - Feature: Consistent WAV PCM recording format across all platforms.
284
296
 
285
- [unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.10.6...HEAD
297
+ [unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.12.0...HEAD
298
+ [2.12.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.11.0...@siteed/expo-audio-studio@2.12.0
299
+ [2.11.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.10.6...@siteed/expo-audio-studio@2.11.0
286
300
  [2.10.6]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.10.5...@siteed/expo-audio-studio@2.10.6
287
301
  [2.10.5]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.10.4...@siteed/expo-audio-studio@2.10.5
288
302
  [2.10.4]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-studio@2.10.3...@siteed/expo-audio-studio@2.10.4
@@ -0,0 +1,332 @@
1
+ package net.siteed.audiostream.integration
2
+
3
+ import android.media.AudioManager
4
+ import android.content.Context
5
+ import androidx.test.ext.junit.runners.AndroidJUnit4
6
+ import androidx.test.platform.app.InstrumentationRegistry
7
+ import org.junit.Test
8
+ import org.junit.Assert.*
9
+ import org.junit.Before
10
+ import org.junit.After
11
+ import org.junit.runner.RunWith
12
+ import net.siteed.audiostream.RecordingConfig
13
+ import java.io.File
14
+
15
+ /**
16
+ * Integration tests for audio focus strategy functionality.
17
+ * These tests run on actual Android devices/emulators to validate that
18
+ * audio focus strategies work correctly in real scenarios.
19
+ */
20
+ @RunWith(AndroidJUnit4::class)
21
+ class AudioFocusStrategyIntegrationTest {
22
+
23
+ private lateinit var context: Context
24
+ private lateinit var audioManager: AudioManager
25
+ private lateinit var filesDir: File
26
+
27
+ @Before
28
+ fun setUp() {
29
+ context = InstrumentationRegistry.getInstrumentation().targetContext
30
+ audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
31
+ filesDir = context.filesDir
32
+ }
33
+
34
+ @After
35
+ fun tearDown() {
36
+ // Clean up any test files
37
+ val testFiles = filesDir.listFiles { _, name ->
38
+ name.startsWith("test_audio_focus_")
39
+ }
40
+ testFiles?.forEach { it.delete() }
41
+ }
42
+
43
+ @Test
44
+ fun testRecordingConfigWithBackgroundStrategy() {
45
+ val options = mapOf(
46
+ "sampleRate" to 44100,
47
+ "channels" to 1,
48
+ "encoding" to "pcm_16bit",
49
+ "keepAwake" to true,
50
+ "autoResumeAfterInterruption" to true,
51
+ "android" to mapOf(
52
+ "audioFocusStrategy" to "background"
53
+ )
54
+ )
55
+
56
+ val result = RecordingConfig.fromMap(options)
57
+ assertTrue("Config creation should succeed", result.isSuccess)
58
+
59
+ val (config, audioFormat) = result.getOrThrow()
60
+
61
+ // Verify audio focus strategy configuration
62
+ assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
63
+ assertTrue("keepAwake should be true for background recording", config.keepAwake)
64
+ assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
65
+
66
+ // Verify audio format is properly configured
67
+ assertNotNull("Audio format should be created", audioFormat)
68
+ assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
69
+ }
70
+
71
+ @Test
72
+ fun testRecordingConfigWithInteractiveStrategy() {
73
+ val options = mapOf(
74
+ "sampleRate" to 44100,
75
+ "channels" to 1,
76
+ "encoding" to "pcm_16bit",
77
+ "keepAwake" to false,
78
+ "autoResumeAfterInterruption" to true,
79
+ "android" to mapOf(
80
+ "audioFocusStrategy" to "interactive"
81
+ )
82
+ )
83
+
84
+ val result = RecordingConfig.fromMap(options)
85
+ assertTrue("Config creation should succeed", result.isSuccess)
86
+
87
+ val (config, audioFormat) = result.getOrThrow()
88
+
89
+ // Verify audio focus strategy configuration
90
+ assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
91
+ assertFalse("keepAwake should be false for interactive recording", config.keepAwake)
92
+ assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
93
+
94
+ // Verify audio format is properly configured
95
+ assertNotNull("Audio format should be created", audioFormat)
96
+ assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
97
+ }
98
+
99
+ @Test
100
+ fun testRecordingConfigWithCommunicationStrategy() {
101
+ val options = mapOf(
102
+ "sampleRate" to 16000, // Common speech sample rate
103
+ "channels" to 1,
104
+ "encoding" to "pcm_16bit",
105
+ "keepAwake" to false,
106
+ "autoResumeAfterInterruption" to true,
107
+ "android" to mapOf(
108
+ "audioFocusStrategy" to "communication"
109
+ )
110
+ )
111
+
112
+ val result = RecordingConfig.fromMap(options)
113
+ assertTrue("Config creation should succeed", result.isSuccess)
114
+
115
+ val (config, audioFormat) = result.getOrThrow()
116
+
117
+ // Verify audio focus strategy configuration
118
+ assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
119
+ assertEquals("Sample rate should be 16000 for speech", 16000, config.sampleRate)
120
+ assertFalse("keepAwake should be false", config.keepAwake)
121
+ assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
122
+
123
+ // Verify audio format is properly configured
124
+ assertNotNull("Audio format should be created", audioFormat)
125
+ assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
126
+ }
127
+
128
+ @Test
129
+ fun testRecordingConfigWithNoneStrategy() {
130
+ val options = mapOf(
131
+ "sampleRate" to 44100,
132
+ "channels" to 1,
133
+ "encoding" to "pcm_16bit",
134
+ "keepAwake" to false,
135
+ "android" to mapOf(
136
+ "audioFocusStrategy" to "none"
137
+ )
138
+ )
139
+
140
+ val result = RecordingConfig.fromMap(options)
141
+ assertTrue("Config creation should succeed", result.isSuccess)
142
+
143
+ val (config, audioFormat) = result.getOrThrow()
144
+
145
+ // Verify audio focus strategy configuration
146
+ assertEquals("Audio focus strategy should be none", "none", config.audioFocusStrategy)
147
+
148
+ // Verify audio format is properly configured
149
+ assertNotNull("Audio format should be created", audioFormat)
150
+ assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
151
+ }
152
+
153
+ @Test
154
+ fun testStrategyOverrideBehavior() {
155
+ val options = mapOf(
156
+ "sampleRate" to 44100,
157
+ "channels" to 1,
158
+ "encoding" to "pcm_16bit",
159
+ "keepAwake" to true, // This would normally default to background
160
+ "android" to mapOf(
161
+ "audioFocusStrategy" to "communication" // But we override to communication
162
+ )
163
+ )
164
+
165
+ val result = RecordingConfig.fromMap(options)
166
+ assertTrue("Config creation should succeed", result.isSuccess)
167
+
168
+ val (config, audioFormat) = result.getOrThrow()
169
+
170
+ // Verify that explicit strategy overrides keepAwake defaults
171
+ assertEquals("Audio focus strategy should be communication (overriding keepAwake default)", "communication", config.audioFocusStrategy)
172
+ assertTrue("keepAwake should still be true", config.keepAwake)
173
+
174
+ // Verify audio format is properly configured
175
+ assertNotNull("Audio format should be created", audioFormat)
176
+ }
177
+
178
+ @Test
179
+ fun testAudioFocusStrategyWithCompression() {
180
+ val options = mapOf(
181
+ "sampleRate" to 44100,
182
+ "channels" to 1,
183
+ "encoding" to "pcm_16bit",
184
+ "android" to mapOf(
185
+ "audioFocusStrategy" to "background"
186
+ ),
187
+ "output" to mapOf(
188
+ "compressed" to mapOf(
189
+ "enabled" to true,
190
+ "format" to "aac",
191
+ "bitrate" to 128000
192
+ )
193
+ )
194
+ )
195
+
196
+ val result = RecordingConfig.fromMap(options)
197
+ assertTrue("Config creation should succeed", result.isSuccess)
198
+
199
+ val (config, audioFormat) = result.getOrThrow()
200
+
201
+ // Verify audio focus strategy works with compression
202
+ assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
203
+ assertTrue("Compressed output should be enabled", config.output.compressed.enabled)
204
+ assertEquals("Compression format should be aac", "aac", config.output.compressed.format)
205
+ assertEquals("Bitrate should be 128000", 128000, config.output.compressed.bitrate)
206
+
207
+ // Verify audio format is properly configured
208
+ assertNotNull("Audio format should be created", audioFormat)
209
+ assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
210
+ }
211
+
212
+ @Test
213
+ fun testAudioFocusStrategyWithNotifications() {
214
+ val options = mapOf(
215
+ "sampleRate" to 44100,
216
+ "channels" to 1,
217
+ "encoding" to "pcm_16bit",
218
+ "showNotification" to true,
219
+ "showWaveformInNotification" to true,
220
+ "android" to mapOf(
221
+ "audioFocusStrategy" to "background"
222
+ ),
223
+ "notification" to mapOf(
224
+ "title" to "Recording Audio",
225
+ "text" to "Background recording in progress",
226
+ "icon" to "ic_mic"
227
+ )
228
+ )
229
+
230
+ val result = RecordingConfig.fromMap(options)
231
+ assertTrue("Config creation should succeed", result.isSuccess)
232
+
233
+ val (config, audioFormat) = result.getOrThrow()
234
+
235
+ // Verify audio focus strategy works with notifications
236
+ assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
237
+ assertTrue("Notifications should be enabled", config.showNotification)
238
+ assertTrue("Waveform in notification should be enabled", config.showWaveformInNotification)
239
+ assertEquals("Notification title should match", "Recording Audio", config.notification.title)
240
+ assertEquals("Notification text should match", "Background recording in progress", config.notification.text)
241
+ assertEquals("Notification icon should match", "ic_mic", config.notification.icon)
242
+
243
+ // Verify audio format is properly configured
244
+ assertNotNull("Audio format should be created", audioFormat)
245
+ assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
246
+ }
247
+
248
+ @Test
249
+ fun testAudioFocusStrategyValidation() {
250
+ // Test all valid strategies
251
+ val strategies = listOf("background", "interactive", "communication", "none")
252
+
253
+ for (strategy in strategies) {
254
+ val options = mapOf(
255
+ "sampleRate" to 44100,
256
+ "channels" to 1,
257
+ "encoding" to "pcm_16bit",
258
+ "android" to mapOf(
259
+ "audioFocusStrategy" to strategy
260
+ )
261
+ )
262
+
263
+ val result = RecordingConfig.fromMap(options)
264
+ assertTrue("Config creation should succeed for strategy: $strategy", result.isSuccess)
265
+
266
+ val (config, _) = result.getOrThrow()
267
+ assertEquals("Audio focus strategy should be $strategy", strategy, config.audioFocusStrategy)
268
+ }
269
+ }
270
+
271
+ @Test
272
+ fun testInvalidAudioFocusStrategyHandling() {
273
+ val options = mapOf(
274
+ "sampleRate" to 44100,
275
+ "channels" to 1,
276
+ "encoding" to "pcm_16bit",
277
+ "android" to mapOf(
278
+ "audioFocusStrategy" to "invalid_strategy"
279
+ )
280
+ )
281
+
282
+ val result = RecordingConfig.fromMap(options)
283
+ assertTrue("Config creation should succeed even with invalid strategy", result.isSuccess)
284
+
285
+ val (config, _) = result.getOrThrow()
286
+ assertEquals("Invalid strategy should be preserved", "invalid_strategy", config.audioFocusStrategy)
287
+ }
288
+
289
+ @Test
290
+ fun testCompleteAudioFocusConfiguration() {
291
+ val options = mapOf(
292
+ "sampleRate" to 44100,
293
+ "channels" to 1,
294
+ "encoding" to "pcm_16bit",
295
+ "keepAwake" to true,
296
+ "autoResumeAfterInterruption" to true,
297
+ "showNotification" to true,
298
+ "showWaveformInNotification" to false,
299
+ "enableProcessing" to false,
300
+ "android" to mapOf(
301
+ "audioFocusStrategy" to "communication"
302
+ ),
303
+ "notification" to mapOf(
304
+ "title" to "Voice Call Recording",
305
+ "text" to "Call in progress",
306
+ "icon" to "ic_call"
307
+ )
308
+ )
309
+
310
+ val result = RecordingConfig.fromMap(options)
311
+ assertTrue("Config creation should succeed", result.isSuccess)
312
+
313
+ val (config, audioFormat) = result.getOrThrow()
314
+
315
+ // Verify complete configuration
316
+ assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
317
+ assertEquals("Sample rate should be 44100", 44100, config.sampleRate)
318
+ assertEquals("Channels should be 1", 1, config.channels)
319
+ assertEquals("Encoding should be pcm_16bit", "pcm_16bit", config.encoding)
320
+ assertTrue("keepAwake should be true", config.keepAwake)
321
+ assertFalse("showWaveformInNotification should be false", config.showWaveformInNotification)
322
+ assertTrue("showNotification should be true", config.showNotification)
323
+ assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
324
+ assertFalse("enableProcessing should be false", config.enableProcessing)
325
+ assertEquals("Notification title should match", "Voice Call Recording", config.notification.title)
326
+ assertEquals("Notification text should match", "Call in progress", config.notification.text)
327
+
328
+ // Verify audio format is properly configured
329
+ assertNotNull("Audio format should be created", audioFormat)
330
+ assertEquals("MIME type should be audio/wav", "audio/wav", audioFormat.mimeType)
331
+ }
332
+ }
@@ -0,0 +1,218 @@
1
+ package net.siteed.audiostream.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.audiostream.OutputConfig
7
+ import net.siteed.audiostream.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
+ // ExpoAudioStreamModule 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
+ }