Commit dba5d2f6 by tanghuan

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

1 parent 7b82321d
...@@ -415,10 +415,11 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver { ...@@ -415,10 +415,11 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
void sendUploadStartResponse(String unique, String uploadId) { void sendUploadStartResponse(String unique, String uploadId) {
var resp = { var resp = {
'unique': unique, 'unique': unique,
'cmd': 'uploadFile', 'cmd': 'uploadStart',
'data': { 'data': {
'uploadId': uploadId, 'uploadId': uploadId,
'status': 1, 'status': 1,
'percent': 0,
'totalPart': 0, 'totalPart': 0,
'sendedPart': 0, 'sendedPart': 0,
'totalByte': 0, 'totalByte': 0,
...@@ -437,13 +438,14 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver { ...@@ -437,13 +438,14 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
/// [totalByte] 总字节数 /// [totalByte] 总字节数
/// [sendedByte] 已上传字节数 /// [sendedByte] 已上传字节数
void sendUploadProgress( 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 = { var resp = {
'unique': unique, 'unique': unique,
'cmd': 'uploadFileProgress', 'cmd': 'uploadProgress',
'data': { 'data': {
'uploadId': uploadId, 'uploadId': uploadId,
'status': 2, 'status': status,
'percent': percent,
'totalPart': totalPart, 'totalPart': totalPart,
'sendedPart': sendedPart, 'sendedPart': sendedPart,
'totalByte': totalByte, 'totalByte': totalByte,
...@@ -457,7 +459,7 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver { ...@@ -457,7 +459,7 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
void sendUploadEnd(String unique, String uploadId, String url) { void sendUploadEnd(String unique, String uploadId, String url) {
var resp = { var resp = {
'unique': '', 'unique': '',
'cmd': 'uploadFileEnd', 'cmd': 'uploadEnd',
'data': { 'data': {
'uploadId': uploadId, 'uploadId': uploadId,
'url': url, 'url': url,
......
...@@ -122,9 +122,30 @@ class UploadStartHandler extends MessageHandler { ...@@ -122,9 +122,30 @@ class UploadStartHandler extends MessageHandler {
bool success = false; bool success = false;
var startTime = DateTime.now(); var startTime = DateTime.now();
if (mimeType != 'video/mp4') { 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 { } 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(); var endTime = DateTime.now();
debugPrint('====================>压缩耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒'); debugPrint('====================>压缩耗时:${endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch} 毫秒');
...@@ -251,6 +272,8 @@ class UploadStartHandler extends MessageHandler { ...@@ -251,6 +272,8 @@ class UploadStartHandler extends MessageHandler {
_webCubit?.sendUploadProgress( _webCubit?.sendUploadProgress(
_cmdUnique, _cmdUnique,
_cmdUploadId, _cmdUploadId,
2,
((_cmdUploadedChunks / _cmdTotalChunks) * 100).floor(),
_cmdTotalChunks, _cmdTotalChunks,
_cmdUploadedChunks, _cmdUploadedChunks,
_cmdTotalByte, _cmdTotalByte,
......
import 'dart:io'; import 'dart:io';
import 'dart:async';
import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart'; import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_new/return_code.dart'; import 'package:ffmpeg_kit_flutter_new/return_code.dart';
import 'package:ffmpeg_kit_flutter_new/statistics.dart';
class VideoUtil { class VideoUtil {
/// ///
/// 将视频格式转换为mp4 /// 将视频格式转换为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; String cmd;
if (Platform.isIOS) { if (Platform.isIOS) {
cmd = '-i "$inputPath" ' cmd = '-i "$inputPath" '
...@@ -29,15 +39,40 @@ class VideoUtil { ...@@ -29,15 +39,40 @@ class VideoUtil {
'-f mp4 ' // 指定输出格式为MP4 '-f mp4 ' // 指定输出格式为MP4
'"$outputPath"'; // 指定输出文件路径 '"$outputPath"'; // 指定输出文件路径
} }
final session = await FFmpegKit.execute(cmd);
final returnCode = await session.getReturnCode(); final completer = Completer<bool>();
return ReturnCode.isSuccess(returnCode);
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 压缩视频 /// 通过 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模式进行压缩,值范围0-51,建议值18-28
// 高质量: CRF 18-20 // 高质量: CRF 18-20
// 中等质量: CRF 23-26 // 中等质量: CRF 23-26
...@@ -64,9 +99,45 @@ class VideoUtil { ...@@ -64,9 +99,45 @@ class VideoUtil {
'-preset medium ' // 编码预设 '-preset medium ' // 编码预设
'-movflags faststart ' // 优化MP4文件结构 '-movflags faststart ' // 优化MP4文件结构
'"$outputPath"'; // 输出文件 '"$outputPath"'; // 输出文件
final session = await FFmpegKit.execute(cmd);
final returnCode = await session.getReturnCode(); final completer = Completer<bool>();
return ReturnCode.isSuccess(returnCode);
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!