From 0297aec6c9a545eb6fa2f9c9e9a3d41b0c717300 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Tue, 30 Dec 2025 00:20:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(call):=20=E5=AE=9E=E7=8E=B0=E9=80=9A?= =?UTF-8?q?=E8=AF=9D=E6=8E=A5=E5=90=AC=E5=92=8CRTC=E9=A2=91=E9=81=93?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 集成permission_handler处理摄像头和麦克风权限 - 实现根据通话类型设置视频状态(语音通话关闭摄像头,视频通话开启摄像头) - 添加channelId验证和RTC频道加入逻辑 - 实现joinChannel和_joinRtcChannel方法处理RTC连接 - 添加权限检查和设置功能 - 优化VideoCallPage中通话消息处理逻辑 - 修复通话接听和拒绝时的消息传递问题 --- lib/controller/message/call_controller.dart | 103 +++++++++++++++++++- lib/im/im_manager.dart | 5 + lib/pages/message/video_call_page.dart | 68 +++---------- 3 files changed, 119 insertions(+), 57 deletions(-) diff --git a/lib/controller/message/call_controller.dart b/lib/controller/message/call_controller.dart index 69043a2..a4a1e22 100644 --- a/lib/controller/message/call_controller.dart +++ b/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 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 _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 _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(); diff --git a/lib/im/im_manager.dart b/lib/im/im_manager.dart index f1362d1..c75e738 100644 --- a/lib/im/im_manager.dart +++ b/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, )); } }, diff --git a/lib/pages/message/video_call_page.dart b/lib/pages/message/video_call_page.dart index 17378a6..bb354dc 100644 --- a/lib/pages/message/video_call_page.dart +++ b/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 { /// 接听通话 Future _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(tag: tag)) { chatController = Get.find(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 { /// 拒绝通话 Future _rejectCall() async { try { - // 尝试从 ChatController 获取最近的通话消息 + // 尝试获取 ChatController ChatController? chatController; - EMMessage? callMessage; - try { final tag = 'chat_${widget.targetUserId}'; if (Get.isRegistered(tag: tag)) { chatController = Get.find(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, );