Browse Source

refactor(video_call): 优化视频通话页面代码结构和功能

- 修复了本地视频视图初始化时机问题,确保接收方接听后正确显示
- 添加了本地视频预览小窗口功能,右上角显示当前通话的本地视频
- 优化了代码格式和布局,统一了缩进和换行规范
- 重构了背景视频构建逻辑,改进了远程用户ID获取机制
- 调整了用户信息显示逻辑,视频通话接通后隐藏头像和昵称
- 优化了控制按钮颜色逻辑,根据激活状态动态调整按钮样式
- 修复了系统UI模式设置参数,确保正确的全屏和方向控制
- 增强了通话状态监听和控制按钮的显示/隐藏逻辑
master
Jolie 3 months ago
parent
commit
76bb0e68c2
1 changed files with 149 additions and 79 deletions
  1. 228
      lib/pages/message/video_call_page.dart

228
lib/pages/message/video_call_page.dart

@ -42,16 +42,16 @@ class VideoCallPage extends StatefulWidget {
class _VideoCallPageState extends State<VideoCallPage> {
final CallController _callController = CallController.instance;
final RTCManager _rtcManager = RTCManager.instance;
Timer? _durationTimer;
String? _targetUserName;
String? _targetAvatarUrl;
// 5
final RxBool showControls = true.obs;
Timer? _hideControlsTimer;
//
VideoViewController? _localVideoViewController;
@ -62,14 +62,12 @@ class _VideoCallPageState extends State<VideoCallPage> {
_initCallStatus();
_startDurationTimer();
_initLocalVideo();
// UI样式
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
}
///
void _initLocalVideo() {
final callSession = _callController.currentCall.value;
@ -89,7 +87,7 @@ class _VideoCallPageState extends State<VideoCallPage> {
void _initCallStatus() {
// 使 CallController
}
///
bool get _isCallConnected {
final callSession = _callController.currentCall.value;
@ -101,8 +99,8 @@ class _VideoCallPageState extends State<VideoCallPage> {
// 使 userData
if (widget.userData != null) {
setState(() {
_targetUserName = widget.userData!.nickName.isNotEmpty
? widget.userData!.nickName
_targetUserName = widget.userData!.nickName.isNotEmpty
? widget.userData!.nickName
: widget.targetUserId;
_targetAvatarUrl = widget.userData!.profilePhoto;
});
@ -112,10 +110,14 @@ class _VideoCallPageState extends State<VideoCallPage> {
// userData ConversationController
if (Get.isRegistered<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
//
final cachedUserInfo = conversationController.getCachedUserInfo(widget.targetUserId);
if (cachedUserInfo != null && (cachedUserInfo.nickName != null || cachedUserInfo.avatarUrl != null)) {
final cachedUserInfo = conversationController.getCachedUserInfo(
widget.targetUserId,
);
if (cachedUserInfo != null &&
(cachedUserInfo.nickName != null ||
cachedUserInfo.avatarUrl != null)) {
setState(() {
_targetUserName = cachedUserInfo.nickName ?? widget.targetUserId;
_targetAvatarUrl = cachedUserInfo.avatarUrl;
@ -124,8 +126,11 @@ class _VideoCallPageState extends State<VideoCallPage> {
}
// IM
final userInfo = await conversationController.loadContact(widget.targetUserId);
if (userInfo != null && (userInfo.nickName != null || userInfo.avatarUrl != null)) {
final userInfo = await conversationController.loadContact(
widget.targetUserId,
);
if (userInfo != null &&
(userInfo.nickName != null || userInfo.avatarUrl != null)) {
setState(() {
_targetUserName = userInfo.nickName ?? widget.targetUserId;
_targetAvatarUrl = userInfo.avatarUrl;
@ -146,7 +151,10 @@ class _VideoCallPageState extends State<VideoCallPage> {
_durationTimer?.cancel();
_hideControlsTimer?.cancel();
_localVideoViewController?.dispose();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: SystemUiOverlay.values,
);
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
super.dispose();
}
@ -158,7 +166,8 @@ class _VideoCallPageState extends State<VideoCallPage> {
if (mounted) {
final wasConnected = _isCallConnected;
//
if (callSession != null && _callController.callDurationSeconds.value > 0) {
if (callSession != null &&
_callController.callDurationSeconds.value > 0) {
if (!wasConnected) {
// 5
_startHideControlsTimer();
@ -169,7 +178,7 @@ class _VideoCallPageState extends State<VideoCallPage> {
}
}
});
//
_callController.callDurationSeconds.listen((seconds) {
if (mounted && !_isCallConnected && seconds > 0) {
@ -193,9 +202,9 @@ class _VideoCallPageState extends State<VideoCallPage> {
/// /
void _toggleControlsVisibility() {
if (!_isCallConnected) return; //
showControls.value = !showControls.value;
// 5
if (showControls.value) {
_startHideControlsTimer();
@ -245,16 +254,19 @@ class _VideoCallPageState extends State<VideoCallPage> {
children: [
// /
_buildBackground(),
//
_buildMinimizeButton(),
//
_buildLocalVideoPreview(),
//
_buildUserInfo(),
//
_buildCallDuration(),
//
_buildControlButtons(),
],
@ -297,31 +309,38 @@ class _VideoCallPageState extends State<VideoCallPage> {
return Obx(() {
// Obx 访
final callSession = _callController.currentCall.value;
final isVideoCall = callSession != null && callSession.callType == CallType.video;
final isVideoCall =
callSession != null && callSession.callType == CallType.video;
final remoteUid = _callController.remoteUid.value;
final remoteUsers = _rtcManager.remoteUsersNotifier.value;
print('📞 [VideoCallPage] _buildBackground Obx 重建,isVideoCall: $isVideoCall, remoteUid: $remoteUid, remoteUsers: $remoteUsers');
print(
'📞 [VideoCallPage] _buildBackground Obx 重建,isVideoCall: $isVideoCall, remoteUid: $remoteUid, remoteUsers: $remoteUsers',
);
//
if (!isVideoCall) {
return _buildAvatarBackground();
}
// remoteUid RTCManager
if (remoteUid == null && remoteUsers.isNotEmpty) {
_callController.remoteUid.value = remoteUsers.first;
print('📞 [VideoCallPage] 从 RTCManager.remoteUsersNotifier 获取到 remoteUid: ${remoteUsers.first}');
print(
'📞 [VideoCallPage] 从 RTCManager.remoteUsersNotifier 获取到 remoteUid: ${remoteUsers.first}',
);
// Obx
}
// remoteUid
final currentRemoteUid = _callController.remoteUid.value;
//
if (currentRemoteUid != null) {
final engine = _rtcManager.engine;
print('📞 [VideoCallPage] currentRemoteUid 不为 null: $currentRemoteUid, engine: ${engine != null}');
print(
'📞 [VideoCallPage] currentRemoteUid 不为 null: $currentRemoteUid, engine: ${engine != null}',
);
if (engine != null) {
print('📞 [VideoCallPage] 显示远端视频视图,UID:$currentRemoteUid');
final remoteVideoViewController = VideoViewController(
@ -331,10 +350,10 @@ class _VideoCallPageState extends State<VideoCallPage> {
return SizedBox(
width: double.infinity,
height: 1.sh,
key: ValueKey('remote_video_$currentRemoteUid'), // 使 key remoteUid
child: AgoraVideoView(
controller: remoteVideoViewController,
),
key: ValueKey(
'remote_video_$currentRemoteUid',
), // 使 key remoteUid
child: AgoraVideoView(controller: remoteVideoViewController),
);
} else {
print('⚠️ [VideoCallPage] engine 为 null,无法显示远端视频');
@ -342,25 +361,76 @@ class _VideoCallPageState extends State<VideoCallPage> {
} else {
print('⚠️ [VideoCallPage] currentRemoteUid 为 null,无法显示远端视频');
}
//
if (_localVideoViewController != null) {
print('📞 [VideoCallPage] 显示本地视频视图');
return SizedBox(
width: double.infinity,
height: 1.sh,
child: AgoraVideoView(
controller: _localVideoViewController!,
),
child: AgoraVideoView(controller: _localVideoViewController!),
);
}
//
print('📞 [VideoCallPage] 显示头像背景');
return _buildAvatarBackground();
});
}
///
Widget _buildLocalVideoPreview() {
return Obx(() {
// Obx 访
final callSession = _callController.currentCall.value;
final isVideoCall =
callSession != null && callSession.callType == CallType.video;
final callDuration = _callController.callDurationSeconds.value;
final isCallConnected = callSession != null && callDuration > 0;
//
if (isVideoCall && _localVideoViewController == null) {
final engine = _rtcManager.engine;
if (engine != null) {
_localVideoViewController = VideoViewController(
rtcEngine: engine,
canvas: const VideoCanvas(uid: 0),
);
print('📞 [VideoCallPage] 在 _buildLocalVideoPreview 中初始化本地视频视图');
}
}
//
if (!isVideoCall ||
!isCallConnected ||
_localVideoViewController == null) {
return const SizedBox.shrink();
}
return Positioned(
top: 26.w,
right: 26.w,
child: Container(
width: 120.w,
height: 160.w, // 4:3
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
border: Border.all(color: Colors.white.withOpacity(0.3), width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
clipBehavior: Clip.antiAlias,
child: AgoraVideoView(controller: _localVideoViewController!),
),
);
});
}
///
Widget _buildAvatarBackground() {
return SizedBox(
@ -383,9 +453,7 @@ class _VideoCallPageState extends State<VideoCallPage> {
///
Widget _buildDefaultBackground() {
return Container(
color: Colors.black,
);
return Container(color: Colors.black);
}
///
@ -395,14 +463,19 @@ class _VideoCallPageState extends State<VideoCallPage> {
final callSession = _callController.currentCall.value;
final callDuration = _callController.callDurationSeconds.value;
final isCallConnected = callSession != null && callDuration > 0;
print('📞 [VideoCallPage] _buildUserInfo Obx 重建,isCallConnected: $isCallConnected, callDuration: $callDuration');
//
if (isCallConnected) {
final isVideoCall =
callSession != null && callSession.callType == CallType.video;
print(
'📞 [VideoCallPage] _buildUserInfo Obx 重建,isCallConnected: $isCallConnected, callDuration: $callDuration, isVideoCall: $isVideoCall',
);
//
// 使
if (isCallConnected && isVideoCall) {
return const SizedBox.shrink();
}
return Positioned(
top: MediaQuery.of(context).size.height * 0.15,
left: 0,
@ -454,12 +527,12 @@ class _VideoCallPageState extends State<VideoCallPage> {
final callSession = _callController.currentCall.value;
final callDuration = _callController.callDurationSeconds.value;
final isCallConnected = callSession != null && callDuration > 0;
//
if (!widget.isInitiator && !isCallConnected) {
final isVideoCall = widget.callType == 'video';
final inviteText = isVideoCall ? '邀请你视频通话' : '邀请你语音通话';
return Positioned(
bottom: MediaQuery.of(context).size.height * 0.25,
left: 0,
@ -476,12 +549,12 @@ class _VideoCallPageState extends State<VideoCallPage> {
),
);
}
//
if (isCallConnected && !showControls.value) {
return const SizedBox.shrink();
}
// "正在呼叫中"
final duration = Duration(seconds: callDuration);
return Positioned(
@ -509,7 +582,7 @@ class _VideoCallPageState extends State<VideoCallPage> {
final callSession = _callController.currentCall.value;
final callDuration = _callController.callDurationSeconds.value;
final isCallConnected = callSession != null && callDuration > 0;
// "拒绝""接听"
if (!widget.isInitiator && !isCallConnected) {
return Positioned(
@ -539,12 +612,12 @@ class _VideoCallPageState extends State<VideoCallPage> {
),
);
}
//
if (isCallConnected && !showControls.value) {
return const SizedBox.shrink();
}
//
return Positioned(
bottom: 40.h,
@ -597,11 +670,9 @@ class _VideoCallPageState extends State<VideoCallPage> {
} else if (isAccept) {
buttonColor = Color(0xFF34C759); // 绿
} else {
buttonColor = isActive
? Colors.white.withOpacity(0.3)
: Colors.white.withOpacity(0.2);
buttonColor = isActive ? Colors.white : Colors.white.withOpacity(0.2);
}
return GestureDetector(
onTap: onTap,
child: Column(
@ -615,23 +686,22 @@ class _VideoCallPageState extends State<VideoCallPage> {
),
child: Icon(
icon,
color: Colors.white,
color: isActive && icon != Icons.call_end && icon != Icons.phone
? Colors.black
: Colors.white,
size: 28.w,
),
),
SizedBox(height: 8.h),
Text(
label,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
style: TextStyle(color: Colors.white, fontSize: 12.sp),
),
],
),
);
}
///
Future<void> _acceptCall() async {
if (widget.callMessage == null) {
@ -645,20 +715,21 @@ class _VideoCallPageState extends State<VideoCallPage> {
if (Get.isRegistered<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(tag: tag);
}
final accepted = await _callController.acceptCall(
message: widget.callMessage!,
chatController: chatController,
);
if (accepted) {
// UI会自动更新
print('✅ [VideoCallPage] 通话已接通');
//
_initLocalVideo();
print('✅ [VideoCallPage] 通话已接通,已重新初始化本地视频视图');
} else {
SmartDialog.showToast('接听失败');
}
}
///
Future<void> _rejectCall() async {
// ChatController
@ -667,16 +738,15 @@ class _VideoCallPageState extends State<VideoCallPage> {
if (Get.isRegistered<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(tag: tag);
}
final rejected = await _callController.rejectCall(
message: widget.callMessage!,
chatController: chatController,
);
if (rejected) {
//
Get.back();
}
}
}
Loading…
Cancel
Save