Browse Source

Refactor animated stickers a bit

master
Eduard Kuzmenko 2 years ago
parent
commit
c70369e4e9
  1. 18
      src/components/wrappers.ts
  2. 80
      src/helpers/blob.ts
  3. 28
      src/helpers/blob/blobConstruct.ts
  4. 33
      src/helpers/blob/blobSafeMimeType.ts
  5. 23
      src/helpers/blob/readBlobAs.ts
  6. 11
      src/helpers/blob/readBlobAsArrayBuffer.ts
  7. 11
      src/helpers/blob/readBlobAsDataURL.ts
  8. 11
      src/helpers/blob/readBlobAsText.ts
  9. 11
      src/helpers/blob/readBlobAsUint8Array.ts
  10. 13
      src/lib/appManagers/appStickersManager.ts
  11. 4
      src/lib/cacheStorage.ts
  12. 5
      src/lib/filemanager.ts
  13. 2
      src/lib/idb.ts
  14. 23
      src/lib/mtproto/apiFileManager.ts
  15. 22
      src/lib/rlottie/lottieLoader.ts
  16. 2
      src/lib/rlottie/queryableWorker.ts
  17. 55
      src/lib/rlottie/rlottie.worker.ts
  18. 6
      src/lib/rlottie/rlottiePlayer.ts
  19. 2
      src/lib/serviceWorker/stream.ts

18
src/components/wrappers.ts

@ -6,7 +6,6 @@
import type Chat from './chat/chat'; import type Chat from './chat/chat';
import { getEmojiToneIndex } from '../vendor/emoji'; import { getEmojiToneIndex } from '../vendor/emoji';
import { readBlobAsText } from '../helpers/blob';
import { deferredPromise } from '../helpers/cancellablePromise'; import { deferredPromise } from '../helpers/cancellablePromise';
import { formatFullSentTime } from '../helpers/date'; import { formatFullSentTime } from '../helpers/date';
import mediaSizes, { ScreenSize } from '../helpers/mediaSizes'; import mediaSizes, { ScreenSize } from '../helpers/mediaSizes';
@ -1277,9 +1276,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
//appDocsManager.downloadDocNew(doc.id).promise.then(res => res.json()).then(async(json) => { //appDocsManager.downloadDocNew(doc.id).promise.then(res => res.json()).then(async(json) => {
//fetch(doc.url).then(res => res.json()).then(async(json) => { //fetch(doc.url).then(res => res.json()).then(async(json) => {
return await appDocsManager.downloadDoc(doc, /* undefined, */lazyLoadQueue?.queueId) return await appDocsManager.downloadDoc(doc, /* undefined, */lazyLoadQueue?.queueId)
.then(readBlobAsText) .then(async(blob) => {
//.then(JSON.parse)
.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 */);
if(middleware && !middleware()) { if(middleware && !middleware()) {
@ -1290,13 +1287,14 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
container: div, container: div,
loop: loop && !emoji, loop: loop && !emoji,
autoplay: play, autoplay: play,
animationData: json, animationData: blob,
width, width,
height, height,
name: 'doc' + doc.id, name: 'doc' + doc.id,
needUpscale, needUpscale,
skipRatio skipRatio,
}, group, toneIndex, middleware); toneIndex
}, group, middleware);
//const deferred = deferredPromise<void>(); //const deferred = deferredPromise<void>();
@ -1596,14 +1594,12 @@ export async function wrapStickerSetThumb({set, lazyLoadQueue, container, group,
if(set.pFlags.animated) { if(set.pFlags.animated) {
return promise return promise
.then(readBlobAsText) .then((blob) => {
//.then(JSON.parse)
.then(json => {
lottieLoader.loadAnimationWorker({ lottieLoader.loadAnimationWorker({
container, container,
loop: true, loop: true,
autoplay, autoplay,
animationData: json, animationData: blob,
width, width,
height, height,
needUpscale: true, needUpscale: true,

80
src/helpers/blob.ts

@ -1,80 +0,0 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*
* Originally from:
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
export function readBlobAs(blob: Blob, method: 'readAsText'): Promise<string>;
export function readBlobAs(blob: Blob, method: 'readAsDataURL'): Promise<string>;
export function readBlobAs(blob: Blob, method: 'readAsArrayBuffer'): Promise<ArrayBuffer>;
export function readBlobAs(blob: Blob, method: 'readAsArrayBuffer' | 'readAsText' | 'readAsDataURL'): Promise<any> {
// const perf = performance.now();
return new Promise<any>((resolve) => {
const reader = new FileReader();
reader.addEventListener('loadend', (e) => {
// console.log('readBlobAs time:', method, performance.now() - perf);
resolve(e.target.result);
});
reader[method](blob);
});
}
export function readBlobAsText(blob: Blob) {
return readBlobAs(blob, 'readAsText');
}
export function readBlobAsDataURL(blob: Blob) {
return readBlobAs(blob, 'readAsDataURL');
}
export function readBlobAsArrayBuffer(blob: Blob) {
return readBlobAs(blob, 'readAsArrayBuffer');
}
export function readBlobAsUint8Array(blob: Blob) {
return readBlobAsArrayBuffer(blob).then(buffer => new Uint8Array(buffer));
}
export function blobConstruct(blobParts: any, mimeType: string = ''): Blob {
let blob;
const safeMimeType = blobSafeMimeType(mimeType);
try {
blob = new Blob(blobParts, {type: safeMimeType});
} catch(e) {
// @ts-ignore
let bb = new BlobBuilder;
blobParts.forEach((blobPart: any) => {
bb.append(blobPart);
});
blob = bb.getBlob(safeMimeType);
}
return blob;
}
// https://www.iana.org/assignments/media-types/media-types.xhtml
export function blobSafeMimeType(mimeType: string) {
if([
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'image/bmp',
'video/mp4',
'video/webm',
'video/quicktime',
'audio/ogg',
'audio/mpeg',
'audio/mp4',
'application/json',
'application/pdf'
].indexOf(mimeType) === -1) {
return 'application/octet-stream';
}
return mimeType;
}

28
src/helpers/blob/blobConstruct.ts

@ -0,0 +1,28 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*
* Originally from:
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
import blobSafeMimeType from "./blobSafeMimeType";
export default function blobConstruct(blobParts: any, mimeType: string = ''): Blob {
let blob;
const safeMimeType = blobSafeMimeType(mimeType);
try {
blob = new Blob(blobParts, {type: safeMimeType});
} catch(e) {
// @ts-ignore
let bb = new BlobBuilder;
blobParts.forEach((blobPart: any) => {
bb.append(blobPart);
});
blob = bb.getBlob(safeMimeType);
}
return blob;
}

33
src/helpers/blob/blobSafeMimeType.ts

@ -0,0 +1,33 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*
* Originally from:
* https://github.com/zhukov/webogram
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
* https://github.com/zhukov/webogram/blob/master/LICENSE
*/
// https://www.iana.org/assignments/media-types/media-types.xhtml
export default function blobSafeMimeType(mimeType: string) {
if([
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'image/bmp',
'video/mp4',
'video/webm',
'video/quicktime',
'audio/ogg',
'audio/mpeg',
'audio/mp4',
'application/json',
'application/pdf'
].indexOf(mimeType) === -1) {
return 'application/octet-stream';
}
return mimeType;
}

23
src/helpers/blob/readBlobAs.ts

@ -0,0 +1,23 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
// import { IS_WEB_WORKER } from "../context";
// const id = IS_WEB_WORKER ? Math.random() * 0x1000 | 0 : 0;
export default function readBlobAs(blob: Blob, method: 'readAsText'): Promise<string>;
export default function readBlobAs(blob: Blob, method: 'readAsDataURL'): Promise<string>;
export default function readBlobAs(blob: Blob, method: 'readAsArrayBuffer'): Promise<ArrayBuffer>;
export default function readBlobAs(blob: Blob, method: 'readAsArrayBuffer' | 'readAsText' | 'readAsDataURL'): Promise<any> {
// const perf = performance.now();
return new Promise<any>((resolve) => {
const reader = new FileReader();
reader.addEventListener('loadend', (e) => {
// console.log(`readBlobAs [${id}] ${method} time ${performance.now() - perf}`);
resolve(e.target.result);
});
reader[method](blob);
});
}

11
src/helpers/blob/readBlobAsArrayBuffer.ts

@ -0,0 +1,11 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import readBlobAs from "./readBlobAs";
export default function readBlobAsArrayBuffer(blob: Blob) {
return readBlobAs(blob, 'readAsArrayBuffer');
}

11
src/helpers/blob/readBlobAsDataURL.ts

@ -0,0 +1,11 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import readBlobAs from "./readBlobAs";
export default function readBlobAsDataURL(blob: Blob) {
return readBlobAs(blob, 'readAsDataURL');
}

11
src/helpers/blob/readBlobAsText.ts

@ -0,0 +1,11 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import readBlobAs from "./readBlobAs";
export default function readBlobAsText(blob: Blob) {
return readBlobAs(blob, 'readAsText');
}

11
src/helpers/blob/readBlobAsUint8Array.ts

@ -0,0 +1,11 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import readBlobAsArrayBuffer from "./readBlobAsArrayBuffer";
export default function readBlobAsUint8Array(blob: Blob) {
return readBlobAsArrayBuffer(blob).then(buffer => new Uint8Array(buffer));
}

13
src/lib/appManagers/appStickersManager.ts

@ -13,7 +13,6 @@ import AppStorage from '../storage';
import { MOUNT_CLASS_TO } from '../../config/debug'; import { MOUNT_CLASS_TO } from '../../config/debug';
import { forEachReverse } from '../../helpers/array'; import { forEachReverse } from '../../helpers/array';
import DATABASE_STATE from '../../config/databases/state'; import DATABASE_STATE from '../../config/databases/state';
import { readBlobAsText } from '../../helpers/blob';
import lottieLoader from '../rlottie/lottieLoader'; import lottieLoader from '../rlottie/lottieLoader';
import mediaSizes from '../../helpers/mediaSizes'; import mediaSizes from '../../helpers/mediaSizes';
import { getEmojiToneIndex } from '../../vendor/emoji'; import { getEmojiToneIndex } from '../../vendor/emoji';
@ -162,7 +161,7 @@ export class AppStickersManager {
public getAnimatedEmojiSounds(overwrite?: boolean) { public getAnimatedEmojiSounds(overwrite?: boolean) {
if(this.getAnimatedEmojiSoundsPromise && !overwrite) return this.getAnimatedEmojiSoundsPromise; if(this.getAnimatedEmojiSoundsPromise && !overwrite) return this.getAnimatedEmojiSoundsPromise;
const promise = this.getAnimatedEmojiSoundsPromise = apiManager.getAppConfig(overwrite).then(appConfig => { const promise = this.getAnimatedEmojiSoundsPromise = Promise.resolve(apiManager.getAppConfig(overwrite)).then(appConfig => {
if(this.getAnimatedEmojiSoundsPromise !== promise) { if(this.getAnimatedEmojiSoundsPromise !== promise) {
return; return;
} }
@ -257,19 +256,19 @@ export class AppStickersManager {
const doc = this.getAnimatedEmojiSticker(emoji); const doc = this.getAnimatedEmojiSticker(emoji);
if(doc) { if(doc) {
return appDocsManager.downloadDoc(doc) return appDocsManager.downloadDoc(doc)
.then(readBlobAsText) .then(async(blob) => {
.then(async(json) => {
const mediaSize = mediaSizes.active.emojiSticker; const mediaSize = mediaSizes.active.emojiSticker;
const toneIndex = getEmojiToneIndex(emoji); const toneIndex = getEmojiToneIndex(emoji);
const animation = await lottieLoader.loadAnimationWorker({ const animation = await lottieLoader.loadAnimationWorker({
container: undefined, container: undefined,
animationData: json, animationData: blob,
width: width ?? mediaSize.width, width: width ?? mediaSize.width,
height: height ?? mediaSize.height, height: height ?? mediaSize.height,
name: 'doc' + doc.id, name: 'doc' + doc.id,
autoplay: false, autoplay: false,
loop: false loop: false,
}, 'none', toneIndex); toneIndex
}, 'none');
animation.addEventListener('firstFrame', () => { animation.addEventListener('firstFrame', () => {
appDocsManager.saveLottiePreview(doc, animation.canvas, toneIndex); appDocsManager.saveLottiePreview(doc, animation.canvas, toneIndex);

4
src/lib/cacheStorage.ts

@ -5,8 +5,8 @@
*/ */
import Modes from '../config/modes'; import Modes from '../config/modes';
import { blobConstruct } from '../helpers/blob'; import blobConstruct from '../helpers/blob/blobConstruct';
import FileManager from './filemanager'; import FileManager from './fileManager';
//import { MOUNT_CLASS_TO } from './mtproto/mtproto_config'; //import { MOUNT_CLASS_TO } from './mtproto/mtproto_config';
//import { logger } from './polyfill'; //import { logger } from './polyfill';

5
src/lib/filemanager.ts

@ -9,10 +9,11 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE * https://github.com/zhukov/webogram/blob/master/LICENSE
*/ */
import { blobConstruct, readBlobAsUint8Array } from "../helpers/blob"; import blobConstruct from "../helpers/blob/blobConstruct";
import readBlobAsUint8Array from "../helpers/blob/readBlobAsUint8Array";
export class FileManager { export class FileManager {
public blobSupported = true; private blobSupported = true;
constructor() { constructor() {
try { try {

2
src/lib/idb.ts

@ -11,7 +11,7 @@
import { Database } from '../config/databases'; import { Database } from '../config/databases';
import Modes from '../config/modes'; import Modes from '../config/modes';
import { blobConstruct } from '../helpers/blob'; import blobConstruct from '../helpers/blob/blobConstruct';
import { safeAssign } from '../helpers/object'; import { safeAssign } from '../helpers/object';
import { logger } from './logger'; import { logger } from './logger';

23
src/lib/mtproto/apiFileManager.ts

@ -12,7 +12,6 @@
import type { ReferenceBytes } from "./referenceDatabase"; import type { ReferenceBytes } from "./referenceDatabase";
import { MOUNT_CLASS_TO } from "../../config/debug"; import { MOUNT_CLASS_TO } from "../../config/debug";
import Modes from "../../config/modes"; import Modes from "../../config/modes";
import { readBlobAsArrayBuffer } from "../../helpers/blob";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { notifyAll, notifySomeone } from "../../helpers/context"; import { notifyAll, notifySomeone } from "../../helpers/context";
import { getFileNameByLocation } from "../../helpers/fileName"; import { getFileNameByLocation } from "../../helpers/fileName";
@ -21,7 +20,7 @@ import { InputFile, InputFileLocation, InputWebFileLocation, UploadFile, UploadW
import { DcId, WorkerTaskVoidTemplate } from "../../types"; import { DcId, WorkerTaskVoidTemplate } from "../../types";
import CacheStorageController from "../cacheStorage"; import CacheStorageController from "../cacheStorage";
import cryptoWorker from "../crypto/cryptoworker"; import cryptoWorker from "../crypto/cryptoworker";
import FileManager from "../filemanager"; import fileManager from "../fileManager";
import { logger, LogTypes } from "../logger"; import { logger, LogTypes } from "../logger";
import apiManager from "./apiManager"; import apiManager from "./apiManager";
import { isWebpSupported } from "./mtproto.worker"; import { isWebpSupported } from "./mtproto.worker";
@ -29,6 +28,7 @@ import { bytesToHex } from "../../helpers/bytes";
import assumeType from "../../helpers/assumeType"; import assumeType from "../../helpers/assumeType";
import ctx from "../../environment/ctx"; import ctx from "../../environment/ctx";
import noop from "../../helpers/noop"; import noop from "../../helpers/noop";
import readBlobAsArrayBuffer from "../../helpers/blob/readBlobAsArrayBuffer";
type Delayed = { type Delayed = {
offset: number, offset: number,
@ -45,8 +45,11 @@ export type DownloadOptions = {
limitPart?: number, limitPart?: number,
queueId?: number, queueId?: number,
onlyCache?: boolean, onlyCache?: boolean,
// getFileMethod: Parameters<CacheStorageController['getFile']>[1]
}; };
type DownloadPromise = CancellablePromise<Blob>;
export type MyUploadFile = UploadFile.uploadFile | UploadWebFile.uploadWebFile; export type MyUploadFile = UploadFile.uploadFile | UploadWebFile.uploadWebFile;
export interface RefreshReferenceTask extends WorkerTaskVoidTemplate { export interface RefreshReferenceTask extends WorkerTaskVoidTemplate {
@ -66,7 +69,7 @@ export class ApiFileManager {
private cacheStorage = new CacheStorageController('cachedFiles'); private cacheStorage = new CacheStorageController('cachedFiles');
private cachedDownloadPromises: { private cachedDownloadPromises: {
[fileName: string]: CancellablePromise<Blob> [fileName: string]: DownloadPromise
} = {}; } = {};
private uploadPromises: { private uploadPromises: {
@ -311,8 +314,8 @@ export class ApiFileManager {
}); });
} }
public downloadFile(options: DownloadOptions): CancellablePromise<Blob> { public downloadFile(options: DownloadOptions): DownloadPromise {
if(!FileManager.isAvailable()) { if(!fileManager.isAvailable()) {
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'}); return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
} }
@ -343,8 +346,8 @@ export class ApiFileManager {
//this.log('downloadFile cachedPromise'); //this.log('downloadFile cachedPromise');
if(size) { if(size) {
return cachedPromise.then((blob: Blob) => { return cachedPromise.then((blob) => {
if(blob.size < size) { if(blob instanceof Blob && blob.size < size) {
this.debug && this.log('downloadFile need to deleteFile, wrong size:', blob.size, size); this.debug && this.log('downloadFile need to deleteFile, wrong size:', blob.size, size);
return this.deleteFile(fileName).then(() => { return this.deleteFile(fileName).then(() => {
@ -361,12 +364,12 @@ export class ApiFileManager {
} }
} }
const deferred = deferredPromise<Blob>(); const deferred: DownloadPromise = deferredPromise();
const mimeType = options.mimeType || 'image/jpeg'; const mimeType = options.mimeType || 'image/jpeg';
let error: Error; let error: Error;
let resolved = false; let resolved = false;
let cacheFileWriter: ReturnType<typeof FileManager['getFakeFileWriter']>; let cacheFileWriter: ReturnType<typeof fileManager['getFakeFileWriter']>;
let errorHandler = (_error: Error) => { let errorHandler = (_error: Error) => {
error = _error; error = _error;
delete this.cachedDownloadPromises[fileName]; delete this.cachedDownloadPromises[fileName];
@ -464,7 +467,7 @@ export class ApiFileManager {
await writeFilePromise; await writeFilePromise;
checkCancel(); checkCancel();
await FileManager.write(fileWriter, processedResult); await fileManager.write(fileWriter, processedResult);
} }
writeFileDeferred.resolve(); writeFileDeferred.resolve();

22
src/lib/rlottie/lottieLoader.ts

@ -12,7 +12,7 @@ import { logger, LogTypes } from "../logger";
import apiManager from "../mtproto/mtprotoworker"; import apiManager from "../mtproto/mtprotoworker";
import RLottiePlayer, { RLottieOptions } from './rlottiePlayer'; import RLottiePlayer, { RLottieOptions } from './rlottiePlayer';
import QueryableWorker from './queryableWorker'; import QueryableWorker from './queryableWorker';
import applyReplacements from './applyReplacements'; import blobConstruct from '../../helpers/blob/blobConstruct';
export type LottieAssetName = 'EmptyFolder' | 'Folders_1' | 'Folders_2' | export type LottieAssetName = 'EmptyFolder' | 'Folders_1' | 'Folders_2' |
'TwoFactorSetupMonkeyClose' | 'TwoFactorSetupMonkeyCloseAndPeek' | 'TwoFactorSetupMonkeyClose' | 'TwoFactorSetupMonkeyCloseAndPeek' |
@ -101,16 +101,16 @@ export class LottieLoader {
return fetch(url) return fetch(url)
.then(res => { .then(res => {
if(!res.headers || res.headers.get('content-type') === 'application/octet-stream') { if(!res.headers || res.headers.get('content-type') === 'application/octet-stream') {
return res.arrayBuffer().then(data => apiManager.invokeCrypto('gzipUncompress', data, true)) return res.arrayBuffer().then(data => apiManager.invokeCrypto('gzipUncompress', data)).then(arr => blobConstruct([arr], ''))
} else { } else {
return res.text(); return res.blob();
} }
}) })
/* .then(str => { /* .then(str => {
return new Promise<string>((resolve) => setTimeout(() => resolve(str), 2e3)); return new Promise<string>((resolve) => setTimeout(() => resolve(str), 2e3));
}) */ }) */
.then(str => { .then(blob => {
const newParams = Object.assign(params, {animationData: str as string/* JSON.parse(str) */, needUpscale: true}); const newParams = Object.assign(params, {animationData: blob, needUpscale: true});
if(!newParams.name) newParams.name = url; if(!newParams.name) newParams.name = url;
return this.loadAnimationWorker(newParams); return this.loadAnimationWorker(newParams);
}); });
@ -130,22 +130,12 @@ export class LottieLoader {
]).then(() => player); ]).then(() => player);
} }
public async loadAnimationWorker(params: RLottieOptions, group = params.group || '', toneIndex = -1, middleware?: () => boolean): Promise<RLottiePlayer> { public async loadAnimationWorker(params: RLottieOptions, group = params.group || '', middleware?: () => boolean): Promise<RLottiePlayer> {
if(!this.isWebAssemblySupported) { if(!this.isWebAssemblySupported) {
return this.loadPromise as any; return this.loadPromise as any;
} }
//params.autoplay = true; //params.autoplay = true;
if(toneIndex >= 1 && toneIndex <= 5) {
/* params.animationData = copy(params.animationData);
this.applyReplacements(params.animationData, toneIndex); */
params.toneIndex = toneIndex;
const newAnimationData = JSON.parse(params.animationData);
applyReplacements(newAnimationData, toneIndex);
params.animationData = JSON.stringify(newAnimationData);
}
if(!this.loaded) { if(!this.loaded) {
await this.loadLottieWorkers(); await this.loadLottieWorkers();
} }

2
src/lib/rlottie/queryableWorker.ts

@ -53,7 +53,7 @@ export default class QueryableWorker extends EventListenerBase<{
transfer.push(arg); transfer.push(arg);
} }
if(arg.buffer && arg.buffer instanceof ArrayBuffer) { if(typeof(arg) === 'object' && arg.buffer instanceof ArrayBuffer) {
transfer.push(arg.buffer); transfer.push(arg.buffer);
} }
}); });

55
src/lib/rlottie/rlottie.worker.ts

@ -4,6 +4,9 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import readBlobAsText from "../../helpers/blob/readBlobAsText";
import applyReplacements from "./applyReplacements";
importScripts('rlottie-wasm.js'); importScripts('rlottie-wasm.js');
//import Module, { allocate, intArrayFromString } from './rlottie-wasm'; //import Module, { allocate, intArrayFromString } from './rlottie-wasm';
@ -19,46 +22,46 @@ export class RLottieItem {
private stringOnWasmHeap: number; private stringOnWasmHeap: number;
private handle: LottieHandlePointer; private handle: LottieHandlePointer;
private frameCount: number; private frameCount: number;
private fps: number;
private dead: boolean; private dead: boolean;
//private context: OffscreenCanvasRenderingContext2D; //private context: OffscreenCanvasRenderingContext2D;
constructor( constructor(
private reqId: number, private reqId: number,
jsString: string,
private width: number, private width: number,
private height: number, private height: number/* ,
private fps: number/* ,
private canvas: OffscreenCanvas */ private canvas: OffscreenCanvas */
) { ) {
this.fps = Math.max(1, Math.min(60, fps || DEFAULT_FPS));
this.frameCount = 0;
//this.context = canvas.getContext('2d'); }
this.init(jsString); public init(json: string, fps: number) {
if(this.dead) {
return;
}
reply('loaded', this.reqId, this.frameCount, this.fps); this.fps = Math.max(1, Math.min(60, fps || DEFAULT_FPS));
//this.context = canvas.getContext('2d');
/* let frame = 0; /* let frame = 0;
setInterval(() => { setInterval(() => {
if(frame >= this.frameCount) frame = 0; if(frame >= this.frameCount) frame = 0;
let _frame = frame++; let _frame = frame++;
this.render(_frame, null); this.render(_frame, null);
}, 1000 / this.fps); */ }, 1000 / this.fps); */
}
private init(jsString: string) {
try { try {
this.handle = worker.Api.init(); this.handle = worker.Api.init();
// @ts-ignore // @ts-ignore
this.stringOnWasmHeap = allocate(intArrayFromString(jsString), 'i8', 0); this.stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0);
this.frameCount = worker.Api.loadFromData(this.handle, this.stringOnWasmHeap); this.frameCount = worker.Api.loadFromData(this.handle, this.stringOnWasmHeap);
worker.Api.resize(this.handle, this.width, this.height); worker.Api.resize(this.handle, this.width, this.height);
reply('loaded', this.reqId, this.frameCount, this.fps);
} catch(e) { } catch(e) {
console.error('init RLottieItem error:', e); console.error('init RLottieItem error:', e);
reply('error', this.reqId, e); reply('error', this.reqId, e);
@ -66,7 +69,7 @@ export class RLottieItem {
} }
public render(frameNo: number, clamped?: Uint8ClampedArray) { public render(frameNo: number, clamped?: Uint8ClampedArray) {
if(this.dead) return; if(this.dead || this.handle === undefined) return;
//return; //return;
if(this.frameCount < frameNo || frameNo < 0) { if(this.frameCount < frameNo || frameNo < 0) {
@ -99,9 +102,11 @@ export class RLottieItem {
public destroy() { public destroy() {
this.dead = true; this.dead = true;
if(this.handle !== undefined) {
worker.Api.destroy(this.handle); worker.Api.destroy(this.handle);
} }
} }
}
class RLottieWorker { class RLottieWorker {
public Api: { public Api: {
@ -138,8 +143,19 @@ _Module.onRuntimeInitialized = function() {
const items: {[reqId: string]: RLottieItem} = {}; const items: {[reqId: string]: RLottieItem} = {};
const queryableFunctions = { const queryableFunctions = {
loadFromData: function(reqId: number, jsString: string, width: number, height: number/* , canvas: OffscreenCanvas */) { loadFromData: function(reqId: number, blob: Blob, width: number, height: number, toneIndex: number/* , canvas: OffscreenCanvas */) {
const item = items[reqId] = new RLottieItem(reqId, width, height/* , canvas */);
readBlobAsText(blob).then((json) => {
try { try {
if(typeof(toneIndex) === 'number' && toneIndex >= 1 && toneIndex <= 5) {
/* params.animationData = copy(params.animationData);
this.applyReplacements(params.animationData, toneIndex); */
const newAnimationData = JSON.parse(json);
applyReplacements(newAnimationData, toneIndex);
json = JSON.stringify(newAnimationData);
}
// ! WARNING, с этой проверкой не все стикеры работают, например - ДУРКА // ! WARNING, с этой проверкой не все стикеры работают, например - ДУРКА
/* if(!/"tgs":\s*?1./.test(jsString)) { /* if(!/"tgs":\s*?1./.test(jsString)) {
throw new Error('Invalid file'); throw new Error('Invalid file');
@ -149,16 +165,17 @@ const queryableFunctions = {
let json = JSON.parse(jsString); let json = JSON.parse(jsString);
console.log('sticker decode:', performance.now() - perf); */ console.log('sticker decode:', performance.now() - perf); */
const match = jsString.match(/"fr":\s*?(\d+?),/); const match = json.match(/"fr":\s*?(\d+?),/);
const frameRate = +match?.[1] || DEFAULT_FPS; const frameRate = +match?.[1] || DEFAULT_FPS;
//console.log('Rendering sticker:', reqId, frameRate, 'now rendered:', Object.keys(items).length); //console.log('Rendering sticker:', reqId, frameRate, 'now rendered:', Object.keys(items).length);
items[reqId] = new RLottieItem(reqId, jsString, width, height, frameRate/* , canvas */); item.init(json, frameRate);
} catch(e) { } catch(err) {
console.error('Invalid file for sticker:', jsString); console.error('Invalid file for sticker:', json);
reply('error', reqId, e); reply('error', reqId, err);
} }
});
}, },
destroy: function(reqId: number) { destroy: function(reqId: number) {
const item = items[reqId]; const item = items[reqId];

6
src/lib/rlottie/rlottiePlayer.ts

@ -15,7 +15,7 @@ export type RLottieOptions = {
container: HTMLElement, container: HTMLElement,
canvas?: HTMLCanvasElement, canvas?: HTMLCanvasElement,
autoplay?: boolean, autoplay?: boolean,
animationData: string, animationData: Blob,
loop?: boolean, loop?: boolean,
width?: number, width?: number,
height?: number, height?: number,
@ -264,8 +264,8 @@ export default class RLottiePlayer extends EventListenerBase<{
this.worker.sendQuery(methodName, this.reqId, ...args); this.worker.sendQuery(methodName, this.reqId, ...args);
} }
public loadFromData(jsonString: string) { public loadFromData(data: RLottieOptions['animationData']) {
this.sendQuery('loadFromData', jsonString, this.width, this.height/* , this.canvas.transferControlToOffscreen() */); this.sendQuery('loadFromData', data, this.width, this.height, this.toneIndex/* , this.canvas.transferControlToOffscreen() */);
} }
public play() { public play() {

2
src/lib/serviceWorker/stream.ts

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import { readBlobAsUint8Array } from "../../helpers/blob"; import readBlobAsUint8Array from "../../helpers/blob/readBlobAsUint8Array";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { getWindowClients } from "../../helpers/context"; import { getWindowClients } from "../../helpers/context";
import debounce from "../../helpers/schedulers/debounce"; import debounce from "../../helpers/schedulers/debounce";

Loading…
Cancel
Save