44// on why that chunk is fragile to touch), so the message dispatch below
55// is kept as its own copy instead of importing from worker.ts.
66import { parentPort } from "node:worker_threads" ;
7+ import { pathToFileURL } from "node:url" ;
78import type { FFmpegCoreModule , FFmpegCoreModuleFactory } from "@project516/ffmpeg-wasm-types" ;
89import type {
910 FFMessage ,
@@ -26,7 +27,11 @@ import type {
2627 FileData ,
2728} from "./types.js" ;
2829import { FFMessageType } from "./const.js" ;
29- import { ERROR_UNKNOWN_MESSAGE_TYPE , ERROR_NOT_LOADED } from "./errors.js" ;
30+ import {
31+ ERROR_UNKNOWN_MESSAGE_TYPE ,
32+ ERROR_NOT_LOADED ,
33+ wasmLoadError ,
34+ } from "./errors.js" ;
3035
3136if ( ! parentPort ) {
3237 throw new Error (
@@ -35,8 +40,8 @@ if (!parentPort) {
3540}
3641
3742let ffmpeg : FFmpegCoreModule ;
38- // True once a load() has succeeded ; see load().
39- let loaded = false ;
43+ // Set while a load() is in flight or done ; see load().
44+ let loading : Promise < void > | null = null ;
4045
4146// `@project516/ffmpeg-wasm-core` resolves relative to the consuming
4247// project's own node_modules, not this package, so a bare specifier is
@@ -59,12 +64,21 @@ const defaultCoreURL = (): string => {
5964 }
6065} ;
6166
67+ // Two or more scheme characters, so a Windows drive path like C:\x is a path
68+ // and not a "c:" URL.
69+ const toURL = ( location : string ) : string =>
70+ / ^ [ a - z ] [ a - z 0 - 9 + . - ] + : / i. test ( location )
71+ ? location
72+ : pathToFileURL ( location ) . href ;
73+
6274const doLoad = async ( {
6375 coreURL : _coreURL ,
6476 wasmURL : _wasmURL ,
6577} : FFMessageLoadConfig ) : Promise < void > => {
66- const coreURL = _coreURL || defaultCoreURL ( ) ;
67- const wasmURL = _wasmURL ? _wasmURL : coreURL . replace ( / \. j s $ / , ".wasm" ) ;
78+ const coreURL = _coreURL ? toURL ( _coreURL ) : defaultCoreURL ( ) ;
79+ const wasmURL = _wasmURL
80+ ? toURL ( _wasmURL )
81+ : coreURL . replace ( / \. j s $ / , ".wasm" ) ;
6882
6983 if ( coreURL . startsWith ( "blob:" ) ) {
7084 // Node's ESM loader cannot import() a blob: URL (unlike fetch(), which
@@ -90,11 +104,15 @@ const doLoad = async ({
90104 throw new Error ( `failed to import ffmpeg-core from ${ coreURL } ` ) ;
91105 }
92106
93- ffmpeg = await createFFmpegCore ( {
94- // Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.
95- // Encoded wasmURL in the URL as a hack to fix locateFile issue.
96- mainScriptUrlOrBlob : `${ coreURL } #${ btoa ( JSON . stringify ( { wasmURL } ) ) } ` ,
97- } ) ;
107+ try {
108+ ffmpeg = await createFFmpegCore ( {
109+ // Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.
110+ // Encoded wasmURL in the URL as a hack to fix locateFile issue.
111+ mainScriptUrlOrBlob : `${ coreURL } #${ btoa ( JSON . stringify ( { wasmURL } ) ) } ` ,
112+ } ) ;
113+ } catch ( e ) {
114+ throw wasmLoadError ( wasmURL , e ) ;
115+ }
98116 ffmpeg . setLogger ( ( data ) =>
99117 parentPort ! . postMessage ( { type : FFMessageType . LOG , data } )
100118 ) ;
@@ -103,13 +121,22 @@ const doLoad = async ({
103121 ) ;
104122} ;
105123
106- // first is decided after doLoad() succeeds, so a failed load never claims
107- // it and exactly one of several concurrent successful loads reports true.
124+ // Only the call that starts the load reports true. Later calls wait for it
125+ // and report false, so a second load() never creates a second core. A failed
126+ // load clears the slot so the next call can retry.
108127const load = async ( config : FFMessageLoadConfig ) : Promise < IsFirst > => {
109- await doLoad ( config ) ;
110- const first = ! loaded ;
111- loaded = true ;
112- return first ;
128+ if ( loading ) {
129+ await loading ;
130+ return false ;
131+ }
132+ loading = doLoad ( config ) ;
133+ try {
134+ await loading ;
135+ } catch ( e ) {
136+ loading = null ;
137+ throw e ;
138+ }
139+ return true ;
113140} ;
114141
115142const exec = ( { args, timeout = - 1 } : FFMessageExecData ) : ExitCode => {
@@ -169,8 +196,8 @@ const deleteDir = ({ path }: FFMessageDeleteDirData): OK => {
169196
170197const mount = ( { fsType, options, mountPoint } : FFMessageMountData ) : OK => {
171198 const str = fsType as keyof typeof ffmpeg . FS . filesystems ;
199+ if ( ! Object . hasOwn ( ffmpeg . FS . filesystems , str ) ) return false ;
172200 const fs = ffmpeg . FS . filesystems [ str ] ;
173- if ( ! fs ) return false ;
174201 ffmpeg . FS . mount ( fs , options , mountPoint ) ;
175202 return true ;
176203} ;
0 commit comments