Migrate downloadWithProgress to util

This commit is contained in:
Jerome Wu
2023-01-09 22:47:39 +08:00
parent 36c666aecf
commit ec183935fe
14 changed files with 110 additions and 225 deletions

View File

@@ -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;

View File

@@ -1,4 +1,3 @@
export const HeaderContentLength = "Content-Length";
export const MIME_TYPE_JAVASCRIPT = "text/javascript";
export const MIME_TYPE_WASM = "application/wasm";

View File

@@ -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()");

View File

@@ -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;

View File

@@ -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,
})
);

View File

@@ -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.