ffmpeg-wasm闭坑指南
本文由 小茗同学 发表于 2026-04-20 浏览(4)
最后修改 2026-04-20 标签:ffmpeg wasm

都AI时代了,为什么还会有这种入门级文章?正是因为AI时代信息泛滥,直接阅读官方文档一个小时能搞定的事情,愣是可能被AI耍得晕头转向,尤其是各种劣质AI工具(也包括头部AI工具),不信你试试这个prompt:基于ffmpeg-wasm实现的一个视频片段截取的单HTML页面示例,要求代码100%可用。

诉求:脱离各类构建工具,仅单HTML文件实现接入ffmpeg-wasm。

ffmpeg-wasm

这里只记录一些关键信息,其它信息问AI去吧。

0.11.x0.12.x2个主要版本,2个版本差异非常大,由于信息差大且部分细节官方也没有特意说明,导致大部分AI工具都被绕的稀里糊涂,信息完全错乱,先讨论最简单的0.11.x版本。

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

其它注意事项

  1. @ffmpeg/core子版本需要和@ffmpeg/ffmpeg一致;
  2. @ffmpeg/core可以部署CDN,更无需转blob特殊处理;
  3. 0.11.xffmpeg/core下面有这3个文件:

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>

注意事项

  1. 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'
  2. 和0.11.x不同的是,@ffmpeg/core下面的文件必须全部toBlob以绕过跨域限制(经测试发现0.11.x由于内置了toBlob逻辑所以无需主动书写);
  3. 特别特别注意!0.11.x版本的@ffmpeg/core是多线程版本,如果需要单线程版需要主动改成@ffmpeg/core-st,而新版本刚好反过来,0.12.x版本下的@ffmpeg/core 默认变成了单线程版本,如果需要多线程版需要主动改成@ffmpeg/core-mt,这个点非常坑,通用的包名不同版本差异很大,这一点官网几乎没有特别说明!!!
  4. 由于默认变成了单线程版,所以@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>

注意事项

  1. 同0.11.x版本,需要注意SharedArrayBuffer问题;
  2. 和单线程版不同的是,ffmpeg-core.worker.js也需要单独toBlob