Fix gif thumbs

Fix dead streaming & downloading through SW
Possible fix for stuck sticker viewer
This commit is contained in:
Eduard Kuzmenko 2022-08-28 18:50:04 +02:00
parent da0975ee59
commit 044f23fc62
25 changed files with 266 additions and 175 deletions

View File

@ -787,15 +787,33 @@ export default class AppMediaViewerBase<
}); });
if(!closing) { if(!closing) {
let mediaElement: HTMLImageElement | HTMLVideoElement; let mediaElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement;
let src: string; let src: string;
if(target instanceof HTMLVideoElement) { // if(target instanceof HTMLVideoElement) {
const elements = Array.from(target.parentElement.querySelectorAll('img')) as HTMLImageElement[]; const selector = 'video, img, .canvas-thumbnail';
if(elements.length) { const queryFrom = target.matches(selector) ? target.parentElement : target;
target = elements.pop(); const elements = Array.from(queryFrom.querySelectorAll(selector)) as HTMLImageElement[];
if(elements.length) {
target = elements.pop();
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if(target instanceof HTMLImageElement) {
canvas.width = target.naturalWidth;
canvas.height = target.naturalHeight;
} else if(target instanceof HTMLVideoElement) {
canvas.width = target.videoWidth;
canvas.height = target.videoHeight;
} else if(target instanceof HTMLCanvasElement) {
canvas.width = target.width;
canvas.height = target.height;
} }
canvas.className = 'canvas-thumbnail thumbnail media-photo';
context.drawImage(target as HTMLImageElement | HTMLCanvasElement, 0, 0);
target = canvas;
} }
// }
if(target.tagName === 'DIV' || target.tagName === 'AVATAR-ELEMENT') { // useContainerAsTarget if(target.tagName === 'DIV' || target.tagName === 'AVATAR-ELEMENT') { // useContainerAsTarget
const images = Array.from(target.querySelectorAll('img')) as HTMLImageElement[]; const images = Array.from(target.querySelectorAll('img')) as HTMLImageElement[];
@ -865,6 +883,8 @@ export default class AppMediaViewerBase<
foreignObject.setAttributeNS(null, 'height', '' + containerRect.height); foreignObject.setAttributeNS(null, 'height', '' + containerRect.height);
mover.prepend(newSvg); mover.prepend(newSvg);
} else if(target instanceof HTMLCanvasElement) {
mediaElement = target;
} }
if(aspecter) { if(aspecter) {

View File

@ -647,7 +647,7 @@ export default class AppSearchSuper {
onlyPreview: true, onlyPreview: true,
withoutPreloader: true, withoutPreloader: true,
noPlayButton: true, noPlayButton: true,
size photoSize: size
})).thumb; })).thumb;
} else { } else {
wrapped = await wrapPhoto({ wrapped = await wrapPhoto({

View File

@ -22,7 +22,7 @@ import {SuperStickerRenderer} from '../emoticonsDropdown/tabs/stickers';
import mediaSizes from '../../helpers/mediaSizes'; import mediaSizes from '../../helpers/mediaSizes';
import readBlobAsDataURL from '../../helpers/blob/readBlobAsDataURL'; import readBlobAsDataURL from '../../helpers/blob/readBlobAsDataURL';
import setInnerHTML from '../../helpers/dom/setInnerHTML'; import setInnerHTML from '../../helpers/dom/setInnerHTML';
import renderImageWithFadeIn from '../../helpers/dom/renderImageWithFadeIn'; import renderMediaWithFadeIn from '../../helpers/dom/renderMediaWithFadeIn';
import {AppManagers} from '../../lib/appManagers/managers'; import {AppManagers} from '../../lib/appManagers/managers';
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText'; import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
import wrapRichText from '../../lib/richTextProcessor/wrapRichText'; import wrapRichText from '../../lib/richTextProcessor/wrapRichText';
@ -188,7 +188,7 @@ export default class InlineHelper extends AutocompleteHelper {
const image = new Image(); const image = new Image();
image.classList.add('media-photo'); image.classList.add('media-photo');
readBlobAsDataURL(blob).then((dataURL) => { readBlobAsDataURL(blob).then((dataURL) => {
renderImageWithFadeIn(mediaContainer, image, dataURL, true); renderMediaWithFadeIn(mediaContainer, image, dataURL, true);
}); });
}); });
} }

View File

@ -6,11 +6,12 @@
import replaceContent from '../../helpers/dom/replaceContent'; import replaceContent from '../../helpers/dom/replaceContent';
import limitSymbols from '../../helpers/string/limitSymbols'; import limitSymbols from '../../helpers/string/limitSymbols';
import {Document, MessageMedia, Photo, WebPage} from '../../layer';
import appImManager, {CHAT_ANIMATION_GROUP} from '../../lib/appManagers/appImManager'; import appImManager, {CHAT_ANIMATION_GROUP} from '../../lib/appManagers/appImManager';
import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize'; import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize';
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText'; import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
import DivAndCaption from '../divAndCaption'; import DivAndCaption from '../divAndCaption';
import {wrapPhoto, wrapSticker} from '../wrappers'; import {wrapPhoto, wrapSticker, wrapVideo} from '../wrappers';
import wrapMessageForReply from '../wrappers/messageForReply'; import wrapMessageForReply from '../wrappers/messageForReply';
const MEDIA_SIZE = 32; const MEDIA_SIZE = 32;
@ -38,49 +39,61 @@ export async function wrapReplyDivAndCaption(options: {
loadPromises = []; loadPromises = [];
} }
let media = message && message.media; let messageMedia: MessageMedia | WebPage.webPage = message?.media;
let setMedia = false, isRound = false; let setMedia = false, isRound = false;
const mediaChildren = mediaEl ? Array.from(mediaEl.children).slice() : []; const mediaChildren = mediaEl ? Array.from(mediaEl.children).slice() : [];
let middleware: () => boolean; let middleware: () => boolean;
if(media && mediaEl) { if(messageMedia && mediaEl) {
subtitleEl.textContent = ''; subtitleEl.textContent = '';
subtitleEl.append(await wrapMessageForReply(message, undefined, undefined, undefined, undefined, true)); subtitleEl.append(await wrapMessageForReply(message, undefined, undefined, undefined, undefined, true));
// console.log('wrap reply', media); messageMedia = (messageMedia as MessageMedia.messageMediaWebPage).webpage as WebPage.webPage || messageMedia;
const photo = (messageMedia as MessageMedia.messageMediaPhoto).photo as Photo.photo;
if(media.webpage) { const document = (messageMedia as MessageMedia.messageMediaDocument).document as Document.document;
media = media.webpage; if(photo || (document && document.thumbs?.length)/* ['video', 'sticker', 'gif', 'round', 'photo', 'audio'].indexOf(document.type) !== -1) */) {
}
if(media.photo || (media.document && media.document.thumbs?.length)/* ['video', 'sticker', 'gif', 'round', 'photo', 'audio'].indexOf(media.document.type) !== -1) */) {
middleware = appImManager.chat.bubbles.getMiddleware(); middleware = appImManager.chat.bubbles.getMiddleware();
const lazyLoadQueue = appImManager.chat.bubbles.lazyLoadQueue; const lazyLoadQueue = appImManager.chat.bubbles.lazyLoadQueue;
if(media.document?.type === 'sticker') { if(document?.type === 'sticker') {
setMedia = true;
await wrapSticker({ await wrapSticker({
doc: media.document, doc: document,
div: mediaEl, div: mediaEl,
lazyLoadQueue, lazyLoadQueue,
group: CHAT_ANIMATION_GROUP, group: CHAT_ANIMATION_GROUP,
// onlyThumb: media.document.sticker === 2, // onlyThumb: document.sticker === 2,
width: MEDIA_SIZE, width: MEDIA_SIZE,
height: MEDIA_SIZE, height: MEDIA_SIZE,
middleware, middleware,
loadPromises loadPromises
}); });
setMedia = true;
} else if(document?.type === 'gif' && document.video_thumbs) {
setMedia = true;
await wrapVideo({
doc: document,
container: mediaEl,
boxWidth: MEDIA_SIZE,
boxHeight: MEDIA_SIZE,
lazyLoadQueue,
noPlayButton: true,
noInfo: true,
middleware,
loadPromises,
withoutPreloader: true,
videoSize: document.video_thumbs[0],
group: CHAT_ANIMATION_GROUP
});
} else { } else {
const photo = media.photo || media.document; const m = photo || document;
isRound = document?.type === 'round';
isRound = photo.type === 'round';
try { try {
await wrapPhoto({ await wrapPhoto({
photo, photo: m,
container: mediaEl, container: mediaEl,
boxWidth: MEDIA_SIZE, boxWidth: MEDIA_SIZE,
boxHeight: MEDIA_SIZE, boxHeight: MEDIA_SIZE,
size: choosePhotoSize(photo, MEDIA_SIZE, MEDIA_SIZE), size: choosePhotoSize(m, MEDIA_SIZE, MEDIA_SIZE),
middleware, middleware,
lazyLoadQueue, lazyLoadQueue,
noBlur: true, noBlur: true,

View File

@ -27,6 +27,7 @@ import {MediaSize} from '../../helpers/mediaSize';
import {ThumbCache} from '../../lib/storages/thumbs'; import {ThumbCache} from '../../lib/storages/thumbs';
import onMediaLoad from '../../helpers/onMediaLoad'; import onMediaLoad from '../../helpers/onMediaLoad';
import apiManagerProxy from '../../lib/mtproto/mtprotoworker'; import apiManagerProxy from '../../lib/mtproto/mtprotoworker';
import {THUMB_TYPE_FULL} from '../../lib/mtproto/mtproto_config';
type SendFileParams = Partial<{ type SendFileParams = Partial<{
file: File, file: File,
@ -377,7 +378,7 @@ export default class PopupNewMedia extends PopupElement {
cacheContext = { cacheContext = {
url: params.objectURL, url: params.objectURL,
downloaded: file.size, downloaded: file.size,
type: 'full' type: THUMB_TYPE_FULL
}; };
} }

View File

@ -208,7 +208,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
const media = document.createElement('div'); const media = document.createElement('div');
media.classList.add('grid-item-media'); media.classList.add('grid-item-media');
let wrapped: ReturnType<typeof wrapPhoto>, size: PhotoSize; let wrapped: ReturnType<typeof wrapPhoto>, size: ReturnType<typeof choosePhotoSize>;
if(hasFile) { if(hasFile) {
size = choosePhotoSize(doc, 200, 200); size = choosePhotoSize(doc, 200, 200);
wrapped = wrapPhoto({ wrapped = wrapPhoto({

View File

@ -302,9 +302,10 @@ export default function attachStickerViewerListeners({listenTo, listenerSetter,
} }
document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp, {capture: true});
}; };
document.addEventListener('mousemove', onMousePreMove); document.addEventListener('mousemove', onMousePreMove);
document.addEventListener('mouseup', onMouseUp, {once: true}); document.addEventListener('mouseup', onMouseUp, {once: true, capture: true});
}); });
} }

View File

@ -109,7 +109,7 @@ export default async function wrapDocument({message, withTime, fontWeight, voice
docDiv.classList.add('document-with-thumb'); docDiv.classList.add('document-with-thumb');
hasThumb = true; hasThumb = true;
const imgs: (HTMLImageElement | HTMLCanvasElement)[] = []; const imgs: (HTMLImageElement | HTMLCanvasElement | HTMLVideoElement)[] = [];
// ! WARNING, use thumbs for check when thumb will be generated for media // ! WARNING, use thumbs for check when thumb will be generated for media
if(message.pFlags.is_outgoing && ['photo', 'video'].includes(doc.type)) { if(message.pFlags.is_outgoing && ['photo', 'video'].includes(doc.type)) {
icoDiv.innerHTML = `<img src="${cacheContext.url}">`; icoDiv.innerHTML = `<img src="${cacheContext.url}">`;

View File

@ -4,9 +4,9 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import renderImageWithFadeIn from '../../helpers/dom/renderImageWithFadeIn'; import renderMediaWithFadeIn from '../../helpers/dom/renderMediaWithFadeIn';
import mediaSizes from '../../helpers/mediaSizes'; import mediaSizes from '../../helpers/mediaSizes';
import {Message, PhotoSize, WebDocument} from '../../layer'; import {Message, PhotoSize, VideoSize, WebDocument} from '../../layer';
import {MyDocument} from '../../lib/appManagers/appDocsManager'; import {MyDocument} from '../../lib/appManagers/appDocsManager';
import {MyPhoto} from '../../lib/appManagers/appPhotosManager'; import {MyPhoto} from '../../lib/appManagers/appPhotosManager';
import rootScope from '../../lib/rootScope'; import rootScope from '../../lib/rootScope';
@ -20,6 +20,9 @@ import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize'
import type {ThumbCache} from '../../lib/storages/thumbs'; import type {ThumbCache} from '../../lib/storages/thumbs';
import appDownloadManager from '../../lib/appManagers/appDownloadManager'; import appDownloadManager from '../../lib/appManagers/appDownloadManager';
import isWebDocument from '../../lib/appManagers/utils/webDocs/isWebDocument'; import isWebDocument from '../../lib/appManagers/utils/webDocs/isWebDocument';
import createVideo from '../../helpers/dom/createVideo';
import noop from '../../helpers/noop';
import {THUMB_TYPE_FULL} from '../../lib/mtproto/mtproto_config';
export default async function wrapPhoto({photo, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware, size, withoutPreloader, loadPromises, autoDownloadSize, noBlur, noThumb, noFadeIn, blurAfter, managers = rootScope.managers}: { export default async function wrapPhoto({photo, message, container, boxWidth, boxHeight, withTail, isOut, lazyLoadQueue, middleware, size, withoutPreloader, loadPromises, autoDownloadSize, noBlur, noThumb, noFadeIn, blurAfter, managers = rootScope.managers}: {
photo: MyPhoto | MyDocument | WebDocument, photo: MyPhoto | MyDocument | WebDocument,
@ -31,7 +34,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
isOut?: boolean, isOut?: boolean,
lazyLoadQueue?: LazyLoadQueue, lazyLoadQueue?: LazyLoadQueue,
middleware?: () => boolean, middleware?: () => boolean,
size?: PhotoSize, size?: PhotoSize | VideoSize,
withoutPreloader?: boolean, withoutPreloader?: boolean,
loadPromises?: Promise<any>[], loadPromises?: Promise<any>[],
autoDownloadSize?: number, autoDownloadSize?: number,
@ -41,24 +44,27 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
blurAfter?: boolean, blurAfter?: boolean,
managers?: AppManagers, managers?: AppManagers,
}) { }) {
const ret = {
loadPromises: {
thumb: Promise.resolve() as Promise<any>,
full: Promise.resolve() as Promise<any>
},
images: {
thumb: null as HTMLImageElement | HTMLCanvasElement,
full: null as HTMLVideoElement | HTMLImageElement
},
preloader: null as ProgressivePreloader,
aspecter: null as HTMLElement
};
const isDocument = photo._ === 'document';
const isWebDoc = isWebDocument(photo); const isWebDoc = isWebDocument(photo);
if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs) && !isWebDoc) { if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs) && !isWebDoc) {
if(boxWidth && boxHeight && !size && photo._ === 'document') { if(boxWidth && boxHeight && !size && isDocument) {
setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message); setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message);
} }
return { return ret;
loadPromises: {
thumb: Promise.resolve(),
full: Promise.resolve()
},
images: {
thumb: null,
full: null
},
preloader: null,
aspecter: null
};
} }
let noAutoDownload = autoDownloadSize === 0; let noAutoDownload = autoDownloadSize === 0;
@ -76,11 +82,10 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
let thumbImage: HTMLImageElement | HTMLCanvasElement; let thumbImage: HTMLImageElement | HTMLCanvasElement;
// let image: HTMLImageElement; // let image: HTMLImageElement;
let cacheContext: ThumbCache; let cacheContext: ThumbCache;
const isGif = photo._ === 'document' && photo.mime_type === 'image/gif' && !size; const isGif = isDocument && photo.mime_type === 'image/gif' && !size;
// if(withTail) { // if(withTail) {
// image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut); // image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut);
// } else { // } else {
const image = new Image();
if(boxWidth && boxHeight && !size) { // !album if(boxWidth && boxHeight && !size) { // !album
const set = setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message, undefined, isGif ? { const set = setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message, undefined, isGif ? {
@ -88,7 +93,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
w: photo.w, w: photo.w,
h: photo.h, h: photo.h,
size: photo.size, size: photo.size,
type: 'full' type: THUMB_TYPE_FULL
} : undefined); } : undefined);
size = set.photoSize; size = set.photoSize;
isFit = set.isFit; isFit = set.isFit;
@ -147,14 +152,29 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
const gotThumb = getStrippedThumbIfNeeded(photo, cacheContext, !noBlur); const gotThumb = getStrippedThumbIfNeeded(photo, cacheContext, !noBlur);
if(gotThumb) { if(gotThumb) {
loadThumbPromise = Promise.all([loadThumbPromise, gotThumb.loadPromise]); loadThumbPromise = Promise.all([loadThumbPromise, gotThumb.loadPromise]);
thumbImage = gotThumb.image; ret.loadPromises.thumb = ret.loadPromises.full = loadThumbPromise;
thumbImage = ret.images.thumb = gotThumb.image;
thumbImage.classList.add('media-photo'); thumbImage.classList.add('media-photo');
aspecter.append(thumbImage); aspecter.append(thumbImage);
} }
} }
// } // }
image.classList.add('media-photo'); if(size?._ === 'photoSizeEmpty' && isDocument) {
return ret;
}
let media: HTMLVideoElement | HTMLImageElement;
if(size?._ === 'videoSize') {
media = ret.images.full = createVideo();
media.autoplay = true;
media.loop = true;
media.muted = true;
media.classList.add('media-photo');
} else {
media = ret.images.full = new Image();
media.classList.add('media-photo');
}
// console.log('wrapPhoto downloaded:', photo, photo.downloaded, container); // console.log('wrapPhoto downloaded:', photo, photo.downloaded, container);
@ -194,7 +214,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
}; };
const renderOnLoad = (url: string) => { const renderOnLoad = (url: string) => {
return renderImageWithFadeIn(container, image, url, needFadeIn, aspecter, thumbImage); return renderMediaWithFadeIn(container, media, url, needFadeIn, aspecter, thumbImage);
}; };
const onLoad = async(url: string) => { const onLoad = async(url: string) => {
@ -236,7 +256,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
noAutoDownload = undefined; noAutoDownload = undefined;
const renderPromise = promise.then(onLoad); const renderPromise = promise.then(onLoad);
renderPromise.catch(() => {}); renderPromise.catch(noop);
return {download: promise, render: renderPromise}; return {download: promise, render: renderPromise};
}; };
@ -261,22 +281,15 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
// const perf = performance.now(); // const perf = performance.now();
await loadThumbPromise; await loadThumbPromise;
ret.loadPromises.thumb = loadThumbPromise;
ret.loadPromises.full = loadPromise || Promise.resolve();
ret.preloader = preloader;
ret.aspecter = aspecter;
// const elapsedTime = performance.now() - perf; // const elapsedTime = performance.now() - perf;
// if(elapsedTime > 4) { // if(elapsedTime > 4) {
// console.log('wrapping photo thumb time', elapsedTime, photo, size); // console.log('wrapping photo thumb time', elapsedTime, photo, size);
// } // }
return { return ret;
loadPromises: {
thumb: loadThumbPromise,
full: loadPromise || Promise.resolve()
},
images: {
thumb: thumbImage,
full: image
},
preloader,
aspecter
};
} }

View File

@ -14,12 +14,13 @@ import createVideo from '../../helpers/dom/createVideo';
import isInDOM from '../../helpers/dom/isInDOM'; import isInDOM from '../../helpers/dom/isInDOM';
import renderImageFromUrl from '../../helpers/dom/renderImageFromUrl'; import renderImageFromUrl from '../../helpers/dom/renderImageFromUrl';
import mediaSizes, {ScreenSize} from '../../helpers/mediaSizes'; import mediaSizes, {ScreenSize} from '../../helpers/mediaSizes';
import noop from '../../helpers/noop';
import onMediaLoad from '../../helpers/onMediaLoad'; import onMediaLoad from '../../helpers/onMediaLoad';
import {fastRaf} from '../../helpers/schedulers'; import {fastRaf} from '../../helpers/schedulers';
import throttle from '../../helpers/schedulers/throttle'; import throttle from '../../helpers/schedulers/throttle';
import sequentialDom from '../../helpers/sequentialDom'; import sequentialDom from '../../helpers/sequentialDom';
import toHHMMSS from '../../helpers/string/toHHMMSS'; import toHHMMSS from '../../helpers/string/toHHMMSS';
import {Message, PhotoSize} from '../../layer'; import {Message, PhotoSize, VideoSize} from '../../layer';
import {MyDocument} from '../../lib/appManagers/appDocsManager'; import {MyDocument} from '../../lib/appManagers/appDocsManager';
import appDownloadManager from '../../lib/appManagers/appDownloadManager'; import appDownloadManager from '../../lib/appManagers/appDownloadManager';
import appImManager from '../../lib/appManagers/appImManager'; import appImManager from '../../lib/appManagers/appImManager';
@ -59,7 +60,7 @@ mediaSizes.addEventListener('changeScreen', (from, to) => {
} }
}); });
export default async function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group, onlyPreview, withoutPreloader, loadPromises, noPlayButton, size, searchContext, autoDownload, managers = rootScope.managers}: { export default async function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group, onlyPreview, withoutPreloader, loadPromises, noPlayButton, photoSize, videoSize, searchContext, autoDownload, managers = rootScope.managers}: {
doc: MyDocument, doc: MyDocument,
container?: HTMLElement, container?: HTMLElement,
message?: Message.message, message?: Message.message,
@ -76,7 +77,8 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
withoutPreloader?: boolean, withoutPreloader?: boolean,
loadPromises?: Promise<any>[], loadPromises?: Promise<any>[],
autoDownload?: ChatAutoDownloadSettings, autoDownload?: ChatAutoDownloadSettings,
size?: PhotoSize, photoSize?: PhotoSize,
videoSize?: VideoSize,
searchContext?: MediaSearchContext, searchContext?: MediaSearchContext,
managers?: AppManagers managers?: AppManagers
}) { }) {
@ -144,7 +146,7 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
withoutPreloader, withoutPreloader,
loadPromises, loadPromises,
autoDownloadSize, autoDownloadSize,
size, size: photoSize,
managers managers
}); });
@ -354,7 +356,7 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
withoutPreloader: true, withoutPreloader: true,
loadPromises, loadPromises,
autoDownloadSize: autoDownload?.photo, autoDownloadSize: autoDownload?.photo,
size, size: photoSize,
managers managers
}); });
@ -386,7 +388,7 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
let cacheContext: ThumbCache; let cacheContext: ThumbCache;
const getCacheContext = async() => { const getCacheContext = async() => {
return cacheContext = await managers.thumbsStorage.getCacheContext(doc); return cacheContext = await managers.thumbsStorage.getCacheContext(doc, videoSize?.type);
}; };
await getCacheContext(); await getCacheContext();
@ -426,18 +428,6 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
} }
}, {once: true}); }, {once: true});
onMediaLoad(video).then(() => {
if(group) {
animationIntersector.addAnimation(video, group);
}
if(preloader && !uploadFileName) {
preloader.detach();
}
renderDeferred.resolve();
});
if(doc.type === 'video') { if(doc.type === 'video') {
const onTimeUpdate = () => { const onTimeUpdate = () => {
if(!video.duration) { if(!video.duration) {
@ -478,7 +468,13 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
let loadPromise: Promise<any> = Promise.resolve(); let loadPromise: Promise<any> = Promise.resolve();
if((preloader && !uploadFileName) || withoutPreloader) { if((preloader && !uploadFileName) || withoutPreloader) {
if(!cacheContext.downloaded && !doc.supportsStreaming) { if(!cacheContext.downloaded && !doc.supportsStreaming) {
const promise = loadPromise = managers.apiFileManager.downloadMediaURL({media: doc, queueId: lazyLoadQueue?.queueId, onlyCache: noAutoDownload}); const promise = loadPromise = appDownloadManager.downloadMediaURL({
media: doc,
queueId: lazyLoadQueue?.queueId,
onlyCache: noAutoDownload,
thumb: videoSize
});
if(preloader) { if(preloader) {
preloader.attach(container, false, promise); preloader.attach(container, false, promise);
} }
@ -512,8 +508,21 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
} }
await getCacheContext(); await getCacheContext();
onMediaLoad(video).then(() => {
if(group) {
animationIntersector.addAnimation(video, group);
}
if(preloader && !uploadFileName) {
preloader.detach();
}
renderDeferred.resolve();
});
renderImageFromUrl(video, cacheContext.url); renderImageFromUrl(video, cacheContext.url);
}, () => {}); }, noop);
return {download: loadPromise, render: renderDeferred}; return {download: loadPromise, render: renderDeferred};
}; };

View File

@ -4,6 +4,8 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import onMediaLoad from '../onMediaLoad';
// import { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck"; // import { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck";
export const loadedURLs: {[url: string]: boolean} = {}; export const loadedURLs: {[url: string]: boolean} = {};
@ -22,17 +24,24 @@ export default function renderImageFromUrl(
) { ) {
if(!url) { if(!url) {
console.error('renderImageFromUrl: no url?', elem, url); console.error('renderImageFromUrl: no url?', elem, url);
callback && callback(); callback?.();
return; return;
} }
if(((loadedURLs[url]/* && false */) && useCache) || elem instanceof HTMLVideoElement) { const isVideo = elem instanceof HTMLVideoElement;
if(((loadedURLs[url]/* && false */) && useCache) || isVideo) {
if(elem) { if(elem) {
set(elem, url); set(elem, url);
} }
callback && callback(); if(callback) {
// callback && getHeavyAnimationPromise().then(() => callback()); if(isVideo) {
onMediaLoad(elem).then(callback);
} else {
callback?.();
}
// callback && getHeavyAnimationPromise().then(() => callback());
}
} else { } else {
const isImage = elem instanceof HTMLImageElement; const isImage = elem instanceof HTMLImageElement;
const loader = isImage ? elem as HTMLImageElement : new Image(); const loader = isImage ? elem as HTMLImageElement : new Image();
@ -48,7 +57,7 @@ export default function renderImageFromUrl(
// console.log('onload:', url, performance.now() - perf); // console.log('onload:', url, performance.now() - perf);
// TODO: переделать прогрузки аватаров до начала анимации, иначе с этим ожиданием они неприятно появляются // TODO: переделать прогрузки аватаров до начала анимации, иначе с этим ожиданием они неприятно появляются
// callback && getHeavyAnimationPromise().then(() => callback()); // callback && getHeavyAnimationPromise().then(() => callback());
callback && callback(); callback?.();
}, {once: true}); }, {once: true});
if(callback) { if(callback) {

View File

@ -1,54 +0,0 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import sequentialDom from '../sequentialDom';
import renderImageFromUrl from './renderImageFromUrl';
export default function renderImageWithFadeIn(
container: HTMLElement,
image: HTMLImageElement,
url: string,
needFadeIn: boolean,
aspecter = container,
thumbImage?: HTMLElement
) {
if(needFadeIn) {
image.classList.add('fade-in');
}
const promise = new Promise<void>((resolve) => {
/* if(photo._ === 'document') {
console.error('wrapPhoto: will render document', photo, size, cacheContext);
return resolve();
} */
renderImageFromUrl(image, url, () => {
sequentialDom.mutateElement(container, () => {
aspecter.append(image);
resolve();
/* fastRaf(() => {
resolve();
}); */
if(needFadeIn) {
image.addEventListener('animationend', () => {
sequentialDom.mutate(() => {
image.classList.remove('fade-in');
thumbImage?.remove();
});
}, {once: true});
} else {
thumbImage?.remove();
}
});
});
});
// recordPromise(promise, 'renderImageWithFadeIn');
return promise;
}

View File

@ -0,0 +1,42 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import sequentialDom from '../sequentialDom';
import {renderImageFromUrlPromise} from './renderImageFromUrl';
export default function renderMediaWithFadeIn(
container: HTMLElement,
media: Parameters<typeof renderImageFromUrlPromise>[0],
url: string,
needFadeIn: boolean,
aspecter = container,
thumbImage?: HTMLElement
) {
if(needFadeIn) {
media.classList.add('fade-in');
}
const promise = renderImageFromUrlPromise(media, url).then(() => {
return sequentialDom.mutateElement(container, () => {
aspecter.append(media);
if(needFadeIn) {
media.addEventListener('animationend', () => {
sequentialDom.mutate(() => {
media.classList.remove('fade-in');
thumbImage?.remove();
});
}, {once: true});
} else {
thumbImage?.remove();
}
});
});
// recordPromise(promise, 'renderImageWithFadeIn');
return promise;
}

View File

@ -10,8 +10,9 @@ import type {ThumbCache} from '../lib/storages/thumbs';
import getImageFromStrippedThumb from './getImageFromStrippedThumb'; import getImageFromStrippedThumb from './getImageFromStrippedThumb';
export default function getStrippedThumbIfNeeded(photo: MyPhoto | MyDocument, cacheContext: ThumbCache, useBlur: boolean, ignoreCache = false) { export default function getStrippedThumbIfNeeded(photo: MyPhoto | MyDocument, cacheContext: ThumbCache, useBlur: boolean, ignoreCache = false) {
if(!cacheContext.downloaded || (['video', 'gif'] as MyDocument['type'][]).includes((photo as MyDocument).type) || ignoreCache) { const isVideo = (['video', 'gif'] as MyDocument['type'][]).includes((photo as MyDocument).type);
if(photo._ === 'document' && cacheContext.downloaded && !ignoreCache) { if(!cacheContext.downloaded || isVideo || ignoreCache) {
if(photo._ === 'document' && cacheContext.downloaded && !ignoreCache && !isVideo) {
return null; return null;
} }

View File

@ -25,6 +25,7 @@ import getDocumentURL from './utils/docs/getDocumentURL';
import type {ThumbCache} from '../storages/thumbs'; import type {ThumbCache} from '../storages/thumbs';
import makeError from '../../helpers/makeError'; import makeError from '../../helpers/makeError';
import {EXTENSION_MIME_TYPE_MAP} from '../../environment/mimeTypeMap'; import {EXTENSION_MIME_TYPE_MAP} from '../../environment/mimeTypeMap';
import {THUMB_TYPE_FULL} from '../mtproto/mtproto_config';
export type MyDocument = Document.document; export type MyDocument = Document.document;
@ -326,7 +327,7 @@ export class AppDocsManager extends AppManager {
w: 0, w: 0,
location: {} as any, location: {} as any,
size: file.size, size: file.size,
type: 'full' type: THUMB_TYPE_FULL
} as PhotoSize.photoSize; } as PhotoSize.photoSize;
let document: MyDocument = { let document: MyDocument = {
_: 'document', _: 'document',

View File

@ -25,7 +25,7 @@ import {MyPhoto} from './appPhotosManager';
import {getFileNameByLocation} from '../../helpers/fileName'; import {getFileNameByLocation} from '../../helpers/fileName';
import DEBUG from '../../config/debug'; import DEBUG from '../../config/debug';
import SlicedArray, {Slice, SliceEnd} from '../../helpers/slicedArray'; import SlicedArray, {Slice, SliceEnd} from '../../helpers/slicedArray';
import {FOLDER_ID_ALL, MUTE_UNTIL, NULL_PEER_ID, REAL_FOLDER_ID, REPLIES_HIDDEN_CHANNEL_ID, REPLIES_PEER_ID, SERVICE_PEER_ID} from '../mtproto/mtproto_config'; import {FOLDER_ID_ALL, MUTE_UNTIL, NULL_PEER_ID, REAL_FOLDER_ID, REPLIES_HIDDEN_CHANNEL_ID, REPLIES_PEER_ID, SERVICE_PEER_ID, THUMB_TYPE_FULL} from '../mtproto/mtproto_config';
import telegramMeWebManager from '../mtproto/telegramMeWebManager'; import telegramMeWebManager from '../mtproto/telegramMeWebManager';
import {getMiddleware} from '../../helpers/middleware'; import {getMiddleware} from '../../helpers/middleware';
import assumeType from '../../helpers/assumeType'; import assumeType from '../../helpers/assumeType';
@ -758,7 +758,7 @@ export class AppMessagesManager extends AppManager {
_: 'photoSize', _: 'photoSize',
w: options.width, w: options.width,
h: options.height, h: options.height,
type: 'full', type: THUMB_TYPE_FULL,
location: null, location: null,
size: file.size size: file.size
} as PhotoSize.photoSize; } as PhotoSize.photoSize;
@ -842,7 +842,7 @@ export class AppMessagesManager extends AppManager {
_: 'photoSize', _: 'photoSize',
w: options.width, w: options.width,
h: options.height, h: options.height,
type: 'full', type: THUMB_TYPE_FULL,
size: file.size size: file.size
}; };
} else if(attachType === 'video') { } else if(attachType === 'video') {
@ -4947,7 +4947,7 @@ export class AppMessagesManager extends AppManager {
if(/* photo._ !== 'photoEmpty' */photo) { if(/* photo._ !== 'photoEmpty' */photo) {
const newPhotoSize = newPhoto.sizes[newPhoto.sizes.length - 1]; const newPhotoSize = newPhoto.sizes[newPhoto.sizes.length - 1];
const cacheContext = this.thumbsStorage.getCacheContext(newPhoto, newPhotoSize.type); const cacheContext = this.thumbsStorage.getCacheContext(newPhoto, newPhotoSize.type);
const oldCacheContext = this.thumbsStorage.getCacheContext(photo, 'full'); const oldCacheContext = this.thumbsStorage.getCacheContext(photo, THUMB_TYPE_FULL);
Object.assign(cacheContext, oldCacheContext); Object.assign(cacheContext, oldCacheContext);
const photoSize = newPhoto.sizes[newPhoto.sizes.length - 1] as PhotoSize.photoSize; const photoSize = newPhoto.sizes[newPhoto.sizes.length - 1] as PhotoSize.photoSize;

View File

@ -6,8 +6,9 @@
import type {MyDocument} from '../../appDocsManager'; import type {MyDocument} from '../../appDocsManager';
import type {MyPhoto} from '../../appPhotosManager'; import type {MyPhoto} from '../../appPhotosManager';
import type {PhotoSize, WebDocument} from '../../../../layer'; import type {PhotoSize, VideoSize, WebDocument} from '../../../../layer';
import calcImageInBox from '../../../../helpers/calcImageInBox'; import calcImageInBox from '../../../../helpers/calcImageInBox';
import {THUMB_TYPE_FULL} from '../../../mtproto/mtproto_config';
export default function choosePhotoSize( export default function choosePhotoSize(
photo: MyPhoto | MyDocument | WebDocument, photo: MyPhoto | MyDocument | WebDocument,
@ -32,15 +33,15 @@ export default function choosePhotoSize(
c crop 640x640 c crop 640x640
d crop 1280x1280 */ d crop 1280x1280 */
let bestPhotoSize: PhotoSize = {_: 'photoSizeEmpty', type: ''}; let sizes: PhotoSize[] = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs as PhotoSize[];
let sizes = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs as PhotoSize[]; let bestPhotoSize: typeof sizes[0] = {_: 'photoSizeEmpty', type: THUMB_TYPE_FULL};
if(pushDocumentSize && sizes && photo._ !== 'photo') { if(pushDocumentSize && sizes && photo._ !== 'photo') {
sizes = sizes.concat({ sizes = sizes.concat({
_: 'photoSize', _: 'photoSize',
w: photo.w, w: photo.w,
h: photo.h, h: photo.h,
size: photo.size, size: photo.size,
type: undefined type: THUMB_TYPE_FULL
}); });
} }

View File

@ -19,6 +19,7 @@ export const SERVICE_PEER_ID: PeerId = 777000;
export const MUTE_UNTIL = 0x7FFFFFFF; export const MUTE_UNTIL = 0x7FFFFFFF;
export const BOT_START_PARAM = ''; export const BOT_START_PARAM = '';
export const MAX_FILE_SAVE_SIZE = 20 * 1024 * 1024; export const MAX_FILE_SAVE_SIZE = 20 * 1024 * 1024;
export const THUMB_TYPE_FULL = '';
export const FOLDER_ID_ALL: REAL_FOLDER_ID = 0; export const FOLDER_ID_ALL: REAL_FOLDER_ID = 0;
export const FOLDER_ID_ARCHIVE: REAL_FOLDER_ID = 1; export const FOLDER_ID_ARCHIVE: REAL_FOLDER_ID = 1;

View File

@ -304,6 +304,10 @@ class ApiManagerProxy extends MTProtoMessagePort {
this.serviceMessagePort.addMultipleEventsListeners({ this.serviceMessagePort.addMultipleEventsListeners({
port: (payload, source, event) => { port: (payload, source, event) => {
this.invokeVoid('serviceWorkerPort', undefined, undefined, [event.ports[0]]); this.invokeVoid('serviceWorkerPort', undefined, undefined, [event.ports[0]]);
},
hello: (payload, source) => {
this.serviceMessagePort.resendLockTask(source);
} }
}); });
// #endif // #endif

View File

@ -128,7 +128,8 @@ export default class SuperMessagePort<
protected onPortDisconnect: (source: MessageEventSource) => void; protected onPortDisconnect: (source: MessageEventSource) => void;
// protected onPortConnect: (source: MessageEventSource) => void; // protected onPortConnect: (source: MessageEventSource) => void;
protected resolveLocks: Map<SendPort, () => void>; protected heldLocks: Map<SendPort, {resolve: () => void, id: string}>;
protected requestedLocks: Map<string, SendPort>;
constructor(protected logSuffix?: string) { constructor(protected logSuffix?: string) {
super(false); super(false);
@ -141,7 +142,8 @@ export default class SuperMessagePort<
this.pending = new Map(); this.pending = new Map();
this.log = logger('MP' + (logSuffix ? '-' + logSuffix : '')); this.log = logger('MP' + (logSuffix ? '-' + logSuffix : ''));
this.debug = DEBUG; this.debug = DEBUG;
this.resolveLocks = new Map(); this.heldLocks = new Map();
this.requestedLocks = new Map();
this.processTaskMap = { this.processTaskMap = {
result: this.processResultTask, result: this.processResultTask,
@ -188,10 +190,10 @@ export default class SuperMessagePort<
if('locks' in navigator) { if('locks' in navigator) {
const id = ['lock', tabId, this.logSuffix || '', Math.random() * 0x7FFFFFFF | 0].join('-'); const id = ['lock', tabId, this.logSuffix || '', Math.random() * 0x7FFFFFFF | 0].join('-');
this.log.warn('created lock', id); this.log.warn('created lock', id);
const promise = new Promise<void>((resolve) => this.resolveLocks.set(port, resolve)) const promise = new Promise<void>((resolve) => this.heldLocks.set(port, {resolve, id}))
.then(() => this.resolveLocks.delete(port)); .then(() => this.heldLocks.delete(port));
navigator.locks.request(id, () => promise); navigator.locks.request(id, () => promise);
this.pushTask(this.createTask('lock', id)); this.resendLockTask(port);
} else { } else {
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
const task = this.createTask('close', undefined); const task = this.createTask('close', undefined);
@ -203,6 +205,15 @@ export default class SuperMessagePort<
this.releasePending(); this.releasePending();
} }
public resendLockTask(port: SendPort) {
const lock = this.heldLocks.get(port);
if(!lock) {
return;
}
this.pushTask(this.createTask('lock', lock.id), port);
}
// ! Can't rely on ping because timers can be suspended // ! Can't rely on ping because timers can be suspended
// protected sendPing(port: SendPort, loop = IS_WORKER) { // protected sendPing(port: SendPort, loop = IS_WORKER) {
// let timeout: number; // let timeout: number;
@ -251,8 +262,8 @@ export default class SuperMessagePort<
this.onPortDisconnect?.(port as any); this.onPortDisconnect?.(port as any);
const resolveLock = this.resolveLocks.get(port as SendPort); const heldLock = this.heldLocks.get(port as SendPort);
resolveLock?.(); heldLock?.resolve();
const error = makeError('PORT_DISCONNECTED'); const error = makeError('PORT_DISCONNECTED');
for(const id in this.awaiting) { for(const id in this.awaiting) {
@ -430,8 +441,15 @@ export default class SuperMessagePort<
// }; // };
protected processLockTask = (task: LockTask, source: MessageEventSource, event: MessageEvent) => { protected processLockTask = (task: LockTask, source: MessageEventSource, event: MessageEvent) => {
navigator.locks.request(task.payload, () => { const id = task.payload;
if(this.requestedLocks.has(id)) {
return;
}
this.requestedLocks.set(id, source);
navigator.locks.request(id, () => {
this.processCloseTask(undefined, source, undefined); this.processCloseTask(undefined, source, undefined);
this.requestedLocks.delete(id);
}); });
}; };
@ -556,7 +574,7 @@ export default class SuperMessagePort<
const interval = ctx.setInterval(() => { const interval = ctx.setInterval(() => {
this.log.error('task still has no result', task, port); this.log.error('task still has no result', task, port);
}, 5e3); }, 60e3);
} else if(false) { } else if(false) {
// let timedOut = false; // let timedOut = false;
const startTime = Date.now(); const startTime = Date.now();

View File

@ -49,8 +49,9 @@ const onWindowConnected = (source: WindowClient) => {
} }
log('windows', Array.from(connectedWindows)); log('windows', Array.from(connectedWindows));
serviceMessagePort.invokeVoid('hello', undefined, source);
sendMessagePortIfNeeded(source); sendMessagePortIfNeeded(source);
connectedWindows.add(source.id); connectedWindows.set(source.id, source);
}; };
export const serviceMessagePort = new ServiceMessagePort<false>(); export const serviceMessagePort = new ServiceMessagePort<false>();
@ -83,7 +84,7 @@ getWindowClients().then((windowClients) => {
}); });
}); });
const connectedWindows: Set<string> = new Set(); const connectedWindows: Map<string, WindowClient> = new Map();
(self as any).connectedWindows = connectedWindows; (self as any).connectedWindows = connectedWindows;
listenMessagePort(serviceMessagePort, undefined, (source) => { listenMessagePort(serviceMessagePort, undefined, (source) => {
log('something has disconnected', source); log('something has disconnected', source);

View File

@ -51,6 +51,7 @@ export default class ServiceMessagePort<Master extends boolean = false> extends
}, { }, {
// to main thread // to main thread
pushClick: (payload: PushNotificationObject) => void, pushClick: (payload: PushNotificationObject) => void,
hello: (payload: void, source: MessageEventSource) => void,
// to mtproto worker // to mtproto worker
requestFilePart: (payload: ServiceRequestFilePartTaskPayload) => Promise<MyUploadFile> | MyUploadFile requestFilePart: (payload: ServiceRequestFilePartTaskPayload) => Promise<MyUploadFile> | MyUploadFile

View File

@ -7,6 +7,7 @@
import type {WebDocument} from '../../layer'; import type {WebDocument} from '../../layer';
import type {MyDocument} from '../appManagers/appDocsManager'; import type {MyDocument} from '../appManagers/appDocsManager';
import type {MyPhoto} from '../appManagers/appPhotosManager'; import type {MyPhoto} from '../appManagers/appPhotosManager';
import {THUMB_TYPE_FULL} from '../mtproto/mtproto_config';
export type ThumbCache = { export type ThumbCache = {
downloaded: number, downloaded: number,
@ -20,7 +21,7 @@ export type ThumbsCache = {
} }
}; };
const thumbFullSize = 'full'; const thumbFullSize = THUMB_TYPE_FULL;
export type ThumbStorageMedia = MyPhoto | MyDocument | WebDocument; export type ThumbStorageMedia = MyPhoto | MyDocument | WebDocument;

View File

@ -175,7 +175,8 @@
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
} */ } */
> img { > img,
> video {
object-fit: cover; object-fit: cover;
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -227,7 +227,8 @@ $inactive-opacity: .4;
} }
} }
&-prev-button, &-next-button { &-prev-button,
&-next-button {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
color: #fff; color: #fff;
@ -302,7 +303,9 @@ $inactive-opacity: .4;
height: 100%; height: 100%;
} }
img, video { img,
video,
.canvas-thumbnail {
width: 100%; width: 100%;
height: 100%; height: 100%;
max-width: 100%; max-width: 100%;
@ -324,7 +327,8 @@ $inactive-opacity: .4;
// !SAFARI // !SAFARI
svg { svg {
img, video { img,
video {
position: unset; position: unset;
} }
} }
@ -395,7 +399,8 @@ $inactive-opacity: .4;
} */ } */
img:not(.thumbnail), img:not(.thumbnail),
video { video,
.canvas-thumbnail {
/* height: auto; /* height: auto;
width: auto; */ width: auto; */
object-fit: contain; object-fit: contain;
@ -410,7 +415,8 @@ $inactive-opacity: .4;
} }
&.hiding { &.hiding {
img, video { img,
video {
opacity: 0; opacity: 0;
} }
} }
@ -437,7 +443,8 @@ $inactive-opacity: .4;
z-index: 5; z-index: 5;
padding: 0 1.25rem; padding: 0 1.25rem;
.btn-icon, .media-viewer-author { .btn-icon,
.media-viewer-author {
color: #fff; color: #fff;
opacity: $inactive-opacity; opacity: $inactive-opacity;