使用ffmpeg.wasm解码视频(avi,mpg等格式)问题

78 阅读1分钟

ffmpegHelper.ts文件

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

export class FFmpegWrapper {
  private ffmpeg: FFmpeg;
  public isLoaded: boolean = false;

  constructor() {
    this.ffmpeg = createFFmpeg({
      log: import.meta.env.VITE_APP_ENV !== 'production'
    });
  }

  async load(): Promise<void> {
    if (!this.isLoaded) {
      await this.ffmpeg.load();
      this.isLoaded = true;
    }
  }

  async transcodeVideoToMP4(remoteVideoUrl: string, inputType: string = 'avi'): Promise<Uint8Array> {
    if (!this.isLoaded) {
      throw new Error('FFmpeg 未初始化,请先调用 load() 方法。');
    }

    try {
      // 获取远程文件[citation:1]
      const response = await fetch(remoteVideoUrl);
      if (!response.ok) {
        throw new Error(`获取视频失败: ${response.status} ${response.statusText}`);
      }
      const videoArrayBuffer = await response.arrayBuffer();

      // 根据输入类型设置文件名
      const inputFileName = `input.${inputType}`;
      const outputFileName = 'output.mp4';

      // 写入文件到FFmpeg的虚拟文件系统[citation:1]
      this.ffmpeg.FS('writeFile', inputFileName, new Uint8Array(videoArrayBuffer));

      // 执行转码命令[citation:1]
      await this.ffmpeg.run(
        '-i',
        inputFileName,
        '-c:v',
        'libx264', // 视频编码器
        '-c:a',
        'aac', // 音频编码器
        '-preset',
        'medium', // 编码速度预设
        '-crf',
        '23', // 质量系数
        '-movflags',
        '+faststart', // 优化网络播放
        outputFileName
      );

      // 读取输出文件
      const data = this.ffmpeg.FS('readFile', outputFileName);

      // 清理文件系统
      this.cleanupFiles([inputFileName, outputFileName]);

      return data;
    } catch (error) {
      this.cleanupFiles([`input.${inputType}`, 'output.mp4']);
      throw error;
    }
  }

  async transcodeVideoToMP4Url(remoteVideoUrl: string, inputType: string = 'avi'): Promise<string> {
    const data = await this.transcodeVideoToMP4(remoteVideoUrl, inputType);
    // 创建可播放的URL
    const blob = new Blob([data.buffer], { type: 'video/mp4' });
    return URL.createObjectURL(blob);
  }

  private cleanupFiles(fileNames: string[]): void {
    fileNames.forEach((fileName) => {
      try {
        this.ffmpeg.FS('unlink', fileName);
      } catch (error) {
        console.warn(`清理文件 ${fileName} 失败:`, error);
      }
    });
  }
}

一、FFmpeg 初始化失败: ReferenceError: SharedArrayBuffer is not defined

解决方法:

Vite 配置 (vite.config.js) : 增加

export default defineConfig({
    server: {
      // ffmpeg需要跨域隔离
      headers: {
        'Cross-Origin-Opener-Policy': 'same-origin',
        'Cross-Origin-Embedder-Policy': 'credentialless' // 无需第三方配合
      }
    }
  })

这个只能本地运行起作用

如果发到线上需要配置nginx

nginx

server {
    
    location / {
        # 启用跨域隔离
        add_header Cross-Origin-Opener-Policy same-origin;
        add_header Cross-Origin-Embedder-Policy credentialless;
        
        # 其他配置...
    }
}

'Cross-Origin-Embedder-Policy': 'credentialless'设置成credentialless才能不影响到第三方的,否则第三方的图片等资源无法播放