Browse Source

feat(call): 添加通话挂断消息处理和频道管理功能

- 添加 RTM 消息监听器处理挂断事件
- 实现 _handleHangupMessage 方法处理对方挂断逻辑
- 添加 _callChannelId 和 _callUid 成员变量管理通话频道信息
- 在发起和接听通话时清空之前的通话信息
- 在挂断通话时发送 RTM 挂断消息并取消订阅频道
- 在结束通话时清理保存的 channelId 和 uid
- 添加 OverlayController 依赖用于关闭通话小窗口
- 优化代码格式和打印日志的换行处理
master
Jolie 3 months ago
parent
commit
b8ff22ab5e
1 changed files with 141 additions and 44 deletions
  1. 185
      lib/controller/message/call_controller.dart

185
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:im_flutter_sdk/im_flutter_sdk.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import '../overlay_controller.dart';
import 'chat_controller.dart'; import 'chat_controller.dart';
/// ///
@ -66,7 +67,7 @@ class CallController extends GetxController {
} }
CallController({NetworkService? networkService}) CallController({NetworkService? networkService})
: _networkService = networkService ?? Get.find<NetworkService>();
: _networkService = networkService ?? Get.find<NetworkService>();
final NetworkService _networkService; final NetworkService _networkService;
@ -78,25 +79,29 @@ class CallController extends GetxController {
// //
final Rx<CallSession?> currentCall = Rx<CallSession?>(null); final Rx<CallSession?> currentCall = Rx<CallSession?>(null);
// //
Timer? _callTimer; Timer? _callTimer;
int _callDurationSeconds = 0; int _callDurationSeconds = 0;
final RxInt callDurationSeconds = RxInt(0); final RxInt callDurationSeconds = RxInt(0);
// UID // UID
final Rxn<int> remoteUid = Rxn<int>(); final Rxn<int> remoteUid = Rxn<int>();
// //
final RxBool isMicMuted = false.obs; final RxBool isMicMuted = false.obs;
// //
final RxBool isSpeakerOn = false.obs; final RxBool isSpeakerOn = false.obs;
// //
final AudioPlayer _callAudioPlayer = AudioPlayer(); final AudioPlayer _callAudioPlayer = AudioPlayer();
bool _isPlayingCallAudio = false; bool _isPlayingCallAudio = false;
// ID和UIDRTM消息
String? _callChannelId;
int? _callUid;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
@ -105,14 +110,16 @@ class CallController extends GetxController {
_callAudioPlayer.onPlayerComplete.listen((_) async { _callAudioPlayer.onPlayerComplete.listen((_) async {
if (_isPlayingCallAudio) { if (_isPlayingCallAudio) {
// //
await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', '')));
await _callAudioPlayer.play(
AssetSource(Assets.audioCall.replaceFirst('assets/', '')),
);
} }
}); });
// RTM RTM // RTM RTM
_registerRtmMessageListener(); _registerRtmMessageListener();
} }
/// RTM /// RTM
void _registerRtmMessageListener() { void _registerRtmMessageListener() {
RTMManager.instance.onMessageEvent = (MessageEvent event) { RTMManager.instance.onMessageEvent = (MessageEvent event) {
@ -120,7 +127,7 @@ class CallController extends GetxController {
}; };
print('✅ [CallController] RTM 消息监听器已注册'); print('✅ [CallController] RTM 消息监听器已注册');
} }
/// RTM /// RTM
void _handleRtmMessage(MessageEvent event) { void _handleRtmMessage(MessageEvent event) {
try { try {
@ -133,10 +140,10 @@ class CallController extends GetxController {
} else { } else {
messageText = event.message.toString(); messageText = event.message.toString();
} }
final messageData = json.decode(messageText) as Map<String, dynamic>; final messageData = json.decode(messageText) as Map<String, dynamic>;
print('📥 [CallController] 收到 RTM 消息: $messageData'); print('📥 [CallController] 收到 RTM 消息: $messageData');
// //
if (messageData['type'] == 'call_message') { if (messageData['type'] == 'call_message') {
final event = messageData['event'] as String?; final event = messageData['event'] as String?;
@ -145,8 +152,14 @@ class CallController extends GetxController {
final uid = messageData['uid']; final uid = messageData['uid'];
if (uid != null) { if (uid != null) {
remoteUid.value = uid is int ? uid : int.tryParse(uid.toString()); 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) { } catch (e) {
@ -213,22 +226,26 @@ class CallController extends GetxController {
SmartDialog.showToast('已有通话正在进行中'); SmartDialog.showToast('已有通话正在进行中');
return false; return false;
} }
// UID
// UID和通话信息
remoteUid.value = null; remoteUid.value = null;
_callChannelId = null;
_callUid = null;
print('📞 [CallController] 发起${callType == CallType.video ? "视频" : "语音"}通话,目标用户: $targetUserId');
print(
'📞 [CallController] 发起${callType == CallType.video ? "视频" : "语音"}通话,目标用户: $targetUserId',
);
// RTC // RTC
final type = callType == CallType.video ? 2 : 1; // 12 final type = callType == CallType.video ? 2 : 1; // 12
final channelData = await createOneOnOneRtcChannel(type: type); final channelData = await createOneOnOneRtcChannel(type: type);
if (channelData == null) { if (channelData == null) {
print('❌ [CallController] 创建RTC频道失败,无法发起通话'); print('❌ [CallController] 创建RTC频道失败,无法发起通话');
SmartDialog.showToast('创建通话频道失败'); SmartDialog.showToast('创建通话频道失败');
return false; return false;
} }
print('✅ [CallController] RTC频道创建成功: ${channelData.channelId}'); print('✅ [CallController] RTC频道创建成功: ${channelData.channelId}');
// //
@ -273,11 +290,11 @@ class CallController extends GetxController {
rtcType: RTCType.call, rtcType: RTCType.call,
); );
print('✅ [CallController] 已加入 RTC 频道: ${channelData.channelId}'); print('✅ [CallController] 已加入 RTC 频道: ${channelData.channelId}');
// RTC RTM // RTC RTM
await RTMManager.instance.subscribe(channelData.channelId); await RTMManager.instance.subscribe(channelData.channelId);
print('✅ [CallController] 已订阅 RTM 频道: ${channelData.channelId}'); print('✅ [CallController] 已订阅 RTM 频道: ${channelData.channelId}');
return true; return true;
} }
@ -297,10 +314,14 @@ class CallController extends GetxController {
final callTypeStr = callInfo['callType'] as String?; final callTypeStr = callInfo['callType'] as String?;
final callType = callTypeStr == 'video' ? CallType.video : CallType.voice; final callType = callTypeStr == 'video' ? CallType.video : CallType.voice;
print('📞 [CallController] 接听${callType == CallType.video ? "视频" : "语音"}通话');
// UID
print(
'📞 [CallController] 接听${callType == CallType.video ? "视频" : "语音"}通话',
);
// UID和通话信息
remoteUid.value = null; remoteUid.value = null;
_callChannelId = null;
_callUid = null;
// //
final session = CallSession( final session = CallSession(
@ -334,6 +355,9 @@ class CallController extends GetxController {
return false; return false;
} }
// channelId
_callChannelId = channelId;
// //
if (callType == CallType.voice) { if (callType == CallType.voice) {
// //
@ -443,6 +467,10 @@ class CallController extends GetxController {
currentCall.value = null; currentCall.value = null;
remoteUid.value = null; remoteUid.value = null;
// channelId uid
_callChannelId = null;
_callUid = null;
// TODO: SDK // TODO: SDK
// await RTCManager.instance.endCall(); // 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修改消息 // 使modifyMessage修改消息
final success = await IMManager.instance.modifyMessage( final success = await IMManager.instance.modifyMessage(
@ -532,18 +557,24 @@ class CallController extends GetxController {
); );
if (success) { if (success) {
print('✅ [CallController] 消息修改成功: messageId=$messageId, callStatus=$callStatus');
print(
'✅ [CallController] 消息修改成功: messageId=$messageId, callStatus=$callStatus',
);
// chatController // chatController
if (chatController != null) { if (chatController != null) {
// //
final index = chatController.messages.indexWhere((msg) => msg.msgId == messageId);
final index = chatController.messages.indexWhere(
(msg) => msg.msgId == messageId,
);
if (index != -1) { if (index != -1) {
final updatedMessage = chatController.messages[index]; final updatedMessage = chatController.messages[index];
if (updatedMessage.body.type == MessageType.CUSTOM) { if (updatedMessage.body.type == MessageType.CUSTOM) {
final customBody = updatedMessage.body as EMCustomMessageBody; final customBody = updatedMessage.body as EMCustomMessageBody;
// Map并更新 // Map并更新
final updatedParams = Map<String, String>.from(customBody.params ?? {});
final updatedParams = Map<String, String>.from(
customBody.params ?? {},
);
updatedParams['callType'] = callType; updatedParams['callType'] = callType;
updatedParams['callStatus'] = callStatus; updatedParams['callStatus'] = callStatus;
if (callDuration != null) { if (callDuration != null) {
@ -572,8 +603,8 @@ class CallController extends GetxController {
return { return {
'callType': params['callType'] ?? 'voice', 'callType': params['callType'] ?? 'voice',
'callStatus': params['callStatus'] ?? 'missed', 'callStatus': params['callStatus'] ?? 'missed',
'callDuration': params['callDuration'] != null
? int.tryParse(params['callDuration']!)
'callDuration': params['callDuration'] != null
? int.tryParse(params['callDuration']!)
: null, : null,
'channelId': params['channelId'], 'channelId': params['channelId'],
}; };
@ -594,10 +625,12 @@ class CallController extends GetxController {
if (_isPlayingCallAudio) { if (_isPlayingCallAudio) {
return; // return; //
} }
_isPlayingCallAudio = true; _isPlayingCallAudio = true;
print('🔊 [CallController] 开始播放来电铃声'); 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) { if (!_isPlayingCallAudio) {
return; // return; //
} }
_isPlayingCallAudio = false; _isPlayingCallAudio = false;
print('🔇 [CallController] 停止播放来电铃声'); print('🔇 [CallController] 停止播放来电铃声');
await _callAudioPlayer.stop(); await _callAudioPlayer.stop();
@ -617,13 +650,18 @@ class CallController extends GetxController {
final base = response.data; final base = response.data;
if (base.isSuccess && base.data != null) { if (base.isSuccess && base.data != null) {
rtcChannel.value = base.data; rtcChannel.value = base.data;
// UID
_callUid = base.data!.uid;
// RTM // RTM
await RTMManager.instance.subscribe(channelName); await RTMManager.instance.subscribe(channelName);
// //
final callSession = currentCall.value; final callSession = currentCall.value;
final callType = callSession?.callType == CallType.video ? 'video' : 'voice';
final callType = callSession?.callType == CallType.video
? 'video'
: 'voice';
// RTM UID // RTM UID
await RTMManager.instance.publishChannelMessage( await RTMManager.instance.publishChannelMessage(
@ -635,7 +673,7 @@ class CallController extends GetxController {
'event': 'accept', 'event': 'accept',
}), }),
); );
await _joinRtcChannel( await _joinRtcChannel(
base.data!.token, base.data!.token,
channelName, channelName,
@ -660,7 +698,7 @@ class CallController extends GetxController {
role: roleType, role: roleType,
rtcType: RTCType.call, rtcType: RTCType.call,
); );
final data = { final data = {
'channelId': channelName, 'channelId': channelName,
'seatNumber': 1, 'seatNumber': 1,
@ -683,7 +721,7 @@ class CallController extends GetxController {
} }
final permanentlyDenied = statuses.values.any( final permanentlyDenied = statuses.values.any(
(status) => status.isPermanentlyDenied,
(status) => status.isPermanentlyDenied,
); );
if (permanentlyDenied) { if (permanentlyDenied) {
SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限'); SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限');
@ -710,15 +748,72 @@ class CallController extends GetxController {
/// ///
Future<void> hangUpCall() async { Future<void> 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频道 // RTC频道
await RTCManager.instance.leaveChannel(); await RTCManager.instance.leaveChannel();
// RTM
if (_callChannelId != null && _callChannelId!.isNotEmpty) {
await RTMManager.instance.unsubscribe(_callChannelId!);
print('✅ [CallController] 已取消订阅 RTM 频道: $_callChannelId');
}
// //
await endCall(callDuration: callDurationSeconds.value); await endCall(callDuration: callDurationSeconds.value);
print('✅ [CallController] 通话已挂断'); print('✅ [CallController] 通话已挂断');
} }
///
Future<void> _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<OverlayController>()) {
final overlayController = Get.find<OverlayController>();
overlayController.hideVideoCall();
print('✅ [CallController] 已关闭通话小窗口');
}
// 退 VideoCallPage VideoCallPage
if (Get.currentRoute.contains('VideoCallPage')) {
Get.back();
print('✅ [CallController] 已退出 VideoCallPage');
}
}
@override @override
void onClose() { void onClose() {
stopCallAudio(); stopCallAudio();
@ -728,6 +823,8 @@ class CallController extends GetxController {
remoteUid.value = null; remoteUid.value = null;
isMicMuted.value = false; isMicMuted.value = false;
isSpeakerOn.value = false; isSpeakerOn.value = false;
_callChannelId = null;
_callUid = null;
_callAudioPlayer.dispose(); _callAudioPlayer.dispose();
super.onClose(); super.onClose();
} }

Loading…
Cancel
Save