diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 4bfa1eca5..6dd1c8e9a 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -23,7 +23,14 @@ import { ApiInvoice, } from '../../types'; -import { DELETED_COMMENTS_CHANNEL_ID, LOCAL_MESSAGE_ID_BASE, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config'; +import { + DELETED_COMMENTS_CHANNEL_ID, + LOCAL_MESSAGE_ID_BASE, + SERVICE_NOTIFICATIONS_USER_ID, + SUPPORTED_IMAGE_CONTENT_TYPES, + SUPPORTED_VIDEO_CONTENT_TYPES, + VIDEO_MOV_TYPE, +} from '../../../config'; import { pick } from '../../../util/iteratees'; import { getApiChatIdFromMtpPeer } from './chats'; import { buildStickerFromDocument } from './symbols'; @@ -286,6 +293,11 @@ export function buildVideoFromDocument(document: GramJs.Document): ApiVideo | un id, mimeType, thumbs, size, attributes, } = document; + // eslint-disable-next-line no-restricted-globals + if (mimeType === VIDEO_MOV_TYPE && !(self as any).isMovSupported) { + return undefined; + } + const videoAttr = attributes .find((a: any): a is GramJs.DocumentAttributeVideo => a instanceof GramJs.DocumentAttributeVideo); @@ -419,7 +431,7 @@ export function buildApiDocument(document: GramJs.TypeDocument): ApiDocument | u height: photoSize.h, }; - if (mimeType.startsWith('image/')) { + if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) { mediaType = 'photo'; const imageAttribute = attributes @@ -432,7 +444,7 @@ export function buildApiDocument(document: GramJs.TypeDocument): ApiDocument | u height, }; } - } else if (mimeType.startsWith('video/')) { + } else if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) { mediaType = 'video'; } } diff --git a/src/api/gramjs/methods/client.ts b/src/api/gramjs/methods/client.ts index 270033fec..a5216d182 100644 --- a/src/api/gramjs/methods/client.ts +++ b/src/api/gramjs/methods/client.ts @@ -44,9 +44,14 @@ export async function init(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) onUpdate = _onUpdate; - const { userAgent, platform, sessionData } = initialArgs; + const { + userAgent, platform, sessionData, isMovSupported, + } = initialArgs; const session = new sessions.CallbackSession(sessionData, onSessionUpdate); + // eslint-disable-next-line no-restricted-globals + (self as any).isMovSupported = isMovSupported; + client = new TelegramClient( session, process.env.TELEGRAM_T_API_ID, diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index 4d89692de..4a652a585 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -18,7 +18,13 @@ import { ApiReportReason, } from '../../types'; -import { ALL_FOLDER_ID, DEBUG, PINNED_MESSAGES_LIMIT } from '../../../config'; +import { + ALL_FOLDER_ID, + DEBUG, + PINNED_MESSAGES_LIMIT, + SUPPORTED_IMAGE_CONTENT_TYPES, + SUPPORTED_VIDEO_CONTENT_TYPES, +} from '../../../config'; import { invokeRequest, uploadFile } from './client'; import { buildApiMessage, @@ -506,9 +512,11 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, const attributes: GramJs.TypeDocumentAttribute[] = [new GramJs.DocumentAttributeFilename({ fileName: filename })]; if (quick) { - if (mimeType.startsWith('image/')) { + if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) { return new GramJs.InputMediaUploadedPhoto({ file: inputFile }); - } else { + } + + if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) { const { width, height, duration } = quick; if (duration !== undefined) { attributes.push(new GramJs.DocumentAttributeVideo({ diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 28634a6a4..310e8c4c6 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -4,6 +4,7 @@ export interface ApiInitialArgs { userAgent: string; platform?: string; sessionData?: ApiSessionData; + isMovSupported?: boolean; } export interface ApiOnProgress { diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index 9defe46a6..76cab54a4 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -13,12 +13,12 @@ import { MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN, SAFE_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN, SAFE_SCREEN_WIDTH_FOR_CHAT_INFO, - CONTENT_TYPES_FOR_QUICK_UPLOAD, ANIMATION_LEVEL_MAX, ANIMATION_END_DELAY, DARK_THEME_BG_COLOR, LIGHT_THEME_BG_COLOR, ANIMATION_LEVEL_MIN, + SUPPORTED_IMAGE_CONTENT_TYPES, } from '../../config'; import { IS_SINGLE_COLUMN_LAYOUT, @@ -99,8 +99,8 @@ type DispatchProps = Pick = ({ @@ -228,7 +228,8 @@ const MiddleColumn: FC = ({ // Filter unnecessary element for drag and drop images in Firefox (https://github.com/Ajaxy/telegram-tt/issues/49) // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#image .filter((item) => item.type !== 'text/uri-list') - .every(canBeQuicklyUploaded); + // As of September 2021, native clients suggest "send quick, but compressed" only for images + .every(isImage); setDropAreaState(shouldDrawQuick ? DropAreaState.QuickFile : DropAreaState.Document); }, []); diff --git a/src/components/middle/composer/AttachMenu.tsx b/src/components/middle/composer/AttachMenu.tsx index 93cc225f4..eab8bde46 100644 --- a/src/components/middle/composer/AttachMenu.tsx +++ b/src/components/middle/composer/AttachMenu.tsx @@ -1,6 +1,6 @@ import React, { FC, memo, useCallback } from '../../../lib/teact/teact'; -import { CONTENT_TYPES_FOR_QUICK_UPLOAD } from '../../../config'; +import { CONTENT_TYPES_WITH_PREVIEW } from '../../../config'; import { IS_TOUCH_ENV } from '../../../util/environment'; import { openSystemFilesDialog } from '../../../util/systemFilesDialog'; import { IAllowedAttachmentOptions } from '../../../modules/helpers'; @@ -35,7 +35,7 @@ const AttachMenu: FC = ({ const handleQuickSelect = useCallback(() => { openSystemFilesDialog( - Array.from(CONTENT_TYPES_FOR_QUICK_UPLOAD).join(','), + Array.from(CONTENT_TYPES_WITH_PREVIEW).join(','), (e) => handleFileSelect(e, true), ); }, [handleFileSelect]); diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index 8c532e55d..368dd0638 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -4,7 +4,12 @@ import React, { import { ApiAttachment, ApiChatMember, ApiUser } from '../../../api/types'; -import { CONTENT_TYPES_FOR_QUICK_UPLOAD, EDITABLE_INPUT_MODAL_ID } from '../../../config'; +import { + CONTENT_TYPES_WITH_PREVIEW, + EDITABLE_INPUT_MODAL_ID, + SUPPORTED_IMAGE_CONTENT_TYPES, + SUPPORTED_VIDEO_CONTENT_TYPES, +} from '../../../config'; import { getFileExtension } from '../../common/helpers/documentInfo'; import captureEscKeyListener from '../../../util/captureEscKeyListener'; import usePrevious from '../../../hooks/usePrevious'; @@ -128,7 +133,7 @@ const AttachmentModal: FC = ({ if (files?.length) { const newFiles = isQuick ? Array.from(files).filter((file) => { - return file.type && CONTENT_TYPES_FOR_QUICK_UPLOAD.has(file.type); + return file.type && CONTENT_TYPES_WITH_PREVIEW.has(file.type); }) : Array.from(files); @@ -149,8 +154,8 @@ const AttachmentModal: FC = ({ return undefined; } - const areAllPhotos = renderingAttachments.every((a) => a.mimeType.startsWith('image/')); - const areAllVideos = renderingAttachments.every((a) => a.mimeType.startsWith('video/')); + const areAllPhotos = renderingAttachments.every((a) => SUPPORTED_IMAGE_CONTENT_TYPES.has(a.mimeType)); + const areAllVideos = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType)); const areAllAudios = renderingAttachments.every((a) => a.mimeType.startsWith('audio/')); let title = ''; diff --git a/src/components/middle/composer/helpers/buildAttachment.ts b/src/components/middle/composer/helpers/buildAttachment.ts index 9f3f97ed7..94ef5cf01 100644 --- a/src/components/middle/composer/helpers/buildAttachment.ts +++ b/src/components/middle/composer/helpers/buildAttachment.ts @@ -1,4 +1,5 @@ import { ApiAttachment } from '../../../../api/types'; +import { SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES } from '../../../../config'; import { preloadImage, preloadVideo, @@ -17,7 +18,7 @@ export default async function buildAttachment( let quick; let previewBlobUrl; - if (mimeType.startsWith('image/')) { + if (SUPPORTED_IMAGE_CONTENT_TYPES.has(mimeType)) { if (isQuick) { const img = await preloadImage(blobUrl); const { width, height } = img; @@ -33,7 +34,7 @@ export default async function buildAttachment( } else { previewBlobUrl = blobUrl; } - } else if (mimeType.startsWith('video/')) { + } else if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) { const { videoWidth: width, videoHeight: height, duration } = await preloadVideo(blobUrl); quick = { width, height, duration }; diff --git a/src/config.ts b/src/config.ts index 7b5e8feac..08c916895 100644 --- a/src/config.ts +++ b/src/config.ts @@ -121,8 +121,19 @@ export const BASE_EMOJI_KEYWORD_LANG = 'en'; export const MENU_TRANSITION_DURATION = 200; export const SLIDE_TRANSITION_DURATION = 450; -export const CONTENT_TYPES_FOR_QUICK_UPLOAD = new Set([ - 'image/png', 'image/gif', 'image/jpeg', 'video/mp4', 'video/avi', 'video/quicktime', +export const VIDEO_MOV_TYPE = 'video/quicktime'; + +export const SUPPORTED_IMAGE_CONTENT_TYPES = new Set([ + 'image/png', 'image/gif', 'image/jpeg', +]); + +export const SUPPORTED_VIDEO_CONTENT_TYPES = new Set([ + 'video/mp4', // video/quicktime added dynamically in environment.ts +]); + +export const CONTENT_TYPES_WITH_PREVIEW = new Set([ + ...SUPPORTED_IMAGE_CONTENT_TYPES, + ...SUPPORTED_VIDEO_CONTENT_TYPES, ]); // eslint-disable-next-line max-len diff --git a/src/modules/actions/api/initial.ts b/src/modules/actions/api/initial.ts index 1f209947d..3af30b5f3 100644 --- a/src/modules/actions/api/initial.ts +++ b/src/modules/actions/api/initial.ts @@ -13,7 +13,7 @@ import { MEDIA_PROGRESSIVE_CACHE_NAME, IS_TEST, } from '../../../config'; -import { PLATFORM_ENV } from '../../../util/environment'; +import { IS_MOV_SUPPORTED, PLATFORM_ENV } from '../../../util/environment'; import { unsubscribe } from '../../../util/notifications'; import * as cacheApi from '../../../util/cacheApi'; import { updateAppBadge } from '../../../util/appBadge'; @@ -37,6 +37,7 @@ addReducer('initApi', (global: GlobalState, actions) => { userAgent: navigator.userAgent, platform: PLATFORM_ENV, sessionData: loadStoredSession(), + isMovSupported: IS_MOV_SUPPORTED, }); })(); }); diff --git a/src/util/environment.ts b/src/util/environment.ts index 45bdc9ad4..48f0c8eae 100644 --- a/src/util/environment.ts +++ b/src/util/environment.ts @@ -4,6 +4,8 @@ import { MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT, MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH, IS_TEST, + SUPPORTED_VIDEO_CONTENT_TYPES, + VIDEO_MOV_TYPE, } from '../config'; export * from './environmentWebp'; @@ -67,6 +69,14 @@ export const IS_CANVAS_FILTER_SUPPORTED = ( ); export const LAYERS_ANIMATION_NAME = IS_ANDROID ? 'slide-fade' : IS_IOS ? 'slide-layers' : 'push-slide'; +const TEST_VIDEO = document.createElement('video'); +export const IS_MOV_SUPPORTED = Boolean( + TEST_VIDEO.canPlayType(VIDEO_MOV_TYPE).replace('no', '') + || IS_IOS, // IOS reports '', but still plays .mov files +); + +if (IS_MOV_SUPPORTED) SUPPORTED_VIDEO_CONTENT_TYPES.add(VIDEO_MOV_TYPE); + export const DPR = window.devicePixelRatio || 1; export const MASK_IMAGE_DISABLED = true;