Browse Source

feat(call): 实现通话接听和RTC频道加入功能

- 集成permission_handler处理摄像头和麦克风权限
- 实现根据通话类型设置视频状态(语音通话关闭摄像头,视频通话开启摄像头)
- 添加channelId验证和RTC频道加入逻辑
- 实现joinChannel和_joinRtcChannel方法处理RTC连接
- 添加权限检查和设置功能
- 优化VideoCallPage中通话消息处理逻辑
- 修复通话接听和拒绝时的消息传递问题
master
Jolie 3 months ago
parent
commit
0297aec6c9
3 changed files with 119 additions and 57 deletions
  1. 103
      lib/controller/message/call_controller.dart
  2. 5
      lib/im/im_manager.dart
  3. 68
      lib/pages/message/video_call_page.dart

103
lib/controller/message/call_controller.dart

@ -9,6 +9,7 @@ import 'package:dating_touchme_app/rtc/rtc_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';
import 'package:permission_handler/permission_handler.dart';
import 'chat_controller.dart';
@ -290,8 +291,39 @@ class CallController extends GetxController {
chatController: chatController,
);
// TODO: SDK
// await RTCManager.instance.acceptCall(targetUserId, callType);
// 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');
//
}
// RTC
try {
await joinChannel(channelId);
print('✅ [CallController] 已加入 RTC 频道: $channelId');
} catch (e) {
print('❌ [CallController] 加入 RTC 频道失败: $e');
SmartDialog.showToast('加入通话频道失败');
return false;
}
return true;
} catch (e) {
@ -577,6 +609,7 @@ class CallController extends GetxController {
'callDuration': params['callDuration'] != null
? int.tryParse(params['callDuration']!)
: null,
'channelId': params['channelId'],
};
}
}
@ -625,6 +658,72 @@ class CallController extends GetxController {
}
}
Future<void> joinChannel(String channelName) async {
final response = await _networkService.rtcApi.getSwRtcToken(channelName);
final base = response.data;
if (base.isSuccess && base.data != null) {
rtcChannel.value = base.data;
await _joinRtcChannel(
base.data!.token,
channelName,
base.data!.uid,
ClientRoleType.clientRoleBroadcaster,
);
}
}
Future<void> _joinRtcChannel(
String token,
String channelName,
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');
}
}
Future<bool> _ensureRtcPermissions() async {
final statuses = await [Permission.camera, Permission.microphone].request();
final allGranted = statuses.values.every((status) => status.isGranted);
if (allGranted) {
return true;
}
final permanentlyDenied = statuses.values.any(
(status) => status.isPermanentlyDenied,
);
if (permanentlyDenied) {
SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限');
await openAppSettings();
} else {
SmartDialog.showToast('请允许摄像头和麦克风权限以进入房间');
}
return false;
}
@override
void onClose() {
stopCallAudio();

5
lib/im/im_manager.dart

@ -1516,7 +1516,9 @@ class IMManager {
Get.to(() => VideoCallPage(
targetUserId: fromId,
isInitiator: false,
callType: callType,
channelId: channelId,
callMessage: message,
));
},
onAccept: () async {
@ -1549,6 +1551,9 @@ class IMManager {
Get.to(() => VideoCallPage(
targetUserId: fromId,
isInitiator: false,
callType: callType,
channelId: channelId,
callMessage: message,
));
}
},

68
lib/pages/message/video_call_page.dart

@ -23,6 +23,7 @@ class VideoCallPage extends StatefulWidget {
final String? channelId;
final MarriageData? userData;
final bool isInitiator; //
final EMMessage? callMessage; //
const VideoCallPage({
super.key,
@ -31,6 +32,7 @@ class VideoCallPage extends StatefulWidget {
this.channelId,
this.userData,
this.isInitiator = true,
this.callMessage,
});
@override
@ -564,46 +566,24 @@ class _VideoCallPageState extends State<VideoCallPage> {
///
Future<void> _acceptCall() async {
try {
// ChatController
if (widget.callMessage == null) {
SmartDialog.showToast('未找到通话邀请消息');
return;
}
// ChatController
ChatController? chatController;
EMMessage? callMessage;
try {
final tag = 'chat_${widget.targetUserId}';
if (Get.isRegistered<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(tag: tag);
//
final messages = chatController.messages;
for (var i = messages.length - 1; i >= 0; i--) {
final msg = messages[i];
if (msg.body.type == MessageType.CUSTOM) {
final customBody = msg.body as EMCustomMessageBody;
// event 'call'
if (customBody.event == 'call') {
final params = customBody.params;
// missed calling
if (params != null) {
final callStatus = params['callStatus'];
if (callStatus == 'missed' || callStatus == 'calling') {
callMessage = msg;
break;
}
}
}
}
}
}
} catch (e) {
print('⚠️ [VideoCallPage] 获取ChatController失败: $e');
}
if (callMessage == null) {
SmartDialog.showToast('未找到通话邀请消息');
return;
}
final accepted = await _callController.acceptCall(
message: callMessage,
message: widget.callMessage!,
chatController: chatController,
);
@ -622,48 +602,26 @@ class _VideoCallPageState extends State<VideoCallPage> {
///
Future<void> _rejectCall() async {
try {
// ChatController
// ChatController
ChatController? chatController;
EMMessage? callMessage;
try {
final tag = 'chat_${widget.targetUserId}';
if (Get.isRegistered<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(tag: tag);
//
final messages = chatController.messages;
for (var i = messages.length - 1; i >= 0; i--) {
final msg = messages[i];
if (msg.body.type == MessageType.CUSTOM) {
final customBody = msg.body as EMCustomMessageBody;
// event 'call'
if (customBody.event == 'call') {
final params = customBody.params;
// missed calling
if (params != null) {
final callStatus = params['callStatus'];
if (callStatus == 'missed' || callStatus == 'calling') {
callMessage = msg;
break;
}
}
}
}
}
}
} catch (e) {
print('⚠️ [VideoCallPage] 获取ChatController失败: $e');
}
if (callMessage == null) {
// 使
if (widget.callMessage == null) {
// 使
await _callController.endCall(callDuration: 0);
Get.back();
return;
}
final rejected = await _callController.rejectCall(
message: callMessage,
message: widget.callMessage!,
chatController: chatController,
);

Loading…
Cancel
Save