@siteed/audio-studio 3.1.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 (96) hide show
  1. package/CHANGELOG.md +375 -4
  2. package/android/src/main/java/net/siteed/audiostudio/AudioStreamDecoder.kt +852 -0
  3. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +167 -3
  4. package/android/src/main/java/net/siteed/audiostudio/Constants.kt +4 -0
  5. package/build/cjs/errors/AudioStreamError.js +161 -0
  6. package/build/cjs/errors/AudioStreamError.js.map +1 -0
  7. package/build/cjs/errors/AudioStreamError.test.js +82 -0
  8. package/build/cjs/errors/AudioStreamError.test.js.map +1 -0
  9. package/build/cjs/index.js +7 -1
  10. package/build/cjs/index.js.map +1 -1
  11. package/build/cjs/streamAudioData.js +534 -0
  12. package/build/cjs/streamAudioData.js.map +1 -0
  13. package/build/cjs/utils/audioProcessing.js +14 -10
  14. package/build/cjs/utils/audioProcessing.js.map +1 -1
  15. package/build/esm/errors/AudioStreamError.js +156 -0
  16. package/build/esm/errors/AudioStreamError.js.map +1 -0
  17. package/build/esm/errors/AudioStreamError.test.js +80 -0
  18. package/build/esm/errors/AudioStreamError.test.js.map +1 -0
  19. package/build/esm/index.js +3 -1
  20. package/build/esm/index.js.map +1 -1
  21. package/build/esm/streamAudioData.js +527 -0
  22. package/build/esm/streamAudioData.js.map +1 -0
  23. package/build/esm/utils/audioProcessing.js +14 -10
  24. package/build/esm/utils/audioProcessing.js.map +1 -1
  25. package/build/types/errors/AudioStreamError.d.ts +25 -0
  26. package/build/types/errors/AudioStreamError.d.ts.map +1 -0
  27. package/build/types/errors/AudioStreamError.test.d.ts +2 -0
  28. package/build/types/errors/AudioStreamError.test.d.ts.map +1 -0
  29. package/build/types/index.d.ts +5 -1
  30. package/build/types/index.d.ts.map +1 -1
  31. package/build/types/streamAudioData.d.ts +119 -0
  32. package/build/types/streamAudioData.d.ts.map +1 -0
  33. package/build/types/utils/audioProcessing.d.ts +2 -2
  34. package/build/types/utils/audioProcessing.d.ts.map +1 -1
  35. package/ios/AudioProcessingHelpers.swift +10 -5
  36. package/ios/AudioStreamDecoder.swift +614 -0
  37. package/ios/AudioStudioModule.swift +186 -3
  38. package/package.json +163 -146
  39. package/scripts/README.md +58 -0
  40. package/src/errors/AudioStreamError.test.ts +92 -0
  41. package/src/errors/AudioStreamError.ts +199 -0
  42. package/src/index.ts +24 -0
  43. package/src/streamAudioData.ts +758 -0
  44. package/src/utils/audioProcessing.ts +25 -14
  45. package/android/src/androidTest/assets/chorus.wav +0 -0
  46. package/android/src/androidTest/assets/jfk.wav +0 -0
  47. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  48. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  49. package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +0 -190
  50. package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +0 -197
  51. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +0 -487
  52. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +0 -250
  53. package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +0 -186
  54. package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +0 -332
  55. package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +0 -324
  56. package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +0 -253
  57. package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +0 -218
  58. package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +0 -120
  59. package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +0 -345
  60. package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +0 -340
  61. package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +0 -252
  62. package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +0 -95
  63. package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +0 -43
  64. package/android/src/test/java/net/siteed/audiostudio/AndroidCallStateTest.kt +0 -37
  65. package/android/src/test/java/net/siteed/audiostudio/AndroidEventEmitterTest.kt +0 -28
  66. package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +0 -279
  67. package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +0 -249
  68. package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +0 -151
  69. package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +0 -273
  70. package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +0 -140
  71. package/android/src/test/java/net/siteed/audiostudio/InterruptionAutoResumePolicyTest.kt +0 -49
  72. package/android/src/test/resources/chorus.wav +0 -0
  73. package/android/src/test/resources/generate_test_audio.py +0 -94
  74. package/android/src/test/resources/jfk.wav +0 -0
  75. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  76. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  77. package/ios/AudioStudioTests/AudioFileHandlerTests.swift +0 -338
  78. package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +0 -331
  79. package/ios/AudioStudioTests/AudioTestHelpers.swift +0 -130
  80. package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +0 -334
  81. package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +0 -105
  82. package/ios/AudioStudioTests/Info.plist +0 -22
  83. package/ios/AudioStudioTests/README.md +0 -39
  84. package/ios/AudioStudioTests/SimpleAudioTest.swift +0 -98
  85. package/ios/AudioStudioTests/TestAudioGenerator.swift +0 -75
  86. package/ios/tests/README.md +0 -41
  87. package/ios/tests/integration/buffer_and_fallback_test.swift +0 -178
  88. package/ios/tests/integration/buffer_duration_test.swift +0 -185
  89. package/ios/tests/integration/compressed_only_output_test.swift +0 -271
  90. package/ios/tests/integration/output_control_test.swift +0 -322
  91. package/ios/tests/integration/run_integration_tests.sh +0 -37
  92. package/ios/tests/opus_support_test_macos.swift +0 -154
  93. package/ios/tests/standalone/audio_processing_test.swift +0 -144
  94. package/ios/tests/standalone/audio_recording_test.swift +0 -277
  95. package/ios/tests/standalone/audio_streaming_test.swift +0 -249
  96. package/ios/tests/standalone/standalone_test.swift +0 -144
@@ -44,8 +44,11 @@ async function processAudioBuffer({ arrayBuffer, fileUri, targetSampleRate, targ
44
44
  // Create context at original sample rate first
45
45
  ctx =
46
46
  audioContext ||
47
- new (window.AudioContext || window.webkitAudioContext)();
47
+ new (window.AudioContext ||
48
+ window.webkitAudioContext)();
48
49
  buffer = await ctx.decodeAudioData(audioData);
50
+ const effectiveTargetSampleRate = targetSampleRate ?? buffer.sampleRate;
51
+ const effectiveTargetChannels = targetChannels ?? buffer.numberOfChannels;
49
52
  logger?.debug('Decoded audio buffer:', {
50
53
  originalChannels: buffer.numberOfChannels,
51
54
  originalSampleRate: buffer.sampleRate,
@@ -63,21 +66,22 @@ async function processAudioBuffer({ arrayBuffer, fileUri, targetSampleRate, targ
63
66
  const bytesPerSample = 2; // 16-bit audio = 2 bytes per sample
64
67
  const adjustedStartSample = position !== undefined
65
68
  ? Math.floor((position / bytesPerSample) *
66
- (buffer.sampleRate / targetSampleRate))
69
+ (buffer.sampleRate / effectiveTargetSampleRate))
67
70
  : startSample;
68
71
  const samplesNeeded = length !== undefined
69
72
  ? Math.floor((length / bytesPerSample) *
70
- (buffer.sampleRate / targetSampleRate))
71
- : endTimeMs !== undefined && startTimeMs !== undefined
72
- ? Math.floor(((endTimeMs - startTimeMs) / 1000) * buffer.sampleRate)
73
+ (buffer.sampleRate / effectiveTargetSampleRate))
74
+ : endTimeMs !== undefined
75
+ ? Math.floor(((endTimeMs - (startTimeMs ?? 0)) / 1000) *
76
+ buffer.sampleRate)
73
77
  : buffer.length - adjustedStartSample;
74
78
  logger?.debug('Sample calculations (adjusted):', {
75
79
  originalStartSample: startSample,
76
80
  adjustedStartSample,
77
81
  samplesNeeded,
78
82
  originalSampleRate: buffer.sampleRate,
79
- targetSampleRate,
80
- conversionRatio: buffer.sampleRate / targetSampleRate,
83
+ targetSampleRate: effectiveTargetSampleRate,
84
+ conversionRatio: buffer.sampleRate / effectiveTargetSampleRate,
81
85
  expectedDurationMs: (samplesNeeded / buffer.sampleRate) * 1000,
82
86
  });
83
87
  // Create temporary buffer for the segment
@@ -91,7 +95,7 @@ async function processAudioBuffer({ arrayBuffer, fileUri, targetSampleRate, targ
91
95
  }
92
96
  }
93
97
  // Create offline context for resampling
94
- const offlineCtx = new OfflineAudioContext(targetChannels, Math.ceil((samplesNeeded * targetSampleRate) / buffer.sampleRate), targetSampleRate);
98
+ const offlineCtx = new OfflineAudioContext(effectiveTargetChannels, Math.ceil((samplesNeeded * effectiveTargetSampleRate) / buffer.sampleRate), effectiveTargetSampleRate);
95
99
  // Create source and connect
96
100
  const source = offlineCtx.createBufferSource();
97
101
  source.buffer = segmentBuffer;
@@ -104,7 +108,7 @@ async function processAudioBuffer({ arrayBuffer, fileUri, targetSampleRate, targ
104
108
  const durationMs = Math.round((samplesNeeded / buffer.sampleRate) * 1000);
105
109
  logger?.debug('Final processed audio:', {
106
110
  outputSamples: channelData.length,
107
- outputSampleRate: targetSampleRate,
111
+ outputSampleRate: effectiveTargetSampleRate,
108
112
  durationMs,
109
113
  });
110
114
  return {
@@ -112,7 +116,7 @@ async function processAudioBuffer({ arrayBuffer, fileUri, targetSampleRate, targ
112
116
  channelData,
113
117
  samples: channelData.length,
114
118
  durationMs,
115
- sampleRate: targetSampleRate,
119
+ sampleRate: effectiveTargetSampleRate,
116
120
  channels: processedBuffer.numberOfChannels,
117
121
  };
118
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"audioProcessing.js","sourceRoot":"","sources":["../../../src/utils/audioProcessing.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/audio-studio/src/utils/audioProcessing.ts\nimport { Platform } from 'react-native'\n\nimport { ConsoleLike } from '../AudioStudio.types'\n\nexport interface ProcessAudioBufferOptions {\n arrayBuffer?: ArrayBuffer\n fileUri?: string\n targetSampleRate: number\n targetChannels: number\n normalizeAudio: boolean\n startTimeMs?: number\n endTimeMs?: number\n position?: number\n length?: number\n audioContext?: AudioContext\n logger?: ConsoleLike\n}\n\nexport interface ProcessedAudioData {\n channelData: Float32Array\n samples: number\n durationMs: number\n sampleRate: number\n channels: number\n buffer: AudioBuffer\n}\n\nexport async function processAudioBuffer({\n arrayBuffer,\n fileUri,\n targetSampleRate,\n targetChannels,\n normalizeAudio,\n startTimeMs,\n endTimeMs,\n position,\n length,\n audioContext,\n logger,\n}: ProcessAudioBufferOptions): Promise<ProcessedAudioData> {\n if (Platform.OS !== 'web') {\n throw new Error('processAudioBuffer is only supported on web')\n }\n\n let ctx: AudioContext | undefined\n let buffer: AudioBuffer | undefined\n\n try {\n // Log initial parameters\n logger?.debug('Process audio buffer - Initial params:', {\n hasArrayBuffer: !!arrayBuffer,\n fileUri,\n targetSampleRate,\n targetChannels,\n normalizeAudio,\n startTimeMs,\n endTimeMs,\n position,\n length,\n })\n\n // Get the audio data\n let audioData: ArrayBuffer\n if (arrayBuffer) {\n audioData = arrayBuffer\n } else if (fileUri) {\n const response = await fetch(fileUri)\n if (!response.ok) {\n throw new Error(\n `Failed to fetch fileUri: ${response.statusText}`\n )\n }\n audioData = await response.arrayBuffer()\n } else {\n throw new Error('Either arrayBuffer or fileUri must be provided')\n }\n\n logger?.debug('Audio data loaded:', {\n byteLength: audioData.byteLength,\n firstBytes: Array.from(new Uint8Array(audioData.slice(0, 16))),\n })\n\n // Create context at original sample rate first\n ctx =\n audioContext ||\n new (window.AudioContext || (window as any).webkitAudioContext)()\n buffer = await ctx.decodeAudioData(audioData)\n\n logger?.debug('Decoded audio buffer:', {\n originalChannels: buffer.numberOfChannels,\n originalSampleRate: buffer.sampleRate,\n originalDuration: buffer.duration,\n originalLength: buffer.length,\n })\n\n // Calculate time range\n const startSample =\n startTimeMs !== undefined\n ? Math.floor((startTimeMs / 1000) * buffer.sampleRate)\n : position !== undefined\n ? Math.floor(position / 2)\n : 0\n\n // Fix: Adjust position calculation based on original sample rate\n // When position is provided in bytes, we need to account for the original sample rate\n const bytesPerSample = 2 // 16-bit audio = 2 bytes per sample\n const adjustedStartSample =\n position !== undefined\n ? Math.floor(\n (position / bytesPerSample) *\n (buffer.sampleRate / targetSampleRate)\n )\n : startSample\n\n const samplesNeeded =\n length !== undefined\n ? Math.floor(\n (length / bytesPerSample) *\n (buffer.sampleRate / targetSampleRate)\n )\n : endTimeMs !== undefined && startTimeMs !== undefined\n ? Math.floor(\n ((endTimeMs - startTimeMs) / 1000) * buffer.sampleRate\n )\n : buffer.length - adjustedStartSample\n\n logger?.debug('Sample calculations (adjusted):', {\n originalStartSample: startSample,\n adjustedStartSample,\n samplesNeeded,\n originalSampleRate: buffer.sampleRate,\n targetSampleRate,\n conversionRatio: buffer.sampleRate / targetSampleRate,\n expectedDurationMs: (samplesNeeded / buffer.sampleRate) * 1000,\n })\n\n // Create temporary buffer for the segment\n const segmentBuffer = ctx.createBuffer(\n buffer.numberOfChannels,\n samplesNeeded,\n buffer.sampleRate\n )\n\n // Copy the segment\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const channelData = buffer.getChannelData(channel)\n const segmentData = segmentBuffer.getChannelData(channel)\n for (let i = 0; i < samplesNeeded; i++) {\n segmentData[i] = channelData[adjustedStartSample + i]\n }\n }\n\n // Create offline context for resampling\n const offlineCtx = new OfflineAudioContext(\n targetChannels,\n Math.ceil((samplesNeeded * targetSampleRate) / buffer.sampleRate),\n targetSampleRate\n )\n\n // Create source and connect\n const source = offlineCtx.createBufferSource()\n source.buffer = segmentBuffer\n source.connect(offlineCtx.destination)\n\n // Render at new sample rate\n source.start()\n const processedBuffer = await offlineCtx.startRendering()\n\n // Get the final audio data\n const channelData = processedBuffer.getChannelData(0)\n const durationMs = Math.round(\n (samplesNeeded / buffer.sampleRate) * 1000\n )\n\n logger?.debug('Final processed audio:', {\n outputSamples: channelData.length,\n outputSampleRate: targetSampleRate,\n durationMs,\n })\n\n return {\n buffer: processedBuffer,\n channelData,\n samples: channelData.length,\n durationMs,\n sampleRate: targetSampleRate,\n channels: processedBuffer.numberOfChannels,\n }\n } catch (error) {\n logger?.error('Failed to process audio buffer:', {\n error,\n position,\n length,\n startTimeMs,\n endTimeMs,\n bufferLength: buffer?.length,\n })\n throw error\n } finally {\n if (!audioContext && ctx) {\n await ctx.close()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"audioProcessing.js","sourceRoot":"","sources":["../../../src/utils/audioProcessing.ts"],"names":[],"mappings":"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","sourcesContent":["// packages/audio-studio/src/utils/audioProcessing.ts\nimport { Platform } from 'react-native'\n\nimport { ConsoleLike } from '../AudioStudio.types'\n\nexport interface ProcessAudioBufferOptions {\n arrayBuffer?: ArrayBuffer\n fileUri?: string\n targetSampleRate?: number\n targetChannels?: number\n normalizeAudio: boolean\n startTimeMs?: number\n endTimeMs?: number\n position?: number\n length?: number\n audioContext?: AudioContext\n logger?: ConsoleLike\n}\n\nexport interface ProcessedAudioData {\n channelData: Float32Array\n samples: number\n durationMs: number\n sampleRate: number\n channels: number\n buffer: AudioBuffer\n}\n\nexport async function processAudioBuffer({\n arrayBuffer,\n fileUri,\n targetSampleRate,\n targetChannels,\n normalizeAudio,\n startTimeMs,\n endTimeMs,\n position,\n length,\n audioContext,\n logger,\n}: ProcessAudioBufferOptions): Promise<ProcessedAudioData> {\n if (Platform.OS !== 'web') {\n throw new Error('processAudioBuffer is only supported on web')\n }\n\n let ctx: AudioContext | undefined\n let buffer: AudioBuffer | undefined\n\n try {\n // Log initial parameters\n logger?.debug('Process audio buffer - Initial params:', {\n hasArrayBuffer: !!arrayBuffer,\n fileUri,\n targetSampleRate,\n targetChannels,\n normalizeAudio,\n startTimeMs,\n endTimeMs,\n position,\n length,\n })\n\n // Get the audio data\n let audioData: ArrayBuffer\n if (arrayBuffer) {\n audioData = arrayBuffer\n } else if (fileUri) {\n const response = await fetch(fileUri)\n if (!response.ok) {\n throw new Error(\n `Failed to fetch fileUri: ${response.statusText}`\n )\n }\n audioData = await response.arrayBuffer()\n } else {\n throw new Error('Either arrayBuffer or fileUri must be provided')\n }\n\n logger?.debug('Audio data loaded:', {\n byteLength: audioData.byteLength,\n firstBytes: Array.from(new Uint8Array(audioData.slice(0, 16))),\n })\n\n // Create context at original sample rate first\n ctx =\n audioContext ||\n new (window.AudioContext ||\n (\n window as unknown as {\n webkitAudioContext?: typeof AudioContext\n }\n ).webkitAudioContext)()\n buffer = await ctx.decodeAudioData(audioData)\n\n const effectiveTargetSampleRate = targetSampleRate ?? buffer.sampleRate\n const effectiveTargetChannels = targetChannels ?? buffer.numberOfChannels\n\n logger?.debug('Decoded audio buffer:', {\n originalChannels: buffer.numberOfChannels,\n originalSampleRate: buffer.sampleRate,\n originalDuration: buffer.duration,\n originalLength: buffer.length,\n })\n\n // Calculate time range\n const startSample =\n startTimeMs !== undefined\n ? Math.floor((startTimeMs / 1000) * buffer.sampleRate)\n : position !== undefined\n ? Math.floor(position / 2)\n : 0\n\n // Fix: Adjust position calculation based on original sample rate\n // When position is provided in bytes, we need to account for the original sample rate\n const bytesPerSample = 2 // 16-bit audio = 2 bytes per sample\n const adjustedStartSample =\n position !== undefined\n ? Math.floor(\n (position / bytesPerSample) *\n (buffer.sampleRate / effectiveTargetSampleRate)\n )\n : startSample\n\n const samplesNeeded =\n length !== undefined\n ? Math.floor(\n (length / bytesPerSample) *\n (buffer.sampleRate / effectiveTargetSampleRate)\n )\n : endTimeMs !== undefined\n ? Math.floor(\n ((endTimeMs - (startTimeMs ?? 0)) / 1000) *\n buffer.sampleRate\n )\n : buffer.length - adjustedStartSample\n\n logger?.debug('Sample calculations (adjusted):', {\n originalStartSample: startSample,\n adjustedStartSample,\n samplesNeeded,\n originalSampleRate: buffer.sampleRate,\n targetSampleRate: effectiveTargetSampleRate,\n conversionRatio: buffer.sampleRate / effectiveTargetSampleRate,\n expectedDurationMs: (samplesNeeded / buffer.sampleRate) * 1000,\n })\n\n // Create temporary buffer for the segment\n const segmentBuffer = ctx.createBuffer(\n buffer.numberOfChannels,\n samplesNeeded,\n buffer.sampleRate\n )\n\n // Copy the segment\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const channelData = buffer.getChannelData(channel)\n const segmentData = segmentBuffer.getChannelData(channel)\n for (let i = 0; i < samplesNeeded; i++) {\n segmentData[i] = channelData[adjustedStartSample + i]\n }\n }\n\n // Create offline context for resampling\n const offlineCtx = new OfflineAudioContext(\n effectiveTargetChannels,\n Math.ceil(\n (samplesNeeded * effectiveTargetSampleRate) / buffer.sampleRate\n ),\n effectiveTargetSampleRate\n )\n\n // Create source and connect\n const source = offlineCtx.createBufferSource()\n source.buffer = segmentBuffer\n source.connect(offlineCtx.destination)\n\n // Render at new sample rate\n source.start()\n const processedBuffer = await offlineCtx.startRendering()\n\n // Get the final audio data\n const channelData = processedBuffer.getChannelData(0)\n const durationMs = Math.round(\n (samplesNeeded / buffer.sampleRate) * 1000\n )\n\n logger?.debug('Final processed audio:', {\n outputSamples: channelData.length,\n outputSampleRate: effectiveTargetSampleRate,\n durationMs,\n })\n\n return {\n buffer: processedBuffer,\n channelData,\n samples: channelData.length,\n durationMs,\n sampleRate: effectiveTargetSampleRate,\n channels: processedBuffer.numberOfChannels,\n }\n } catch (error) {\n logger?.error('Failed to process audio buffer:', {\n error,\n position,\n length,\n startTimeMs,\n endTimeMs,\n bufferLength: buffer?.length,\n })\n throw error\n } finally {\n if (!audioContext && ctx) {\n await ctx.close()\n }\n }\n}\n"]}
@@ -0,0 +1,156 @@
1
+ const RECOVERABLE = [
2
+ 'ERR_AUDIO_STREAM_CANCELLED',
3
+ 'ERR_AUDIO_STREAM_BUSY',
4
+ 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT',
5
+ 'ERR_AUDIO_STREAM_PERMISSION_DENIED',
6
+ ];
7
+ export class AudioStreamError extends Error {
8
+ code;
9
+ recoverable;
10
+ fileUri;
11
+ platform;
12
+ nativeCode;
13
+ nativeMessage;
14
+ constructor(payload) {
15
+ super(payload.message);
16
+ this.name = 'AudioStreamError';
17
+ this.code = payload.code;
18
+ this.recoverable = payload.recoverable;
19
+ this.fileUri = payload.fileUri;
20
+ this.platform = payload.platform;
21
+ this.nativeCode = payload.nativeCode;
22
+ this.nativeMessage = payload.nativeMessage;
23
+ }
24
+ toJSON() {
25
+ return {
26
+ code: this.code,
27
+ message: this.message,
28
+ recoverable: this.recoverable,
29
+ fileUri: this.fileUri,
30
+ platform: this.platform,
31
+ nativeCode: this.nativeCode,
32
+ nativeMessage: this.nativeMessage,
33
+ };
34
+ }
35
+ }
36
+ function getNativeMessage(err) {
37
+ if (err instanceof Error)
38
+ return err.message;
39
+ if (typeof err === 'string')
40
+ return err;
41
+ try {
42
+ return JSON.stringify(err) ?? String(err);
43
+ }
44
+ catch {
45
+ return String(err);
46
+ }
47
+ }
48
+ function getNativeCode(err) {
49
+ if (err && typeof err === 'object' && 'code' in err) {
50
+ const code = err.code;
51
+ if (typeof code === 'string')
52
+ return code;
53
+ }
54
+ return undefined;
55
+ }
56
+ function isUnknownAudioStreamCode(raw) {
57
+ if (!raw)
58
+ return false;
59
+ return (raw.toUpperCase().startsWith('ERR_AUDIO_STREAM_') &&
60
+ normalizeCode(raw) === null);
61
+ }
62
+ function normalizeCode(raw) {
63
+ if (!raw)
64
+ return null;
65
+ const upper = raw.toUpperCase();
66
+ if (upper.startsWith('ERR_AUDIO_STREAM_')) {
67
+ const known = [
68
+ 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT',
69
+ 'ERR_AUDIO_STREAM_INVALID_RANGE',
70
+ 'ERR_AUDIO_STREAM_DECODE_FAILED',
71
+ 'ERR_AUDIO_STREAM_CANCELLED',
72
+ 'ERR_AUDIO_STREAM_PERMISSION_DENIED',
73
+ 'ERR_AUDIO_STREAM_FILE_NOT_FOUND',
74
+ 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT',
75
+ 'ERR_AUDIO_STREAM_NATIVE_UNAVAILABLE',
76
+ 'ERR_AUDIO_STREAM_BUSY',
77
+ 'ERR_AUDIO_STREAM_UNKNOWN',
78
+ ];
79
+ if (known.includes(upper)) {
80
+ return upper;
81
+ }
82
+ }
83
+ if (upper.includes('FILE_NOT_FOUND') || upper === 'ENOENT') {
84
+ return 'ERR_AUDIO_STREAM_FILE_NOT_FOUND';
85
+ }
86
+ if (upper.includes('PERMISSION') || upper === 'EACCES') {
87
+ return 'ERR_AUDIO_STREAM_PERMISSION_DENIED';
88
+ }
89
+ if (upper.includes('UNSUPPORTED') ||
90
+ upper.includes('NO_SUITABLE_CODEC') ||
91
+ upper.includes('NO SUITABLE CODEC') ||
92
+ upper.includes('NOT SUPPORTED')) {
93
+ return 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT';
94
+ }
95
+ if (upper.includes('INVALID_RANGE') ||
96
+ upper.includes('OUT_OF_RANGE') ||
97
+ upper.includes('INVALID_TIME')) {
98
+ return 'ERR_AUDIO_STREAM_INVALID_RANGE';
99
+ }
100
+ if (upper.includes('CANCELLED') || upper.includes('CANCELED')) {
101
+ return 'ERR_AUDIO_STREAM_CANCELLED';
102
+ }
103
+ if (upper.includes('BUSY')) {
104
+ return 'ERR_AUDIO_STREAM_BUSY';
105
+ }
106
+ if (upper.includes('BACKPRESSURE')) {
107
+ return 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT';
108
+ }
109
+ if (upper.includes('DECODE') ||
110
+ upper.includes('CODEC') ||
111
+ upper.includes('MALFORMED')) {
112
+ return 'ERR_AUDIO_STREAM_DECODE_FAILED';
113
+ }
114
+ return null;
115
+ }
116
+ export function mapStreamError(err, fileUri, platform) {
117
+ if (err instanceof AudioStreamError)
118
+ return err;
119
+ const nativeMessage = getNativeMessage(err);
120
+ const nativeCode = getNativeCode(err);
121
+ const lower = nativeMessage.toLowerCase();
122
+ if (isUnknownAudioStreamCode(nativeCode)) {
123
+ console.warn(`[AudioStreamError] Unknown native audio stream error code: ${nativeCode}`);
124
+ }
125
+ let code = normalizeCode(nativeCode) ??
126
+ normalizeCode(nativeMessage) ??
127
+ 'ERR_AUDIO_STREAM_UNKNOWN';
128
+ if (code === 'ERR_AUDIO_STREAM_UNKNOWN') {
129
+ if (lower.includes('not found') || lower.includes('does not exist')) {
130
+ code = 'ERR_AUDIO_STREAM_FILE_NOT_FOUND';
131
+ }
132
+ else if (lower.includes('unsupported') ||
133
+ lower.includes('no suitable codec')) {
134
+ code = 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT';
135
+ }
136
+ else if (lower.includes('permission') || lower.includes('denied')) {
137
+ code = 'ERR_AUDIO_STREAM_PERMISSION_DENIED';
138
+ }
139
+ else if (lower.includes('decode') || lower.includes('codec')) {
140
+ code = 'ERR_AUDIO_STREAM_DECODE_FAILED';
141
+ }
142
+ else if (lower.includes('cancel')) {
143
+ code = 'ERR_AUDIO_STREAM_CANCELLED';
144
+ }
145
+ }
146
+ return new AudioStreamError({
147
+ code,
148
+ message: `Audio stream failed (${code}): ${nativeMessage}`,
149
+ recoverable: RECOVERABLE.includes(code),
150
+ fileUri,
151
+ platform,
152
+ nativeCode,
153
+ nativeMessage,
154
+ });
155
+ }
156
+ //# sourceMappingURL=AudioStreamError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioStreamError.js","sourceRoot":"","sources":["../../../src/errors/AudioStreamError.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Stable typed errors for `streamAudioData`. Callers can switch on `code`.\n */\nexport type AudioStreamErrorCode =\n | 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT'\n | 'ERR_AUDIO_STREAM_INVALID_RANGE'\n | 'ERR_AUDIO_STREAM_DECODE_FAILED'\n | 'ERR_AUDIO_STREAM_CANCELLED'\n | 'ERR_AUDIO_STREAM_PERMISSION_DENIED'\n | 'ERR_AUDIO_STREAM_FILE_NOT_FOUND'\n | 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT'\n | 'ERR_AUDIO_STREAM_NATIVE_UNAVAILABLE'\n | 'ERR_AUDIO_STREAM_BUSY'\n | 'ERR_AUDIO_STREAM_UNKNOWN'\n\nexport interface AudioStreamErrorPayload {\n code: AudioStreamErrorCode\n message: string\n recoverable: boolean\n fileUri?: string\n platform?: string\n nativeCode?: string\n nativeMessage?: string\n}\n\nconst RECOVERABLE: AudioStreamErrorCode[] = [\n 'ERR_AUDIO_STREAM_CANCELLED',\n 'ERR_AUDIO_STREAM_BUSY',\n 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT',\n 'ERR_AUDIO_STREAM_PERMISSION_DENIED',\n]\n\nexport class AudioStreamError extends Error {\n readonly code: AudioStreamErrorCode\n readonly recoverable: boolean\n readonly fileUri?: string\n readonly platform?: string\n readonly nativeCode?: string\n readonly nativeMessage?: string\n\n constructor(payload: AudioStreamErrorPayload) {\n super(payload.message)\n this.name = 'AudioStreamError'\n this.code = payload.code\n this.recoverable = payload.recoverable\n this.fileUri = payload.fileUri\n this.platform = payload.platform\n this.nativeCode = payload.nativeCode\n this.nativeMessage = payload.nativeMessage\n }\n\n toJSON(): AudioStreamErrorPayload {\n return {\n code: this.code,\n message: this.message,\n recoverable: this.recoverable,\n fileUri: this.fileUri,\n platform: this.platform,\n nativeCode: this.nativeCode,\n nativeMessage: this.nativeMessage,\n }\n }\n}\n\nfunction getNativeMessage(err: unknown): string {\n if (err instanceof Error) return err.message\n if (typeof err === 'string') return err\n try {\n return JSON.stringify(err) ?? String(err)\n } catch {\n return String(err)\n }\n}\n\nfunction getNativeCode(err: unknown): string | undefined {\n if (err && typeof err === 'object' && 'code' in err) {\n const code = (err as { code?: unknown }).code\n if (typeof code === 'string') return code\n }\n return undefined\n}\n\nfunction isUnknownAudioStreamCode(raw: string | undefined): boolean {\n if (!raw) return false\n return (\n raw.toUpperCase().startsWith('ERR_AUDIO_STREAM_') &&\n normalizeCode(raw) === null\n )\n}\n\nfunction normalizeCode(raw: string | undefined): AudioStreamErrorCode | null {\n if (!raw) return null\n const upper = raw.toUpperCase()\n if (upper.startsWith('ERR_AUDIO_STREAM_')) {\n const known: AudioStreamErrorCode[] = [\n 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT',\n 'ERR_AUDIO_STREAM_INVALID_RANGE',\n 'ERR_AUDIO_STREAM_DECODE_FAILED',\n 'ERR_AUDIO_STREAM_CANCELLED',\n 'ERR_AUDIO_STREAM_PERMISSION_DENIED',\n 'ERR_AUDIO_STREAM_FILE_NOT_FOUND',\n 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT',\n 'ERR_AUDIO_STREAM_NATIVE_UNAVAILABLE',\n 'ERR_AUDIO_STREAM_BUSY',\n 'ERR_AUDIO_STREAM_UNKNOWN',\n ]\n if ((known as string[]).includes(upper)) {\n return upper as AudioStreamErrorCode\n }\n }\n if (upper.includes('FILE_NOT_FOUND') || upper === 'ENOENT') {\n return 'ERR_AUDIO_STREAM_FILE_NOT_FOUND'\n }\n if (upper.includes('PERMISSION') || upper === 'EACCES') {\n return 'ERR_AUDIO_STREAM_PERMISSION_DENIED'\n }\n if (\n upper.includes('UNSUPPORTED') ||\n upper.includes('NO_SUITABLE_CODEC') ||\n upper.includes('NO SUITABLE CODEC') ||\n upper.includes('NOT SUPPORTED')\n ) {\n return 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT'\n }\n if (\n upper.includes('INVALID_RANGE') ||\n upper.includes('OUT_OF_RANGE') ||\n upper.includes('INVALID_TIME')\n ) {\n return 'ERR_AUDIO_STREAM_INVALID_RANGE'\n }\n if (upper.includes('CANCELLED') || upper.includes('CANCELED')) {\n return 'ERR_AUDIO_STREAM_CANCELLED'\n }\n if (upper.includes('BUSY')) {\n return 'ERR_AUDIO_STREAM_BUSY'\n }\n if (upper.includes('BACKPRESSURE')) {\n return 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT'\n }\n if (\n upper.includes('DECODE') ||\n upper.includes('CODEC') ||\n upper.includes('MALFORMED')\n ) {\n return 'ERR_AUDIO_STREAM_DECODE_FAILED'\n }\n return null\n}\n\nexport function mapStreamError(\n err: unknown,\n fileUri?: string,\n platform?: string\n): AudioStreamError {\n if (err instanceof AudioStreamError) return err\n\n const nativeMessage = getNativeMessage(err)\n const nativeCode = getNativeCode(err)\n const lower = nativeMessage.toLowerCase()\n\n if (isUnknownAudioStreamCode(nativeCode)) {\n console.warn(\n `[AudioStreamError] Unknown native audio stream error code: ${nativeCode}`\n )\n }\n\n let code =\n normalizeCode(nativeCode) ??\n normalizeCode(nativeMessage) ??\n 'ERR_AUDIO_STREAM_UNKNOWN'\n\n if (code === 'ERR_AUDIO_STREAM_UNKNOWN') {\n if (lower.includes('not found') || lower.includes('does not exist')) {\n code = 'ERR_AUDIO_STREAM_FILE_NOT_FOUND'\n } else if (\n lower.includes('unsupported') ||\n lower.includes('no suitable codec')\n ) {\n code = 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT'\n } else if (lower.includes('permission') || lower.includes('denied')) {\n code = 'ERR_AUDIO_STREAM_PERMISSION_DENIED'\n } else if (lower.includes('decode') || lower.includes('codec')) {\n code = 'ERR_AUDIO_STREAM_DECODE_FAILED'\n } else if (lower.includes('cancel')) {\n code = 'ERR_AUDIO_STREAM_CANCELLED'\n }\n }\n\n return new AudioStreamError({\n code,\n message: `Audio stream failed (${code}): ${nativeMessage}`,\n recoverable: RECOVERABLE.includes(code),\n fileUri,\n platform,\n nativeCode,\n nativeMessage,\n })\n}\n"]}
@@ -0,0 +1,80 @@
1
+ import { AudioStreamError, mapStreamError } from './AudioStreamError';
2
+ describe('AudioStreamError', () => {
3
+ it('passes through an existing AudioStreamError unchanged', () => {
4
+ const original = new AudioStreamError({
5
+ code: 'ERR_AUDIO_STREAM_CANCELLED',
6
+ message: 'aborted',
7
+ recoverable: true,
8
+ });
9
+ expect(mapStreamError(original)).toBe(original);
10
+ });
11
+ it('maps native FILE_NOT_FOUND code', () => {
12
+ const mapped = mapStreamError({
13
+ code: 'FILE_NOT_FOUND',
14
+ message: 'gone',
15
+ });
16
+ expect(mapped.code).toBe('ERR_AUDIO_STREAM_FILE_NOT_FOUND');
17
+ expect(mapped.recoverable).toBe(false);
18
+ });
19
+ it('maps unsupported codec text', () => {
20
+ const mapped = mapStreamError(new Error('No suitable codec for audio/opus'));
21
+ expect(mapped.code).toBe('ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT');
22
+ });
23
+ it('marks cancellation as recoverable', () => {
24
+ const mapped = mapStreamError({
25
+ code: 'ERR_AUDIO_STREAM_CANCELLED',
26
+ message: 'user cancelled',
27
+ });
28
+ expect(mapped.code).toBe('ERR_AUDIO_STREAM_CANCELLED');
29
+ expect(mapped.recoverable).toBe(true);
30
+ });
31
+ it('maps backpressure timeout as recoverable', () => {
32
+ const mapped = mapStreamError({
33
+ code: 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT',
34
+ message: 'ack timed out',
35
+ });
36
+ expect(mapped.code).toBe('ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT');
37
+ expect(mapped.recoverable).toBe(true);
38
+ });
39
+ it('falls back to UNKNOWN', () => {
40
+ const mapped = mapStreamError({});
41
+ expect(mapped.code).toBe('ERR_AUDIO_STREAM_UNKNOWN');
42
+ });
43
+ it('warns when native returns an unknown audio stream code', () => {
44
+ const warn = jest.spyOn(console, 'warn').mockImplementation(() => { });
45
+ const mapped = mapStreamError({
46
+ code: 'ERR_AUDIO_STREAM_FOOBAR',
47
+ message: 'new native code',
48
+ });
49
+ expect(mapped.code).toBe('ERR_AUDIO_STREAM_UNKNOWN');
50
+ expect(warn).toHaveBeenCalledWith('[AudioStreamError] Unknown native audio stream error code: ERR_AUDIO_STREAM_FOOBAR');
51
+ warn.mockRestore();
52
+ });
53
+ it('preserves nativeCode and nativeMessage', () => {
54
+ const mapped = mapStreamError({
55
+ code: 'WEIRD_NATIVE_CODE',
56
+ message: 'something went wrong on the bridge',
57
+ });
58
+ expect(mapped.nativeCode).toBe('WEIRD_NATIVE_CODE');
59
+ expect(mapped.nativeMessage).toContain('bridge');
60
+ });
61
+ it('serialises to a stable JSON payload', () => {
62
+ const err = new AudioStreamError({
63
+ code: 'ERR_AUDIO_STREAM_DECODE_FAILED',
64
+ message: 'decoder bust',
65
+ recoverable: false,
66
+ fileUri: 'file:///a.m4a',
67
+ platform: 'ios',
68
+ });
69
+ expect(err.toJSON()).toEqual({
70
+ code: 'ERR_AUDIO_STREAM_DECODE_FAILED',
71
+ message: 'decoder bust',
72
+ recoverable: false,
73
+ fileUri: 'file:///a.m4a',
74
+ platform: 'ios',
75
+ nativeCode: undefined,
76
+ nativeMessage: undefined,
77
+ });
78
+ });
79
+ });
80
+ //# sourceMappingURL=AudioStreamError.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioStreamError.test.js","sourceRoot":"","sources":["../../../src/errors/AudioStreamError.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { AudioStreamError, mapStreamError } from './AudioStreamError'\n\ndescribe('AudioStreamError', () => {\n it('passes through an existing AudioStreamError unchanged', () => {\n const original = new AudioStreamError({\n code: 'ERR_AUDIO_STREAM_CANCELLED',\n message: 'aborted',\n recoverable: true,\n })\n expect(mapStreamError(original)).toBe(original)\n })\n\n it('maps native FILE_NOT_FOUND code', () => {\n const mapped = mapStreamError({\n code: 'FILE_NOT_FOUND',\n message: 'gone',\n })\n expect(mapped.code).toBe('ERR_AUDIO_STREAM_FILE_NOT_FOUND')\n expect(mapped.recoverable).toBe(false)\n })\n\n it('maps unsupported codec text', () => {\n const mapped = mapStreamError(\n new Error('No suitable codec for audio/opus')\n )\n expect(mapped.code).toBe('ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT')\n })\n\n it('marks cancellation as recoverable', () => {\n const mapped = mapStreamError({\n code: 'ERR_AUDIO_STREAM_CANCELLED',\n message: 'user cancelled',\n })\n expect(mapped.code).toBe('ERR_AUDIO_STREAM_CANCELLED')\n expect(mapped.recoverable).toBe(true)\n })\n\n it('maps backpressure timeout as recoverable', () => {\n const mapped = mapStreamError({\n code: 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT',\n message: 'ack timed out',\n })\n expect(mapped.code).toBe('ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT')\n expect(mapped.recoverable).toBe(true)\n })\n\n it('falls back to UNKNOWN', () => {\n const mapped = mapStreamError({})\n expect(mapped.code).toBe('ERR_AUDIO_STREAM_UNKNOWN')\n })\n\n it('warns when native returns an unknown audio stream code', () => {\n const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})\n const mapped = mapStreamError({\n code: 'ERR_AUDIO_STREAM_FOOBAR',\n message: 'new native code',\n })\n expect(mapped.code).toBe('ERR_AUDIO_STREAM_UNKNOWN')\n expect(warn).toHaveBeenCalledWith(\n '[AudioStreamError] Unknown native audio stream error code: ERR_AUDIO_STREAM_FOOBAR'\n )\n warn.mockRestore()\n })\n\n it('preserves nativeCode and nativeMessage', () => {\n const mapped = mapStreamError({\n code: 'WEIRD_NATIVE_CODE',\n message: 'something went wrong on the bridge',\n })\n expect(mapped.nativeCode).toBe('WEIRD_NATIVE_CODE')\n expect(mapped.nativeMessage).toContain('bridge')\n })\n\n it('serialises to a stable JSON payload', () => {\n const err = new AudioStreamError({\n code: 'ERR_AUDIO_STREAM_DECODE_FAILED',\n message: 'decoder bust',\n recoverable: false,\n fileUri: 'file:///a.m4a',\n platform: 'ios',\n })\n expect(err.toJSON()).toEqual({\n code: 'ERR_AUDIO_STREAM_DECODE_FAILED',\n message: 'decoder bust',\n recoverable: false,\n fileUri: 'file:///a.m4a',\n platform: 'ios',\n nativeCode: undefined,\n nativeMessage: undefined,\n })\n })\n})\n"]}
@@ -6,6 +6,7 @@ import { extractPreview } from './AudioAnalysis/extractPreview';
6
6
  import { initMelStreamingWasm, computeMelFrameWasm, } from './AudioAnalysis/melSpectrogramWasm';
7
7
  import { AudioRecorderProvider, useSharedAudioRecorder, } from './AudioRecorder.provider';
8
8
  import AudioStudioModule from './AudioStudioModule';
9
+ import { getAudioDecodeCapabilities, streamAudioData, } from './streamAudioData';
9
10
  import { trimAudio } from './trimAudio';
10
11
  import { useAudioRecorder } from './useAudioRecorder';
11
12
  export * from './utils/convertPCMToFloat32';
@@ -19,8 +20,9 @@ export { AudioDeviceManager, audioDeviceManager } from './AudioDeviceManager';
19
20
  export { useAudioDevices } from './hooks/useAudioDevices';
20
21
  export { setMelSpectrogramWasmUrl } from './AudioAnalysis/wasmConfig';
21
22
  export { extractPreviewBars } from './AudioAnalysis/extractPreviewBars';
22
- export { AudioRecorderProvider, AudioStudioModule, extractRawWavAnalysis, extractAudioAnalysis, extractPreview, trimAudio, extractAudioData, extractMelSpectrogram, initMelStreamingWasm, computeMelFrameWasm, MAX_DURATION_MS, useAudioRecorder, useSharedAudioRecorder, };
23
+ export { AudioRecorderProvider, AudioStudioModule, extractRawWavAnalysis, extractAudioAnalysis, extractPreview, trimAudio, extractAudioData, streamAudioData, getAudioDecodeCapabilities, extractMelSpectrogram, initMelStreamingWasm, computeMelFrameWasm, MAX_DURATION_MS, useAudioRecorder, useSharedAudioRecorder, };
23
24
  export { AudioExtractionError, mapExtractionError, } from './errors/AudioExtractionError';
25
+ export { AudioStreamError, mapStreamError, } from './errors/AudioStreamError';
24
26
  /** @deprecated Use AudioStudioModule instead */
25
27
  export const ExpoAudioStreamModule = AudioStudioModule;
26
28
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,eAAe;AAEf,OAAO,EACH,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,EACH,qBAAqB,EACrB,eAAe,GAClB,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EACH,oBAAoB,EACpB,mBAAmB,GACtB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EACH,qBAAqB,EACrB,sBAAsB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,iBAAiB,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,+BAA+B;AAC/B,OAAO,EACH,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GAE1B,MAAM,iCAAiC,CAAA;AAExC,4BAA4B;AAC5B,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE7E,8BAA8B;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAEzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAEvE,OAAO,EACH,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,GACzB,CAAA;AAED,OAAO,EACH,oBAAoB,EACpB,kBAAkB,GACrB,MAAM,+BAA+B,CAAA;AAUtC,gDAAgD;AAChD,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAA","sourcesContent":["// src/index.ts\n\nimport {\n extractRawWavAnalysis,\n extractAudioAnalysis,\n} from './AudioAnalysis/extractAudioAnalysis'\nimport { extractAudioData } from './AudioAnalysis/extractAudioData'\nimport {\n extractMelSpectrogram,\n MAX_DURATION_MS,\n} from './AudioAnalysis/extractMelSpectrogram'\nimport { extractPreview } from './AudioAnalysis/extractPreview'\nimport {\n initMelStreamingWasm,\n computeMelFrameWasm,\n} from './AudioAnalysis/melSpectrogramWasm'\nimport {\n AudioRecorderProvider,\n useSharedAudioRecorder,\n} from './AudioRecorder.provider'\nimport AudioStudioModule from './AudioStudioModule'\nimport { trimAudio } from './trimAudio'\nimport { useAudioRecorder } from './useAudioRecorder'\n\nexport * from './utils/convertPCMToFloat32'\nexport * from './utils/getWavFileInfo'\nexport * from './utils/writeWavHeader'\n\n// Export platform capabilities\nexport {\n getPlatformCapabilities,\n isEncodingSupported,\n isBitDepthSupported,\n getFallbackEncoding,\n getFallbackBitDepth,\n validateRecordingConfig,\n type PlatformCapabilities,\n} from './constants/platformLimitations'\n\n// Export AudioDeviceManager\nexport { AudioDeviceManager, audioDeviceManager } from './AudioDeviceManager'\n\n// Export useAudioDevices hook\nexport { useAudioDevices } from './hooks/useAudioDevices'\n\nexport { setMelSpectrogramWasmUrl } from './AudioAnalysis/wasmConfig'\nexport { extractPreviewBars } from './AudioAnalysis/extractPreviewBars'\n\nexport {\n AudioRecorderProvider,\n AudioStudioModule,\n extractRawWavAnalysis,\n extractAudioAnalysis,\n extractPreview,\n trimAudio,\n extractAudioData,\n extractMelSpectrogram,\n initMelStreamingWasm,\n computeMelFrameWasm,\n MAX_DURATION_MS,\n useAudioRecorder,\n useSharedAudioRecorder,\n}\n\nexport {\n AudioExtractionError,\n mapExtractionError,\n} from './errors/AudioExtractionError'\nexport type {\n AudioExtractionErrorCode,\n AudioExtractionErrorPayload,\n} from './errors/AudioExtractionError'\n\n// Export all types\nexport type * from './AudioAnalysis/AudioAnalysis.types'\nexport type * from './AudioStudio.types'\n\n/** @deprecated Use AudioStudioModule instead */\nexport const ExpoAudioStreamModule = AudioStudioModule\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,eAAe;AAEf,OAAO,EACH,qBAAqB,EACrB,oBAAoB,GACvB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,EACH,qBAAqB,EACrB,eAAe,GAClB,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EACH,oBAAoB,EACpB,mBAAmB,GACtB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,EACH,qBAAqB,EACrB,sBAAsB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,iBAAiB,MAAM,qBAAqB,CAAA;AACnD,OAAO,EACH,0BAA0B,EAC1B,eAAe,GAClB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,+BAA+B;AAC/B,OAAO,EACH,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,GAE1B,MAAM,iCAAiC,CAAA;AAExC,4BAA4B;AAC5B,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE7E,8BAA8B;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAEzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAEvE,OAAO,EACH,qBAAqB,EACrB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,eAAe,EACf,0BAA0B,EAC1B,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,GACzB,CAAA;AAED,OAAO,EACH,oBAAoB,EACpB,kBAAkB,GACrB,MAAM,+BAA+B,CAAA;AAMtC,OAAO,EACH,gBAAgB,EAChB,cAAc,GACjB,MAAM,2BAA2B,CAAA;AAmBlC,gDAAgD;AAChD,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAA","sourcesContent":["// src/index.ts\n\nimport {\n extractRawWavAnalysis,\n extractAudioAnalysis,\n} from './AudioAnalysis/extractAudioAnalysis'\nimport { extractAudioData } from './AudioAnalysis/extractAudioData'\nimport {\n extractMelSpectrogram,\n MAX_DURATION_MS,\n} from './AudioAnalysis/extractMelSpectrogram'\nimport { extractPreview } from './AudioAnalysis/extractPreview'\nimport {\n initMelStreamingWasm,\n computeMelFrameWasm,\n} from './AudioAnalysis/melSpectrogramWasm'\nimport {\n AudioRecorderProvider,\n useSharedAudioRecorder,\n} from './AudioRecorder.provider'\nimport AudioStudioModule from './AudioStudioModule'\nimport {\n getAudioDecodeCapabilities,\n streamAudioData,\n} from './streamAudioData'\nimport { trimAudio } from './trimAudio'\nimport { useAudioRecorder } from './useAudioRecorder'\n\nexport * from './utils/convertPCMToFloat32'\nexport * from './utils/getWavFileInfo'\nexport * from './utils/writeWavHeader'\n\n// Export platform capabilities\nexport {\n getPlatformCapabilities,\n isEncodingSupported,\n isBitDepthSupported,\n getFallbackEncoding,\n getFallbackBitDepth,\n validateRecordingConfig,\n type PlatformCapabilities,\n} from './constants/platformLimitations'\n\n// Export AudioDeviceManager\nexport { AudioDeviceManager, audioDeviceManager } from './AudioDeviceManager'\n\n// Export useAudioDevices hook\nexport { useAudioDevices } from './hooks/useAudioDevices'\n\nexport { setMelSpectrogramWasmUrl } from './AudioAnalysis/wasmConfig'\nexport { extractPreviewBars } from './AudioAnalysis/extractPreviewBars'\n\nexport {\n AudioRecorderProvider,\n AudioStudioModule,\n extractRawWavAnalysis,\n extractAudioAnalysis,\n extractPreview,\n trimAudio,\n extractAudioData,\n streamAudioData,\n getAudioDecodeCapabilities,\n extractMelSpectrogram,\n initMelStreamingWasm,\n computeMelFrameWasm,\n MAX_DURATION_MS,\n useAudioRecorder,\n useSharedAudioRecorder,\n}\n\nexport {\n AudioExtractionError,\n mapExtractionError,\n} from './errors/AudioExtractionError'\nexport type {\n AudioExtractionErrorCode,\n AudioExtractionErrorPayload,\n} from './errors/AudioExtractionError'\n\nexport {\n AudioStreamError,\n mapStreamError,\n} from './errors/AudioStreamError'\nexport type {\n AudioStreamErrorCode,\n AudioStreamErrorPayload,\n} from './errors/AudioStreamError'\n\nexport type {\n StreamAudioDataOptions,\n StreamAudioDataChunk,\n StreamAudioDataProgress,\n StreamAudioDataResult,\n StreamAudioDataCallbacks,\n AudioDecodeCapabilities,\n} from './streamAudioData'\n\n// Export all types\nexport type * from './AudioAnalysis/AudioAnalysis.types'\nexport type * from './AudioStudio.types'\n\n/** @deprecated Use AudioStudioModule instead */\nexport const ExpoAudioStreamModule = AudioStudioModule\n"]}