@siteed/audio-studio 3.1.0 → 3.2.0-beta.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.
- package/CHANGELOG.md +30 -1
- package/README.md +97 -50
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +190 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +29 -83
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +17 -1
- package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +186 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioProcessor.kt +473 -380
- package/android/src/main/java/net/siteed/audiostudio/AudioStreamDecoder.kt +640 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +187 -13
- package/android/src/main/java/net/siteed/audiostudio/AudioTrimmer.kt +174 -212
- package/android/src/main/java/net/siteed/audiostudio/Constants.kt +4 -0
- package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/cjs/AudioAnalysis/extractPreview.js +92 -15
- package/build/cjs/AudioAnalysis/extractPreview.js.map +1 -1
- package/build/cjs/AudioAnalysis/extractPreviewBars.js +134 -0
- package/build/cjs/AudioAnalysis/extractPreviewBars.js.map +1 -0
- package/build/cjs/errors/AudioExtractionError.js +127 -0
- package/build/cjs/errors/AudioExtractionError.js.map +1 -0
- package/build/cjs/errors/AudioStreamError.js +152 -0
- package/build/cjs/errors/AudioStreamError.js.map +1 -0
- package/build/cjs/errors/AudioStreamError.test.js +61 -0
- package/build/cjs/errors/AudioStreamError.test.js.map +1 -0
- package/build/cjs/index.js +12 -1
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/streamAudioData.js +467 -0
- package/build/cjs/streamAudioData.js.map +1 -0
- package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/esm/AudioAnalysis/extractPreview.js +92 -15
- package/build/esm/AudioAnalysis/extractPreview.js.map +1 -1
- package/build/esm/AudioAnalysis/extractPreviewBars.js +128 -0
- package/build/esm/AudioAnalysis/extractPreviewBars.js.map +1 -0
- package/build/esm/errors/AudioExtractionError.js +122 -0
- package/build/esm/errors/AudioExtractionError.js.map +1 -0
- package/build/esm/errors/AudioStreamError.js +147 -0
- package/build/esm/errors/AudioStreamError.js.map +1 -0
- package/build/esm/errors/AudioStreamError.test.js +59 -0
- package/build/esm/errors/AudioStreamError.test.js.map +1 -0
- package/build/esm/index.js +5 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/streamAudioData.js +460 -0
- package/build/esm/streamAudioData.js.map +1 -0
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +79 -0
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
- package/build/types/AudioAnalysis/extractPreview.d.ts +2 -2
- package/build/types/AudioAnalysis/extractPreview.d.ts.map +1 -1
- package/build/types/AudioAnalysis/extractPreviewBars.d.ts +12 -0
- package/build/types/AudioAnalysis/extractPreviewBars.d.ts.map +1 -0
- package/build/types/errors/AudioExtractionError.d.ts +24 -0
- package/build/types/errors/AudioExtractionError.d.ts.map +1 -0
- package/build/types/errors/AudioStreamError.d.ts +25 -0
- package/build/types/errors/AudioStreamError.d.ts.map +1 -0
- package/build/types/errors/AudioStreamError.test.d.ts +2 -0
- package/build/types/errors/AudioStreamError.test.d.ts.map +1 -0
- package/build/types/index.d.ts +8 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/streamAudioData.d.ts +114 -0
- package/build/types/streamAudioData.d.ts.map +1 -0
- package/ios/AudioProcessingHelpers.swift +10 -5
- package/ios/AudioProcessor.swift +99 -0
- package/ios/AudioStreamDecoder.swift +523 -0
- package/ios/AudioStudioModule.swift +210 -3
- package/ios/AudioStudioTests/AudioStreamDecoderTests.swift +128 -0
- package/package.json +7 -7
- package/src/AudioAnalysis/AudioAnalysis.types.ts +82 -0
- package/src/AudioAnalysis/extractPreview.ts +118 -17
- package/src/AudioAnalysis/extractPreviewBars.ts +193 -0
- package/src/errors/AudioExtractionError.ts +167 -0
- package/src/errors/AudioStreamError.test.ts +65 -0
- package/src/errors/AudioStreamError.ts +185 -0
- package/src/index.ts +34 -0
- package/src/streamAudioData.ts +654 -0
|
@@ -1,25 +1,102 @@
|
|
|
1
|
+
import { mapExtractionError } from '../errors/AudioExtractionError';
|
|
1
2
|
import { extractAudioAnalysis } from './extractAudioAnalysis';
|
|
3
|
+
const DEFAULT_SILENCE_THRESHOLD = 0.01;
|
|
4
|
+
/**
|
|
5
|
+
* Apply a silence threshold to the data points by recomputing the `silent` flag from rms.
|
|
6
|
+
* Returns a new array (does not mutate the source).
|
|
7
|
+
*/
|
|
8
|
+
function applySilenceThreshold(dataPoints, threshold) {
|
|
9
|
+
return dataPoints.map((p) => ({
|
|
10
|
+
...p,
|
|
11
|
+
silent: p.rms < threshold,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
const SMALL_TOTAL_INSTANT_THRESHOLD = 50;
|
|
15
|
+
const PROGRESSIVE_BATCH_DELAY_MS = 30;
|
|
16
|
+
const PROGRESSIVE_BATCH_COUNT = 8;
|
|
17
|
+
/**
|
|
18
|
+
* Schedule progressive emission of points after the native one-shot resolve.
|
|
19
|
+
* Native progressive streaming is a future enhancement; today the points are
|
|
20
|
+
* micro-batched on the JS side so consumers (and the agentic recipe runner)
|
|
21
|
+
* can observe an in-flight `pointsReceived < totalPoints` window.
|
|
22
|
+
*/
|
|
23
|
+
function emitPointsProgressively(dataPoints, onPointReady, signal, logger) {
|
|
24
|
+
const total = dataPoints.length;
|
|
25
|
+
if (total === 0)
|
|
26
|
+
return;
|
|
27
|
+
const safeEmit = (point, index) => {
|
|
28
|
+
if (signal?.aborted)
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
onPointReady(point, index, total);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
// Swallow callback errors so a buggy consumer cannot break extraction.
|
|
35
|
+
logger?.warn?.('extractPreview onPointReady callback failed', err);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
if (signal?.aborted)
|
|
39
|
+
return;
|
|
40
|
+
if (total <= SMALL_TOTAL_INSTANT_THRESHOLD) {
|
|
41
|
+
for (let i = 0; i < total; i++)
|
|
42
|
+
safeEmit(dataPoints[i], i);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// First quarter flushes immediately so the UI shows something within a frame.
|
|
46
|
+
const firstFlushCount = Math.max(1, Math.floor(total / 4));
|
|
47
|
+
for (let i = 0; i < firstFlushCount; i++)
|
|
48
|
+
safeEmit(dataPoints[i], i);
|
|
49
|
+
if (firstFlushCount >= total)
|
|
50
|
+
return;
|
|
51
|
+
const remaining = total - firstFlushCount;
|
|
52
|
+
const batchSize = Math.max(1, Math.ceil(remaining / PROGRESSIVE_BATCH_COUNT));
|
|
53
|
+
let cursor = firstFlushCount;
|
|
54
|
+
const pump = () => {
|
|
55
|
+
if (signal?.aborted)
|
|
56
|
+
return;
|
|
57
|
+
const end = Math.min(total, cursor + batchSize);
|
|
58
|
+
for (let i = cursor; i < end; i++)
|
|
59
|
+
safeEmit(dataPoints[i], i);
|
|
60
|
+
cursor = end;
|
|
61
|
+
if (cursor < total) {
|
|
62
|
+
setTimeout(pump, PROGRESSIVE_BATCH_DELAY_MS);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
setTimeout(pump, PROGRESSIVE_BATCH_DELAY_MS);
|
|
66
|
+
}
|
|
2
67
|
/**
|
|
3
68
|
* Generates a simplified preview of the audio waveform for quick visualization.
|
|
4
69
|
* Ideal for UI rendering with a specified number of points.
|
|
5
70
|
*
|
|
6
71
|
* @param options - The options for the preview, including file URI and time range.
|
|
7
72
|
* @returns A promise that resolves to the audio preview data.
|
|
73
|
+
* @throws {AudioExtractionError} when the underlying extraction fails.
|
|
8
74
|
*/
|
|
9
|
-
export async function extractPreview({ fileUri, numberOfPoints = 100, startTimeMs = 0, endTimeMs = 30000,
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
75
|
+
export async function extractPreview({ fileUri, numberOfPoints = 100, startTimeMs = 0, endTimeMs = 30000, decodingOptions, logger, onPointReady, signal, }) {
|
|
76
|
+
const durationMs = Math.max(1, endTimeMs - startTimeMs);
|
|
77
|
+
const segmentDurationMs = Math.max(1, Math.floor(durationMs / numberOfPoints));
|
|
78
|
+
let analysis;
|
|
79
|
+
try {
|
|
80
|
+
analysis = await extractAudioAnalysis({
|
|
81
|
+
fileUri,
|
|
82
|
+
startTimeMs,
|
|
83
|
+
endTimeMs,
|
|
84
|
+
logger,
|
|
85
|
+
segmentDurationMs,
|
|
86
|
+
decodingOptions,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
throw mapExtractionError(err, fileUri);
|
|
91
|
+
}
|
|
92
|
+
const threshold = decodingOptions?.silenceRmsThreshold ?? DEFAULT_SILENCE_THRESHOLD;
|
|
93
|
+
const adjusted = {
|
|
94
|
+
...analysis,
|
|
95
|
+
dataPoints: applySilenceThreshold(analysis.dataPoints, threshold),
|
|
96
|
+
};
|
|
97
|
+
if (onPointReady) {
|
|
98
|
+
emitPointsProgressively(adjusted.dataPoints, onPointReady, signal, logger);
|
|
99
|
+
}
|
|
100
|
+
return adjusted;
|
|
24
101
|
}
|
|
25
102
|
//# sourceMappingURL=extractPreview.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractPreview.js","sourceRoot":"","sources":["../../../src/AudioAnalysis/extractPreview.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"extractPreview.js","sourceRoot":"","sources":["../../../src/AudioAnalysis/extractPreview.ts"],"names":[],"mappings":"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","sourcesContent":["import { mapExtractionError } from '../errors/AudioExtractionError'\nimport { PreviewOptions, AudioAnalysis, DataPoint } from './AudioAnalysis.types'\nimport { extractAudioAnalysis } from './extractAudioAnalysis'\n\nconst DEFAULT_SILENCE_THRESHOLD = 0.01\n\n/**\n * Apply a silence threshold to the data points by recomputing the `silent` flag from rms.\n * Returns a new array (does not mutate the source).\n */\nfunction applySilenceThreshold(\n dataPoints: DataPoint[],\n threshold: number\n): DataPoint[] {\n return dataPoints.map((p) => ({\n ...p,\n silent: p.rms < threshold,\n }))\n}\n\nconst SMALL_TOTAL_INSTANT_THRESHOLD = 50\nconst PROGRESSIVE_BATCH_DELAY_MS = 30\nconst PROGRESSIVE_BATCH_COUNT = 8\n\n/**\n * Schedule progressive emission of points after the native one-shot resolve.\n * Native progressive streaming is a future enhancement; today the points are\n * micro-batched on the JS side so consumers (and the agentic recipe runner)\n * can observe an in-flight `pointsReceived < totalPoints` window.\n */\nfunction emitPointsProgressively(\n dataPoints: DataPoint[],\n onPointReady: NonNullable<PreviewOptions['onPointReady']>,\n signal?: PreviewOptions['signal'],\n logger?: PreviewOptions['logger']\n): void {\n const total = dataPoints.length\n if (total === 0) return\n\n const safeEmit = (point: DataPoint, index: number) => {\n if (signal?.aborted) return\n try {\n onPointReady(point, index, total)\n } catch (err) {\n // Swallow callback errors so a buggy consumer cannot break extraction.\n logger?.warn?.('extractPreview onPointReady callback failed', err)\n }\n }\n\n if (signal?.aborted) return\n if (total <= SMALL_TOTAL_INSTANT_THRESHOLD) {\n for (let i = 0; i < total; i++) safeEmit(dataPoints[i], i)\n return\n }\n\n // First quarter flushes immediately so the UI shows something within a frame.\n const firstFlushCount = Math.max(1, Math.floor(total / 4))\n for (let i = 0; i < firstFlushCount; i++) safeEmit(dataPoints[i], i)\n\n if (firstFlushCount >= total) return\n\n const remaining = total - firstFlushCount\n const batchSize = Math.max(\n 1,\n Math.ceil(remaining / PROGRESSIVE_BATCH_COUNT)\n )\n let cursor = firstFlushCount\n const pump = () => {\n if (signal?.aborted) return\n const end = Math.min(total, cursor + batchSize)\n for (let i = cursor; i < end; i++) safeEmit(dataPoints[i], i)\n cursor = end\n if (cursor < total) {\n setTimeout(pump, PROGRESSIVE_BATCH_DELAY_MS)\n }\n }\n setTimeout(pump, PROGRESSIVE_BATCH_DELAY_MS)\n}\n\n/**\n * Generates a simplified preview of the audio waveform for quick visualization.\n * Ideal for UI rendering with a specified number of points.\n *\n * @param options - The options for the preview, including file URI and time range.\n * @returns A promise that resolves to the audio preview data.\n * @throws {AudioExtractionError} when the underlying extraction fails.\n */\nexport async function extractPreview({\n fileUri,\n numberOfPoints = 100,\n startTimeMs = 0,\n endTimeMs = 30000,\n decodingOptions,\n logger,\n onPointReady,\n signal,\n}: PreviewOptions): Promise<AudioAnalysis> {\n const durationMs = Math.max(1, endTimeMs - startTimeMs)\n const segmentDurationMs = Math.max(\n 1,\n Math.floor(durationMs / numberOfPoints)\n )\n\n let analysis: AudioAnalysis\n try {\n analysis = await extractAudioAnalysis({\n fileUri,\n startTimeMs,\n endTimeMs,\n logger,\n segmentDurationMs,\n decodingOptions,\n })\n } catch (err) {\n throw mapExtractionError(err, fileUri)\n }\n\n const threshold =\n decodingOptions?.silenceRmsThreshold ?? DEFAULT_SILENCE_THRESHOLD\n const adjusted: AudioAnalysis = {\n ...analysis,\n dataPoints: applySilenceThreshold(analysis.dataPoints, threshold),\n }\n\n if (onPointReady) {\n emitPointsProgressively(\n adjusted.dataPoints,\n onPointReady,\n signal,\n logger\n )\n }\n\n return adjusted\n}\n"]}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { extractPreview } from './extractPreview';
|
|
2
|
+
import AudioStudioModule from '../AudioStudioModule';
|
|
3
|
+
import { mapExtractionError } from '../errors/AudioExtractionError';
|
|
4
|
+
import { cleanNativeOptions } from '../utils/cleanNativeOptions';
|
|
5
|
+
const DEFAULT_PREVIEW_BARS = 100;
|
|
6
|
+
const DEFAULT_PREVIEW_END_TIME_MS = 30000;
|
|
7
|
+
const DEFAULT_SILENCE_THRESHOLD = 0.01;
|
|
8
|
+
function hasNativePreviewBars(module) {
|
|
9
|
+
return (module !== null &&
|
|
10
|
+
(typeof module === 'object' || typeof module === 'function') &&
|
|
11
|
+
typeof module.extractPreviewBars ===
|
|
12
|
+
'function');
|
|
13
|
+
}
|
|
14
|
+
function clamp01(value) {
|
|
15
|
+
if (!Number.isFinite(value))
|
|
16
|
+
return 0;
|
|
17
|
+
return Math.max(0, Math.min(1, value));
|
|
18
|
+
}
|
|
19
|
+
function getPointTimeMs(value, fallbackMs) {
|
|
20
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
21
|
+
? Math.round(value)
|
|
22
|
+
: fallbackMs;
|
|
23
|
+
}
|
|
24
|
+
function pointToPreviewBar(point, index, fallbackBarDurationMs, silenceRmsThreshold) {
|
|
25
|
+
const fallbackStartTimeMs = Math.round(index * fallbackBarDurationMs);
|
|
26
|
+
const fallbackEndTimeMs = Math.round((index + 1) * fallbackBarDurationMs);
|
|
27
|
+
const startTimeMs = getPointTimeMs(point.startTime, fallbackStartTimeMs);
|
|
28
|
+
const endTimeMs = getPointTimeMs(point.endTime, fallbackEndTimeMs);
|
|
29
|
+
const rms = clamp01(point.rms);
|
|
30
|
+
return {
|
|
31
|
+
id: point.id ?? index,
|
|
32
|
+
amplitude: clamp01(point.amplitude),
|
|
33
|
+
rms,
|
|
34
|
+
silent: point.silent ?? rms < silenceRmsThreshold,
|
|
35
|
+
startTimeMs,
|
|
36
|
+
endTimeMs: Math.max(startTimeMs, endTimeMs),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function calculateRange(values) {
|
|
40
|
+
if (values.length === 0) {
|
|
41
|
+
return { min: 0, max: 0 };
|
|
42
|
+
}
|
|
43
|
+
let min = Number.POSITIVE_INFINITY;
|
|
44
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
45
|
+
for (const value of values) {
|
|
46
|
+
const safeValue = clamp01(value);
|
|
47
|
+
min = Math.min(min, safeValue);
|
|
48
|
+
max = Math.max(max, safeValue);
|
|
49
|
+
}
|
|
50
|
+
return { min, max };
|
|
51
|
+
}
|
|
52
|
+
function fromAudioAnalysis(analysis, requestedNumberOfBars, silenceRmsThreshold) {
|
|
53
|
+
const barDurationMs = analysis.segmentDurationMs ||
|
|
54
|
+
Math.max(1, analysis.durationMs / Math.max(1, analysis.dataPoints.length));
|
|
55
|
+
const bars = analysis.dataPoints.map((point, index) => pointToPreviewBar(point, index, barDurationMs, silenceRmsThreshold));
|
|
56
|
+
return {
|
|
57
|
+
bars,
|
|
58
|
+
durationMs: analysis.durationMs,
|
|
59
|
+
sampleRate: analysis.sampleRate,
|
|
60
|
+
numberOfChannels: analysis.numberOfChannels,
|
|
61
|
+
bitDepth: analysis.bitDepth,
|
|
62
|
+
samples: analysis.samples,
|
|
63
|
+
requestedNumberOfBars,
|
|
64
|
+
barDurationMs,
|
|
65
|
+
amplitudeRange: calculateRange(bars.map((bar) => bar.amplitude)),
|
|
66
|
+
rmsRange: calculateRange(bars.map((bar) => bar.rms)),
|
|
67
|
+
extractionTimeMs: analysis.extractionTimeMs,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function emitBarsProgressively(bars, onBarReady) {
|
|
71
|
+
const total = bars.length;
|
|
72
|
+
for (let index = 0; index < total; index++) {
|
|
73
|
+
onBarReady(bars[index], index, total);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Extracts compact waveform preview bars for UI rendering.
|
|
78
|
+
*
|
|
79
|
+
* Native platforms may provide a compact `extractPreviewBars` bridge. Until that
|
|
80
|
+
* bridge is available, this safely falls back to the existing `extractPreview`
|
|
81
|
+
* compatibility path and adapts `DataPoint` objects into compact bars.
|
|
82
|
+
*
|
|
83
|
+
* @throws {AudioExtractionError} when the underlying extraction fails.
|
|
84
|
+
*/
|
|
85
|
+
export async function extractPreviewBars({ fileUri, numberOfBars = DEFAULT_PREVIEW_BARS, startTimeMs = 0, endTimeMs = DEFAULT_PREVIEW_END_TIME_MS, decodingOptions, logger, onBarReady, }) {
|
|
86
|
+
const requestedNumberOfBars = Math.max(1, Math.floor(numberOfBars));
|
|
87
|
+
const nativeOptions = {
|
|
88
|
+
fileUri,
|
|
89
|
+
numberOfBars: requestedNumberOfBars,
|
|
90
|
+
startTimeMs,
|
|
91
|
+
endTimeMs,
|
|
92
|
+
decodingOptions,
|
|
93
|
+
};
|
|
94
|
+
const nativeModule = AudioStudioModule;
|
|
95
|
+
if (hasNativePreviewBars(nativeModule)) {
|
|
96
|
+
let result;
|
|
97
|
+
try {
|
|
98
|
+
result = await nativeModule.extractPreviewBars(cleanNativeOptions(nativeOptions));
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
throw mapExtractionError(err, fileUri);
|
|
102
|
+
}
|
|
103
|
+
if (onBarReady) {
|
|
104
|
+
emitBarsProgressively(result.bars, onBarReady);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
let analysis;
|
|
109
|
+
try {
|
|
110
|
+
analysis = await extractPreview({
|
|
111
|
+
fileUri,
|
|
112
|
+
numberOfPoints: requestedNumberOfBars,
|
|
113
|
+
startTimeMs,
|
|
114
|
+
endTimeMs,
|
|
115
|
+
decodingOptions,
|
|
116
|
+
logger,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
throw mapExtractionError(err, fileUri);
|
|
121
|
+
}
|
|
122
|
+
const result = fromAudioAnalysis(analysis, requestedNumberOfBars, decodingOptions?.silenceRmsThreshold ?? DEFAULT_SILENCE_THRESHOLD);
|
|
123
|
+
if (onBarReady) {
|
|
124
|
+
emitBarsProgressively(result.bars, onBarReady);
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=extractPreviewBars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractPreviewBars.js","sourceRoot":"","sources":["../../../src/AudioAnalysis/extractPreviewBars.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AudioAnalysis,\n DataPoint,\n PreviewBar,\n PreviewBarsOptions,\n PreviewBarsResult,\n} from './AudioAnalysis.types'\nimport { extractPreview } from './extractPreview'\nimport AudioStudioModule from '../AudioStudioModule'\nimport { mapExtractionError } from '../errors/AudioExtractionError'\nimport { cleanNativeOptions } from '../utils/cleanNativeOptions'\n\nconst DEFAULT_PREVIEW_BARS = 100\nconst DEFAULT_PREVIEW_END_TIME_MS = 30000\nconst DEFAULT_SILENCE_THRESHOLD = 0.01\n\ninterface NativePreviewBarsModule {\n extractPreviewBars: (\n options: Record<string, unknown>\n ) => Promise<PreviewBarsResult>\n}\n\nfunction hasNativePreviewBars(\n module: unknown\n): module is NativePreviewBarsModule {\n return (\n module !== null &&\n (typeof module === 'object' || typeof module === 'function') &&\n typeof (module as NativePreviewBarsModule).extractPreviewBars ===\n 'function'\n )\n}\n\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value)) return 0\n return Math.max(0, Math.min(1, value))\n}\n\nfunction getPointTimeMs(value: number | undefined, fallbackMs: number): number {\n return typeof value === 'number' && Number.isFinite(value)\n ? Math.round(value)\n : fallbackMs\n}\n\nfunction pointToPreviewBar(\n point: DataPoint,\n index: number,\n fallbackBarDurationMs: number,\n silenceRmsThreshold: number\n): PreviewBar {\n const fallbackStartTimeMs = Math.round(index * fallbackBarDurationMs)\n const fallbackEndTimeMs = Math.round((index + 1) * fallbackBarDurationMs)\n const startTimeMs = getPointTimeMs(point.startTime, fallbackStartTimeMs)\n const endTimeMs = getPointTimeMs(point.endTime, fallbackEndTimeMs)\n const rms = clamp01(point.rms)\n\n return {\n id: point.id ?? index,\n amplitude: clamp01(point.amplitude),\n rms,\n silent: point.silent ?? rms < silenceRmsThreshold,\n startTimeMs,\n endTimeMs: Math.max(startTimeMs, endTimeMs),\n }\n}\n\nfunction calculateRange(values: number[]): { min: number; max: number } {\n if (values.length === 0) {\n return { min: 0, max: 0 }\n }\n\n let min = Number.POSITIVE_INFINITY\n let max = Number.NEGATIVE_INFINITY\n\n for (const value of values) {\n const safeValue = clamp01(value)\n min = Math.min(min, safeValue)\n max = Math.max(max, safeValue)\n }\n\n return { min, max }\n}\n\nfunction fromAudioAnalysis(\n analysis: AudioAnalysis,\n requestedNumberOfBars: number,\n silenceRmsThreshold: number\n): PreviewBarsResult {\n const barDurationMs =\n analysis.segmentDurationMs ||\n Math.max(\n 1,\n analysis.durationMs / Math.max(1, analysis.dataPoints.length)\n )\n const bars = analysis.dataPoints.map((point, index) =>\n pointToPreviewBar(point, index, barDurationMs, silenceRmsThreshold)\n )\n\n return {\n bars,\n durationMs: analysis.durationMs,\n sampleRate: analysis.sampleRate,\n numberOfChannels: analysis.numberOfChannels,\n bitDepth: analysis.bitDepth,\n samples: analysis.samples,\n requestedNumberOfBars,\n barDurationMs,\n amplitudeRange: calculateRange(bars.map((bar) => bar.amplitude)),\n rmsRange: calculateRange(bars.map((bar) => bar.rms)),\n extractionTimeMs: analysis.extractionTimeMs,\n }\n}\n\nfunction emitBarsProgressively(\n bars: PreviewBar[],\n onBarReady: NonNullable<PreviewBarsOptions['onBarReady']>\n): void {\n const total = bars.length\n for (let index = 0; index < total; index++) {\n onBarReady(bars[index], index, total)\n }\n}\n\n/**\n * Extracts compact waveform preview bars for UI rendering.\n *\n * Native platforms may provide a compact `extractPreviewBars` bridge. Until that\n * bridge is available, this safely falls back to the existing `extractPreview`\n * compatibility path and adapts `DataPoint` objects into compact bars.\n *\n * @throws {AudioExtractionError} when the underlying extraction fails.\n */\nexport async function extractPreviewBars({\n fileUri,\n numberOfBars = DEFAULT_PREVIEW_BARS,\n startTimeMs = 0,\n endTimeMs = DEFAULT_PREVIEW_END_TIME_MS,\n decodingOptions,\n logger,\n onBarReady,\n}: PreviewBarsOptions): Promise<PreviewBarsResult> {\n const requestedNumberOfBars = Math.max(1, Math.floor(numberOfBars))\n const nativeOptions = {\n fileUri,\n numberOfBars: requestedNumberOfBars,\n startTimeMs,\n endTimeMs,\n decodingOptions,\n }\n\n const nativeModule = AudioStudioModule as unknown\n if (hasNativePreviewBars(nativeModule)) {\n let result: PreviewBarsResult\n try {\n result = await nativeModule.extractPreviewBars(\n cleanNativeOptions(nativeOptions)\n )\n } catch (err) {\n throw mapExtractionError(err, fileUri)\n }\n\n if (onBarReady) {\n emitBarsProgressively(result.bars, onBarReady)\n }\n return result\n }\n\n let analysis: AudioAnalysis\n try {\n analysis = await extractPreview({\n fileUri,\n numberOfPoints: requestedNumberOfBars,\n startTimeMs,\n endTimeMs,\n decodingOptions,\n logger,\n })\n } catch (err) {\n throw mapExtractionError(err, fileUri)\n }\n\n const result = fromAudioAnalysis(\n analysis,\n requestedNumberOfBars,\n decodingOptions?.silenceRmsThreshold ?? DEFAULT_SILENCE_THRESHOLD\n )\n\n if (onBarReady) {\n emitBarsProgressively(result.bars, onBarReady)\n }\n\n return result\n}\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
export class AudioExtractionError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
nativeMessage;
|
|
4
|
+
fileUri;
|
|
5
|
+
constructor(payload) {
|
|
6
|
+
super(payload.message);
|
|
7
|
+
this.name = 'AudioExtractionError';
|
|
8
|
+
this.code = payload.code;
|
|
9
|
+
this.nativeMessage = payload.nativeMessage;
|
|
10
|
+
this.fileUri = payload.fileUri;
|
|
11
|
+
}
|
|
12
|
+
toJSON() {
|
|
13
|
+
return {
|
|
14
|
+
code: this.code,
|
|
15
|
+
message: this.message,
|
|
16
|
+
nativeMessage: this.nativeMessage,
|
|
17
|
+
fileUri: this.fileUri,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function getNativeMessage(err) {
|
|
22
|
+
if (err instanceof Error)
|
|
23
|
+
return err.message;
|
|
24
|
+
if (typeof err === 'string')
|
|
25
|
+
return err;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(err) ?? String(err);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return String(err);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getNativeCode(err) {
|
|
34
|
+
if (err && typeof err === 'object' && 'code' in err) {
|
|
35
|
+
const code = err.code;
|
|
36
|
+
if (typeof code === 'string')
|
|
37
|
+
return code;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
function mapNativeCode(code) {
|
|
42
|
+
if (!code)
|
|
43
|
+
return null;
|
|
44
|
+
const normalized = code.toUpperCase();
|
|
45
|
+
if (normalized.includes('FILE_NOT_FOUND') ||
|
|
46
|
+
normalized === 'ENOENT' ||
|
|
47
|
+
normalized.includes('NO_SUCH_FILE')) {
|
|
48
|
+
return 'file_not_found';
|
|
49
|
+
}
|
|
50
|
+
if (normalized.includes('PERMISSION') ||
|
|
51
|
+
normalized === 'EACCES' ||
|
|
52
|
+
normalized.includes('NOT_AUTHORIZED')) {
|
|
53
|
+
return 'permission_denied';
|
|
54
|
+
}
|
|
55
|
+
if (normalized.includes('UNSUPPORTED') ||
|
|
56
|
+
normalized.includes('NO_SUITABLE_CODEC')) {
|
|
57
|
+
return 'unsupported_codec';
|
|
58
|
+
}
|
|
59
|
+
if (normalized.includes('INVALID_RANGE') ||
|
|
60
|
+
normalized.includes('INVALID_HEADER') ||
|
|
61
|
+
normalized.includes('MALFORMED') ||
|
|
62
|
+
normalized.includes('CORRUPT')) {
|
|
63
|
+
return 'malformed_file';
|
|
64
|
+
}
|
|
65
|
+
if (normalized.includes('PROCESSING_ERROR') ||
|
|
66
|
+
normalized.includes('AUDIO_READ_ERROR') ||
|
|
67
|
+
normalized.includes('DECODE')) {
|
|
68
|
+
return 'decode_failed';
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Map a thrown native/JS value into an AudioExtractionError with a stable code.
|
|
74
|
+
* Heuristics inspect message text and known native error codes.
|
|
75
|
+
*/
|
|
76
|
+
export function mapExtractionError(err, fileUri) {
|
|
77
|
+
if (err instanceof AudioExtractionError)
|
|
78
|
+
return err;
|
|
79
|
+
const nativeMessage = getNativeMessage(err);
|
|
80
|
+
const lower = nativeMessage.toLowerCase();
|
|
81
|
+
let code = mapNativeCode(getNativeCode(err)) ?? 'unknown';
|
|
82
|
+
if (code === 'unknown' &&
|
|
83
|
+
(lower.includes('unsupported') ||
|
|
84
|
+
lower.includes('not supported') ||
|
|
85
|
+
lower.includes('no suitable codec') ||
|
|
86
|
+
lower.includes('no track'))) {
|
|
87
|
+
code = 'unsupported_codec';
|
|
88
|
+
}
|
|
89
|
+
else if (code === 'unknown' &&
|
|
90
|
+
(lower.includes('not found') ||
|
|
91
|
+
lower.includes('no such file') ||
|
|
92
|
+
lower.includes('does not exist'))) {
|
|
93
|
+
code = 'file_not_found';
|
|
94
|
+
}
|
|
95
|
+
else if (code === 'unknown' &&
|
|
96
|
+
(lower.includes('permission') ||
|
|
97
|
+
lower.includes('denied') ||
|
|
98
|
+
lower.includes('not authorized'))) {
|
|
99
|
+
code = 'permission_denied';
|
|
100
|
+
}
|
|
101
|
+
else if (code === 'unknown' &&
|
|
102
|
+
(lower.includes('malformed') ||
|
|
103
|
+
lower.includes('corrupt') ||
|
|
104
|
+
lower.includes('invalid header') ||
|
|
105
|
+
lower.includes('invalid wav'))) {
|
|
106
|
+
code = 'malformed_file';
|
|
107
|
+
}
|
|
108
|
+
else if (code === 'unknown' &&
|
|
109
|
+
(lower.includes('decode') ||
|
|
110
|
+
lower.includes('codec') ||
|
|
111
|
+
lower.includes('mediaextractor') ||
|
|
112
|
+
lower.includes('avaudio'))) {
|
|
113
|
+
code = 'decode_failed';
|
|
114
|
+
}
|
|
115
|
+
return new AudioExtractionError({
|
|
116
|
+
code,
|
|
117
|
+
message: `Audio extraction failed (${code}): ${nativeMessage}`,
|
|
118
|
+
nativeMessage,
|
|
119
|
+
fileUri,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=AudioExtractionError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioExtractionError.js","sourceRoot":"","sources":["../../../src/errors/AudioExtractionError.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Typed error class for audio extraction failures.\n * Wraps native module errors with stable codes consumers can switch on.\n */\nexport type AudioExtractionErrorCode =\n | 'unsupported_codec'\n | 'malformed_file'\n | 'decode_failed'\n | 'permission_denied'\n | 'file_not_found'\n | 'unknown'\n\nexport interface AudioExtractionErrorPayload {\n code: AudioExtractionErrorCode\n message: string\n nativeMessage?: string\n fileUri?: string\n}\n\nexport class AudioExtractionError extends Error {\n readonly code: AudioExtractionErrorCode\n readonly nativeMessage?: string\n readonly fileUri?: string\n\n constructor(payload: AudioExtractionErrorPayload) {\n super(payload.message)\n this.name = 'AudioExtractionError'\n this.code = payload.code\n this.nativeMessage = payload.nativeMessage\n this.fileUri = payload.fileUri\n }\n\n toJSON(): AudioExtractionErrorPayload {\n return {\n code: this.code,\n message: this.message,\n nativeMessage: this.nativeMessage,\n fileUri: this.fileUri,\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\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 mapNativeCode(\n code: string | undefined\n): AudioExtractionErrorCode | null {\n if (!code) return null\n\n const normalized = code.toUpperCase()\n if (\n normalized.includes('FILE_NOT_FOUND') ||\n normalized === 'ENOENT' ||\n normalized.includes('NO_SUCH_FILE')\n ) {\n return 'file_not_found'\n }\n if (\n normalized.includes('PERMISSION') ||\n normalized === 'EACCES' ||\n normalized.includes('NOT_AUTHORIZED')\n ) {\n return 'permission_denied'\n }\n if (\n normalized.includes('UNSUPPORTED') ||\n normalized.includes('NO_SUITABLE_CODEC')\n ) {\n return 'unsupported_codec'\n }\n if (\n normalized.includes('INVALID_RANGE') ||\n normalized.includes('INVALID_HEADER') ||\n normalized.includes('MALFORMED') ||\n normalized.includes('CORRUPT')\n ) {\n return 'malformed_file'\n }\n if (\n normalized.includes('PROCESSING_ERROR') ||\n normalized.includes('AUDIO_READ_ERROR') ||\n normalized.includes('DECODE')\n ) {\n return 'decode_failed'\n }\n\n return null\n}\n\n/**\n * Map a thrown native/JS value into an AudioExtractionError with a stable code.\n * Heuristics inspect message text and known native error codes.\n */\nexport function mapExtractionError(\n err: unknown,\n fileUri?: string\n): AudioExtractionError {\n if (err instanceof AudioExtractionError) return err\n\n const nativeMessage = getNativeMessage(err)\n const lower = nativeMessage.toLowerCase()\n\n let code = mapNativeCode(getNativeCode(err)) ?? 'unknown'\n if (\n code === 'unknown' &&\n (lower.includes('unsupported') ||\n lower.includes('not supported') ||\n lower.includes('no suitable codec') ||\n lower.includes('no track'))\n ) {\n code = 'unsupported_codec'\n } else if (\n code === 'unknown' &&\n (lower.includes('not found') ||\n lower.includes('no such file') ||\n lower.includes('does not exist'))\n ) {\n code = 'file_not_found'\n } else if (\n code === 'unknown' &&\n (lower.includes('permission') ||\n lower.includes('denied') ||\n lower.includes('not authorized'))\n ) {\n code = 'permission_denied'\n } else if (\n code === 'unknown' &&\n (lower.includes('malformed') ||\n lower.includes('corrupt') ||\n lower.includes('invalid header') ||\n lower.includes('invalid wav'))\n ) {\n code = 'malformed_file'\n } else if (\n code === 'unknown' &&\n (lower.includes('decode') ||\n lower.includes('codec') ||\n lower.includes('mediaextractor') ||\n lower.includes('avaudio'))\n ) {\n code = 'decode_failed'\n }\n\n return new AudioExtractionError({\n code,\n message: `Audio extraction failed (${code}): ${nativeMessage}`,\n nativeMessage,\n fileUri,\n })\n}\n"]}
|
|
@@ -0,0 +1,147 @@
|
|
|
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 normalizeCode(raw) {
|
|
57
|
+
if (!raw)
|
|
58
|
+
return null;
|
|
59
|
+
const upper = raw.toUpperCase();
|
|
60
|
+
if (upper.startsWith('ERR_AUDIO_STREAM_')) {
|
|
61
|
+
const known = [
|
|
62
|
+
'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT',
|
|
63
|
+
'ERR_AUDIO_STREAM_INVALID_RANGE',
|
|
64
|
+
'ERR_AUDIO_STREAM_DECODE_FAILED',
|
|
65
|
+
'ERR_AUDIO_STREAM_CANCELLED',
|
|
66
|
+
'ERR_AUDIO_STREAM_PERMISSION_DENIED',
|
|
67
|
+
'ERR_AUDIO_STREAM_FILE_NOT_FOUND',
|
|
68
|
+
'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT',
|
|
69
|
+
'ERR_AUDIO_STREAM_NATIVE_UNAVAILABLE',
|
|
70
|
+
'ERR_AUDIO_STREAM_BUSY',
|
|
71
|
+
'ERR_AUDIO_STREAM_UNKNOWN',
|
|
72
|
+
];
|
|
73
|
+
if (known.includes(upper)) {
|
|
74
|
+
return upper;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (upper.includes('FILE_NOT_FOUND') || upper === 'ENOENT') {
|
|
78
|
+
return 'ERR_AUDIO_STREAM_FILE_NOT_FOUND';
|
|
79
|
+
}
|
|
80
|
+
if (upper.includes('PERMISSION') || upper === 'EACCES') {
|
|
81
|
+
return 'ERR_AUDIO_STREAM_PERMISSION_DENIED';
|
|
82
|
+
}
|
|
83
|
+
if (upper.includes('UNSUPPORTED') ||
|
|
84
|
+
upper.includes('NO_SUITABLE_CODEC') ||
|
|
85
|
+
upper.includes('NO SUITABLE CODEC') ||
|
|
86
|
+
upper.includes('NOT SUPPORTED')) {
|
|
87
|
+
return 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT';
|
|
88
|
+
}
|
|
89
|
+
if (upper.includes('INVALID_RANGE') ||
|
|
90
|
+
upper.includes('OUT_OF_RANGE') ||
|
|
91
|
+
upper.includes('INVALID_TIME')) {
|
|
92
|
+
return 'ERR_AUDIO_STREAM_INVALID_RANGE';
|
|
93
|
+
}
|
|
94
|
+
if (upper.includes('CANCELLED') || upper.includes('CANCELED')) {
|
|
95
|
+
return 'ERR_AUDIO_STREAM_CANCELLED';
|
|
96
|
+
}
|
|
97
|
+
if (upper.includes('BUSY')) {
|
|
98
|
+
return 'ERR_AUDIO_STREAM_BUSY';
|
|
99
|
+
}
|
|
100
|
+
if (upper.includes('BACKPRESSURE')) {
|
|
101
|
+
return 'ERR_AUDIO_STREAM_BACKPRESSURE_TIMEOUT';
|
|
102
|
+
}
|
|
103
|
+
if (upper.includes('DECODE') ||
|
|
104
|
+
upper.includes('CODEC') ||
|
|
105
|
+
upper.includes('MALFORMED')) {
|
|
106
|
+
return 'ERR_AUDIO_STREAM_DECODE_FAILED';
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
export function mapStreamError(err, fileUri, platform) {
|
|
111
|
+
if (err instanceof AudioStreamError)
|
|
112
|
+
return err;
|
|
113
|
+
const nativeMessage = getNativeMessage(err);
|
|
114
|
+
const nativeCode = getNativeCode(err);
|
|
115
|
+
const lower = nativeMessage.toLowerCase();
|
|
116
|
+
let code = normalizeCode(nativeCode) ??
|
|
117
|
+
normalizeCode(nativeMessage) ??
|
|
118
|
+
'ERR_AUDIO_STREAM_UNKNOWN';
|
|
119
|
+
if (code === 'ERR_AUDIO_STREAM_UNKNOWN') {
|
|
120
|
+
if (lower.includes('not found') || lower.includes('does not exist')) {
|
|
121
|
+
code = 'ERR_AUDIO_STREAM_FILE_NOT_FOUND';
|
|
122
|
+
}
|
|
123
|
+
else if (lower.includes('unsupported') ||
|
|
124
|
+
lower.includes('no suitable codec')) {
|
|
125
|
+
code = 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT';
|
|
126
|
+
}
|
|
127
|
+
else if (lower.includes('permission') || lower.includes('denied')) {
|
|
128
|
+
code = 'ERR_AUDIO_STREAM_PERMISSION_DENIED';
|
|
129
|
+
}
|
|
130
|
+
else if (lower.includes('decode') || lower.includes('codec')) {
|
|
131
|
+
code = 'ERR_AUDIO_STREAM_DECODE_FAILED';
|
|
132
|
+
}
|
|
133
|
+
else if (lower.includes('cancel')) {
|
|
134
|
+
code = 'ERR_AUDIO_STREAM_CANCELLED';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return new AudioStreamError({
|
|
138
|
+
code,
|
|
139
|
+
message: `Audio stream failed (${code}): ${nativeMessage}`,
|
|
140
|
+
recoverable: RECOVERABLE.includes(code),
|
|
141
|
+
fileUri,
|
|
142
|
+
platform,
|
|
143
|
+
nativeCode,
|
|
144
|
+
nativeMessage,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
//# 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 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 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"]}
|