mirror of
https://github.com/siddharthvaddem/openscreen.git
synced 2026-11-02 04:54:20 +08:00
fix: add AbortSignal to worker, fix data: URL filename in loadSourceFile
- computePeaksInWorker now accepts an AbortSignal; terminates the worker immediately on abort and rejects with AbortError so orphaned workers cannot outlive the effect cleanup - useAudioPeaks wires an AbortController into the effect; controller.abort() fires alongside the existing cancelled flag on URL change or unmount - loadSourceFile handles data: URLs by parsing the MIME type, deriving a safe filename (e.g. video.mp4), and setting Blob type correctly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9a72f31148
commit
928cabc68d
@@ -10,14 +10,29 @@ function getAudioCtx(): AudioContext {
|
||||
|
||||
/**
|
||||
* Offloads peak computation to a Web Worker (zero-copy via Transferable).
|
||||
* Returns a Promise that resolves with the peaks Float32Array.
|
||||
* Accepts an optional AbortSignal — if aborted, the worker is terminated
|
||||
* immediately and the promise rejects with an AbortError.
|
||||
*/
|
||||
function computePeaksInWorker(audioBuffer: AudioBuffer): Promise<Float32Array> {
|
||||
function computePeaksInWorker(
|
||||
audioBuffer: AudioBuffer,
|
||||
signal?: AbortSignal,
|
||||
): Promise<Float32Array> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (signal?.aborted) {
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
return;
|
||||
}
|
||||
|
||||
const worker = new Worker(new URL("./audioPeaksWorker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
|
||||
const onAbort = () => {
|
||||
worker.terminate();
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
};
|
||||
signal?.addEventListener("abort", onAbort, { once: true });
|
||||
|
||||
// slice() creates an owned copy so the transfer is safe and the
|
||||
// AudioBuffer remains valid if anything else holds a reference.
|
||||
const channels: Float32Array[] = [];
|
||||
@@ -26,11 +41,13 @@ function computePeaksInWorker(audioBuffer: AudioBuffer): Promise<Float32Array> {
|
||||
}
|
||||
|
||||
worker.onmessage = (e: MessageEvent<Float32Array>) => {
|
||||
signal?.removeEventListener("abort", onAbort);
|
||||
worker.terminate();
|
||||
resolve(e.data);
|
||||
};
|
||||
|
||||
worker.onerror = (e) => {
|
||||
signal?.removeEventListener("abort", onAbort);
|
||||
worker.terminate();
|
||||
reject(e);
|
||||
};
|
||||
@@ -73,6 +90,7 @@ export function useAudioPeaks(videoUrl?: string): Float32Array | null {
|
||||
|
||||
setPeaks(null);
|
||||
let cancelled = false;
|
||||
const controller = new AbortController();
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
@@ -80,11 +98,13 @@ export function useAudioPeaks(videoUrl?: string): Float32Array | null {
|
||||
if (cancelled) return;
|
||||
const audioBuffer = await getAudioCtx().decodeAudioData(arrayBuffer);
|
||||
if (cancelled) return;
|
||||
const p = await computePeaksInWorker(audioBuffer);
|
||||
const p = await computePeaksInWorker(audioBuffer, controller.signal);
|
||||
if (cancelled) return;
|
||||
cacheRef.current.set(videoUrl, p);
|
||||
setPeaks(p);
|
||||
} catch {
|
||||
} catch (err) {
|
||||
// AbortError means the effect cleaned up — no state update needed.
|
||||
if (err instanceof DOMException && err.name === "AbortError") return;
|
||||
// No audio track or unsupported format — clear stale data silently.
|
||||
if (!cancelled) setPeaks(null);
|
||||
}
|
||||
@@ -92,6 +112,7 @@ export function useAudioPeaks(videoUrl?: string): Float32Array | null {
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
controller.abort();
|
||||
};
|
||||
}, [videoUrl]);
|
||||
|
||||
|
||||
@@ -187,8 +187,21 @@ export class StreamingVideoDecoder {
|
||||
SOURCE_LOAD_TIMEOUT_MS,
|
||||
"Timed out while loading the source video.",
|
||||
);
|
||||
const filename = videoUrl.split(/[\\/]/).pop() || "video";
|
||||
const blob = new Blob([buffer]);
|
||||
|
||||
let filename: string;
|
||||
let mime = "";
|
||||
|
||||
if (videoUrl.startsWith("data:")) {
|
||||
// data:video/mp4;base64,... → mime="video/mp4", filename="video.mp4"
|
||||
const mimeMatch = videoUrl.match(/^data:([^;,]+)/);
|
||||
mime = mimeMatch?.[1] ?? "";
|
||||
const ext = mime.split("/")[1]?.replace(/[^a-z0-9]/gi, "") ?? "";
|
||||
filename = ext ? `video.${ext}` : "video";
|
||||
} else {
|
||||
filename = videoUrl.split(/[\\/]/).pop() || "video";
|
||||
}
|
||||
|
||||
const blob = new Blob([buffer], { type: mime });
|
||||
return {
|
||||
blob,
|
||||
file: new File([blob], filename, { type: blob.type || "application/octet-stream" }),
|
||||
|
||||
Reference in New Issue
Block a user