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

devtools/2024/12/23 2:15:01/

一、效果图

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/devtools/31475.html

相关文章

机器学习决策树模型

决策树模型从单一到集成 【机器学习】决策树&#xff08;上&#xff09;——ID3、C4.5、CART&#xff08;非常详细&#xff09;【机器学习】决策树&#xff08;中&#xff09;——Random Forest、Adaboost、GBDT &#xff08;非常详细&#xff09;【机器学习】决策树&#xff…

排序算法--直接选择排序

前提&#xff1a; 选择排序&#xff1a;选择排序(Selection sort)是一种比较简单的排序算法。它的算法思想是每一次从待排序的数据元素中选出最小(或最大)的一个元素&#xff0c;存放在序列的起始位置&#xff0c;直到全部待排序的数据元素排完。 话不多说&#xff0c;直接放图…

uboot-网络配置

文章目录 一、网络简介二、修改PHY芯片地址三、删除 uboot 中 74LV595 的驱动代码1.删除宏定义&#xff0c;添加ENET1和ENET2复位引脚&#xff0c;宏定义2.删除内容如下 四、添加 I.MX6U-ALPHA 开发板网络复位引脚驱动 一、网络简介 &#x1f4a6;I.MX6UL/ULL 内部有个以太网 …

Mac 版 安装NVM

优质博文IT-BLOG-CN NVM&#xff08;Node Version Manager&#xff09;是一个用于管理多个Node.js版本的工具。它允许开发者在同一台机器上安装和切换不同版本的Node.js&#xff0c;以便在不同的项目中使用不同的Node.js版本。macOS用户可以使用homebrew来安装NVM。 一、安装h…

20240503安装HEVC解码器播放H265格式的8K视频

20240503安装HEVC解码器播放H265格式的8K视频 2024/5/3 9:55 缘起&#xff1a;由于youtube支持8K视频了&#xff0c;想尝尝鲜&#xff01; 主摄像头当然是选择SONY的【夜摄/弱光场景】&#xff0c;根据优选&#xff0c;小米&#xff08;MI&#xff09;13Ultra 最佳了。 在开始播…

React Native支持Tailwind CSS 语法

React Native支持Tailwind CSS 语法 一、前沿背景 回想下我们平时按照官方的规范进行书写样式是什么样&#xff1f; 是像下面这样&#xff1a; const MyComponent () > {return (<View><Text style{{ fontSize: 20 }}>开发者演示专用</Text></View…

ssh远程访问windows系统下的jupyterlab

网上配置这一堆那一堆&#xff0c;特别乱&#xff0c;找了好久整理后发在这里 由于既想打游戏又想做深度学习&#xff0c;不舍得显卡性能白白消耗&#xff0c;这里尝试使用笔记本连接主机 OpenSSH 最初是为 Linux 系统开发的&#xff0c;现在也支持包括 Windows 和 macOS 在内…

使用OpenCV分割彩色图像

使用OpenCV分割彩色图像 算法原理效果代码 算法原理 色彩空间转换 使用cv.cvtColor函数将输入的图像从BGR&#xff08;蓝绿红&#xff09;色彩空间转换到HSV色彩空间。HSV色彩空间是一种更符合人眼对颜色感知的模型&#xff0c;其中H&#xff08;Hue&#xff09;表示色调&…