• Flutter高仿微信-第34篇-单聊-小视频


    Flutter高仿微信系列共59篇,从Flutter客户端、Kotlin客户端、Web服务器、数据库表结构、Xmpp即时通讯服务器、视频通话服务器、腾讯云服务器全面讲解。

     详情请查看

    效果图:

    详情请参考 Flutter高仿微信-第29篇-单聊 , 这里只是提取小视频的部分代码。

    实现代码:

    //打开相册权限
    void _openAblumPermission() async {
      bool isPhotosGranted = await Permission.photos.isGranted;
      bool isPhotosDenied = await Permission.photos.isDenied;
      if(isPhotosGranted){
        _openAblum();
      } else {
        if(isPhotosDenied){
          _openAblum();
        } else {
          //跳转到设置页面提示
          _showPhotosConfirmationAlert(context);
        }
      }
    }
    //打开相册选择小视频
    void _openAblum() {
      List selectedAssets = [];
      AssetPicker.pickAssets(
        context,
        pickerConfig: AssetPickerConfig(
          maxAssets: 1,
          selectedAssets: selectedAssets,
        ),
      ).then((imageList) {
        if(imageList == null){
          return;
        }
        imageList as List;
        for(int i = 0; i < imageList.length; i++){
          AssetEntity ae = imageList[i];
          ae.file.then((file) async {
            String resultFilePath = file?.path??"";
            _processVideoAndPicture(resultFilePath);
          });
        }
      });
    }

    //处理图片和小视频(相册、拍照)
    void _processVideoAndPicture(String resultFilePath) async {
    
      if(resultFilePath == null || "" == resultFilePath){
        return;
      }
    
      String messageId = UUID.getUUID();
    
      if(CommonUtils.isVideo(resultFilePath)){
        /**
         * 小视频发送流程,因为小视频比较大,压缩时间比较长。发送的视频,先本地优先显示,查看播放小视频
         * 1、先复制一份小视频
         * 2、生成缩略图显示
         * 3、压缩小视频
         * 4、删除复制的小视频
         */
        //_testmp4(resultFilePath);
    
        //最大100M的视频, 不是1024*1024*500 , 使用1000*100*500
        int maxSize = 100000000;
        int fileSize = File(resultFilePath).lengthSync();
        if(fileSize > maxSize){
          CommonToast.show(context, "上传视频大小不能超过100M", duration: 3);
          return ;
        }
    
        //小视频生成缩略图大概5秒左右,先刷新站位图
        widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, resultFilePath, "", 0, messageId);
    
        String videoFormat = await getVideoFormat(resultFilePath);
        File srcFile = File(resultFilePath);
        String newVideoFileName = await FileUtils.getBaseFile("new_${DateUtil.getNowDateMs()}.mp4");
        srcFile.copySync(newVideoFileName);
    
        String thumbnailFileName = await FileUtils.getBaseFile("thum_${DateUtil.getNowDateMs()}.png");
        //生成缩略图
        await VideoThumbnail.thumbnailFile(video: resultFilePath, thumbnailPath: thumbnailFileName);
        //获取视频时间
        int second = await getVideoTime(resultFilePath);
        //int size = File(resultFilePath).lengthSync();
        //先刷新
        widget.refreshMediaCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, resultFilePath, thumbnailFileName, second, messageId);
        //压缩完成再发送
    
        MediaInfo? mediaInfo = await CompressVideoUtils.compressVideo(newVideoFileName);
        String compressVideoPath = mediaInfo?.path??"";
    
        //int csecond = await getVideoTime(resultFilePath);
        //int csize = File(compressVideoPath).lengthSync();
    
        widget.sendMedialCallback(CommonUtils.CHAT_CONTENT_TYPE_VIDEO, compressVideoPath, second, messageId);
      }
    }

    //获取视频时间
    Future getVideoTime(String resultFilePath) async {
      int time = 0;
      final FlutterFFprobe _flutterFFprobe =  FlutterFFprobe();
      MediaInformation info = await _flutterFFprobe.getMediaInformation(resultFilePath);
      if (info.getStreams() != null) {
        String duration = info.getMediaProperties()?['duration'];
        String size = info.getMediaProperties()?['size'];
        double durationDouble = double.parse(duration);
        time = durationDouble.toInt();
        LogUtils.d("多媒体文件大小:${size}");
      }
      return time;
    }

    //刷新多媒体(图片、语音、小视频) (先刷新本地,然后小视频压缩完成再慢慢发送)
    void _refreshMedia(int type, String mediaURL, String thumbnailFileName, {int mediaSecond=0, String messageId = "" }) async {
    
      bool isNetwork = await CommonNetwork.isNetwork();
      if(!isNetwork) {
        CommonUtils.showNetworkError(context);
        return;
      }
    
      bool deleteContacts = await isDeleteContacts(widget.account, widget.toChatId);
      if(deleteContacts){
        WnBaseDialog.showAddFriendsDialog(context, widget.toChatId);
        return;
      }
    
      String addTime = WnDateUtils.getCurrentTime();
    
      //先刷新本地聊天
      ChatBean chatBean = ChatBean(fromAccount: widget.account, toAccount: widget.toChatId,addTime:addTime,messageId: messageId,isRead: 1);
    
    
      chatBean.contentType = type;
      if(type == CommonUtils.CHAT_CONTENT_TYPE_VIDEO){
        //小视频会刷新本地2次。但messageId都是一样的
        chatBean.videoLocal = mediaURL;
        chatBean.imgPathLocal = thumbnailFileName;
        chatBean.second = mediaSecond;
    
        ChatBean? localChatBean = await ChatRepository.getInstance().findChatByMessageId(messageId);
        //状态变更,向聊天记录中插入新记录
        if(localChatBean == null){
          items.add(chatBean);
          ChatRepository.getInstance().insertChat(chatBean);
        } else {
          chatBean.id = localChatBean.id;
          ChatRepository.getInstance().updateChat(chatBean);
          //如果已经存在,先删除在添加
          for(int i = 0; i < items.length; i++){
            ChatBean item = items[i];
            if(item.messageId == messageId){
              items.remove(item);
              break;
            }
          }
          items.add(chatBean);
        }
    
        setState(() {
    
        });
    
      }
    
    
      //LogUtils.d("滚动到底部3");
      jumpToBottom(100);
    }

    //发送多媒体(图片、语音、小视频)
    void _sendMedia(int type, String mediaURL, {int mediaSecond = 0, String messageId = ""}) async {
    
      bool isNetwork = await CommonNetwork.isNetwork();
      if(!isNetwork) {
        return;
      }
    
      bool deleteContacts = await isDeleteContacts(widget.account, widget.toChatId);
      if(deleteContacts){
        return;
      }
    
      //上传文件
      ChatBean serverChatBean;
      String message = "";
      ChatSendBean chatSendBean = ChatSendBean();
      chatSendBean.contentType = type;
      chatSendBean.messageId = messageId;
      chatSendBean.addTime = WnDateUtils.getCurrentTime();
      if(type == CommonUtils.CHAT_CONTENT_TYPE_VIDEO){
        //小视频
        serverChatBean = await UploadUtils.getInstance().uploadChatVideo(widget.account, widget.toChatId, mediaURL);
        message = "${type}${CommonUtils.CHAT_MESSAGE_SPILE}${serverChatBean.video}";
        chatSendBean.content = serverChatBean.video??"";
        chatSendBean.second = mediaSecond;
      } else {
        return ;
      }
      message = jsonEncode(chatSendBean);
      _sendMessage(message);
    }

    接收小视频:

    String serverVideoPath = CommonUtils.BASE_URL_UPLOAD + content;
    String localVideoPath = await FileUtils.getBaseFile("${DateUtil.getNowDateMs()}.mp4");
    //先下载小视频
    await DownloadUtils.getInstance().downloadFile(serverVideoPath, localVideoPath);
    //生成缩略图
    String? thumbnailFileName = await VideoThumbnail.thumbnailFile(video: localVideoPath);
    
    chatBean.video = serverVideoPath;
    chatBean.videoLocal = localVideoPath;
    chatBean.imgPathLocal = thumbnailFileName??"";
    chatBean.second = second;
    //删除服务器文件
    await HttpUtils.getInstance().deleteChatFile(content);
    

    //通知栏提示
    NotificationUtils.getInstance().showNotification(chatBean);
    //插入本地数据库
    ChatRepository.getInstance().insertChat(chatBean);
    //EventBus刷新页面
    eventBus.emit(chatBean);

    显示小视频缩略图:

    /**
     * Author : wangning
     * Email : maoning20080809@163.com
     * Date : 2022/11/16 0:42
     * Description : 单聊小视频缩略图, 已经存在缩略图图片,直接显示
     */
    class CommonThumbnailWidget extends StatefulWidget {
    
      final String image;
      final VoidCallback onPressed;
      final EdgeInsetsGeometry padding;
      //视频多少秒
      final int second;
      //是否隐藏秒数
      bool isHideSecond;
      double width;
      double height;
      final bool isNetwork;
    
      CommonThumbnailWidget(
          {required this.image, required this.onPressed,required this.padding, required this.second,
            this.isHideSecond = false, this.width = 100, this.height = 200, this.isNetwork = false});
    
      @override
      State createState() => _CommonThumbnailWidgetState();
    }
    
    class _CommonThumbnailWidgetState extends State {
    
      @override
      void initState() {
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return RawMaterialButton(
    
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
            padding: widget.padding,
            constraints: const BoxConstraints(minWidth: 0.0, minHeight: 0.0),
            child:ClipRRect(
              borderRadius: BorderRadius.all(Radius.circular(1.0)),
              child: _getAssetFile(),
            ),
            onPressed: widget.onPressed
        );
      }
    
      //处理缩略图为空
      Widget _getAssetFile(){
        return Container(
          decoration: widget.image == "" ? boxDecoration(2):boxDecoration(0),
          child: Stack(
            alignment : AlignmentDirectional.center,
            children: [
              widget.image == "" ? SizedBox(width: widget.width, height: widget.height,): getFileWidget(widget.image),
              widget.image == "" ? const CircularProgressIndicator() : const Icon(Icons.play_arrow,color: Colors.white,size: 60.0,),
              Positioned(
                bottom: 10,
                right: 10,
                child: Offstage(
                  offstage: widget.isHideSecond,
                  child: Text("${CommonUtils.changeVideoTime(widget.second)}", style: TextStyle(color: Colors.white),),
                ),
              ),
    
            ],
          ),
        );
      }
    
      BoxDecoration boxDecoration(double width){
        return BoxDecoration(
          border: Border.all(
              width: width,
              color: Colors.grey
          ),
        );
      }
    
      Widget getFileWidget(String fileName){
        //文件存在
        if(File(fileName).existsSync()){
          return Image.file(File(widget.image),fit: BoxFit.cover,width: widget.width,height: widget.height,);
        } else if(widget.isNetwork){
          return CommonUtils.showBaseImage(CommonUtils.getReallyImage(widget.image), width: widget.width, height: widget.height);
        } else {
          return Image.asset(CommonUtils.getNetworkDefaultError(),fit: BoxFit.fitWidth,width: widget.width,height: widget.height,);
        }
      }
    
    }
  • 相关阅读:
    番外8.1 配置+管理文件系统
    C++模板函数
    Redis入门完整教程:Java客户端Jedis
    详解:整合SSM
    耿耿为民心
    leetcode 34. 在排序数组中查找元素的第一个和最后一个位置(二分经典题)
    [附源码]计算机毕业设计JAVA鞋店销售管理
    JVM原理学习笔记总结
    Maven execution terminated abnormally (exit code 1) 创建Maven项目时报错解决方法
    python神经网络编程 豆瓣,python神经网络库 keras
  • 原文地址:https://blog.csdn.net/maoning20080808/article/details/128014284