Migrate downloadWithProgress to util
This commit is contained in:
@@ -3,7 +3,6 @@ import { FFMessageType } from "./const";
|
||||
import {
|
||||
CallbackData,
|
||||
Callbacks,
|
||||
DownloadProgressEvent,
|
||||
FSNode,
|
||||
FFMessageEventCallback,
|
||||
FFMessageLoadConfig,
|
||||
@@ -18,22 +17,6 @@ import { getMessageID } from "./utils";
|
||||
import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors";
|
||||
|
||||
export declare interface FFmpeg {
|
||||
/**
|
||||
* Listen to download progress events from `ffmpeg.load()`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* ffmpeg.on(FFmpeg.DOWNLOAD, ({ url, total, received, delta, done }) => {
|
||||
* // ...
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @category Event
|
||||
*/
|
||||
on(
|
||||
event: typeof FFmpeg.DOWNLOAD,
|
||||
listener: (data: DownloadProgressEvent) => void
|
||||
): this;
|
||||
/**
|
||||
* Listen to log events from `ffmpeg.exec()`.
|
||||
*
|
||||
@@ -81,7 +64,6 @@ export declare interface FFmpeg {
|
||||
* ```
|
||||
*/
|
||||
export class FFmpeg extends EventEmitter {
|
||||
/** @event */ static readonly DOWNLOAD = "download" as const;
|
||||
/** @event */ static readonly LOG = "log" as const;
|
||||
/** @event */ static readonly PROGRESS = "progress" as const;
|
||||
|
||||
@@ -89,7 +71,6 @@ export class FFmpeg extends EventEmitter {
|
||||
/**
|
||||
* #resolves and #rejects tracks Promise resolves and rejects to
|
||||
* be called when we receive message from web worker.
|
||||
*
|
||||
*/
|
||||
#resolves: Callbacks = {};
|
||||
#rejects: Callbacks = {};
|
||||
@@ -123,9 +104,6 @@ export class FFmpeg extends EventEmitter {
|
||||
case FFMessageType.DELETE_DIR:
|
||||
this.#resolves[id](data);
|
||||
break;
|
||||
case FFMessageType.DOWNLOAD:
|
||||
this.emit(FFmpeg.DOWNLOAD, data as DownloadProgressEvent);
|
||||
break;
|
||||
case FFMessageType.LOG:
|
||||
this.emit(FFmpeg.LOG, data as LogEvent);
|
||||
break;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export const HeaderContentLength = "Content-Length";
|
||||
export const MIME_TYPE_JAVASCRIPT = "text/javascript";
|
||||
export const MIME_TYPE_WASM = "application/wasm";
|
||||
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
export const ERROR_RESPONSE_BODY_READER = new Error(
|
||||
"failed to get response body reader"
|
||||
);
|
||||
export const ERROR_UNKNOWN_MESSAGE_TYPE = new Error("unknown message type");
|
||||
export const ERROR_NOT_LOADED = new Error(
|
||||
"ffmpeg is not loaded, call `await ffmpeg.load()` first"
|
||||
);
|
||||
export const ERROR_INCOMPLETED_DOWNLOAD = new Error(
|
||||
"failed to complete download"
|
||||
);
|
||||
export const ERROR_TERMINATED = new Error("called FFmpeg.terminate()");
|
||||
|
||||
@@ -112,14 +112,6 @@ export interface FFMessageEvent extends MessageEvent {
|
||||
data: FFMessage;
|
||||
}
|
||||
|
||||
export interface DownloadProgressEvent {
|
||||
url: string | URL;
|
||||
total: number;
|
||||
received: number;
|
||||
delta: number;
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
export interface LogEvent {
|
||||
type: string;
|
||||
message: string;
|
||||
@@ -144,7 +136,6 @@ export type CallbackData =
|
||||
| FileData
|
||||
| ExitCode
|
||||
| ErrorMessage
|
||||
| DownloadProgressEvent
|
||||
| LogEvent
|
||||
| Progress
|
||||
| IsFirst
|
||||
@@ -164,5 +155,3 @@ export interface FFMessageEventCallback {
|
||||
data: CallbackData;
|
||||
};
|
||||
}
|
||||
|
||||
export type ProgressCallback = (event: DownloadProgressEvent) => void;
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import {
|
||||
ERROR_RESPONSE_BODY_READER,
|
||||
ERROR_INCOMPLETED_DOWNLOAD,
|
||||
} from "./errors";
|
||||
import { HeaderContentLength } from "./const";
|
||||
import { ProgressCallback } from "./types";
|
||||
|
||||
/**
|
||||
* Generate an unique message ID.
|
||||
*/
|
||||
@@ -12,79 +5,3 @@ export const getMessageID = (() => {
|
||||
let messageID = 0;
|
||||
return () => messageID++;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Download content of a URL with progress.
|
||||
*
|
||||
* Progress only works when Content-Length is provided by the server.
|
||||
*
|
||||
*/
|
||||
export const downloadWithProgress = async (
|
||||
url: string | URL,
|
||||
cb: ProgressCallback
|
||||
): Promise<ArrayBuffer> => {
|
||||
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({ url, total, received, delta, done });
|
||||
break;
|
||||
}
|
||||
|
||||
chunks.push(value);
|
||||
received += delta;
|
||||
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({
|
||||
url,
|
||||
total: buf.byteLength,
|
||||
received: buf.byteLength,
|
||||
delta: 0,
|
||||
done: true,
|
||||
});
|
||||
}
|
||||
|
||||
return buf;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert an URL to an Blob URL to avoid issues like CORS.
|
||||
*/
|
||||
export const toBlobURL = async (
|
||||
url: string,
|
||||
/** mime type like `text/javascript` and `application/wasm` */
|
||||
mimeType: string,
|
||||
cb: ProgressCallback
|
||||
): Promise<string> =>
|
||||
URL.createObjectURL(
|
||||
new Blob([await downloadWithProgress(url, cb)], {
|
||||
type: mimeType,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -21,13 +21,7 @@ import type {
|
||||
FSNode,
|
||||
FileData,
|
||||
} from "./types";
|
||||
import { toBlobURL } from "./utils";
|
||||
import {
|
||||
CORE_URL,
|
||||
FFMessageType,
|
||||
MIME_TYPE_JAVASCRIPT,
|
||||
MIME_TYPE_WASM,
|
||||
} from "./const";
|
||||
import { CORE_URL, FFMessageType } from "./const";
|
||||
import { ERROR_UNKNOWN_MESSAGE_TYPE, ERROR_NOT_LOADED } from "./errors";
|
||||
|
||||
declare global {
|
||||
@@ -42,30 +36,14 @@ const load = async ({
|
||||
coreURL: _coreURL = CORE_URL,
|
||||
wasmURL: _wasmURL,
|
||||
workerURL: _workerURL,
|
||||
blob = true,
|
||||
thread = false,
|
||||
}: FFMessageLoadConfig): Promise<IsFirst> => {
|
||||
const first = !ffmpeg;
|
||||
let coreURL = _coreURL;
|
||||
let wasmURL = _wasmURL ? _wasmURL : _coreURL.replace(/.js$/g, ".wasm");
|
||||
let workerURL = _workerURL
|
||||
const coreURL = _coreURL;
|
||||
const wasmURL = _wasmURL ? _wasmURL : _coreURL.replace(/.js$/g, ".wasm");
|
||||
const workerURL = _workerURL
|
||||
? _workerURL
|
||||
: _coreURL.replace(/.js$/g, ".worker.js");
|
||||
|
||||
if (blob) {
|
||||
coreURL = await toBlobURL(coreURL, MIME_TYPE_JAVASCRIPT, (data) =>
|
||||
self.postMessage({ type: FFMessageType.DOWNLOAD, data })
|
||||
);
|
||||
wasmURL = await toBlobURL(wasmURL, MIME_TYPE_WASM, (data) =>
|
||||
self.postMessage({ type: FFMessageType.DOWNLOAD, data })
|
||||
);
|
||||
if (thread) {
|
||||
workerURL = await toBlobURL(workerURL, MIME_TYPE_JAVASCRIPT, (data) =>
|
||||
self.postMessage({ type: FFMessageType.DOWNLOAD, data })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
importScripts(coreURL);
|
||||
ffmpeg = await (self as WorkerGlobalScope).createFFmpegCore({
|
||||
// Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.
|
||||
|
||||
Reference in New Issue
Block a user