OpenCut/apps/web/src/services/waveform-cache/service.ts

88 lines
2.0 KiB
TypeScript

"use client";
import { createAudioContext } from "@/media/audio";
import {
buildSourceWaveformSummary,
type SourceWaveformSummary,
} from "@/media/waveform-summary";
interface GetSourceWaveformSummaryArgs {
sourceKey: string;
audioBuffer?: AudioBuffer;
sourceFile?: File;
audioUrl?: string;
}
export class WaveformCache {
private summaries = new Map<string, Promise<SourceWaveformSummary>>();
getSourceSummary({
sourceKey,
audioBuffer,
sourceFile,
audioUrl,
}: GetSourceWaveformSummaryArgs): Promise<SourceWaveformSummary> {
const existing = this.summaries.get(sourceKey);
if (existing) {
return existing;
}
const promise = this.buildSummary({
sourceKey,
audioBuffer,
sourceFile,
audioUrl,
}).catch((error) => {
this.summaries.delete(sourceKey);
throw error;
});
this.summaries.set(sourceKey, promise);
return promise;
}
clearSource({ sourceKey }: { sourceKey: string }): void {
this.summaries.delete(sourceKey);
}
clearAll(): void {
this.summaries.clear();
}
private async buildSummary({
sourceKey,
audioBuffer,
sourceFile,
audioUrl,
}: GetSourceWaveformSummaryArgs): Promise<SourceWaveformSummary> {
if (audioBuffer) {
return buildSourceWaveformSummary({ sourceKey, buffer: audioBuffer });
}
let arrayBuffer: ArrayBuffer | null = null;
if (sourceFile) {
arrayBuffer = await sourceFile.arrayBuffer();
} else if (audioUrl) {
const response = await fetch(audioUrl);
if (!response.ok) {
throw new Error(`Failed to fetch waveform source: ${response.status}`);
}
arrayBuffer = await response.arrayBuffer();
}
if (!arrayBuffer) {
throw new Error(`No waveform source available for ${sourceKey}`);
}
const audioContext = createAudioContext();
try {
const buffer = await audioContext.decodeAudioData(arrayBuffer.slice(0));
return buildSourceWaveformSummary({ sourceKey, buffer });
} finally {
void audioContext.close();
}
}
}
export const waveformCache = new WaveformCache();