TelegramPWA/src/util/files.ts
2024-09-06 15:42:57 +02:00

139 lines
3.8 KiB
TypeScript

import { CONTENT_TYPES_WITH_PREVIEW } from '../config';
import { pause } from './schedulers';
// Polyfill for Safari: `File` is not available in web worker
if (typeof File === 'undefined') {
// eslint-disable-next-line no-global-assign, no-restricted-globals, func-names
self.File = class extends Blob {
name: string;
constructor(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag) {
if (options) {
const { type, ...rest } = options;
super(fileBits, { type });
Object.assign(this, rest);
} else {
super(fileBits);
}
this.name = fileName;
}
} as typeof File;
}
export function blobToDataUri(blob: Blob): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
const { result } = e.target || {};
if (typeof result === 'string') {
resolve(result);
}
reject(new Error('Failed to read blob'));
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
export function blobToFile(blob: Blob, fileName: string) {
return new File([blob], fileName, {
lastModified: Date.now(),
type: blob.type,
});
}
export function preloadImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
export function preloadVideo(url: string): Promise<HTMLVideoElement> {
return new Promise((resolve, reject) => {
const video = document.createElement('video');
video.volume = 0;
video.onloadedmetadata = () => resolve(video);
video.onerror = reject;
video.src = url;
});
}
export async function createPosterForVideo(url: string): Promise<string | undefined> {
try {
const video = await preloadVideo(url);
return await Promise.race([
pause(2000) as Promise<undefined>,
new Promise<string | undefined>((resolve, reject) => {
video.onseeked = () => {
if (!video.videoWidth || !video.videoHeight) {
resolve(undefined);
}
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d')!;
ctx.drawImage(video, 0, 0);
canvas.toBlob((blob) => {
resolve(blob ? URL.createObjectURL(blob) : undefined);
});
};
video.onerror = reject;
video.currentTime = Math.min(video.duration, 1);
}),
]);
} catch (e) {
return undefined;
}
}
export async function fetchBlob(blobUrl: string) {
const response = await fetch(blobUrl);
return response.blob();
}
export async function fetchFile(blobUrl: string, fileName: string) {
const blob = await fetchBlob(blobUrl);
return blobToFile(blob, fileName);
}
export function imgToCanvas(img: HTMLImageElement) {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d')!;
ctx.drawImage(img, 0, 0);
return canvas;
}
export function hasPreview(file: File) {
return CONTENT_TYPES_WITH_PREVIEW.has(file.type);
}
export function validateFiles(files: File[] | FileList | null): File[] | undefined {
if (!files?.length) {
return undefined;
}
return Array.from(files).map(fixMovMime).filter((file) => file.size);
}
// .mov MIME type not reported sometimes https://developer.mozilla.org/en-US/docs/Web/API/File/type#sect1
function fixMovMime(file: File) {
const ext = file.name.split('.').pop()!;
if (!file.type && ext.toLowerCase() === 'mov') {
return new File([file], file.name, { type: 'video/quicktime' });
}
return file;
}