import 'dart:async'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/model/rtc/chat_audio_product_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/network/network_service.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../widget/live/live_recharge_popup.dart'; import '../discover/room_controller.dart'; import '../overlay_controller.dart'; import 'chat_controller.dart'; /// 通话类型 enum CallType { voice, // 语音通话 video, // 视频通话 } /// 通话状态 enum CallStatus { waitCalling, // 等待接通 calling, // 通话中 missed, // 未接听 cancelled, // 已取消 rejected, // 已拒绝 } /// 通话会话信息 class CallSession { final String targetUserId; final CallType callType; final CallStatus status; final bool isInitiator; // 是否是发起方 final DateTime startTime; DateTime? endTime; CallSession({ required this.targetUserId, required this.callType, required this.status, required this.isInitiator, required this.startTime, this.endTime, }); /// 获取通话时长(秒) int get duration { final end = endTime ?? DateTime.now(); return end.difference(startTime).inSeconds; } } /// 通话相关控制器 class CallController extends GetxController { static CallController? _instance; static CallController get instance { if (_instance != null) { return _instance!; } // 如果 GetX 中已注册,使用 Get.find 获取 if (Get.isRegistered()) { _instance = Get.find(); return _instance!; } // 否则创建新实例并注册 _instance = Get.put(CallController(), permanent: true); return _instance!; } CallController({NetworkService? networkService}) : _networkService = networkService ?? Get.find(); final NetworkService _networkService; /// 当前频道信息 final Rxn rtcChannel = Rxn(); /// 是否正在创建频道 final RxBool isCreatingChannel = false.obs; // 当前正在进行的通话 final Rx currentCall = Rx(null); // 通话计时器(用于记录通话时长) Timer? _callTimer; int _callDurationSeconds = 0; final RxInt callDurationSeconds = RxInt(0); // 通话超时计时器(发起方等待对方接听的30秒超时) Timer? _callTimeoutTimer; // 消费定时器(每隔1分钟调用一次消费接口) Timer? _consumeTimer; // 远端用户UID(用于显示远端视频) final Rxn remoteUid = Rxn(); // 麦克风静音状态 final RxBool isMicMuted = false.obs; // 扬声器开启状态 final RxBool isSpeakerOn = false.obs; // 音频播放器(用于播放来电铃声) final AudioPlayer _callAudioPlayer = AudioPlayer(); bool _isPlayingCallAudio = false; // 当前通话的频道ID和UID(用于发送RTM消息) String? _callChannelId; int? _callUid; @override void onInit() { super.onInit(); print('📞 [CallController] 通话控制器已初始化'); // 监听音频播放完成事件,实现循环播放 _callAudioPlayer.onPlayerComplete.listen((_) async { if (_isPlayingCallAudio) { // 如果还在播放状态,重新播放(循环播放) await _callAudioPlayer.play( AssetSource(Assets.audioCall.replaceFirst('assets/', '')), ); } }); // RTM 消息监听器已移除,通话相关消息改为通过onMessageContentChanged处理 } /// 创建一对一RTC频道 /// [type] 1为音频,2为视频 Future createOneOnOneRtcChannel({ required int type, required String toUserId, }) async { if (isCreatingChannel.value) { print('⚠️ 正在创建频道,请稍候'); return null; } // 验证 type 参数 if (type != 2 && type != 3) { SmartDialog.showToast('类型参数错误:1为音频,2为视频'); return null; } // 检查权限:语音通话需要麦克风,视频通话需要摄像头和麦克风 final hasPermission = await _ensureCallPermissions(type); if (!hasPermission) { print('❌ [CallController] 权限检查失败,无法创建通话频道'); return null; } isCreatingChannel.value = true; final response = await _networkService.rtcApi.createOneOnOneRtcChannel({ 'type': type, 'toUserId': toUserId, }); if (response.data.isSuccess && response.data.data != null) { if (!response.data.data!.success && response.data.data!.code == 'E0002') { // 玫瑰不足,显示 toast 并弹出充值弹框 SmartDialog.showToast('玫瑰不足请充值'); isCreatingChannel.value = false; Get.log('❌ 送礼失败: ${response.data.data}'); // 使用 addPostFrameCallback 确保在下一帧显示弹框,避免与 toast 冲突 WidgetsBinding.instance.addPostFrameCallback((_) { SmartDialog.show( alignment: Alignment.bottomCenter, maskColor: Colors.black.withOpacity(0.5), builder: (_) => const LiveRechargePopup(), ); }); return null; } rtcChannel.value = response.data.data; print('✅ 创建一对一RTC频道成功: ${response.data.data?.channelId}'); isCreatingChannel.value = false; return response.data.data; } else { final message = response.data.message.isNotEmpty ? response.data.message : '创建频道失败'; SmartDialog.showToast(message); print('❌ 创建一对一RTC频道失败: $message'); isCreatingChannel.value = false; return null; } } /// 获取聊天音频产品列表 /// [toUserId] 目标用户ID Future?> listChatAudioProduct(String toUserId) async { try { final response = await _networkService.rtcApi.listChatAudioProduct(toUserId); if (response.data.isSuccess) { print('✅ [CallController] 获取聊天音频产品列表成功'); return response.data.data; } else { final message = response.data.message.isNotEmpty ? response.data.message : '获取聊天音频产品列表失败'; SmartDialog.showToast(message); print('❌ [CallController] 获取聊天音频产品列表失败: $message'); return null; } } catch (e) { SmartDialog.showToast('网络请求失败,请重试'); print('❌ [CallController] 获取聊天音频产品列表异常: $e'); return null; } } /// 发起通话 /// [targetUserId] 目标用户ID /// [callType] 通话类型:语音或视频 /// [chatController] 聊天控制器,用于发送通话消息 Future initiateCall({ required String targetUserId, required CallType callType, ChatController? chatController, }) async { if (currentCall.value != null) { SmartDialog.showToast('当前正在通话中'); return false; } // 检查是否在直播间 try { if (Get.isRegistered()) { final roomController = Get.find(); if (roomController.isLive.value) { SmartDialog.showToast('请先退出直播间'); print('⚠️ [CallController] 当前在直播间,无法发起通话'); return false; } } } catch (e) { // 忽略错误,继续处理发起通话逻辑 print('⚠️ [CallController] 检查直播间状态失败: $e'); } // 清空之前的远端用户UID和通话信息 remoteUid.value = null; print( '📞 [CallController] 发起${callType == CallType.video ? "视频" : "语音"}通话,目标用户: $targetUserId', ); // 发起通话前,先创建一对一 RTC 频道 final type = callType == CallType.video ? 3 : 2; // 1为音频,2为视频 final channelData = await createOneOnOneRtcChannel( type: type, toUserId: targetUserId, ); if (channelData == null) { return false; } if (!channelData.success) { print('❌ [CallController] 创建RTC频道失败,无法发起通话'); SmartDialog.showToast('创建通话频道失败'); return false; } _callUid = channelData?.uid; _callChannelId = channelData?.channelId; print('✅ [CallController] RTC频道创建成功: ${channelData!.channelId}'); // 创建通话会话 final session = CallSession( targetUserId: targetUserId, callType: callType, status: CallStatus.waitCalling, isInitiator: true, startTime: DateTime.now(), ); currentCall.value = session; // 发送通话消息(发起) final callTypeStr = callType == CallType.video ? 'video' : 'voice'; await _sendCallMessage( targetUserId: targetUserId, callType: callTypeStr, callStatus: 'waitCalling', // 初始状态为未接听,等待对方响应 channelId: channelData.channelId, // 传递频道ID chatController: chatController, uid: channelData.uid, ); startCallAudio(); // 根据通话类型设置摄像头状态 if (callType == CallType.voice) { // 语音通话:禁用视频(关闭摄像头) await RTCManager.instance.disableVideo(); print('📞 [CallController] 语音通话,已关闭摄像头'); } else { // 视频通话:启用视频(打开摄像头) await RTCManager.instance.enableVideo(); print('📞 [CallController] 视频通话,已打开摄像头'); } // 加入 RTC 频道,发起真正的通话 await RTCManager.instance.joinChannel( token: channelData.token, channelId: channelData.channelId, uid: channelData.uid, role: ClientRoleType.clientRoleBroadcaster, rtcType: RTCType.call, ); print('✅ [CallController] 已加入 RTC 频道: ${channelData.channelId}'); // 启动30秒超时计时器(如果30秒内对方未接听,自动取消通话) _startCallTimeoutTimer(); return true; } /// 接听通话 /// [message] 通话消息 /// [chatController] 聊天控制器,用于更新通话消息 Future acceptCall({ required EMMessage message, ChatController? chatController, }) async { // 检查是否在直播间 try { if (Get.isRegistered()) { final roomController = Get.find(); if (roomController.isLive.value) { SmartDialog.showToast('请先退出直播间再接听'); print('⚠️ [CallController] 当前在直播间,无法接听通话'); return false; } } } catch (e) { // 忽略错误,继续处理接听逻辑 print('⚠️ [CallController] 检查直播间状态失败: $e'); } final callInfo = _parseCallInfo(message); if (callInfo == null) { return false; } final targetUserId = message.from ?? ''; final callTypeStr = callInfo['callType'] as String?; final callType = callTypeStr == 'video' ? CallType.video : CallType.voice; print( '📞 [CallController] 接听${callType == CallType.video ? "视频" : "语音"}通话', ); // 清空之前的远端用户UID和通话信息 remoteUid.value = null; _callChannelId = null; _callUid = null; // 创建通话会话 final session = CallSession( targetUserId: targetUserId, callType: callType, status: CallStatus.calling, isInitiator: false, startTime: DateTime.now(), ); currentCall.value = session; // 停止播放来电铃声(已接通) stopCallAudio(); // 开始计时 _startCallTimer(); // 从通话信息中获取 channelId 和 uid final channelId = callInfo['channelId'] as String?; if (channelId == null || channelId.isEmpty) { print('❌ [CallController] channelId 为空,无法加入 RTC 频道'); SmartDialog.showToast('频道ID不存在'); return false; } // 保存 channelId 为全局变量 _callChannelId = channelId; // 调用连接一对一RTC频道接口 final connectResponse = await _networkService.rtcApi .connectOneOnOneRtcChannel({'channelId': channelId}); if (!connectResponse.data.isSuccess) { SmartDialog.showToast(connectResponse.data.message); return false; } if (!connectResponse.data.data!['success'] && connectResponse.data.data!['code'] == 'E0002') { // 玫瑰不足,显示 toast 并弹出充值弹框 SmartDialog.showToast('玫瑰不足请充值'); // 使用 addPostFrameCallback 确保在下一帧显示弹框,避免与 toast 冲突 WidgetsBinding.instance.addPostFrameCallback((_) { SmartDialog.show( alignment: Alignment.bottomCenter, maskColor: Colors.black.withOpacity(0.5), builder: (_) => const LiveRechargePopup(), ); }); return false; } print('✅ [CallController] 已调用连接一对一RTC频道接口,channelId: $channelId'); // 根据通话类型设置摄像头状态 if (callType == CallType.voice) { // 语音通话:禁用视频(关闭摄像头) await RTCManager.instance.disableVideo(); print('📞 [CallController] 语音通话,已关闭摄像头'); } else { // 视频通话:启用视频(打开摄像头) await RTCManager.instance.enableVideo(); print('📞 [CallController] 视频通话,已打开摄像头'); } // 获取 RTC token 并加入频道 final response = await _networkService.rtcApi.getSwRtcToken(channelId); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; // 保存 UID 为全局变量 _callUid = base.data!.uid; await _joinRtcChannel( base.data!.token, channelId, base.data!.uid, ClientRoleType.clientRoleBroadcaster, ); // 从消息中获取发起方的 uid,设置为远端用户 UID final initiatorUid = callInfo['uid'] as int?; if (initiatorUid != null) { remoteUid.value = initiatorUid; print( '📞 [CallController] 从消息中获取到发起方 UID: $initiatorUid,已设置 remoteUid', ); } // 接收方接听后,立即调用一次消费接口并启动定时器 // 确保 _callChannelId 已设置 if (_callChannelId != null && _callChannelId!.isNotEmpty) { Future .delayed(Duration(seconds: 1), () async { await _consumeOneOnOneRtcChannel(); }); _startConsumeTimer(); print('✅ [CallController] 接收方接听后已启动消费定时器'); } } else { SmartDialog.showToast('获取RTC token失败'); return false; } return true; } /// 拒绝通话 /// [message] 通话消息 /// [chatController] 聊天控制器,用于更新通话消息 Future rejectCall({ required EMMessage message, ChatController? chatController, }) async { print('📞 [CallController] 拒绝通话'); // 停止播放来电铃声(已拒绝) stopCallAudio(); // 从消息中获取 channelId String? channelId; if (message.body is EMCustomMessageBody) { final customBody = message.body as EMCustomMessageBody; final params = customBody.params; if (params != null && params.containsKey('channelId')) { channelId = params['channelId']?.toString(); } } // 如果有 channelId,调用拒绝接口 if (channelId != null && channelId.isNotEmpty) { final response = await _networkService.rtcApi.refuseOneOnOneRtcChannel({ 'channelId': channelId, }); if (!response.data.isSuccess) { SmartDialog.showToast(response.data.message); return false; } print('✅ [CallController] 已调用拒绝一对一RTC频道接口,channelId: $channelId'); // 服务端会自动修改消息callStatus为'rejected',客户端通过onMessageContentChanged收到通知 } // 清理通话会话 currentCall.value = null; return true; } /// 结束通话(通话完成) /// [callDuration] 通话时长(秒) /// [chatController] 聊天控制器,用于更新通话消息 Future endCall({ required int callDuration, EMMessage? message, ChatController? chatController, }) async { print('📞 [CallController] 结束通话,时长: ${callDuration}秒'); // 停止播放来电铃声(通话结束) stopCallAudio(); // 停止计时和超时计时器 _stopCallTimer(); _stopCallTimeoutTimer(); _stopConsumeTimer(); // 清理通话会话和远端用户UID currentCall.value = null; remoteUid.value = null; // 清理保存的 channelId 和 uid _callChannelId = null; _callUid = null; // TODO: 这里可以集成实际的通话SDK,结束通话 // 例如:await RTCManager.instance.endCall(); return true; } /// 开始通话计时 void _startCallTimer() { _callDurationSeconds = 0; callDurationSeconds.value = 0; _callTimer?.cancel(); _callTimer = Timer.periodic(Duration(seconds: 1), (timer) { _callDurationSeconds++; callDurationSeconds.value = _callDurationSeconds; }); } /// 停止通话计时 void _stopCallTimer() { _callTimer?.cancel(); _callTimer = null; _callDurationSeconds = 0; callDurationSeconds.value = 0; } /// 启动通话超时计时器(30秒后如果对方未接听,自动取消通话) void _startCallTimeoutTimer() { _callTimeoutTimer?.cancel(); _callTimeoutTimer = Timer(Duration(seconds: 30), () { final callSession = currentCall.value; // 如果是发起方且对方未接听(通话时长仍为0且remoteUid为null),自动取消通话 if (callSession != null && callSession.isInitiator && callDurationSeconds.value == 0 && remoteUid.value == null) { print('⏰ [CallController] 30秒超时,对方未接听,自动取消通话'); hangUpCall(); } }); } /// 停止通话超时计时器 void _stopCallTimeoutTimer() { _callTimeoutTimer?.cancel(); _callTimeoutTimer = null; } /// 启动消费定时器(每隔1分钟调用一次消费接口) void _startConsumeTimer() { _stopConsumeTimer(); // 先停止之前的定时器 _consumeTimer = Timer.periodic(Duration(minutes: 1), (timer) { _consumeOneOnOneRtcChannel(); }); print('✅ [CallController] 已启动消费定时器,每隔1分钟调用一次'); } /// 停止消费定时器 void _stopConsumeTimer() { _consumeTimer?.cancel(); _consumeTimer = null; } /// 调用消费一对一RTC频道接口 Future _consumeOneOnOneRtcChannel() async { final consumeChannelId = _callChannelId; if (consumeChannelId == null || consumeChannelId.isEmpty) { print('⚠️ [CallController] channelId为空,无法调用消费接口'); return; } try { final response = await _networkService.rtcApi.consumeOneOnOneRtcChannel({ 'channelId': consumeChannelId, }); if (response.data.isSuccess) { print('✅ [CallController] 已调用消费一对一RTC频道接口,channelId: $consumeChannelId'); } else { print('⚠️ [CallController] 消费一对一RTC频道接口失败: ${response.data.message}'); } } catch (e) { print('⚠️ [CallController] 调用消费接口异常: $e'); } } /// 发送通话消息 Future _sendCallMessage({ required String targetUserId, required String callType, required String callStatus, int? callDuration, int? uid, String? channelId, ChatController? chatController, }) async { // 如果提供了 chatController,使用它发送消息 return await chatController!.sendCallMessage( callType: callType, callStatus: callStatus, callDuration: callDuration, channelId: channelId, uid: uid, ); } /// 从自定义消息中解析通话信息 Map? _parseCallInfo(EMMessage message) { if (message.body.type == MessageType.CUSTOM) { final customBody = message.body as EMCustomMessageBody; if (customBody.event == 'call' && customBody.params != null) { final params = customBody.params!; return { 'callType': params['callType'] ?? 'voice', 'callStatus': params['callStatus'] ?? 'missed', 'callDuration': params['callDuration'] != null ? int.tryParse(params['callDuration']!) : null, 'channelId': params['channelId'], 'uid': params['uid'] != null ? int.tryParse(params['uid']!) : null, }; } } return null; } /// 检查是否有正在进行的通话 bool get isInCall => currentCall.value != null; /// 获取当前通话时长(秒) int get currentCallDuration => callDurationSeconds.value; /// 开始播放来电铃声(循环播放) /// 可以是发起方或接收方调用 Future startCallAudio() async { if (_isPlayingCallAudio) { return; // 已经在播放中 } _isPlayingCallAudio = true; print('🔊 [CallController] 开始播放来电铃声'); await _callAudioPlayer.play( AssetSource(Assets.audioCall.replaceFirst('assets/', '')), ); } /// 停止播放来电铃声 /// 可以是发起方或接收方调用 Future stopCallAudio() async { if (!_isPlayingCallAudio) { return; // 没有在播放 } _isPlayingCallAudio = false; print('🔇 [CallController] 停止播放来电铃声'); await _callAudioPlayer.stop(); } Future joinChannel(String channelName) async { final response = await _networkService.rtcApi.getSwRtcToken(channelName); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; // 保存 UID 为全局变量 _callUid = base.data!.uid; await _joinRtcChannel( base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleBroadcaster, ); // 服务端会自动修改消息callStatus为'calling',客户端通过onMessageContentChanged收到通知 } } Future _joinRtcChannel( String token, String channelName, int uid, ClientRoleType roleType, ) async { final granted = await _ensureRtcPermissions(); if (!granted) return; await RTCManager.instance.joinChannel( token: token, channelId: channelName, uid: uid, role: roleType, rtcType: RTCType.call, ); print('✅ [CallController] 已加入 RTC 频道: $channelName'); } /// 检查通话权限 /// [type] 2=语音通话,3=视频通话 Future _ensureCallPermissions(int type) async { if (type == 2) { // 语音通话:只需要麦克风权限 final micStatus = await Permission.microphone.request(); if (micStatus.isGranted) { return true; } if (micStatus.isPermanentlyDenied) { SmartDialog.showToast('请在系统设置中开启麦克风权限'); await openAppSettings(); } else { SmartDialog.showToast('请允许麦克风权限以进行语音通话'); } return false; } else if (type == 3) { // 视频通话:需要摄像头和麦克风权限 final statuses = await [Permission.camera, Permission.microphone].request(); final allGranted = statuses.values.every((status) => status.isGranted); if (allGranted) { return true; } final permanentlyDenied = statuses.values.any( (status) => status.isPermanentlyDenied, ); if (permanentlyDenied) { SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限'); await openAppSettings(); } else { SmartDialog.showToast('请允许摄像头和麦克风权限以进行视频通话'); } return false; } return false; } Future _ensureRtcPermissions() async { final statuses = await [Permission.camera, Permission.microphone].request(); final allGranted = statuses.values.every((status) => status.isGranted); if (allGranted) { return true; } final permanentlyDenied = statuses.values.any( (status) => status.isPermanentlyDenied, ); if (permanentlyDenied) { SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限'); await openAppSettings(); } else { SmartDialog.showToast('请允许摄像头和麦克风权限以进入房间'); } return false; } /// 切换麦克风状态 Future toggleMic() async { isMicMuted.value = !isMicMuted.value; await RTCManager.instance.muteLocalAudio(isMicMuted.value); print('📞 [CallController] 麦克风${isMicMuted.value ? "已静音" : "已取消静音"}'); } /// 切换扬声器状态 Future toggleSpeaker() async { isSpeakerOn.value = !isSpeakerOn.value; await RTCManager.instance.setEnableSpeakerphone(isSpeakerOn.value); print('📞 [CallController] 扬声器${isSpeakerOn.value ? "已开启" : "已关闭"}'); } /// 挂断通话 Future hangUpCall() async { final callSession = currentCall.value; // 取消超时计时器 _stopCallTimeoutTimer(); // 如果是发起方且处于呼叫中状态(对方还没接听),先调用取消接口 if (callSession != null && callSession.isInitiator && callDurationSeconds.value == 0 && _callChannelId != null && _callChannelId!.isNotEmpty) { try { final response = await _networkService.rtcApi.cancelOneOnOneRtcChannel({ 'channelId': _callChannelId!, }); if (response.data.isSuccess) { print('✅ [CallController] 已调用取消一对一RTC频道接口,channelId: $_callChannelId'); } else { print('⚠️ [CallController] 取消一对一RTC频道接口失败: ${response.data.message}'); } } catch (e) { print('⚠️ [CallController] 调用取消接口异常: $e'); } } else if (callSession != null && callDurationSeconds.value > 0 && _callChannelId != null && _callChannelId!.isNotEmpty) { // 如果通话已接通(无论是发起方还是接收方),调用终止接口 try { final response = await _networkService.rtcApi.terminateOneOnOneRtcChannel( {'channelId': _callChannelId!}, ); if (response.data.isSuccess) { print('✅ [CallController] 已调用终止一对一RTC频道接口,channelId: $_callChannelId'); } else { print('⚠️ [CallController] 终止一对一RTC频道接口失败: ${response.data.message}'); } } catch (e) { print('⚠️ [CallController] 调用终止接口异常: $e'); } } // 离开RTC频道 try { await RTCManager.instance.leaveChannel(); } catch (e) { print('⚠️ [CallController] 离开RTC频道异常: $e'); } // 服务端会自动修改消息callStatus为'cancelled'或'terminated',客户端通过onMessageContentChanged收到通知 // 结束通话(传递通话时长) await endCall(callDuration: callDurationSeconds.value); // 关闭视频通话邀请弹框(如果正在显示) SmartDialog.dismiss(); // 关闭通话小窗口 if (Get.isRegistered()) { final overlayController = Get.find(); overlayController.hideVideoCall(); print('✅ [CallController] 已关闭通话小窗口'); } // 退出 VideoCallPage(如果当前在 VideoCallPage) if (Get.currentRoute.contains('VideoCallPage')) { Get.back(); print('✅ [CallController] 已退出 VideoCallPage'); } print('✅ [CallController] 通话已挂断'); } /// 处理通话消息callStatus变化(通过onMessageContentChanged调用) Future handleCallStatusChange({ required EMMessage message, required String callStatus, String? channelId, int? uid, int? callDuration, }) async { print( '📞 [CallController] 处理callStatus变化: callStatus=$callStatus, channelId=$channelId', ); // 对于 cancelled 和 terminated 状态,即使没有 currentCall,也要处理(关闭小窗口等) if (callStatus == 'cancelled' || callStatus == 'terminated') { final callSession = currentCall.value; // 如果有 channelId,验证是否匹配当前通话(如果存在) if (callSession != null && channelId != null && channelId.isNotEmpty && _callChannelId != channelId) { print( '⚠️ [CallController] channelId不匹配,忽略callStatus变化: 当前=$_callChannelId, 消息=$channelId', ); return; } // 处理取消/终止状态(即使没有 callSession 也要关闭小窗口) print( '📞 [CallController] 通话被取消/终止,callStatus=$callStatus, hasCallSession=${callSession != null}', ); // 关闭视频通话邀请弹框(如果正在显示) SmartDialog.dismiss(); print('✅ [CallController] 已关闭视频通话邀请弹框'); // 取消超时计时器 _stopCallTimeoutTimer(); // 停止播放来电铃声 stopCallAudio(); // 停止通话计时器 _stopCallTimer(); // 停止消费定时器 _stopConsumeTimer(); // 离开RTC频道 await RTCManager.instance.leaveChannel(); // 如果有通话会话,结束通话 if (callSession != null) { await endCall(callDuration: callDuration ?? callDurationSeconds.value); } // 关闭通话小窗口 if (Get.isRegistered()) { final overlayController = Get.find(); overlayController.hideVideoCall(); print('✅ [CallController] 已关闭通话小窗口'); } // 退出 VideoCallPage(如果当前在 VideoCallPage) if (Get.currentRoute.contains('VideoCallPage')) { Get.back(); print('✅ [CallController] 已退出 VideoCallPage'); } return; } // 对于其他状态,需要检查是否有进行中的通话 final callSession = currentCall.value; if (callSession == null) { print('⚠️ [CallController] 当前没有进行中的通话,忽略callStatus变化'); return; } // 如果提供了channelId,验证是否匹配当前通话 if (channelId != null && channelId.isNotEmpty && _callChannelId != channelId) { print( '⚠️ [CallController] channelId不匹配,忽略callStatus变化: 当前=$_callChannelId, 消息=$channelId', ); return; } try { if (callStatus == 'calling') { // 通话接通 print('📞 [CallController] 通话已接通,callStatus=$callStatus'); // 确保 _callChannelId 已设置(优先使用传入的 channelId) if (channelId != null && channelId.isNotEmpty) { _callChannelId = channelId; } // 立即调用一次消费接口,然后启动定时器每隔1分钟调用一次 // 如果定时器还没有启动,则启动它(避免重复启动) if (_consumeTimer == null) { _consumeOneOnOneRtcChannel(); _startConsumeTimer(); print('✅ [CallController] 通话接通后已启动消费定时器'); } else { // 如果定时器已经启动,只调用一次消费接口(可能是接收方接听后服务端更新了消息状态) _consumeOneOnOneRtcChannel(); print('✅ [CallController] 消费定时器已存在,只调用一次消费接口'); } // 如果是发起方,设置远端用户UID并启动计时器 if (callSession.isInitiator) { // 停止播放来电铃声(对方已接听) stopCallAudio(); // 取消超时计时器(对方已接听) _stopCallTimeoutTimer(); // 启动通话计时器 _startCallTimer(); print('📞 [CallController] 已启动通话计时器'); // remoteUid 会由 RTCManager 的 onUserJoined 事件自动设置 // 如果此时还没有设置,尝试从 RTCManager 的 remoteUsersNotifier 中获取 if (remoteUid.value == null) { final rtcManager = RTCManager.instance; final remoteUsers = rtcManager.remoteUsersNotifier.value; if (remoteUsers.isNotEmpty) { remoteUid.value = remoteUsers.first; print( '📞 [CallController] 从 RTCManager.remoteUsersNotifier 获取到 remoteUid: ${remoteUsers.first}', ); } else if (uid != null) { // 如果 RTCManager 还没有远端用户,使用消息中的 uid 作为备用 remoteUid.value = uid; print('📞 [CallController] 使用消息中的 uid 设置 remoteUid: $uid'); } } } } else if (callStatus == 'rejected') { // 通话被拒绝 print('📞 [CallController] 通话被拒绝,callStatus=$callStatus'); // 如果是发起方,执行退出逻辑 if (callSession.isInitiator) { // 取消超时计时器(对方已拒绝) _stopCallTimeoutTimer(); // 关闭通话小窗口 if (Get.isRegistered()) { final overlayController = Get.find(); overlayController.hideVideoCall(); print('✅ [CallController] 已关闭通话小窗口'); } // 退出 VideoCallPage(如果当前在 VideoCallPage) if (Get.currentRoute.contains('VideoCallPage')) { Get.back(); print('✅ [CallController] 已退出 VideoCallPage'); } // 结束通话 await endCall(callDuration: callDurationSeconds.value); } } // 注意:cancelled 和 terminated 状态已在方法开头处理,这里不再重复处理 } catch (e) { print('❌ [CallController] 处理callStatus变化失败: $e'); } } @override void onClose() { stopCallAudio(); _stopCallTimer(); _stopCallTimeoutTimer(); currentCall.value = null; rtcChannel.value = null; remoteUid.value = null; isMicMuted.value = false; isSpeakerOn.value = false; _callChannelId = null; _callUid = null; _stopConsumeTimer(); _callAudioPlayer.dispose(); super.onClose(); } }