From ecc8a5ee3c9e8f8d999ccfa8e4f724b1120b168b Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Tue, 30 Dec 2025 15:19:02 +0800 Subject: [PATCH] =?UTF-8?q?refactor(call):=20=E9=87=8D=E6=9E=84=E9=80=9A?= =?UTF-8?q?=E8=AF=9D=E6=8E=A7=E5=88=B6=E5=99=A8=E5=B9=B6=E9=9B=86=E6=88=90?= =?UTF-8?q?RTM=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除所有try-catch异常处理块,简化代码逻辑 - 添加RTMManager导入并实现RTM频道订阅功能 - 在加入RTC频道后自动订阅对应的RTM频道 - 通过RTM消息传递通话接受事件和用户UID信息 - 优化通话音频播放逻辑,移除循环播放异常处理 - 简化通话创建、接听、拒绝、取消和结束流程 - 在视频通话页面移除用户信息加载的异常处理 - 改进通话状态管理和错误处理机制 --- lib/controller/message/call_controller.dart | 783 +++++++++----------- lib/pages/message/video_call_page.dart | 147 ++-- lib/service/live_chat_message_service.dart | 16 + 3 files changed, 423 insertions(+), 523 deletions(-) diff --git a/lib/controller/message/call_controller.dart b/lib/controller/message/call_controller.dart index 65ebe19..0db4d5a 100644 --- a/lib/controller/message/call_controller.dart +++ b/lib/controller/message/call_controller.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:dating_touchme_app/generated/assets.dart'; @@ -6,6 +7,7 @@ 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'; @@ -101,12 +103,7 @@ class CallController extends GetxController { _callAudioPlayer.onPlayerComplete.listen((_) async { if (_isPlayingCallAudio) { // 如果还在播放状态,重新播放(循环播放) - try { - await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', ''))); - } catch (e) { - print('❌ [CallController] 循环播放来电铃声失败: $e'); - _isPlayingCallAudio = false; - } + await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', ''))); } }); } @@ -127,30 +124,23 @@ class CallController extends GetxController { isCreatingChannel.value = true; - try { - 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}'); - return response.data.data; - } else { - final message = response.data.message.isNotEmpty - ? response.data.message - : '创建频道失败'; - SmartDialog.showToast(message); - print('❌ 创建一对一RTC频道失败: $message'); - return null; - } - } catch (e) { - final errorMessage = '创建频道异常:$e'; - SmartDialog.showToast(errorMessage); - print('❌ $errorMessage'); - return null; - } finally { + 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; } } @@ -173,91 +163,76 @@ class CallController extends GetxController { required CallType callType, ChatController? chatController, }) async { - try { - if (currentCall.value != null) { - SmartDialog.showToast('已有通话正在进行中'); - return false; - } - - // 清空之前的远端用户UID - remoteUid.value = null; - - print('📞 [CallController] 发起${callType == CallType.video ? "视频" : "语音"}通话,目标用户: $targetUserId'); + if (currentCall.value != null) { + SmartDialog.showToast('已有通话正在进行中'); + return false; + } + + // 清空之前的远端用户UID + remoteUid.value = null; - // 发起通话前,先创建一对一 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}'); + print('📞 [CallController] 发起${callType == CallType.video ? "视频" : "语音"}通话,目标用户: $targetUserId'); - // 创建通话会话 - 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, - ); + // 发起通话前,先创建一对一 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(); - - // 根据通话类型设置摄像头状态 - try { - if (callType == CallType.voice) { - // 语音通话:禁用视频(关闭摄像头) - await RTCManager.instance.disableVideo(); - print('📞 [CallController] 语音通话,已关闭摄像头'); - } else { - // 视频通话:启用视频(打开摄像头) - await RTCManager.instance.enableVideo(); - print('📞 [CallController] 视频通话,已打开摄像头'); - } - } catch (e) { - print('⚠️ [CallController] 设置视频状态失败: $e'); - // 继续执行,不阻止加入频道 - } + startCallAudio(); - // 加入 RTC 频道,发起真正的通话 - try { - await RTCManager.instance.joinChannel( - token: channelData.token, - channelId: channelData.channelId, - uid: channelData.uid, - role: ClientRoleType.clientRoleBroadcaster, - rtcType: RTCType.call, - ); - print('✅ [CallController] 已加入 RTC 频道: ${channelData.channelId}'); - } catch (e) { - print('❌ [CallController] 加入 RTC 频道失败: $e'); - SmartDialog.showToast('加入通话频道失败'); - currentCall.value = null; - return false; - } - - return true; - } catch (e) { - print('❌ [CallController] 发起通话失败: $e'); - SmartDialog.showToast('发起通话失败: $e'); - currentCall.value = null; - return false; + // 根据通话类型设置摄像头状态 + 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; } /// 接听通话 @@ -267,85 +242,68 @@ class CallController extends GetxController { required EMMessage message, ChatController? chatController, }) async { - try { - final callInfo = _parseCallInfo(message); - if (callInfo == null) { - return false; - } + 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; + 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; + 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; + // 创建通话会话 + final session = CallSession( + targetUserId: targetUserId, + callType: callType, + status: CallStatus.calling, + isInitiator: false, + startTime: DateTime.now(), + ); + currentCall.value = session; - // 停止播放来电铃声(已接通) - stopCallAudio(); + // 停止播放来电铃声(已接通) + stopCallAudio(); - // 开始计时 - _startCallTimer(); + // 开始计时 + _startCallTimer(); - // 更新通话消息状态为通话中 - await _updateCallMessageStatus( - message: message, - callStatus: 'calling', - callDuration: 0, - chatController: chatController, - ); + // 更新通话消息状态为通话中 + 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; - } + // 从通话信息中获取 channelId + final channelId = callInfo['channelId'] as String?; + if (channelId == null || channelId.isEmpty) { + print('❌ [CallController] channelId 为空,无法加入 RTC 频道'); + SmartDialog.showToast('频道ID不存在'); + return false; + } - // 根据通话类型设置摄像头状态 - try { - if (callType == CallType.voice) { - // 语音通话:禁用视频(关闭摄像头) - await RTCManager.instance.disableVideo(); - print('📞 [CallController] 语音通话,已关闭摄像头'); - } else { - // 视频通话:启用视频(打开摄像头) - await RTCManager.instance.enableVideo(); - print('📞 [CallController] 视频通话,已打开摄像头'); - } - } catch (e) { - print('⚠️ [CallController] 设置视频状态失败: $e'); - // 继续执行,不阻止加入频道 - } + // 根据通话类型设置摄像头状态 + if (callType == CallType.voice) { + // 语音通话:禁用视频(关闭摄像头) + await RTCManager.instance.disableVideo(); + print('📞 [CallController] 语音通话,已关闭摄像头'); + } else { + // 视频通话:启用视频(打开摄像头) + await RTCManager.instance.enableVideo(); + print('📞 [CallController] 视频通话,已打开摄像头'); + } - // 加入 RTC 频道,接听通话 - try { - await joinChannel(channelId); - print('✅ [CallController] 已加入 RTC 频道: $channelId'); - } catch (e) { - print('❌ [CallController] 加入 RTC 频道失败: $e'); - SmartDialog.showToast('加入通话频道失败'); - return false; - } + // 加入 RTC 频道,接听通话 + await joinChannel(channelId); + print('✅ [CallController] 已加入 RTC 频道: $channelId'); - return true; - } catch (e) { - print('❌ [CallController] 接听通话失败: $e'); - SmartDialog.showToast('接听通话失败: $e'); - return false; - } + return true; } /// 拒绝通话 @@ -355,31 +313,25 @@ class CallController extends GetxController { required EMMessage message, ChatController? chatController, }) async { - try { - print('📞 [CallController] 拒绝通话'); + print('📞 [CallController] 拒绝通话'); - // 停止播放来电铃声(已拒绝) - stopCallAudio(); + // 停止播放来电铃声(已拒绝) + stopCallAudio(); - // 更新通话消息状态为已拒绝 - await _updateCallMessageStatus( - message: message, - callStatus: 'rejected', - chatController: chatController, - ); + // 更新通话消息状态为已拒绝 + await _updateCallMessageStatus( + message: message, + callStatus: 'rejected', + chatController: chatController, + ); - // 清理通话会话 - currentCall.value = null; + // 清理通话会话 + currentCall.value = null; - // TODO: 这里可以集成实际的通话SDK,拒绝通话 - // 例如:await RTCManager.instance.rejectCall(message.from ?? ''); + // TODO: 这里可以集成实际的通话SDK,拒绝通话 + // 例如:await RTCManager.instance.rejectCall(message.from ?? ''); - return true; - } catch (e) { - print('❌ [CallController] 拒绝通话失败: $e'); - SmartDialog.showToast('拒绝通话失败: $e'); - return false; - } + return true; } /// 取消通话 @@ -389,36 +341,30 @@ class CallController extends GetxController { EMMessage? message, ChatController? chatController, }) async { - try { - print('📞 [CallController] 取消通话'); - - // 如果有消息,更新通话消息状态为已取消 - if (message != null) { - await _updateCallMessageStatus( - message: message, - callStatus: 'cancelled', - chatController: chatController, - ); - } + print('📞 [CallController] 取消通话'); - // 停止播放来电铃声(已取消) - stopCallAudio(); + // 如果有消息,更新通话消息状态为已取消 + if (message != null) { + await _updateCallMessageStatus( + message: message, + callStatus: 'cancelled', + chatController: chatController, + ); + } - // 停止计时 - _stopCallTimer(); + // 停止播放来电铃声(已取消) + stopCallAudio(); - // 清理通话会话 - currentCall.value = null; + // 停止计时 + _stopCallTimer(); - // TODO: 这里可以集成实际的通话SDK,取消通话 - // 例如:await RTCManager.instance.cancelCall(); + // 清理通话会话 + currentCall.value = null; - return true; - } catch (e) { - print('❌ [CallController] 取消通话失败: $e'); - SmartDialog.showToast('取消通话失败: $e'); - return false; - } + // TODO: 这里可以集成实际的通话SDK,取消通话 + // 例如:await RTCManager.instance.cancelCall(); + + return true; } /// 结束通话(通话完成) @@ -429,38 +375,32 @@ class CallController extends GetxController { EMMessage? message, ChatController? chatController, }) async { - try { - print('📞 [CallController] 结束通话,时长: ${callDuration}秒'); - - // 停止播放来电铃声(通话结束) - stopCallAudio(); - - // 停止计时 - _stopCallTimer(); - - // 如果有消息,更新通话消息状态为通话中(显示时长) - if (message != null) { - await _updateCallMessageStatus( - message: message, - callStatus: 'calling', - callDuration: callDuration, - chatController: chatController, - ); - } + print('📞 [CallController] 结束通话,时长: ${callDuration}秒'); - // 清理通话会话和远端用户UID - currentCall.value = null; - remoteUid.value = null; + // 停止播放来电铃声(通话结束) + stopCallAudio(); - // TODO: 这里可以集成实际的通话SDK,结束通话 - // 例如:await RTCManager.instance.endCall(); + // 停止计时 + _stopCallTimer(); - return true; - } catch (e) { - print('❌ [CallController] 结束通话失败: $e'); - SmartDialog.showToast('结束通话失败: $e'); - return false; + // 如果有消息,更新通话消息状态为通话中(显示时长) + 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; } /// 开始通话计时 @@ -491,39 +431,34 @@ class CallController extends GetxController { String? channelId, ChatController? chatController, }) async { - try { - // 如果提供了 chatController,使用它发送消息 - if (chatController != null) { - return await chatController.sendCallMessage( - callType: callType, - callStatus: callStatus, - callDuration: callDuration, - channelId: channelId, - ); - } - - // 否则直接通过 IMManager 发送自定义消息 - final callParams = { - 'callType': callType, - 'callStatus': callStatus, - }; - if (callDuration != null) { - callParams['callDuration'] = callDuration.toString(); - } - if (channelId != null && channelId.isNotEmpty) { - callParams['channelId'] = channelId; - } - - final message = await IMManager.instance.sendCustomMessage( - targetUserId, - 'call', - callParams, + // 如果提供了 chatController,使用它发送消息 + if (chatController != null) { + return await chatController.sendCallMessage( + callType: callType, + callStatus: callStatus, + callDuration: callDuration, + channelId: channelId, ); - return message != null; - } catch (e) { - print('❌ [CallController] 发送通话消息失败: $e'); - return false; } + + // 否则直接通过 IMManager 发送自定义消息 + final callParams = { + 'callType': callType, + 'callStatus': callStatus, + }; + if (callDuration != null) { + callParams['callDuration'] = callDuration.toString(); + } + if (channelId != null && channelId.isNotEmpty) { + callParams['channelId'] = channelId; + } + + final message = await IMManager.instance.sendCustomMessage( + targetUserId, + 'call', + callParams, + ); + return message != null; } /// 更新通话消息状态(使用modifyMessage修改现有消息) @@ -533,104 +468,91 @@ class CallController extends GetxController { int? callDuration, ChatController? chatController, }) async { - try { - // 解析现有通话信息 - final callInfo = _parseCallInfo(message); - if (callInfo == null) { - return false; - } + // 解析现有通话信息 + final callInfo = _parseCallInfo(message); + if (callInfo == null) { + return false; + } - final callType = callInfo['callType'] as String? ?? 'voice'; - final messageId = message.msgId; + final callType = callInfo['callType'] as String? ?? 'voice'; + final messageId = message.msgId; - if (messageId.isEmpty) { - print('❌ [CallController] 消息ID为空,无法修改消息'); - return false; + 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(); } - // 如果是自定义消息,使用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, // 不修改扩展属性 + ); - // 创建新的消息体 - 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) { - // 更新消息体中的参数 - try { - 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(); - } + 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(); } - } catch (e) { - print('⚠️ [CallController] 更新本地消息列表失败: $e'); + // 注意:EMCustomMessageBody的params可能是只读的,这里可能需要重新创建消息 + // 暂时先通知UI更新,实际的消息体更新会在收到onMessageContentChanged回调时处理 + chatController.update(); } } } - - return success; } - // 如果不是自定义消息,返回失败 - return false; - } catch (e) { - print('❌ [CallController] 更新通话消息状态失败: $e'); - return false; + + return success; } + // 如果不是自定义消息,返回失败 + return false; } /// 从自定义消息中解析通话信息 Map? _parseCallInfo(EMMessage message) { - try { - 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'], - }; - } + 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'], + }; } - } catch (e) { - print('解析通话信息失败: $e'); } return null; } @@ -648,14 +570,9 @@ class CallController extends GetxController { return; // 已经在播放中 } - try { - _isPlayingCallAudio = true; - print('🔊 [CallController] 开始播放来电铃声'); - await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', ''))); - } catch (e) { - print('❌ [CallController] 播放来电铃声失败: $e'); - _isPlayingCallAudio = false; - } + _isPlayingCallAudio = true; + print('🔊 [CallController] 开始播放来电铃声'); + await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', ''))); } /// 停止播放来电铃声 @@ -665,13 +582,9 @@ class CallController extends GetxController { return; // 没有在播放 } - try { - _isPlayingCallAudio = false; - print('🔇 [CallController] 停止播放来电铃声'); - await _callAudioPlayer.stop(); - } catch (e) { - print('❌ [CallController] 停止播放来电铃声失败: $e'); - } + _isPlayingCallAudio = false; + print('🔇 [CallController] 停止播放来电铃声'); + await _callAudioPlayer.stop(); } Future joinChannel(String channelName) async { @@ -679,6 +592,25 @@ class CallController extends GetxController { 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, @@ -694,31 +626,28 @@ class CallController extends GetxController { int uid, ClientRoleType roleType, ) async { - try { - 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('加入通话成功'); - } catch (e) { - SmartDialog.showToast('加入频道失败:$e'); + 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 { @@ -742,49 +671,27 @@ class CallController extends GetxController { /// 切换麦克风状态 Future toggleMic() async { - try { - isMicMuted.value = !isMicMuted.value; - await RTCManager.instance.muteLocalAudio(isMicMuted.value); - print('📞 [CallController] 麦克风${isMicMuted.value ? "已静音" : "已取消静音"}'); - } catch (e) { - print('❌ [CallController] 切换麦克风状态失败: $e'); - // 如果失败,恢复状态 - isMicMuted.value = !isMicMuted.value; - } + isMicMuted.value = !isMicMuted.value; + await RTCManager.instance.muteLocalAudio(isMicMuted.value); + print('📞 [CallController] 麦克风${isMicMuted.value ? "已静音" : "已取消静音"}'); } /// 切换扬声器状态 Future toggleSpeaker() async { - try { - isSpeakerOn.value = !isSpeakerOn.value; - await RTCManager.instance.setEnableSpeakerphone(isSpeakerOn.value); - print('📞 [CallController] 扬声器${isSpeakerOn.value ? "已开启" : "已关闭"}'); - } catch (e) { - print('❌ [CallController] 切换扬声器状态失败: $e'); - // 如果失败,恢复状态 - isSpeakerOn.value = !isSpeakerOn.value; - } + isSpeakerOn.value = !isSpeakerOn.value; + await RTCManager.instance.setEnableSpeakerphone(isSpeakerOn.value); + print('📞 [CallController] 扬声器${isSpeakerOn.value ? "已开启" : "已关闭"}'); } /// 挂断通话 Future hangUpCall() async { - try { - // 离开RTC频道 - await RTCManager.instance.leaveChannel(); - - // 结束通话(传递通话时长) - await endCall(callDuration: callDurationSeconds.value); - - print('✅ [CallController] 通话已挂断'); - } catch (e) { - print('❌ [CallController] 挂断通话失败: $e'); - // 即使离开频道失败,也结束通话 - try { - await endCall(callDuration: callDurationSeconds.value); - } catch (e2) { - print('❌ [CallController] 结束通话失败: $e2'); - } - } + // 离开RTC频道 + await RTCManager.instance.leaveChannel(); + + // 结束通话(传递通话时长) + await endCall(callDuration: callDurationSeconds.value); + + print('✅ [CallController] 通话已挂断'); } @override diff --git a/lib/pages/message/video_call_page.dart b/lib/pages/message/video_call_page.dart index 80ec02f..55c50fa 100644 --- a/lib/pages/message/video_call_page.dart +++ b/lib/pages/message/video_call_page.dart @@ -110,32 +110,28 @@ class _VideoCallPageState extends State { } // 如果没有传入 userData,尝试从 ConversationController 获取 - try { - if (Get.isRegistered()) { - final conversationController = Get.find(); - - // 先从缓存中获取 - final cachedUserInfo = conversationController.getCachedUserInfo(widget.targetUserId); - if (cachedUserInfo != null && (cachedUserInfo.nickName != null || cachedUserInfo.avatarUrl != null)) { - setState(() { - _targetUserName = cachedUserInfo.nickName ?? widget.targetUserId; - _targetAvatarUrl = cachedUserInfo.avatarUrl; - }); - return; - } + if (Get.isRegistered()) { + final conversationController = Get.find(); + + // 先从缓存中获取 + final cachedUserInfo = conversationController.getCachedUserInfo(widget.targetUserId); + if (cachedUserInfo != null && (cachedUserInfo.nickName != null || cachedUserInfo.avatarUrl != null)) { + setState(() { + _targetUserName = cachedUserInfo.nickName ?? widget.targetUserId; + _targetAvatarUrl = cachedUserInfo.avatarUrl; + }); + return; + } - // 如果缓存中没有,尝试从 IM 加载 - 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; - }); - return; - } + // 如果缓存中没有,尝试从 IM 加载 + 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; + }); + return; } - } catch (e) { - print('⚠️ [VideoCallPage] 加载用户信息失败: $e'); } // 如果都获取不到,使用默认值 @@ -619,73 +615,54 @@ class _VideoCallPageState extends State { /// 接听通话 Future _acceptCall() async { - try { - if (widget.callMessage == null) { - SmartDialog.showToast('未找到通话邀请消息'); - return; - } + if (widget.callMessage == null) { + SmartDialog.showToast('未找到通话邀请消息'); + return; + } - // 尝试获取 ChatController - ChatController? chatController; - try { - final tag = 'chat_${widget.targetUserId}'; - if (Get.isRegistered(tag: tag)) { - chatController = Get.find(tag: tag); - } - } catch (e) { - print('⚠️ [VideoCallPage] 获取ChatController失败: $e'); - } - - final accepted = await _callController.acceptCall( - message: widget.callMessage!, - chatController: chatController, - ); - - if (accepted) { - // 通话已接通,UI会自动更新 - print('✅ [VideoCallPage] 通话已接通'); - } else { - SmartDialog.showToast('接听失败'); - } - } catch (e) { - print('❌ [VideoCallPage] 接听通话失败: $e'); - SmartDialog.showToast('接听失败: $e'); + // 尝试获取 ChatController + ChatController? chatController; + final tag = 'chat_${widget.targetUserId}'; + if (Get.isRegistered(tag: tag)) { + chatController = Get.find(tag: tag); + } + + final accepted = await _callController.acceptCall( + message: widget.callMessage!, + chatController: chatController, + ); + + if (accepted) { + // 通话已接通,UI会自动更新 + print('✅ [VideoCallPage] 通话已接通'); + } else { + SmartDialog.showToast('接听失败'); } } /// 拒绝通话 Future _rejectCall() async { - try { - // 尝试获取 ChatController - ChatController? chatController; - try { - final tag = 'chat_${widget.targetUserId}'; - if (Get.isRegistered(tag: tag)) { - chatController = Get.find(tag: tag); - } - } catch (e) { - print('⚠️ [VideoCallPage] 获取ChatController失败: $e'); - } - - if (widget.callMessage == null) { - // 即使没有消息,也执行拒绝操作(关闭页面) - await _callController.endCall(callDuration: 0); - Get.back(); - return; - } - - final rejected = await _callController.rejectCall( - message: widget.callMessage!, - chatController: chatController, - ); - - if (rejected) { - // 拒绝成功,返回上一页 - Get.back(); - } - } catch (e) { - print('❌ [VideoCallPage] 拒绝通话失败: $e'); - // 即使失败也返回上一页 + // 尝试获取 ChatController + ChatController? chatController; + final tag = 'chat_${widget.targetUserId}'; + if (Get.isRegistered(tag: tag)) { + chatController = Get.find(tag: tag); + } + + if (widget.callMessage == null) { + // 即使没有消息,也执行拒绝操作(关闭页面) + await _callController.endCall(callDuration: 0); + Get.back(); + return; + } + + final rejected = await _callController.rejectCall( + message: widget.callMessage!, + chatController: chatController, + ); + + if (rejected) { + // 拒绝成功,返回上一页 Get.back(); } } diff --git a/lib/service/live_chat_message_service.dart b/lib/service/live_chat_message_service.dart index e433d38..d26db52 100644 --- a/lib/service/live_chat_message_service.dart +++ b/lib/service/live_chat_message_service.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:agora_rtm/agora_rtm.dart'; import 'package:dating_touchme_app/controller/discover/room_controller.dart'; import 'package:dating_touchme_app/controller/global.dart'; +import 'package:dating_touchme_app/controller/message/call_controller.dart'; import 'package:dating_touchme_app/controller/overlay_controller.dart'; import 'package:dating_touchme_app/model/live/live_chat_message.dart'; import 'package:dating_touchme_app/pages/discover/live_end_page.dart'; @@ -75,6 +76,21 @@ class LiveChatMessageService { return; } + // 处理通话消息 + if (messageData['type'] == 'call_message') { + final event = messageData['event'] as String?; + if (event == 'accept') { + // 接收方收到 accept 消息,设置远端用户 UID + final uid = messageData['uid']; + if (uid != null && Get.isRegistered()) { + final callController = Get.find(); + callController.remoteUid.value = uid is int ? uid : int.tryParse(uid.toString()); + print('📞 [LiveChatMessageService] 收到 accept 消息,设置 remoteUid: ${callController.remoteUid.value}'); + } + } + return; + } + // 只处理聊天消息类型 if (messageData['type'] == 'chat_message') { final chatMessage = LiveChatMessage.fromJson(messageData);