import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'package:get/get.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:video_player/video_player.dart'; import 'package:dating_touchme_app/pages/message/video_player_page.dart'; import 'package:dating_touchme_app/generated/assets.dart'; class VideoItem extends StatefulWidget { final EMVideoMessageBody videoBody; final bool isSentByMe; final bool showTime; final String formattedTime; final EMMessage message; // 添加消息对象以获取状态 final VoidCallback? onResend; // 添加重发回调 const VideoItem({ required this.videoBody, required this.isSentByMe, required this.showTime, required this.formattedTime, required this.message, this.onResend, super.key, }); @override State createState() => _VideoItemState(); } // 消息发送状态枚举 enum MessageSendStatus { sending, success, failed, } class _VideoItemState extends State { String? _thumbnailPath; bool _isLoadingVideo = false; @override void initState() { super.initState(); // 🚀 极致性能优化:只准备缩略图路径,完全不初始化视频控制器 _prepareThumbnail(); } /// 准备缩略图路径(轻量级操作) void _prepareThumbnail() { final thumbLocal = widget.videoBody.thumbnailLocalPath; final thumbRemote = widget.videoBody.thumbnailRemotePath; print('🖼️ [VideoItem] 缩略图调试信息:'); print('本地缩略图路径: $thumbLocal'); print('远程缩略图路径: $thumbRemote'); // 优先使用本地缩略图 if (thumbLocal != null && thumbLocal.isNotEmpty) { final file = File(thumbLocal); if (file.existsSync()) { _thumbnailPath = thumbLocal; print('✅ 使用本地缩略图: $thumbLocal'); return; } else { print('⚠️ 本地缩略图文件不存在: $thumbLocal'); } } // 使用远程缩略图 if (thumbRemote != null && thumbRemote.isNotEmpty) { _thumbnailPath = thumbRemote; print('✅ 使用远程缩略图: $thumbRemote'); return; } // 🎯 备选方案:如果没有缩略图,尝试使用视频第一帧 final videoLocal = widget.videoBody.localPath; final videoRemote = widget.videoBody.remotePath; print('⚠️ 没有缩略图,尝试使用视频路径作为预览'); print('本地视频路径: $videoLocal'); print('远程视频路径: $videoRemote'); // 如果有本地视频,使用视频文件生成预览 if (videoLocal.isNotEmpty && File(videoLocal).existsSync()) { print('💡 [VideoItem] 将使用视频第一帧作为预览'); _generateThumbnailFromVideo(videoLocal); } else { print('❌ 没有可用的缩略图和视频,将显示占位符图标'); } } /// 🎯 从视频生成缩略图(备选方案) void _generateThumbnailFromVideo(String videoPath) async { try { print('🎬 [VideoItem] 开始从视频生成缩略图...'); // 使用 video_player 获取第一帧 final controller = VideoPlayerController.file(File(videoPath)); await controller.initialize(); // 设置缩略图路径为视频路径(让 UI 知道要使用视频预览) if (mounted) { setState(() { _thumbnailPath = 'video:$videoPath'; // 特殊标记 }); print('✅ [VideoItem] 使用视频第一帧作为预览'); } // 释放控制器 controller.dispose(); } catch (e) { print('❌ [VideoItem] 生成视频预览失败: $e'); } } @override void dispose() { // 无需释放资源,因为没有初始化任何控制器 super.dispose(); } // 构建时间标签 Widget _buildTimeLabel() { return Container( alignment: Alignment.center, padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), child: Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), child: Text( widget.formattedTime, style: TextStyle(fontSize: 12.sp, color: Colors.grey), ), ), ); } // 格式化时长 String _formatDuration(int seconds) { final minutes = seconds ~/ 60; final secs = seconds % 60; return '${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}'; } // 播放视频 void _playVideo() async { // 如果正在加载,不处理点击 if (_isLoadingVideo) { return; } // 获取视频路径 final localPath = widget.videoBody.localPath; final remotePath = widget.videoBody.remotePath; String? videoPath; bool isNetwork = false; // 优先使用本地路径 if (localPath.isNotEmpty && File(localPath).existsSync()) { videoPath = localPath; isNetwork = false; } else if (remotePath != null && remotePath.isNotEmpty) { videoPath = remotePath; isNetwork = true; } if (videoPath != null) { // 显示加载状态(如果是网络视频) if (isNetwork) { setState(() { _isLoadingVideo = true; }); // 模拟加载延迟(可选) await Future.delayed(Duration(milliseconds: 300)); } // 使用 Chewie 播放器页面 await Get.to( () => VideoPlayerPage( videoPath: videoPath!, isNetwork: isNetwork, ), transition: Transition.fade, duration: const Duration(milliseconds: 200), ); // 隐藏加载状态 if (mounted && isNetwork) { setState(() { _isLoadingVideo = false; }); } } else { SmartDialog.showToast('⚠️ 视频路径不可用,请稍后重试'); } } @override Widget build(BuildContext context) { return Column( children: [ if (widget.showTime) _buildTimeLabel(), Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), child: Builder( builder: (context) { // 计算视频尺寸 double maxWidth = 180.w; double width = maxWidth; double height = width * (304 / 289); // 按289:304比例计算高度 final videoHeight = height + 10.h; // 加上 margin top (10.h) return Row( mainAxisAlignment: widget.isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!widget.isSentByMe) _buildAvatar(), if (!widget.isSentByMe) SizedBox(width: 8.w), // 发送消息时,状态在左侧,与视频垂直居中对齐 if (widget.isSentByMe) SizedBox( height: videoHeight, child: Center( child: _buildMessageStatus(), ), ), if (widget.isSentByMe) SizedBox(width: 10.w), // 🚀 极致性能优化:无需初始化,直接显示缩略图 Stack( children: [ GestureDetector( onTap: _playVideo, child: Container( margin: EdgeInsets.only(top: 10.h), width: 180.w, height: 180.w * (304 / 289), // 按289:304比例计算高度 decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(18.w), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(18.w), child: Stack( fit: StackFit.expand, children: [ // 🚀 背景层:默认占位符 Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ Colors.grey[300]!, Colors.grey[400]!, ], ), ), child: Icon( Icons.videocam, size: 48.w, color: Colors.grey[600], ), ), // 🚀 缩略图层:始终显示缩略图(如果有) if (_thumbnailPath != null && _thumbnailPath!.isNotEmpty) _buildThumbnail(), // 播放按钮和时长覆盖层 Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withOpacity(0.1), Colors.black.withOpacity(0.5), ], ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 播放按钮(增强动画效果) Container( padding: EdgeInsets.all(8.w), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), shape: BoxShape.circle, ), child: Icon( Icons.play_arrow_rounded, size: 48.w, color: Colors.white, ), ), ], ), ), // 右下角显示时长标签 Positioned( right: 8.w, bottom: 8.h, child: Container( padding: EdgeInsets.symmetric( horizontal: 8.w, vertical: 4.h, ), decoration: BoxDecoration( color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(4.w), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.videocam_rounded, size: 12.w, color: Colors.white, ), SizedBox(width: 4.w), Text( _formatDuration(widget.videoBody.duration ?? 0), style: TextStyle( fontSize: 11.sp, color: Colors.white, fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ), ), ), ), // 🚀 加载指示器(播放时显示) if (_isLoadingVideo) Positioned.fill( child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), borderRadius: BorderRadius.circular(18.w), ), child: Center( child: CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation( Colors.white, ), ), ), ), ), ], ), if (widget.isSentByMe) SizedBox(width: 8.w), if (widget.isSentByMe) _buildAvatar(), ], ); }, ), ), ], ); } // 构建头像 Widget _buildAvatar() { return Container( width: 40.w, height: 40.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.w), image: DecorationImage( image: AssetImage(Assets.imagesAvatarsExample), fit: BoxFit.cover, ), ), ); } // 构建消息状态(发送中、已发送、失败重发) Widget _buildMessageStatus() { // 只对发送的消息显示状态 if (!widget.isSentByMe) { return SizedBox.shrink(); } // 检查消息状态 final status = widget.message.status; if (status == MessageStatus.FAIL) { // 发送失败,显示重发按钮 return GestureDetector( onTap: widget.onResend, child: Container( width: 20.w, height: 20.w, decoration: BoxDecoration( color: Colors.red.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( Icons.refresh, size: 14.w, color: Colors.red, ), ), ); } else if (status == MessageStatus.PROGRESS) { // 发送中,显示加载动画 return Container( width: 16.w, height: 16.w, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Colors.grey, ), ), ); } else { // 发送成功,不显示任何状态 return SizedBox.shrink(); } } // 🚀 性能优化:构建缓存的缩略图 Widget _buildThumbnail() { if (_thumbnailPath == null || _thumbnailPath!.isEmpty) { print('⚠️ [VideoItem] 缩略图路径为空,不渲染'); return const SizedBox.shrink(); } print('🎨 [VideoItem] 开始渲染缩略图: $_thumbnailPath'); // 🎯 检查是否是视频预览模式 if (_thumbnailPath!.startsWith('video:')) { print('🎬 [VideoItem] 使用视频第一帧预览模式'); final videoPath = _thumbnailPath!.substring(6); // 移除 "video:" 前缀 return _buildVideoPreview(videoPath); } // 判断是本地路径还是远程路径 if (_thumbnailPath!.startsWith('http')) { print('🌐 [VideoItem] 渲染网络缩略图'); // 🚀 使用 CachedNetworkImage 缓存网络图片 return CachedNetworkImage( imageUrl: _thumbnailPath!, fit: BoxFit.contain, width: double.infinity, height: double.infinity, placeholder: (context, url) { print('⏳ [VideoItem] 网络缩略图加载中...'); return Container( color: Colors.grey[300], child: Center( child: SizedBox( width: 24.w, height: 24.w, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.grey[600], ), ), ), ); }, errorWidget: (context, url, error) { print('❌ [VideoItem] 网络缩略图加载失败: $error'); return const SizedBox.shrink(); }, // 🚀 内存缓存配置 memCacheWidth: 400, // 限制缓存图片宽度 memCacheHeight: 300, // 限制缓存图片高度 ); } else { print('📁 [VideoItem] 渲染本地缩略图'); // 本地文件 final file = File(_thumbnailPath!); if (file.existsSync()) { print('✅ [VideoItem] 本地缩略图文件存在,开始渲染'); return Image.file( file, fit: BoxFit.contain, width: double.infinity, height: double.infinity, // 🚀 设置缓存宽高,减少内存占用 cacheWidth: 400, cacheHeight: 300, errorBuilder: (context, error, stackTrace) { print('❌ [VideoItem] 本地缩略图渲染失败: $error'); return const SizedBox.shrink(); }, ); } else { print('❌ [VideoItem] 本地缩略图文件不存在: $_thumbnailPath'); return const SizedBox.shrink(); } } } // 🎯 构建视频第一帧预览(备选方案) Widget _buildVideoPreview(String videoPath) { return FutureBuilder( future: _initVideoPreview(videoPath), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData && snapshot.data!.value.isInitialized) { print('✅ [VideoItem] 视频预览加载成功'); // 🎯 使用 FittedBox 保持视频比例,避免变形 return FittedBox( fit: BoxFit.contain, child: SizedBox( width: snapshot.data!.value.size.width, height: snapshot.data!.value.size.height, child: VideoPlayer(snapshot.data!), ), ); } else { print('⏳ [VideoItem] 视频预览加载中...'); return Container( color: Colors.grey[300], child: Center( child: SizedBox( width: 24.w, height: 24.w, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.grey[600], ), ), ), ); } }, ); } // 初始化视频预览 Future _initVideoPreview(String videoPath) async { final controller = VideoPlayerController.file(File(videoPath)); await controller.initialize(); await controller.seekTo(Duration.zero); // 定位到第一帧 return controller; } }