858 lines
28 KiB
TypeScript
858 lines
28 KiB
TypeScript
/*
|
|
* https://github.com/morethanwords/tweb
|
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
|
*/
|
|
|
|
import IS_WEBP_SUPPORTED from '../../environment/webpSupport';
|
|
import assumeType from '../../helpers/assumeType';
|
|
import getPathFromBytes from '../../helpers/bytes/getPathFromBytes';
|
|
import deferredPromise from '../../helpers/cancellablePromise';
|
|
import computeLockColor from '../../helpers/computeLockColor';
|
|
import cancelEvent from '../../helpers/dom/cancelEvent';
|
|
import {attachClickEvent} from '../../helpers/dom/clickEvent';
|
|
import createVideo from '../../helpers/dom/createVideo';
|
|
import findUpClassName from '../../helpers/dom/findUpClassName';
|
|
import renderImageFromUrl, {renderImageFromUrlPromise} from '../../helpers/dom/renderImageFromUrl';
|
|
import getImageFromStrippedThumb from '../../helpers/getImageFromStrippedThumb';
|
|
import getPreviewURLFromThumb from '../../helpers/getPreviewURLFromThumb';
|
|
import makeError from '../../helpers/makeError';
|
|
import {makeMediaSize} from '../../helpers/mediaSize';
|
|
import mediaSizes from '../../helpers/mediaSizes';
|
|
import {Middleware} from '../../helpers/middleware';
|
|
import onMediaLoad from '../../helpers/onMediaLoad';
|
|
import {isSavingLottiePreview, saveLottiePreview} from '../../helpers/saveLottiePreview';
|
|
import throttle from '../../helpers/schedulers/throttle';
|
|
import sequentialDom from '../../helpers/sequentialDom';
|
|
import {PhotoSize, VideoSize} from '../../layer';
|
|
import {MyDocument} from '../../lib/appManagers/appDocsManager';
|
|
import appDownloadManager from '../../lib/appManagers/appDownloadManager';
|
|
import appImManager from '../../lib/appManagers/appImManager';
|
|
import {AppManagers} from '../../lib/appManagers/managers';
|
|
import getServerMessageId from '../../lib/appManagers/utils/messageId/getServerMessageId';
|
|
import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize';
|
|
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
|
|
import lottieLoader from '../../lib/rlottie/lottieLoader';
|
|
import type RLottiePlayer from '../../lib/rlottie/rlottiePlayer';
|
|
import rootScope from '../../lib/rootScope';
|
|
import type {ThumbCache} from '../../lib/storages/thumbs';
|
|
import webpWorkerController from '../../lib/webp/webpWorkerController';
|
|
import {Awaited, SendMessageEmojiInteractionData} from '../../types';
|
|
import {getEmojiToneIndex} from '../../vendor/emoji';
|
|
import animationIntersector, {AnimationItemGroup} from '../animationIntersector';
|
|
import LazyLoadQueue from '../lazyLoadQueue';
|
|
import PopupStickers from '../popups/stickers';
|
|
import {hideToast, toastNew} from '../toast';
|
|
import wrapStickerAnimation from './stickerAnimation';
|
|
import framesCache from '../../helpers/framesCache';
|
|
import {IS_MOBILE} from '../../environment/userAgent';
|
|
import liteMode, {LiteModeKey} from '../../helpers/liteMode';
|
|
import PopupElement from '../popups';
|
|
|
|
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp#L40
|
|
export const STICKER_EFFECT_MULTIPLIER = 1 + 0.245 * 2;
|
|
const EMOJI_EFFECT_MULTIPLIER = 3;
|
|
|
|
const locksUrls: {[docId: string]: string} = {};
|
|
|
|
export const videosCache: {[key: string]: Promise<any>} = {};
|
|
|
|
const onAnimationEnd = (element: HTMLElement, onAnimationEnd: () => void, timeout: number) => {
|
|
const onEnd = () => {
|
|
element.removeEventListener('animationend', onEnd);
|
|
onAnimationEnd();
|
|
clearTimeout(_timeout);
|
|
};
|
|
element.addEventListener('animationend', onEnd);
|
|
const _timeout = setTimeout(onEnd, timeout);
|
|
};
|
|
|
|
export default async function wrapSticker({doc, div, middleware, loadStickerMiddleware, lazyLoadQueue, exportLoad, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn, needUpscale, skipRatio, static: asStatic, managers = rootScope.managers, fullThumb, isOut, noPremium, withLock, relativeEffect, loopEffect, isCustomEmoji, syncedVideo, liteModeKey, isEffect}: {
|
|
doc: MyDocument,
|
|
div: HTMLElement | HTMLElement[],
|
|
middleware?: Middleware,
|
|
loadStickerMiddleware?: Middleware,
|
|
lazyLoadQueue?: LazyLoadQueue,
|
|
exportLoad?: number,
|
|
group?: AnimationItemGroup,
|
|
play?: boolean,
|
|
onlyThumb?: boolean,
|
|
emoji?: string,
|
|
width?: number,
|
|
height?: number,
|
|
withThumb?: boolean,
|
|
loop?: RLottiePlayer['loop'],
|
|
loadPromises?: Promise<any>[],
|
|
needFadeIn?: boolean,
|
|
needUpscale?: boolean,
|
|
skipRatio?: number,
|
|
static?: boolean,
|
|
managers?: AppManagers,
|
|
fullThumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
|
|
isOut?: boolean,
|
|
noPremium?: boolean,
|
|
withLock?: boolean,
|
|
relativeEffect?: boolean,
|
|
loopEffect?: boolean,
|
|
isCustomEmoji?: boolean,
|
|
syncedVideo?: boolean,
|
|
liteModeKey?: LiteModeKey | false,
|
|
isEffect?: boolean
|
|
}) {
|
|
div = Array.isArray(div) ? div : [div];
|
|
|
|
liteModeKey ??= 'stickers_panel';
|
|
|
|
if(isCustomEmoji) {
|
|
emoji = doc.stickerEmojiRaw;
|
|
}
|
|
|
|
const stickerType = doc.sticker;
|
|
if(stickerType === 1) {
|
|
asStatic = true;
|
|
}
|
|
|
|
if(!width && !height) {
|
|
const sizes = mediaSizes.active;
|
|
const boxSize = emoji ? sizes.emojiSticker : (doc.animated ? sizes.animatedSticker : sizes.staticSticker);
|
|
const size = makeMediaSize(doc.w, doc.h).aspectFitted(boxSize);
|
|
width = size.width;
|
|
height = size.height;
|
|
}
|
|
|
|
if(stickerType === 2) {
|
|
// LottieLoader.loadLottie();
|
|
lottieLoader.loadLottieWorkers();
|
|
}
|
|
|
|
loop = !!(!emoji || isCustomEmoji) && loop;
|
|
|
|
div.forEach((div) => {
|
|
div.dataset.docId = '' + doc.id;
|
|
if(emoji) {
|
|
div.dataset.stickerEmoji = emoji;
|
|
}
|
|
|
|
div.dataset.stickerPlay = '' + +(play || false);
|
|
div.dataset.stickerLoop = '' + +(loop || false);
|
|
|
|
div.classList.add('media-sticker-wrapper');
|
|
});
|
|
|
|
if(play && !liteMode.isAvailable(liteModeKey) && !isCustomEmoji && !isEffect) {
|
|
play = false;
|
|
loop = false;
|
|
}
|
|
|
|
/* if(stickerType === 3) {
|
|
const videoRes = wrapVideo({
|
|
doc,
|
|
boxWidth: width,
|
|
boxHeight: height,
|
|
container: div,
|
|
group,
|
|
lazyLoadQueue,
|
|
middleware,
|
|
withoutPreloader: true,
|
|
loadPromises,
|
|
noPlayButton: true,
|
|
noInfo: true
|
|
});
|
|
|
|
if(videoRes.thumb) {
|
|
if(videoRes.thumb.images.thumb) {
|
|
videoRes.thumb.images.thumb.classList.add('media-sticker', 'thumbnail');
|
|
}
|
|
|
|
if(videoRes.thumb.images.full) {
|
|
videoRes.thumb.images.full.classList.add('media-sticker');
|
|
}
|
|
}
|
|
|
|
return videoRes.loadPromise;
|
|
} */
|
|
|
|
// console.log('wrap sticker', doc, div, onlyThumb);
|
|
|
|
let cacheContext: ThumbCache;
|
|
const getCacheContext = async(type: string = cacheContext?.type) => {
|
|
return cacheContext = await managers.thumbsStorage.getCacheContext(doc, type);
|
|
};
|
|
|
|
const isAnimated = !asStatic && (stickerType === 2 || stickerType === 3);
|
|
|
|
const effectThumb = getStickerEffectThumb(doc);
|
|
if(isOut !== undefined && effectThumb && !isOut) {
|
|
div.forEach((div) => div.classList.add('reflect-x'));
|
|
}
|
|
|
|
const willHaveLock = effectThumb && withLock;
|
|
if(willHaveLock) {
|
|
const lockUrl = locksUrls[doc.id];
|
|
div.forEach((div) => {
|
|
div.classList.add('is-premium-sticker', 'tgico-premium_lock');
|
|
lockUrl && div.style.setProperty('--lock-url', `url(${lockUrl})`);
|
|
});
|
|
}
|
|
|
|
if(asStatic && stickerType !== 1) {
|
|
const thumb = choosePhotoSize(doc, width, height, false) as PhotoSize.photoSize;
|
|
await getCacheContext(thumb.type);
|
|
} else {
|
|
await getCacheContext(fullThumb?.type);
|
|
}
|
|
|
|
const toneIndex = emoji && !isCustomEmoji ? getEmojiToneIndex(emoji) : -1;
|
|
const downloaded = cacheContext.downloaded && !needFadeIn;
|
|
|
|
const isThumbNeededForType = isAnimated;
|
|
const lottieCachedThumb = stickerType === 2 || stickerType === 3 ? await managers.appDocsManager.getLottieCachedThumb(doc.id, toneIndex) : undefined;
|
|
|
|
const ret = {render: undefined as typeof loadPromise, load: undefined as typeof load};
|
|
let loadThumbPromise = deferredPromise<void>();
|
|
let haveThumbCached = false;
|
|
if((
|
|
doc.thumbs?.length ||
|
|
lottieCachedThumb
|
|
) &&
|
|
!div[0].firstElementChild && (
|
|
!downloaded ||
|
|
isThumbNeededForType ||
|
|
onlyThumb
|
|
) && withThumb !== false/* && doc.thumbs[0]._ !== 'photoSizeEmpty' */
|
|
) {
|
|
let thumb = lottieCachedThumb || doc.thumbs[0];
|
|
|
|
// console.log('wrap sticker', thumb, div);
|
|
|
|
const afterRender = (div: HTMLElement, thumbImage: HTMLElement) => {
|
|
if(!div.childElementCount) {
|
|
sequentialDom.mutateElement(div, () => {
|
|
if(!div.childElementCount) {
|
|
thumbImage.classList.add('media-sticker', 'thumbnail');
|
|
div.append(thumbImage);
|
|
}
|
|
|
|
loadThumbPromise.resolve();
|
|
});
|
|
} else {
|
|
loadThumbPromise.resolve();
|
|
}
|
|
};
|
|
|
|
if('url' in thumb) {
|
|
haveThumbCached = true;
|
|
div.forEach((div) => {
|
|
const thumbImage = new Image();
|
|
renderImageFromUrl(thumbImage, (thumb as any).url, () => afterRender(div, thumbImage));
|
|
});
|
|
} else if('bytes' in thumb) {
|
|
if(thumb._ === 'photoPathSize') {
|
|
if(!thumb.bytes.length) {
|
|
thumb = doc.thumbs.find((t) => (t as PhotoSize.photoStrippedSize).bytes?.length) || thumb;
|
|
}
|
|
|
|
const d = getPathFromBytes((thumb as PhotoSize.photoStrippedSize).bytes);
|
|
const ns = 'http://www.w3.org/2000/svg';
|
|
const svg = document.createElementNS(ns, 'svg');
|
|
svg.classList.add('rlottie-vector', 'media-sticker', 'thumbnail');
|
|
svg.setAttributeNS(null, 'viewBox', `0 0 ${doc.w || 512} ${doc.h || 512}`);
|
|
|
|
// const defs = document.createElementNS(ns, 'defs');
|
|
// const linearGradient = document.createElementNS(ns, 'linearGradient');
|
|
// linearGradient.setAttributeNS(null, 'id', 'g');
|
|
// linearGradient.setAttributeNS(null, 'x1', '-300%');
|
|
// linearGradient.setAttributeNS(null, 'x2', '-200%');
|
|
// linearGradient.setAttributeNS(null, 'y1', '0');
|
|
// linearGradient.setAttributeNS(null, 'y2', '0');
|
|
// const stops = [
|
|
// ['-10%', '.1'],
|
|
// ['30%', '.07'],
|
|
// ['70%', '.07'],
|
|
// ['110%', '.1']
|
|
// ].map(([offset, stopOpacity]) => {
|
|
// const stop = document.createElementNS(ns, 'stop');
|
|
// stop.setAttributeNS(null, 'offset', offset);
|
|
// stop.setAttributeNS(null, 'stop-opacity', stopOpacity);
|
|
// return stop;
|
|
// });
|
|
// const animates = [
|
|
// ['-300%', '1200%'],
|
|
// ['-200%', '1300%']
|
|
// ].map(([from, to], idx) => {
|
|
// const animate = document.createElementNS(ns, 'animate');
|
|
// animate.setAttributeNS(null, 'attributeName', 'x' + (idx + 1));
|
|
// animate.setAttributeNS(null, 'from', from);
|
|
// animate.setAttributeNS(null, 'to', to);
|
|
// animate.setAttributeNS(null, 'dur', '3s');
|
|
// animate.setAttributeNS(null, 'repeatCount', 'indefinite');
|
|
// return animate;
|
|
// });
|
|
// linearGradient.append(...stops, ...animates);
|
|
// defs.append(linearGradient);
|
|
// svg.append(defs);
|
|
|
|
const path = document.createElementNS(ns, 'path');
|
|
path.setAttributeNS(null, 'd', d);
|
|
if(liteMode.isAvailable('animations') && !isCustomEmoji) path.setAttributeNS(null, 'fill', 'url(#g)');
|
|
svg.append(path);
|
|
div.forEach((div, idx) => div.append(idx > 0 ? svg.cloneNode(true) : svg));
|
|
haveThumbCached = true;
|
|
loadThumbPromise.resolve();
|
|
} else if(toneIndex <= 0) {
|
|
const r = () => {
|
|
(div as HTMLElement[]).forEach((div) => {
|
|
const thumbImage = new Image();
|
|
const url = getPreviewURLFromThumb(doc, thumb as PhotoSize.photoStrippedSize, true);
|
|
renderImageFromUrl(thumbImage, url, () => afterRender(div, thumbImage));
|
|
});
|
|
};
|
|
|
|
if((IS_WEBP_SUPPORTED || doc.pFlags.stickerThumbConverted || cacheContext.url)/* && false */) {
|
|
haveThumbCached = true;
|
|
r();
|
|
} else {
|
|
haveThumbCached = true;
|
|
webpWorkerController.convert('main-' + doc.id, thumb.bytes).then((bytes) => {
|
|
managers.appDocsManager.saveWebPConvertedStrippedThumb(doc.id, bytes);
|
|
(thumb as PhotoSize.photoStrippedSize).bytes = bytes;
|
|
doc.pFlags.stickerThumbConverted = true;
|
|
|
|
if((middleware && !middleware()) || (div as HTMLElement[])[0].childElementCount) {
|
|
loadThumbPromise.resolve();
|
|
return;
|
|
}
|
|
|
|
r();
|
|
}).catch(() => loadThumbPromise.resolve());
|
|
}
|
|
}
|
|
} else if(((stickerType === 2 && toneIndex <= 0) || stickerType === 3) && (withThumb || onlyThumb)) {
|
|
const load = async() => {
|
|
if((div as HTMLElement[])[0].childElementCount || (middleware && !middleware())) {
|
|
loadThumbPromise.resolve();
|
|
return;
|
|
}
|
|
|
|
const r = (div: HTMLElement, thumbImage: HTMLElement, url: string) => {
|
|
if(div.childElementCount || (middleware && !middleware())) {
|
|
loadThumbPromise.resolve();
|
|
return;
|
|
}
|
|
|
|
if(!url) afterRender(div, thumbImage);
|
|
else renderImageFromUrl(thumbImage, url, () => afterRender(div, thumbImage));
|
|
};
|
|
|
|
await getCacheContext();
|
|
(div as HTMLElement[]).forEach((div) => {
|
|
if(cacheContext.url) {
|
|
r(div, new Image(), cacheContext.url);
|
|
} else if('bytes' in thumb) {
|
|
const res = getImageFromStrippedThumb(doc, thumb as PhotoSize.photoStrippedSize, true);
|
|
res.loadPromise.then(() => r(div, res.image, ''));
|
|
|
|
// return managers.appDocsManager.getThumbURL(doc, thumb as PhotoSize.photoStrippedSize).promise.then(r);
|
|
} else {
|
|
appDownloadManager.downloadMediaURL({
|
|
media: doc,
|
|
thumb: thumb as PhotoSize
|
|
}).then(async(url) => {
|
|
return r(div, new Image(), url);
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
if(lazyLoadQueue && onlyThumb) {
|
|
lazyLoadQueue.push({div: div[0], load});
|
|
loadThumbPromise.resolve();
|
|
return ret;
|
|
} else {
|
|
load();
|
|
|
|
if((thumb as any).url) {
|
|
haveThumbCached = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(loadPromises && haveThumbCached) {
|
|
loadPromises.push(loadThumbPromise);
|
|
}
|
|
|
|
if(onlyThumb/* || true */) { // for sticker panel
|
|
return ret;
|
|
}
|
|
|
|
const middlewareError = makeError('MIDDLEWARE');
|
|
const load = async() => {
|
|
if(middleware && !middleware()) {
|
|
throw middlewareError;
|
|
}
|
|
|
|
if(stickerType === 2 && !asStatic) {
|
|
const blob = await appDownloadManager.downloadMedia({media: doc, queueId: lazyLoadQueue?.queueId, thumb: fullThumb});
|
|
if(middleware && !middleware()) {
|
|
throw middlewareError;
|
|
}
|
|
|
|
const animation = await lottieLoader.loadAnimationWorker({
|
|
container: (div as HTMLElement[])[0],
|
|
loop,
|
|
autoplay: play,
|
|
animationData: blob,
|
|
width,
|
|
height,
|
|
name: 'doc' + doc.id,
|
|
needUpscale,
|
|
skipRatio,
|
|
toneIndex,
|
|
sync: isCustomEmoji,
|
|
middleware: loadStickerMiddleware ?? middleware,
|
|
group,
|
|
liteModeKey: liteModeKey || undefined
|
|
});
|
|
|
|
// const deferred = deferredPromise<void>();
|
|
|
|
const setLockColor = willHaveLock ? () => {
|
|
const lockUrl = locksUrls[doc.id] ??= computeLockColor(animation.canvas[0]);
|
|
(div as HTMLElement[]).forEach((div) => div.style.setProperty('--lock-url', `url(${lockUrl})`));
|
|
} : undefined;
|
|
|
|
const onFirstFrame = (container: HTMLElement, canvas: HTMLCanvasElement) => {
|
|
const element = container.firstElementChild !== canvas && container.firstElementChild as HTMLElement;
|
|
if(needFadeIn !== false) {
|
|
needFadeIn = (needFadeIn || !element || element.tagName === 'svg') && liteMode.isAvailable('animations');
|
|
}
|
|
|
|
const cb = () => {
|
|
if(element && element !== canvas && element.tagName !== 'DIV') {
|
|
element.remove();
|
|
}
|
|
};
|
|
|
|
if(!needFadeIn) {
|
|
if(element) {
|
|
sequentialDom.mutate(cb);
|
|
}
|
|
} else {
|
|
sequentialDom.mutate(() => {
|
|
canvas && canvas.classList.add('fade-in');
|
|
if(element) {
|
|
element.classList.add('fade-out');
|
|
}
|
|
|
|
onAnimationEnd(canvas || element, () => {
|
|
sequentialDom.mutate(() => {
|
|
canvas && canvas.classList.remove('fade-in');
|
|
cb();
|
|
});
|
|
}, 400);
|
|
});
|
|
}
|
|
};
|
|
|
|
animation.addEventListener('firstFrame', () => {
|
|
const canvas = animation.canvas[0];
|
|
if(withThumb !== false || isCustomEmoji) {
|
|
saveLottiePreview(doc, canvas, toneIndex);
|
|
}
|
|
|
|
if(willHaveLock) {
|
|
setLockColor();
|
|
}
|
|
|
|
if(!isCustomEmoji) {
|
|
(div as HTMLElement[]).forEach((container, idx) => {
|
|
onFirstFrame(container, animation.canvas[idx]);
|
|
});
|
|
}
|
|
}, {once: true});
|
|
|
|
if(emoji) {
|
|
managers.appStickersManager.preloadAnimatedEmojiStickerAnimation(emoji);
|
|
}
|
|
|
|
return animation;
|
|
|
|
// return deferred;
|
|
// await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
} else if(asStatic || stickerType === 3) {
|
|
const isSingleVideo = isAnimated && syncedVideo;
|
|
const cacheName = isSingleVideo ? framesCache.generateName('' + doc.id, 0, 0, undefined, undefined) : undefined;
|
|
|
|
const cachePromise = videosCache[cacheName];
|
|
if(cachePromise) {
|
|
return cachePromise as typeof promise;
|
|
}
|
|
|
|
const d = isSingleVideo ? (div as HTMLElement[]).slice(0, 1) : div as HTMLElement[];
|
|
const media: HTMLElement[] = d.map(() => {
|
|
let media: HTMLElement;
|
|
if(asStatic) {
|
|
media = new Image();
|
|
} else {
|
|
const video = media = createVideo();
|
|
video.muted = true;
|
|
if(play) video.autoplay = true;
|
|
if(loop) video.loop = true;
|
|
|
|
if(loop && typeof(loop) === 'number') {
|
|
let previousTime = 0, playedTimes = 0;
|
|
function onTimeupdate(this: HTMLVideoElement) {
|
|
if(previousTime > this.currentTime && ++playedTimes === loop as number) {
|
|
this.autoplay = false;
|
|
this.loop = false;
|
|
this.pause();
|
|
video.removeEventListener('timeupdate', onTimeupdate);
|
|
}
|
|
|
|
previousTime = this.currentTime;
|
|
}
|
|
|
|
video.addEventListener('timeupdate', onTimeupdate);
|
|
}
|
|
}
|
|
|
|
media.classList.add('media-sticker');
|
|
return media;
|
|
});
|
|
|
|
const thumbImage = (div as HTMLElement[]).map((div, idx) => (div.firstElementChild as HTMLElement) !== media[idx] && div.firstElementChild) as HTMLElement[];
|
|
if(needFadeIn !== false) {
|
|
needFadeIn = (needFadeIn || !downloaded || (asStatic ? thumbImage[0] : (!thumbImage[0] || thumbImage[0].tagName === 'svg'))) && liteMode.isAvailable('animations');
|
|
}
|
|
|
|
if(needFadeIn) {
|
|
media.forEach((media) => media.classList.add('fade-in'));
|
|
}
|
|
|
|
const promise = new Promise<HTMLVideoElement[] | HTMLImageElement[]>(async(resolve, reject) => {
|
|
const r = async() => {
|
|
if(middleware && !middleware()) {
|
|
reject(middlewareError);
|
|
return;
|
|
}
|
|
|
|
const mediaLength = media.length;
|
|
const loaded: HTMLElement[] = [];
|
|
const onLoad = (div: HTMLElement, media: HTMLElement, thumbImage: HTMLElement) => {
|
|
sequentialDom.mutateElement(div, () => {
|
|
if(middleware && !middleware()) {
|
|
reject(middlewareError);
|
|
return;
|
|
}
|
|
|
|
if(!media) {
|
|
if(!isSingleVideo || !isAnimated) {
|
|
thumbImage?.remove();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if(media as HTMLVideoElement) {
|
|
const w = width * window.devicePixelRatio;
|
|
const h = height * window.devicePixelRatio;
|
|
if(!isSavingLottiePreview(doc, toneIndex, w, h)) {
|
|
// const perf = performance.now();
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = w;
|
|
canvas.height = h;
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.drawImage(media as HTMLVideoElement, 0, 0, canvas.width, canvas.height);
|
|
saveLottiePreview(doc, canvas, toneIndex);
|
|
// console.log('perf', performance.now() - perf);
|
|
}
|
|
}
|
|
|
|
if(isSingleVideo) {
|
|
resolve(media as any);
|
|
return;
|
|
}
|
|
|
|
div.append(media);
|
|
|
|
if(needFadeIn) {
|
|
thumbImage && thumbImage.classList.add('fade-out');
|
|
onAnimationEnd(media, () => {
|
|
media.classList.remove('fade-in');
|
|
thumbImage?.remove();
|
|
}, 400);
|
|
} else {
|
|
thumbImage?.remove();
|
|
}
|
|
|
|
if(isAnimated) {
|
|
animationIntersector.addAnimation({
|
|
animation: media as HTMLVideoElement,
|
|
observeElement: div,
|
|
group,
|
|
controlled: middleware,
|
|
liteModeKey: liteModeKey || undefined
|
|
});
|
|
}
|
|
|
|
if(loaded.push(media) === mediaLength) {
|
|
resolve(loaded as any);
|
|
}
|
|
});
|
|
};
|
|
|
|
await getCacheContext();
|
|
let lastPromise: Promise<any>;
|
|
(div as HTMLElement[]).forEach((div, idx) => {
|
|
const _media = media[idx];
|
|
const cb = () => onLoad(div, _media, thumbImage[idx]);
|
|
if(_media) lastPromise = renderImageFromUrlPromise(_media, cacheContext.url);
|
|
lastPromise.then(cb);
|
|
});
|
|
};
|
|
|
|
await getCacheContext();
|
|
if(cacheContext.url) r();
|
|
else {
|
|
let promise: Promise<any>;
|
|
if(stickerType !== 1 && asStatic) {
|
|
const thumb = choosePhotoSize(doc, width, height, false) as PhotoSize.photoSize;
|
|
// promise = managers.appDocsManager.getThumbURL(doc, thumb).promise
|
|
promise = appDownloadManager.downloadMediaURL({media: doc, thumb, queueId: lazyLoadQueue?.queueId});
|
|
} else {
|
|
promise = appDownloadManager.downloadMediaURL({media: doc, queueId: lazyLoadQueue?.queueId});
|
|
}
|
|
|
|
promise.then(r, reject);
|
|
}
|
|
});
|
|
|
|
if(cacheName) {
|
|
videosCache[cacheName] = promise as any;
|
|
loadStickerMiddleware && promise.finally(() => {
|
|
if(!loadStickerMiddleware()) {
|
|
delete videosCache[cacheName];
|
|
}
|
|
});
|
|
}
|
|
|
|
return promise;
|
|
}
|
|
};
|
|
|
|
if(exportLoad && ((exportLoad === 1 && (!downloaded || isAnimated)) || exportLoad === 2)) {
|
|
ret.load = load;
|
|
return ret;
|
|
}
|
|
|
|
const loadPromise: Promise<Awaited<ReturnType<typeof load>> | void> = lazyLoadQueue && (!downloaded || isAnimated) ?
|
|
(lazyLoadQueue.push({div: div[0], load}), Promise.resolve()) :
|
|
load();
|
|
|
|
if(downloaded && (asStatic/* || stickerType === 3 */)) {
|
|
loadThumbPromise = loadPromise as any;
|
|
if(loadPromises) {
|
|
loadPromises.push(loadThumbPromise);
|
|
}
|
|
}
|
|
|
|
if(stickerType === 2 && effectThumb && isOut !== undefined && !noPremium) {
|
|
attachStickerEffectHandler({
|
|
container: div[0],
|
|
doc,
|
|
managers,
|
|
middleware,
|
|
isOut,
|
|
width,
|
|
loadPromise,
|
|
relativeEffect,
|
|
loopEffect
|
|
});
|
|
}
|
|
|
|
ret.render = loadPromise as any;
|
|
return ret;
|
|
}
|
|
|
|
function attachStickerEffectHandler({container, doc, managers, middleware, isOut, width, loadPromise, relativeEffect, loopEffect}: {
|
|
container: HTMLElement,
|
|
doc: MyDocument,
|
|
managers: AppManagers,
|
|
middleware: Middleware,
|
|
isOut: boolean,
|
|
width: number,
|
|
loadPromise: Promise<any>,
|
|
relativeEffect?: boolean,
|
|
loopEffect?: boolean
|
|
}) {
|
|
managers.appStickersManager.preloadSticker(doc.id, true);
|
|
|
|
let playing = false;
|
|
attachClickEvent(container, async(e) => {
|
|
const isAvailable = liteMode.isAvailable('effects_premiumstickers') || relativeEffect;
|
|
cancelEvent(e);
|
|
if(!e.isTrusted && !isAvailable) {
|
|
return;
|
|
}
|
|
|
|
if(playing || !isAvailable) {
|
|
const a = document.createElement('a');
|
|
a.onclick = () => {
|
|
hideToast();
|
|
PopupElement.createPopup(PopupStickers, doc.stickerSetInput).show();
|
|
};
|
|
|
|
toastNew({
|
|
langPackKey: 'Sticker.Premium.Click.Info',
|
|
langPackArguments: [a]
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
playing = true;
|
|
|
|
await loadPromise;
|
|
const {animationDiv, stickerPromise} = wrapStickerAnimation({
|
|
doc,
|
|
middleware,
|
|
side: isOut ? 'right' : 'left',
|
|
size: width * STICKER_EFFECT_MULTIPLIER,
|
|
target: container,
|
|
play: true,
|
|
fullThumb: getStickerEffectThumb(doc),
|
|
relativeEffect,
|
|
loopEffect
|
|
});
|
|
|
|
if(isOut !== undefined && !isOut/* && !relativeEffect */) {
|
|
animationDiv.classList.add('reflect-x');
|
|
}
|
|
|
|
stickerPromise.then((player) => {
|
|
player.addEventListener('destroy', () => {
|
|
playing = false;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function onEmojiStickerClick({event, container, managers, peerId, middleware}: {
|
|
event: Event,
|
|
container: HTMLElement,
|
|
managers: AppManagers,
|
|
peerId: PeerId,
|
|
middleware: Middleware
|
|
}) {
|
|
cancelEvent(event);
|
|
|
|
const bubble = findUpClassName(container, 'bubble');
|
|
const emoji = container.dataset.stickerEmoji;
|
|
|
|
const animation = !container.classList.contains('custom-emoji') ? lottieLoader.getAnimation(container) : undefined;
|
|
if(animation?.paused) {
|
|
const doc = await managers.appStickersManager.getAnimatedEmojiSoundDocument(emoji);
|
|
if(doc) {
|
|
const audio = document.createElement('audio');
|
|
audio.style.display = 'none';
|
|
container.parentElement.append(audio);
|
|
|
|
try {
|
|
const url = await appDownloadManager.downloadMediaURL({media: doc});
|
|
|
|
audio.src = url;
|
|
audio.play();
|
|
await onMediaLoad(audio, undefined, true);
|
|
|
|
audio.addEventListener('ended', () => {
|
|
audio.src = '';
|
|
audio.remove();
|
|
}, {once: true});
|
|
} catch(err) {
|
|
|
|
}
|
|
}
|
|
|
|
animation.autoplay = true;
|
|
animation.restart();
|
|
}
|
|
|
|
if(!peerId.isUser() || !liteMode.isAvailable('effects_emoji')) {
|
|
return;
|
|
}
|
|
|
|
const activeAnimations: Set<{}> = (container as any).activeAnimations ??= new Set();
|
|
if(activeAnimations.size >= 3) {
|
|
return;
|
|
}
|
|
|
|
const doc = await managers.appStickersManager.getAnimatedEmojiSticker(emoji, true);
|
|
if(!doc) {
|
|
return;
|
|
}
|
|
|
|
const data: SendMessageEmojiInteractionData = (container as any).emojiData ??= {
|
|
a: [],
|
|
v: 1
|
|
};
|
|
|
|
const sendInteractionThrottled: () => void = (container as any).sendInteractionThrottled ??= throttle(() => {
|
|
const length = data.a.length;
|
|
if(!length) {
|
|
return;
|
|
}
|
|
|
|
const firstTime = data.a[0].t;
|
|
|
|
data.a.forEach((a) => {
|
|
a.t = (a.t - firstTime) / 1000;
|
|
});
|
|
|
|
const bubble = findUpClassName(container, 'bubble');
|
|
managers.appMessagesManager.setTyping(appImManager.chat.peerId, {
|
|
_: 'sendMessageEmojiInteraction',
|
|
msg_id: getServerMessageId(+bubble.dataset.mid),
|
|
emoticon: emoji,
|
|
interaction: {
|
|
_: 'dataJSON',
|
|
data: JSON.stringify(data)
|
|
}
|
|
}, true, appImManager.chat.threadId);
|
|
|
|
data.a.length = 0;
|
|
}, 1000, false);
|
|
|
|
const o = {};
|
|
activeAnimations.add(o);
|
|
|
|
const isOut = bubble ? bubble.classList.contains('is-out') : undefined;
|
|
const {animationDiv} = wrapStickerAnimation({
|
|
doc,
|
|
middleware,
|
|
side: isOut ? 'right' : 'left',
|
|
size: 360,
|
|
target: container,
|
|
play: true,
|
|
withRandomOffset: true,
|
|
onUnmount: () => {
|
|
activeAnimations.delete(o);
|
|
}
|
|
});
|
|
|
|
if(isOut !== undefined && !isOut) {
|
|
animationDiv.classList.add('reflect-x');
|
|
}
|
|
|
|
// using a trick here: simulated event from interlocutor's interaction won't fire ours
|
|
if(event.isTrusted) {
|
|
data.a.push({
|
|
i: 1,
|
|
t: Date.now()
|
|
});
|
|
|
|
sendInteractionThrottled();
|
|
}
|
|
// });
|
|
}
|