adressing Rabbits comments

This commit is contained in:
Alan Trebugeais
2026-05-09 20:55:41 +02:00
parent 19069cfa32
commit 327d80963c
5 changed files with 60 additions and 39 deletions
@@ -141,7 +141,7 @@ export function AnnotationSettingsPanel({
return (
<div className="flex-[2] min-w-0 bg-editor-panel border border-foreground/10 rounded-2xl flex flex-col shadow-xl h-full overflow-hidden">
<div className="flex-1 min-h-0 p-4 overflow-y-auto custom-scrollbar">
<div className="mb-6">
<div className="mb-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-foreground">
{t("annotations.settings")}
@@ -786,6 +786,7 @@ export function AnnotationSettingsPanel({
<li>{t("annotations.tipCycleBackward")}</li>
</ul>
</div>
</div>
</div>
<div className="flex-shrink-0 border-t border-foreground/10 bg-editor-panel p-4 pt-3">
<Button
@@ -798,7 +799,6 @@ export function AnnotationSettingsPanel({
{t("annotations.deleteAnnotation")}
</Button>
</div>
</div>
</div>
);
}
@@ -1,14 +1,13 @@
import { mapSourceTimeToTimelineTime } from "../types";
import { getClipSourceEndMs, sortClipRegions } from "../types";
import type { ClipRegion } from "../types";
export function getActiveClipIdAtSourceTime(
sourceTimeSeconds: number,
clipRegions: ClipRegion[],
): string | null {
const sourceMs = sourceTimeSeconds * 1000;
const timelineMs = mapSourceTimeToTimelineTime(sourceMs, clipRegions);
const activeClip = clipRegions.find(
(clip) => timelineMs >= clip.startMs && timelineMs < clip.endMs,
const sourceMs = Math.round(sourceTimeSeconds * 1000);
const activeClip = sortClipRegions(clipRegions).find(
(clip) => sourceMs >= clip.startMs && sourceMs < getClipSourceEndMs(clip),
);
return activeClip?.id ?? null;
}
@@ -120,7 +120,9 @@ export function useSourceAudioTrackSettings({
[selectedClipId]: {
...prevClip,
[id]: {
volume: Math.max(0, Math.min(2, volume)),
volume: Number.isFinite(volume)
? Math.max(0, Math.min(2, volume))
: (prevClip[id]?.volume ?? 1),
normalize: prevClip[id]?.normalize ?? false,
},
},
@@ -8,43 +8,52 @@ export class WaveformGenerator {
private peaksCache = new Map<string, AudioPeaksData>();
private pending = new Map<string, Promise<AudioPeaksData>>();
private workerRequestSeq = 0;
private workerResolvers = new Map<number, (peaks: Float32Array) => void>();
private workerResolvers = new Map<number, { resolve: (peaks: Float32Array) => void; reject: (err: Error) => void }>();
constructor() {
this.audioContext = new (window.AudioContext || (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext)();
this.worker = new WorkerConstructor();
this.worker.addEventListener(
"message",
(event: MessageEvent<{ requestId: number; peaks: Float32Array }>) => {
const { requestId, peaks } = event.data;
const resolve = this.workerResolvers.get(requestId);
if (!resolve) return;
(event: MessageEvent<{ requestId: number; peaks?: Float32Array; error?: string }>) => {
const { requestId, peaks, error } = event.data;
const resolver = this.workerResolvers.get(requestId);
if (!resolver) return;
this.workerResolvers.delete(requestId);
resolve(peaks);
if (error) {
resolver.reject(new Error(error));
} else if (peaks) {
resolver.resolve(peaks);
}
},
);
this.worker.addEventListener("error", (error: ErrorEvent) => {
console.error("[WaveformGenerator] Worker fatal error:", error);
const fatalError = error.error ?? new Error(error.message || "Worker crashed");
// Reject all pending requests if the worker itself crashes
for (const resolver of this.workerResolvers.values()) {
resolver.reject(fatalError);
}
this.workerResolvers.clear();
});
}
private computePeaksWithWorker(channelData: Float32Array, samples: number): Promise<Float32Array> {
private computePeaksWithWorker(channels: Float32Array[], samples: number): Promise<Float32Array> {
return new Promise((resolve, reject) => {
const requestId = ++this.workerRequestSeq;
const onError = (error: ErrorEvent) => {
this.worker.removeEventListener("error", onError);
this.workerResolvers.delete(requestId);
reject(error.error ?? new Error(error.message));
};
this.worker.addEventListener("error", onError, { once: true });
this.workerResolvers.set(requestId, (peaks) => {
this.worker.removeEventListener("error", onError);
resolve(peaks);
});
this.workerResolvers.set(requestId, { resolve, reject });
this.worker.postMessage(
{
requestId,
channelData,
channels,
samples,
},
[channelData.buffer],
channels.map(c => c.buffer),
);
});
}
@@ -65,8 +74,14 @@ export class WaveformGenerator {
const arrayBuffer = await response.arrayBuffer();
const decoded = await this.audioContext.decodeAudioData(arrayBuffer);
const channelData = decoded.getChannelData(0).slice();
const peaks = await this.computePeaksWithWorker(channelData, peakCount);
const channels: Float32Array[] = [];
for (let i = 0; i < decoded.numberOfChannels; i++) {
// We slice to transfer the underlying buffer to the worker
channels.push(decoded.getChannelData(i).slice());
}
const peaks = await this.computePeaksWithWorker(channels, peakCount);
let max = 0;
for (let i = 0; i < peaks.length; i++) {
@@ -1,34 +1,39 @@
self.onmessage = (e: MessageEvent) => {
const { requestId, channelData, samples } = e.data as {
const { requestId, channels, samples } = e.data as {
requestId: number;
channelData: Float32Array;
channels: Float32Array[];
samples: number;
};
if (!channelData || samples <= 0) {
if (!channels || channels.length === 0 || samples <= 0) {
const empty = new Float32Array(0);
(self as any).postMessage({ requestId, peaks: empty }, [empty.buffer]);
return;
}
try {
const step = Math.max(1, Math.floor(channelData.length / samples));
const firstChannel = channels[0];
const step = Math.max(1, Math.floor(firstChannel.length / samples));
const result = new Float32Array(samples);
for (let i = 0; i < samples; i++) {
const start = i * step;
const end = Math.min(start + step, channelData.length);
const end = Math.min(start + step, firstChannel.length);
let max = 0;
for (let j = start; j < end; j++) {
const val = Math.abs(channelData[j]);
if (val > max) max = val;
for (let c = 0; c < channels.length; c++) {
const val = Math.abs(channels[c][j]);
if (val > max) max = val;
}
}
result[i] = max;
}
(self as any).postMessage({ requestId, peaks: result }, [result.buffer]);
} catch {
const empty = new Float32Array(0);
(self as any).postMessage({ requestId, peaks: empty }, [empty.buffer]);
} catch (err) {
(self as any).postMessage({
requestId,
error: err instanceof Error ? err.message : "Unknown worker error",
});
}
};