diff --git a/src/components/popups/newMedia.ts b/src/components/popups/newMedia.ts index fd521224..2c44061c 100644 --- a/src/components/popups/newMedia.ts +++ b/src/components/popups/newMedia.ts @@ -21,15 +21,20 @@ import isSendShortcutPressed from "../../helpers/dom/isSendShortcutPressed"; import placeCaretAtEnd from "../../helpers/dom/placeCaretAtEnd"; import rootScope from "../../lib/rootScope"; import RichTextProcessor from "../../lib/richtextprocessor"; +import { MediaSize } from "../../helpers/mediaSizes"; type SendFileParams = Partial<{ file: File, objectURL: string, - thumbBlob: Blob, - thumbURL: string, + thumb: { + blob: Blob, + url: string, + size: MediaSize + }, width: number, height: number, - duration: number + duration: number, + noSound: boolean }>; // TODO: .gif upload as video @@ -263,11 +268,18 @@ export default class PopupNewMedia extends PopupElement { params.width = video.videoWidth; params.height = video.videoHeight; params.duration = Math.floor(video.duration); + + const audioDecodedByteCount = (video as any).webkitAudioDecodedByteCount; + if(audioDecodedByteCount !== undefined) { + params.noSound = !audioDecodedByteCount; + } itemDiv.append(video); - createPosterFromVideo(video).then(blob => { - params.thumbBlob = blob; - params.thumbURL = URL.createObjectURL(blob); + createPosterFromVideo(video).then(thumb => { + params.thumb = { + url: URL.createObjectURL(thumb.blob), + ...thumb + }; resolve(itemDiv); }); }); diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 4807dd06..dd95c463 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -309,8 +309,9 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai const cacheContext = appDownloadManager.getCacheContext(doc); + const isUpload = !!message?.media?.preloader; let preloader: ProgressivePreloader; - if(message?.media?.preloader) { // means upload + if(isUpload) { // means upload preloader = message.media.preloader as ProgressivePreloader; preloader.attach(container, false); noAutoDownload = undefined; @@ -333,7 +334,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai } let loadPromise: Promise = Promise.resolve(); - if(preloader) { + if(preloader && !isUpload) { if(!cacheContext.downloaded && !doc.supportsStreaming) { const promise = loadPromise = appDocsManager.downloadDoc(doc, lazyLoadQueue?.queueId, noAutoDownload); preloader.attach(container, false, promise); @@ -354,7 +355,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai console.error("Error " + video.error.code + "; details: " + video.error.message); } - if(preloader) { + if(preloader && !isUpload) { preloader.detach(); } @@ -386,7 +387,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai animationIntersector.addAnimation(video, group); } - if(preloader) { + if(preloader && !isUpload) { preloader.detach(); } @@ -410,7 +411,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai return {download: loadPromise, render: deferred}; }; - if(preloader) { + if(preloader && !isUpload) { preloader.setDownloadFunction(load); } diff --git a/src/helpers/files.ts b/src/helpers/files.ts index 67bf9d2c..2f40926b 100644 --- a/src/helpers/files.ts +++ b/src/helpers/files.ts @@ -4,38 +4,63 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import { makeMediaSize, MediaSize } from "./mediaSizes"; import { pause } from "./schedulers/pause"; import { isAppleMobile } from "./userAgent"; +export function scaleMediaElement(options: { + media: CanvasImageSource, + mediaSize: MediaSize, + boxSize: MediaSize, + quality?: number, + mimeType?: 'image/jpeg' | 'image/png' +}): Promise<{blob: Blob, size: MediaSize}> { + return new Promise((resolve) => { + const canvas = document.createElement('canvas'); + const size = options.mediaSize.aspectFitted(options.boxSize); + canvas.width = size.width * window.devicePixelRatio; + canvas.height = size.height * window.devicePixelRatio; + const ctx = canvas.getContext('2d'); + ctx.drawImage(options.media, 0, 0, canvas.width, canvas.height); + canvas.toBlob(blob => { + resolve({blob, size}); + }, options.mimeType ?? 'image/jpeg', options.quality ?? 1); + }); +} + export function preloadVideo(url: string): Promise { return new Promise((resolve, reject) => { const video = document.createElement('video'); video.volume = 0; - video.onloadedmetadata = () => resolve(video); - video.onerror = reject; + video.addEventListener('loadedmetadata', () => resolve(video), {once: true}); + video.addEventListener('error', reject, {once: true}); video.src = url; }); } -export function createPosterFromVideo(video: HTMLVideoElement): Promise { +export function createPosterFromVideo(video: HTMLVideoElement): ReturnType { return new Promise((resolve, reject) => { video.onseeked = () => { - const canvas = document.createElement('canvas'); - canvas.width = Math.min(1280, video.videoWidth); - canvas.height = Math.min(720, video.videoHeight); - const ctx = canvas.getContext('2d')!; - ctx.drawImage(video, 0, 0); - canvas.toBlob(blob => { - resolve(blob); - }, 'image/jpeg', 1); + video.onseeked = () => { + scaleMediaElement({ + media: video, + mediaSize: makeMediaSize(video.videoWidth, video.videoHeight), + boxSize: makeMediaSize(320, 240), + quality: .9 + }).then(resolve); + + video.onseeked = undefined; + }; + + video.currentTime = 0; }; - + video.onerror = reject; video.currentTime = Math.min(video.duration, 1); }); } -export async function createPosterForVideo(url: string): Promise { +export async function createPosterForVideo(url: string) { const video = await preloadVideo(url); return Promise.race([ diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 834e2f69..32980468 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -58,6 +58,7 @@ import telegramMeWebManager from "../mtproto/telegramMeWebManager"; import { getMiddleware } from "../../helpers/middleware"; import assumeType from "../../helpers/assumeType"; import appMessagesIdsManager from "./appMessagesIdsManager"; +import type { MediaSize } from "../../helpers/mediaSizes"; //console.trace('include'); // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет @@ -599,13 +600,17 @@ export class AppMessagesManager { width: number, height: number, objectURL: string, - thumbBlob: Blob, - thumbURL: string, + thumb: { + blob: Blob, + url: string, + size: MediaSize + }, duration: number, background: true, silent: true, clearDraft: true, scheduleDate: number, + noSound: boolean, waveform: Uint8Array, }> = {}) { @@ -696,10 +701,11 @@ export class AppMessagesManager { apiFileName = 'video.mp4'; actionName = 'sendMessageUploadVideoAction'; - let videoAttribute: DocumentAttribute.documentAttributeVideo = { + const videoAttribute: DocumentAttribute.documentAttributeVideo = { _: 'documentAttributeVideo', pFlags: { - round_message: options.isRoundMessage + round_message: options.isRoundMessage, + supports_streaming: true }, duration: options.duration, w: options.width, @@ -707,6 +713,15 @@ export class AppMessagesManager { }; attributes.push(videoAttribute); + + // * must follow after video attribute + if(options.noSound && + file.size > (10 * 1024) && + file.size < (10 * 1024 * 1024)) { + attributes.push({ + _: 'documentAttributeAnimated' + }); + } } else { attachType = 'document'; apiFileName = 'document.' + fileType.split('/')[1]; @@ -749,18 +764,18 @@ export class AppMessagesManager { size: file.size }; } else if(attachType === 'video') { - if(options.thumbURL) { + if(options.thumb) { thumb = { _: 'photoSize', - w: options.width, - h: options.height, - type: 'full', - size: options.thumbBlob.size + w: options.thumb.size.width, + h: options.thumb.size.height, + type: 'local-thumb', + size: options.thumb.blob.size }; const thumbCacheContext = appDownloadManager.getCacheContext(document, thumb.type); thumbCacheContext.downloaded = thumb.size; - thumbCacheContext.url = options.thumbURL; + thumbCacheContext.url = options.thumb.url; } } @@ -800,7 +815,6 @@ export class AppMessagesManager { if(err.name === 'AbortError' && !uploaded) { this.log('cancelling upload', media); - sentDeferred.reject(err); this.cancelPendingMessage(message.random_id); this.setTyping(peerId, {_: 'sendMessageCancelAction'}); @@ -867,12 +881,12 @@ export class AppMessagesManager { let thumbUploadPromise: typeof uploadPromise; if(attachType === 'video' && options.objectURL) { thumbUploadPromise = new Promise((resolve, reject) => { - const blobPromise = options.thumbBlob ? Promise.resolve(options.thumbBlob) : createPosterForVideo(options.objectURL); - blobPromise.then(blob => { - if(!blob) { + const thumbPromise = options.thumb && options.thumb.blob ? Promise.resolve(options.thumb) : createPosterForVideo(options.objectURL); + thumbPromise.then(thumb => { + if(!thumb) { resolve(null); } else { - appDownloadManager.upload(blob).then(resolve, reject); + appDownloadManager.upload(thumb.blob).then(resolve, reject); } }, reject); }); @@ -3571,23 +3585,16 @@ export class AppMessagesManager { if(peerId < 0 && appPeersManager.isChannel(peerId)) { const channelId = -peerId; - const channel = appChatsManager.getChat(channelId); - if(!channel.pFlags.creator && !(channel.pFlags.editor && channel.pFlags.megagroup)) { - const goodMsgIds: number[] = []; - if(channel.pFlags.editor || channel.pFlags.megagroup) { - mids.forEach((msgId, i) => { - const message = this.getMessageByPeer(peerId, mids[i]); - if(message.pFlags.out) { - goodMsgIds.push(msgId); - } - }); - } + const channel: Chat.channel = appChatsManager.getChat(channelId); + if(!channel.pFlags.creator && !channel.admin_rights?.pFlags?.delete_messages) { + mids = mids.filter((mid) => { + const message = this.getMessageByPeer(peerId, mid); + return !!message.pFlags.out; + }); - if(!goodMsgIds.length) { + if(!mids.length) { return; } - - mids = goodMsgIds; } promise = apiManager.invokeApi('channels.deleteMessages', { diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index 541d3031..1af81b7a 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -175,7 +175,7 @@ export class ApiFileManager { } public cancelDownload(fileName: string) { - const promises = (this.cachedDownloadPromises[fileName] ? [this.cachedDownloadPromises[fileName]] : []) || + const promises = (this.cachedDownloadPromises[fileName] ? [this.cachedDownloadPromises[fileName]] : undefined) || (this.uploadPromises[fileName] ? Array.from(this.uploadPromises[fileName]) : []); let canceled = false; for(let i = 0, length = promises.length; i < length; ++i) { diff --git a/src/scss/partials/_poll.scss b/src/scss/partials/_poll.scss index 8458d3be..a9ac7cd8 100644 --- a/src/scss/partials/_poll.scss +++ b/src/scss/partials/_poll.scss @@ -174,7 +174,8 @@ poll-element { &-votes-count { color: var(--secondary-text-color); font-size: .875rem; - padding-top: .1875rem; + // padding-top: .1875rem; // THIS IS MOCKUP! + margin-top: -.5rem; // this is human-readable } &-line {