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

发布于:2024-05-01 ⋅ 阅读:(32) ⋅ 点赞:(0)

一、效果图

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);

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

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));
  }
}


网站公告

今日签到

点亮在社区的每一天
去签到