|
|
|
@ -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 = <String, String>{ |
|
|
|
'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 = <String, String>{ |
|
|
|
'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 = <String, String>{ |
|
|
|
'callType': callType, |
|
|
|
'callStatus': callStatus, |
|
|
|
}; |
|
|
|
if (callDuration != null) { |
|
|
|
callParams['callDuration'] = callDuration.toString(); |
|
|
|
} |
|
|
|
|
|
|
|
// 如果是自定义消息,使用modifyMessage修改 |
|
|
|
if (message.body.type == MessageType.CUSTOM) { |
|
|
|
// 构建新的参数 |
|
|
|
final callParams = <String, String>{ |
|
|
|
'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<String, String>.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<String, String>.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<String, dynamic>? _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<void> 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<bool> _ensureRtcPermissions() async { |
|
|
|
@ -742,49 +671,27 @@ class CallController extends GetxController { |
|
|
|
|
|
|
|
/// 切换麦克风状态 |
|
|
|
Future<void> 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<void> 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<void> 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 |
|
|
|
|