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: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 = networkService ?? Get.find<NetworkService>();
final NetworkService _networkService;
@ -78,25 +79,29 @@ class CallController extends GetxController {
//
final Rx<CallSession?> currentCall = Rx<CallSession?>(null);
//
Timer? _callTimer;
int _callDurationSeconds = 0;
final RxInt callDurationSeconds = RxInt(0);
// UID
final Rxn<int> remoteUid = Rxn<int>();
//
final RxBool isMicMuted = false.obs;
//
final RxBool isSpeakerOn = false.obs;
//
final AudioPlayer _callAudioPlayer = AudioPlayer();
bool _isPlayingCallAudio = false;
// ID和UIDRTM消息
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<String, dynamic>;
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; // 12
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<String, String>.from(customBody.params ?? {});
final updatedParams = Map<String, String>.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<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频道
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<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
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();
}

Loading…
Cancel
Save