Files
kit-ui/lib/media/wasm/wasmLoader.ts
Sebastian Krüger 87f1384175 Fix FFmpeg WASM loading: use CDN instead of local files
The 'Cannot find module as expression is too dynamic' error occurs at runtime
when FFmpeg tries to dynamically load the core module. Using CDN URLs bypasses
this bundler issue entirely since absolute URLs don't require bundler analysis.

Switched to jsdelivr CDN for FFmpeg core and WASM files - this is a proven
approach used by many projects.

Fixes: wasmLoader.ts runtime error on media conversion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-25 20:36:45 +01:00

142 lines
3.8 KiB
TypeScript

import type { ConverterEngine, WASMModuleState } from '@/types/media';
import type { FFmpeg as FFmpegType } from '@ffmpeg/ffmpeg';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { initializeImageMagick } from '@imagemagick/magick-wasm';
/**
* WASM module loading state
*/
const moduleState: WASMModuleState = {
ffmpeg: false,
imagemagick: false,
};
/**
* Cached WASM instances
*/
let ffmpegInstance: FFmpegType | null = null;
let imagemagickInstance: any = null;
/**
* Load FFmpeg WASM module
*/
export async function loadFFmpeg(): Promise<FFmpegType> {
if (ffmpegInstance && moduleState.ffmpeg) {
return ffmpegInstance;
}
try {
if (!FFmpeg) {
throw new Error('FFmpeg class not available');
}
ffmpegInstance = new FFmpeg();
if (!ffmpegInstance) {
throw new Error('Failed to create FFmpeg instance');
}
ffmpegInstance.on('log', ({ message }: { message: string }) => {
console.log('[FFmpeg]', message);
});
// Use CDN URLs for FFmpeg core - avoids "dynamic require" bundler issues
// with local file paths that FFmpeg's internal code cannot resolve at runtime
const coreURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm/ffmpeg-core.js';
const wasmURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.6/dist/esm/ffmpeg-core.wasm';
await ffmpegInstance.load({ coreURL, wasmURL });
moduleState.ffmpeg = true;
console.log('[FFmpeg] Loaded successfully from local assets');
return ffmpegInstance;
} catch (error) {
console.error('[FFmpeg] Failed to load:', error);
throw new Error('Failed to load FFmpeg WASM module');
}
}
/**
* Load ImageMagick WASM module
*/
export async function loadImageMagick(): Promise<any> {
if (imagemagickInstance && moduleState.imagemagick) {
return imagemagickInstance;
}
try {
// File is guaranteed to exist in /wasm/ by the postinstall script
const wasmUrl = '/wasm/magick.wasm';
console.log('[ImageMagick] Loading local WASM:', wasmUrl);
const response = await fetch(wasmUrl);
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');
await initializeImageMagick(arrayBuffer);
// Store the module for later use
imagemagickInstance = { initialized: true };
moduleState.imagemagick = true;
console.log('[ImageMagick] Loaded and initialized successfully from local asset');
return imagemagickInstance;
} catch (error) {
console.error('[ImageMagick] Failed to load:', error);
throw new Error(`Failed to load ImageMagick WASM module: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Get loaded module state
*/
export function getModuleState(): WASMModuleState {
return { ...moduleState };
}
/**
* Check if a specific module is loaded
*/
export function isModuleLoaded(engine: ConverterEngine): boolean {
return moduleState[engine];
}
/**
* Load appropriate WASM module for converter engine
*/
export async function loadModule(engine: ConverterEngine): Promise<any> {
switch (engine) {
case 'ffmpeg':
return loadFFmpeg();
case 'imagemagick':
return loadImageMagick();
default:
throw new Error(`Unknown converter engine: ${engine}`);
}
}
/**
* Unload all WASM modules and free memory
*/
export function unloadAll(): void {
if (ffmpegInstance) {
// FFmpeg doesn't have an explicit unload method
// Just null the instance
ffmpegInstance = null;
moduleState.ffmpeg = false;
}
if (imagemagickInstance) {
imagemagickInstance = null;
moduleState.imagemagick = false;
}
console.log('All WASM modules unloaded');
}