Commit dba5d2f6 by tanghuan

增加视频转码和压缩的进度

1 parent 7b82321d
......@@ -415,10 +415,11 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
void sendUploadStartResponse(String unique, String uploadId) {
var resp = {
'unique': unique,
'cmd': 'uploadFile',
'cmd': 'uploadStart',
'data': {
'uploadId': uploadId,
'status': 1,
'percent': 0,
'totalPart': 0,
'sendedPart': 0,
'totalByte': 0,
......@@ -437,13 +438,14 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
/// [totalByte] 总字节数
/// [sendedByte] 已上传字节数
void sendUploadProgress(
String unique, String uploadId, int totalPart, int sendedPart, int totalByte, int sendedByte) {
String unique, String uploadId, int status, int percent, int totalPart, int sendedPart, int totalByte, int sendedByte) {
var resp = {
'unique': unique,
'cmd': 'uploadFileProgress',
'cmd': 'uploadProgress',
'data': {
'uploadId': uploadId,
'status': 2,
'status': status,
'percent': percent,
'totalPart': totalPart,
'sendedPart': sendedPart,
'totalByte': totalByte,
......@@ -457,7 +459,7 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
void sendUploadEnd(String unique, String uploadId, String url) {
var resp = {
'unique': '',
'cmd': 'uploadFileEnd',
'cmd': 'uploadEnd',
'data': {
'uploadId': uploadId,
'url': url,
......
......@@ -122,9 +122,30 @@ class UploadStartHandler extends MessageHandler {
bool success = false;
var startTime = DateTime.now();
if (mimeType != 'video/mp4') {
success = await VideoUtil.convertToMp4(inputPath, outputPath);
success = await VideoUtil.convertToMp4(
inputPath,
outputPath,
onProgress: (progress) {
// progress 范围 0 ~ 100
debugPrint('转码进度: $progress%');
/// 发送转码进度
_webCubit?.sendUploadProgress(_cmdUnique, _cmdUploadId, 1, progress, 0, 0, 0, 0);
},
);
} else {
success = await VideoUtil.compressVideo(inputPath, outputPath, 'low');
success = await VideoUtil.compressVideo(
inputPath,
outputPath,
'low',
onProgress: (progress) {
// progress 范围 0 ~ 100
debugPrint('压缩进度: $progress%');
/// 发送压缩进度
_webCubit?.sendUploadProgress(_cmdUnique, _cmdUploadId, 1, progress, 0, 0, 0, 0);
},
);
}
var endTime = DateTime.now();
debugPrint('====================>压缩耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒');
......@@ -251,6 +272,8 @@ class UploadStartHandler extends MessageHandler {
_webCubit?.sendUploadProgress(
_cmdUnique,
_cmdUploadId,
2,
((_cmdUploadedChunks / _cmdTotalChunks) * 100).floor(),
_cmdTotalChunks,
_cmdUploadedChunks,
_cmdTotalByte,
......
import 'dart:io';
import 'dart:async';
import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
import 'package:ffmpeg_kit_flutter_new/statistics.dart';
class VideoUtil {
///
/// 将视频格式转换为mp4
/// 转码的同时,进行压缩
/// [onProgress] 进度回调,值范围 0.0 ~ 1.0
///
static Future<bool> convertToMp4(String inputPath, String outputPath) async {
static Future<bool> convertToMp4(
String inputPath,
String outputPath, {
void Function(int progress)? onProgress,
}) async {
// 先获取视频总时长(微秒)
final duration = await _getVideoDuration(inputPath);
String cmd;
if (Platform.isIOS) {
cmd = '-i "$inputPath" '
......@@ -29,15 +39,40 @@ class VideoUtil {
'-f mp4 ' // 指定输出格式为MP4
'"$outputPath"'; // 指定输出文件路径
}
final session = await FFmpegKit.execute(cmd);
final returnCode = await session.getReturnCode();
return ReturnCode.isSuccess(returnCode);
final completer = Completer<bool>();
FFmpegKit.executeAsync(
cmd,
(session) async {
final returnCode = await session.getReturnCode();
completer.complete(ReturnCode.isSuccess(returnCode));
},
null,
(Statistics statistics) {
if (onProgress != null && duration > 0) {
final currentTime = statistics.getTime();
final progress = (currentTime / duration).clamp(0.0, 1.0);
onProgress((progress * 100).floor());
}
},
);
return completer.future;
}
///
/// 通过 ffmpeg 压缩视频
/// [onProgress] 进度回调,值范围 0.0 ~ 1.0
///
static Future<bool> compressVideo(String inputPath, String outputPath, String quality) async {
static Future<bool> compressVideo(
String inputPath,
String outputPath,
String quality, {
void Function(int progress)? onProgress,
}) async {
final duration = await _getVideoDuration(inputPath);
// 使用CRF模式进行压缩,值范围0-51,建议值18-28
// 高质量: CRF 18-20
// 中等质量: CRF 23-26
......@@ -64,9 +99,45 @@ class VideoUtil {
'-preset medium ' // 编码预设
'-movflags faststart ' // 优化MP4文件结构
'"$outputPath"'; // 输出文件
final session = await FFmpegKit.execute(cmd);
final returnCode = await session.getReturnCode();
return ReturnCode.isSuccess(returnCode);
final completer = Completer<bool>();
FFmpegKit.executeAsync(
cmd,
(session) async {
final returnCode = await session.getReturnCode();
completer.complete(ReturnCode.isSuccess(returnCode));
},
null,
(Statistics statistics) {
if (onProgress != null && duration > 0) {
final currentTime = statistics.getTime();
final progress = (currentTime / duration).clamp(0.0, 1.0);
onProgress((progress * 100).floor());
}
},
);
return completer.future;
}
/// 获取视频总时长,返回毫秒
static Future<int> _getVideoDuration(String videoPath) async {
final session = await FFmpegKit.execute(
'-i "$videoPath"',
);
final output = await session.getOutput();
// 从 ffmpeg 输出中解析时长,格式如: Duration: 00:01:23.45
final regex = RegExp(r'Duration:\s*(\d+):(\d+):(\d+)\.(\d+)');
final match = regex.firstMatch(output ?? '');
if (match != null) {
final hours = int.parse(match.group(1)!);
final minutes = int.parse(match.group(2)!);
final seconds = int.parse(match.group(3)!);
final ms = int.parse(match.group(4)!);
return (hours * 3600 + minutes * 60 + seconds) * 1000 + ms * 10;
}
return 0;
}
/// 为视频文件生成缩略图
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!