mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 15:25:44 +00:00
adressing Rabbits comments
This commit is contained in:
@@ -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",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user