From c70369e4e9e86d81478e7f0e4de7f7e9bfbb9f58 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sat, 5 Feb 2022 23:20:38 +0400 Subject: [PATCH] Refactor animated stickers a bit --- src/components/wrappers.ts | 18 ++--- src/helpers/blob.ts | 80 --------------------- src/helpers/blob/blobConstruct.ts | 28 ++++++++ src/helpers/blob/blobSafeMimeType.ts | 33 +++++++++ src/helpers/blob/readBlobAs.ts | 23 ++++++ src/helpers/blob/readBlobAsArrayBuffer.ts | 11 +++ src/helpers/blob/readBlobAsDataURL.ts | 11 +++ src/helpers/blob/readBlobAsText.ts | 11 +++ src/helpers/blob/readBlobAsUint8Array.ts | 11 +++ src/lib/appManagers/appStickersManager.ts | 13 ++-- src/lib/cacheStorage.ts | 4 +- src/lib/filemanager.ts | 5 +- src/lib/idb.ts | 2 +- src/lib/mtproto/apiFileManager.ts | 23 +++--- src/lib/rlottie/lottieLoader.ts | 22 ++---- src/lib/rlottie/queryableWorker.ts | 2 +- src/lib/rlottie/rlottie.worker.ts | 87 ++++++++++++++--------- src/lib/rlottie/rlottiePlayer.ts | 6 +- src/lib/serviceWorker/stream.ts | 2 +- 19 files changed, 223 insertions(+), 169 deletions(-) delete mode 100644 src/helpers/blob.ts create mode 100644 src/helpers/blob/blobConstruct.ts create mode 100644 src/helpers/blob/blobSafeMimeType.ts create mode 100644 src/helpers/blob/readBlobAs.ts create mode 100644 src/helpers/blob/readBlobAsArrayBuffer.ts create mode 100644 src/helpers/blob/readBlobAsDataURL.ts create mode 100644 src/helpers/blob/readBlobAsText.ts create mode 100644 src/helpers/blob/readBlobAsUint8Array.ts diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 93011cad..76607e68 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -6,7 +6,6 @@ import type Chat from './chat/chat'; import { getEmojiToneIndex } from '../vendor/emoji'; -import { readBlobAsText } from '../helpers/blob'; import { deferredPromise } from '../helpers/cancellablePromise'; import { formatFullSentTime } from '../helpers/date'; 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) => { //fetch(doc.url).then(res => res.json()).then(async(json) => { return await appDocsManager.downloadDoc(doc, /* undefined, */lazyLoadQueue?.queueId) - .then(readBlobAsText) - //.then(JSON.parse) - .then(async(json) => { + .then(async(blob) => { //console.timeEnd('download sticker' + doc.id); //console.log('loaded sticker:', doc, div/* , blob */); if(middleware && !middleware()) { @@ -1290,13 +1287,14 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o container: div, loop: loop && !emoji, autoplay: play, - animationData: json, + animationData: blob, width, height, name: 'doc' + doc.id, needUpscale, - skipRatio - }, group, toneIndex, middleware); + skipRatio, + toneIndex + }, group, middleware); //const deferred = deferredPromise(); @@ -1596,14 +1594,12 @@ export async function wrapStickerSetThumb({set, lazyLoadQueue, container, group, if(set.pFlags.animated) { return promise - .then(readBlobAsText) - //.then(JSON.parse) - .then(json => { + .then((blob) => { lottieLoader.loadAnimationWorker({ container, loop: true, autoplay, - animationData: json, + animationData: blob, width, height, needUpscale: true, diff --git a/src/helpers/blob.ts b/src/helpers/blob.ts deleted file mode 100644 index 14117c4a..00000000 --- a/src/helpers/blob.ts +++ /dev/null @@ -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 - * https://github.com/zhukov/webogram/blob/master/LICENSE - */ - -export function readBlobAs(blob: Blob, method: 'readAsText'): Promise; -export function readBlobAs(blob: Blob, method: 'readAsDataURL'): Promise; -export function readBlobAs(blob: Blob, method: 'readAsArrayBuffer'): Promise; -export function readBlobAs(blob: Blob, method: 'readAsArrayBuffer' | 'readAsText' | 'readAsDataURL'): Promise { - // const perf = performance.now(); - return new Promise((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; -} diff --git a/src/helpers/blob/blobConstruct.ts b/src/helpers/blob/blobConstruct.ts new file mode 100644 index 00000000..dbc1df76 --- /dev/null +++ b/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 + * 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; +} diff --git a/src/helpers/blob/blobSafeMimeType.ts b/src/helpers/blob/blobSafeMimeType.ts new file mode 100644 index 00000000..aacff84e --- /dev/null +++ b/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 + * 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; +} diff --git a/src/helpers/blob/readBlobAs.ts b/src/helpers/blob/readBlobAs.ts new file mode 100644 index 00000000..30cc0341 --- /dev/null +++ b/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; +export default function readBlobAs(blob: Blob, method: 'readAsDataURL'): Promise; +export default function readBlobAs(blob: Blob, method: 'readAsArrayBuffer'): Promise; +export default function readBlobAs(blob: Blob, method: 'readAsArrayBuffer' | 'readAsText' | 'readAsDataURL'): Promise { + // const perf = performance.now(); + return new Promise((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); + }); +} diff --git a/src/helpers/blob/readBlobAsArrayBuffer.ts b/src/helpers/blob/readBlobAsArrayBuffer.ts new file mode 100644 index 00000000..f61878f6 --- /dev/null +++ b/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'); +} diff --git a/src/helpers/blob/readBlobAsDataURL.ts b/src/helpers/blob/readBlobAsDataURL.ts new file mode 100644 index 00000000..e6a6e7c3 --- /dev/null +++ b/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'); +} diff --git a/src/helpers/blob/readBlobAsText.ts b/src/helpers/blob/readBlobAsText.ts new file mode 100644 index 00000000..6d1fc550 --- /dev/null +++ b/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'); +} diff --git a/src/helpers/blob/readBlobAsUint8Array.ts b/src/helpers/blob/readBlobAsUint8Array.ts new file mode 100644 index 00000000..b3c6ed30 --- /dev/null +++ b/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)); +} diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index f2e2232c..7319857c 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -13,7 +13,6 @@ import AppStorage from '../storage'; import { MOUNT_CLASS_TO } from '../../config/debug'; import { forEachReverse } from '../../helpers/array'; import DATABASE_STATE from '../../config/databases/state'; -import { readBlobAsText } from '../../helpers/blob'; import lottieLoader from '../rlottie/lottieLoader'; import mediaSizes from '../../helpers/mediaSizes'; import { getEmojiToneIndex } from '../../vendor/emoji'; @@ -162,7 +161,7 @@ export class AppStickersManager { public getAnimatedEmojiSounds(overwrite?: boolean) { 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) { return; } @@ -257,19 +256,19 @@ export class AppStickersManager { const doc = this.getAnimatedEmojiSticker(emoji); if(doc) { return appDocsManager.downloadDoc(doc) - .then(readBlobAsText) - .then(async(json) => { + .then(async(blob) => { const mediaSize = mediaSizes.active.emojiSticker; const toneIndex = getEmojiToneIndex(emoji); const animation = await lottieLoader.loadAnimationWorker({ container: undefined, - animationData: json, + animationData: blob, width: width ?? mediaSize.width, height: height ?? mediaSize.height, name: 'doc' + doc.id, autoplay: false, - loop: false - }, 'none', toneIndex); + loop: false, + toneIndex + }, 'none'); animation.addEventListener('firstFrame', () => { appDocsManager.saveLottiePreview(doc, animation.canvas, toneIndex); diff --git a/src/lib/cacheStorage.ts b/src/lib/cacheStorage.ts index ecea45e5..022f72ee 100644 --- a/src/lib/cacheStorage.ts +++ b/src/lib/cacheStorage.ts @@ -5,8 +5,8 @@ */ import Modes from '../config/modes'; -import { blobConstruct } from '../helpers/blob'; -import FileManager from './filemanager'; +import blobConstruct from '../helpers/blob/blobConstruct'; +import FileManager from './fileManager'; //import { MOUNT_CLASS_TO } from './mtproto/mtproto_config'; //import { logger } from './polyfill'; diff --git a/src/lib/filemanager.ts b/src/lib/filemanager.ts index f73ca38b..61e4d2c5 100644 --- a/src/lib/filemanager.ts +++ b/src/lib/filemanager.ts @@ -9,10 +9,11 @@ * 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 { - public blobSupported = true; + private blobSupported = true; constructor() { try { diff --git a/src/lib/idb.ts b/src/lib/idb.ts index 1250d174..b8f47f30 100644 --- a/src/lib/idb.ts +++ b/src/lib/idb.ts @@ -11,7 +11,7 @@ import { Database } from '../config/databases'; import Modes from '../config/modes'; -import { blobConstruct } from '../helpers/blob'; +import blobConstruct from '../helpers/blob/blobConstruct'; import { safeAssign } from '../helpers/object'; import { logger } from './logger'; diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index 41d802ff..40dcd62b 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -12,7 +12,6 @@ import type { ReferenceBytes } from "./referenceDatabase"; import { MOUNT_CLASS_TO } from "../../config/debug"; import Modes from "../../config/modes"; -import { readBlobAsArrayBuffer } from "../../helpers/blob"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; import { notifyAll, notifySomeone } from "../../helpers/context"; import { getFileNameByLocation } from "../../helpers/fileName"; @@ -21,7 +20,7 @@ import { InputFile, InputFileLocation, InputWebFileLocation, UploadFile, UploadW import { DcId, WorkerTaskVoidTemplate } from "../../types"; import CacheStorageController from "../cacheStorage"; import cryptoWorker from "../crypto/cryptoworker"; -import FileManager from "../filemanager"; +import fileManager from "../fileManager"; import { logger, LogTypes } from "../logger"; import apiManager from "./apiManager"; import { isWebpSupported } from "./mtproto.worker"; @@ -29,6 +28,7 @@ import { bytesToHex } from "../../helpers/bytes"; import assumeType from "../../helpers/assumeType"; import ctx from "../../environment/ctx"; import noop from "../../helpers/noop"; +import readBlobAsArrayBuffer from "../../helpers/blob/readBlobAsArrayBuffer"; type Delayed = { offset: number, @@ -45,8 +45,11 @@ export type DownloadOptions = { limitPart?: number, queueId?: number, onlyCache?: boolean, + // getFileMethod: Parameters[1] }; +type DownloadPromise = CancellablePromise; + export type MyUploadFile = UploadFile.uploadFile | UploadWebFile.uploadWebFile; export interface RefreshReferenceTask extends WorkerTaskVoidTemplate { @@ -66,7 +69,7 @@ export class ApiFileManager { private cacheStorage = new CacheStorageController('cachedFiles'); private cachedDownloadPromises: { - [fileName: string]: CancellablePromise + [fileName: string]: DownloadPromise } = {}; private uploadPromises: { @@ -311,8 +314,8 @@ export class ApiFileManager { }); } - public downloadFile(options: DownloadOptions): CancellablePromise { - if(!FileManager.isAvailable()) { + public downloadFile(options: DownloadOptions): DownloadPromise { + if(!fileManager.isAvailable()) { return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'}); } @@ -343,8 +346,8 @@ export class ApiFileManager { //this.log('downloadFile cachedPromise'); if(size) { - return cachedPromise.then((blob: Blob) => { - if(blob.size < size) { + return cachedPromise.then((blob) => { + if(blob instanceof Blob && blob.size < size) { this.debug && this.log('downloadFile need to deleteFile, wrong size:', blob.size, size); return this.deleteFile(fileName).then(() => { @@ -361,12 +364,12 @@ export class ApiFileManager { } } - const deferred = deferredPromise(); + const deferred: DownloadPromise = deferredPromise(); const mimeType = options.mimeType || 'image/jpeg'; let error: Error; let resolved = false; - let cacheFileWriter: ReturnType; + let cacheFileWriter: ReturnType; let errorHandler = (_error: Error) => { error = _error; delete this.cachedDownloadPromises[fileName]; @@ -464,7 +467,7 @@ export class ApiFileManager { await writeFilePromise; checkCancel(); - await FileManager.write(fileWriter, processedResult); + await fileManager.write(fileWriter, processedResult); } writeFileDeferred.resolve(); diff --git a/src/lib/rlottie/lottieLoader.ts b/src/lib/rlottie/lottieLoader.ts index a56dfe71..0cc6fade 100644 --- a/src/lib/rlottie/lottieLoader.ts +++ b/src/lib/rlottie/lottieLoader.ts @@ -12,7 +12,7 @@ import { logger, LogTypes } from "../logger"; import apiManager from "../mtproto/mtprotoworker"; import RLottiePlayer, { RLottieOptions } from './rlottiePlayer'; import QueryableWorker from './queryableWorker'; -import applyReplacements from './applyReplacements'; +import blobConstruct from '../../helpers/blob/blobConstruct'; export type LottieAssetName = 'EmptyFolder' | 'Folders_1' | 'Folders_2' | 'TwoFactorSetupMonkeyClose' | 'TwoFactorSetupMonkeyCloseAndPeek' | @@ -101,16 +101,16 @@ export class LottieLoader { return fetch(url) .then(res => { 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 { - return res.text(); + return res.blob(); } }) /* .then(str => { return new Promise((resolve) => setTimeout(() => resolve(str), 2e3)); }) */ - .then(str => { - const newParams = Object.assign(params, {animationData: str as string/* JSON.parse(str) */, needUpscale: true}); + .then(blob => { + const newParams = Object.assign(params, {animationData: blob, needUpscale: true}); if(!newParams.name) newParams.name = url; return this.loadAnimationWorker(newParams); }); @@ -130,22 +130,12 @@ export class LottieLoader { ]).then(() => player); } - public async loadAnimationWorker(params: RLottieOptions, group = params.group || '', toneIndex = -1, middleware?: () => boolean): Promise { + public async loadAnimationWorker(params: RLottieOptions, group = params.group || '', middleware?: () => boolean): Promise { if(!this.isWebAssemblySupported) { return this.loadPromise as any; } //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) { await this.loadLottieWorkers(); } diff --git a/src/lib/rlottie/queryableWorker.ts b/src/lib/rlottie/queryableWorker.ts index e7535ef7..b1b445ad 100644 --- a/src/lib/rlottie/queryableWorker.ts +++ b/src/lib/rlottie/queryableWorker.ts @@ -53,7 +53,7 @@ export default class QueryableWorker extends EventListenerBase<{ transfer.push(arg); } - if(arg.buffer && arg.buffer instanceof ArrayBuffer) { + if(typeof(arg) === 'object' && arg.buffer instanceof ArrayBuffer) { transfer.push(arg.buffer); } }); diff --git a/src/lib/rlottie/rlottie.worker.ts b/src/lib/rlottie/rlottie.worker.ts index 5295f8d1..a3f5ea5c 100644 --- a/src/lib/rlottie/rlottie.worker.ts +++ b/src/lib/rlottie/rlottie.worker.ts @@ -4,6 +4,9 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import readBlobAsText from "../../helpers/blob/readBlobAsText"; +import applyReplacements from "./applyReplacements"; + importScripts('rlottie-wasm.js'); //import Module, { allocate, intArrayFromString } from './rlottie-wasm'; @@ -19,46 +22,46 @@ export class RLottieItem { private stringOnWasmHeap: number; private handle: LottieHandlePointer; private frameCount: number; + private fps: number; private dead: boolean; //private context: OffscreenCanvasRenderingContext2D; constructor( private reqId: number, - jsString: string, private width: number, - private height: number, - private fps: number/* , + private height: number/* , 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; setInterval(() => { if(frame >= this.frameCount) frame = 0; let _frame = frame++; this.render(_frame, null); }, 1000 / this.fps); */ - } - private init(jsString: string) { try { this.handle = worker.Api.init(); // @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); worker.Api.resize(this.handle, this.width, this.height); + + reply('loaded', this.reqId, this.frameCount, this.fps); } catch(e) { console.error('init RLottieItem error:', e); reply('error', this.reqId, e); @@ -66,7 +69,7 @@ export class RLottieItem { } public render(frameNo: number, clamped?: Uint8ClampedArray) { - if(this.dead) return; + if(this.dead || this.handle === undefined) return; //return; if(this.frameCount < frameNo || frameNo < 0) { @@ -99,7 +102,9 @@ export class RLottieItem { public destroy() { this.dead = true; - worker.Api.destroy(this.handle); + if(this.handle !== undefined) { + worker.Api.destroy(this.handle); + } } } @@ -138,27 +143,39 @@ _Module.onRuntimeInitialized = function() { const items: {[reqId: string]: RLottieItem} = {}; const queryableFunctions = { - loadFromData: function(reqId: number, jsString: string, width: number, height: number/* , canvas: OffscreenCanvas */) { - try { - // ! WARNING, с этой проверкой не все стикеры работают, например - ДУРКА - /* if(!/"tgs":\s*?1./.test(jsString)) { - throw new Error('Invalid file'); - } */ - - /* let perf = performance.now(); - let json = JSON.parse(jsString); - console.log('sticker decode:', performance.now() - perf); */ - - 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/* , canvas */); - } catch(e) { - console.error('Invalid file for sticker:', jsString); - reply('error', reqId, e); - } + 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 { + 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, с этой проверкой не все стикеры работают, например - ДУРКА + /* if(!/"tgs":\s*?1./.test(jsString)) { + throw new Error('Invalid file'); + } */ + + /* let perf = performance.now(); + let json = JSON.parse(jsString); + console.log('sticker decode:', performance.now() - perf); */ + + const match = json.match(/"fr":\s*?(\d+?),/); + const frameRate = +match?.[1] || DEFAULT_FPS; + + //console.log('Rendering sticker:', reqId, frameRate, 'now rendered:', Object.keys(items).length); + + item.init(json, frameRate); + } catch(err) { + console.error('Invalid file for sticker:', json); + reply('error', reqId, err); + } + }); }, destroy: function(reqId: number) { const item = items[reqId]; diff --git a/src/lib/rlottie/rlottiePlayer.ts b/src/lib/rlottie/rlottiePlayer.ts index 3a9cac88..3f0960b1 100644 --- a/src/lib/rlottie/rlottiePlayer.ts +++ b/src/lib/rlottie/rlottiePlayer.ts @@ -15,7 +15,7 @@ export type RLottieOptions = { container: HTMLElement, canvas?: HTMLCanvasElement, autoplay?: boolean, - animationData: string, + animationData: Blob, loop?: boolean, width?: number, height?: number, @@ -264,8 +264,8 @@ export default class RLottiePlayer extends EventListenerBase<{ this.worker.sendQuery(methodName, this.reqId, ...args); } - public loadFromData(jsonString: string) { - this.sendQuery('loadFromData', jsonString, this.width, this.height/* , this.canvas.transferControlToOffscreen() */); + public loadFromData(data: RLottieOptions['animationData']) { + this.sendQuery('loadFromData', data, this.width, this.height, this.toneIndex/* , this.canvas.transferControlToOffscreen() */); } public play() { diff --git a/src/lib/serviceWorker/stream.ts b/src/lib/serviceWorker/stream.ts index 7d6431c8..ff6a9037 100644 --- a/src/lib/serviceWorker/stream.ts +++ b/src/lib/serviceWorker/stream.ts @@ -4,7 +4,7 @@ * 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 { getWindowClients } from "../../helpers/context"; import debounce from "../../helpers/schedulers/debounce";