flutter实现选择图片视频上传到oss和图片视频的预览功能

embedded/2024/10/11 1:20:41/

一、效果图

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库

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、选择上传图片视频方式

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类

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、根据选择视频方式产品图片视频资源

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、判断资源类型

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、获取视频缩略图

Future<String> getVideoThumbnail(String path) async {String? thumbnailPath = await VideoThumbnail.thumbnailFile(video: path, imageFormat: ImageFormat.JPEG, maxWidth: 128, quality: 25);return thumbnailPath!;
}

7、获取资源显示的视图

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、打开预览图片

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、预览图片视图

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);@overrideState<PreviewImage> createState() => _PreviewImageState();
}class _PreviewImageState extends State<PreviewImage> {@overrideWidget 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、预览视频视图

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});@overrideState<PreviewVideo> createState() => _PreviewVideoState();
}class _PreviewVideoState extends State<PreviewVideo> {VideoPlayerController? _controller;late VoidCallback listener;_PreviewVideoState() {listener = () {if (!mounted) {return;}setState(() {});};}@overridevoid 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);}@overridevoid dispose() {super.dispose();_controller!.removeListener(listener);_controller?.pause();_controller?.dispose();}@overridevoid didUpdateWidget(covariant PreviewVideo oldWidget) {super.didUpdateWidget(oldWidget);if (oldWidget.isFocus! && !widget.isFocus!) {// pause_controller?.pause();}}@overrideWidget 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));}
}

http://www.ppmy.cn/embedded/26297.html

相关文章

【记录】Python3| 将 PDF 转换成 HTML/XML(✅⭐pdfminer.six)

本文将会被汇总至 【记录】Python3&#xff5c;2024年 PDF 转 XML 或 HTML 的第三方库的使用方式、测评过程以及对比结果&#xff08;汇总&#xff09;&#xff0c;更多其他工具请访问该文章查看。 注意&#xff01;pdfminer.six 和 pdfminer3k 不是同一个&#xff01;&#xf…

IP 端口号

IP && 端口号 一: IP二:端口号2.1:知名端口号2.2:端口号的重复问题业务端口:管理端口调试端口 2.3:如何确认端口号是否被其他进程占用??? 一: IP IP地址是网络层提供的概念,通过IP地址我们可以确定主机. 二:端口号 端口号是传输层提供的概念 一个端口号对应一个进…

【Linux】进程地址空间

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

ubuntu下安装配置python3.11

方案1 添加仓库&#xff1a; $ sudo add-apt-repository ppa:deadsnakes/ppa $ sudo apt update $ sudo apt install python3.11然后查看有多少个python版本已经安装了&#xff1a; ls -l /usr/bin/python*python2.7,python 3.8 ,python 3.11. 然后&#xff0c;设置系统默认…

图片怎样变小尺寸?一键修改图片大小的方法

不管是平时的工作还是学习中&#xff0c;我们经常需要根据不同的需求调整图片尺寸&#xff0c;无论是在社交媒体上分享照片&#xff0c;还是在网页设计中使用图片。对于一些电脑小白来说修改图片尺寸可能会变得有些困难。但是现在有许多在线工具可以帮助我们简单快速地将图片改…

TCP/IP和HTTP协议

TCP/IP OSI 七层模型在提出时的出发点是基于标准化的考虑&#xff0c;而没有考虑到具体的市场需求&#xff0c;使得该模型结构复杂&#xff0c;部分功能冗余&#xff0c;因而完全实现 OSI 参考模型的系统不多。而 TCP/IP 参考模型直接面向市场需求&#xff0c;实现起来也比较…

互联网的路由选择协议

一、内部网关协议RIP &#xff08;1&#xff09;概述 RIP是一种分布式的、基于距离向量的路由选择协议。 RIP认为一个好的路由就是它通过的路由器的数目少&#xff0c;即“距离短”RIP允许一条路径最多只能包含15个路由器 &#xff08;2&#xff09;RIP的特点 和谁交换信息…

MySQL基础学习(待整理)

MySQL 简介 学习路径 MySQL 安装 卸载预安装的mariadb rpm -qa | grep mariadb rpm -e --nodeps mariadb-libs安装网络工具 yum -y install net-tools yum -y install libaio下载rpm-bundle.tar安装包&#xff0c;并解压&#xff0c;使用rpm进行安装 rpm -ivh \ mysql-communi…