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,
w: width,
h: height,
supportsStreaming: true,
}));
}
}

View File

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

View File

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

View File

@ -1,7 +1,12 @@
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
export default async function buildAttachment(
@ -18,13 +23,10 @@ export default async function buildAttachment(
const { width, height } = img;
if (width > MAX_QUICK_IMG_SIZE || height > MAX_QUICK_IMG_SIZE || mimeType !== 'image/jpeg') {
const newBlob = await squeezeImage(img);
if (newBlob) {
URL.revokeObjectURL(blobUrl);
return buildAttachment(filename, newBlob, true, options);
} else {
return buildAttachment(filename, blob, false, options);
}
const resizedUrl = await scaleImage(blobUrl, MAX_QUICK_IMG_SIZE / Math.max(width, height), 'image/jpeg');
URL.revokeObjectURL(blobUrl);
const newBlob = await fetchBlob(resizedUrl);
return buildAttachment(filename, newBlob, true, options);
}
quick = { width, height };
@ -32,12 +34,8 @@ export default async function buildAttachment(
previewBlobUrl = blobUrl;
}
} else if (mimeType.startsWith('video/')) {
// Videos < 10 MB are always sent in quick mode (in other clients).
// Quick mode for videos > 10 MB is not supported until client-side video squeezing is implemented.
if (size < MAX_QUICK_VIDEO_SIZE) {
const { videoWidth: width, videoHeight: height, duration } = await preloadVideo(blobUrl);
quick = { width, height, duration };
}
const { videoWidth: width, videoHeight: height, duration } = await preloadVideo(blobUrl);
quick = { width, height, duration };
previewBlobUrl = await createPosterForVideo(blobUrl);
}
@ -52,28 +50,3 @@ export default async function buildAttachment(
...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" />
)}
{isTransferring ? (
<span className="message-upload-progress">...</span>
<span className="message-upload-progress">
{isUploading ? `${Math.round(transferProgress * 100)}%` : '...'}
</span>
) : (
<div className="message-media-duration">
{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 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