Browse Source

Emoji animation sound

master
Eduard Kuzmenko 2 years ago
parent
commit
ffd3e231d4
  1. 4
      .env
  2. 32
      src/components/wrappers.ts
  3. 7
      src/helpers/fixBase64String.ts
  4. 93
      src/lib/appManagers/appStickersManager.ts
  5. 51
      src/lib/mtproto/appConfig.d.ts
  6. 12
      src/lib/mtproto/mtprotoworker.ts
  7. 15
      src/lib/mtproto/referenceDatabase.ts
  8. 4
      src/lib/rootScope.ts
  9. 3
      src/pages/pageSignQR.ts

4
.env

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
API_ID=1025907
API_HASH=452b0359b988148995f22ff0f4229750
VERSION=1.0.4
VERSION_FULL=1.0.4 (73)
BUILD=73
VERSION_FULL=1.0.4 (74)
BUILD=74

32
src/components/wrappers.ts

@ -1338,15 +1338,39 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1338,15 +1338,39 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
let sendInteractionThrottled: () => void;
attachClickEvent(div, (e) => {
cancelEvent(e);
let animation = LottieLoader.getAnimation(div);
appStickersManager.preloadAnimatedEmojiStickerAnimation(emoji);
attachClickEvent(div, async(e) => {
const animation = LottieLoader.getAnimation(div);
if(animation.paused) {
const doc = appStickersManager.getAnimatedEmojiSoundDocument(emoji);
if(doc) {
const audio = document.createElement('audio');
try {
await appDocsManager.downloadDoc(doc);
const cacheContext = appDownloadManager.getCacheContext(doc);
audio.src = cacheContext.url;
await onMediaLoad(audio);
audio.addEventListener('ended', () => {
audio.src = '';
}, {once: true});
audio.play();
} catch(err) {
}
}
animation.autoplay = true;
animation.restart();
}
cancelEvent(e);
const doc = appStickersManager.getAnimatedEmojiSticker(emoji, true);
if(!doc) {
return;
@ -1375,7 +1399,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -1375,7 +1399,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
animation.addEventListener('enterFrame', (frameNo) => {
if(frameNo === animation.maxFrame) {
animation.remove();
// animationDiv.remove();
animationDiv.remove();
appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll);
}
});

7
src/helpers/fixBase64String.ts

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
export default function fixBase64String(str: string, toUrl: boolean) {
if(toUrl) {
return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
} else {
return str.replace(/-/g, '+').replace(/_/g, '/');
}
}

93
src/lib/appManagers/appStickersManager.ts

@ -19,6 +19,7 @@ import mediaSizes from '../../helpers/mediaSizes'; @@ -19,6 +19,7 @@ import mediaSizes from '../../helpers/mediaSizes';
import { getEmojiToneIndex } from '../../vendor/emoji';
import RichTextProcessor from '../richtextprocessor';
import assumeType from '../../helpers/assumeType';
import fixBase64String from '../../helpers/fixBase64String';
const CACHE_TIME = 3600e3;
@ -29,6 +30,8 @@ const LOCAL_IDS_SET = new Set([ @@ -29,6 +30,8 @@ const LOCAL_IDS_SET = new Set([
EMOJI_ANIMATIONS_SET_LOCAL_ID
]);
// let TEST_FILE_REFERENCE_REFRESH = true;
export type MyStickerSetInput = {
id: StickerSet.stickerSet['id'],
access_hash?: StickerSet.stickerSet['access_hash']
@ -39,14 +42,21 @@ export type MyMessagesStickerSet = MessagesStickerSet.messagesStickerSet; @@ -39,14 +42,21 @@ export type MyMessagesStickerSet = MessagesStickerSet.messagesStickerSet;
export class AppStickersManager {
private storage = new AppStorage<Record<Long, MyMessagesStickerSet>, typeof DATABASE_STATE>(DATABASE_STATE, 'stickerSets');
private getStickerSetPromises: {[setId: Long]: Promise<MyMessagesStickerSet>} = {};
private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>} = {};
private getStickerSetPromises: {[setId: Long]: Promise<MyMessagesStickerSet>};
private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>};
private greetingStickers: Document.document[];
private getGreetingStickersTimeout: number;
private getGreetingStickersPromise: Promise<void>;
private sounds: Record<string, MyDocument>;
getAnimatedEmojiSoundsPromise: Promise<void>;
constructor() {
this.getStickerSetPromises = {};
this.getStickersByEmoticonsPromises = {};
this.sounds = {};
this.getAnimatedEmojiStickerSet();
rootScope.addMultipleEventsListeners({
@ -143,12 +153,59 @@ export class AppStickersManager { @@ -143,12 +153,59 @@ export class AppStickersManager {
public getAnimatedEmojiStickerSet() {
return Promise.all([
this.getStickerSet({id: EMOJI_SET_LOCAL_ID}, {saveById: true}),
this.getStickerSet({id: EMOJI_ANIMATIONS_SET_LOCAL_ID}, {saveById: true})
this.getStickerSet({id: EMOJI_ANIMATIONS_SET_LOCAL_ID}, {saveById: true}),
this.getAnimatedEmojiSounds()
]).then(([emoji, animations]) => {
return {emoji, animations};
});
}
public getAnimatedEmojiSounds(overwrite?: boolean) {
if(this.getAnimatedEmojiSoundsPromise && !overwrite) return this.getAnimatedEmojiSoundsPromise;
return this.getAnimatedEmojiSoundsPromise = apiManager.getAppConfig(overwrite).then(appConfig => {
for(const emoji in appConfig.emojies_sounds) {
const sound = appConfig.emojies_sounds[emoji];
const bytesStr = atob(fixBase64String(sound.file_reference_base64, false));
const bytes = new Uint8Array(bytesStr.length);
for(let i = 0, length = bytes.length; i < length; ++i) {
bytes[i] = bytesStr[i].charCodeAt(0);
}
// if(TEST_FILE_REFERENCE_REFRESH) {
// bytes[0] = bytes[1] = bytes[2] = bytes[3] = bytes[4] = 0;
// sound.access_hash += '999';
// }
const doc = appDocsManager.saveDoc({
_: 'document',
pFlags: {},
flags: 0,
id: sound.id,
access_hash: sound.access_hash,
attributes: [/* {
_: 'documentAttributeAudio',
duration: 1,
pFlags: {}
} */],
date: 0,
dc_id: rootScope.config.this_dc,
file_reference: bytes,
mime_type: 'audio/mp3',
size: 1
// size: 101010 // test loading everytime
}, {
type: 'emojiesSounds'
});
this.sounds[emoji] = doc;
}
// if(TEST_FILE_REFERENCE_REFRESH) {
// TEST_FILE_REFERENCE_REFRESH = false;
// }
});
}
public async getRecentStickers(): Promise<Modify<MessagesRecentStickers.messagesRecentStickers, {
stickers: Document[]
}>> {
@ -164,17 +221,25 @@ export class AppStickersManager { @@ -164,17 +221,25 @@ export class AppStickersManager {
return res;
}
private cleanEmoji(emoji: string) {
return emoji.replace(/\ufe0f/g, '').replace(/🏻|🏼|🏽|🏾|🏿/g, '');
}
public getAnimatedEmojiSticker(emoji: string, isAnimation?: boolean) {
const stickerSet = this.storage.getFromCache(isAnimation ? EMOJI_ANIMATIONS_SET_LOCAL_ID : EMOJI_SET_LOCAL_ID);
if(!stickerSet || !stickerSet.documents) return undefined;
emoji = emoji.replace(/\ufe0f/g, '').replace(/🏻|🏼|🏽|🏾|🏿/g, '');
emoji = this.cleanEmoji(emoji);
const pack = stickerSet.packs.find(p => p.emoticon === emoji);
return pack ? appDocsManager.getDoc(pack.documents[0]) : undefined;
}
public getAnimatedEmojiSoundDocument(emoji: string) {
return this.sounds[this.cleanEmoji(emoji)];
}
public preloadAnimatedEmojiSticker(emoji: string, width?: number, height?: number) {
return this.getAnimatedEmojiStickerSet().then(() => {
const preloadEmojiPromise = this.getAnimatedEmojiStickerSet().then(() => {
const doc = this.getAnimatedEmojiSticker(emoji);
if(doc) {
return appDocsManager.downloadDoc(doc)
@ -199,6 +264,24 @@ export class AppStickersManager { @@ -199,6 +264,24 @@ export class AppStickersManager {
});
}
});
return Promise.all([
preloadEmojiPromise,
this.preloadAnimatedEmojiStickerAnimation(emoji)
]);
}
public preloadAnimatedEmojiStickerAnimation(emoji: string) {
return this.getAnimatedEmojiStickerSet().then(() => {
const doc = this.getAnimatedEmojiSticker(emoji, true);
if(doc) {
const soundDoc = this.getAnimatedEmojiSoundDocument(emoji);
return Promise.all([
appDocsManager.downloadDoc(doc),
soundDoc ? appDocsManager.downloadDoc(soundDoc) : undefined
]);
}
});
}
public saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) {

51
src/lib/mtproto/appConfig.d.ts vendored

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
export interface MTAppConfig {
test?: number;
emojies_animated_zoom?: number;
emojies_send_dice?: string[];
emojies_send_dice_success?: EmojiesSendDiceSuccess;
emojies_sounds?: EmojiesSounds;
gif_search_branding?: string;
gif_search_emojies?: string[];
stickers_emoji_suggest_only_api?: boolean;
stickers_emoji_cache_time?: number;
groupcall_video_participants_max?: number;
youtube_pip?: string;
qr_login_camera?: boolean;
qr_login_code?: string;
dialog_filters_enabled?: boolean;
dialog_filters_tooltip?: boolean;
ignore_restriction_reasons?: string[];
autoarchive_setting_available?: boolean;
pending_suggestions?: any[];
autologin_token?: string;
autologin_domains?: string[];
round_video_encoding?: RoundVideoEncoding;
chat_read_mark_expire_period?: number;
chat_read_mark_size_threshold?: number;
reactions_default?: string;
reactions_uniq_max?: number;
}
export interface EmojiesSendDiceSuccess {
[k]: EmojiesSendDiceSuccessDetails
}
export interface EmojiesSendDiceSuccessDetails {
value?: number;
frame_start?: number;
}
export type EmojiesSounds = Record<string, EmojiSound>;
export interface EmojiSound {
id?: string;
access_hash?: string;
file_reference_base64?: string;
}
export interface RoundVideoEncoding {
diameter?: number;
video_bitrate?: number;
audio_bitrate?: number;
max_size?: number;
}

12
src/lib/mtproto/mtprotoworker.ts

@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
import type { LocalStorageProxyTask, LocalStorageProxyTaskResponse } from '../localStorage';
//import type { LocalStorageProxyDeleteTask, LocalStorageProxySetTask } from '../storage';
import type { Awaited, InvokeApiOptions, WorkerTaskVoidTemplate } from '../../types';
import type { Config, InputFile, MethodDeclMap, User } from '../../layer';
import type { Config, InputFile, JSONValue, MethodDeclMap, User } from '../../layer';
import MTProtoWorker from 'worker-loader!./mtproto.worker';
//import './mtproto.worker';
import { isObject } from '../../helpers/object';
@ -32,6 +32,7 @@ import { CacheStorageDbName } from '../cacheStorage'; @@ -32,6 +32,7 @@ import { CacheStorageDbName } from '../cacheStorage';
import { pause } from '../../helpers/schedulers/pause';
import IS_WEBP_SUPPORTED from '../../environment/webpSupport';
import type { ApiError } from './apiManager';
import { MTAppConfig } from './appConfig';
type Task = {
taskId: number,
@ -105,6 +106,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -105,6 +106,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
private postMessagesWaiting: any[][] = [];
private getConfigPromise: Promise<Config.config>;
private getAppConfigPromise: Promise<MTAppConfig>;
constructor() {
super();
@ -693,6 +695,14 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -693,6 +695,14 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
return config;
});
}
public getAppConfig(overwrite?: boolean): Promise<MTAppConfig> {
if(this.getAppConfigPromise && !overwrite) return this.getAppConfigPromise;
return this.getAppConfigPromise = this.invokeApi('help.getAppConfig').then(config => {
rootScope.appConfig = config;
return config;
});
}
}
const apiManagerProxy = new ApiManagerProxy();

15
src/lib/mtproto/referenceDatabase.ts

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
import { RefreshReferenceTask, RefreshReferenceTaskResponse } from "./apiFileManager";
import appMessagesManager from "../appManagers/appMessagesManager";
import appStickersManager from "../appManagers/appStickersManager";
import { Photo } from "../../layer";
import { bytesToHex } from "../../helpers/bytes";
import { deepEqual } from "../../helpers/object";
@ -14,7 +15,7 @@ import apiManager from "./mtprotoworker"; @@ -14,7 +15,7 @@ import apiManager from "./mtprotoworker";
import assumeType from "../../helpers/assumeType";
import { logger } from "../logger";
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage;
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage | ReferenceContext.referenceContextEmojiesSounds;
export namespace ReferenceContext {
export type referenceContextProfilePhoto = {
type: 'profilePhoto',
@ -26,6 +27,10 @@ export namespace ReferenceContext { @@ -26,6 +27,10 @@ export namespace ReferenceContext {
peerId: PeerId,
messageId: number
};
export type referenceContextEmojiesSounds = {
type: 'emojiesSounds'
};
}
export type ReferenceBytes = Photo.photo['file_reference'];
@ -38,6 +43,7 @@ class ReferenceDatabase { @@ -38,6 +43,7 @@ class ReferenceDatabase {
//private references: Map<ReferenceBytes, number[]> = new Map();
private links: {[hex: string]: ReferenceBytes} = {};
private log = logger('RD', undefined, true);
private refreshEmojiesSoundsPromise: Promise<any>;
constructor() {
apiManager.addTaskListener('refreshReference', (task: RefreshReferenceTask) => {
@ -125,6 +131,13 @@ class ReferenceDatabase { @@ -125,6 +131,13 @@ class ReferenceDatabase {
// });
}
case 'emojiesSounds': {
promise = this.refreshEmojiesSoundsPromise || appStickersManager.getAnimatedEmojiSounds(true).then(() => {
this.refreshEmojiesSoundsPromise = undefined;
});
break;
}
default: {
this.log.warn('refreshReference: not implemented context', context);
return Promise.reject();

4
src/lib/rootScope.ts

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type { Message, StickerSet, Update, NotifyPeer, PeerNotifySettings, ConstructorDeclMap, Config, PollResults, Poll, WebPage, GroupCall, GroupCallParticipant, PhoneCall } from "../layer";
import type { Message, StickerSet, Update, NotifyPeer, PeerNotifySettings, ConstructorDeclMap, Config, PollResults, Poll, WebPage, GroupCall, GroupCallParticipant, PhoneCall, MethodDeclMap } from "../layer";
import type { MyDocument } from "./appManagers/appDocsManager";
import type { AppMessagesManager, Dialog, MessagesStorage, MyMessage } from "./appManagers/appMessagesManager";
import type { MyDialogFilter } from "./storages/filters";
@ -23,6 +23,7 @@ import type Chat from "../components/chat/chat"; @@ -23,6 +23,7 @@ import type Chat from "../components/chat/chat";
import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config";
import EventListenerBase from "../helpers/eventListenerBase";
import { MOUNT_CLASS_TO } from "../config/debug";
import { MTAppConfig } from "./mtproto/appConfig";
export type BroadcastEvents = {
'chat_full_update': ChatId,
@ -183,6 +184,7 @@ export class RootScope extends EventListenerBase<{ @@ -183,6 +184,7 @@ export class RootScope extends EventListenerBase<{
message_length_max: 4096,
caption_length_max: 1024,
};
public appConfig: MTAppConfig;
public themeColor: string;
private _themeColorElem: Element;

3
src/pages/pageSignQR.ts

@ -19,6 +19,7 @@ import rootScope from '../lib/rootScope'; @@ -19,6 +19,7 @@ import rootScope from '../lib/rootScope';
import { putPreloader } from '../components/misc';
import getLanguageChangeButton from '../components/languageChangeButton';
import { pause } from '../helpers/schedulers/pause';
import fixBase64String from '../helpers/fixBase64String';
const FETCH_INTERVAL = 3;
@ -105,7 +106,7 @@ let onFirstMount = async() => { @@ -105,7 +106,7 @@ let onFirstMount = async() => {
prevToken = loginToken.token;
let encoded = bytesToBase64(loginToken.token);
let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, "");
let url = "tg://login?token=" + fixBase64String(encoded, true);
const style = window.getComputedStyle(document.documentElement);
const surfaceColor = style.getPropertyValue('--surface-color').trim();

Loading…
Cancel
Save