Browse Source

Fix gif thumbs

Fix dead streaming & downloading through SW
Possible fix for stuck sticker viewer
master
Eduard Kuzmenko 2 years ago committed by r4sas
parent
commit
5f429acfe2
  1. 30
      src/components/appMediaViewerBase.ts
  2. 2
      src/components/appSearchSuper..ts
  3. 4
      src/components/chat/inlineHelper.ts
  4. 51
      src/components/chat/replyContainer.ts
  5. 3
      src/components/popups/newMedia.ts
  6. 2
      src/components/sidebarLeft/tabs/background.ts
  7. 3
      src/components/stickerViewer.ts
  8. 2
      src/components/wrappers/document.ts
  9. 83
      src/components/wrappers/photo.ts
  10. 49
      src/components/wrappers/video.ts
  11. 19
      src/helpers/dom/renderImageFromUrl.ts
  12. 54
      src/helpers/dom/renderImageWithFadeIn.ts
  13. 42
      src/helpers/dom/renderMediaWithFadeIn.ts
  14. 5
      src/helpers/getStrippedThumbIfNeeded.ts
  15. 3
      src/lib/appManagers/appDocsManager.ts
  16. 8
      src/lib/appManagers/appMessagesManager.ts
  17. 9
      src/lib/appManagers/utils/photos/choosePhotoSize.ts
  18. 1
      src/lib/mtproto/mtproto_config.ts
  19. 4
      src/lib/mtproto/mtprotoworker.ts
  20. 36
      src/lib/mtproto/superMessagePort.ts
  21. 5
      src/lib/serviceWorker/index.service.ts
  22. 1
      src/lib/serviceWorker/serviceMessagePort.ts
  23. 3
      src/lib/storages/thumbs.ts
  24. 3
      src/scss/partials/_chatPinned.scss
  25. 19
      src/scss/partials/_mediaViewer.scss

30
src/components/appMediaViewerBase.ts

@ -787,15 +787,33 @@ export default class AppMediaViewerBase< @@ -787,15 +787,33 @@ export default class AppMediaViewerBase<
});
if(!closing) {
let mediaElement: HTMLImageElement | HTMLVideoElement;
let mediaElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement;
let src: string;
if(target instanceof HTMLVideoElement) {
const elements = Array.from(target.parentElement.querySelectorAll('img')) as HTMLImageElement[];
if(elements.length) {
target = elements.pop();
// if(target instanceof HTMLVideoElement) {
const selector = 'video, img, .canvas-thumbnail';
const queryFrom = target.matches(selector) ? target.parentElement : target;
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
const images = Array.from(target.querySelectorAll('img')) as HTMLImageElement[];
@ -865,6 +883,8 @@ export default class AppMediaViewerBase< @@ -865,6 +883,8 @@ export default class AppMediaViewerBase<
foreignObject.setAttributeNS(null, 'height', '' + containerRect.height);
mover.prepend(newSvg);
} else if(target instanceof HTMLCanvasElement) {
mediaElement = target;
}
if(aspecter) {

2
src/components/appSearchSuper..ts

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

4
src/components/chat/inlineHelper.ts

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

51
src/components/chat/replyContainer.ts

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

3
src/components/popups/newMedia.ts

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

2
src/components/sidebarLeft/tabs/background.ts

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

3
src/components/stickerViewer.ts

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

2
src/components/wrappers/document.ts

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

83
src/components/wrappers/photo.ts

@ -4,9 +4,9 @@ @@ -4,9 +4,9 @@
* 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 {Message, PhotoSize, WebDocument} from '../../layer';
import {Message, PhotoSize, VideoSize, WebDocument} from '../../layer';
import {MyDocument} from '../../lib/appManagers/appDocsManager';
import {MyPhoto} from '../../lib/appManagers/appPhotosManager';
import rootScope from '../../lib/rootScope';
@ -20,6 +20,9 @@ import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize' @@ -20,6 +20,9 @@ import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize'
import type {ThumbCache} from '../../lib/storages/thumbs';
import appDownloadManager from '../../lib/appManagers/appDownloadManager';
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}: {
photo: MyPhoto | MyDocument | WebDocument,
@ -31,7 +34,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -31,7 +34,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
isOut?: boolean,
lazyLoadQueue?: LazyLoadQueue,
middleware?: () => boolean,
size?: PhotoSize,
size?: PhotoSize | VideoSize,
withoutPreloader?: boolean,
loadPromises?: Promise<any>[],
autoDownloadSize?: number,
@ -41,24 +44,27 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -41,24 +44,27 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
blurAfter?: boolean,
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);
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);
}
return {
loadPromises: {
thumb: Promise.resolve(),
full: Promise.resolve()
},
images: {
thumb: null,
full: null
},
preloader: null,
aspecter: null
};
return ret;
}
let noAutoDownload = autoDownloadSize === 0;
@ -76,11 +82,10 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -76,11 +82,10 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
let thumbImage: HTMLImageElement | HTMLCanvasElement;
// let image: HTMLImageElement;
let cacheContext: ThumbCache;
const isGif = photo._ === 'document' && photo.mime_type === 'image/gif' && !size;
const isGif = isDocument && photo.mime_type === 'image/gif' && !size;
// if(withTail) {
// image = wrapMediaWithTail(photo, message, container, boxWidth, boxHeight, isOut);
// } else {
const image = new Image();
if(boxWidth && boxHeight && !size) { // !album
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 @@ -88,7 +93,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
w: photo.w,
h: photo.h,
size: photo.size,
type: 'full'
type: THUMB_TYPE_FULL
} : undefined);
size = set.photoSize;
isFit = set.isFit;
@ -147,14 +152,29 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -147,14 +152,29 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
const gotThumb = getStrippedThumbIfNeeded(photo, cacheContext, !noBlur);
if(gotThumb) {
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');
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);
@ -194,7 +214,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -194,7 +214,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
};
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) => {
@ -236,7 +256,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -236,7 +256,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
noAutoDownload = undefined;
const renderPromise = promise.then(onLoad);
renderPromise.catch(() => {});
renderPromise.catch(noop);
return {download: promise, render: renderPromise};
};
@ -261,22 +281,15 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo @@ -261,22 +281,15 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
// const perf = performance.now();
await loadThumbPromise;
ret.loadPromises.thumb = loadThumbPromise;
ret.loadPromises.full = loadPromise || Promise.resolve();
ret.preloader = preloader;
ret.aspecter = aspecter;
// const elapsedTime = performance.now() - perf;
// if(elapsedTime > 4) {
// console.log('wrapping photo thumb time', elapsedTime, photo, size);
// }
return {
loadPromises: {
thumb: loadThumbPromise,
full: loadPromise || Promise.resolve()
},
images: {
thumb: thumbImage,
full: image
},
preloader,
aspecter
};
return ret;
}

49
src/components/wrappers/video.ts

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

19
src/helpers/dom/renderImageFromUrl.ts

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

54
src/helpers/dom/renderImageWithFadeIn.ts

@ -1,54 +0,0 @@ @@ -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;
}

42
src/helpers/dom/renderMediaWithFadeIn.ts

@ -0,0 +1,42 @@ @@ -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;
}

5
src/helpers/getStrippedThumbIfNeeded.ts

@ -10,8 +10,9 @@ import type {ThumbCache} from '../lib/storages/thumbs'; @@ -10,8 +10,9 @@ import type {ThumbCache} from '../lib/storages/thumbs';
import getImageFromStrippedThumb from './getImageFromStrippedThumb';
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) {
if(photo._ === 'document' && cacheContext.downloaded && !ignoreCache) {
const isVideo = (['video', 'gif'] as MyDocument['type'][]).includes((photo as MyDocument).type);
if(!cacheContext.downloaded || isVideo || ignoreCache) {
if(photo._ === 'document' && cacheContext.downloaded && !ignoreCache && !isVideo) {
return null;
}

3
src/lib/appManagers/appDocsManager.ts

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

8
src/lib/appManagers/appMessagesManager.ts

@ -25,7 +25,7 @@ import {MyPhoto} from './appPhotosManager'; @@ -25,7 +25,7 @@ import {MyPhoto} from './appPhotosManager';
import {getFileNameByLocation} from '../../helpers/fileName';
import DEBUG from '../../config/debug';
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 {getMiddleware} from '../../helpers/middleware';
import assumeType from '../../helpers/assumeType';
@ -758,7 +758,7 @@ export class AppMessagesManager extends AppManager { @@ -758,7 +758,7 @@ export class AppMessagesManager extends AppManager {
_: 'photoSize',
w: options.width,
h: options.height,
type: 'full',
type: THUMB_TYPE_FULL,
location: null,
size: file.size
} as PhotoSize.photoSize;
@ -842,7 +842,7 @@ export class AppMessagesManager extends AppManager { @@ -842,7 +842,7 @@ export class AppMessagesManager extends AppManager {
_: 'photoSize',
w: options.width,
h: options.height,
type: 'full',
type: THUMB_TYPE_FULL,
size: file.size
};
} else if(attachType === 'video') {
@ -4947,7 +4947,7 @@ export class AppMessagesManager extends AppManager { @@ -4947,7 +4947,7 @@ export class AppMessagesManager extends AppManager {
if(/* photo._ !== 'photoEmpty' */photo) {
const newPhotoSize = newPhoto.sizes[newPhoto.sizes.length - 1];
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);
const photoSize = newPhoto.sizes[newPhoto.sizes.length - 1] as PhotoSize.photoSize;

9
src/lib/appManagers/utils/photos/choosePhotoSize.ts

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

1
src/lib/mtproto/mtproto_config.ts

@ -19,6 +19,7 @@ export const SERVICE_PEER_ID: PeerId = 777000; @@ -19,6 +19,7 @@ export const SERVICE_PEER_ID: PeerId = 777000;
export const MUTE_UNTIL = 0x7FFFFFFF;
export const BOT_START_PARAM = '';
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_ARCHIVE: REAL_FOLDER_ID = 1;

4
src/lib/mtproto/mtprotoworker.ts

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

36
src/lib/mtproto/superMessagePort.ts

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

5
src/lib/serviceWorker/index.service.ts

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

1
src/lib/serviceWorker/serviceMessagePort.ts

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

3
src/lib/storages/thumbs.ts

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

3
src/scss/partials/_chatPinned.scss

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

19
src/scss/partials/_mediaViewer.scss

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

Loading…
Cancel
Save