Commit fac3e49d by tanghuan

优化

1 parent 0925b7fc
......@@ -194,14 +194,14 @@ class WebCubit extends Cubit<WebState> {
var versionConfig = await _getVersionConfig();
var correctVersion = versionConfig['version'] as String;
var downloadUrl = versionConfig['zip'] as String;
var urgency = versionConfig['urgency'] as int? ?? 0;
var force = versionConfig['force'] as String;
// 当前使用的H5版本
var curVersion = getIt.get<SharedPreferences>().getString('h5_version') ?? Constant.h5Version;
// 版本不一致则需要升级
if (curVersion != correctVersion) {
if (urgency == 1) {
if (force == "1") {
// 一直等待升级完成
// 遮罩界面
emit(state.copyWith(isUpgrading: true));
......@@ -256,11 +256,13 @@ class WebCubit extends Cubit<WebState> {
}
String version = response.data['version'] as String;
String force = response.data['force'] as String;
String zip = response.data['zip'] as String;
return {
'version': version,
'zip': 'http://192.168.2.177/1.0.0.zip',
// 'zip': zip,
'force': force,
// 'zip': 'http://192.168.2.177/1.0.0.zip',
'zip': '$zip$version.zip',
};
} finally {
dio.close(force: true);
......@@ -481,11 +483,8 @@ class WebCubit extends Cubit<WebState> {
_controller.reload();
}
///
/// 1 清理非 h5_version 的缓存
/// 2 清理文件目录和缓存目录(不包括解压的H5资源目录)
///
Future<void> clearStorage() async {
// 1 清理非 h5_version 的缓存
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.getKeys().forEach((key) {
if (!key.startsWith('h5_version')) {
......@@ -493,26 +492,34 @@ class WebCubit extends Cubit<WebState> {
}
});
var version = sharedPreferences.getString('h5_version') ?? Constant.h5Version;
// 2 清理 http_dist_assets 下的非当前版本号的文件和目录
var dir = await getApplicationSupportDirectory();
String httpDirPath = '${dir.path}/${Constant.h5DistDir}';
var httpDir = Directory(httpDirPath);
if (!httpDir.existsSync()) {
return;
var httpDir = Directory('${dir.path}/${Constant.h5DistDir}');
if (httpDir.existsSync()) {
var version = sharedPreferences.getString('h5_version') ?? Constant.h5Version;
await for (final FileSystemEntity entity in httpDir.list()) {
if (entity is Directory) {
// 删除目录
if (!entity.path.endsWith(version)) {
await entity.delete(recursive: true);
}
} else if (entity is File) {
// 删除文件
if (!entity.path.endsWith('$version.zip')) {
await entity.delete();
}
}
}
}
// 查询目录下的所有文件和目录
List<FileSystemEntity> entities = await Directory(httpDirPath).list().toList();
for (var entity in entities) {
if (entity is Directory) {
// 删除目录
if (!entity.path.endsWith(version)) {
// 3 清理临时目录下的所有文件和目录
var tempDir = await getTemporaryDirectory();
if (tempDir.existsSync()) {
await for (final FileSystemEntity entity in tempDir.list()) {
if (entity is Directory) {
await entity.delete(recursive: true);
}
} else if (entity is File) {
// 删除文件
if (!entity.path.endsWith('$version.zip')) {
} else {
await entity.delete();
}
}
......
import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/file_type_util.dart';
import 'package:appframe/utils/video_util.dart';
import 'package:dio/dio.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
......@@ -26,13 +27,7 @@ class CompressImageHandler extends MessageHandler {
throw Exception('参数错误');
}
var compressedWidth = params['compressedWidth'] as int?;
// if (compressedWidth == null) {
// throw Exception('参数错误');
// }
var compressedHeight = params['compressedHeight'] as int?;
// if (compressedHeight == null) {
// throw Exception('参数错误');
// }
if (compressedWidth == null && compressedHeight == null) {
throw Exception('参数错误');
......@@ -40,8 +35,8 @@ class CompressImageHandler extends MessageHandler {
if (compressedWidth == null) {
compressedWidth = compressedHeight;
} else if (compressedHeight == null) {
compressedHeight = compressedWidth;
} else {
compressedHeight ??= compressedWidth;
}
// 获取后缀名
......@@ -128,8 +123,19 @@ class CompressVideoHandler extends MessageHandler {
}
print('原视频大小:${originFile.lengthSync()}');
String? mimeType = await FileTypeUtil.getMimeType(originFile);
if (!(mimeType?.startsWith('video/') ?? false)) {
throw Exception('非视频文件');
}
final outputPath = '${tempDir.path}/${Uuid().v4()}.mp4';
var result = await VideoUtil.compressVideo(srcPath, outputPath, quality);
bool result;
if (mimeType != 'video/mp4') {
result = await VideoUtil.convertToMp4(srcPath, outputPath);
} else {
result = await VideoUtil.compressVideo(srcPath, outputPath, quality);
}
if (!result) {
throw Exception('视频压缩失败');
}
......@@ -139,32 +145,5 @@ class CompressVideoHandler extends MessageHandler {
"tempFilePath": '/temp$outputPath',
"size": File(outputPath).lengthSync(),
};
// VideoQuality videoQuality;
// switch (quality) {
// case 'low':
// videoQuality = VideoQuality.LowQuality;
// break;
// case 'middle':
// videoQuality = VideoQuality.MediumQuality;
// break;
// case 'high':
// videoQuality = VideoQuality.HighestQuality;
// break;
// default:
// throw Exception('参数错误');
// }
//
// final mediaInfo = await VideoCompress.compressVideo(
// srcPath,
// quality: videoQuality,
// deleteOrigin: false,
// includeAudio: true,
// );
//
// return {
// "tempFilePath": "/temp${mediaInfo!.path}",
// "size": mediaInfo.filesize,
// };
}
}
......@@ -3,9 +3,11 @@ import 'dart:io';
import 'package:appframe/services/dispatcher.dart';
import 'package:appframe/utils/file_type_util.dart';
import 'package:dio/dio.dart';
import 'package:ffmpeg_kit_flutter_new/ffprobe_kit.dart';
import 'package:ffmpeg_kit_flutter_new/media_information_session.dart';
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:video_compress/video_compress.dart';
class VideoInfoHandler extends MessageHandler {
@override
......@@ -15,8 +17,26 @@ class VideoInfoHandler extends MessageHandler {
}
final url = params['url'] as String;
final filePath = await _getFilePath(url);
final file = File(filePath);
if (!file.existsSync()) {
throw Exception('视频文件不存在');
}
// 使用 ffmpeg_kit_flutter_new 获取视频信息
final mediaInfoSession = await FFprobeKit.getMediaInformation(filePath);
final returnCode = await mediaInfoSession.getReturnCode();
if (!ReturnCode.isSuccess(returnCode)) {
throw Exception('获取视频信息失败');
}
final result = await _extractVideoInfo(mediaInfoSession, file, filePath);
return result;
}
String filePath;
/// 根据URL获取文件路径,如果URL是网络地址则下载到本地
Future<String> _getFilePath(String url) async {
if (url.startsWith('http')) {
// 获取后缀名
String ext = path.extension(url);
......@@ -30,18 +50,41 @@ class VideoInfoHandler extends MessageHandler {
throw Exception('文件下载失败');
}
filePath = targetPath;
return targetPath;
} else {
filePath = url;
return url;
}
}
final file = File(filePath);
if (!file.existsSync()) {
throw Exception('视频文件不存在');
/// 提取视频信息
Future<Map<String, dynamic>> _extractVideoInfo(
MediaInformationSession mediaInfoSession, File file, String filePath) async {
final mediaInformation = mediaInfoSession.getMediaInformation();
if (mediaInformation == null) {
throw Exception('获取视频信息失败');
}
// 获取视频时长
final durationStr = mediaInformation.getDuration();
if (durationStr == null) {
throw Exception('获取视频信息失败');
}
var duration = (double.tryParse(durationStr) ?? 0).ceil();
// 获取视频流信息
final videoStreams = mediaInformation
.getStreams()
.where((stream) => stream.getAllProperties() != null && stream.getAllProperties()!['codec_type'] == 'video')
.toList();
if (videoStreams.isEmpty) {
throw Exception('获取视频信息失败');
}
// 使用video_compress获取视频信息
final mediaInfo = await VideoCompress.getMediaInfo(filePath);
final videoStream = videoStreams[0];
final properties = videoStream.getAllProperties();
int width = properties!['width'] ?? 0;
int height = properties['height'] ?? 0;
// 获取文件大小
final size = await file.length();
......@@ -52,10 +95,10 @@ class VideoInfoHandler extends MessageHandler {
return {
'tempFilePath': '/temp$filePath',
'width': mediaInfo.width ?? 0,
'height': mediaInfo.height ?? 0,
'width': width,
'height': height,
'type': fileExtension,
'duration': (mediaInfo.duration ?? 0) / 1000, // 转换为秒
'duration': duration, // 已经是秒单位
'size': size,
};
}
......
import 'dart:io';
import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:video_compress/video_compress.dart';
/// 缩略图工具类
///
......@@ -28,28 +29,29 @@ class ThumbnailUtil {
/// 返回缩略图路径
static Future<String?> genVideoThumbnail(String videoPath, Directory dir) async {
try {
var fileThumbnail = await VideoCompress.getFileThumbnail(videoPath, quality: 50, position: -1);
return fileThumbnail.path;
final thumbnailPath = '${dir.path}/video_thumb_${DateTime.now().millisecondsSinceEpoch}.jpg';
// 使用 ffmpeg_kit_flutter_new 生成视频缩略图
// 构建FFmpeg命令行参数
String cmd = '-i "$videoPath" ' // 指定输入文件路径
'-ss 1 ' // 从视频第1秒处截取画面
'-vframes 1 ' // 只截取一帧画面
'-vf scale=128:-1 ' // 设置缩略图宽度为128像素,高度按比例缩放
'-y ' // 覆盖已存在的输出文件
'"$thumbnailPath"'; // 指定输出文件路径
final session = await FFmpegKit.execute(cmd);
final returnCode = await session.getReturnCode();
if (ReturnCode.isSuccess(returnCode)) {
return thumbnailPath;
} else {
print('生成视频缩略图失败: ${await session.getFailStackTrace()}');
return null;
}
} catch (e) {
print('生成视频缩略图出错: $e');
return null;
}
// try {
// final thumbnailPath = '${dir.path}/video_thumb_${DateTime.now().millisecondsSinceEpoch}.jpg';
//
// final thumbPath = await VideoThumbnail.thumbnailFile(
// video: videoPath,
// thumbnailPath: thumbnailPath,
// imageFormat: ImageFormat.JPEG,
// maxWidth: 128, // 缩略图最大宽度
// quality: 75, // 图片质量
// );
//
// return thumbPath;
// } catch (e) {
// print('生成视频缩略图出错: $e');
// return null;
// }
}
}
......@@ -9,25 +9,25 @@ class VideoUtil {
/// 转码的同时,进行压缩
///
static Future<bool> convertToMp4(String inputPath, String outputPath) async {
String cmd;
if(Platform.isIOS) {
// 构建命令
// 1. -c:v h264_videotoolbox : 启用 iOS 硬件加速
// 2. -b:v 1500k : 限制视频码率为 1.5Mbps (体积小,手机看足够)
// 3. -vf scale=1280:-2 : 缩放到 720p (保持比例)
// 4. -c:a aac : 音频转为 AAC (兼容性最好)
// 5. -b:a 128k : 音频码率
cmd =
'-i "$inputPath" '
'-c:v h264_videotoolbox -b:v 1500k '
'-vf scale=1280:-2 '
'-c:a aac -b:a 128k '
if (Platform.isIOS) {
cmd = '-i "$inputPath" '
'-c:v h264_videotoolbox ' // 启用 iOS 硬件加速
'-b:v 1500k ' // 限制视频码率为 1.5Mbps (体积小,手机看足够)
'-vf scale=1280:-2 ' // 缩放到 720p (保持比例)
'-c:a aac ' // 音频转为 AAC (兼容性最好)
'-b:a 128k ' // 音频码率
'"$outputPath"';
print("开始极速转码: $cmd");
} else {
cmd = '-i "$inputPath" -c:v libx264 -crf 28 -c:a aac -b:a 128k -strict experimental -movflags faststart -f mp4 "$outputPath"';
cmd = '-i "$inputPath" ' // 指定输入文件路径
'-c:v libx264 ' // 设置视频编码器为libx264(H.264)
'-crf 28 ' // 设置恒定速率因子CRF为28(中等压缩质量)
'-c:a aac ' // 设置音频编码器为AAC
'-b:a 128k ' // 设置音频比特率为128kbps
'-strict experimental ' // 允许使用实验性编解码器功能
'-movflags faststart ' // 优化MP4文件结构,使视频可以快速启动播放
'-f mp4 ' // 指定输出格式为MP4
'"$outputPath"'; // 指定输出文件路径
}
final session = await FFmpegKit.execute(cmd);
final returnCode = await session.getReturnCode();
......@@ -56,8 +56,15 @@ class VideoUtil {
default:
throw Exception('参数错误');
}
final session = await FFmpegKit.execute(
'-i "$inputPath" -c:v libx264 -crf $crf -c:a aac -b:a 128k -preset medium -movflags faststart "$outputPath"');
String cmd = '-i "$inputPath" ' // 输入文件
'-c:v libx264 ' // 视频编码器
'-crf $crf ' // 恒定速率因子(质量控制)
'-c:a aac ' // 音频编码器
'-b:a 128k ' // 音频比特率
'-preset medium ' // 编码预设
'-movflags faststart ' // 优化MP4文件结构
'"$outputPath"'; // 输出文件
final session = await FFmpegKit.execute(cmd);
final returnCode = await session.getReturnCode();
return ReturnCode.isSuccess(returnCode);
}
......
......@@ -37,8 +37,7 @@ dependencies:
flutter_bloc: ^9.1.1
flutter_localization: ^0.3.3
flutter_image_compress: ^2.4.0
ffmpeg_kit_flutter_new: ^4.1.0
# 建议:如果项目中不需要 image_picker(被 wechat_assets_picker 替代),则保持注释或删除
# image_picker: ^1.2.0
......@@ -55,8 +54,8 @@ dependencies:
# --- 音视频与直播 (重灾区) ---
# 确保 ffmpeg_kit 版本与你的架构兼容。
# 如果只是为了压缩视频,建议评估是否移除 video_compress,直接用 ffmpeg
#ffmpeg_kit_flutter_new: ^3.2.0
video_compress: ^3.1.4
ffmpeg_kit_flutter_new: ^4.1.0
# video_compress: ^3.1.4
video_player: ^2.10.0
# video_thumbnail 已被注释,确认是否需要生成缩略图,如果需要,ffmpeg_kit 也能做
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!