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<AssetEntity> selectedAssets = []; AssetPicker.pickAssets( context, pickerConfig: AssetPickerConfig( maxAssets: 1, selectedAssets: selectedAssets, ), ).then((imageList) { if(imageList == null){ return; } imageList as List<AssetEntity>; 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<int> 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<CommonThumbnailWidget> createState() => _CommonThumbnailWidgetState(); } class _CommonThumbnailWidgetState extends State<CommonThumbnailWidget> { @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,); } } }