Migrate downloadWithProgress to util
This commit is contained in:
		
							parent
							
								
									36c666aecf
								
							
						
					
					
						commit
						ec183935fe
					
				@ -1,13 +1,13 @@
 | 
				
			|||||||
# syntax=docker/dockerfile-upstream:master-labs
 | 
					# syntax=docker/dockerfile-upstream:master-labs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Base emsdk image with environment variables.
 | 
					# Base emsdk image with environment variables.
 | 
				
			||||||
FROM emscripten/emsdk:3.1.18 AS emsdk-base
 | 
					FROM emscripten/emsdk:3.1.29 AS emsdk-base
 | 
				
			||||||
ARG EXTRA_CFLAGS
 | 
					ARG EXTRA_CFLAGS
 | 
				
			||||||
ARG EXTRA_LDFLAGS
 | 
					ARG EXTRA_LDFLAGS
 | 
				
			||||||
ARG FFMPEG_ST
 | 
					ARG FFMPEG_ST
 | 
				
			||||||
ARG FFMPEG_MT
 | 
					ARG FFMPEG_MT
 | 
				
			||||||
ENV INSTALL_DIR=/opt
 | 
					ENV INSTALL_DIR=/opt
 | 
				
			||||||
ENV FFMPEG_VERSION=n5.1
 | 
					ENV FFMPEG_VERSION=n5.1.2
 | 
				
			||||||
ENV CFLAGS="-I$INSTALL_DIR/include $CFLAGS $EXTRA_CFLAGS"
 | 
					ENV CFLAGS="-I$INSTALL_DIR/include $CFLAGS $EXTRA_CFLAGS"
 | 
				
			||||||
ENV CXXFLAGS="$CFLAGS"
 | 
					ENV CXXFLAGS="$CFLAGS"
 | 
				
			||||||
ENV LDFLAGS="-L$INSTALL_DIR/lib $LDFLAGS $CFLAGS $EXTRA_LDFLAGS"
 | 
					ENV LDFLAGS="-L$INSTALL_DIR/lib $LDFLAGS $CFLAGS $EXTRA_LDFLAGS"
 | 
				
			||||||
 | 
				
			|||||||
@ -1,43 +1 @@
 | 
				
			|||||||
# Introduction
 | 
					# Introduction
 | 
				
			||||||
 | 
					 | 
				
			||||||
Let's discover **Docusaurus in less than 5 minutes**.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Getting Started
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Get started by **creating a new site**.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### What you'll need
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [Node.js](https://nodejs.org/en/download/) version 16.14 or above:
 | 
					 | 
				
			||||||
  - When installing Node.js, you are recommended to check all checkboxes related to dependencies.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Generate a new site
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Generate a new Docusaurus site using the **classic template**.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The classic template will automatically be added to your project after you run the command:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
npm init docusaurus@latest my-website classic
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The command also installs all necessary dependencies you need to run Docusaurus.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Start your site
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Run the development server:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
cd my-website
 | 
					 | 
				
			||||||
npm run start
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							@ -8,11 +8,11 @@
 | 
				
			|||||||
    "pretest": "lerna run build --scope='@ffmpeg/*'",
 | 
					    "pretest": "lerna run build --scope='@ffmpeg/*'",
 | 
				
			||||||
    "test": "server-test test:browser:server http://localhost:3000 test:all",
 | 
					    "test": "server-test test:browser:server http://localhost:3000 test:all",
 | 
				
			||||||
    "test:all": "npm-run-all test:*:*:*",
 | 
					    "test:all": "npm-run-all test:*:*:*",
 | 
				
			||||||
    "test:browser": "mocha-headless-chrome -a allow-file-access-from-files -a enable-features=SharedArrayBuffer",
 | 
					    "test:browser": "mocha-headless-chrome -a enable-features=SharedArrayBuffer",
 | 
				
			||||||
    "test:browser:core:mt": "npm run test:browser -- -f tests/ffmpeg-core-mt.test.html",
 | 
					    "test:browser:core:mt": "npm run test:browser -- -f http://localhost:3000/tests/ffmpeg-core-mt.test.html",
 | 
				
			||||||
    "test:browser:core:st": "npm run test:browser -- -f tests/ffmpeg-core-st.test.html",
 | 
					    "test:browser:core:st": "npm run test:browser -- -f http://localhost:3000/tests/ffmpeg-core-st.test.html",
 | 
				
			||||||
    "test:browser:ffmpeg:mt": "npm run test:browser -- -f tests/ffmpeg-mt.test.html",
 | 
					    "test:browser:ffmpeg:mt": "npm run test:browser -- -f http://localhost:3000/tests/ffmpeg-mt.test.html",
 | 
				
			||||||
    "test:browser:ffmpeg:st": "npm run test:browser -- -f tests/ffmpeg-st.test.html",
 | 
					    "test:browser:ffmpeg:st": "npm run test:browser -- -f http://localhost:3000/tests/ffmpeg-st.test.html",
 | 
				
			||||||
    "test:browser:server": "http-server -c-1 --cors -p 3000 .",
 | 
					    "test:browser:server": "http-server -c-1 --cors -p 3000 .",
 | 
				
			||||||
    "test:node": "mocha --exit --bail -t 60000",
 | 
					    "test:node": "mocha --exit --bail -t 60000",
 | 
				
			||||||
    "test:node:core:mt": "npm run test:node -- --require tests/test-helper-mt.js tests/ffmpeg-core.test.js",
 | 
					    "test:node:core:mt": "npm run test:node -- --require tests/test-helper-mt.js tests/ffmpeg-core.test.js",
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ import { FFMessageType } from "./const";
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
  CallbackData,
 | 
					  CallbackData,
 | 
				
			||||||
  Callbacks,
 | 
					  Callbacks,
 | 
				
			||||||
  DownloadProgressEvent,
 | 
					 | 
				
			||||||
  FSNode,
 | 
					  FSNode,
 | 
				
			||||||
  FFMessageEventCallback,
 | 
					  FFMessageEventCallback,
 | 
				
			||||||
  FFMessageLoadConfig,
 | 
					  FFMessageLoadConfig,
 | 
				
			||||||
@ -18,22 +17,6 @@ import { getMessageID } from "./utils";
 | 
				
			|||||||
import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors";
 | 
					import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export declare interface FFmpeg {
 | 
					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()`.
 | 
					   * Listen to log events from `ffmpeg.exec()`.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
@ -81,7 +64,6 @@ export declare interface FFmpeg {
 | 
				
			|||||||
 * ```
 | 
					 * ```
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class FFmpeg extends EventEmitter {
 | 
					export class FFmpeg extends EventEmitter {
 | 
				
			||||||
  /** @event */ static readonly DOWNLOAD = "download" as const;
 | 
					 | 
				
			||||||
  /** @event */ static readonly LOG = "log" as const;
 | 
					  /** @event */ static readonly LOG = "log" as const;
 | 
				
			||||||
  /** @event */ static readonly PROGRESS = "progress" 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
 | 
					   * #resolves and #rejects tracks Promise resolves and rejects to
 | 
				
			||||||
   * be called when we receive message from web worker.
 | 
					   * be called when we receive message from web worker.
 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  #resolves: Callbacks = {};
 | 
					  #resolves: Callbacks = {};
 | 
				
			||||||
  #rejects: Callbacks = {};
 | 
					  #rejects: Callbacks = {};
 | 
				
			||||||
@ -123,9 +104,6 @@ export class FFmpeg extends EventEmitter {
 | 
				
			|||||||
          case FFMessageType.DELETE_DIR:
 | 
					          case FFMessageType.DELETE_DIR:
 | 
				
			||||||
            this.#resolves[id](data);
 | 
					            this.#resolves[id](data);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
          case FFMessageType.DOWNLOAD:
 | 
					 | 
				
			||||||
            this.emit(FFmpeg.DOWNLOAD, data as DownloadProgressEvent);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          case FFMessageType.LOG:
 | 
					          case FFMessageType.LOG:
 | 
				
			||||||
            this.emit(FFmpeg.LOG, data as LogEvent);
 | 
					            this.emit(FFmpeg.LOG, data as LogEvent);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,3 @@
 | 
				
			|||||||
export const HeaderContentLength = "Content-Length";
 | 
					 | 
				
			||||||
export const MIME_TYPE_JAVASCRIPT = "text/javascript";
 | 
					export const MIME_TYPE_JAVASCRIPT = "text/javascript";
 | 
				
			||||||
export const MIME_TYPE_WASM = "application/wasm";
 | 
					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_UNKNOWN_MESSAGE_TYPE = new Error("unknown message type");
 | 
				
			||||||
export const ERROR_NOT_LOADED = new Error(
 | 
					export const ERROR_NOT_LOADED = new Error(
 | 
				
			||||||
  "ffmpeg is not loaded, call `await ffmpeg.load()` first"
 | 
					  "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()");
 | 
					export const ERROR_TERMINATED = new Error("called FFmpeg.terminate()");
 | 
				
			||||||
 | 
				
			|||||||
@ -112,14 +112,6 @@ export interface FFMessageEvent extends MessageEvent {
 | 
				
			|||||||
  data: FFMessage;
 | 
					  data: FFMessage;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DownloadProgressEvent {
 | 
					 | 
				
			||||||
  url: string | URL;
 | 
					 | 
				
			||||||
  total: number;
 | 
					 | 
				
			||||||
  received: number;
 | 
					 | 
				
			||||||
  delta: number;
 | 
					 | 
				
			||||||
  done: boolean;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface LogEvent {
 | 
					export interface LogEvent {
 | 
				
			||||||
  type: string;
 | 
					  type: string;
 | 
				
			||||||
  message: string;
 | 
					  message: string;
 | 
				
			||||||
@ -144,7 +136,6 @@ export type CallbackData =
 | 
				
			|||||||
  | FileData
 | 
					  | FileData
 | 
				
			||||||
  | ExitCode
 | 
					  | ExitCode
 | 
				
			||||||
  | ErrorMessage
 | 
					  | ErrorMessage
 | 
				
			||||||
  | DownloadProgressEvent
 | 
					 | 
				
			||||||
  | LogEvent
 | 
					  | LogEvent
 | 
				
			||||||
  | Progress
 | 
					  | Progress
 | 
				
			||||||
  | IsFirst
 | 
					  | IsFirst
 | 
				
			||||||
@ -164,5 +155,3 @@ export interface FFMessageEventCallback {
 | 
				
			|||||||
    data: CallbackData;
 | 
					    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.
 | 
					 * Generate an unique message ID.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@ -12,79 +5,3 @@ export const getMessageID = (() => {
 | 
				
			|||||||
  let messageID = 0;
 | 
					  let messageID = 0;
 | 
				
			||||||
  return () => messageID++;
 | 
					  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,
 | 
					  FSNode,
 | 
				
			||||||
  FileData,
 | 
					  FileData,
 | 
				
			||||||
} from "./types";
 | 
					} from "./types";
 | 
				
			||||||
import { toBlobURL } from "./utils";
 | 
					import { CORE_URL, FFMessageType } from "./const";
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CORE_URL,
 | 
					 | 
				
			||||||
  FFMessageType,
 | 
					 | 
				
			||||||
  MIME_TYPE_JAVASCRIPT,
 | 
					 | 
				
			||||||
  MIME_TYPE_WASM,
 | 
					 | 
				
			||||||
} from "./const";
 | 
					 | 
				
			||||||
import { ERROR_UNKNOWN_MESSAGE_TYPE, ERROR_NOT_LOADED } from "./errors";
 | 
					import { ERROR_UNKNOWN_MESSAGE_TYPE, ERROR_NOT_LOADED } from "./errors";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
@ -42,30 +36,14 @@ const load = async ({
 | 
				
			|||||||
  coreURL: _coreURL = CORE_URL,
 | 
					  coreURL: _coreURL = CORE_URL,
 | 
				
			||||||
  wasmURL: _wasmURL,
 | 
					  wasmURL: _wasmURL,
 | 
				
			||||||
  workerURL: _workerURL,
 | 
					  workerURL: _workerURL,
 | 
				
			||||||
  blob = true,
 | 
					 | 
				
			||||||
  thread = false,
 | 
					 | 
				
			||||||
}: FFMessageLoadConfig): Promise<IsFirst> => {
 | 
					}: FFMessageLoadConfig): Promise<IsFirst> => {
 | 
				
			||||||
  const first = !ffmpeg;
 | 
					  const first = !ffmpeg;
 | 
				
			||||||
  let coreURL = _coreURL;
 | 
					  const coreURL = _coreURL;
 | 
				
			||||||
  let wasmURL = _wasmURL ? _wasmURL : _coreURL.replace(/.js$/g, ".wasm");
 | 
					  const wasmURL = _wasmURL ? _wasmURL : _coreURL.replace(/.js$/g, ".wasm");
 | 
				
			||||||
  let workerURL = _workerURL
 | 
					  const workerURL = _workerURL
 | 
				
			||||||
    ? _workerURL
 | 
					    ? _workerURL
 | 
				
			||||||
    : _coreURL.replace(/.js$/g, ".worker.js");
 | 
					    : _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);
 | 
					  importScripts(coreURL);
 | 
				
			||||||
  ffmpeg = await (self as WorkerGlobalScope).createFFmpegCore({
 | 
					  ffmpeg = await (self as WorkerGlobalScope).createFFmpegCore({
 | 
				
			||||||
    // Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.
 | 
					    // Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								packages/util/src/const.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/util/src/const.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export const HeaderContentLength = "Content-Length";
 | 
				
			||||||
							
								
								
									
										6
									
								
								packages/util/src/errors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/util/src/errors.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					export const ERROR_RESPONSE_BODY_READER = new Error(
 | 
				
			||||||
 | 
					  "failed to get response body reader"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					export const ERROR_INCOMPLETED_DOWNLOAD = new Error(
 | 
				
			||||||
 | 
					  "failed to complete download"
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
@ -1,3 +1,10 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ERROR_RESPONSE_BODY_READER,
 | 
				
			||||||
 | 
					  ERROR_INCOMPLETED_DOWNLOAD,
 | 
				
			||||||
 | 
					} from "./errors";
 | 
				
			||||||
 | 
					import { HeaderContentLength } from "./const";
 | 
				
			||||||
 | 
					import { ProgressCallback } from "./types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const readFromBlobOrFile = (blob: Blob | File): Promise<Uint8Array> =>
 | 
					export const readFromBlobOrFile = (blob: Blob | File): Promise<Uint8Array> =>
 | 
				
			||||||
  new Promise((resolve, reject) => {
 | 
					  new Promise((resolve, reject) => {
 | 
				
			||||||
    const fileReader = new FileReader();
 | 
					    const fileReader = new FileReader();
 | 
				
			||||||
@ -105,3 +112,79 @@ export const toBlobURL = async (
 | 
				
			|||||||
  const blob = new Blob([buf], { type: mimeType });
 | 
					  const blob = new Blob([buf], { type: mimeType });
 | 
				
			||||||
  return URL.createObjectURL(blob);
 | 
					  return URL.createObjectURL(blob);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 toBlobURLWithProgress = 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,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								packages/util/src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/util/src/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					export interface DownloadProgressEvent {
 | 
				
			||||||
 | 
					  url: string | URL;
 | 
				
			||||||
 | 
					  total: number;
 | 
				
			||||||
 | 
					  received: number;
 | 
				
			||||||
 | 
					  delta: number;
 | 
				
			||||||
 | 
					  done: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ProgressCallback = (event: DownloadProgressEvent) => void;
 | 
				
			||||||
@ -23,33 +23,6 @@ describe(genName("FFmpeg.load()"), function () {
 | 
				
			|||||||
  //   await ffmpeg.load();
 | 
					  //   await ffmpeg.load();
 | 
				
			||||||
  //   expect(ffmpeg).to.be.ok;
 | 
					  //   expect(ffmpeg).to.be.ok;
 | 
				
			||||||
  // });
 | 
					  // });
 | 
				
			||||||
 | 
					 | 
				
			||||||
  it("should work when blob is false", async function () {
 | 
					 | 
				
			||||||
    // FIXME: failed to load ffmpeg-core.worker.js when blob is false.
 | 
					 | 
				
			||||||
    if (FFMPEG_TYPE === "mt") this.skip();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const ffmpeg = new FFmpeg();
 | 
					 | 
				
			||||||
    await ffmpeg.load({
 | 
					 | 
				
			||||||
      coreURL: CORE_URL,
 | 
					 | 
				
			||||||
      blob: false,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    expect(ffmpeg).to.be.ok;
 | 
					 | 
				
			||||||
    ffmpeg.terminate();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it("should receive download progress events", async () => {
 | 
					 | 
				
			||||||
    const ffmpeg = new FFmpeg();
 | 
					 | 
				
			||||||
    let done = false;
 | 
					 | 
				
			||||||
    ffmpeg.on(FFmpeg.DOWNLOAD, ({ done: _done }) => {
 | 
					 | 
				
			||||||
      done = _done;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    await ffmpeg.load({
 | 
					 | 
				
			||||||
      coreURL: CORE_URL,
 | 
					 | 
				
			||||||
      thread: FFMPEG_TYPE === "mt",
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    expect(done).to.be.true;
 | 
					 | 
				
			||||||
    ffmpeg.terminate();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe(
 | 
					describe(
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user