Do not compute waveform for a long audio
This commit is contained in:
parent
44ecfe4746
commit
b1d49f7b3e
3 changed files with 64 additions and 6 deletions
BIN
fixtures/long-audio.mp3
Normal file
BIN
fixtures/long-audio.mp3
Normal file
Binary file not shown.
|
@ -9,6 +9,7 @@ import { WaveformCache } from '../types/Audio';
|
|||
|
||||
const MAX_WAVEFORM_COUNT = 1000;
|
||||
const MAX_PARALLEL_COMPUTE = 8;
|
||||
const MAX_AUDIO_DURATION = 15 * 60; // 15 minutes
|
||||
|
||||
export type ComputePeaksResult = {
|
||||
duration: number;
|
||||
|
@ -35,6 +36,36 @@ const computeQueue = new PQueue({
|
|||
concurrency: MAX_PARALLEL_COMPUTE,
|
||||
});
|
||||
|
||||
async function getAudioDuration(
|
||||
url: string,
|
||||
buffer: ArrayBuffer
|
||||
): Promise<number> {
|
||||
const blob = new Blob([buffer]);
|
||||
const blobURL = URL.createObjectURL(blob);
|
||||
|
||||
const audio = new Audio();
|
||||
audio.muted = true;
|
||||
audio.src = blobURL;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
audio.addEventListener('loadedmetadata', () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
audio.addEventListener('error', event => {
|
||||
const error = new Error(
|
||||
`Failed to load audio from: ${url} due to error: ${event.type}`
|
||||
);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
if (Number.isNaN(audio.duration)) {
|
||||
throw new Error(`Invalid audio duration for: ${url}`);
|
||||
}
|
||||
return audio.duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load audio from `url`, decode PCM data, and compute RMS peaks for displaying
|
||||
* the waveform.
|
||||
|
@ -61,10 +92,21 @@ async function doComputePeaks(
|
|||
const response = await fetch(url);
|
||||
const raw = await response.arrayBuffer();
|
||||
|
||||
const duration = await getAudioDuration(url, raw);
|
||||
|
||||
const peaks = new Array(barCount).fill(0);
|
||||
if (duration > MAX_AUDIO_DURATION) {
|
||||
window.log.info(
|
||||
`GlobalAudioContext: audio ${url} duration ${duration}s is too long`
|
||||
);
|
||||
const emptyResult = { peaks, duration };
|
||||
waveformCache.set(url, emptyResult);
|
||||
return emptyResult;
|
||||
}
|
||||
|
||||
const data = await audioContext.decodeAudioData(raw);
|
||||
|
||||
// Compute RMS peaks
|
||||
const peaks = new Array(barCount).fill(0);
|
||||
const norms = new Array(barCount).fill(0);
|
||||
|
||||
const samplesPerPeak = data.length / peaks.length;
|
||||
|
@ -94,7 +136,7 @@ async function doComputePeaks(
|
|||
peaks[i] /= max;
|
||||
}
|
||||
|
||||
const result = { peaks, duration: data.duration };
|
||||
const result = { peaks, duration };
|
||||
waveformCache.set(url, result);
|
||||
return result;
|
||||
}
|
||||
|
@ -118,10 +160,11 @@ export async function computePeaks(
|
|||
const promise = computeQueue.add(() => doComputePeaks(url, barCount));
|
||||
|
||||
inProgressMap.set(computeKey, promise);
|
||||
const result = await promise;
|
||||
try {
|
||||
return await promise;
|
||||
} finally {
|
||||
inProgressMap.delete(computeKey);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const globalContents: Contents = {
|
||||
|
|
|
@ -804,6 +804,21 @@ story.add('Audio', () => {
|
|||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('Long Audio', () => {
|
||||
const props = createProps({
|
||||
attachments: [
|
||||
{
|
||||
contentType: AUDIO_MP3,
|
||||
fileName: 'long-audio.mp3',
|
||||
url: '/fixtures/long-audio.mp3',
|
||||
},
|
||||
],
|
||||
status: 'sent',
|
||||
});
|
||||
|
||||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('Audio with Caption', () => {
|
||||
const props = createProps({
|
||||
attachments: [
|
||||
|
|
Loading…
Add table
Reference in a new issue