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



















![[附源码]java毕业设计在线课程网站](https://img-blog.csdnimg.cn/bce4d25e54a84cdf853e240364f52811.png)