You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firefox无法将录制视频保存为MP4格式,Safari/Edge正常求助

Firefox下MediaRecorder录制视频无法保存为MP4的解决方案

问题根源

你的代码中虽然在创建Blob时指定了video/mp4类型,但Firefox的MediaRecorder并不支持直接录制H.264编码的MP4格式——它默认只能输出WebM(VP8/VP9视频+Opus音频)。你看到的“MP4文件”只是强行修改了后缀名,文件编码本质还是WebM,所以实际下载后仍是WebM格式。

而Safari和Edge支持H.264编码,因此MediaRecorder可以直接生成符合MP4标准的文件,无需转码。

解决方案

1. 动态适配浏览器支持的录制格式(无转码)

先检测浏览器支持的媒体格式,让MediaRecorder使用最优的可用格式,同时匹配正确的文件后缀:

// 优先检测支持的MP4格式,降级到WebM
const getSupportedRecorderConfig = () => {
  const candidateFormats = [
    { mimeType: 'video/mp4;codecs=avc1.42E01E,mp4a.40.2' }, // H.264 + AAC
    { mimeType: 'video/webm;codecs=vp9,opus' }, // VP9 + Opus
    { mimeType: 'video/webm;codecs=vp8,opus' }  // VP8 + Opus
  ];
  return candidateFormats.find(config => MediaRecorder.isTypeSupported(config.mimeType)) || {};
};

function startRecording() {
  clearRecordedData();
  navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    .then((stream) => {
      const recorderOptions = getSupportedRecorderConfig();
      mediaRecorder = new MediaRecorder(stream, recorderOptions);

      const liveCamera = document.getElementById('liveCamera');
      liveCamera.srcObject = stream;
      liveCamera.muted = true;

      mediaRecorder.ondataavailable = (event) => {
        if (event.data.size > 0) {
          recordedChunks.push(event.data);
        }
      };

      mediaRecorder.onstop = () => {
        // 使用MediaRecorder实际的MIME类型创建Blob,而非硬写video/mp4
        recordedBlob = new Blob(recordedChunks, { type: mediaRecorder.mimeType }); 

        recordedVideo.src = URL.createObjectURL(recordedBlob);
        enableButtons(['startRecord', 'saveRecord', 'playRecord']);
        disableButtons(['stopRecord']);
      };

      mediaRecorder.start();
      recordedTime = 0;
      updateRecordedTime();
      disableButtons(['startRecord']);
      enableButtons(['stopRecord']);
    })
    .catch((error) => {
      console.error('getUserMedia error:', error);
    });
}

function saveRecording() {
  if (recordedBlob) {
    const url = URL.createObjectURL(recordedBlob);
    const a = document.createElement('a');
    a.href = url;
    const urlParams = new URLSearchParams(window.location.search);
    const sourceValue = urlParams.get('source');
    // 根据实际MIME类型设置文件后缀
    const fileExtension = mediaRecorder.mimeType.includes('mp4') ? '.mp4' : '.webm';
    a.download = sourceValue + fileExtension;

    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }
}

2. 客户端转码为MP4(强制输出MP4)

如果业务必须要求所有浏览器都输出MP4格式,可以使用ffmpeg.wasm在客户端将WebM转码为MP4:

步骤1:安装依赖

npm install @ffmpeg/ffmpeg @ffmpeg/core

步骤2:修改录制停止后的逻辑

import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';

const ffmpeg = createFFmpeg({ log: false });

// 转码WebM到MP4的工具函数
async function transcodeWebMToMP4(webmBlob) {
  if (!ffmpeg.isLoaded()) {
    await ffmpeg.load();
  }
  // 将Blob写入ffmpeg虚拟文件系统
  ffmpeg.FS('writeFile', 'input.webm', await fetchFile(webmBlob));
  // 执行转码命令:H.264视频 + AAC音频
  await ffmpeg.run('-i', 'input.webm', '-c:v', 'libx264', '-preset', 'fast', '-c:a', 'aac', 'output.mp4');
  // 读取转码后的文件
  const mp4Data = ffmpeg.FS('readFile', 'output.mp4');
  // 清理临时文件
  ffmpeg.FS('unlink', 'input.webm');
  ffmpeg.FS('unlink', 'output.mp4');
  // 返回MP4格式的Blob
  return new Blob([mp4Data.buffer], { type: 'video/mp4' });
}

// 修改startRecording中的onstop回调
mediaRecorder.onstop = async () => {
  let finalBlob = new Blob(recordedChunks, { type: mediaRecorder.mimeType });
  // 如果录制的是WebM,转码为MP4
  if (mediaRecorder.mimeType.includes('webm')) {
    try {
      finalBlob = await transcodeWebMToMP4(finalBlob);
    } catch (err) {
      console.error('转码失败:', err);
      alert('MP4转码失败,将保存原始WebM文件');
    }
  }
  recordedBlob = finalBlob;
  recordedVideo.src = URL.createObjectURL(recordedBlob);
  enableButtons(['startRecord', 'saveRecord', 'playRecord']);
  disableButtons(['stopRecord']);
};

// saveRecording函数可以保持原逻辑,直接使用.mp4后缀
function saveRecording() {
  if (recordedBlob) {
    const url = URL.createObjectURL(recordedBlob);
    const a = document.createElement('a');
    a.href = url;
    const urlParams = new URLSearchParams(window.location.search);
    const sourceValue = urlParams.get('source');
    a.download = sourceValue + '.mp4';

    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }
}

注意:客户端转码会占用大量CPU资源,大文件转码可能导致页面卡顿,建议添加加载提示,并在转码过程中禁用相关按钮。

内容的提问来源于stack exchange,提问作者Zq5437

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 06:27:10