diff --git a/examples/browser/README.md b/examples/browser/README.md index 0d75e27..cb6b78d 100644 --- a/examples/browser/README.md +++ b/examples/browser/README.md @@ -8,3 +8,15 @@ $ npm start ``` Visit http://localhost:3000/examples/browser/transcode.html + + +Web Worker Examples +================== + +To run the webworker example, execute: + +``` +$ npm run start:worker +``` + +Visit http://localhost:3000/examples/browser/transcode.worker.html diff --git a/examples/browser/transcode.worker.html b/examples/browser/transcode.worker.html new file mode 100644 index 0000000..51fa522 --- /dev/null +++ b/examples/browser/transcode.worker.html @@ -0,0 +1,48 @@ + + + + + +

Upload a video to transcode to mp4 (x264) and play!

+
+ +

+ + + diff --git a/examples/browser/transcode.worker.js b/examples/browser/transcode.worker.js new file mode 100644 index 0000000..76557d9 --- /dev/null +++ b/examples/browser/transcode.worker.js @@ -0,0 +1,24 @@ +importScripts('/dist/ffmpeg.dev.js'); +const ffmpeg = self.FFmpeg.createFFmpeg({log: true}); + +onmessage = async (event) => { + try { + const {buffer, name, inType, outType} = event.data; + if (!ffmpeg.isLoaded()) { + await ffmpeg.load(); + } + + ffmpeg.FS('writeFile', `${name}.${inType}`, new Uint8Array(buffer)); + await ffmpeg.run('-i', `${name}.${inType}`, `${name}.${outType}`); + const data = ffmpeg.FS('readFile', `${name}.${outType}`); + + postMessage({buffer: data.buffer, type: "result"}, [data.buffer]); + + // delete files from memory + ffmpeg.FS('unlink', `${name}.${inType}`); + ffmpeg.FS('unlink', `${name}.${outType}`); + } catch (e) { + postMessage({type: "error", error: e}); + } +} + diff --git a/package-lock.json b/package-lock.json index 2017608..9a43460 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@ffmpeg/ffmpeg", - "version": "0.10.1", + "version": "0.10.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 365f995..09dd5ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ffmpeg/ffmpeg", - "version": "0.10.1", + "version": "0.10.2", "description": "FFmpeg WebAssembly version", "main": "src/index.js", "types": "src/index.d.ts", @@ -9,13 +9,15 @@ }, "scripts": { "start": "node scripts/server.js", + "start:worker": "node scripts/worker-server.js", "build": "rimraf dist && webpack --config scripts/webpack.config.prod.js", + "build:worker": "rimraf dist && webpack --config scripts/webpack.config.worker.prod.js", "prepublishOnly": "npm run build", "lint": "eslint src", "wait": "rimraf dist && wait-on http://localhost:3000/dist/ffmpeg.dev.js", "test": "npm-run-all -p -r start test:all", "test:all": "npm-run-all wait test:browser:ffmpeg test:node:all", - "test:node": "node --experimental-wasm-threads node_modules/.bin/_mocha --exit --bail --require ./scripts/test-helper.js", + "test:node": "node node_modules/mocha/bin/_mocha --exit --bail --require ./scripts/test-helper.js", "test:node:all": "npm run test:node -- ./tests/*.test.js", "test:browser": "mocha-headless-chrome -a allow-file-access-from-files -a incognito -a no-sandbox -a disable-setuid-sandbox -a disable-logging -t 300000", "test:browser:ffmpeg": "npm run test:browser -- -f ./tests/ffmpeg.test.html" diff --git a/scripts/server.js b/scripts/server.js index 4ccc4b5..bf135a8 100644 --- a/scripts/server.js +++ b/scripts/server.js @@ -8,7 +8,15 @@ const webpackConfig = require('./webpack.config.dev'); const compiler = webpack(webpackConfig); const app = express(); +function coi(req, res, next) { + res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + next(); +} + app.use(cors()); +app.use(coi); + app.use('/', express.static(path.resolve(__dirname, '..'))); app.use(middleware(compiler, { publicPath: '/dist', writeToDisk: true })); diff --git a/scripts/webpack.config.worker.dev.js b/scripts/webpack.config.worker.dev.js new file mode 100644 index 0000000..5683212 --- /dev/null +++ b/scripts/webpack.config.worker.dev.js @@ -0,0 +1,29 @@ +const path = require('path'); +const webpack = require('webpack'); +const common = require('./webpack.config.common'); + +const genConfig = ({ + entry, filename, library, libraryTarget, +}) => ({ + ...common, + mode: 'development', + target: 'webworker', + entry, + output: { + filename, + library, + libraryTarget, + }, + devServer: { + allowedHosts: ['localhost', '.gitpod.io'], + }, +}); + +module.exports = [ + genConfig({ + entry: path.resolve(__dirname, '..', 'src', 'index.js'), + filename: 'ffmpeg.dev.js', + library: 'FFmpeg', + libraryTarget: 'umd', + }), +]; diff --git a/scripts/webpack.config.worker.prod.js b/scripts/webpack.config.worker.prod.js new file mode 100644 index 0000000..ee51cd7 --- /dev/null +++ b/scripts/webpack.config.worker.prod.js @@ -0,0 +1,27 @@ +const path = require('path'); +const common = require('./webpack.config.common'); + +const genConfig = ({ + entry, filename, library, libraryTarget, +}) => ({ + ...common, + mode: 'production', + devtool: 'source-map', + target: 'webworker', + entry, + output: { + path: path.resolve(__dirname, '..', 'dist'), + filename, + library, + libraryTarget, + }, +}); + +module.exports = [ + genConfig({ + entry: path.resolve(__dirname, '..', 'src', 'index.js'), + filename: 'ffmpeg.min.js', + library: 'FFmpeg', + libraryTarget: 'umd', + }), +]; diff --git a/scripts/worker-server.js b/scripts/worker-server.js new file mode 100644 index 0000000..ffd9a34 --- /dev/null +++ b/scripts/worker-server.js @@ -0,0 +1,25 @@ +const webpack = require('webpack'); +const middleware = require('webpack-dev-middleware'); +const express = require('express'); +const path = require('path'); +const cors = require('cors'); +const webpackConfig = require('./webpack.config.worker.dev'); + +const compiler = webpack(webpackConfig); +const app = express(); + +function coi(req, res, next) { + res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); + res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); + next(); +} + +app.use(cors()); +app.use(coi); + +app.use('/', express.static(path.resolve(__dirname, '..'))); +app.use(middleware(compiler, { publicPath: '/dist', writeToDisk: true })); + +module.exports = app.listen(3000, () => { + console.log('Server is running on port 3000'); +}); diff --git a/src/browser/defaultOptions.js b/src/browser/defaultOptions.js index 491a8a8..cbb687c 100644 --- a/src/browser/defaultOptions.js +++ b/src/browser/defaultOptions.js @@ -1,11 +1,10 @@ -const resolveURL = require('resolve-url'); const { devDependencies } = require('../../package.json'); /* * Default options for browser environment */ -module.exports = { - corePath: typeof process !== 'undefined' && process.env.NODE_ENV === 'development' - ? resolveURL('/node_modules/@ffmpeg/core/dist/ffmpeg-core.js') - : `https://unpkg.com/@ffmpeg/core@${devDependencies['@ffmpeg/core'].substring(1)}/dist/ffmpeg-core.js`, -}; +const corePath = typeof process !== 'undefined' && process.env.NODE_ENV === 'development' + ? new URL('/node_modules/@ffmpeg/core/dist/ffmpeg-core.js', import.meta.url).href + : `https://unpkg.com/@ffmpeg/core@${devDependencies['@ffmpeg/core'].substring(1)}/dist/ffmpeg-core.js` + +export {corePath}; diff --git a/src/browser/fetchFile.js b/src/browser/fetchFile.js index 74dcb7d..ab0ea4a 100644 --- a/src/browser/fetchFile.js +++ b/src/browser/fetchFile.js @@ -1,5 +1,3 @@ -const resolveURL = require('resolve-url'); - const readFromBlobOrFile = (blob) => ( new Promise((resolve, reject) => { const fileReader = new FileReader(); @@ -13,7 +11,7 @@ const readFromBlobOrFile = (blob) => ( }) ); -module.exports = async (_data) => { +export const fetchFile = async (_data) => { let data = _data; if (typeof _data === 'undefined') { return new Uint8Array(); @@ -27,7 +25,7 @@ module.exports = async (_data) => { .map((c) => c.charCodeAt(0)); /* From remote server/URL */ } else { - const res = await fetch(resolveURL(_data)); + const res = await fetch(new URL(_data, import.meta.url).href); data = await res.arrayBuffer(); } /* From Blob or File */ diff --git a/src/browser/getCreateFFmpegCore.js b/src/browser/getCreateFFmpegCore.js index b402348..bc94d04 100644 --- a/src/browser/getCreateFFmpegCore.js +++ b/src/browser/getCreateFFmpegCore.js @@ -1,5 +1,4 @@ /* eslint-disable no-undef */ -const resolveURL = require('resolve-url'); const { log } = require('../utils/log'); const { CREATE_FFMPEG_CORE_IS_NOT_DEFINED, @@ -19,28 +18,28 @@ const toBlobURL = async (url, mimeType) => { return blobURL; }; -module.exports = async ({ corePath: _corePath, workerPath: _workerPath, wasmPath: _wasmPath }) => { - if (typeof _corePath !== 'string') { - throw Error('corePath should be a string!'); - } - const coreRemotePath = resolveURL(_corePath); - const corePath = await toBlobURL( - coreRemotePath, - 'application/javascript', - ); - const wasmPath = await toBlobURL( - _wasmPath !== undefined ? _wasmPath : coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.wasm'), - 'application/wasm', - ); - const workerPath = await toBlobURL( - _workerPath !== undefined ? _workerPath : coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.worker.js'), - 'application/javascript', - ); - if (typeof createFFmpegCore === 'undefined') { - return new Promise((resolve) => { - const script = document.createElement('script'); - const eventHandler = () => { - script.removeEventListener('load', eventHandler); +export const getCreateFFmpegCore = async ({ corePath: _corePath, workerPath: _workerPath, wasmPath: _wasmPath }) => { + // in Web Worker context + if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) { + if (typeof _corePath !== 'string') { + throw Error('corePath should be a string!'); + } + const coreRemotePath = new URL(_corePath, import.meta.url).href; + const corePath = await toBlobURL( + coreRemotePath, + 'application/javascript', + ); + const wasmPath = await toBlobURL( + _wasmPath !== undefined ? _wasmPath : coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.wasm'), + 'application/wasm', + ); + const workerPath = await toBlobURL( + _workerPath !== undefined ? _workerPath : coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.worker.js'), + 'application/javascript', + ); + if (typeof createFFmpegCore === 'undefined') { + return new Promise((resolve) => { + globalThis.importScripts(corePath); if (typeof createFFmpegCore === 'undefined') { throw Error(CREATE_FFMPEG_CORE_IS_NOT_DEFINED(coreRemotePath)); } @@ -51,18 +50,63 @@ module.exports = async ({ corePath: _corePath, workerPath: _workerPath, wasmPath wasmPath, workerPath, }); - }; - script.src = corePath; - script.type = 'text/javascript'; - script.addEventListener('load', eventHandler); - document.getElementsByTagName('head')[0].appendChild(script); + }); + } + log('info', 'ffmpeg-core.js script is loaded already'); + return Promise.resolve({ + createFFmpegCore, + corePath, + wasmPath, + workerPath, + }); + } else { + if (typeof _corePath !== 'string') { + throw Error('corePath should be a string!'); + } + const coreRemotePath = new URL(_corePath, import.meta.url).href; + const corePath = await toBlobURL( + coreRemotePath, + 'application/javascript', + ); + const wasmPath = await toBlobURL( + coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.wasm'), + 'application/wasm', + ); + const workerPath = await toBlobURL( + coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.worker.js'), + 'application/javascript', + ); + if (typeof createFFmpegCore === 'undefined') { + return new Promise((resolve) => { + const script = document.createElement('script'); + const eventHandler = () => { + script.removeEventListener('load', eventHandler); + if (typeof createFFmpegCore === 'undefined') { + throw Error(CREATE_FFMPEG_CORE_IS_NOT_DEFINED(coreRemotePath)); + } + log('info', 'ffmpeg-core.js script loaded'); + resolve({ + createFFmpegCore, + corePath, + wasmPath, + workerPath, + }); + }; + script.src = corePath; + script.type = 'text/javascript'; + script.addEventListener('load', eventHandler); + document.getElementsByTagName('head')[0].appendChild(script); + }); + } + log('info', 'ffmpeg-core.js script is loaded already'); + return Promise.resolve({ + createFFmpegCore, + corePath, + wasmPath, + workerPath, }); } - log('info', 'ffmpeg-core.js script is loaded already'); - return Promise.resolve({ - createFFmpegCore, - corePath, - wasmPath, - workerPath, - }); -}; + +} + + diff --git a/src/browser/index.js b/src/browser/index.js index e73bfaa..5faffd6 100644 --- a/src/browser/index.js +++ b/src/browser/index.js @@ -1,9 +1,5 @@ const defaultOptions = require('./defaultOptions'); -const getCreateFFmpegCore = require('./getCreateFFmpegCore'); -const fetchFile = require('./fetchFile'); +const {getCreateFFmpegCore} = require('./getCreateFFmpegCore'); +const {fetchFile} = require('./fetchFile'); -module.exports = { - defaultOptions, - getCreateFFmpegCore, - fetchFile, -}; +export {defaultOptions, getCreateFFmpegCore, fetchFile}; \ No newline at end of file diff --git a/src/createFFmpeg.js b/src/createFFmpeg.js index 3f38978..d4af6f9 100644 --- a/src/createFFmpeg.js +++ b/src/createFFmpeg.js @@ -110,7 +110,7 @@ module.exports = (_options = {}) => { * as we are using blob URL instead of original URL to avoid cross origin issues. */ locateFile: (path, prefix) => { - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' || typeof WorkerGlobalScope !== 'undefined') { if (typeof wasmPath !== 'undefined' && path.endsWith('ffmpeg-core.wasm')) { return wasmPath; diff --git a/src/index.js b/src/index.js index 8c69a5b..39e04bc 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,7 @@ require('regenerator-runtime/runtime'); const createFFmpeg = require('./createFFmpeg'); const { fetchFile } = require('./node'); -module.exports = { +export { /* * Create ffmpeg instance. * Each ffmpeg instance owns an isolated MEMFS and works