使用Flutter FFmpeg合并相册视频失败及相关问题咨询
问题解答
一、视频路径后缀显示为jpeg的原因
通过LecleFlutterAbsolutePath获取到的路径后缀为jpeg,但实际是视频文件,核心原因是:
- 该插件处理媒体URI时,会将文件临时复制到应用缓存目录,且自动生成的临时文件名后缀不匹配文件实际格式,属于插件命名逻辑的问题,和文件本身的内容无关。
解决方式:
- 无需纠结后缀:FFmpeg会根据文件实际编码识别内容,不受后缀名影响,即使后缀是jpeg,只要文件是视频格式就能正常处理。
- 手动修正后缀:如果担心后续逻辑受影响,可获取路径后替换后缀:
String correctedV1Path = v1Path.replaceAll(RegExp(r'\.[^.]+$'), '.mp4'); - 替换路径获取方式:放弃使用
LecleFlutterAbsolutePath,直接通过Android的MediaStore查询原视频真实路径,避免插件的命名bug。
二、FFmpeg报错及相册显示输出视频的解决方案
1. FFmpeg报错error 1的排查与修复
报错属于通用执行失败,常见原因及解决:
- 视频参数不兼容:
concat滤镜要求两段视频的分辨率、帧率、编码格式、像素格式完全一致,否则会失败。建议改用兼容性更好的concat demuxer方式:// 先创建文本文件列出视频路径 final tempDir = await getTemporaryDirectory(); final listFile = File('${tempDir.path}/video_list.txt'); await listFile.writeAsString('file \'$v1\'\nfile \'$v2\''); // 使用demuxer命令 final command = '-f concat -safe 0 -i ${listFile.path} -c copy $outputPath'; - 命令转义问题:原命令中的单引号在Dart字符串里易引发解析错误,可直接移除内部单引号:
final command = '-i $v1 -i $v2 -filter_complex [0:0][1:0]concat=n=2:v=1:a=0[out] -map [out] $outputPath'; - 权限缺失:若输出到公共目录,需确保应用申请了
WRITE_EXTERNAL_STORAGE权限(Android 10+可改用MediaStore或MANAGE_EXTERNAL_STORAGE权限)。 - 查看详细日志:通过以下代码获取FFmpeg执行日志,定位具体错误:
final rc = await FFmpegKit.execute(command); final logs = await rc.getAllLogsAsString(); print("FFmpeg日志:$logs");
2. 设置输出路径让视频在相册显示
应用私有目录(getApplicationDocumentsDirectory())的文件不会被系统媒体扫描器索引,因此相册无法识别。需将输出文件放到公共视频目录,并通知系统扫描:
步骤1:获取公共视频目录路径
import 'package:path_provider/path_provider.dart'; final externalVideoDir = await getExternalStoragePublicDirectory(DirectoryMovies); final outputDir = Directory('${externalVideoDir.path}/MyMergedVideos'); await outputDir.create(recursive: true); // 生成唯一文件名避免覆盖 final outputPath = '${outputDir.path}/merged_video_${DateTime.now().millisecondsSinceEpoch}.mp4';
步骤2:通知系统扫描新文件
生成视频后,需触发系统媒体扫描器,让相册识别该文件:
import 'package:flutter/services.dart'; // 定义MethodChannel调用原生扫描逻辑 void notifyMediaScanner(String filePath) { const MethodChannel channel = MethodChannel('com.example.media_scanner'); channel.invokeMethod('scanFile', {'path': filePath}); }
同时在Android原生代码中实现扫描逻辑(MainActivity.kt):
class MainActivity : FlutterActivity() { private val CHANNEL = "com.example.media_scanner" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "scanFile") { val path = call.argument<String>("path") ?: run { result.error("INVALID_PATH", "路径为空", null) return@setMethodCallHandler } MediaScannerConnection.connect(this, object : MediaScannerConnection.MediaScannerConnectionClient { override fun onMediaScannerConnected() { MediaScannerConnection.scanFile(this@MainActivity, arrayOf(path), null) { _, _ -> MediaScannerConnection.disconnect() result.success(null) } } override fun onScanCompleted(p0: String?, p1: Uri?) {} }) } else { result.notImplemented() } } } }
3. 修正merge方法逻辑
原Video.merge方法忽略了传入的output参数,直接使用私有目录,需调整为使用公共路径:
class Video { static Future<void> merge(String v1, String v2) async { // 获取公共视频目录 final externalVideoDir = await getExternalStoragePublicDirectory(DirectoryMovies); final outputDir = Directory('${externalVideoDir.path}/MyMergedVideos'); await outputDir.create(recursive: true); final outputPath = '${outputDir.path}/merged_${DateTime.now().millisecondsSinceEpoch}.mp4'; // 使用concat demuxer合并 final tempDir = await getTemporaryDirectory(); final listFile = File('${tempDir.path}/video_list.txt'); await listFile.writeAsString('file \'$v1\'\nfile \'$v2\''); final command = '-f concat -safe 0 -i ${listFile.path} -c copy $outputPath'; try { final rc = await FFmpegKit.execute(command); final returnCode = await rc.getReturnCode(); if (ReturnCode.isSuccess(returnCode)) { print("视频合并成功"); notifyMediaScanner(outputPath); // 通知系统扫描 } else { final logs = await rc.getAllLogsAsString(); print("合并失败,日志:$logs"); } } catch (e) { print("合并异常:$e"); } } }
内容的提问来源于stack exchange,提问作者Stéphane de Luca
相关产品推荐
相关产品推荐

