You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

291 lines
9.3 KiB

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<VideoItem> createState() => _VideoItemState();
}
class _VideoItemState extends State<VideoItem> {
VideoPlayerController? _controller;
bool _isInitialized = false;
String? _thumbnailPath;
@override
void initState() {
super.initState();
_initializeVideo();
}
Future<void> _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();
}
}
}
}