@siteed/expo-audio-studio 2.9.0 → 2.10.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 +9 -1
- package/android/build.gradle +9 -0
- 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/audiostream/AudioProcessorInstrumentedTest.kt +197 -0
- package/android/src/androidTest/java/net/siteed/audiostream/AudioRecorderInstrumentedTest.kt +541 -0
- package/android/src/androidTest/java/net/siteed/audiostream/integration/BufferDurationIntegrationTest.kt +324 -0
- package/android/src/androidTest/java/net/siteed/audiostream/integration/OutputControlIntegrationTest.kt +340 -0
- package/android/src/androidTest/java/net/siteed/audiostream/integration/README.md +95 -0
- package/android/src/androidTest/java/net/siteed/audiostream/integration/run_integration_tests.sh +28 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +264 -13
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +3 -13
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +118 -55
- package/android/src/main/java/net/siteed/audiostream/LogUtils.kt +32 -4
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +50 -15
- package/android/src/test/java/net/siteed/audiostream/AudioFileHandlerTest.kt +279 -0
- package/android/src/test/java/net/siteed/audiostream/AudioFormatUtilsTest.kt +273 -0
- package/android/src/test/resources/chorus.wav +0 -0
- package/android/src/test/resources/generate_test_audio.py +94 -0
- 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/build/cjs/ExpoAudioStream.types.js.map +1 -1
- package/build/cjs/ExpoAudioStream.web.js +37 -34
- package/build/cjs/ExpoAudioStream.web.js.map +1 -1
- package/build/cjs/WebRecorder.web.js +12 -10
- package/build/cjs/WebRecorder.web.js.map +1 -1
- package/build/esm/ExpoAudioStream.types.js.map +1 -1
- package/build/esm/ExpoAudioStream.web.js +37 -34
- package/build/esm/ExpoAudioStream.web.js.map +1 -1
- package/build/esm/WebRecorder.web.js +12 -10
- package/build/esm/WebRecorder.web.js.map +1 -1
- package/build/types/ExpoAudioStream.types.d.ts +54 -22
- package/build/types/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/types/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/types/WebRecorder.web.d.ts.map +1 -1
- package/ios/AudioNotificationManager.swift +2 -6
- package/ios/AudioStreamManager.swift +116 -50
- package/ios/ExpoAudioStream.podspec +6 -0
- package/ios/ExpoAudioStreamModule.swift +11 -8
- package/ios/ExpoAudioStudioTests/AudioFileHandlerTests.swift +338 -0
- package/ios/ExpoAudioStudioTests/AudioFormatUtilsTests.swift +331 -0
- package/ios/ExpoAudioStudioTests/AudioTestHelpers.swift +130 -0
- package/ios/ExpoAudioStudioTests/Info.plist +22 -0
- package/ios/ExpoAudioStudioTests/SimpleAudioTest.swift +98 -0
- package/ios/ExpoAudioStudioTests/TestAudioGenerator.swift +75 -0
- package/ios/RecordingSettings.swift +53 -22
- package/ios/tests/integration/buffer_duration_test.swift +185 -0
- package/ios/tests/integration/output_control_test.swift +322 -0
- package/ios/tests/integration/run_integration_tests.sh +27 -0
- package/ios/tests/standalone/audio_processing_test.swift +144 -0
- package/ios/tests/standalone/audio_recording_test.swift +277 -0
- package/ios/tests/standalone/audio_streaming_test.swift +249 -0
- package/ios/tests/standalone/standalone_test.swift +144 -0
- package/package.json +140 -133
- package/src/ExpoAudioStream.types.ts +66 -22
- package/src/ExpoAudioStream.web.ts +43 -38
- package/src/WebRecorder.web.ts +13 -10
- package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
- package/ios/siteedexpoaudiostudio.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/siteedexpoaudiostudio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- /package/plugin/build/{index.d.ts → index.d.cts} +0 -0
|
@@ -285,13 +285,15 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
285
285
|
bitDepth: this.bitDepth,
|
|
286
286
|
channels: recordingConfig.channels ?? 1,
|
|
287
287
|
sampleRate: recordingConfig.sampleRate ?? 44100,
|
|
288
|
-
compression: recordingConfig.
|
|
288
|
+
compression: recordingConfig.output?.compressed?.enabled
|
|
289
289
|
? {
|
|
290
|
-
...recordingConfig.
|
|
291
|
-
bitrate:
|
|
290
|
+
...recordingConfig.output.compressed,
|
|
291
|
+
bitrate:
|
|
292
|
+
recordingConfig.output.compressed.bitrate ?? 128000,
|
|
292
293
|
size: 0,
|
|
293
294
|
mimeType: 'audio/webm',
|
|
294
|
-
format:
|
|
295
|
+
format:
|
|
296
|
+
recordingConfig.output.compressed.format ?? 'opus',
|
|
295
297
|
compressedFileUri: '',
|
|
296
298
|
}
|
|
297
299
|
: undefined,
|
|
@@ -459,56 +461,56 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
459
461
|
let fileUri = `${this.streamUuid}.${this.extension}`
|
|
460
462
|
let mimeType = `audio/${this.extension}`
|
|
461
463
|
|
|
462
|
-
// Handle both compressed and uncompressed blobs according to configuration
|
|
463
|
-
const
|
|
464
|
-
this.recordingConfig?.
|
|
464
|
+
// Handle both compressed and uncompressed blobs according to new output configuration
|
|
465
|
+
const primaryEnabled =
|
|
466
|
+
this.recordingConfig?.output?.primary?.enabled ?? true
|
|
467
|
+
const compressedEnabled =
|
|
468
|
+
this.recordingConfig?.output?.compressed?.enabled ?? false
|
|
465
469
|
|
|
466
|
-
// Process compressed blob if available
|
|
467
|
-
if (compressedBlob) {
|
|
470
|
+
// Process compressed blob if available and enabled
|
|
471
|
+
if (compressedBlob && compressedEnabled) {
|
|
468
472
|
const compressedUri = URL.createObjectURL(compressedBlob)
|
|
469
473
|
const compressedInfo = {
|
|
470
474
|
compressedFileUri: compressedUri,
|
|
471
475
|
size: compressedBlob.size,
|
|
472
476
|
mimeType: 'audio/webm',
|
|
473
|
-
format:
|
|
477
|
+
format:
|
|
478
|
+
this.recordingConfig?.output?.compressed?.format ??
|
|
479
|
+
'opus',
|
|
474
480
|
bitrate:
|
|
475
|
-
this.recordingConfig?.
|
|
481
|
+
this.recordingConfig?.output?.compressed?.bitrate ??
|
|
482
|
+
128000,
|
|
476
483
|
}
|
|
477
484
|
|
|
478
|
-
//
|
|
479
|
-
|
|
485
|
+
// Store compression info
|
|
486
|
+
compression = compressedInfo
|
|
487
|
+
|
|
488
|
+
// If primary is disabled, use compressed as main file
|
|
489
|
+
if (!primaryEnabled) {
|
|
480
490
|
this.logger?.debug(
|
|
481
|
-
'Using compressed audio as primary output'
|
|
491
|
+
'Using compressed audio as primary output (primary disabled)'
|
|
482
492
|
)
|
|
483
493
|
fileUri = compressedUri
|
|
484
494
|
mimeType = 'audio/webm'
|
|
485
|
-
|
|
486
|
-
// Store compression info
|
|
487
|
-
compression = compressedInfo
|
|
488
|
-
} else {
|
|
489
|
-
// Compression was enabled during recording but not set as primary
|
|
490
|
-
// Store as alternate format
|
|
491
|
-
compression = compressedInfo
|
|
492
495
|
}
|
|
493
496
|
}
|
|
494
497
|
|
|
495
|
-
// Process uncompressed WAV if available
|
|
496
|
-
if (uncompressedBlob) {
|
|
498
|
+
// Process uncompressed WAV if available and primary is enabled
|
|
499
|
+
if (uncompressedBlob && primaryEnabled) {
|
|
497
500
|
const wavUri = URL.createObjectURL(uncompressedBlob)
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
fileUri = wavUri
|
|
506
|
-
mimeType = 'audio/wav'
|
|
507
|
-
}
|
|
501
|
+
fileUri = wavUri
|
|
502
|
+
mimeType = 'audio/wav'
|
|
503
|
+
} else if (!primaryEnabled && !compressedEnabled) {
|
|
504
|
+
// No outputs enabled - streaming only mode
|
|
505
|
+
this.logger?.debug('No outputs enabled - streaming only mode')
|
|
506
|
+
fileUri = ''
|
|
507
|
+
mimeType = 'audio/wav'
|
|
508
508
|
}
|
|
509
509
|
|
|
510
510
|
// Use the stored streamUuid for the final filename
|
|
511
|
-
const filename =
|
|
511
|
+
const filename = fileUri
|
|
512
|
+
? `${this.streamUuid}.${this.extension}`
|
|
513
|
+
: 'stream-only'
|
|
512
514
|
const result: AudioRecording = {
|
|
513
515
|
fileUri,
|
|
514
516
|
filename,
|
|
@@ -517,7 +519,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
517
519
|
channels: this.recordingConfig?.channels ?? 1,
|
|
518
520
|
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
519
521
|
durationMs: this.currentDurationMs,
|
|
520
|
-
size: this.currentSize,
|
|
522
|
+
size: primaryEnabled ? this.currentSize : 0,
|
|
521
523
|
mimeType,
|
|
522
524
|
compression,
|
|
523
525
|
}
|
|
@@ -630,13 +632,16 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
630
632
|
interval: this.currentInterval,
|
|
631
633
|
intervalAnalysis: this.currentIntervalAnalysis,
|
|
632
634
|
mimeType: `audio/${this.extension}`,
|
|
633
|
-
compression: this.recordingConfig?.
|
|
635
|
+
compression: this.recordingConfig?.output?.compressed?.enabled
|
|
634
636
|
? {
|
|
635
637
|
size: this.totalCompressedSize,
|
|
636
638
|
mimeType: 'audio/webm',
|
|
637
|
-
format:
|
|
639
|
+
format:
|
|
640
|
+
this.recordingConfig.output.compressed.format ??
|
|
641
|
+
'opus',
|
|
638
642
|
bitrate:
|
|
639
|
-
this.recordingConfig.
|
|
643
|
+
this.recordingConfig.output.compressed.bitrate ??
|
|
644
|
+
128000,
|
|
640
645
|
compressedFileUri: `${this.streamUuid}.webm`,
|
|
641
646
|
}
|
|
642
647
|
: undefined,
|
package/src/WebRecorder.web.ts
CHANGED
|
@@ -154,7 +154,7 @@ export class WebRecorder {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// Initialize compressed recording if enabled
|
|
157
|
-
if (recordingConfig.
|
|
157
|
+
if (recordingConfig.output?.compressed?.enabled) {
|
|
158
158
|
this.initializeCompressedRecorder()
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -234,9 +234,9 @@ export class WebRecorder {
|
|
|
234
234
|
)
|
|
235
235
|
const samples = chunk.length // Number of samples in this chunk
|
|
236
236
|
|
|
237
|
-
// Only store PCM data if
|
|
237
|
+
// Only store PCM data if primary output is enabled
|
|
238
238
|
const shouldStoreUncompressed =
|
|
239
|
-
this.config.
|
|
239
|
+
this.config.output?.primary?.enabled ?? true
|
|
240
240
|
|
|
241
241
|
// Store PCM chunks when needed - this is for the final WAV file
|
|
242
242
|
if (shouldStoreUncompressed) {
|
|
@@ -275,9 +275,12 @@ export class WebRecorder {
|
|
|
275
275
|
size: this.pendingCompressedChunk.size,
|
|
276
276
|
totalSize: this.compressedSize,
|
|
277
277
|
mimeType: 'audio/webm',
|
|
278
|
-
format:
|
|
278
|
+
format:
|
|
279
|
+
this.config.output?.compressed?.format ??
|
|
280
|
+
'opus',
|
|
279
281
|
bitrate:
|
|
280
|
-
this.config.
|
|
282
|
+
this.config.output?.compressed?.bitrate ??
|
|
283
|
+
128000,
|
|
281
284
|
}
|
|
282
285
|
: undefined
|
|
283
286
|
|
|
@@ -312,11 +315,11 @@ export class WebRecorder {
|
|
|
312
315
|
interval,
|
|
313
316
|
position: this.position,
|
|
314
317
|
deviceId: this.config.deviceId ?? 'default',
|
|
315
|
-
compression: this.config.
|
|
318
|
+
compression: this.config.output?.compressed
|
|
316
319
|
? {
|
|
317
|
-
enabled: this.config.
|
|
318
|
-
format: this.config.
|
|
319
|
-
bitrate: this.config.
|
|
320
|
+
enabled: this.config.output.compressed.enabled,
|
|
321
|
+
format: this.config.output.compressed.format,
|
|
322
|
+
bitrate: this.config.output.compressed.bitrate,
|
|
320
323
|
}
|
|
321
324
|
: 'disabled',
|
|
322
325
|
})
|
|
@@ -843,7 +846,7 @@ export class WebRecorder {
|
|
|
843
846
|
{
|
|
844
847
|
mimeType,
|
|
845
848
|
audioBitsPerSecond:
|
|
846
|
-
this.config.
|
|
849
|
+
this.config.output?.compressed?.bitrate ?? 128000,
|
|
847
850
|
}
|
|
848
851
|
)
|
|
849
852
|
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
package net.siteed.audiostream
|
|
2
|
-
|
|
3
|
-
import org.junit.Test
|
|
4
|
-
import org.junit.Assert.*
|
|
5
|
-
|
|
6
|
-
class AudioProcessorTest {
|
|
7
|
-
|
|
8
|
-
private val sampleRate = 44100
|
|
9
|
-
private val channels = 1
|
|
10
|
-
private val encoding = "pcm_16bit"
|
|
11
|
-
private val pointsPerSecond = 1000
|
|
12
|
-
private val algorithm = "rms"
|
|
13
|
-
private val features = mapOf("rms" to true, "zcr" to true)
|
|
14
|
-
|
|
15
|
-
private val recordingConfig = RecordingConfig(
|
|
16
|
-
sampleRate = sampleRate,
|
|
17
|
-
channels = channels,
|
|
18
|
-
encoding = encoding,
|
|
19
|
-
interval = 1000,
|
|
20
|
-
enableProcessing = true,
|
|
21
|
-
pointsPerSecond = pointsPerSecond,
|
|
22
|
-
algorithm = algorithm,
|
|
23
|
-
features = features
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
private val audioProcessor = AudioProcessor()
|
|
27
|
-
|
|
28
|
-
@Test
|
|
29
|
-
fun testProcessAudioData() {
|
|
30
|
-
val data = generateSineWave(440.0, sampleRate, 2.0)
|
|
31
|
-
|
|
32
|
-
val result = audioProcessor.processAudioData(data, recordingConfig)
|
|
33
|
-
|
|
34
|
-
assertNotNull(result)
|
|
35
|
-
assertEquals(pointsPerSecond, result.pointsPerSecond)
|
|
36
|
-
assertEquals((data.size / sampleRate) * 1000, result.durationMs.toInt())
|
|
37
|
-
assertEquals(16, result.bitDepth)
|
|
38
|
-
assertEquals(channels, result.numberOfChannels)
|
|
39
|
-
assertEquals(sampleRate, result.sampleRate.toInt())
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Helper function to generate a sine wave
|
|
43
|
-
private fun generateSineWave(frequency: Double, sampleRate: Int, durationSeconds: Double): ByteArray {
|
|
44
|
-
val numSamples = (sampleRate * durationSeconds).toInt()
|
|
45
|
-
val output = ByteArray(numSamples * 2) // 16-bit PCM
|
|
46
|
-
|
|
47
|
-
for (i in 0 until numSamples) {
|
|
48
|
-
val time = i / sampleRate.toDouble()
|
|
49
|
-
val amplitude = (Math.sin(2.0 * Math.PI * frequency * time) * 32767).toInt()
|
|
50
|
-
output[i * 2] = (amplitude and 0xff).toByte()
|
|
51
|
-
output[i * 2 + 1] = ((amplitude shr 8) and 0xff).toByte()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return output
|
|
55
|
-
}
|
|
56
|
-
}
|
|
File without changes
|