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:video_player/video_player.dart'; import 'package:dating_touchme_app/pages/message/video_player_page.dart'; class VideoItem extends StatefulWidget { final EMVideoMessageBody videoBody; final bool isSentByMe; final bool showTime; final String formattedTime; const VideoItem({ required this.videoBody, required this.isSentByMe, required this.showTime, required this.formattedTime, super.key, }); @override State createState() => _VideoItemState(); } class _VideoItemState extends State { VideoPlayerController? _controller; bool _isInitialized = false; String? _thumbnailPath; @override void initState() { super.initState(); _initializeVideo(); } Future _initializeVideo() async { try { // 获取本地视频路径 final localPath = widget.videoBody.localPath; final remotePath = widget.videoBody.remotePath; print('=== 视频消息调试信息 ==='); print('本地路径: $localPath'); print('远程路径: $remotePath'); print('视频时长: ${widget.videoBody.duration}秒'); if (localPath.isNotEmpty && File(localPath).existsSync()) { // 使用本地文件 print('使用本地视频文件'); _controller = VideoPlayerController.file(File(localPath)); } else if (remotePath != null && remotePath.isNotEmpty) { // 使用远程URL print('使用远程视频URL'); _controller = VideoPlayerController.networkUrl(Uri.parse(remotePath)); } else { print('⚠️ 警告: 没有可用的视频路径'); } if (_controller != null) { await _controller!.initialize(); setState(() { _isInitialized = true; }); print('✅ 视频初始化成功'); } // 获取缩略图路径 final thumbLocal = widget.videoBody.thumbnailLocalPath; final thumbRemote = widget.videoBody.thumbnailRemotePath; print('缩略图本地路径: $thumbLocal'); print('缩略图远程路径: $thumbRemote'); if (thumbLocal != null && thumbLocal.isNotEmpty) { _thumbnailPath = thumbLocal; print('使用本地缩略图'); } else if (thumbRemote != null && thumbRemote.isNotEmpty) { _thumbnailPath = thumbRemote; print('使用远程缩略图'); } else { print('⚠️ 警告: 没有可用的缩略图'); } print('======================'); } catch (e) { print('❌ 初始化视频失败: $e'); } } @override void dispose() { _controller?.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() { // 获取视频路径 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) { // 使用 Chewie 播放器页面 Get.to( () => VideoPlayerPage( videoPath: videoPath!, isNetwork: isNetwork, ), transition: Transition.fade, duration: const Duration(milliseconds: 200), ); } else { Get.snackbar( '提示', '视频路径不可用', snackPosition: SnackPosition.BOTTOM, ); } } @override Widget build(BuildContext context) { return Column( children: [ if (widget.showTime) _buildTimeLabel(), Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h), child: Row( mainAxisAlignment: widget.isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 视频消息气泡 GestureDetector( onTap: _playVideo, child: Container( width: 200.w, height: 150.h, decoration: BoxDecoration( color: Colors.grey[300], borderRadius: BorderRadius.circular(8.w), ), child: ClipRRect( borderRadius: BorderRadius.circular(8.w), child: Stack( fit: StackFit.expand, children: [ // 背景层:始终显示占位符 Container( color: Colors.grey[300], child: Icon( Icons.videocam, size: 48.w, color: Colors.grey[600], ), ), // 视频层或缩略图层 if (_isInitialized && _controller != null) Center( child: AspectRatio( aspectRatio: _controller!.value.aspectRatio, child: VideoPlayer(_controller!), ), ) else if (_thumbnailPath != null && _thumbnailPath!.isNotEmpty) _buildThumbnail(), // 播放按钮和时长覆盖层 Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withOpacity(0.4), ], ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 播放按钮 Icon( Icons.play_circle_filled, size: 56.w, color: Colors.white.withOpacity(0.9), ), SizedBox(height: 8.h), // 视频时长 Container( padding: EdgeInsets.symmetric( horizontal: 8.w, vertical: 4.h, ), decoration: BoxDecoration( color: Colors.black.withOpacity(0.6), borderRadius: BorderRadius.circular(4.w), ), child: Text( _formatDuration(widget.videoBody.duration ?? 0), style: TextStyle( fontSize: 12.sp, color: Colors.white, fontWeight: FontWeight.w500, ), ), ), ], ), ), ], ), ), ), ), ], ), ), ], ); } // 构建缩略图 Widget _buildThumbnail() { if (_thumbnailPath == null || _thumbnailPath!.isEmpty) { return const SizedBox.shrink(); } // 判断是本地路径还是远程路径 if (_thumbnailPath!.startsWith('http')) { return Image.network( _thumbnailPath!, fit: BoxFit.cover, width: double.infinity, height: double.infinity, errorBuilder: (context, error, stackTrace) { return const SizedBox.shrink(); }, ); } else { final file = File(_thumbnailPath!); if (file.existsSync()) { return Image.file( file, fit: BoxFit.cover, width: double.infinity, height: double.infinity, ); } else { return const SizedBox.shrink(); } } } }