Composer: Various improvements for attachments (audio, video, photo) (#1443)

This commit is contained in:
Alexander Zinchuk 2021-09-17 17:17:25 +03:00
parent d121b82de7
commit fdda362cb3
6 changed files with 29 additions and 51 deletions

View File

@ -515,6 +515,7 @@ async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment,
duration, duration,
w: width, w: width,
h: height, h: height,
supportsStreaming: true,
})); }));
} }
} }

View File

@ -156,12 +156,11 @@ const Audio: FC<OwnProps> = ({
transitionClassNames: spinnerClassNames, transitionClassNames: spinnerClassNames,
} = useShowTransition(isTransferring); } = useShowTransition(isTransferring);
const handleButtonClick = useCallback(() => { const shouldRenderCross = shouldRenderSpinner && (isLoadingForPlaying || isUploading);
if (isUploading && !isPlaying) {
if (onCancelUpload) {
onCancelUpload();
}
const handleButtonClick = useCallback(() => {
if (isUploading) {
onCancelUpload?.();
return; return;
} }
@ -269,7 +268,7 @@ const Audio: FC<OwnProps> = ({
); );
const buttonClassNames = ['toggle-play']; const buttonClassNames = ['toggle-play'];
if (isLoadingForPlaying) { if (shouldRenderCross) {
buttonClassNames.push('loading'); buttonClassNames.push('loading');
} else if (isPlaying) { } else if (isPlaying) {
buttonClassNames.push('pause'); buttonClassNames.push('pause');
@ -334,13 +333,13 @@ const Audio: FC<OwnProps> = ({
<i className="icon-pause" /> <i className="icon-pause" />
</Button> </Button>
{shouldRenderSpinner && ( {shouldRenderSpinner && (
<div className={buildClassName('media-loading', spinnerClassNames, isLoadingForPlaying && 'interactive')}> <div className={buildClassName('media-loading', spinnerClassNames, shouldRenderCross && 'interactive')}>
<ProgressSpinner <ProgressSpinner
progress={transferProgress} progress={transferProgress}
transparent transparent
size="m" size="m"
onClick={isLoadingForPlaying ? handleButtonClick : undefined} onClick={shouldRenderCross ? handleButtonClick : undefined}
noCross={!isLoadingForPlaying} noCross={!shouldRenderCross}
/> />
</div> </div>
)} )}

View File

@ -151,12 +151,15 @@ const AttachmentModal: FC<OwnProps> = ({
const areAllPhotos = renderingAttachments.every((a) => a.mimeType.startsWith('image/')); const areAllPhotos = renderingAttachments.every((a) => a.mimeType.startsWith('image/'));
const areAllVideos = renderingAttachments.every((a) => a.mimeType.startsWith('video/')); const areAllVideos = renderingAttachments.every((a) => a.mimeType.startsWith('video/'));
const areAllAudios = renderingAttachments.every((a) => a.mimeType.startsWith('audio/'));
let title = ''; let title = '';
if (areAllPhotos) { if (areAllPhotos) {
title = lang('PreviewSender.SendPhoto', renderingAttachments.length, 'i'); title = lang('PreviewSender.SendPhoto', renderingAttachments.length, 'i');
} else if (areAllVideos) { } else if (areAllVideos) {
title = lang('PreviewSender.SendVideo', renderingAttachments.length, 'i'); title = lang('PreviewSender.SendVideo', renderingAttachments.length, 'i');
} else if (areAllAudios) {
title = lang('PreviewSender.SendAudio', renderingAttachments.length, 'i');
} else { } else {
title = lang('PreviewSender.SendFile', renderingAttachments.length, 'i'); title = lang('PreviewSender.SendFile', renderingAttachments.length, 'i');
} }

View File

@ -1,7 +1,12 @@
import { ApiAttachment } from '../../../../api/types'; import { ApiAttachment } from '../../../../api/types';
import { preloadImage, preloadVideo, createPosterForVideo } from '../../../../util/files'; import {
preloadImage,
preloadVideo,
createPosterForVideo,
fetchBlob,
} from '../../../../util/files';
import { scaleImage } from '../../../../util/imageResize';
const MAX_QUICK_VIDEO_SIZE = 10 * 1024 ** 2; // 10 MB
const MAX_QUICK_IMG_SIZE = 1280; // px const MAX_QUICK_IMG_SIZE = 1280; // px
export default async function buildAttachment( export default async function buildAttachment(
@ -18,13 +23,10 @@ export default async function buildAttachment(
const { width, height } = img; const { width, height } = img;
if (width > MAX_QUICK_IMG_SIZE || height > MAX_QUICK_IMG_SIZE || mimeType !== 'image/jpeg') { if (width > MAX_QUICK_IMG_SIZE || height > MAX_QUICK_IMG_SIZE || mimeType !== 'image/jpeg') {
const newBlob = await squeezeImage(img); const resizedUrl = await scaleImage(blobUrl, MAX_QUICK_IMG_SIZE / Math.max(width, height), 'image/jpeg');
if (newBlob) { URL.revokeObjectURL(blobUrl);
URL.revokeObjectURL(blobUrl); const newBlob = await fetchBlob(resizedUrl);
return buildAttachment(filename, newBlob, true, options); return buildAttachment(filename, newBlob, true, options);
} else {
return buildAttachment(filename, blob, false, options);
}
} }
quick = { width, height }; quick = { width, height };
@ -32,12 +34,8 @@ export default async function buildAttachment(
previewBlobUrl = blobUrl; previewBlobUrl = blobUrl;
} }
} else if (mimeType.startsWith('video/')) { } else if (mimeType.startsWith('video/')) {
// Videos < 10 MB are always sent in quick mode (in other clients). const { videoWidth: width, videoHeight: height, duration } = await preloadVideo(blobUrl);
// Quick mode for videos > 10 MB is not supported until client-side video squeezing is implemented. quick = { width, height, duration };
if (size < MAX_QUICK_VIDEO_SIZE) {
const { videoWidth: width, videoHeight: height, duration } = await preloadVideo(blobUrl);
quick = { width, height, duration };
}
previewBlobUrl = await createPosterForVideo(blobUrl); previewBlobUrl = await createPosterForVideo(blobUrl);
} }
@ -52,28 +50,3 @@ export default async function buildAttachment(
...options, ...options,
}; };
} }
function squeezeImage(img: HTMLImageElement): Promise<Blob | null> {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
let { width, height } = img;
if (width > MAX_QUICK_IMG_SIZE || height > MAX_QUICK_IMG_SIZE) {
if (width >= height) {
height *= MAX_QUICK_IMG_SIZE / width;
width = MAX_QUICK_IMG_SIZE;
} else {
width *= MAX_QUICK_IMG_SIZE / height;
height = MAX_QUICK_IMG_SIZE;
}
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, width, height);
canvas.toBlob(resolve, 'image/jpeg', 100);
});
}

View File

@ -194,7 +194,9 @@ const Video: FC<OwnProps> = ({
<i className="icon-download" /> <i className="icon-download" />
)} )}
{isTransferring ? ( {isTransferring ? (
<span className="message-upload-progress">...</span> <span className="message-upload-progress">
{isUploading ? `${Math.round(transferProgress * 100)}%` : '...'}
</span>
) : ( ) : (
<div className="message-media-duration"> <div className="message-media-duration">
{video.isGif ? 'GIF' : formatMediaDuration(Math.max(duration - playProgress, 0))} {video.isGif ? 'GIF' : formatMediaDuration(Math.max(duration - playProgress, 0))}

View File

@ -122,7 +122,7 @@ export const MENU_TRANSITION_DURATION = 200;
export const SLIDE_TRANSITION_DURATION = 450; export const SLIDE_TRANSITION_DURATION = 450;
export const CONTENT_TYPES_FOR_QUICK_UPLOAD = new Set([ export const CONTENT_TYPES_FOR_QUICK_UPLOAD = new Set([
'image/png', 'image/gif', 'image/jpeg', 'video/mp4', 'video/avi', 'video/quicktime', 'image/png', 'image/gif', 'image/jpeg',
]); ]);
// eslint-disable-next-line max-len // eslint-disable-next-line max-len