import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:agora_rtm/agora_rtm.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/im/im_manager.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:dating_touchme_app/rtc/rtm_manager.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 '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 { _instance ??= Get.put(CallController()); 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); // 远端用户UID(用于显示远端视频) final Rxn remoteUid = Rxn(); // 麦克风静音状态 final RxBool isMicMuted = false.obs; // 扬声器开启状态 final RxBool isSpeakerOn = false.obs; // 音频播放器(用于播放来电铃声) final AudioPlayer _callAudioPlayer = AudioPlayer(); bool _isPlayingCallAudio = false; @override void onInit() { super.onInit(); print('📞 [CallController] 通话控制器已初始化'); // 监听音频播放完成事件,实现循环播放 _callAudioPlayer.onPlayerComplete.listen((_) async { if (_isPlayingCallAudio) { // 如果还在播放状态,重新播放(循环播放) await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', ''))); } }); // 注册 RTM 消息监听器,用于接收通话相关的 RTM 消息 _registerRtmMessageListener(); } /// 注册 RTM 消息监听器 void _registerRtmMessageListener() { RTMManager.instance.onMessageEvent = (MessageEvent event) { _handleRtmMessage(event); }; print('✅ [CallController] RTM 消息监听器已注册'); } /// 处理 RTM 消息 void _handleRtmMessage(MessageEvent event) { try { // 解析消息内容 String messageText; if (event.message is String) { messageText = event.message as String; } else if (event.message is Uint8List) { messageText = utf8.decode(event.message as Uint8List); } else { messageText = event.message.toString(); } final messageData = json.decode(messageText) as Map; print('📥 [CallController] 收到 RTM 消息: $messageData'); // 处理通话消息 if (messageData['type'] == 'call_message') { final event = messageData['event'] as String?; if (event == 'accept') { // 发起方收到 accept 消息,设置远端用户 UID final uid = messageData['uid']; if (uid != null) { remoteUid.value = uid is int ? uid : int.tryParse(uid.toString()); print('📞 [CallController] 收到 accept 消息,设置 remoteUid: ${remoteUid.value}'); } } } } catch (e) { print('❌ [CallController] 处理 RTM 消息失败: $e'); } } /// 创建一对一RTC频道 /// [type] 1为音频,2为视频 Future createOneOnOneRtcChannel({required int type}) async { if (isCreatingChannel.value) { print('⚠️ 正在创建频道,请稍候'); return null; } // 验证 type 参数 if (type != 1 && type != 2) { SmartDialog.showToast('类型参数错误:1为音频,2为视频'); return null; } isCreatingChannel.value = true; final response = await _networkService.rtcApi.createOneOnOneRtcChannel({ 'type': type, }); if (response.data.isSuccess && response.data.data != 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; } } /// 创建音频通话频道 Future createAudioChannel() { return createOneOnOneRtcChannel(type: 1); } /// 创建视频通话频道 Future createVideoChannel() { return createOneOnOneRtcChannel(type: 2); } /// 发起通话 /// [targetUserId] 目标用户ID /// [callType] 通话类型:语音或视频 /// [chatController] 聊天控制器,用于发送通话消息 Future initiateCall({ required String targetUserId, required CallType callType, ChatController? chatController, }) async { if (currentCall.value != null) { SmartDialog.showToast('已有通话正在进行中'); return false; } // 清空之前的远端用户UID remoteUid.value = null; print('📞 [CallController] 发起${callType == CallType.video ? "视频" : "语音"}通话,目标用户: $targetUserId'); // 发起通话前,先创建一对一 RTC 频道 final type = callType == CallType.video ? 2 : 1; // 1为音频,2为视频 final channelData = await createOneOnOneRtcChannel(type: type); if (channelData == null) { print('❌ [CallController] 创建RTC频道失败,无法发起通话'); SmartDialog.showToast('创建通话频道失败'); return false; } print('✅ [CallController] RTC频道创建成功: ${channelData.channelId}'); // 创建通话会话 final session = CallSession( targetUserId: targetUserId, callType: callType, status: CallStatus.calling, 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, ); 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}'); // 加入 RTC 频道后订阅 RTM 频道 await RTMManager.instance.subscribe(channelData.channelId); print('✅ [CallController] 已订阅 RTM 频道: ${channelData.channelId}'); return true; } /// 接听通话 /// [message] 通话消息 /// [chatController] 聊天控制器,用于更新通话消息 Future acceptCall({ required EMMessage message, ChatController? chatController, }) async { 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; // 创建通话会话 final session = CallSession( targetUserId: targetUserId, callType: callType, status: CallStatus.calling, isInitiator: false, startTime: DateTime.now(), ); currentCall.value = session; // 停止播放来电铃声(已接通) stopCallAudio(); // 开始计时 _startCallTimer(); // 更新通话消息状态为通话中 await _updateCallMessageStatus( message: message, callStatus: 'calling', callDuration: 0, chatController: chatController, ); // 从通话信息中获取 channelId final channelId = callInfo['channelId'] as String?; if (channelId == null || channelId.isEmpty) { print('❌ [CallController] channelId 为空,无法加入 RTC 频道'); SmartDialog.showToast('频道ID不存在'); return false; } // 根据通话类型设置摄像头状态 if (callType == CallType.voice) { // 语音通话:禁用视频(关闭摄像头) await RTCManager.instance.disableVideo(); print('📞 [CallController] 语音通话,已关闭摄像头'); } else { // 视频通话:启用视频(打开摄像头) await RTCManager.instance.enableVideo(); print('📞 [CallController] 视频通话,已打开摄像头'); } // 加入 RTC 频道,接听通话 await joinChannel(channelId); print('✅ [CallController] 已加入 RTC 频道: $channelId'); return true; } /// 拒绝通话 /// [message] 通话消息 /// [chatController] 聊天控制器,用于更新通话消息 Future rejectCall({ required EMMessage message, ChatController? chatController, }) async { print('📞 [CallController] 拒绝通话'); // 停止播放来电铃声(已拒绝) stopCallAudio(); // 更新通话消息状态为已拒绝 await _updateCallMessageStatus( message: message, callStatus: 'rejected', chatController: chatController, ); // 清理通话会话 currentCall.value = null; // TODO: 这里可以集成实际的通话SDK,拒绝通话 // 例如:await RTCManager.instance.rejectCall(message.from ?? ''); return true; } /// 取消通话 /// [message] 通话消息(可选,如果是发起方取消) /// [chatController] 聊天控制器,用于更新通话消息 Future cancelCall({ EMMessage? message, ChatController? chatController, }) async { print('📞 [CallController] 取消通话'); // 如果有消息,更新通话消息状态为已取消 if (message != null) { await _updateCallMessageStatus( message: message, callStatus: 'cancelled', chatController: chatController, ); } // 停止播放来电铃声(已取消) stopCallAudio(); // 停止计时 _stopCallTimer(); // 清理通话会话 currentCall.value = null; // TODO: 这里可以集成实际的通话SDK,取消通话 // 例如:await RTCManager.instance.cancelCall(); return true; } /// 结束通话(通话完成) /// [callDuration] 通话时长(秒) /// [chatController] 聊天控制器,用于更新通话消息 Future endCall({ required int callDuration, EMMessage? message, ChatController? chatController, }) async { print('📞 [CallController] 结束通话,时长: ${callDuration}秒'); // 停止播放来电铃声(通话结束) stopCallAudio(); // 停止计时 _stopCallTimer(); // 如果有消息,更新通话消息状态为通话中(显示时长) if (message != null) { await _updateCallMessageStatus( message: message, callStatus: 'calling', callDuration: callDuration, chatController: chatController, ); } // 清理通话会话和远端用户UID currentCall.value = null; remoteUid.value = 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; } /// 发送通话消息 Future _sendCallMessage({ required String targetUserId, required String callType, required String callStatus, int? callDuration, String? channelId, ChatController? chatController, }) async { // 如果提供了 chatController,使用它发送消息 return await chatController!.sendCallMessage( callType: callType, callStatus: callStatus, callDuration: callDuration, channelId: channelId, ); } /// 更新通话消息状态(使用modifyMessage修改现有消息) Future _updateCallMessageStatus({ required EMMessage message, required String callStatus, int? callDuration, ChatController? chatController, }) async { // 解析现有通话信息 final callInfo = _parseCallInfo(message); if (callInfo == null) { return false; } final callType = callInfo['callType'] as String? ?? 'voice'; final messageId = message.msgId; if (messageId.isEmpty) { print('❌ [CallController] 消息ID为空,无法修改消息'); return false; } // 如果是自定义消息,使用modifyMessage修改 if (message.body.type == MessageType.CUSTOM) { // 构建新的参数 final callParams = { 'callType': callType, 'callStatus': callStatus, }; if (callDuration != null) { callParams['callDuration'] = callDuration.toString(); } // 创建新的消息体 final customBody = EMCustomMessageBody( event: 'call', params: callParams, ); // 使用modifyMessage修改消息 final success = await IMManager.instance.modifyMessage( messageId: messageId, msgBody: customBody, attributes: null, // 不修改扩展属性 ); if (success) { print('✅ [CallController] 消息修改成功: messageId=$messageId, callStatus=$callStatus'); // 如果提供了chatController,更新本地消息列表 if (chatController != null) { // 更新消息体中的参数 final index = chatController.messages.indexWhere((msg) => msg.msgId == messageId); if (index != -1) { final updatedMessage = chatController.messages[index]; if (updatedMessage.body.type == MessageType.CUSTOM) { final customBody = updatedMessage.body as EMCustomMessageBody; // 创建新的参数Map并更新 final updatedParams = Map.from(customBody.params ?? {}); updatedParams['callType'] = callType; updatedParams['callStatus'] = callStatus; if (callDuration != null) { updatedParams['callDuration'] = callDuration.toString(); } // 注意:EMCustomMessageBody的params可能是只读的,这里可能需要重新创建消息 // 暂时先通知UI更新,实际的消息体更新会在收到onMessageContentChanged回调时处理 chatController.update(); } } } } return success; } // 如果不是自定义消息,返回失败 return false; } /// 从自定义消息中解析通话信息 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'], }; } } 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; // 订阅 RTM 频道 await RTMManager.instance.subscribe(channelName); // 获取当前通话信息 final callSession = currentCall.value; final callType = callSession?.callType == CallType.video ? 'video' : 'voice'; // 发布 RTM 消息,包含 UID 和通话相关字段 await RTMManager.instance.publishChannelMessage( channelName: channelName, message: json.encode({ 'type': 'call_message', 'uid': base.data!.uid, 'callType': callType, 'event': 'accept', }), ); await _joinRtcChannel( base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleBroadcaster, ); } } 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, ); final data = { 'channelId': channelName, 'seatNumber': 1, 'isMicrophoneOn': true, 'isVideoOn': true, }; final response = await _networkService.rtcApi.connectRtcChannel(data); if (!response.data.isSuccess) { SmartDialog.showToast(response.data.message); return; } SmartDialog.showToast('加入通话成功'); } 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 { // 离开RTC频道 await RTCManager.instance.leaveChannel(); // 结束通话(传递通话时长) await endCall(callDuration: callDurationSeconds.value); print('✅ [CallController] 通话已挂断'); } @override void onClose() { stopCallAudio(); _stopCallTimer(); currentCall.value = null; rtcChannel.value = null; remoteUid.value = null; isMicMuted.value = false; isSpeakerOn.value = false; _callAudioPlayer.dispose(); super.onClose(); } }