feat: streamline WASM loading with local priority and cleanup UI
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -31,6 +31,9 @@ yarn-error.log*
|
|||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
|
# wasm binaries
|
||||||
|
/public/wasm/*
|
||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|||||||
@@ -375,15 +375,9 @@ export function FileConverter() {
|
|||||||
const completedCount = conversionJobs.filter(job => job.status === 'completed').length;
|
const completedCount = conversionJobs.filter(job => job.status === 'completed').length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-4xl mx-auto space-y-6">
|
<div className="w-full space-y-8">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>File Converter</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Convert videos, audio, and images directly in your browser using WebAssembly
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
{/* File upload */}
|
{/* File upload */}
|
||||||
<FileUpload
|
<FileUpload
|
||||||
|
|||||||
@@ -15,6 +15,33 @@ const moduleState: WASMModuleState = {
|
|||||||
let ffmpegInstance: FFmpeg | null = null;
|
let ffmpegInstance: FFmpeg | null = null;
|
||||||
let imagemagickInstance: any = null;
|
let imagemagickInstance: any = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to fetch a file with local priority and CDN fallback
|
||||||
|
*/
|
||||||
|
async function fetchWithFallback(localPath: string, cdnUrl: string): Promise<ArrayBuffer> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(localPath);
|
||||||
|
if (!response.ok) throw new Error(`Local fetch failed: ${response.status}`);
|
||||||
|
console.log(`[WASM] Loaded from local: ${localPath}`);
|
||||||
|
return await response.arrayBuffer();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[WASM] Local load failed for ${localPath}, falling back to CDN:`, e);
|
||||||
|
const response = await fetch(cdnUrl);
|
||||||
|
if (!response.ok) throw new Error(`CDN fetch failed: ${response.status}`);
|
||||||
|
console.log(`[WASM] Loaded from CDN: ${cdnUrl}`);
|
||||||
|
return await response.arrayBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to create a blob URL from a fallback fetch
|
||||||
|
*/
|
||||||
|
async function getBlobUrl(localPath: string, cdnUrl: string, mimeType: string): Promise<string> {
|
||||||
|
const buffer = await fetchWithFallback(localPath, cdnUrl);
|
||||||
|
const blob = new Blob([buffer], { type: mimeType });
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load FFmpeg WASM module
|
* Load FFmpeg WASM module
|
||||||
*/
|
*/
|
||||||
@@ -25,21 +52,19 @@ export async function loadFFmpeg(): Promise<FFmpeg> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { FFmpeg } = await import('@ffmpeg/ffmpeg');
|
const { FFmpeg } = await import('@ffmpeg/ffmpeg');
|
||||||
const { toBlobURL } = await import('@ffmpeg/util');
|
|
||||||
|
|
||||||
ffmpegInstance = new FFmpeg();
|
ffmpegInstance = new FFmpeg();
|
||||||
|
|
||||||
// Load core and dependencies
|
|
||||||
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
|
|
||||||
|
|
||||||
ffmpegInstance.on('log', ({ message }) => {
|
ffmpegInstance.on('log', ({ message }) => {
|
||||||
console.log('[FFmpeg]', message);
|
console.log('[FFmpeg]', message);
|
||||||
});
|
});
|
||||||
|
|
||||||
await ffmpegInstance.load({
|
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
|
||||||
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
|
|
||||||
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
|
// Load core and dependencies with local priority
|
||||||
});
|
const coreURL = await getBlobUrl('/wasm/ffmpeg-core.js', `${baseURL}/ffmpeg-core.js`, 'text/javascript');
|
||||||
|
const wasmURL = await getBlobUrl('/wasm/ffmpeg-core.wasm', `${baseURL}/ffmpeg-core.wasm`, 'application/wasm');
|
||||||
|
|
||||||
|
await ffmpegInstance.load({ coreURL, wasmURL });
|
||||||
|
|
||||||
moduleState.ffmpeg = true;
|
moduleState.ffmpeg = true;
|
||||||
console.log('FFmpeg loaded successfully');
|
console.log('FFmpeg loaded successfully');
|
||||||
@@ -62,42 +87,15 @@ export async function loadImageMagick(): Promise<any> {
|
|||||||
try {
|
try {
|
||||||
const { initializeImageMagick } = await import('@imagemagick/magick-wasm');
|
const { initializeImageMagick } = await import('@imagemagick/magick-wasm');
|
||||||
|
|
||||||
// Initialize ImageMagick with WASM file from public directory
|
const localWasmUrl = '/wasm/magick.wasm';
|
||||||
// In production (static export), this will be served from /wasm/magick.wasm
|
const cdnUrl = 'https://unpkg.com/@imagemagick/magick-wasm@0.0.38/dist/magick.wasm';
|
||||||
const wasmUrl = '/wasm/magick.wasm';
|
|
||||||
|
|
||||||
console.log('[ImageMagick] Attempting to load WASM from:', wasmUrl);
|
const arrayBuffer = await fetchWithFallback(localWasmUrl, cdnUrl);
|
||||||
|
console.log('[ImageMagick] WASM file size:', arrayBuffer.byteLength, 'bytes');
|
||||||
// Test fetch the WASM file first to debug
|
|
||||||
try {
|
await initializeImageMagick(arrayBuffer);
|
||||||
const response = await fetch(wasmUrl);
|
|
||||||
console.log('[ImageMagick] WASM fetch response:', {
|
|
||||||
ok: response.ok,
|
|
||||||
status: response.status,
|
|
||||||
contentType: response.headers.get('content-type'),
|
|
||||||
contentLength: response.headers.get('content-length'),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch WASM file: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
|
||||||
console.log('[ImageMagick] WASM file size:', arrayBuffer.byteLength, 'bytes');
|
|
||||||
|
|
||||||
if (arrayBuffer.byteLength === 0) {
|
|
||||||
throw new Error('WASM file is empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now initialize with the buffer directly
|
|
||||||
await initializeImageMagick(arrayBuffer);
|
|
||||||
} catch (fetchError) {
|
|
||||||
console.error('[ImageMagick] Failed to fetch WASM:', fetchError);
|
|
||||||
throw fetchError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImageMagick = await import('@imagemagick/magick-wasm');
|
const ImageMagick = await import('@imagemagick/magick-wasm');
|
||||||
|
|
||||||
imagemagickInstance = ImageMagick;
|
imagemagickInstance = ImageMagick;
|
||||||
moduleState.imagemagick = true;
|
moduleState.imagemagick = true;
|
||||||
console.log('[ImageMagick] Loaded and initialized successfully');
|
console.log('[ImageMagick] Loaded and initialized successfully');
|
||||||
|
|||||||
Reference in New Issue
Block a user