封面图
播放视频
视频的校验
(大于1080p 即像素点大于 1920 * 1080 = 2073600 像素点 或者 视频的内存大小 大于50mb) 进行压缩
, 否则直接上传即可.视频的播放
.(这里包括一系列视频的操作方法
)video_thumbnail : 用于从视频文件中生成缩略图。
video_player : 是 Flutter 中用于播放视频
的重要库。它提供了一套完整的 API
来处理视频播放相关的功能,支持多种视频格式,能够在 Android 和 iOS 平台上实现流畅的视频播放体验
ideo_compress : 是一个在 Flutter 应用中用于视频压缩
的库。它帮助开发者方便地减小视频文件的大小,同时在一定程度上保持视频的质量,这在应用开发中对于优化存储、减少网络传输带宽
等场景非常有用
我们封装一个视频的工具类, 里面包含了一些对视频的操作的方法, 包括获取视频的大小
, 视频的像素
, 获取视频的封面图
...
video_utils
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
class VideoUtils {
}
视频的大小
传入视频的路径, 创建了一个
File
对象, 通过lengthSync()方法 拿到字节大小 ,然后 / 1024 / 1024 就可以得到 mb的大小.
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:video_player/video_player.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
class VideoUtils {
// 获取视频的大小(以 MB 为单位)
static Future<double> getVideoFileSize(String videoPath) async {
final File file = File(videoPath);
return file.lengthSync() / 1024 / 1024;
}
视频的像素大小
通过创建 VideoPlayerController 来初始化指定视频文件,获取其
宽度和高度
并相乘,最后释放控制器资源,以此实现获取视频像素大小(宽度乘高度)
的功能,函数定义为静态异步方法
,接收视频路径作为参数并返回像素大小的双精度值
。
// 获取视频的像素大小(宽度 * 高度)
static Future<double> getVideoPixelSize(String videoPath) async {
final VideoPlayerController _controller = VideoPlayerController.file(File(videoPath));
await _controller.initialize();
final double width = _controller.value.size.width;
final double height = _controller.value.size.height;
await _controller.dispose();
return width * height;
}
视频的封面
调用 VideoThumbnail.thumbnailFile 来基于指定
视频路径
、临时目录路径
、设定的图片格式(JPEG)
、最大高度(128)以及质量(75)等参数生成视频封面图
,并返回该封面图的路径
// 获取视频的封面图
static Future<String?> getVideoThumbnail(String videoPath) async {
final thumbnailPath = await VideoThumbnail.thumbnailFile(
video: videoPath,
thumbnailPath: (await getTemporaryDirectory()).path,
imageFormat: ImageFormat.JPEG,
maxHeight: 128, // 设置最大高度
quality: 75, // 设置质量
);
return thumbnailPath;
}
在这个类里面 我们定义了
是否需要压缩
,压缩视频
这两个静态方法.
compress
:这是一个异步方法,用于压缩指定路径的视频。
首先订阅视频压缩进度,在每次进度更新时,将进度赋值给_progress
变量,并且如果onProgress
回调函数不为空,则将进度传递给外部。
然后调用VideoCompress
类的compressVideo
方法来实际执行视频压缩操作,传入视频路径
、指定压缩质量
(VideoQuality.Res1280x720Quality
)以及是否删除原始视频
(此处为false
)等参数,
最后返回压缩后的视频相关信息(类型为MediaInfo
)
import 'package:chat/src/utils/video_uploader.dart';
import 'package:chat/src/utils/video_utils.dart';
import 'package:video_compress/video_compress.dart';
import 'dart:async';
class VideoCompressor {
// 是否压缩 (压缩前的校验)
// 视频像素大小 小于 720p || 视频大小 小于 50mb --> 压缩
static Future<bool> shouldCompress(String videoPath) async {
double p = await VideoUtils.getVideoPixelSize(videoPath);
double s = await VideoUtils.getVideoFileSize(videoPath);
if (p > VideoUploader.maxVideoPixel || s >= VideoUploader.maxVideoSize ) {
return true;
}else{
return false;
}
}
// 压缩视频的方法
Future<MediaInfo?> compress(String videoPath) async {
// 订阅压缩进度
_subscription = VideoCompress.compressProgress$.subscribe((progress) {
_progress = progress;
// 回调进度给外部
if (onProgress != null) {
onProgress!(progress);
}
});
MediaInfo? info = await VideoCompress.compressVideo(
videoPath,
quality: VideoQuality.Res1280x720Quality,
deleteOrigin: false,
);
return info;
}
// 释放资源
void dispose() {
_subscription.unsubscribe(); // 取消订阅
}
}
VideoCompressor.shouldCompress
方法判断是否需要压缩, 他会返回一个bool值.
videoCompressor.compress(path)
进行压缩 并返回压缩后的文件.
static Future<String> compressAndUploadVideo(String path) async {
bool shouldCompress = await VideoCompressor.shouldCompress(path);
if (shouldCompress) {
VideoCompressor videoCompressor = VideoCompressor(onProgress: (progress) {
// print('当前压缩进度: ${progress.toStringAsFixed(1)}%');
// 可以考虑对于进度进行一下展示
});
MediaInfo? info = await videoCompressor.compress(path); // 等待压缩,返回压缩后的信息
if (info != null){
final res = await VideoUploader.uploadVideoFile(info.file!);
videoCompressor.dispose();
return res;
} else {
throw Exception('Failed to compress video');
}
} else {
return await VideoUploader.uploadVideoFile(File(path));
}
}
late VideoPlayerController _videoController;
@override
void initState() {
// print("widget.videoPath: ${widget.videoPath}");
super.initState();
_videoController = VideoPlayerController.file(File(widget.videoPath))
..initialize().then((_) {
setState(() {});
_videoController.play();
}).catchError((error) {
print('Error initializing video: $error');
});
}
判断是否初始化完毕
_videoController.value.isInitialized
AspectRatio
组件来保持视频的原始宽高比,_controller.value.aspectRatio
获取视频的宽高比,VideoPlayer(_controller)
则用于显示视频。
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: _videoController.value.isInitialized
? GestureDetector(
onTap: () {
// 点击暂停/播放
if (_videoController.value.isPlaying) {
_videoController.pause();
} else {
_videoController.play();
}
setState(() {});
},
child: AspectRatio(
aspectRatio: _videoController.value.aspectRatio,
child: VideoPlayer(_videoController),
),
)
: CircularProgressIndicator(color: TColors.instance.primaryColor),
),
);
}
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}