From b8ff22ab5e525c9676eed3ff0b32a4b4d1784330 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Tue, 30 Dec 2025 16:21:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(call):=20=E6=B7=BB=E5=8A=A0=E9=80=9A?= =?UTF-8?q?=E8=AF=9D=E6=8C=82=E6=96=AD=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=92=8C=E9=A2=91=E9=81=93=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 RTM 消息监听器处理挂断事件 - 实现 _handleHangupMessage 方法处理对方挂断逻辑 - 添加 _callChannelId 和 _callUid 成员变量管理通话频道信息 - 在发起和接听通话时清空之前的通话信息 - 在挂断通话时发送 RTM 挂断消息并取消订阅频道 - 在结束通话时清理保存的 channelId 和 uid - 添加 OverlayController 依赖用于关闭通话小窗口 - 优化代码格式和打印日志的换行处理 --- lib/controller/message/call_controller.dart | 185 +++++++++++++++----- 1 file changed, 141 insertions(+), 44 deletions(-) diff --git a/lib/controller/message/call_controller.dart b/lib/controller/message/call_controller.dart index 6114014..51be975 100644 --- a/lib/controller/message/call_controller.dart +++ b/lib/controller/message/call_controller.dart @@ -15,6 +15,7 @@ import 'package:get/get.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'package:permission_handler/permission_handler.dart'; +import '../overlay_controller.dart'; import 'chat_controller.dart'; /// 通话类型 @@ -66,7 +67,7 @@ class CallController extends GetxController { } CallController({NetworkService? networkService}) - : _networkService = networkService ?? Get.find(); + : _networkService = networkService ?? Get.find(); final NetworkService _networkService; @@ -78,25 +79,29 @@ class CallController extends GetxController { // 当前正在进行的通话 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; + // 当前通话的频道ID和UID(用于发送RTM消息) + String? _callChannelId; + int? _callUid; + @override void onInit() { super.onInit(); @@ -105,14 +110,16 @@ class CallController extends GetxController { _callAudioPlayer.onPlayerComplete.listen((_) async { if (_isPlayingCallAudio) { // 如果还在播放状态,重新播放(循环播放) - await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', ''))); + await _callAudioPlayer.play( + AssetSource(Assets.audioCall.replaceFirst('assets/', '')), + ); } }); - + // 注册 RTM 消息监听器,用于接收通话相关的 RTM 消息 _registerRtmMessageListener(); } - + /// 注册 RTM 消息监听器 void _registerRtmMessageListener() { RTMManager.instance.onMessageEvent = (MessageEvent event) { @@ -120,7 +127,7 @@ class CallController extends GetxController { }; print('✅ [CallController] RTM 消息监听器已注册'); } - + /// 处理 RTM 消息 void _handleRtmMessage(MessageEvent event) { try { @@ -133,10 +140,10 @@ class CallController extends GetxController { } 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?; @@ -145,8 +152,14 @@ class CallController extends GetxController { final uid = messageData['uid']; if (uid != null) { remoteUid.value = uid is int ? uid : int.tryParse(uid.toString()); - print('📞 [CallController] 收到 accept 消息,设置 remoteUid: ${remoteUid.value}'); + print( + '📞 [CallController] 收到 accept 消息,设置 remoteUid: ${remoteUid.value}', + ); } + } else if (event == 'hangup') { + // 收到挂断消息,执行退出逻辑 + print('📞 [CallController] 收到 hangup 消息,执行退出逻辑'); + _handleHangupMessage(); } } } catch (e) { @@ -213,22 +226,26 @@ class CallController extends GetxController { SmartDialog.showToast('已有通话正在进行中'); return false; } - - // 清空之前的远端用户UID + + // 清空之前的远端用户UID和通话信息 remoteUid.value = null; + _callChannelId = null; + _callUid = null; - print('📞 [CallController] 发起${callType == CallType.video ? "视频" : "语音"}通话,目标用户: $targetUserId'); + 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}'); // 创建通话会话 @@ -273,11 +290,11 @@ class CallController extends GetxController { rtcType: RTCType.call, ); print('✅ [CallController] 已加入 RTC 频道: ${channelData.channelId}'); - + // 加入 RTC 频道后订阅 RTM 频道 await RTMManager.instance.subscribe(channelData.channelId); print('✅ [CallController] 已订阅 RTM 频道: ${channelData.channelId}'); - + return true; } @@ -297,10 +314,14 @@ class CallController extends GetxController { final callTypeStr = callInfo['callType'] as String?; final callType = callTypeStr == 'video' ? CallType.video : CallType.voice; - print('📞 [CallController] 接听${callType == CallType.video ? "视频" : "语音"}通话'); - - // 清空之前的远端用户UID + print( + '📞 [CallController] 接听${callType == CallType.video ? "视频" : "语音"}通话', + ); + + // 清空之前的远端用户UID和通话信息 remoteUid.value = null; + _callChannelId = null; + _callUid = null; // 创建通话会话 final session = CallSession( @@ -334,6 +355,9 @@ class CallController extends GetxController { return false; } + // 保存 channelId 为全局变量 + _callChannelId = channelId; + // 根据通话类型设置摄像头状态 if (callType == CallType.voice) { // 语音通话:禁用视频(关闭摄像头) @@ -443,6 +467,10 @@ class CallController extends GetxController { currentCall.value = null; remoteUid.value = null; + // 清理保存的 channelId 和 uid + _callChannelId = null; + _callUid = null; + // TODO: 这里可以集成实际的通话SDK,结束通话 // 例如:await RTCManager.instance.endCall(); @@ -519,10 +547,7 @@ class CallController extends GetxController { } // 创建新的消息体 - final customBody = EMCustomMessageBody( - event: 'call', - params: callParams, - ); + final customBody = EMCustomMessageBody(event: 'call', params: callParams); // 使用modifyMessage修改消息 final success = await IMManager.instance.modifyMessage( @@ -532,18 +557,24 @@ class CallController extends GetxController { ); if (success) { - print('✅ [CallController] 消息修改成功: messageId=$messageId, callStatus=$callStatus'); - + print( + '✅ [CallController] 消息修改成功: messageId=$messageId, callStatus=$callStatus', + ); + // 如果提供了chatController,更新本地消息列表 if (chatController != null) { // 更新消息体中的参数 - final index = chatController.messages.indexWhere((msg) => msg.msgId == messageId); + 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 ?? {}); + final updatedParams = Map.from( + customBody.params ?? {}, + ); updatedParams['callType'] = callType; updatedParams['callStatus'] = callStatus; if (callDuration != null) { @@ -572,8 +603,8 @@ class CallController extends GetxController { return { 'callType': params['callType'] ?? 'voice', 'callStatus': params['callStatus'] ?? 'missed', - 'callDuration': params['callDuration'] != null - ? int.tryParse(params['callDuration']!) + 'callDuration': params['callDuration'] != null + ? int.tryParse(params['callDuration']!) : null, 'channelId': params['channelId'], }; @@ -594,10 +625,12 @@ class CallController extends GetxController { if (_isPlayingCallAudio) { return; // 已经在播放中 } - + _isPlayingCallAudio = true; print('🔊 [CallController] 开始播放来电铃声'); - await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', ''))); + await _callAudioPlayer.play( + AssetSource(Assets.audioCall.replaceFirst('assets/', '')), + ); } /// 停止播放来电铃声 @@ -606,7 +639,7 @@ class CallController extends GetxController { if (!_isPlayingCallAudio) { return; // 没有在播放 } - + _isPlayingCallAudio = false; print('🔇 [CallController] 停止播放来电铃声'); await _callAudioPlayer.stop(); @@ -617,13 +650,18 @@ class CallController extends GetxController { final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; - + + // 保存 UID 为全局变量 + _callUid = base.data!.uid; + // 订阅 RTM 频道 await RTMManager.instance.subscribe(channelName); - + // 获取当前通话信息 final callSession = currentCall.value; - final callType = callSession?.callType == CallType.video ? 'video' : 'voice'; + final callType = callSession?.callType == CallType.video + ? 'video' + : 'voice'; // 发布 RTM 消息,包含 UID 和通话相关字段 await RTMManager.instance.publishChannelMessage( @@ -635,7 +673,7 @@ class CallController extends GetxController { 'event': 'accept', }), ); - + await _joinRtcChannel( base.data!.token, channelName, @@ -660,7 +698,7 @@ class CallController extends GetxController { role: roleType, rtcType: RTCType.call, ); - + final data = { 'channelId': channelName, 'seatNumber': 1, @@ -683,7 +721,7 @@ class CallController extends GetxController { } final permanentlyDenied = statuses.values.any( - (status) => status.isPermanentlyDenied, + (status) => status.isPermanentlyDenied, ); if (permanentlyDenied) { SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限'); @@ -710,15 +748,72 @@ class CallController extends GetxController { /// 挂断通话 Future hangUpCall() async { + // 发送 RTM 挂断消息 + if (_callChannelId != null && + _callChannelId!.isNotEmpty && + _callUid != null) { + final callSession = currentCall.value; + final callType = callSession?.callType == CallType.video + ? 'video' + : 'voice'; + + await RTMManager.instance.publishChannelMessage( + channelName: _callChannelId!, + message: json.encode({ + 'type': 'call_message', + 'uid': _callUid!, + 'callType': callType, + 'event': 'hangup', + }), + ); + print( + '✅ [CallController] 已发送 RTM 挂断消息,channelId: $_callChannelId, uid: $_callUid', + ); + } + // 离开RTC频道 await RTCManager.instance.leaveChannel(); - + + // 取消订阅 RTM 频道 + if (_callChannelId != null && _callChannelId!.isNotEmpty) { + await RTMManager.instance.unsubscribe(_callChannelId!); + print('✅ [CallController] 已取消订阅 RTM 频道: $_callChannelId'); + } + // 结束通话(传递通话时长) await endCall(callDuration: callDurationSeconds.value); - + print('✅ [CallController] 通话已挂断'); } + /// 处理挂断消息(对方挂断时调用) + Future _handleHangupMessage() async { + // 离开RTC频道 + await RTCManager.instance.leaveChannel(); + + // 取消订阅 RTM 频道 + if (_callChannelId != null && _callChannelId!.isNotEmpty) { + await RTMManager.instance.unsubscribe(_callChannelId!); + print('✅ [CallController] 已取消订阅 RTM 频道: $_callChannelId'); + } + + // 结束通话 + await endCall(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'); + } + } + @override void onClose() { stopCallAudio(); @@ -728,6 +823,8 @@ class CallController extends GetxController { remoteUid.value = null; isMicMuted.value = false; isSpeakerOn.value = false; + _callChannelId = null; + _callUid = null; _callAudioPlayer.dispose(); super.onClose(); }