diff --git a/src/api/gramjs/methods/media.ts b/src/api/gramjs/methods/media.ts index 70983a8b5..cec15d0b8 100644 --- a/src/api/gramjs/methods/media.ts +++ b/src/api/gramjs/methods/media.ts @@ -17,7 +17,6 @@ import * as cacheApi from '../../../util/cacheApi'; import { getEntityTypeById } from '../gramjsBuilders'; const MEDIA_ENTITY_TYPES = new Set(['msg', 'sticker', 'gif', 'wallpaper', 'photo', 'webDocument', 'document']); -const TGS_MIME_TYPE = 'application/x-tgsticker'; export default async function downloadMedia( { @@ -63,8 +62,8 @@ export default async function downloadMedia( export type EntityType = ( 'msg' | 'sticker' | 'wallpaper' | 'gif' | 'channel' | 'chat' | 'user' | 'photo' | 'stickerSet' | 'webDocument' | - 'document' - ); + 'document' | 'staticMap' +); async function download( url: string, @@ -84,12 +83,6 @@ async function download( entityType, entityId, sizeType, params, mediaMatchType, } = parsed; - if (entityType === 'file') { - const response = await fetch(entityId); - const data = await response.arrayBuffer(); - return { data }; - } - if (!isConnected) { return Promise.reject(new Error('ERROR: Client is not connected')); } @@ -192,7 +185,7 @@ async function download( return { mimeType, data, fullSize }; } else if (entityType === 'stickerSet') { const data = await client.downloadStickerSetThumb(entity); - const mimeType = mediaFormat === ApiMediaFormat.Lottie ? TGS_MIME_TYPE : getMimeType(data); + const mimeType = getMimeType(data); return { mimeType, data }; } else { @@ -247,15 +240,11 @@ async function parseMedia( ): Promise { switch (mediaFormat) { case ApiMediaFormat.BlobUrl: - case ApiMediaFormat.Lottie: { return new Blob([data], { type: mimeType }); - } - case ApiMediaFormat.Text: { + case ApiMediaFormat.Text: return data.toString(); - } - case ApiMediaFormat.Progressive: { + case ApiMediaFormat.Progressive: return data.buffer; - } } return undefined; @@ -307,7 +296,7 @@ export function parseMediaUrl(url: string) { : url.startsWith('webDocument') ? url.match(/(webDocument):(.+)/) : url.match( - /(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|file|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/, + /(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/, ); if (!mediaMatch) { return undefined; @@ -316,14 +305,6 @@ export function parseMediaUrl(url: string) { const mediaMatchType = mediaMatch[1]; const entityId: string | number = mediaMatch[2]; - if (mediaMatchType === 'file') { - return { - mediaMatchType, - entityType: 'file', - entityId, - }; - } - let entityType: EntityType; const params = mediaMatch[3]; const sizeType = params?.replace('?size=', '') || undefined; @@ -331,9 +312,7 @@ export function parseMediaUrl(url: string) { if (mediaMatch[1] === 'avatar' || mediaMatch[1] === 'profile') { entityType = getEntityTypeById(entityId); } else { - entityType = mediaMatch[1] as ( - 'msg' | 'sticker' | 'wallpaper' | 'gif' | 'stickerSet' | 'photo' | 'webDocument' | 'document' - ); + entityType = mediaMatch[1] as EntityType; } return { diff --git a/src/api/types/media.ts b/src/api/types/media.ts index 6cfb51b2d..ca0dd8da0 100644 --- a/src/api/types/media.ts +++ b/src/api/types/media.ts @@ -3,7 +3,6 @@ export enum ApiMediaFormat { BlobUrl, - Lottie, Progressive, Stream, Text, diff --git a/src/components/auth/AuthQrCode.tsx b/src/components/auth/AuthQrCode.tsx index 2b82000d2..59cd34a20 100644 --- a/src/components/auth/AuthQrCode.tsx +++ b/src/components/auth/AuthQrCode.tsx @@ -1,7 +1,7 @@ import QrCodeStyling from 'qr-code-styling'; import type { FC } from '../../lib/teact/teact'; import React, { - useEffect, useRef, memo, useCallback, useState, + useEffect, useRef, memo, useCallback, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; @@ -9,11 +9,11 @@ import type { GlobalState } from '../../global/types'; import type { LangCode } from '../../types'; import { DEFAULT_LANG_CODE } from '../../config'; +import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets'; import { setLanguage } from '../../util/langProvider'; import buildClassName from '../../util/buildClassName'; import renderText from '../common/helpers/renderText'; import { getSuggestedLanguage } from './helpers/getSuggestedLanguage'; -import getAnimationData from '../common/helpers/animatedAssets'; import useLangString from '../../hooks/useLangString'; import useFlag from '../../hooks/useFlag'; @@ -22,8 +22,8 @@ import useMediaTransition from '../../hooks/useMediaTransition'; import Loading from '../ui/Loading'; import Button from '../ui/Button'; -import AnimatedSticker from '../common/AnimatedSticker'; import blankUrl from '../../assets/blank.png'; +import AnimatedIcon from '../common/AnimatedIcon'; type StateProps = Pick @@ -73,17 +73,7 @@ const AuthCode: FC = ({ const [isLoading, markIsLoading, unmarkIsLoading] = useFlag(); const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag(); - const [animationData, setAnimationData] = useState(); - const [isAnimationLoaded, markAnimationLoaded] = useFlag(); - const transitionClassNames = useMediaTransition(isQrMounted); - const airplaneTransitionClassNames = useMediaTransition(isAnimationLoaded); - - useEffect(() => { - if (!animationData) { - getAnimationData('QrPlane').then(setAnimationData); - } - }, [animationData]); useEffect(() => { if (!authQrCode) { @@ -142,17 +132,13 @@ const AuthCode: FC = ({ ref={qrCodeRef} style={`width: ${QR_SIZE}px; height: ${QR_SIZE}px`} /> - {animationData && ( - - )} + {!isQrMounted &&
} diff --git a/src/components/calls/group/GroupCall.tsx b/src/components/calls/group/GroupCall.tsx index 5c23ccc01..1c9a65050 100644 --- a/src/components/calls/group/GroupCall.tsx +++ b/src/components/calls/group/GroupCall.tsx @@ -19,6 +19,7 @@ import { IS_REQUEST_FULLSCREEN_SUPPORTED, IS_SINGLE_COLUMN_LAYOUT, } from '../../../util/environment'; +import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets'; import buildClassName from '../../../util/buildClassName'; import { selectGroupCall, @@ -338,7 +339,11 @@ const GroupCall: FC = ({
{hasVideo && (IS_ANDROID || IS_IOS) && ( )} diff --git a/src/components/right/management/Management.scss b/src/components/right/management/Management.scss index e7b67d7fc..ac0bc3f0f 100644 --- a/src/components/right/management/Management.scss +++ b/src/components/right/management/Management.scss @@ -36,22 +36,7 @@ } .section-icon { - width: 8.75rem; - height: 8.75rem; margin: 0 auto 2rem; - position: relative; - - .AnimatedSticker { - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - - &.hidden { - display: none; - } - } } .ListItem { diff --git a/src/global/actions/api/reactions.ts b/src/global/actions/api/reactions.ts index 1c237d311..3d2f12fcd 100644 --- a/src/global/actions/api/reactions.ts +++ b/src/global/actions/api/reactions.ts @@ -31,10 +31,10 @@ addActionHandler('loadAvailableReactions', async () => { // Preload animations result.forEach((availableReaction) => { if (availableReaction.aroundAnimation) { - mediaLoader.fetch(`sticker${availableReaction.aroundAnimation.id}`, ApiMediaFormat.Lottie); + mediaLoader.fetch(`sticker${availableReaction.aroundAnimation.id}`, ApiMediaFormat.BlobUrl); } if (availableReaction.centerIcon) { - mediaLoader.fetch(`sticker${availableReaction.centerIcon.id}`, ApiMediaFormat.Lottie); + mediaLoader.fetch(`sticker${availableReaction.centerIcon.id}`, ApiMediaFormat.BlobUrl); } }); diff --git a/src/global/helpers/messageMedia.ts b/src/global/helpers/messageMedia.ts index e99d84803..5eaa0feba 100644 --- a/src/global/helpers/messageMedia.ts +++ b/src/global/helpers/messageMedia.ts @@ -302,15 +302,10 @@ export function getAudioHasCover(media: ApiAudio) { export function getMessageMediaFormat( message: ApiMessage, target: Target, ): ApiMediaFormat { - const { - sticker, video, audio, voice, - } = message.content; - + const { video, audio, voice } = message.content; const fullVideo = video || getMessageWebPageVideo(message); - if (sticker && target === 'inline' && sticker.isLottie) { - return ApiMediaFormat.Lottie; - } else if (fullVideo && IS_PROGRESSIVE_SUPPORTED && ( + if (fullVideo && IS_PROGRESSIVE_SUPPORTED && ( target === 'viewerFull' || target === 'inline' )) { return ApiMediaFormat.Progressive; diff --git a/src/lib/rlottie/RLottie.ts b/src/lib/rlottie/RLottie.ts index b8566cf45..433925918 100644 --- a/src/lib/rlottie/RLottie.ts +++ b/src/lib/rlottie/RLottie.ts @@ -7,6 +7,7 @@ import { import WorkerConnector from '../../util/WorkerConnector'; import { animate } from '../../util/animation'; import cycleRestrict from '../../util/cycleRestrict'; +import generateIdFor from '../../util/generateIdFor'; interface Params { noLoop?: boolean; @@ -25,6 +26,7 @@ const HIGH_PRIORITY_QUALITY = IS_SINGLE_COLUMN_LAYOUT ? 0.75 : 1; const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75; const HIGH_PRIORITY_CACHE_MODULO = IS_SAFARI ? 2 : 4; const LOW_PRIORITY_CACHE_MODULO = 0; +const KEY_STORE = {}; const workers = new Array(MAX_WORKERS).fill(undefined).map( () => new WorkerConnector(new Worker(new URL('./rlottie.worker.ts', import.meta.url))), @@ -36,7 +38,7 @@ class RLottie { private imgSize!: number; - private key!: string; + private key = generateIdFor(KEY_STORE); private msPerFrame = 1000 / 60; @@ -66,6 +68,8 @@ class RLottie { private isWaiting = true; + private isEnded = false; + private isOnLoadFired = false; private isDestroyed = false; @@ -83,9 +87,8 @@ class RLottie { private lastRenderAt?: number; constructor( - private id: string, private container: HTMLDivElement, - private animationData: string, + private tgsUrl: string, private params: Params = {}, private onLoad?: () => void, private customColor?: [number, number, number], @@ -100,7 +103,11 @@ class RLottie { return this.isAnimating || this.isWaiting; } - play() { + play(forceRestart = false) { + if (this.isEnded && forceRestart) { + this.approxFrameIndex = Math.floor(0); + } + this.stopFrameIndex = undefined; this.direction = 1; this.doPlay(); @@ -117,20 +124,6 @@ class RLottie { this.chunks = this.chunks.map((chunk, i) => (i === currentChunkIndex ? chunk : undefined)); } - goToAndPlay(frameIndex: number) { - this.approxFrameIndex = Math.floor(frameIndex / this.reduceFactor); - this.stopFrameIndex = undefined; - this.direction = 1; - this.doPlay(); - } - - goToAndStop(frameIndex: number) { - this.approxFrameIndex = Math.floor(frameIndex / this.reduceFactor); - this.stopFrameIndex = Math.floor(frameIndex / this.reduceFactor); - this.direction = 1; - this.doPlay(); - } - playSegment([startFrameIndex, stopFrameIndex]: [number, number]) { this.approxFrameIndex = Math.floor(startFrameIndex / this.reduceFactor); this.stopFrameIndex = Math.floor(stopFrameIndex / this.reduceFactor); @@ -184,8 +177,6 @@ class RLottie { } private initConfig() { - this.key = `${this.id}_${this.imgSize}`; - const { isLowPriority } = this.params; this.cacheModulo = isLowPriority ? LOW_PRIORITY_CACHE_MODULO : HIGH_PRIORITY_CACHE_MODULO; @@ -221,7 +212,7 @@ class RLottie { name: 'init', args: [ this.key, - this.animationData, + this.tgsUrl, this.imgSize, this.params.isLowPriority, this.onRendererInit.bind(this), @@ -247,16 +238,16 @@ class RLottie { } } - changeData(animationData: string) { + changeData(tgsUrl: string) { this.pause(); - this.animationData = animationData; + this.tgsUrl = tgsUrl; this.initConfig(); workers[this.workerIndex].request({ name: 'changeData', args: [ this.key, - this.animationData, + this.tgsUrl, this.params.isLowPriority, this.onChangeData.bind(this), ], @@ -291,6 +282,7 @@ class RLottie { this.lastRenderAt = undefined; } + this.isEnded = false; this.isAnimating = true; this.isWaiting = false; @@ -359,6 +351,7 @@ class RLottie { if (delta > 0 && (frameIndex === this.framesCount! - 1 || expectedNextFrameIndex > this.framesCount! - 1)) { if (this.params.noLoop) { this.isAnimating = false; + this.isEnded = true; this.onEnded?.(); return false; } @@ -369,6 +362,7 @@ class RLottie { } else if (delta < 0 && (frameIndex === 0 || expectedNextFrameIndex < 0)) { if (this.params.noLoop) { this.isAnimating = false; + this.isEnded = true; this.onEnded?.(); return false; } @@ -379,10 +373,10 @@ class RLottie { } else if ( this.stopFrameIndex !== undefined && (frameIndex === this.stopFrameIndex - || ( - (delta > 0 && expectedNextFrameIndex > this.stopFrameIndex) - || (delta < 0 && expectedNextFrameIndex < this.stopFrameIndex) - )) + || ( + (delta > 0 && expectedNextFrameIndex > this.stopFrameIndex) + || (delta < 0 && expectedNextFrameIndex < this.stopFrameIndex) + )) ) { this.stopFrameIndex = undefined; this.isAnimating = false; diff --git a/src/lib/rlottie/rlottie.worker.ts b/src/lib/rlottie/rlottie.worker.ts index 7a5eeb5a8..e4fb9f4d2 100644 --- a/src/lib/rlottie/rlottie.worker.ts +++ b/src/lib/rlottie/rlottie.worker.ts @@ -39,7 +39,7 @@ const renderers = new Map { diff --git a/src/util/mediaLoader.ts b/src/util/mediaLoader.ts index 32abadbf4..65df847bc 100644 --- a/src/util/mediaLoader.ts +++ b/src/util/mediaLoader.ts @@ -19,7 +19,6 @@ import { webpToPng } from './webpToPng'; const asCacheApiType = { [ApiMediaFormat.BlobUrl]: cacheApi.Type.Blob, - [ApiMediaFormat.Lottie]: cacheApi.Type.Blob, [ApiMediaFormat.Text]: cacheApi.Type.Text, [ApiMediaFormat.Progressive]: undefined, [ApiMediaFormat.Stream]: undefined,