diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d5153b6e..8994257d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -36,6 +36,8 @@ jobs:
${{ runner.OS }}-
- name: Install Node.js modules
run: npm ci
+ - name: Lint
+ run: npm run lint
- name: Build
run: npm run build
- name: Test
diff --git a/CHANGES.md b/CHANGES.md
index b30b6357..35f0f475 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,8 +2,12 @@
## 4.2.0 - unreleased
-- New opus-media-recorder plugin: provides cross-browser Opus codec support with
- various audio formats such as Ogg and WebM (#355)
+- New ffmpeg.wasm converter plugin: convert recorded data into other
+ audio/video/image file formats (#522)
+- New opus-media-recorder plugin: provides cross-browser Opus codec
+ support for various audio formats such as Ogg and WebM (#355)
+- ffmpeg.js plugin is deprecated; use new ffmpeg.wasm plugin instead
+- Bump required version for videojs-wavesurfer (3.4.0 or newer)
## 4.1.1 - 2020/11/01
diff --git a/build-config/fragments/dev.js b/build-config/fragments/dev.js
index c549fbfb..7b13b527 100644
--- a/build-config/fragments/dev.js
+++ b/build-config/fragments/dev.js
@@ -56,6 +56,16 @@ module.exports = {
console.log(colors.green(' [examples] wasm mime-type handler ready'));
console.log('');
+ // ========================================================
+ // use proper headers for SharedArrayBuffer on Firefox
+ // see https://github.com/ffmpegwasm/ffmpeg.wasm/issues/102
+ // ========================================================
+ app.use((req, res, next) => {
+ res.header('Cross-Origin-Opener-Policy', 'same-origin');
+ res.header('Cross-Origin-Embedder-Policy', 'require-corp');
+ next();
+ });
+
// =============================================
// file upload handler for simple upload example
// =============================================
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 3f907245..fa19cdc7 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -34,6 +34,7 @@
- [UmiJS](frameworks/umijs.md)
- Plugins
+ - [ffmpeg.wasm](plugins/ffmpeg.wasm.md)
- [ffmpeg.js](plugins/ffmpeg.js.md)
- [ts-ebml](plugins/ts-ebml.md)
- [webm-wasm](plugins/webm-wasm.md)
diff --git a/docs/demo/video-only-ffmpegwasm.html b/docs/demo/video-only-ffmpegwasm.html
new file mode 100644
index 00000000..cfca31d9
--- /dev/null
+++ b/docs/demo/video-only-ffmpegwasm.html
@@ -0,0 +1,99 @@
+
+
+
+
+ ffmpeg.wasm video-only example - Record Plugin for Video.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/dependencies.md b/docs/dependencies.md
index fb3fec51..c8cda0aa 100644
--- a/docs/dependencies.md
+++ b/docs/dependencies.md
@@ -40,4 +40,5 @@ Optional dependencies when using other [video plugins](plugins#video):
Optional dependencies when using [converter plugins](plugins#converter):
- [ts-ebml](plugins/ts-ebml.md) - Creates seekable WebM files, by injecting metadata like duration.
-- [ffmpeg.js](plugins/ffmpeg.js.md) - Run [FFmpeg](https://ffmpeg.org) in the browser and perform on-the-fly transcoding of recorded data.
+- [ffmpeg.wasm](plugins/ffmpeg.wasm.md) - Run [FFmpeg](https://ffmpeg.org) in the browser and perform on-the-fly transcoding of recorded data.
+- [ffmpeg.js](plugins/ffmpeg.js.md) - Deprecated FFmpeg plugin.
diff --git a/docs/options.md b/docs/options.md
index 1a836b6c..024cb50f 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -34,7 +34,7 @@ Additional options for this plugin are:
| `videoRecorderType` | string or function | `'auto'` | Video recorder type to use. This allows you to specify an alternative recorder class, e.g. `WhammyRecorder`. Defaults to `auto` which let's recordrtc specify the best available recorder type. |
| `videoBitRate` | float | `1200` | The video bitrate in kbps (only used in webm-wasm plugin). |
| `videoFrameRate` | float | `30` | The video frame rate in frames per second (only used in webm-wasm plugin). |
-| `videoWorkerURL` | string | `''` | URL for the video worker, for example: `../node_modules/webm-wasm/dist/webm-worker.js`. Currently only used for webm-wasm plugin. Use an empty string '' to disable (default). |
+| `videoWorkerURL` | string | `''` | URL for the video worker, for example: `../node_modules/webm-wasm/dist/webm-worker.js`. Currently only used for the webm-wasm plugin. Use an empty string '' to disable (default). |
| `videoWebAssemblyURL` | string | `''` | URL for the video worker WebAssembly file. Use an empty string '' to disable (default). Currently only used for the webm-wasm plugin. |
| `audioEngine` | string | `'recordrtc'` | Audio recording library/plugin to use. Legal values are `recordrtc`, `libvorbis.js`, `vmsg`, `opus-recorder`, `opus-media-recorder`, `lamejs` and `recorder.js`. |
| `audioRecorderType` | string or function | `'auto'` | Audio recorder type to use. This allows you to specify an alternative recorder class, e.g. `StereoAudioRecorder`. Defaults to `auto` which let's recordrtc specify the best available recorder type. Currently this setting is only used with the `recordrtc` `audioEngine`. |
@@ -48,8 +48,8 @@ Additional options for this plugin are:
| `audioBufferUpdate` | boolean | `false` | Enables the `audioBufferUpdate` event that provides real-time `AudioBuffer` instances from the input audio device. |
| `animationFrameRate` | float | `200` | Frame rate for animated GIF (in frames per second). |
| `animationQuality` | float | `10` | Sets quality of color quantization (conversion of images to the maximum 256 colors allowed by the GIF specification). Lower values (minimum = 1) produce better colors, but slow processing significantly. The default produces good color mapping at reasonable speeds. Values greater than 20 do not yield significant improvements in speed. |
-| `convertEngine` | string | `''` | Media converter library to use. Legal values are `ts-ebml` and `ffmpeg.js`. Use an empty string `''` to disable (default). Inspect the [player.convertedData](recorded-data#convert-data) object for the converted data. |
-| `convertWorkerURL` | string | `''` | URL for the converter worker, for example: `/node_modules/ffmpeg.js/ffmpeg-worker-mp4.js`. Currently only used for ffmpeg.js plugin. Use an empty string '' to disable (default). |
+| `convertEngine` | string | `''` | Media converter library to use. Legal values are `ts-ebml`, `ffmpeg.wasm` and `ffmpeg.js`. Use an empty string `''` to disable (default). Inspect the [player.convertedData](recorded-data#convert-data) object for the converted data. |
+| `convertWorkerURL` | string | `''` | URL for the converter worker, for example: `/node_modules/ffmpeg.js/ffmpeg-worker-mp4.js`. Currently only used for ffmpeg.wasm and ffmpeg.js plugins. Use an empty string '' to disable (default). |
| `convertOptions` | array | `[]` | List of string options to pass to the convert engine. |
| `hotKeys` | boolean or function | `false` | Enable [keyboard hotkeys](hotkeys.md). Disabled by default. |
-| `pluginLibraryOptions` | object | `{}` | Use this object to specify additional settings for the library used by the plugin. Currently only used in the opus-recorder and vmsg plugins. |
+| `pluginLibraryOptions` | object | `{}` | Use this object to specify additional settings for the library used by the plugin. Currently only used for the ffmpeg.wasm, ffmpeg.js, opus-recorder and vmsg plugins. |
diff --git a/docs/package-lock.json b/docs/package-lock.json
index 8fd727ff..899079f1 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -4,6 +4,29 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "@ffmpeg/core": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.8.5.tgz",
+ "integrity": "sha512-hemVFmhVLbD/VZaCG2BvCzFglKytMIMJ5aJfc12eXN4O4cG0wXnGTMVzlK1KKW/6viHhJMPkc9h4UDnJW8Uivg=="
+ },
+ "@ffmpeg/ffmpeg": {
+ "version": "0.9.7",
+ "resolved": "https://registry.npmjs.org/@ffmpeg/ffmpeg/-/ffmpeg-0.9.7.tgz",
+ "integrity": "sha512-WpZkNnqYGoaMcMd1EpaDi7nxRyEd05OjOTAfItH/ZwvAKJpr7ksvHKTC/NjP0li6mFrTFLGudP81J1tG0babdg==",
+ "requires": {
+ "is-url": "^1.2.4",
+ "node-fetch": "^2.6.1",
+ "regenerator-runtime": "^0.13.7",
+ "resolve-url": "^0.2.1"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
+ }
+ }
+ },
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@@ -1066,6 +1089,11 @@
"has-symbols": "^1.0.1"
}
},
+ "is-url": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
+ },
"is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
@@ -1311,6 +1339,11 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
+ "node-fetch": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
+ },
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -2068,8 +2101,7 @@
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
- "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
- "dev": true
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
},
"ret": {
"version": "0.1.15",
diff --git a/docs/package.json b/docs/package.json
index 9f38b124..9db0f340 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -34,6 +34,8 @@
"url": "git+https://github.com/collab-project/videojs-record.git"
},
"dependencies": {
+ "@ffmpeg/core": "^0.8.5",
+ "@ffmpeg/ffmpeg": "^0.9.7",
"ffmpeg.js": "^4.2.9003",
"lamejs": "^1.2.0",
"libvorbis.js": "^1.1.2",
diff --git a/docs/plugins.md b/docs/plugins.md
index 0ed7ec85..459ea8cb 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -9,7 +9,8 @@ following plugins:
| Plugin | Description |
| --- | --- |
-| [ffmpeg.js](plugins/ffmpeg.js.md) | Run [FFmpeg](https://ffmpeg.org) in the browser and perform on-the-fly transcoding of recorded data. |
+| [ffmpeg.wasm](plugins/ffmpeg.wasm.md) | Run [FFmpeg](https://ffmpeg.org) in the browser and perform on-the-fly transcoding of recorded data. |
+| [ffmpeg.js](plugins/ffmpeg.js.md) | Deprecated FFmpeg plugin. |
| [ts-ebml](plugins/ts-ebml.md) | Creates seekable WebM files, by injecting metadata like duration. |
## Video
diff --git a/docs/plugins/ffmpeg.js.md b/docs/plugins/ffmpeg.js.md
index 811ade85..cd706eea 100644
--- a/docs/plugins/ffmpeg.js.md
+++ b/docs/plugins/ffmpeg.js.md
@@ -1,5 +1,7 @@
# ffmpeg.js plugin
+Note: this plugin is deprecated, use the [ffmpeg.wasm](ffmpeg.wasm.md) plugin instead.
+
[FFmpeg](https://ffmpeg.org) is the Swiss-army knife of media transcoding. This plugin allows
you to run FFmpeg in the browser and perform on-the-fly transcoding of recorded data.
diff --git a/docs/plugins/ffmpeg.wasm.md b/docs/plugins/ffmpeg.wasm.md
new file mode 100644
index 00000000..baea1369
--- /dev/null
+++ b/docs/plugins/ffmpeg.wasm.md
@@ -0,0 +1,63 @@
+# ffmpeg.wasm plugin
+
+[FFmpeg](https://ffmpeg.org) is the Swiss-army knife of media transcoding. This plugin allows
+you to run FFmpeg in the browser and perform on-the-fly transcoding of recorded data.
+
+This plugin uses [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm) that provides a
+Webassembly / Javascript port of FFmpeg.
+
+## Example
+
+- [online demo](https://collab-project.github.io/videojs-record/demo/video-only-ffmpegwasm.html)
+- [demo source](https://github.com/collab-project/videojs-record/blob/master/examples/plugins/video-only-ffmpegwasm.html)
+
+## Usage
+
+Install the library:
+
+```console
+npm install --save @ffmpeg/ffmpeg @ffmpeg/core
+```
+
+Include the library and place it before any other scripts:
+
+```html
+
+```
+
+Import the plugin:
+
+```javascript
+import FFmpegWasmEngine from 'videojs-record/dist/plugins/videojs.record.ffmpeg-wasm.js';
+```
+
+And configure the `ffmpeg.wasm` `convertEngine`. For example:
+
+```javascript
+record: {
+ audio: true,
+ video: true,
+ maxLength: 20,
+ debug: true,
+ // enable ffmpeg.wasm plugin
+ convertEngine: 'ffmpeg.wasm',
+ convertWorkerURL: '../../node_modules/@ffmpeg/core/dist/ffmpeg-core.js',
+ // convert recorded data to MP4 (and copy over audio data without encoding)
+ convertOptions: ['-c:v', 'libx264', '-preset', 'slow', '-crf', '22', '-c:a', 'copy', '-f', 'mp4'],
+ // specify output mime-type
+ pluginLibraryOptions: {
+ outputType: 'video/mp4'
+ }
+}
+```
+
+## Options
+
+Options for this plugin:
+
+| Option | Value | Description |
+| --- | --- | --- |
+| `convertEngine` | `ffmpeg.wasm` | Enables the plugin. |
+| `convertOptions` | `['-f', 'mp3', '-codec:a', 'libmp3lame', '-qscale:a', '2']` | Array of arguments for FFmpeg. |
+| `pluginLibraryOptions` | `{outputType: 'video/mp4'}` | Specify output mime-type and other options. |
+| `convertWorkerURL` | `/path/to/@ffmpeg/core/dist/ffmpeg-core.js` | Specify encoding worker. |
diff --git a/docs/recorded-data.md b/docs/recorded-data.md
index b6741e74..d62c6de5 100644
--- a/docs/recorded-data.md
+++ b/docs/recorded-data.md
@@ -15,7 +15,7 @@ player.on('finishRecord', function() {
## Save data
Use the `saveAs` method to show a 'Save as' browser dialog where the user can
-choose the storage location for the recorded data. It accepts a `name` object that
+choose the storage location for the recorded data. It accepts an object that
contains a mapping between the media type and the filename. For example:
```javascript
@@ -48,7 +48,7 @@ record: {
}
```
-And listen for the `finishConvert` event. For example:
+And listen for the `finishConvert` event:
```javascript
// converter ready and stream is available
diff --git a/examples/plugins/video-only-ffmpegwasm.html b/examples/plugins/video-only-ffmpegwasm.html
new file mode 100644
index 00000000..f1b65c49
--- /dev/null
+++ b/examples/plugins/video-only-ffmpegwasm.html
@@ -0,0 +1,104 @@
+
+
+
+
+ ffmpeg.wasm video-only example - Record Plugin for Video.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/karma.conf.js b/karma.conf.js
index aa0db873..35ddbd56 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -135,6 +135,9 @@ module.exports = function(config) {
{pattern: 'node_modules/webm-wasm/dist/webm-wasm.wasm', included: false, served: true, type: 'wasm'},
// ffmpeg.js
{pattern: 'node_modules/ffmpeg.js/ffmpeg-worker-mp4.js', included: false, served: true},
+ // ffmpeg.wasm
+ {pattern: 'node_modules/@ffmpeg/ffmpeg/dist/ffmpeg.min.js', included: false, served: true},
+ {pattern: 'node_modules/@ffmpeg/core/dist/ffmpeg-core.js', included: false, served: true},
// gif-recorder: only available on CDN
'http://cdn.webrtc-experiment.com/gif-recorder.js',
diff --git a/package-lock.json b/package-lock.json
index 92cd892c..24c28a8d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1800,6 +1800,38 @@
}
}
},
+ "@ffmpeg/core": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.8.5.tgz",
+ "integrity": "sha512-hemVFmhVLbD/VZaCG2BvCzFglKytMIMJ5aJfc12eXN4O4cG0wXnGTMVzlK1KKW/6viHhJMPkc9h4UDnJW8Uivg==",
+ "dev": true
+ },
+ "@ffmpeg/ffmpeg": {
+ "version": "0.9.7",
+ "resolved": "https://registry.npmjs.org/@ffmpeg/ffmpeg/-/ffmpeg-0.9.7.tgz",
+ "integrity": "sha512-WpZkNnqYGoaMcMd1EpaDi7nxRyEd05OjOTAfItH/ZwvAKJpr7ksvHKTC/NjP0li6mFrTFLGudP81J1tG0babdg==",
+ "dev": true,
+ "requires": {
+ "is-url": "^1.2.4",
+ "node-fetch": "^2.6.1",
+ "regenerator-runtime": "^0.13.7",
+ "resolve-url": "^0.2.1"
+ },
+ "dependencies": {
+ "node-fetch": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
+ "dev": true
+ },
+ "regenerator-runtime": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
+ "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
+ "dev": true
+ }
+ }
+ },
"@istanbuljs/load-nyc-config": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz",
@@ -3745,7 +3777,7 @@
},
"cheerio": {
"version": "0.22.0",
- "resolved": "http://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz",
"integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=",
"dev": true,
"requires": {
@@ -3769,7 +3801,7 @@
"dependencies": {
"css-select": {
"version": "1.2.0",
- "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
"dev": true,
"requires": {
@@ -9129,6 +9161,12 @@
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
+ "is-url": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
+ "dev": true
+ },
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
@@ -10460,9 +10498,9 @@
}
},
"mini-css-extract-plugin": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.4.tgz",
- "integrity": "sha512-dNjqyeogUd8ucUgw5sxm1ahvSfSUgef7smbmATRSbDm4EmNx5kQA6VdUEhEeCKSjX6CTYjb5vxgMUvRjqP3uHg==",
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.5.tgz",
+ "integrity": "sha512-tvmzcwqJJXau4OQE5vT72pRT18o2zF+tQJp8CWchqvfQnTlflkzS+dANYcRdyPRWUWRkfmeNTKltx0NZI/b5dQ==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
@@ -16070,12 +16108,12 @@
}
},
"videojs-wavesurfer": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/videojs-wavesurfer/-/videojs-wavesurfer-3.3.0.tgz",
- "integrity": "sha512-mNI80/lVb/VxT/wim/4ppE/L9C9WShQYzc+0t2q35hB4+1EcTQlJqMzxkQhc6uquL36cm99VmbNuJpAAKJCr0w==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/videojs-wavesurfer/-/videojs-wavesurfer-3.4.0.tgz",
+ "integrity": "sha512-nEmaQ4MCYnr7bwoF3SHVPjXJB9BiEob5qJf8jsqmOW6uEcNeLIBtQLzbqHbynNluVa1AY2BDo99qalzduXQSPA==",
"requires": {
"video.js": ">=7.0.5",
- "wavesurfer.js": ">=4.0.1"
+ "wavesurfer.js": ">=4.4.0"
}
},
"vm-browserify": {
diff --git a/package.json b/package.json
index 3bef75a9..a0e1e422 100644
--- a/package.json
+++ b/package.json
@@ -74,7 +74,7 @@
"dependencies": {
"recordrtc": ">=5.6.1",
"video.js": ">=7.0.5",
- "videojs-wavesurfer": ">=3.3.0",
+ "videojs-wavesurfer": ">=3.4.0",
"webrtc-adapter": ">=7.7.0"
},
"devDependencies": {
@@ -82,6 +82,8 @@
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/register": "^7.12.10",
+ "@ffmpeg/core": "^0.8.5",
+ "@ffmpeg/ffmpeg": "^0.9.7",
"@mattiasbuelens/web-streams-polyfill": "^0.3.2",
"add-zero": "^1.0.0",
"babel-loader": "^8.2.2",
@@ -121,7 +123,7 @@
"lamejs": ">=1.2.0",
"libvorbis.js": ">=1.1.2",
"log-timestamp": "^0.3.0",
- "mini-css-extract-plugin": "^1.3.4",
+ "mini-css-extract-plugin": "^1.3.5",
"node-fs-extra": "^0.8.2",
"node-static": "^0.7.11",
"npm-run-all": "^4.1.5",
diff --git a/src/js/defaults.js b/src/js/defaults.js
index 4332f6e8..f73e1bfd 100644
--- a/src/js/defaults.js
+++ b/src/js/defaults.js
@@ -121,8 +121,8 @@ const pluginDefaultOptions = {
imageOutputQuality: 0.92,
// Accepts numbers in milliseconds; use this to force intervals-based blobs.
timeSlice: 0,
- // Media converter library to use. Legal values are 'ts-ebml' and 'ffmpeg.js'.
- // Use an empty string '' to disable (default).
+ // Media converter library to use. Legal values are 'ts-ebml', 'ffmpeg.wasm'
+ // and 'ffmpeg.js'. Use an empty string '' to disable (default).
convertEngine: '',
// URL for the converter worker.
convertWorkerURL: '',
@@ -131,7 +131,7 @@ const pluginDefaultOptions = {
// Enable keyboard hotkeys.
hotKeys: false,
// Use this object to specify additional settings for the library used by the
- // plugin (only used in opus-recorder and vmsg plugins).
+ // plugin (only used in opus-recorder, ffmpeg.js, ffmpeg.wasm and vmsg plugins).
pluginLibraryOptions: {}
};
diff --git a/src/js/engine/convert-engine.js b/src/js/engine/convert-engine.js
index 79473198..9af831fa 100644
--- a/src/js/engine/convert-engine.js
+++ b/src/js/engine/convert-engine.js
@@ -12,9 +12,10 @@ const Component = videojs.getComponent('Component');
// supported convert plugin engines
const TSEBML = 'ts-ebml';
const FFMPEGJS = 'ffmpeg.js';
+const FFMPEGWASM = 'ffmpeg.wasm';
// all convert plugins
-const CONVERT_PLUGINS = [TSEBML, FFMPEGJS];
+const CONVERT_PLUGINS = [TSEBML, FFMPEGJS, FFMPEGWASM];
/**
* Base class for converter backends.
@@ -76,5 +77,5 @@ videojs.ConvertEngine = ConvertEngine;
Component.registerComponent('ConvertEngine', ConvertEngine);
export {
- ConvertEngine, CONVERT_PLUGINS, TSEBML, FFMPEGJS
+ ConvertEngine, CONVERT_PLUGINS, TSEBML, FFMPEGJS, FFMPEGWASM
};
diff --git a/src/js/engine/engine-loader.js b/src/js/engine/engine-loader.js
index 7ae8cc7d..e8458d88 100644
--- a/src/js/engine/engine-loader.js
+++ b/src/js/engine/engine-loader.js
@@ -6,7 +6,7 @@
import videojs from 'video.js';
import RecordRTCEngine from './record-rtc';
-import {CONVERT_PLUGINS, TSEBML, FFMPEGJS} from './convert-engine';
+import {CONVERT_PLUGINS, TSEBML, FFMPEGJS, FFMPEGWASM} from './convert-engine';
import {RECORDRTC, LIBVORBISJS, RECORDERJS, LAMEJS, OPUSRECORDER, OPUSMEDIARECORDER, VMSG, WEBMWASM, AUDIO_PLUGINS} from './record-engine';
/**
@@ -124,6 +124,11 @@ const getConvertEngine = function(convertEngine) {
ConvertEngineClass = videojs.FFmpegjsEngine;
break;
+ case FFMPEGWASM:
+ // ffmpeg.wasm
+ ConvertEngineClass = videojs.FFmpegWasmEngine;
+ break;
+
default:
// unknown engine
throw new Error('Unknown convertEngine: ' + convertEngine);
diff --git a/src/js/plugins/ffmpeg-wasm-plugin.js b/src/js/plugins/ffmpeg-wasm-plugin.js
new file mode 100644
index 00000000..29d89b29
--- /dev/null
+++ b/src/js/plugins/ffmpeg-wasm-plugin.js
@@ -0,0 +1,111 @@
+/**
+ * @file ffmpeg-wasm-plugin.js
+ * @since 4.2.0
+ */
+
+import videojs from 'video.js';
+
+const ConvertEngine = videojs.getComponent('ConvertEngine');
+
+/**
+ * Converter engine using the ffmpeg.wasm library.
+ *
+ * @class
+ * @augments videojs.ConvertEngine
+ */
+class FFmpegWasmEngine extends ConvertEngine {
+ /**
+ * Creates an instance of this class.
+ *
+ * @param {Player} player
+ * The `Player` that this class should be attached to.
+ *
+ * @param {Object} [options]
+ * The key/value store of player options.
+ */
+ constructor(player, options) {
+ super(player, options);
+
+ /**
+ * Enables console logging for debugging purposes.
+ *
+ * @type {boolean}
+ */
+ this.debug = false;
+ /**
+ * Path to script `ffmpeg-core.js`.
+ *
+ * @type {string}
+ */
+ this.convertWorkerURL = './node_modules/@ffmpeg/core/dist/ffmpeg-core.js';
+ /**
+ * Mime-type for output.
+ *
+ * @type {string}
+ */
+ this.outputType = null;
+ /**
+ * Additional configuration options for the ffmpeg.wasm library.
+ *
+ * @type {object}
+ */
+ this.pluginLibraryOptions = {};
+ }
+
+ /**
+ * Inject metadata.
+ *
+ * @param {Blob} data - Recorded data that needs to be converted.
+ */
+ async convert(data) {
+ // set output mime type
+ if (this.pluginLibraryOptions.outputType === undefined) {
+ throw new Error('no outputType specified!');
+ }
+ this.outputType = this.pluginLibraryOptions.outputType;
+
+ const {version, createFFmpeg, fetchFile} = FFmpeg;
+ const ffmpeg = createFFmpeg({
+ corePath: this.convertWorkerURL,
+ log: this.debug
+ });
+ // save timestamp
+ const timestamp = new Date();
+ timestamp.setTime(data.lastModified);
+
+ // use temporary filenames
+ const tempInputName = 'input_' + timestamp.getTime();
+ const tempOutputName = 'output_' + timestamp.getTime();
+
+ // add ffmpeg options
+ let opts = ['-i', tempInputName];
+ opts = opts.concat(this.convertOptions);
+ opts.push(tempOutputName);
+
+ // notify listeners
+ this.player().trigger('startConvert');
+
+ // load and convert blob
+ await ffmpeg.load();
+ ffmpeg.FS('writeFile', tempInputName, await fetchFile(data));
+ await ffmpeg.run(...opts);
+ const output = ffmpeg.FS('readFile', tempOutputName);
+
+ // create new blob
+ let result = new Blob([output.buffer], {type: this.outputType});
+
+ // add existing file info
+ this.addFileInfo(result, timestamp);
+
+ // store result
+ this.player().convertedData = result;
+
+ // notify listeners
+ this.player().trigger('finishConvert');
+ }
+}
+
+// expose plugin
+videojs.FFmpegWasmEngine = FFmpegWasmEngine;
+
+export default FFmpegWasmEngine;
diff --git a/src/js/plugins/ffmpegjs-plugin.js b/src/js/plugins/ffmpegjs-plugin.js
index a4374940..b86f52c1 100644
--- a/src/js/plugins/ffmpegjs-plugin.js
+++ b/src/js/plugins/ffmpegjs-plugin.js
@@ -10,6 +10,8 @@ const ConvertEngine = videojs.getComponent('ConvertEngine');
/**
* Converter engine using the ffmpeg.js library.
*
+ * Deprecated. Use the ffmpeg.wasm plugin instead.
+ *
* @class
* @augments videojs.ConvertEngine
*/
@@ -46,7 +48,7 @@ class FFmpegjsEngine extends ConvertEngine {
*/
this.outputType = null;
/**
- * Additional configuration options for the opus-recorder library.
+ * Additional configuration options for the ffmpeg.js library.
*
* @type {object}
*/
diff --git a/src/js/plugins/opus-media-recorder-plugin.js b/src/js/plugins/opus-media-recorder-plugin.js
index 67283dd0..678bb51f 100644
--- a/src/js/plugins/opus-media-recorder-plugin.js
+++ b/src/js/plugins/opus-media-recorder-plugin.js
@@ -3,6 +3,8 @@
* @since 4.2.0
*/
+import videojs from 'video.js';
+
const RecordEngine = videojs.getComponent('RecordEngine');
/**
diff --git a/test/engine/convert-engine.spec.js b/test/engine/convert-engine.spec.js
index 477fcb48..ef5629e6 100644
--- a/test/engine/convert-engine.spec.js
+++ b/test/engine/convert-engine.spec.js
@@ -4,7 +4,7 @@
import TestHelpers from '../test-helpers';
-import {ConvertEngine, CONVERT_PLUGINS, TSEBML, FFMPEGJS} from '../../src/js/engine/convert-engine';
+import {ConvertEngine, CONVERT_PLUGINS, TSEBML, FFMPEGJS, FFMPEGWASM} from '../../src/js/engine/convert-engine';
/** @test {convert-engine} */
@@ -32,7 +32,8 @@ describe('engine.convert-engine', () => {
it('contain supported convert plugin engines', () => {
expect(TSEBML).toEqual('ts-ebml');
expect(FFMPEGJS).toEqual('ffmpeg.js');
- expect(CONVERT_PLUGINS.length).toEqual(2);
+ expect(FFMPEGWASM).toEqual('ffmpeg.wasm');
+ expect(CONVERT_PLUGINS.length).toEqual(3);
});
it('loads blob', (done) => {
diff --git a/test/plugins/ffmpeg-wasm-plugin.spec.js b/test/plugins/ffmpeg-wasm-plugin.spec.js
new file mode 100644
index 00000000..37206187
--- /dev/null
+++ b/test/plugins/ffmpeg-wasm-plugin.spec.js
@@ -0,0 +1,48 @@
+/**
+ * @since 4.2.0
+ */
+
+import TestHelpers from '../test-helpers';
+
+// registers the plugin
+import FFmpegWasmEngine from '../../src/js/plugins/ffmpegjs-plugin';
+import {FFMPEGWASM} from '../../src/js/engine/convert-engine';
+
+/** @test {FFmpegWasmEngine} */
+describe('plugins.ffmpeg-wasm-plugin', () => {
+ let player;
+
+ beforeEach(() => {
+ // create video-only player with ffmpeg.wasm plugin
+ player = TestHelpers.makeConvertPluginPlayer(FFMPEGWASM);
+ });
+
+ afterEach(() => {
+ player.dispose();
+ });
+
+ /** @test {FFmpegWasmEngine} */
+ it('converts', (done) => {
+ // allow test to fail
+ pending('disabled until test runner failure is figured out');
+
+ player.one('deviceReady', () => {
+ let req = new Request(TestHelpers.TEST_WEBM);
+ fetch(req).then((response) => {
+ return response.blob();
+ }).then((blob) => {
+ player.one('finishConvert', () => {
+ expect(player.convertedData instanceof Blob).toBeTruthy();
+ expect(player.convertedData.name).toEndWith('.mp4');
+ done();
+ });
+ player.record().converter.convert(blob);
+ });
+ });
+
+ player.one('ready', () => {
+ // start device
+ player.record().getDevice();
+ });
+ });
+});
diff --git a/test/test-helpers.js b/test/test-helpers.js
index ee1de8be..97740f55 100644
--- a/test/test-helpers.js
+++ b/test/test-helpers.js
@@ -9,7 +9,7 @@ import {Player, mergeOptions} from 'video.js';
import adapter from 'webrtc-adapter';
import {LIBVORBISJS, RECORDERJS, LAMEJS, OPUSRECORDER, VMSG, WEBMWASM, OPUSMEDIARECORDER} from '../src/js/engine/record-engine';
-import {TSEBML, FFMPEGJS} from '../src/js/engine/convert-engine';
+import {TSEBML, FFMPEGJS, FFMPEGWASM} from '../src/js/engine/convert-engine';
const TestHelpers = {
TEST_OGG: '/base/test/support/audio.ogg',
@@ -238,6 +238,13 @@ const TestHelpers = {
recordPluginOptions.pluginLibraryOptions = {outputType: 'audio/mpeg'};
break;
+ case FFMPEGWASM:
+ recordPluginOptions.convertEngine = FFMPEGWASM;
+ recordPluginOptions.convertWorkerURL = '/base/node_modules/@ffmpeg/core/dist/ffmpeg-core.js';
+ recordPluginOptions.convertOptions = ['-c:v', 'libx264', '-preset', 'slow', '-crf', '22', '-c:a', 'copy', '-f', 'mp4'];
+ recordPluginOptions.pluginLibraryOptions = {outputType: 'video/mp4'};
+ break;
+
default:
recordPluginOptions.convertEngine = pluginName;
break;