@siteed/expo-audio-studio 2.9.0 → 2.10.1

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 (67) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/android/build.gradle +9 -0
  3. package/android/src/androidTest/assets/chorus.wav +0 -0
  4. package/android/src/androidTest/assets/jfk.wav +0 -0
  5. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  6. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  7. package/android/src/androidTest/java/net/siteed/audiostream/AudioProcessorInstrumentedTest.kt +197 -0
  8. package/android/src/androidTest/java/net/siteed/audiostream/AudioRecorderInstrumentedTest.kt +541 -0
  9. package/android/src/androidTest/java/net/siteed/audiostream/integration/BufferDurationIntegrationTest.kt +324 -0
  10. package/android/src/androidTest/java/net/siteed/audiostream/integration/OutputControlIntegrationTest.kt +340 -0
  11. package/android/src/androidTest/java/net/siteed/audiostream/integration/README.md +95 -0
  12. package/android/src/androidTest/java/net/siteed/audiostream/integration/run_integration_tests.sh +28 -0
  13. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +264 -13
  14. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +3 -13
  15. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +118 -55
  16. package/android/src/main/java/net/siteed/audiostream/LogUtils.kt +32 -4
  17. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +50 -15
  18. package/android/src/test/java/net/siteed/audiostream/AudioFileHandlerTest.kt +279 -0
  19. package/android/src/test/java/net/siteed/audiostream/AudioFormatUtilsTest.kt +273 -0
  20. package/android/src/test/resources/chorus.wav +0 -0
  21. package/android/src/test/resources/generate_test_audio.py +94 -0
  22. package/android/src/test/resources/jfk.wav +0 -0
  23. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  24. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  25. package/build/cjs/ExpoAudioStream.types.js.map +1 -1
  26. package/build/cjs/ExpoAudioStream.web.js +37 -34
  27. package/build/cjs/ExpoAudioStream.web.js.map +1 -1
  28. package/build/cjs/WebRecorder.web.js +12 -10
  29. package/build/cjs/WebRecorder.web.js.map +1 -1
  30. package/build/cjs/useAudioRecorder.js.map +1 -1
  31. package/build/esm/ExpoAudioStream.types.js.map +1 -1
  32. package/build/esm/ExpoAudioStream.web.js +37 -34
  33. package/build/esm/ExpoAudioStream.web.js.map +1 -1
  34. package/build/esm/WebRecorder.web.js +12 -10
  35. package/build/esm/WebRecorder.web.js.map +1 -1
  36. package/build/esm/useAudioRecorder.js.map +1 -1
  37. package/build/types/ExpoAudioStream.types.d.ts +54 -22
  38. package/build/types/ExpoAudioStream.types.d.ts.map +1 -1
  39. package/build/types/ExpoAudioStream.web.d.ts.map +1 -1
  40. package/build/types/WebRecorder.web.d.ts.map +1 -1
  41. package/ios/AudioNotificationManager.swift +2 -6
  42. package/ios/AudioStreamManager.swift +116 -50
  43. package/ios/ExpoAudioStream.podspec +6 -0
  44. package/ios/ExpoAudioStreamModule.swift +11 -8
  45. package/ios/ExpoAudioStudioTests/AudioFileHandlerTests.swift +338 -0
  46. package/ios/ExpoAudioStudioTests/AudioFormatUtilsTests.swift +331 -0
  47. package/ios/ExpoAudioStudioTests/AudioTestHelpers.swift +130 -0
  48. package/ios/ExpoAudioStudioTests/Info.plist +22 -0
  49. package/ios/ExpoAudioStudioTests/SimpleAudioTest.swift +98 -0
  50. package/ios/ExpoAudioStudioTests/TestAudioGenerator.swift +75 -0
  51. package/ios/RecordingSettings.swift +53 -22
  52. package/ios/tests/integration/buffer_duration_test.swift +185 -0
  53. package/ios/tests/integration/output_control_test.swift +322 -0
  54. package/ios/tests/integration/run_integration_tests.sh +27 -0
  55. package/ios/tests/standalone/audio_processing_test.swift +144 -0
  56. package/ios/tests/standalone/audio_recording_test.swift +277 -0
  57. package/ios/tests/standalone/audio_streaming_test.swift +249 -0
  58. package/ios/tests/standalone/standalone_test.swift +144 -0
  59. package/package.json +140 -133
  60. package/src/ExpoAudioStream.types.ts +66 -22
  61. package/src/ExpoAudioStream.web.ts +43 -38
  62. package/src/WebRecorder.web.ts +13 -10
  63. package/src/useAudioRecorder.tsx +1 -1
  64. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
  65. package/ios/siteedexpoaudiostudio.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  66. package/ios/siteedexpoaudiostudio.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  67. /package/plugin/build/{index.d.ts → index.d.cts} +0 -0
package/package.json CHANGED
@@ -1,134 +1,141 @@
1
1
  {
2
- "name": "@siteed/expo-audio-studio",
3
- "version": "2.9.0",
4
- "description": "Comprehensive audio processing library for React Native and Expo with recording, analysis, visualization, and streaming capabilities across iOS, Android, and web",
5
- "license": "MIT",
6
- "type": "commonjs",
7
- "main": "./build/cjs/index.js",
8
- "module": "./build/esm/index.js",
9
- "types": "./build/types/index.d.ts",
10
- "expo": {
11
- "plugin": "./app.plugin.js"
12
- },
13
- "author": "Arthur Breton <abreton@siteed.net> (https://github.com/deeeed)",
14
- "homepage": "https://github.com/deeeed/expo-audio-stream/blob/main/packages/expo-audio-studio/README.md",
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/deeeed/expo-audio-stream.git",
18
- "directory": "packages/expo-audio-studio"
19
- },
20
- "bugs": {
21
- "url": "https://github.com/deeeed/expo-audio-stream/issues"
22
- },
23
- "keywords": [
24
- "react-native",
25
- "expo",
26
- "audio",
27
- "recording",
28
- "audio-analysis",
29
- "audio-processing",
30
- "audio-visualization",
31
- "waveform",
32
- "spectrogram",
33
- "mel-spectrogram",
34
- "mfcc",
35
- "audio-features",
36
- "audio-compression",
37
- "opus",
38
- "aac",
39
- "pcm",
40
- "wav",
41
- "cross-platform",
42
- "background-recording",
43
- "audio-trimming",
44
- "dual-stream"
45
- ],
46
- "files": [
47
- "src",
48
- "android",
49
- "ios",
50
- "cpp",
51
- "plugin",
52
- "app.plugin.js",
53
- "LICENSE",
54
- "CHANGELOG.md",
55
- "generated",
56
- "expo-module.config.json",
57
- "README.md",
58
- "package.json",
59
- "*.podspec",
60
- "build",
61
- "!ios/build",
62
- "!android/build",
63
- "!android/gradle",
64
- "!android/gradlew",
65
- "!android/gradlew.bat",
66
- "!android/local.properties",
67
- "!**/__tests__",
68
- "!**/__fixtures__",
69
- "!**/__mocks__",
70
- "!**/.*"
71
- ],
72
- "scripts": {
73
- "build": "rimraf build && yarn build:types && yarn build:cjs && yarn build:esm && yarn build:plugin",
74
- "build:cjs": "tsc -p tsconfig.cjs.json",
75
- "build:esm": "tsc -p tsconfig.esm.json",
76
- "build:types": "tsc -p tsconfig.types.json",
77
- "build:plugin": "tsc --build plugin/tsconfig.json",
78
- "build:plugin:dev": "expo-module build plugin",
79
- "build:dev": "expo-module build",
80
- "clean": "expo-module clean && rimraf build plugin/build",
81
- "lint": "expo-module lint",
82
- "test": "expo-module test",
83
- "typecheck": "tsc --noEmit",
84
- "docgen": "typedoc src/index.ts --plugin typedoc-plugin-markdown --readme none --out ../../documentation_site/docs/api-reference/API",
85
- "prepare": "yarn build && node -e \"require('fs').renameSync('./plugin/build/index.d.ts', './plugin/build/index.d.cts')\"",
86
- "prepublishOnly": "expo-module prepublishOnly",
87
- "expo-module": "expo-module",
88
- "open:ios": "open -a \"Xcode\" ../../apps/playground/ios",
89
- "open:android": "open -a \"Android Studio\" ../../apps/playground/android",
90
- "size": "bundle-size && size-limit",
91
- "release": "./publish.sh"
92
- },
93
- "devDependencies": {
94
- "@expo/config-plugins": "~10.0.0",
95
- "@siteed/publisher": "^0.4.18",
96
- "@size-limit/preset-big-lib": "^11.1.4",
97
- "@types/jest": "^29.5.12",
98
- "@types/node": "^20.12.7",
99
- "@types/react": "~19.0.10",
100
- "@typescript-eslint/eslint-plugin": "^7.7.0",
101
- "@typescript-eslint/parser": "^7.7.0",
102
- "bundle-size": "^1.1.5",
103
- "eslint": "^8.56.0",
104
- "eslint-config-prettier": "^9.1.0",
105
- "eslint-config-universe": "^12.0.0",
106
- "eslint-plugin-import": "^2.29.1",
107
- "eslint-plugin-prettier": "^5.1.3",
108
- "eslint-plugin-promise": "^6.1.1",
109
- "eslint-plugin-react": "^7.34.1",
110
- "expo": "^53.0.9",
111
- "expo-module-scripts": "^4.0.2",
112
- "jest": "^29.7.0",
113
- "prettier": "^3.2.5",
114
- "react-native": "0.79.2",
115
- "rimraf": "^6.0.1",
116
- "size-limit": "^11.1.4",
117
- "ts-node": "^10.9.2",
118
- "typedoc": "^0.27.4",
119
- "typedoc-plugin-markdown": "~4.4.2",
120
- "typescript": "~5.8.3"
121
- },
122
- "peerDependencies": {
123
- "expo": "*",
124
- "react": "*",
125
- "react-native": "*"
126
- },
127
- "publishConfig": {
128
- "access": "public",
129
- "registry": "https://registry.npmjs.org"
130
- },
131
- "dependencies": {
132
- "expo-modules-core": "~2.3.13"
133
- }
134
- }
2
+ "name": "@siteed/expo-audio-studio",
3
+ "version": "2.10.1",
4
+ "description": "Comprehensive audio processing library for React Native and Expo with recording, analysis, visualization, and streaming capabilities across iOS, Android, and web",
5
+ "license": "MIT",
6
+ "type": "commonjs",
7
+ "main": "./build/cjs/index.js",
8
+ "module": "./build/esm/index.js",
9
+ "types": "./build/types/index.d.ts",
10
+ "expo": {
11
+ "plugin": "./app.plugin.js"
12
+ },
13
+ "author": "Arthur Breton <abreton@siteed.net> (https://github.com/deeeed)",
14
+ "homepage": "https://github.com/deeeed/expo-audio-stream/blob/main/packages/expo-audio-studio/README.md",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/deeeed/expo-audio-stream.git",
18
+ "directory": "packages/expo-audio-studio"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/deeeed/expo-audio-stream/issues"
22
+ },
23
+ "keywords": [
24
+ "react-native",
25
+ "expo",
26
+ "audio",
27
+ "recording",
28
+ "audio-analysis",
29
+ "audio-processing",
30
+ "audio-visualization",
31
+ "waveform",
32
+ "spectrogram",
33
+ "mel-spectrogram",
34
+ "mfcc",
35
+ "audio-features",
36
+ "audio-compression",
37
+ "opus",
38
+ "aac",
39
+ "pcm",
40
+ "wav",
41
+ "cross-platform",
42
+ "background-recording",
43
+ "audio-trimming",
44
+ "dual-stream"
45
+ ],
46
+ "files": [
47
+ "src",
48
+ "android",
49
+ "ios",
50
+ "cpp",
51
+ "plugin",
52
+ "app.plugin.js",
53
+ "LICENSE",
54
+ "CHANGELOG.md",
55
+ "generated",
56
+ "expo-module.config.json",
57
+ "README.md",
58
+ "package.json",
59
+ "*.podspec",
60
+ "build",
61
+ "!ios/build",
62
+ "!android/build",
63
+ "!android/gradle",
64
+ "!android/gradlew",
65
+ "!android/gradlew.bat",
66
+ "!android/local.properties",
67
+ "!**/__tests__",
68
+ "!**/__fixtures__",
69
+ "!**/__mocks__",
70
+ "!**/.*"
71
+ ],
72
+ "scripts": {
73
+ "build": "rimraf build && yarn build:types && yarn build:cjs && yarn build:esm && yarn build:plugin",
74
+ "build:cjs": "tsc -p tsconfig.cjs.json",
75
+ "build:esm": "tsc -p tsconfig.esm.json",
76
+ "build:types": "tsc -p tsconfig.types.json",
77
+ "build:plugin": "tsc --build plugin/tsconfig.json",
78
+ "build:plugin:dev": "expo-module build plugin",
79
+ "build:dev": "expo-module build",
80
+ "clean": "expo-module clean && rimraf build plugin/build",
81
+ "lint": "expo-module lint",
82
+ "test": "expo-module test",
83
+ "test:android": "yarn test:android:unit && yarn test:android:instrumented",
84
+ "test:android:unit": "cd ../../apps/playground/android && ./gradlew :siteed-expo-audio-studio:test",
85
+ "test:android:instrumented": "cd ../../apps/playground/android && ./gradlew :siteed-expo-audio-studio:connectedAndroidTest",
86
+ "test:android:unit:watch": "cd ../../apps/playground/android && ./gradlew :siteed-expo-audio-studio:test --continuous",
87
+ "test:ios": "cd ios && xcodebuild test -workspace ExpoAudioStudio.xcworkspace -scheme ExpoAudioStudio -destination 'platform=iOS Simulator,name=iPhone 14'",
88
+ "test:coverage": "cd ../../apps/playground/android && ./gradlew :siteed-expo-audio-studio:jacocoTestReport",
89
+ "typecheck": "tsc --noEmit",
90
+ "docgen": "typedoc src/index.ts --plugin typedoc-plugin-markdown --readme none --out ../../documentation_site/docs/api-reference/API",
91
+ "prepare": "yarn build && node -e \"require('fs').renameSync('./plugin/build/index.d.ts', './plugin/build/index.d.cts')\"",
92
+ "prepublishOnly.disabled": "expo-module prepublishOnly",
93
+ "expo-module": "expo-module",
94
+ "open:ios": "open -a \"Xcode\" ../../apps/playground/ios",
95
+ "open:android": "open -a \"Android Studio\" ../../apps/playground/android",
96
+ "size": "bundle-size && size-limit",
97
+ "release": "./publish.sh"
98
+ },
99
+ "devDependencies": {
100
+ "@expo/config-plugins": "~10.0.0",
101
+ "@expo/npm-proofread": "^1.0.1",
102
+ "@siteed/publisher": "^0.4.18",
103
+ "@size-limit/preset-big-lib": "^11.1.4",
104
+ "@types/jest": "^29.5.12",
105
+ "@types/node": "^20.12.7",
106
+ "@types/react": "~19.0.10",
107
+ "@typescript-eslint/eslint-plugin": "^7.7.0",
108
+ "@typescript-eslint/parser": "^7.7.0",
109
+ "bundle-size": "^1.1.5",
110
+ "eslint": "^8.56.0",
111
+ "eslint-config-prettier": "^9.1.0",
112
+ "eslint-config-universe": "^12.0.0",
113
+ "eslint-plugin-import": "^2.29.1",
114
+ "eslint-plugin-prettier": "^5.1.3",
115
+ "eslint-plugin-promise": "^6.1.1",
116
+ "eslint-plugin-react": "^7.34.1",
117
+ "expo": "^53.0.9",
118
+ "expo-module-scripts": "^4.1.7",
119
+ "jest": "^29.7.0",
120
+ "prettier": "^3.2.5",
121
+ "react-native": "0.79.2",
122
+ "rimraf": "^6.0.1",
123
+ "size-limit": "^11.1.4",
124
+ "ts-node": "^10.9.2",
125
+ "typedoc": "^0.27.4",
126
+ "typedoc-plugin-markdown": "~4.4.2",
127
+ "typescript": "~5.8.3"
128
+ },
129
+ "peerDependencies": {
130
+ "expo": "*",
131
+ "react": "*",
132
+ "react-native": "*"
133
+ },
134
+ "publishConfig": {
135
+ "access": "public",
136
+ "registry": "https://registry.npmjs.org"
137
+ },
138
+ "dependencies": {
139
+ "expo-modules-core": "~2.3.13"
140
+ }
141
+ }
@@ -209,15 +209,7 @@ export interface IOSConfig {
209
209
 
210
210
  /** Web platform specific configuration options */
211
211
  export interface WebConfig {
212
- /**
213
- * Whether to store uncompressed audio data for WAV generation
214
- *
215
- * When true, all PCM chunks are stored in memory to create a WAV file when compression is disabled
216
- * When false, uncompressed audio won't be available, but memory usage will be lower
217
- *
218
- * Default: true (for backward compatibility)
219
- */
220
- storeUncompressedAudio?: boolean
212
+ // Reserved for future web-specific options
221
213
  }
222
214
 
223
215
  // Add new type for interruption reasons
@@ -291,6 +283,45 @@ export const DeviceDisconnectionBehavior = {
291
283
  export type DeviceDisconnectionBehaviorType =
292
284
  (typeof DeviceDisconnectionBehavior)[keyof typeof DeviceDisconnectionBehavior]
293
285
 
286
+ /**
287
+ * Configuration for audio output files during recording
288
+ */
289
+ export interface OutputConfig {
290
+ /**
291
+ * Configuration for the primary (uncompressed) output file
292
+ */
293
+ primary?: {
294
+ /** Whether to create the primary output file (default: true) */
295
+ enabled?: boolean
296
+ /** Format for the primary output (currently only 'wav' is supported) */
297
+ format?: 'wav'
298
+ }
299
+
300
+ /**
301
+ * Configuration for the compressed output file
302
+ */
303
+ compressed?: {
304
+ /** Whether to create a compressed output file (default: false) */
305
+ enabled?: boolean
306
+ /**
307
+ * Format for compression
308
+ * - 'aac': Advanced Audio Coding - supported on all platforms
309
+ * - 'opus': Opus encoding - supported on Android and Web; on iOS will automatically fall back to AAC
310
+ */
311
+ format?: 'aac' | 'opus'
312
+ /** Bitrate for compression in bits per second (default: 128000) */
313
+ bitrate?: number
314
+ }
315
+
316
+ // Future enhancement: Post-processing pipeline
317
+ // postProcessing?: {
318
+ // normalize?: boolean
319
+ // trimSilence?: boolean
320
+ // noiseReduction?: boolean
321
+ // customProcessors?: AudioProcessor[]
322
+ // }
323
+ }
324
+
294
325
  export interface RecordingConfig {
295
326
  /** Sample rate for recording in Hz (16000, 44100, or 48000) */
296
327
  sampleRate?: SampleRate
@@ -340,19 +371,16 @@ export interface RecordingConfig {
340
371
  /** Callback function to handle audio features extraction results */
341
372
  onAudioAnalysis?: (_: AudioAnalysisEvent) => Promise<void>
342
373
 
343
- /** Configuration for audio compression */
344
- compression?: {
345
- /** Enable audio compression */
346
- enabled: boolean
347
- /**
348
- * Format for compression
349
- * - 'aac': Advanced Audio Coding - supported on all platforms
350
- * - 'opus': Opus encoding - supported on Android and Web; on iOS will automatically fall back to AAC
351
- */
352
- format: 'aac' | 'opus'
353
- /** Bitrate for compression in bits per second */
354
- bitrate?: number
355
- }
374
+ /**
375
+ * Configuration for audio output files
376
+ *
377
+ * Examples:
378
+ * - Primary only (default): `{ primary: { enabled: true } }`
379
+ * - Compressed only: `{ primary: { enabled: false }, compressed: { enabled: true, format: 'aac' } }`
380
+ * - Both outputs: `{ compressed: { enabled: true } }`
381
+ * - Streaming only: `{ primary: { enabled: false } }`
382
+ */
383
+ output?: OutputConfig
356
384
 
357
385
  /** Whether to automatically resume recording after an interruption (default is false) */
358
386
  autoResumeAfterInterruption?: boolean
@@ -370,6 +398,22 @@ export interface RecordingConfig {
370
398
 
371
399
  /** How to handle device disconnection during recording */
372
400
  deviceDisconnectionBehavior?: DeviceDisconnectionBehaviorType
401
+
402
+ /**
403
+ * Buffer duration in seconds. Controls the size of audio buffers
404
+ * used during recording. Smaller values reduce latency but increase
405
+ * CPU usage. Larger values improve efficiency but increase latency.
406
+ *
407
+ * Platform Notes:
408
+ * - iOS/macOS: Minimum effective 0.1s, uses accumulation below
409
+ * - Android: Respects all sizes within hardware limits
410
+ * - Web: Fully configurable
411
+ *
412
+ * Default: undefined (uses platform default ~23ms at 44.1kHz)
413
+ * Recommended: 0.01 - 0.5 seconds
414
+ * Optimal iOS: >= 0.1 seconds
415
+ */
416
+ bufferDurationSeconds?: number
373
417
  }
374
418
 
375
419
  export interface NotificationConfig {
@@ -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.compression
288
+ compression: recordingConfig.output?.compressed?.enabled
289
289
  ? {
290
- ...recordingConfig.compression,
291
- bitrate: recordingConfig.compression?.bitrate ?? 128000,
290
+ ...recordingConfig.output.compressed,
291
+ bitrate:
292
+ recordingConfig.output.compressed.bitrate ?? 128000,
292
293
  size: 0,
293
294
  mimeType: 'audio/webm',
294
- format: recordingConfig.compression?.format ?? 'opus',
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 compressionEnabled =
464
- this.recordingConfig?.compression?.enabled ?? false
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: 'opus',
477
+ format:
478
+ this.recordingConfig?.output?.compressed?.format ??
479
+ 'opus',
474
480
  bitrate:
475
- this.recordingConfig?.compression?.bitrate ?? 128000,
481
+ this.recordingConfig?.output?.compressed?.bitrate ??
482
+ 128000,
476
483
  }
477
484
 
478
- // If compression is enabled, use compressed blob as primary format
479
- if (compressionEnabled) {
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
- // If compression is disabled or no compressed blob is available,
500
- // use WAV as primary format
501
- if (!compressionEnabled || !compressedBlob) {
502
- this.logger?.debug(
503
- 'Using uncompressed WAV as primary output'
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 = `${this.streamUuid}.${this.extension}`
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?.compression?.enabled
635
+ compression: this.recordingConfig?.output?.compressed?.enabled
634
636
  ? {
635
637
  size: this.totalCompressedSize,
636
638
  mimeType: 'audio/webm',
637
- format: this.recordingConfig.compression.format ?? 'opus',
639
+ format:
640
+ this.recordingConfig.output.compressed.format ??
641
+ 'opus',
638
642
  bitrate:
639
- this.recordingConfig.compression.bitrate ?? 128000,
643
+ this.recordingConfig.output.compressed.bitrate ??
644
+ 128000,
640
645
  compressedFileUri: `${this.streamUuid}.webm`,
641
646
  }
642
647
  : undefined,
@@ -154,7 +154,7 @@ export class WebRecorder {
154
154
  }
155
155
 
156
156
  // Initialize compressed recording if enabled
157
- if (recordingConfig.compression?.enabled) {
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 web.storeUncompressedAudio is not explicitly false
237
+ // Only store PCM data if primary output is enabled
238
238
  const shouldStoreUncompressed =
239
- this.config.web?.storeUncompressedAudio !== false
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: 'opus',
278
+ format:
279
+ this.config.output?.compressed?.format ??
280
+ 'opus',
279
281
  bitrate:
280
- this.config.compression?.bitrate ?? 128000,
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.compression
318
+ compression: this.config.output?.compressed
316
319
  ? {
317
- enabled: this.config.compression.enabled,
318
- format: this.config.compression.format,
319
- bitrate: this.config.compression.bitrate,
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.compression?.bitrate ?? 128000,
849
+ this.config.output?.compressed?.bitrate ?? 128000,
847
850
  }
848
851
  )
849
852
 
@@ -566,7 +566,7 @@ export function useAudioRecorder({
566
566
  }, [dispatch])
567
567
 
568
568
  useEffect(() => {
569
- let intervalId: NodeJS.Timeout | undefined
569
+ let intervalId: ReturnType<typeof setInterval> | undefined
570
570
 
571
571
  if (state.isRecording || state.isPaused) {
572
572
  // Immediately check status when starting