都AI时代了,为什么还会有这种入门级文章?正是因为AI时代信息泛滥,直接阅读官方文档一个小时能搞定的事情,愣是可能被AI耍得晕头转向,尤其是各种劣质AI工具(也包括头部AI工具),不信你试试这个prompt:基于ffmpeg-wasm实现的一个视频片段截取的单HTML页面示例,要求代码100%可用。
诉求:脱离各类构建工具,仅单HTML文件实现接入ffmpeg-wasm。
ffmpeg-wasm
这里只记录一些关键信息,其它信息问AI去吧。
分0.11.x和0.12.x2个主要版本,2个版本差异非常大,由于信息差大且部分细节官方也没有特意说明,导致大部分AI工具都被绕的稀里糊涂,信息完全错乱,先讨论最简单的0.11.x版本。
0.11.x文档: https://ffmpegwasm-0-11-x.netlify.app/0.12.x文档: https://ffmpegwasm.netlify.app/docs/overview
0.11.x 单文件傻瓜可用版
以下是最终正确答案,但就这短短的几行代码,问遍全网AI没有一个能一次性正确回答出来的,以下代码仅需server模式运行,不需要任何特殊配置:
<body>
<h1>ffmpeg-wasm0.11.x单文件单线程示例</h1>
<video id="player" controls></video>
<br/>
<input type="file" id="uploader">
<script src="https://unpkg.shop.jd.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script>
<script>
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({
// 必须指定名称,否则会报错
mainName: 'main',
// 指定单线程版本,默认是多线程,使用会有诸多限制
corePath: 'https://unpkg.com/@ffmpeg/core-st@0.11.1/dist/ffmpeg-core.js',
log: true,
});
const transcode = async ({ target: { files } }) => {
const name = 'input.mp4';
await ffmpeg.load();
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
await ffmpeg.run('-i', name, '-ss', '00:00:00', '-t', '00:00:05', '-c', 'copy', 'output.mp4');
const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('player');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
}
document.getElementById('uploader').addEventListener('change', transcode);
</script>
</body>
0.11.x 单文件多线程版
默认是多线程版,完整示例:
<!doctype html>
<html>
<head>
<title>ffmpeg-wasm示例</title>
</head>
<body>
<video id="player" controls></video>
<br/>
<input type="file" id="uploader">
<!-- 将 @ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js 复制到项目目录,这个JS不能放CDN -->
<!-- <script src="https://unpkg.shop.jd.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script> -->
<script src="./ffmpeg.min.js"></script>
<script>
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true });
const transcode = async ({ target: { files } }) => {
const name = 'input.mp4';
await ffmpeg.load();
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
await ffmpeg.run('-i', name, '-ss', '00:00:00', '-t', '00:00:05', '-c', 'copy', 'output.mp4');
const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('player');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
}
document.getElementById('uploader').addEventListener('change', transcode);
</script>
</body>
</html>
SharedArrayBuffer问题
直接运行会报错ReferenceError: SharedArrayBuffer is not defined,原因官方有说明:
SharedArrayBuffer is only available to pages that are cross-origin isolated. So you need to host your own server with Cross-Origin-Embedder-Policy: require-corp and Cross-Origin-Opener-Policy: same-origin headers to use ffmpeg.wasm.
如果你是whistle用户,可以这样快速验证:
xxx.com/test-ffmpeg.html resHeaders://Cross-Origin-Embedder-Policy=require-corp&Cross-Origin-Opener-Policy=same-origin
CDN问题
如果添加了上述header后,ffmpeg.min.js仍托管于CDN,加载时会报错,必须把该文件和html文件放在同一个域名下:
net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep
其它注意事项

0.12.x 单文件单线程版
<body>
<h1>ffmpeg-wasm0.12.x单线程示例</h1>
<video id="player" controls></video>
<br /><br />
<input type="file" id="uploader" />
<!-- <script src="https://unpkg.shop.jd.com/@ffmpeg/ffmpeg@0.12.15/dist/umd/ffmpeg.js"></script> -->
<script src="./ffmpeg.js"></script>
<script>
async function toBlobURL(url, mimeType) {
const data = await fetch(url).then(resp => resp.arrayBuffer());
const blob = new Blob([data], { type: mimeType });
return URL.createObjectURL(blob);
}
async function fetchFile(source) {
const arrayBuffer = await source.arrayBuffer();
return new Uint8Array(arrayBuffer);
}
const loadFFmpeg = async () => {
console.log('ffmpeg start load');
const ffmpeg = new FFmpegWASM.FFmpeg();
const baseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/umd';
ffmpeg.on('log', ({ message }) => {
console.log(message);
});
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
console.log('ffmpeg load success');
return ffmpeg;
};
const transcode = async ({ target: { files } }) => {
const ffmpeg = await loadFFmpeg();
const name = 'input.mp4';
await ffmpeg.writeFile('input.mp4', await fetchFile(files[0]));
await ffmpeg.exec(['-i', name, '-ss', '00:00:00', '-t', '00:00:05', '-c', 'copy', 'output.mp4']);
const data = await ffmpeg.readFile('output.mp4');
const video = document.getElementById('player');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
};
document.getElementById('uploader').addEventListener('change', transcode);
</script>
</body>
注意事项
umd/ffmpeg.js必须复制到项目里面,如果直接从CDN引入会报错:SecurityError: Failed to construct 'Worker': Script at 'https://unpkg.shop.jd.com/@ffmpeg/ffmpeg@0.12.15/dist/umd/814.ffmpeg.js' cannot be accessed from origin 'xxx';- 和0.11.x不同的是,
@ffmpeg/core下面的文件必须全部toBlob以绕过跨域限制(经测试发现0.11.x由于内置了toBlob逻辑所以无需主动书写); - 特别特别注意!
0.11.x版本的@ffmpeg/core是多线程版本,如果需要单线程版需要主动改成@ffmpeg/core-st,而新版本刚好反过来,0.12.x版本下的@ffmpeg/core默认变成了单线程版本,如果需要多线程版需要主动改成@ffmpeg/core-mt,这个点非常坑,通用的包名不同版本差异很大,这一点官网几乎没有特别说明!!! - 由于默认变成了单线程版,所以
@ffmpeg/core下面少了worker.js文件,文件单独移动到mt版本去了,这是之前非常疑惑的,之前问AI根本就不知道这一点;
0.12.x 单文件多线程版
<body>
<h1>ffmpeg-wasm0.12.x多线程示例</h1>
<video id="player" controls></video>
<br /><br />
<input type="file" id="uploader" />
<!-- <script src="https://unpkg.shop.jd.com/@ffmpeg/ffmpeg@0.12.15/dist/umd/ffmpeg.js"></script> -->
<script src="./ffmpeg.js"></script>
<script>
async function toBlobURL(url, mimeType) {
const data = await fetch(url).then(resp => resp.arrayBuffer());
const blob = new Blob([data], { type: mimeType });
return URL.createObjectURL(blob);
}
async function fetchFile(source) {
const arrayBuffer = await source.arrayBuffer();
return new Uint8Array(arrayBuffer);
}
const loadFFmpeg = async () => {
console.log('ffmpeg start load');
const ffmpeg = new FFmpegWASM.FFmpeg();
const baseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core-mt@0.12.10/dist/umd';
ffmpeg.on('log', ({ message }) => {
console.log(message);
});
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
// 多线程版特殊增加
workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript'),
});
console.log('ffmpeg load success');
return ffmpeg;
};
const transcode = async ({ target: { files } }) => {
const ffmpeg = await loadFFmpeg();
const name = 'input.mp4';
await ffmpeg.writeFile('input.mp4', await fetchFile(files[0]));
await ffmpeg.exec(['-i', name, '-ss', '00:00:00', '-t', '00:00:05', '-c', 'copy', 'output.mp4']);
const data = await ffmpeg.readFile('output.mp4');
const video = document.getElementById('player');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
};
document.getElementById('uploader').addEventListener('change', transcode);
</script>
</body>
注意事项
- 同0.11.x版本,需要注意
SharedArrayBuffer问题; - 和单线程版不同的是,
ffmpeg-core.worker.js也需要单独toBlob;
