154 lines
4.7 KiB
JavaScript
154 lines
4.7 KiB
JavaScript
|
import { ERROR_RESPONSE_BODY_READER, ERROR_INCOMPLETED_DOWNLOAD, } from "./errors.js";
|
||
|
import { HeaderContentLength } from "./const.js";
|
||
|
const readFromBlobOrFile = (blob) => new Promise((resolve, reject) => {
|
||
|
const fileReader = new FileReader();
|
||
|
fileReader.onload = () => {
|
||
|
const { result } = fileReader;
|
||
|
if (result instanceof ArrayBuffer) {
|
||
|
resolve(new Uint8Array(result));
|
||
|
}
|
||
|
else {
|
||
|
resolve(new Uint8Array());
|
||
|
}
|
||
|
};
|
||
|
fileReader.onerror = (event) => {
|
||
|
reject(Error(`File could not be read! Code=${event?.target?.error?.code || -1}`));
|
||
|
};
|
||
|
fileReader.readAsArrayBuffer(blob);
|
||
|
});
|
||
|
/**
|
||
|
* An util function to fetch data from url string, base64, URL, File or Blob format.
|
||
|
*
|
||
|
* Examples:
|
||
|
* ```ts
|
||
|
* // URL
|
||
|
* await fetchFile("http://localhost:3000/video.mp4");
|
||
|
* // base64
|
||
|
* await fetchFile("data:<type>;base64,wL2dvYWwgbW9yZ...");
|
||
|
* // URL
|
||
|
* await fetchFile(new URL("video.mp4", import.meta.url));
|
||
|
* // File
|
||
|
* fileInput.addEventListener('change', (e) => {
|
||
|
* await fetchFile(e.target.files[0]);
|
||
|
* });
|
||
|
* // Blob
|
||
|
* const blob = new Blob(...);
|
||
|
* await fetchFile(blob);
|
||
|
* ```
|
||
|
*/
|
||
|
export const fetchFile = async (file) => {
|
||
|
let data;
|
||
|
if (typeof file === "string") {
|
||
|
/* From base64 format */
|
||
|
if (/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(file)) {
|
||
|
data = atob(file.split(",")[1])
|
||
|
.split("")
|
||
|
.map((c) => c.charCodeAt(0));
|
||
|
/* From remote server/URL */
|
||
|
}
|
||
|
else {
|
||
|
data = await (await fetch(file)).arrayBuffer();
|
||
|
}
|
||
|
}
|
||
|
else if (file instanceof URL) {
|
||
|
data = await (await fetch(file)).arrayBuffer();
|
||
|
}
|
||
|
else if (file instanceof File || file instanceof Blob) {
|
||
|
data = await readFromBlobOrFile(file);
|
||
|
}
|
||
|
else {
|
||
|
return new Uint8Array();
|
||
|
}
|
||
|
return new Uint8Array(data);
|
||
|
};
|
||
|
/**
|
||
|
* importScript dynamically import a script, useful when you
|
||
|
* want to use different versions of ffmpeg.wasm based on environment.
|
||
|
*
|
||
|
* Example:
|
||
|
*
|
||
|
* ```ts
|
||
|
* await importScript("http://localhost:3000/ffmpeg.js");
|
||
|
* ```
|
||
|
*/
|
||
|
export const importScript = async (url) => new Promise((resolve) => {
|
||
|
const script = document.createElement("script");
|
||
|
const eventHandler = () => {
|
||
|
script.removeEventListener("load", eventHandler);
|
||
|
resolve();
|
||
|
};
|
||
|
script.src = url;
|
||
|
script.type = "text/javascript";
|
||
|
script.addEventListener("load", eventHandler);
|
||
|
document.getElementsByTagName("head")[0].appendChild(script);
|
||
|
});
|
||
|
/**
|
||
|
* Download content of a URL with progress.
|
||
|
*
|
||
|
* Progress only works when Content-Length is provided by the server.
|
||
|
*
|
||
|
*/
|
||
|
export const downloadWithProgress = async (url, cb) => {
|
||
|
const resp = await fetch(url);
|
||
|
let buf;
|
||
|
try {
|
||
|
// Set total to -1 to indicate that there is not Content-Type Header.
|
||
|
const total = parseInt(resp.headers.get(HeaderContentLength) || "-1");
|
||
|
const reader = resp.body?.getReader();
|
||
|
if (!reader)
|
||
|
throw ERROR_RESPONSE_BODY_READER;
|
||
|
const chunks = [];
|
||
|
let received = 0;
|
||
|
for (;;) {
|
||
|
const { done, value } = await reader.read();
|
||
|
const delta = value ? value.length : 0;
|
||
|
if (done) {
|
||
|
if (total != -1 && total !== received)
|
||
|
throw ERROR_INCOMPLETED_DOWNLOAD;
|
||
|
cb && cb({ url, total, received, delta, done });
|
||
|
break;
|
||
|
}
|
||
|
chunks.push(value);
|
||
|
received += delta;
|
||
|
cb && cb({ url, total, received, delta, done });
|
||
|
}
|
||
|
const data = new Uint8Array(received);
|
||
|
let position = 0;
|
||
|
for (const chunk of chunks) {
|
||
|
data.set(chunk, position);
|
||
|
position += chunk.length;
|
||
|
}
|
||
|
buf = data.buffer;
|
||
|
}
|
||
|
catch (e) {
|
||
|
console.log(`failed to send download progress event: `, e);
|
||
|
// Fetch arrayBuffer directly when it is not possible to get progress.
|
||
|
buf = await resp.arrayBuffer();
|
||
|
cb &&
|
||
|
cb({
|
||
|
url,
|
||
|
total: buf.byteLength,
|
||
|
received: buf.byteLength,
|
||
|
delta: 0,
|
||
|
done: true,
|
||
|
});
|
||
|
}
|
||
|
return buf;
|
||
|
};
|
||
|
/**
|
||
|
* toBlobURL fetches data from an URL and return a blob URL.
|
||
|
*
|
||
|
* Example:
|
||
|
*
|
||
|
* ```ts
|
||
|
* await toBlobURL("http://localhost:3000/ffmpeg.js", "text/javascript");
|
||
|
* ```
|
||
|
*/
|
||
|
export const toBlobURL = async (url, mimeType, progress = false, cb) => {
|
||
|
const buf = progress
|
||
|
? await downloadWithProgress(url, cb)
|
||
|
: await (await fetch(url)).arrayBuffer();
|
||
|
const blob = new Blob([buf], { type: mimeType });
|
||
|
return URL.createObjectURL(blob);
|
||
|
};
|