fix(downloads): cache ffmpeg core js and wasm

This creates a blob url outside of the worker for for the core .js and .wasm files so they aren't downloaded on each run.
This commit is contained in:
Daniel 2026-03-03 23:24:18 +00:00 committed by GitHub
parent 50a5b79d70
commit 44a7c3b61c
7 changed files with 60 additions and 21 deletions

View file

@ -5,6 +5,7 @@
"": {
"name": "monochrome",
"dependencies": {
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1",
@ -328,6 +329,8 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
"@ffmpeg/core": ["@ffmpeg/core@0.12.10", "", {}, "sha512-dzNplnn2Nxle2c2i2rrDhqcB19q9cglCkWnoMTDN9Q9l3PvdjZWd1HfSPjCNWc/p8Q3CT+Es9fWOR0UhAeYQZA=="],
"@ffmpeg/ffmpeg": ["@ffmpeg/ffmpeg@0.12.15", "", { "dependencies": { "@ffmpeg/types": "^0.12.4" } }, "sha512-1C8Obr4GsN3xw+/1Ww6PFM84wSQAGsdoTuTWPOj2OizsRDLT4CXTaVjPhkw6ARyDus1B9X/L2LiXHqYYsGnRFw=="],
"@ffmpeg/types": ["@ffmpeg/types@0.12.4", "", {}, "sha512-k9vJQNBGTxE5AhYDtOYR5rO5fKsspbg51gbcwtbkw2lCdoIILzklulcjJfIDwrtn7XhDeF2M+THwJ2FGrLeV6A=="],

View file

@ -11,7 +11,7 @@ import { APICache } from './cache.js';
import { addMetadataToAudio } from './metadata.js';
import { DashDownloader } from './dash-downloader.js';
import { encodeToMp3, MP3EncodingError } from './mp3-encoder.js';
import { ffmpeg } from './ffmpeg.js';
import { ffmpeg, loadFfmpeg } from './ffmpeg.js';
import { initTagLib } from './taglib.js';
export const DASH_MANIFEST_UNAVAILABLE_CODE = 'DASH_MANIFEST_UNAVAILABLE';
@ -1112,6 +1112,9 @@ export class LosslessAPI {
async downloadTrack(id, quality = 'HI_RES_LOSSLESS', filename, options = {}) {
// Initialize taglib in the background.
initTagLib().catch(console.error);
// Load ffmpeg in the background.
loadFfmpeg().catch(console.error);
const { onProgress, track } = options;
try {

View file

@ -16,7 +16,7 @@ import { addMetadataToAudio } from './metadata.js';
import { DashDownloader } from './dash-downloader.js';
import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js';
import { encodeToMp3 } from './mp3-encoder.js';
import { ffmpeg } from './ffmpeg.js';
import { ffmpeg, loadFfmpeg } from './ffmpeg.js';
import { initTagLib } from './taglib.js';
const downloadTasks = new Map();
@ -273,6 +273,9 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign
// Initialize taglib in the background.
initTagLib().catch(console.error);
// Load ffmpeg in the background.
loadFfmpeg().catch(console.error);
let enrichedTrack = {
...track,
artist: track.artist || (track.artists && track.artists.length > 0 ? track.artists[0] : null),

View file

@ -1,3 +1,8 @@
import { toBlobURL } from '@ffmpeg/util';
const ffmpegBase = 'https://unpkg.com/@ffmpeg/core/dist/esm';
const coreJs = `${ffmpegBase}/ffmpeg-core.js`;
const coreWasm = `${ffmpegBase}/ffmpeg-core.wasm`;
class FfmpegError extends Error {
constructor(message) {
super(message);
@ -6,6 +11,20 @@ class FfmpegError extends Error {
}
}
export function loadFfmpeg() {
return (
loadFfmpeg.promise ||
(loadFfmpeg.promise = (async () => {
const data = {
coreURL: await toBlobURL(coreJs, 'text/javascript'),
wasmURL: await toBlobURL(coreWasm, 'application/wasm'),
};
return data;
})())
);
}
async function ffmpegWorker(
audioBlob,
args = {},
@ -15,6 +34,7 @@ async function ffmpegWorker(
signal = null
) {
const audioData = await audioBlob.arrayBuffer();
const assets = loadFfmpeg();
return new Promise((resolve, reject) => {
const worker = new Worker(new URL('./ffmpeg.worker.js', import.meta.url), { type: 'module' });
@ -57,18 +77,20 @@ async function ffmpegWorker(
reject(new FfmpegError('Worker failed: ' + error.message));
};
// Transfer audio data to worker
worker.postMessage(
{
audioData,
...args,
output: {
name: outputName,
mime: outputMime,
(async () => {
worker.postMessage(
{
audioData,
...args,
output: {
name: outputName,
mime: outputMime,
},
loadOptions: await assets,
},
},
[audioData]
);
[audioData]
);
})();
});
}

View file

@ -1,10 +1,9 @@
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';
let ffmpeg = null;
let loadingPromise = null;
async function loadFFmpeg() {
async function loadFFmpeg(loadOptions = {}) {
if (loadingPromise) return loadingPromise;
loadingPromise = (async () => {
@ -25,11 +24,7 @@ async function loadFFmpeg() {
self.postMessage({ type: 'progress', stage: 'loading', message: 'Loading FFmpeg...' });
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
await ffmpeg.load(loadOptions);
})();
return loadingPromise;
@ -45,10 +40,12 @@ self.onmessage = async (e) => {
},
encodeStartMessage = 'Encoding...',
encodeEndMessage = 'Finalizing...',
loadOptions = {},
} = e.data;
try {
await loadFFmpeg();
console.log(loadOptions);
await loadFFmpeg(loadOptions);
self.postMessage({ type: 'progress', stage: 'encoding', message: encodeStartMessage });

10
package-lock.json generated
View file

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1",
@ -2578,6 +2579,15 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@ffmpeg/core": {
"version": "0.12.10",
"resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.12.10.tgz",
"integrity": "sha512-dzNplnn2Nxle2c2i2rrDhqcB19q9cglCkWnoMTDN9Q9l3PvdjZWd1HfSPjCNWc/p8Q3CT+Es9fWOR0UhAeYQZA==",
"license": "GPL-2.0-or-later",
"engines": {
"node": ">=16.x"
}
},
"node_modules/@ffmpeg/ffmpeg": {
"version": "0.12.15",
"resolved": "https://registry.npmjs.org/@ffmpeg/ffmpeg/-/ffmpeg-0.12.15.tgz",

View file

@ -50,6 +50,7 @@
"source-map": "^0.7.4"
},
"dependencies": {
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1",