一、效果图
flutter实现选择图片视频上传到oss和图片视频的预览功
二、所需要的依赖
image_picker: ^1.1.0 //选择图片
flutter_oss_aliyun: ^6.4.1 //图片上传到阿里云oss
uuid: ^4.4.0 //生成唯一uuid
interactiveviewer_gallery: ^0.6.0 //图片视频预览
cached_network_image: ^3.3.1 //缓存网络图片,避免多次请求
video_thumbnail: ^0.5.3 //视频生成缩略图
video_player: ^2.8.6 //视频播放
三、主要源码解析
1、初始化oss,参考 flutter_oss_aliyun库
c
initUploadOss() {
Auth authGetter() {
return Auth(
accessKey: "",
accessSecret: '',
expire: '2024-05-23T13:26:58Z',
secureToken:'',
);
}
Client.init(
ossEndpoint: ProjectConfig.ossEndpoint,
bucketName: ProjectConfig.bucketName,
authGetter: authGetter);
}
2、选择上传图片视频方式
c
List<Map<String, dynamic>> selectPictureMethodDicData = [
{
"label": "选择照片",
"value": "0",
},
{
"label": "拍照",
"value": "1",
},
{
"label": "选择视频",
"value": "2",
},
{
"label": "拍视频",
"value": "3",
},
{
"label": "选择单张照片/视频",
"value": "4",
},
{
"label": "选择多张照片或者视频",
"value": "5",
},
];
3、定义MedaiModel类
c
import 'dart:typed_data';
class MedaiModel {
int process;
String ossUrl;
final String localUrl;
final Uint8List bytes;
final String extension;
final String id;
final String thumbnailPath;
final String sourceType;
MedaiModel({
required this.process,
required this.ossUrl,
required this.localUrl,
required this.bytes,
required this.extension,
required this.id,
required this.thumbnailPath,
required this.sourceType,
});
factory MedaiModel.fromJson(Map<String, dynamic> json) {
return MedaiModel(
process: json["process"] ?? 0,
ossUrl: json["ossUrl"] ?? "",
localUrl: json["localUrl"] ?? "",
bytes: json["bytes"] ?? Uint8List(0),
extension: json["extension"] ?? "",
id: json["id"] ?? "",
thumbnailPath: json["thumbnailPath"] ?? "",
sourceType: json["sourceType"] ?? "",
);
}
Map<String, dynamic> toJson() {
return {
'process': process,
'ossUrl': ossUrl,
'localUrl': localUrl,
'bytes': bytes,
'extension': extension,
'id': id,
'thumbnailPath': thumbnailPath,
'sourceType': sourceType,
};
}
}
4、根据选择视频方式产品图片视频资源
c
Future selectMedia(String value, RxList<dynamic>? filesController,
{bool isMultiple = false,
int fileSize = 0,
int limit = 1,
int imageQuality = 90,
Duration? maxDuration,
bool isMedia = false}) async {
final ImagePicker picker = ImagePicker();
ImageSource imageSource = ImageSource.gallery;
List<XFile>? files = [];
if (value == "0" || value == "2") {
//相册
imageSource = ImageSource.gallery;
} else if (value == "1" || value == "3") {
//相机
imageSource = ImageSource.camera;
}
if (value == "0" || value == "1") {
//照片
if (isMultiple) {
final List<XFile> images =
await picker.pickMultiImage(imageQuality: imageQuality, limit: limit);
files.addAll(images);
} else {
final XFile? image = await picker.pickImage(
source: imageSource, imageQuality: imageQuality);
if (image != null) {
files.add(image);
}
}
} else if (value == "2" || value == "3") {
//视频
final XFile? image =
await picker.pickVideo(source: imageSource, maxDuration: maxDuration);
if (image != null) {
files.add(image);
}
}
if (value == "4") {
final XFile? media = await picker.pickMedia(imageQuality: imageQuality);
if (media != null) {
files.add(media);
}
} else if (value == "5") {
final List<XFile> medias = await picker.pickMultipleMedia(
imageQuality: imageQuality, limit: limit);
if (medias.length > 1) {
files.addAll(medias);
}
}
for (var file in files) {
bool isUpload = true;
Uint8List? bytes = await file.readAsBytes();
if (fileSize > 0) {
int? byte = bytes.lengthInBytes;
if (byte / 1024 / 1024 > fileSize) {
EasyLoading.showToast("资源大小应小于${fileSize}M");
isUpload = false;
}
}
if (isUpload) {
int indexOf = file.path.lastIndexOf(".");
String extension = file.path.substring(indexOf + 1);
String sourceType = getSourceType(file.path);
String thumbnailPath = file.path;
if (sourceType == 'video') {
thumbnailPath = await getVideoThumbnail(file.path);
}
MedaiModel medaiModel = MedaiModel.fromJson({
"uploadProcess": 0,
"ossUrl": "",
"localUrl": file.path,
"bytes": bytes,
"extension": extension,
"id": Uuid().v1(),
"thumbnailPath": thumbnailPath,
"sourceType": sourceType,
});
filesController!.add(medaiModel);
}
}
for (int i = 0; i < filesController!.length; i++) {
MedaiModel file = filesController[i];
if (file.ossUrl == "") {
String uploadPath =
'public/upload-test/${getNowDate()}/${Uuid().v1()}.${file.extension}';
try {
await Client().putObject(
file.bytes,
uploadPath,
option: PutRequestOption(
onSendProgress: (int sent, int total) {
file.process = ((sent / total) * 100).toInt();
print("上传的进度${file.process}");
filesController.refresh(); //不添加此句,会在下一次才更新
},
),
);
file.ossUrl =
"https://${ProjectConfig.bucketName}.${ProjectConfig.ossEndpoint}/$uploadPath";
} catch (e) {
print("上传失败$e");
}
}
}
}
5、判断资源类型
c
String getSourceType(String path) {
String ossPath = path.split("?")[0];
int index = ossPath.lastIndexOf(".");
String typeStr = ossPath.substring(index + 1).toUpperCase();
List<String> imagesList = ["BMP", "JPG", "JPEG", "PNG", "GIF"];
List<String> videoList = ["AVI", "WMV", "MPG", "MPEG", "MOV", "MP4"];
if (imagesList.contains(typeStr)) {
return "image";
} else if (videoList.contains(typeStr)) {
return "video";
} else {
return "image";
}
}
6、获取视频缩略图
c
Future<String> getVideoThumbnail(String path) async {
String? thumbnailPath = await VideoThumbnail.thumbnailFile(
video: path, imageFormat: ImageFormat.JPEG, maxWidth: 128, quality: 25);
return thumbnailPath!;
}
7、获取资源显示的视图
c
Widget getSourceView(MedaiModel source,
{double width = 100, double height = 100}) {
if (source.sourceType == 'video') {
return Image.file(File(source.thumbnailPath),
width: width, height: height, fit: BoxFit.cover);
} else {
if (source.localUrl != '') {
return Image.file(File(source.localUrl),
width: width, height: height, fit: BoxFit.cover);
} else {
return CachedNetworkImage(
imageUrl: source.ossUrl,
width: width,
height: height,
fit: BoxFit.cover);
}
}
}
8、打开预览图片
c
void openGallery(List<MedaiModel> sourceList, MedaiModel source) {
int initIndex = 0;
for (int i = 0; i < sourceList.length; i++) {
if (sourceList[i].id == source.id) {
initIndex = i;
break;
}
}
Navigator.of(Get.context!).push(
HeroDialogRoute<void>(
builder: (BuildContext context) => DisplayGesture(
child: InteractiveviewerGallery<dynamic>(
sources: sourceList,
initIndex: initIndex,
itemBuilder: (BuildContext context, int index, bool isFocus) {
MedaiModel sourceEntity = sourceList[index];
if (sourceEntity.sourceType == 'video') {
return PreviewVideo(
sourceEntity,
isFocus: isFocus,
);
} else {
return PreviewImage(sourceEntity);
}
},
onPageChanged: (int pageIndex) {
print("nell-pageIndex:$pageIndex");
},
),
),
),
);
}
9、预览图片视图
c
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:company_manage_flutter/model/mediaModel.dart';
import 'package:flutter/material.dart';
class PreviewImage extends StatefulWidget {
MedaiModel source;
PreviewImage(this.source);
@override
State<PreviewImage> createState() => _PreviewImageState();
}
class _PreviewImageState extends State<PreviewImage> {
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => Navigator.of(context).pop(),
child: Center(
child: Hero(
tag: widget.source.id,
child: widget.source.localUrl == ''
? CachedNetworkImage(
imageUrl: widget.source.ossUrl,
fit: BoxFit.contain,
)
: Image.file(
File(widget.source.localUrl),
fit: BoxFit.contain,
),
),
),
);
}
}
10、预览视频视图
c
import 'dart:io';
import 'package:company_manage_flutter/model/mediaModel.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class PreviewVideo extends StatefulWidget {
final MedaiModel source;
final bool? isFocus;
PreviewVideo(this.source, {this.isFocus});
@override
State<PreviewVideo> createState() => _PreviewVideoState();
}
class _PreviewVideoState extends State<PreviewVideo> {
VideoPlayerController? _controller;
late VoidCallback listener;
_PreviewVideoState() {
listener = () {
if (!mounted) {
return;
}
setState(() {});
};
}
@override
void initState() {
super.initState();
init();
}
init() async {
if (widget.source.localUrl == '') {
_controller =
VideoPlayerController.networkUrl(Uri.parse(widget.source.ossUrl));
} else {
_controller =
VideoPlayerController.file(File(widget.source.localUrl));
}
// loop play
// _controller!.setLooping(true);
await _controller!.initialize();
setState(() {});
_controller!.addListener(listener);
}
@override
void dispose() {
super.dispose();
_controller!.removeListener(listener);
_controller?.pause();
_controller?.dispose();
}
@override
void didUpdateWidget(covariant PreviewVideo oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.isFocus! && !widget.isFocus!) {
// pause
_controller?.pause();
}
}
@override
Widget build(BuildContext context) {
return _controller!.value.isInitialized
? Stack(
alignment: Alignment.center,
children: [
GestureDetector(
onTap: () {
setState(() {
_controller!.value.isPlaying
? _controller!.pause()
: _controller!.play();
});
},
child: Hero(
tag: widget.source.id,
child: AspectRatio(
aspectRatio: _controller!.value.aspectRatio,
child: VideoPlayer(_controller!),
),
),
),
_controller!.value.isPlaying == true
? SizedBox()
: IgnorePointer(
ignoring: true,
child: Icon(
Icons.play_arrow,
size: 100,
color: Colors.red,
),
),
],
)
: Theme(
data: ThemeData(
cupertinoOverrideTheme:
CupertinoThemeData(brightness: Brightness.dark)),
child: CupertinoActivityIndicator(radius: 30));
}
}