RLottie fixes:
Fix displaying some stickers without "tgs": 1 in json Fix double conversion (parsing and stringifying) for render
This commit is contained in:
parent
cd418286b7
commit
5ead8cca2f
@ -140,7 +140,7 @@ export default class StickersTab implements EmoticonsTab {
|
|||||||
if(stickerSet.set.pFlags.animated) {
|
if(stickerSet.set.pFlags.animated) {
|
||||||
promise
|
promise
|
||||||
.then(readBlobAsText)
|
.then(readBlobAsText)
|
||||||
.then(JSON.parse)
|
//.then(JSON.parse)
|
||||||
.then(json => {
|
.then(json => {
|
||||||
lottieLoader.loadAnimationWorker({
|
lottieLoader.loadAnimationWorker({
|
||||||
container: li,
|
container: li,
|
||||||
|
@ -609,7 +609,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
|||||||
//fetch(doc.url).then(res => res.json()).then(async(json) => {
|
//fetch(doc.url).then(res => res.json()).then(async(json) => {
|
||||||
/* return */ await appDocsManager.downloadDocNew(doc)
|
/* return */ await appDocsManager.downloadDocNew(doc)
|
||||||
.then(readBlobAsText)
|
.then(readBlobAsText)
|
||||||
.then(JSON.parse)
|
//.then(JSON.parse)
|
||||||
.then(async(json) => {
|
.then(async(json) => {
|
||||||
//console.timeEnd('download sticker' + doc.id);
|
//console.timeEnd('download sticker' + doc.id);
|
||||||
//console.log('loaded sticker:', doc, div/* , blob */);
|
//console.log('loaded sticker:', doc, div/* , blob */);
|
||||||
|
@ -5,6 +5,7 @@ import { copy } from "./utils";
|
|||||||
import EventListenerBase from "../helpers/eventListenerBase";
|
import EventListenerBase from "../helpers/eventListenerBase";
|
||||||
import mediaSizes from "../helpers/mediaSizes";
|
import mediaSizes from "../helpers/mediaSizes";
|
||||||
import { isApple, isSafari } from "../helpers/userAgent";
|
import { isApple, isSafari } from "../helpers/userAgent";
|
||||||
|
import RLottieWorker from 'worker-loader!./rlottie/rlottie.worker';
|
||||||
|
|
||||||
let convert = (value: number) => {
|
let convert = (value: number) => {
|
||||||
return Math.round(Math.min(Math.max(value, 0), 1) * 255);
|
return Math.round(Math.min(Math.max(value, 0), 1) * 255);
|
||||||
@ -14,7 +15,7 @@ type RLottiePlayerListeners = 'enterFrame' | 'ready' | 'firstFrame' | 'cached';
|
|||||||
type RLottieOptions = {
|
type RLottieOptions = {
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
autoplay?: boolean,
|
autoplay?: boolean,
|
||||||
animationData: any,
|
animationData: string,
|
||||||
loop?: boolean,
|
loop?: boolean,
|
||||||
width?: number,
|
width?: number,
|
||||||
height?: number,
|
height?: number,
|
||||||
@ -396,12 +397,9 @@ export class RLottiePlayer extends EventListenerBase<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
class QueryableWorker extends EventListenerBase<any> {
|
class QueryableWorker extends EventListenerBase<any> {
|
||||||
private worker: Worker;
|
constructor(private worker: Worker, private defaultListener: (data: any) => void = () => {}, onError?: (error: any) => void) {
|
||||||
|
|
||||||
constructor(url: string, private defaultListener: (data: any) => void = () => {}, onError?: (error: any) => void) {
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.worker = new Worker(url);
|
|
||||||
if(onError) {
|
if(onError) {
|
||||||
this.worker.onerror = onError;
|
this.worker.onerror = onError;
|
||||||
}
|
}
|
||||||
@ -530,7 +528,7 @@ class LottieLoader {
|
|||||||
return this.loadPromise = new Promise((resolve, reject) => {
|
return this.loadPromise = new Promise((resolve, reject) => {
|
||||||
let remain = this.workersLimit;
|
let remain = this.workersLimit;
|
||||||
for(let i = 0; i < this.workersLimit; ++i) {
|
for(let i = 0; i < this.workersLimit; ++i) {
|
||||||
const worker = this.workers[i] = new QueryableWorker('rlottie.worker.js');
|
const worker = this.workers[i] = new QueryableWorker(new RLottieWorker());
|
||||||
|
|
||||||
worker.addListener('ready', () => {
|
worker.addListener('ready', () => {
|
||||||
this.log('worker #' + i + ' ready');
|
this.log('worker #' + i + ' ready');
|
||||||
@ -595,7 +593,7 @@ class LottieLoader {
|
|||||||
.then(res => res.arrayBuffer())
|
.then(res => res.arrayBuffer())
|
||||||
.then(data => apiManager.gzipUncompress<string>(data, true))
|
.then(data => apiManager.gzipUncompress<string>(data, true))
|
||||||
.then(str => {
|
.then(str => {
|
||||||
return this.loadAnimationWorker(Object.assign(params, {animationData: JSON.parse(str), needUpscale: true}));
|
return this.loadAnimationWorker(Object.assign(params, {animationData: str/* JSON.parse(str) */, needUpscale: true}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,8 +601,12 @@ class LottieLoader {
|
|||||||
//params.autoplay = true;
|
//params.autoplay = true;
|
||||||
|
|
||||||
if(toneIndex >= 1 && toneIndex <= 5) {
|
if(toneIndex >= 1 && toneIndex <= 5) {
|
||||||
params.animationData = copy(params.animationData);
|
/* params.animationData = copy(params.animationData);
|
||||||
this.applyReplacements(params.animationData, toneIndex);
|
this.applyReplacements(params.animationData, toneIndex); */
|
||||||
|
|
||||||
|
const newAnimationData = JSON.parse(params.animationData);
|
||||||
|
this.applyReplacements(newAnimationData, toneIndex);
|
||||||
|
params.animationData = JSON.stringify(newAnimationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this.loaded) {
|
if(!this.loaded) {
|
||||||
|
193
src/lib/rlottie/rlottie.worker.ts
Normal file
193
src/lib/rlottie/rlottie.worker.ts
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
importScripts('rlottie-wasm.js');
|
||||||
|
//import Module, { allocate, intArrayFromString } from './rlottie-wasm';
|
||||||
|
|
||||||
|
const _Module = Module as any;
|
||||||
|
|
||||||
|
const DEFAULT_FPS = 60;
|
||||||
|
|
||||||
|
export class RLottieItem {
|
||||||
|
private stringOnWasmHeap: any = null;
|
||||||
|
private handle: any = null;
|
||||||
|
private frameCount = 0;
|
||||||
|
|
||||||
|
private dead = false;
|
||||||
|
|
||||||
|
constructor(private reqId: number, jsString: string, private width: number, private height: number, private fps: number) {
|
||||||
|
this.fps = Math.max(1, Math.min(60, fps || DEFAULT_FPS));
|
||||||
|
|
||||||
|
this.init(jsString);
|
||||||
|
|
||||||
|
reply('loaded', this.reqId, this.frameCount, this.fps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private init(jsString: string) {
|
||||||
|
try {
|
||||||
|
this.handle = worker.Api.init();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.stringOnWasmHeap = allocate(intArrayFromString(jsString), 'i8', 0);
|
||||||
|
|
||||||
|
this.frameCount = worker.Api.loadFromData(this.handle, this.stringOnWasmHeap);
|
||||||
|
|
||||||
|
worker.Api.resize(this.handle, this.width, this.height);
|
||||||
|
} catch(e) {
|
||||||
|
console.error('init RLottieItem error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(frameNo: number, clamped: Uint8ClampedArray) {
|
||||||
|
if(this.dead) return;
|
||||||
|
//return;
|
||||||
|
|
||||||
|
if(this.frameCount < frameNo || frameNo < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
worker.Api.render(this.handle, frameNo);
|
||||||
|
|
||||||
|
var bufferPointer = worker.Api.buffer(this.handle);
|
||||||
|
|
||||||
|
var data = _Module.HEAPU8.subarray(bufferPointer, bufferPointer + (this.width * this.height * 4));
|
||||||
|
|
||||||
|
if(!clamped) {
|
||||||
|
clamped = new Uint8ClampedArray(data);
|
||||||
|
} else {
|
||||||
|
clamped.set(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
reply('frame', this.reqId, frameNo, clamped);
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Render error:', e);
|
||||||
|
this.dead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.dead = true;
|
||||||
|
|
||||||
|
worker.Api.destroy(this.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RLottieWorker {
|
||||||
|
public Api: any = {};
|
||||||
|
|
||||||
|
public initApi() {
|
||||||
|
this.Api = {
|
||||||
|
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']),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
this.initApi();
|
||||||
|
reply('ready');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const worker = new RLottieWorker();
|
||||||
|
|
||||||
|
Module.onRuntimeInitialized = function() {
|
||||||
|
worker.init();
|
||||||
|
};
|
||||||
|
|
||||||
|
var items: {[reqId: string]: RLottieItem} = {};
|
||||||
|
var queryableFunctions = {
|
||||||
|
loadFromData: function(reqId: number, jsString: string, width: number, height: number) {
|
||||||
|
try {
|
||||||
|
// ! WARNING, с этой проверкой не все стикеры работают, например - ДУРКА
|
||||||
|
/* if(!/"tgs":\s*?1./.test(jsString)) {
|
||||||
|
throw new Error('Invalid file');
|
||||||
|
} */
|
||||||
|
|
||||||
|
const match = jsString.match(/"fr":\s*?(\d+?),/);
|
||||||
|
const frameRate = +match?.[1] || DEFAULT_FPS;
|
||||||
|
|
||||||
|
console.log('Rendering sticker:', reqId, frameRate, 'now rendered:', Object.keys(items).length);
|
||||||
|
|
||||||
|
items[reqId] = new RLottieItem(reqId, jsString, width, height, frameRate);
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Invalid file for sticker:', jsString);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy: function(reqId: number) {
|
||||||
|
items[reqId].destroy();
|
||||||
|
delete items[reqId];
|
||||||
|
},
|
||||||
|
renderFrame: function(reqId: number, frameNo: number, clamped: Uint8ClampedArray) {
|
||||||
|
//console.log('worker renderFrame', reqId, frameNo, clamped);
|
||||||
|
items[reqId].render(frameNo, clamped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function defaultReply(message: any) {
|
||||||
|
// your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
|
||||||
|
// do something
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when run in WebKit derived browsers.
|
||||||
|
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
|
||||||
|
* transfer data between WebWorkers and the main thread.
|
||||||
|
* https://github.com/mapbox/mapbox-gl-js/issues/8771
|
||||||
|
*
|
||||||
|
* This should be removed once the underlying Safari issue is fixed.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param scope {WindowOrWorkerGlobalScope} Since this function is used both on the main thread and WebWorker context,
|
||||||
|
* let the calling scope pass in the global scope object.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
var _isSafari: boolean = null;
|
||||||
|
function isSafari(scope: any) {
|
||||||
|
if(_isSafari == null) {
|
||||||
|
var userAgent = scope.navigator ? scope.navigator.userAgent : null;
|
||||||
|
_isSafari = !!scope.safari ||
|
||||||
|
!!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome'))));
|
||||||
|
}
|
||||||
|
return _isSafari;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reply(...args: any[]) {
|
||||||
|
if(arguments.length < 1) {
|
||||||
|
throw new TypeError('reply - not enough arguments');
|
||||||
|
}
|
||||||
|
|
||||||
|
//if(arguments[0] == 'frame') return;
|
||||||
|
|
||||||
|
var args = Array.prototype.slice.call(arguments, 1);
|
||||||
|
if(isSafari(self)) {
|
||||||
|
postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': args });
|
||||||
|
} else {
|
||||||
|
var transfer = [];
|
||||||
|
for(var i = 0; i < args.length; i++) {
|
||||||
|
if(args[i] instanceof ArrayBuffer) {
|
||||||
|
transfer.push(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(args[i].buffer && args[i].buffer instanceof ArrayBuffer) {
|
||||||
|
transfer.push(args[i].buffer);
|
||||||
|
//args[i] = args[i].buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': args }, transfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
//postMessage({ 'queryMethodListener': arguments[0], 'queryMethodArguments': Array.prototype.slice.call(arguments, 1) });
|
||||||
|
//console.error(transfer, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage = function(oEvent) {
|
||||||
|
if(oEvent.data instanceof Object && oEvent.data.hasOwnProperty('queryMethod') && oEvent.data.hasOwnProperty('queryMethodArguments')) {
|
||||||
|
// @ts-ignore
|
||||||
|
queryableFunctions[oEvent.data.queryMethod].apply(self, oEvent.data.queryMethodArguments);
|
||||||
|
} else {
|
||||||
|
defaultReply(oEvent.data);
|
||||||
|
}
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user