import { inflate } from 'pako/dist/pako_inflate'; import createWorkerInterface from '../../util/createWorkerInterface'; import type { CancellableCallback } from '../../util/WorkerConnector'; import 'script-loader!./rlottie-wasm'; declare const Module: any; declare function allocate(...args: any[]): string; declare function intArrayFromString(str: String): string; let rLottieApi: Record; const rLottieApiPromise = new Promise((resolve) => { Module.onRuntimeInitialized = () => { rLottieApi = { init: Module.cwrap('lottie_init', '', []), destroy: Module.cwrap('lottie_destroy', '', ['number']), resize: Module.cwrap('lottie_resize', '', ['number', 'number', 'number']), buffer: Module.cwrap('lottie_buffer', 'number', ['number']), render: Module.cwrap('lottie_render', '', ['number', 'number']), loadFromData: Module.cwrap('lottie_load_from_data', 'number', ['number', 'number']), }; resolve(); }; }); const HIGH_PRIORITY_MAX_FPS = 60; const LOW_PRIORITY_MAX_FPS = 30; const renderers = new Map(); async function init( key: string, tgsUrl: string, imgSize: number, isLowPriority: boolean, onInit: CancellableCallback, ) { if (!rLottieApi) { await rLottieApiPromise; } const json = await extractJson(tgsUrl); const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0); const handle = rLottieApi.init(); const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap); rLottieApi.resize(handle, imgSize, imgSize); const { reduceFactor, msPerFrame, reducedFramesCount } = calcParams(json, isLowPriority, framesCount); renderers.set(key, { imgSize, reduceFactor, handle }); onInit(reduceFactor, msPerFrame, reducedFramesCount); } async function changeData( key: string, tgsUrl: string, isLowPriority: boolean, onInit: CancellableCallback, ) { if (!rLottieApi) { await rLottieApiPromise; } const json = await extractJson(tgsUrl); const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0); const { handle } = renderers.get(key)!; const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap); const { reduceFactor, msPerFrame, reducedFramesCount } = calcParams(json, isLowPriority, framesCount); onInit(reduceFactor, msPerFrame, reducedFramesCount); } async function extractJson(tgsUrl: string) { const response = await fetch(tgsUrl); const contentType = response.headers.get('Content-Type'); // Support deprecated JSON format cached locally if (contentType?.startsWith('text/')) { return response.text(); } const arrayBuffer = await response.arrayBuffer(); return inflate(arrayBuffer, { to: 'string' }); } function calcParams(json: string, isLowPriority: boolean, framesCount: number) { const animationData = JSON.parse(json); const maxFps = isLowPriority ? LOW_PRIORITY_MAX_FPS : HIGH_PRIORITY_MAX_FPS; const sourceFps = animationData.fr || maxFps; const reduceFactor = sourceFps % maxFps === 0 ? sourceFps / maxFps : 1; return { reduceFactor, msPerFrame: 1000 / (sourceFps / reduceFactor), reducedFramesCount: Math.ceil(framesCount / reduceFactor), }; } async function renderFrames( key: string, fromIndex: number, toIndex: number, onProgress: CancellableCallback, ) { if (!rLottieApi) { await rLottieApiPromise; } const { imgSize, reduceFactor, handle } = renderers.get(key)!; for (let i = fromIndex; i <= toIndex; i++) { const realIndex = i * reduceFactor; rLottieApi.render(handle, realIndex); const bufferPointer = rLottieApi.buffer(handle); const data = Module.HEAPU8.subarray(bufferPointer, bufferPointer + (imgSize * imgSize * 4)); const arrayBuffer = new Uint8ClampedArray(data).buffer; onProgress(i, arrayBuffer); } } function destroy(key: string) { const renderer = renderers.get(key)!; rLottieApi.destroy(renderer.handle); renderers.delete(key); } createWorkerInterface({ init, changeData, renderFrames, destroy, });