|
|
@ -42,16 +42,16 @@ class VideoCallPage extends StatefulWidget { |
|
|
class _VideoCallPageState extends State<VideoCallPage> { |
|
|
class _VideoCallPageState extends State<VideoCallPage> { |
|
|
final CallController _callController = CallController.instance; |
|
|
final CallController _callController = CallController.instance; |
|
|
final RTCManager _rtcManager = RTCManager.instance; |
|
|
final RTCManager _rtcManager = RTCManager.instance; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Timer? _durationTimer; |
|
|
Timer? _durationTimer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String? _targetUserName; |
|
|
String? _targetUserName; |
|
|
String? _targetAvatarUrl; |
|
|
String? _targetAvatarUrl; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 是否显示控制按钮和时长(接通后5秒隐藏) |
|
|
// 是否显示控制按钮和时长(接通后5秒隐藏) |
|
|
final RxBool showControls = true.obs; |
|
|
final RxBool showControls = true.obs; |
|
|
Timer? _hideControlsTimer; |
|
|
Timer? _hideControlsTimer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 本地视频视图控制器 |
|
|
// 本地视频视图控制器 |
|
|
VideoViewController? _localVideoViewController; |
|
|
VideoViewController? _localVideoViewController; |
|
|
|
|
|
|
|
|
@ -62,14 +62,12 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
_initCallStatus(); |
|
|
_initCallStatus(); |
|
|
_startDurationTimer(); |
|
|
_startDurationTimer(); |
|
|
_initLocalVideo(); |
|
|
_initLocalVideo(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置系统UI样式 |
|
|
// 设置系统UI样式 |
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); |
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); |
|
|
SystemChrome.setPreferredOrientations([ |
|
|
|
|
|
DeviceOrientation.portraitUp, |
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 初始化本地视频视图 |
|
|
/// 初始化本地视频视图 |
|
|
void _initLocalVideo() { |
|
|
void _initLocalVideo() { |
|
|
final callSession = _callController.currentCall.value; |
|
|
final callSession = _callController.currentCall.value; |
|
|
@ -89,7 +87,7 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
void _initCallStatus() { |
|
|
void _initCallStatus() { |
|
|
// 不需要初始化,直接使用 CallController 的响应式变量 |
|
|
// 不需要初始化,直接使用 CallController 的响应式变量 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 判断通话是否已接通 |
|
|
/// 判断通话是否已接通 |
|
|
bool get _isCallConnected { |
|
|
bool get _isCallConnected { |
|
|
final callSession = _callController.currentCall.value; |
|
|
final callSession = _callController.currentCall.value; |
|
|
@ -101,8 +99,8 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
// 优先使用传入的 userData |
|
|
// 优先使用传入的 userData |
|
|
if (widget.userData != null) { |
|
|
if (widget.userData != null) { |
|
|
setState(() { |
|
|
setState(() { |
|
|
_targetUserName = widget.userData!.nickName.isNotEmpty |
|
|
|
|
|
? widget.userData!.nickName |
|
|
|
|
|
|
|
|
_targetUserName = widget.userData!.nickName.isNotEmpty |
|
|
|
|
|
? widget.userData!.nickName |
|
|
: widget.targetUserId; |
|
|
: widget.targetUserId; |
|
|
_targetAvatarUrl = widget.userData!.profilePhoto; |
|
|
_targetAvatarUrl = widget.userData!.profilePhoto; |
|
|
}); |
|
|
}); |
|
|
@ -112,10 +110,14 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
// 如果没有传入 userData,尝试从 ConversationController 获取 |
|
|
// 如果没有传入 userData,尝试从 ConversationController 获取 |
|
|
if (Get.isRegistered<ConversationController>()) { |
|
|
if (Get.isRegistered<ConversationController>()) { |
|
|
final conversationController = Get.find<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(() { |
|
|
setState(() { |
|
|
_targetUserName = cachedUserInfo.nickName ?? widget.targetUserId; |
|
|
_targetUserName = cachedUserInfo.nickName ?? widget.targetUserId; |
|
|
_targetAvatarUrl = cachedUserInfo.avatarUrl; |
|
|
_targetAvatarUrl = cachedUserInfo.avatarUrl; |
|
|
@ -124,8 +126,11 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 如果缓存中没有,尝试从 IM 加载 |
|
|
// 如果缓存中没有,尝试从 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(() { |
|
|
setState(() { |
|
|
_targetUserName = userInfo.nickName ?? widget.targetUserId; |
|
|
_targetUserName = userInfo.nickName ?? widget.targetUserId; |
|
|
_targetAvatarUrl = userInfo.avatarUrl; |
|
|
_targetAvatarUrl = userInfo.avatarUrl; |
|
|
@ -146,7 +151,10 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
_durationTimer?.cancel(); |
|
|
_durationTimer?.cancel(); |
|
|
_hideControlsTimer?.cancel(); |
|
|
_hideControlsTimer?.cancel(); |
|
|
_localVideoViewController?.dispose(); |
|
|
_localVideoViewController?.dispose(); |
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); |
|
|
|
|
|
|
|
|
SystemChrome.setEnabledSystemUIMode( |
|
|
|
|
|
SystemUiMode.manual, |
|
|
|
|
|
overlays: SystemUiOverlay.values, |
|
|
|
|
|
); |
|
|
SystemChrome.setPreferredOrientations(DeviceOrientation.values); |
|
|
SystemChrome.setPreferredOrientations(DeviceOrientation.values); |
|
|
super.dispose(); |
|
|
super.dispose(); |
|
|
} |
|
|
} |
|
|
@ -158,7 +166,8 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
if (mounted) { |
|
|
if (mounted) { |
|
|
final wasConnected = _isCallConnected; |
|
|
final wasConnected = _isCallConnected; |
|
|
// 如果通话存在且已经开始计时,说明已接通 |
|
|
// 如果通话存在且已经开始计时,说明已接通 |
|
|
if (callSession != null && _callController.callDurationSeconds.value > 0) { |
|
|
|
|
|
|
|
|
if (callSession != null && |
|
|
|
|
|
_callController.callDurationSeconds.value > 0) { |
|
|
if (!wasConnected) { |
|
|
if (!wasConnected) { |
|
|
// 刚接通,启动5秒隐藏定时器 |
|
|
// 刚接通,启动5秒隐藏定时器 |
|
|
_startHideControlsTimer(); |
|
|
_startHideControlsTimer(); |
|
|
@ -169,7 +178,7 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 监听通话时长变化(已接通时启动隐藏定时器) |
|
|
// 监听通话时长变化(已接通时启动隐藏定时器) |
|
|
_callController.callDurationSeconds.listen((seconds) { |
|
|
_callController.callDurationSeconds.listen((seconds) { |
|
|
if (mounted && !_isCallConnected && seconds > 0) { |
|
|
if (mounted && !_isCallConnected && seconds > 0) { |
|
|
@ -193,9 +202,9 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
/// 切换控制按钮的显示/隐藏(点击屏幕时调用) |
|
|
/// 切换控制按钮的显示/隐藏(点击屏幕时调用) |
|
|
void _toggleControlsVisibility() { |
|
|
void _toggleControlsVisibility() { |
|
|
if (!_isCallConnected) return; // 未接通时不处理 |
|
|
if (!_isCallConnected) return; // 未接通时不处理 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
showControls.value = !showControls.value; |
|
|
showControls.value = !showControls.value; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果显示控制按钮,重新启动5秒隐藏定时器 |
|
|
// 如果显示控制按钮,重新启动5秒隐藏定时器 |
|
|
if (showControls.value) { |
|
|
if (showControls.value) { |
|
|
_startHideControlsTimer(); |
|
|
_startHideControlsTimer(); |
|
|
@ -245,16 +254,19 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
children: [ |
|
|
children: [ |
|
|
// 背景视频/头像(模糊) |
|
|
// 背景视频/头像(模糊) |
|
|
_buildBackground(), |
|
|
_buildBackground(), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 最小化按钮(左上角) |
|
|
// 最小化按钮(左上角) |
|
|
_buildMinimizeButton(), |
|
|
_buildMinimizeButton(), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 本地视频小窗口(右上角,视频通话且已接通时显示) |
|
|
|
|
|
_buildLocalVideoPreview(), |
|
|
|
|
|
|
|
|
// 用户信息 |
|
|
// 用户信息 |
|
|
_buildUserInfo(), |
|
|
_buildUserInfo(), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 通话时长 |
|
|
// 通话时长 |
|
|
_buildCallDuration(), |
|
|
_buildCallDuration(), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 底部控制按钮 |
|
|
// 底部控制按钮 |
|
|
_buildControlButtons(), |
|
|
_buildControlButtons(), |
|
|
], |
|
|
], |
|
|
@ -297,31 +309,38 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
return Obx(() { |
|
|
return Obx(() { |
|
|
// 在 Obx 中访问响应式变量,确保建立监听关系 |
|
|
// 在 Obx 中访问响应式变量,确保建立监听关系 |
|
|
final callSession = _callController.currentCall.value; |
|
|
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 remoteUid = _callController.remoteUid.value; |
|
|
final remoteUsers = _rtcManager.remoteUsersNotifier.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) { |
|
|
if (!isVideoCall) { |
|
|
return _buildAvatarBackground(); |
|
|
return _buildAvatarBackground(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果 remoteUid 为空,尝试从 RTCManager 的远端用户列表中获取 |
|
|
// 如果 remoteUid 为空,尝试从 RTCManager 的远端用户列表中获取 |
|
|
if (remoteUid == null && remoteUsers.isNotEmpty) { |
|
|
if (remoteUid == null && remoteUsers.isNotEmpty) { |
|
|
_callController.remoteUid.value = remoteUsers.first; |
|
|
_callController.remoteUid.value = remoteUsers.first; |
|
|
print('📞 [VideoCallPage] 从 RTCManager.remoteUsersNotifier 获取到 remoteUid: ${remoteUsers.first}'); |
|
|
|
|
|
|
|
|
print( |
|
|
|
|
|
'📞 [VideoCallPage] 从 RTCManager.remoteUsersNotifier 获取到 remoteUid: ${remoteUsers.first}', |
|
|
|
|
|
); |
|
|
// Obx 会自动重建,所以这里不需要手动返回 |
|
|
// Obx 会自动重建,所以这里不需要手动返回 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 再次获取 remoteUid(可能刚刚被设置) |
|
|
// 再次获取 remoteUid(可能刚刚被设置) |
|
|
final currentRemoteUid = _callController.remoteUid.value; |
|
|
final currentRemoteUid = _callController.remoteUid.value; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果远端用户已加入,显示远端视频视图(对方画面) |
|
|
// 如果远端用户已加入,显示远端视频视图(对方画面) |
|
|
if (currentRemoteUid != null) { |
|
|
if (currentRemoteUid != null) { |
|
|
final engine = _rtcManager.engine; |
|
|
final engine = _rtcManager.engine; |
|
|
print('📞 [VideoCallPage] currentRemoteUid 不为 null: $currentRemoteUid, engine: ${engine != null}'); |
|
|
|
|
|
|
|
|
print( |
|
|
|
|
|
'📞 [VideoCallPage] currentRemoteUid 不为 null: $currentRemoteUid, engine: ${engine != null}', |
|
|
|
|
|
); |
|
|
if (engine != null) { |
|
|
if (engine != null) { |
|
|
print('📞 [VideoCallPage] 显示远端视频视图,UID:$currentRemoteUid'); |
|
|
print('📞 [VideoCallPage] 显示远端视频视图,UID:$currentRemoteUid'); |
|
|
final remoteVideoViewController = VideoViewController( |
|
|
final remoteVideoViewController = VideoViewController( |
|
|
@ -331,10 +350,10 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
return SizedBox( |
|
|
return SizedBox( |
|
|
width: double.infinity, |
|
|
width: double.infinity, |
|
|
height: 1.sh, |
|
|
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 { |
|
|
} else { |
|
|
print('⚠️ [VideoCallPage] engine 为 null,无法显示远端视频'); |
|
|
print('⚠️ [VideoCallPage] engine 为 null,无法显示远端视频'); |
|
|
@ -342,25 +361,76 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
} else { |
|
|
} else { |
|
|
print('⚠️ [VideoCallPage] currentRemoteUid 为 null,无法显示远端视频'); |
|
|
print('⚠️ [VideoCallPage] currentRemoteUid 为 null,无法显示远端视频'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有远端视频,显示本地视频视图(自己的画面) |
|
|
// 如果没有远端视频,显示本地视频视图(自己的画面) |
|
|
if (_localVideoViewController != null) { |
|
|
if (_localVideoViewController != null) { |
|
|
print('📞 [VideoCallPage] 显示本地视频视图'); |
|
|
print('📞 [VideoCallPage] 显示本地视频视图'); |
|
|
return SizedBox( |
|
|
return SizedBox( |
|
|
width: double.infinity, |
|
|
width: double.infinity, |
|
|
height: 1.sh, |
|
|
height: 1.sh, |
|
|
child: AgoraVideoView( |
|
|
|
|
|
controller: _localVideoViewController!, |
|
|
|
|
|
), |
|
|
|
|
|
|
|
|
child: AgoraVideoView(controller: _localVideoViewController!), |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果本地视频也没有,显示模糊的头像背景 |
|
|
// 如果本地视频也没有,显示模糊的头像背景 |
|
|
print('📞 [VideoCallPage] 显示头像背景'); |
|
|
print('📞 [VideoCallPage] 显示头像背景'); |
|
|
return _buildAvatarBackground(); |
|
|
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() { |
|
|
Widget _buildAvatarBackground() { |
|
|
return SizedBox( |
|
|
return SizedBox( |
|
|
@ -383,9 +453,7 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
|
|
|
|
|
|
/// 构建默认背景 |
|
|
/// 构建默认背景 |
|
|
Widget _buildDefaultBackground() { |
|
|
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 callSession = _callController.currentCall.value; |
|
|
final callDuration = _callController.callDurationSeconds.value; |
|
|
final callDuration = _callController.callDurationSeconds.value; |
|
|
final isCallConnected = callSession != null && callDuration > 0; |
|
|
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 const SizedBox.shrink(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Positioned( |
|
|
return Positioned( |
|
|
top: MediaQuery.of(context).size.height * 0.15, |
|
|
top: MediaQuery.of(context).size.height * 0.15, |
|
|
left: 0, |
|
|
left: 0, |
|
|
@ -454,12 +527,12 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
final callSession = _callController.currentCall.value; |
|
|
final callSession = _callController.currentCall.value; |
|
|
final callDuration = _callController.callDurationSeconds.value; |
|
|
final callDuration = _callController.callDurationSeconds.value; |
|
|
final isCallConnected = callSession != null && callDuration > 0; |
|
|
final isCallConnected = callSession != null && callDuration > 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是被呼叫方且未接通,显示邀请文字 |
|
|
// 如果是被呼叫方且未接通,显示邀请文字 |
|
|
if (!widget.isInitiator && !isCallConnected) { |
|
|
if (!widget.isInitiator && !isCallConnected) { |
|
|
final isVideoCall = widget.callType == 'video'; |
|
|
final isVideoCall = widget.callType == 'video'; |
|
|
final inviteText = isVideoCall ? '邀请你视频通话' : '邀请你语音通话'; |
|
|
final inviteText = isVideoCall ? '邀请你视频通话' : '邀请你语音通话'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Positioned( |
|
|
return Positioned( |
|
|
bottom: MediaQuery.of(context).size.height * 0.25, |
|
|
bottom: MediaQuery.of(context).size.height * 0.25, |
|
|
left: 0, |
|
|
left: 0, |
|
|
@ -476,12 +549,12 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
), |
|
|
), |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已接通但控制按钮已隐藏,不显示时长 |
|
|
// 如果已接通但控制按钮已隐藏,不显示时长 |
|
|
if (isCallConnected && !showControls.value) { |
|
|
if (isCallConnected && !showControls.value) { |
|
|
return const SizedBox.shrink(); |
|
|
return const SizedBox.shrink(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 呼叫方或已接通,显示时长或"正在呼叫中" |
|
|
// 呼叫方或已接通,显示时长或"正在呼叫中" |
|
|
final duration = Duration(seconds: callDuration); |
|
|
final duration = Duration(seconds: callDuration); |
|
|
return Positioned( |
|
|
return Positioned( |
|
|
@ -509,7 +582,7 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
final callSession = _callController.currentCall.value; |
|
|
final callSession = _callController.currentCall.value; |
|
|
final callDuration = _callController.callDurationSeconds.value; |
|
|
final callDuration = _callController.callDurationSeconds.value; |
|
|
final isCallConnected = callSession != null && callDuration > 0; |
|
|
final isCallConnected = callSession != null && callDuration > 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是被呼叫方且未接通,显示"拒绝"和"接听"按钮 |
|
|
// 如果是被呼叫方且未接通,显示"拒绝"和"接听"按钮 |
|
|
if (!widget.isInitiator && !isCallConnected) { |
|
|
if (!widget.isInitiator && !isCallConnected) { |
|
|
return Positioned( |
|
|
return Positioned( |
|
|
@ -539,12 +612,12 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
), |
|
|
), |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果已接通但控制按钮已隐藏,不显示按钮 |
|
|
// 如果已接通但控制按钮已隐藏,不显示按钮 |
|
|
if (isCallConnected && !showControls.value) { |
|
|
if (isCallConnected && !showControls.value) { |
|
|
return const SizedBox.shrink(); |
|
|
return const SizedBox.shrink(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 呼叫方或已接通,显示常规控制按钮 |
|
|
// 呼叫方或已接通,显示常规控制按钮 |
|
|
return Positioned( |
|
|
return Positioned( |
|
|
bottom: 40.h, |
|
|
bottom: 40.h, |
|
|
@ -597,11 +670,9 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
} else if (isAccept) { |
|
|
} else if (isAccept) { |
|
|
buttonColor = Color(0xFF34C759); // 绿色 |
|
|
buttonColor = Color(0xFF34C759); // 绿色 |
|
|
} else { |
|
|
} else { |
|
|
buttonColor = isActive |
|
|
|
|
|
? Colors.white.withOpacity(0.3) |
|
|
|
|
|
: Colors.white.withOpacity(0.2); |
|
|
|
|
|
|
|
|
buttonColor = isActive ? Colors.white : Colors.white.withOpacity(0.2); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return GestureDetector( |
|
|
return GestureDetector( |
|
|
onTap: onTap, |
|
|
onTap: onTap, |
|
|
child: Column( |
|
|
child: Column( |
|
|
@ -615,23 +686,22 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
), |
|
|
), |
|
|
child: Icon( |
|
|
child: Icon( |
|
|
icon, |
|
|
icon, |
|
|
color: Colors.white, |
|
|
|
|
|
|
|
|
color: isActive && icon != Icons.call_end && icon != Icons.phone |
|
|
|
|
|
? Colors.black |
|
|
|
|
|
: Colors.white, |
|
|
size: 28.w, |
|
|
size: 28.w, |
|
|
), |
|
|
), |
|
|
), |
|
|
), |
|
|
SizedBox(height: 8.h), |
|
|
SizedBox(height: 8.h), |
|
|
Text( |
|
|
Text( |
|
|
label, |
|
|
label, |
|
|
style: TextStyle( |
|
|
|
|
|
color: Colors.white, |
|
|
|
|
|
fontSize: 12.sp, |
|
|
|
|
|
), |
|
|
|
|
|
|
|
|
style: TextStyle(color: Colors.white, fontSize: 12.sp), |
|
|
), |
|
|
), |
|
|
], |
|
|
], |
|
|
), |
|
|
), |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 接听通话 |
|
|
/// 接听通话 |
|
|
Future<void> _acceptCall() async { |
|
|
Future<void> _acceptCall() async { |
|
|
if (widget.callMessage == null) { |
|
|
if (widget.callMessage == null) { |
|
|
@ -645,20 +715,21 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
if (Get.isRegistered<ChatController>(tag: tag)) { |
|
|
if (Get.isRegistered<ChatController>(tag: tag)) { |
|
|
chatController = Get.find<ChatController>(tag: tag); |
|
|
chatController = Get.find<ChatController>(tag: tag); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final accepted = await _callController.acceptCall( |
|
|
final accepted = await _callController.acceptCall( |
|
|
message: widget.callMessage!, |
|
|
message: widget.callMessage!, |
|
|
chatController: chatController, |
|
|
chatController: chatController, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (accepted) { |
|
|
if (accepted) { |
|
|
// 通话已接通,UI会自动更新 |
|
|
|
|
|
print('✅ [VideoCallPage] 通话已接通'); |
|
|
|
|
|
|
|
|
// 通话已接通,重新初始化本地视频视图(接收方接听后需要初始化) |
|
|
|
|
|
_initLocalVideo(); |
|
|
|
|
|
print('✅ [VideoCallPage] 通话已接通,已重新初始化本地视频视图'); |
|
|
} else { |
|
|
} else { |
|
|
SmartDialog.showToast('接听失败'); |
|
|
SmartDialog.showToast('接听失败'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// 拒绝通话 |
|
|
/// 拒绝通话 |
|
|
Future<void> _rejectCall() async { |
|
|
Future<void> _rejectCall() async { |
|
|
// 尝试获取 ChatController |
|
|
// 尝试获取 ChatController |
|
|
@ -667,16 +738,15 @@ class _VideoCallPageState extends State<VideoCallPage> { |
|
|
if (Get.isRegistered<ChatController>(tag: tag)) { |
|
|
if (Get.isRegistered<ChatController>(tag: tag)) { |
|
|
chatController = Get.find<ChatController>(tag: tag); |
|
|
chatController = Get.find<ChatController>(tag: tag); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final rejected = await _callController.rejectCall( |
|
|
final rejected = await _callController.rejectCall( |
|
|
message: widget.callMessage!, |
|
|
message: widget.callMessage!, |
|
|
chatController: chatController, |
|
|
chatController: chatController, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (rejected) { |
|
|
if (rejected) { |
|
|
// 拒绝成功,返回上一页 |
|
|
// 拒绝成功,返回上一页 |
|
|
Get.back(); |
|
|
Get.back(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|