Attachment Modal: Fix custom emoji playback in caption (#2759)
This commit is contained in:
parent
4090cf093d
commit
dd659cdb71
@ -16,7 +16,7 @@ import useSyncEffect from '../../hooks/useSyncEffect';
|
||||
|
||||
export type OwnProps = {
|
||||
ref?: RefObject<HTMLDivElement>;
|
||||
animationId?: string;
|
||||
renderId?: string;
|
||||
className?: string;
|
||||
style?: string;
|
||||
tgsUrl?: string;
|
||||
@ -60,7 +60,7 @@ setTimeout(ensureLottie, LOTTIE_LOAD_DELAY);
|
||||
|
||||
const AnimatedSticker: FC<OwnProps> = ({
|
||||
ref,
|
||||
animationId,
|
||||
renderId,
|
||||
className,
|
||||
style,
|
||||
tgsUrl,
|
||||
@ -86,7 +86,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
containerRef = ref;
|
||||
}
|
||||
|
||||
const containerId = useMemo(() => generateIdFor(ID_STORE, true), []);
|
||||
const viewId = useMemo(() => generateIdFor(ID_STORE, true), []);
|
||||
|
||||
const [animation, setAnimation] = useState<RLottieInstance>();
|
||||
const animationRef = useRef<RLottieInstance>();
|
||||
@ -128,11 +128,10 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
}
|
||||
|
||||
const newAnimation = RLottie.init(
|
||||
containerId,
|
||||
container,
|
||||
onLoad,
|
||||
animationId || generateIdFor(ID_STORE, true),
|
||||
tgsUrl,
|
||||
container,
|
||||
renderId || generateIdFor(ID_STORE, true),
|
||||
viewId,
|
||||
{
|
||||
noLoop,
|
||||
size,
|
||||
@ -141,6 +140,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
coords: sharedCanvasCoords,
|
||||
},
|
||||
color,
|
||||
onLoad,
|
||||
onEnded,
|
||||
onLoop,
|
||||
);
|
||||
@ -165,8 +165,8 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
});
|
||||
}
|
||||
}, [
|
||||
animation, animationId, tgsUrl, color, isLowPriority, noLoop, onLoad, quality, size, speed, onEnded, onLoop,
|
||||
containerId, sharedCanvas, sharedCanvasCoords,
|
||||
animation, renderId, tgsUrl, color, isLowPriority, noLoop, onLoad, quality, size, speed, onEnded, onLoop,
|
||||
viewId, sharedCanvas, sharedCanvasCoords,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -177,27 +177,27 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
animationRef.current?.removeContainer(containerId);
|
||||
animationRef.current?.removeView(viewId);
|
||||
};
|
||||
}, [containerId]);
|
||||
}, [viewId]);
|
||||
|
||||
const playAnimation = useCallback((shouldRestart = false) => {
|
||||
if (animation && (playRef.current || playSegmentRef.current)) {
|
||||
if (playSegmentRef.current) {
|
||||
animation.playSegment(playSegmentRef.current);
|
||||
} else {
|
||||
animation.play(shouldRestart, containerId);
|
||||
animation.play(shouldRestart, viewId);
|
||||
}
|
||||
}
|
||||
}, [animation, containerId]);
|
||||
}, [animation, viewId]);
|
||||
|
||||
const pauseAnimation = useCallback(() => {
|
||||
if (!animation) {
|
||||
return;
|
||||
}
|
||||
|
||||
animation.pause(containerId);
|
||||
}, [animation, containerId]);
|
||||
animation.pause(viewId);
|
||||
}, [animation, viewId]);
|
||||
|
||||
const freezeAnimation = useCallback(() => {
|
||||
isFrozen.current = true;
|
||||
@ -234,9 +234,9 @@ const AnimatedSticker: FC<OwnProps> = ({
|
||||
|
||||
useSyncEffect(([prevSharedCanvasCoords]) => {
|
||||
if (prevSharedCanvasCoords !== undefined && sharedCanvasCoords !== prevSharedCanvasCoords) {
|
||||
animation?.setSharedCanvasCoords(containerId, sharedCanvasCoords);
|
||||
animation?.setSharedCanvasCoords(viewId, sharedCanvasCoords);
|
||||
}
|
||||
}, [sharedCanvasCoords, containerId, animation]);
|
||||
}, [sharedCanvasCoords, viewId, animation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!animation) {
|
||||
|
||||
@ -121,7 +121,7 @@ const StickerView: FC<OwnProps> = ({
|
||||
useMedia(previewMediaHash, !shouldLoad || !shouldPreloadPreview, undefined, cacheBuster);
|
||||
|
||||
const randomIdPrefix = useMemo(() => generateIdFor(ID_STORE, true), []);
|
||||
const idKey = [
|
||||
const renderId = [
|
||||
(withSharedAnimation ? SHARED_PREFIX : randomIdPrefix), id, realSize, customColor?.join(','),
|
||||
].filter(Boolean).join('_');
|
||||
|
||||
@ -141,8 +141,8 @@ const StickerView: FC<OwnProps> = ({
|
||||
/>
|
||||
{isLottie ? (
|
||||
<AnimatedSticker
|
||||
key={idKey}
|
||||
animationId={idKey}
|
||||
key={renderId}
|
||||
renderId={renderId}
|
||||
size={realSize}
|
||||
className={buildClassName(
|
||||
styles.media,
|
||||
|
||||
@ -155,7 +155,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
sharedCanvasRef,
|
||||
sharedCanvasHqRef,
|
||||
absoluteContainerRef,
|
||||
isAttachmentModalInput ? 'attachment' : undefined,
|
||||
isAttachmentModalInput ? 'attachment' : 'composer',
|
||||
isActive,
|
||||
);
|
||||
|
||||
|
||||
@ -40,18 +40,18 @@ export default function useInputCustomEmojis(
|
||||
sharedCanvasRef: React.RefObject<HTMLCanvasElement>,
|
||||
sharedCanvasHqRef: React.RefObject<HTMLCanvasElement>,
|
||||
absoluteContainerRef: React.RefObject<HTMLElement>,
|
||||
prefixId?: string,
|
||||
prefixId: string,
|
||||
isActive?: boolean,
|
||||
) {
|
||||
const { rgbColor: textColor } = useDynamicColorListener(inputRef);
|
||||
const mapRef = useRef<Map<string, CustomEmojiPlayer>>(new Map());
|
||||
const playersById = useRef<Map<string, CustomEmojiPlayer>>(new Map());
|
||||
|
||||
const removeContainers = useCallback((ids: string[]) => {
|
||||
const clearPlayers = useCallback((ids: string[]) => {
|
||||
ids.forEach((id) => {
|
||||
const player = mapRef.current.get(id);
|
||||
const player = playersById.current.get(id);
|
||||
if (player) {
|
||||
player.destroy();
|
||||
mapRef.current.delete(id);
|
||||
playersById.current.delete(id);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
@ -59,17 +59,17 @@ export default function useInputCustomEmojis(
|
||||
const synchronizeElements = useCallback(() => {
|
||||
if (!inputRef.current || !sharedCanvasRef.current || !sharedCanvasHqRef.current) return;
|
||||
const global = getGlobal();
|
||||
const removedContainers = new Set(mapRef.current.keys());
|
||||
const customEmojies = Array.from(inputRef.current.querySelectorAll<HTMLElement>('.custom-emoji'));
|
||||
const playerIdsToClear = new Set(playersById.current.keys());
|
||||
const customEmojis = Array.from(inputRef.current.querySelectorAll<HTMLElement>('.custom-emoji'));
|
||||
|
||||
customEmojies.forEach((element) => {
|
||||
customEmojis.forEach((element) => {
|
||||
if (!element.dataset.uniqueId) {
|
||||
return;
|
||||
}
|
||||
const id = `${prefixId || ''}${element.dataset.uniqueId}${textColor?.join(',') || ''}`;
|
||||
const playerId = `${prefixId}${element.dataset.uniqueId}${textColor?.join(',') || ''}`;
|
||||
const documentId = element.dataset.documentId!;
|
||||
|
||||
removedContainers.delete(id);
|
||||
playerIdsToClear.delete(playerId);
|
||||
|
||||
const mediaUrl = getCustomEmojiMediaDataForInput(documentId);
|
||||
if (!mediaUrl) {
|
||||
@ -81,8 +81,8 @@ export default function useInputCustomEmojis(
|
||||
const x = round((elementBounds.left - canvasBounds.left) / canvasBounds.width, 4);
|
||||
const y = round((elementBounds.top - canvasBounds.top) / canvasBounds.height, 4);
|
||||
|
||||
if (mapRef.current.has(id)) {
|
||||
const player = mapRef.current.get(id)!;
|
||||
if (playersById.current.has(playerId)) {
|
||||
const player = playersById.current.get(playerId)!;
|
||||
player.updatePosition(x, y);
|
||||
return;
|
||||
}
|
||||
@ -92,14 +92,17 @@ export default function useInputCustomEmojis(
|
||||
return;
|
||||
}
|
||||
const isHq = customEmoji?.stickerSetInfo && selectIsAlwaysHighPriorityEmoji(global, customEmoji.stickerSetInfo);
|
||||
const renderId = [
|
||||
prefixId, documentId, textColor?.join(','),
|
||||
].filter(Boolean).join('_');
|
||||
|
||||
const animation = createPlayer({
|
||||
customEmoji,
|
||||
sharedCanvasRef,
|
||||
sharedCanvasHqRef,
|
||||
absoluteContainerRef,
|
||||
uniqueId: id,
|
||||
containerId: prefixId || id,
|
||||
renderId,
|
||||
viewId: playerId,
|
||||
mediaUrl,
|
||||
isHq,
|
||||
position: { x, y },
|
||||
@ -107,11 +110,11 @@ export default function useInputCustomEmojis(
|
||||
});
|
||||
animation.play();
|
||||
|
||||
mapRef.current.set(id, animation);
|
||||
playersById.current.set(playerId, animation);
|
||||
});
|
||||
|
||||
removeContainers(Array.from(removedContainers));
|
||||
}, [absoluteContainerRef, textColor, inputRef, prefixId, removeContainers, sharedCanvasHqRef, sharedCanvasRef]);
|
||||
clearPlayers(Array.from(playerIdsToClear));
|
||||
}, [absoluteContainerRef, textColor, inputRef, prefixId, clearPlayers, sharedCanvasHqRef, sharedCanvasRef]);
|
||||
|
||||
useEffect(() => {
|
||||
addCustomEmojiInputRenderCallback(synchronizeElements);
|
||||
@ -123,7 +126,7 @@ export default function useInputCustomEmojis(
|
||||
|
||||
useEffect(() => {
|
||||
if (!getHtml() || !inputRef.current || !sharedCanvasRef.current || !isActive) {
|
||||
removeContainers(Array.from(mapRef.current.keys()));
|
||||
clearPlayers(Array.from(playersById.current.keys()));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -131,7 +134,7 @@ export default function useInputCustomEmojis(
|
||||
fastRaf(() => {
|
||||
synchronizeElements();
|
||||
});
|
||||
}, [getHtml, synchronizeElements, inputRef, removeContainers, sharedCanvasRef, isActive]);
|
||||
}, [getHtml, synchronizeElements, inputRef, clearPlayers, sharedCanvasRef, isActive]);
|
||||
|
||||
useEffectWithPrevDeps(([prevTextColor]) => {
|
||||
if (textColor !== prevTextColor) {
|
||||
@ -148,13 +151,13 @@ export default function useInputCustomEmojis(
|
||||
useResizeObserver(sharedCanvasRef, throttledSynchronizeElements);
|
||||
|
||||
const freezeAnimation = useCallback(() => {
|
||||
mapRef.current.forEach((player) => {
|
||||
playersById.current.forEach((player) => {
|
||||
player.pause();
|
||||
});
|
||||
}, []);
|
||||
|
||||
const unfreezeAnimation = useCallback(() => {
|
||||
mapRef.current.forEach((player) => {
|
||||
playersById.current.forEach((player) => {
|
||||
player.play();
|
||||
});
|
||||
}, []);
|
||||
@ -174,8 +177,8 @@ function createPlayer({
|
||||
sharedCanvasRef,
|
||||
sharedCanvasHqRef,
|
||||
absoluteContainerRef,
|
||||
uniqueId,
|
||||
containerId,
|
||||
renderId,
|
||||
viewId,
|
||||
mediaUrl,
|
||||
position,
|
||||
isHq,
|
||||
@ -185,8 +188,8 @@ function createPlayer({
|
||||
sharedCanvasRef: React.RefObject<HTMLCanvasElement>;
|
||||
sharedCanvasHqRef: React.RefObject<HTMLCanvasElement>;
|
||||
absoluteContainerRef: React.RefObject<HTMLElement>;
|
||||
uniqueId: string;
|
||||
containerId: string;
|
||||
renderId: string;
|
||||
viewId: string;
|
||||
mediaUrl: string;
|
||||
position: { x: number; y: number };
|
||||
isHq?: boolean;
|
||||
@ -194,11 +197,10 @@ function createPlayer({
|
||||
}): CustomEmojiPlayer {
|
||||
if (customEmoji.isLottie) {
|
||||
const lottie = RLottie.init(
|
||||
containerId,
|
||||
isHq ? sharedCanvasHqRef.current! : sharedCanvasRef.current!,
|
||||
undefined,
|
||||
uniqueId,
|
||||
mediaUrl,
|
||||
isHq ? sharedCanvasHqRef.current! : sharedCanvasRef.current!,
|
||||
renderId,
|
||||
viewId,
|
||||
{
|
||||
size: SIZE,
|
||||
coords: position,
|
||||
@ -210,9 +212,9 @@ function createPlayer({
|
||||
return {
|
||||
play: () => lottie.play(),
|
||||
pause: () => lottie.pause(),
|
||||
destroy: () => lottie.removeContainer(uniqueId),
|
||||
destroy: () => lottie.removeView(viewId),
|
||||
updatePosition: (x: number, y: number) => {
|
||||
return lottie.setSharedCanvasCoords(uniqueId, { x, y });
|
||||
return lottie.setSharedCanvasCoords(viewId, { x, y });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { createConnector } from '../../util/PostMessageConnector';
|
||||
import { animate } from '../../util/animation';
|
||||
import cycleRestrict from '../../util/cycleRestrict';
|
||||
import { fastRaf } from '../../util/schedulers';
|
||||
import generateIdFor from '../../util/generateIdFor';
|
||||
|
||||
interface Params {
|
||||
noLoop?: boolean;
|
||||
@ -28,8 +29,9 @@ const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
|
||||
const LOW_PRIORITY_QUALITY_SIZE_THRESHOLD = 24;
|
||||
const HIGH_PRIORITY_CACHE_MODULO = IS_SAFARI ? 2 : 4;
|
||||
const LOW_PRIORITY_CACHE_MODULO = 0;
|
||||
const ID_STORE = {};
|
||||
|
||||
const instancesById = new Map<string, RLottie>();
|
||||
const instancesByRenderId = new Map<string, RLottie>();
|
||||
|
||||
const workers = new Array(MAX_WORKERS).fill(undefined).map(
|
||||
() => createConnector<RLottieApi>(new Worker(new URL('./rlottie.worker.ts', import.meta.url))),
|
||||
@ -39,7 +41,7 @@ let lastWorkerIndex = -1;
|
||||
class RLottie {
|
||||
// Config
|
||||
|
||||
private containers = new Map<string, {
|
||||
private views = new Map<string, {
|
||||
canvas: HTMLCanvasElement;
|
||||
ctx: CanvasRenderingContext2D;
|
||||
isLoaded?: boolean;
|
||||
@ -90,40 +92,46 @@ class RLottie {
|
||||
private lastRenderAt?: number;
|
||||
|
||||
static init(...args: ConstructorParameters<typeof RLottie>) {
|
||||
const [container, canvas, onLoad, id, , params] = args;
|
||||
let instance = instancesById.get(id);
|
||||
const [
|
||||
, canvas,
|
||||
renderId,
|
||||
viewId = generateIdFor(ID_STORE, true),
|
||||
params, ,
|
||||
onLoad,
|
||||
] = args;
|
||||
let instance = instancesByRenderId.get(renderId);
|
||||
|
||||
if (!instance) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
instance = new RLottie(...args);
|
||||
instancesById.set(id, instance);
|
||||
instancesByRenderId.set(renderId, instance);
|
||||
} else {
|
||||
instance.addContainer(container, canvas, onLoad, params?.coords);
|
||||
instance.addView(viewId, canvas, onLoad, params?.coords);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
constructor(
|
||||
containerId: string,
|
||||
container: HTMLDivElement | HTMLCanvasElement,
|
||||
onLoad: NoneToVoidFunction | undefined,
|
||||
private id: string,
|
||||
private tgsUrl: string,
|
||||
private params: Params = { },
|
||||
private container: HTMLDivElement | HTMLCanvasElement,
|
||||
private renderId: string,
|
||||
private viewId: string = generateIdFor(ID_STORE, true),
|
||||
private params: Params = {},
|
||||
private customColor?: [number, number, number],
|
||||
private onLoad?: NoneToVoidFunction | undefined,
|
||||
private onEnded?: (isDestroyed?: boolean) => void,
|
||||
private onLoop?: () => void,
|
||||
) {
|
||||
this.addContainer(containerId, container, onLoad, params.coords);
|
||||
this.addView(viewId, container, onLoad, params.coords);
|
||||
this.initConfig();
|
||||
this.initRenderer();
|
||||
}
|
||||
|
||||
public removeContainer(containerId: string) {
|
||||
public removeView(viewId: string) {
|
||||
const {
|
||||
canvas, ctx, isSharedCanvas, coords,
|
||||
} = this.containers.get(containerId)!;
|
||||
} = this.views.get(viewId)!;
|
||||
|
||||
if (isSharedCanvas) {
|
||||
ctx.clearRect(coords!.x, coords!.y, this.imgSize, this.imgSize);
|
||||
@ -131,9 +139,9 @@ class RLottie {
|
||||
canvas.remove();
|
||||
}
|
||||
|
||||
this.containers.delete(containerId);
|
||||
this.views.delete(viewId);
|
||||
|
||||
if (!this.containers.size) {
|
||||
if (!this.views.size) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
@ -142,9 +150,9 @@ class RLottie {
|
||||
return this.isAnimating || this.isWaiting;
|
||||
}
|
||||
|
||||
play(forceRestart = false, containerId?: string) {
|
||||
if (containerId) {
|
||||
this.containers.get(containerId)!.isPaused = false;
|
||||
play(forceRestart = false, viewId?: string) {
|
||||
if (viewId) {
|
||||
this.views.get(viewId)!.isPaused = false;
|
||||
}
|
||||
|
||||
if (this.isEnded && forceRestart) {
|
||||
@ -156,11 +164,11 @@ class RLottie {
|
||||
this.doPlay();
|
||||
}
|
||||
|
||||
pause(containerId?: string) {
|
||||
if (containerId) {
|
||||
this.containers.get(containerId)!.isPaused = true;
|
||||
pause(viewId?: string) {
|
||||
if (viewId) {
|
||||
this.views.get(viewId)!.isPaused = true;
|
||||
|
||||
const areAllContainersPaused = Array.from(this.containers.values()).every(({ isPaused }) => isPaused);
|
||||
const areAllContainersPaused = Array.from(this.views.values()).every(({ isPaused }) => isPaused);
|
||||
if (!areAllContainersPaused) {
|
||||
return;
|
||||
}
|
||||
@ -202,8 +210,8 @@ class RLottie {
|
||||
this.params.noLoop = noLoop;
|
||||
}
|
||||
|
||||
setSharedCanvasCoords(containerId: string, newCoords: Params['coords']) {
|
||||
const containerInfo = this.containers.get(containerId)!;
|
||||
setSharedCanvasCoords(viewId: string, newCoords: Params['coords']) {
|
||||
const containerInfo = this.views.get(viewId)!;
|
||||
const {
|
||||
canvas, ctx,
|
||||
} = containerInfo;
|
||||
@ -230,8 +238,8 @@ class RLottie {
|
||||
}
|
||||
}
|
||||
|
||||
private addContainer(
|
||||
containerId: string,
|
||||
private addView(
|
||||
viewId: string,
|
||||
container: HTMLDivElement | HTMLCanvasElement,
|
||||
onLoad?: NoneToVoidFunction,
|
||||
coords?: Params['coords'],
|
||||
@ -272,7 +280,7 @@ class RLottie {
|
||||
|
||||
container.appendChild(canvas);
|
||||
|
||||
this.containers.set(containerId, {
|
||||
this.views.set(viewId, {
|
||||
canvas, ctx, onLoad,
|
||||
});
|
||||
} else {
|
||||
@ -287,7 +295,7 @@ class RLottie {
|
||||
|
||||
imgSize = Math.round(this.params.size! * sizeFactor);
|
||||
|
||||
this.containers.set(containerId, {
|
||||
this.views.set(viewId, {
|
||||
canvas,
|
||||
ctx,
|
||||
isSharedCanvas: true,
|
||||
@ -328,7 +336,7 @@ class RLottie {
|
||||
this.clearCache();
|
||||
this.destroyRenderer();
|
||||
|
||||
instancesById.delete(this.id);
|
||||
instancesByRenderId.delete(this.renderId);
|
||||
}
|
||||
|
||||
private clearCache() {
|
||||
@ -359,7 +367,7 @@ class RLottie {
|
||||
workers[this.workerIndex].request({
|
||||
name: 'init',
|
||||
args: [
|
||||
this.id,
|
||||
this.renderId,
|
||||
this.tgsUrl,
|
||||
this.imgSize,
|
||||
this.params.isLowPriority || false,
|
||||
@ -372,7 +380,7 @@ class RLottie {
|
||||
private destroyRenderer() {
|
||||
workers[this.workerIndex].request({
|
||||
name: 'destroy',
|
||||
args: [this.id],
|
||||
args: [this.renderId],
|
||||
});
|
||||
}
|
||||
|
||||
@ -395,7 +403,7 @@ class RLottie {
|
||||
workers[this.workerIndex].request({
|
||||
name: 'changeData',
|
||||
args: [
|
||||
this.id,
|
||||
this.renderId,
|
||||
this.tgsUrl,
|
||||
this.params.isLowPriority || false,
|
||||
this.onChangeData.bind(this),
|
||||
@ -441,7 +449,7 @@ class RLottie {
|
||||
|
||||
// Paused from outside
|
||||
if (!this.isAnimating) {
|
||||
const areAllLoaded = Array.from(this.containers.values()).every(({ isLoaded }) => isLoaded);
|
||||
const areAllLoaded = Array.from(this.views.values()).every(({ isLoaded }) => isLoaded);
|
||||
if (areAllLoaded) {
|
||||
return false;
|
||||
}
|
||||
@ -464,7 +472,7 @@ class RLottie {
|
||||
}
|
||||
|
||||
if (frameIndex !== this.prevFrameIndex) {
|
||||
this.containers.forEach((containerData) => {
|
||||
this.views.forEach((containerData) => {
|
||||
const {
|
||||
ctx, isLoaded, isPaused, coords: { x, y } = {}, onLoad,
|
||||
} = containerData;
|
||||
@ -554,7 +562,7 @@ class RLottie {
|
||||
|
||||
workers[this.workerIndex].request({
|
||||
name: 'renderFrames',
|
||||
args: [this.id, frameIndex, this.onFrameLoad.bind(this)],
|
||||
args: [this.renderId, frameIndex, this.onFrameLoad.bind(this)],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user