import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:dating_touchme_app/controller/global.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/network/network_service.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; import 'package:dating_touchme_app/service/live_chat_message_service.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../model/live/live_chat_message.dart'; // 当前角色 enum CurrentRole { broadcaster, //主持 maleAudience, //男嘉宾 femaleAudience, //女嘉宾 audience, //观众 normalUser, //普通用户 } /// 直播房间相关控制器 class RoomController extends GetxController { RoomController({NetworkService? networkService}) : _networkService = networkService ?? Get.find(); final NetworkService _networkService; CurrentRole currentRole = CurrentRole.normalUser; bool isLive = false; /// 当前频道信息 final Rxn rtcChannel = Rxn(); final Rxn rtcChannelDetail = Rxn(); /// 聊天消息列表 final RxList chatMessages = [].obs; /// 消息服务实例 final LiveChatMessageService _messageService = LiveChatMessageService.instance; @override void onInit() { super.onInit(); // 注册消息监听 _registerMessageListener(); } @override void onClose() { super.onClose(); // 移除消息监听 _messageService.unregisterMessageListener(); } /// 注册消息监听 void _registerMessageListener() { _messageService.registerMessageListener( onMessageReceived: (message) { _addMessage(message); }, onMessageError: (error) { print('❌ 消息处理错误: $error'); }, ); } /// 添加消息到列表(带去重和数量限制) void _addMessage(LiveChatMessage message) { // 去重:检查是否已存在相同的消息(基于 userId + content + timestamp) final exists = chatMessages.any( (m) => m.userId == message.userId && m.content == message.content && (m.timestamp - message.timestamp).abs() < 1000, ); // 1秒内的相同消息视为重复 if (exists) { print('⚠️ 消息已存在,跳过添加'); return; } chatMessages.add(message); print('✅ 消息已添加到列表,当前消息数: ${chatMessages.length}'); // 限制消息数量,最多保留100条 if (chatMessages.length > 300) { chatMessages.removeAt(0); print('📝 消息列表已满,移除最旧的消息'); } } /// 调用接口创建 RTC 频道 Future createRtcChannel() async { final granted = await _ensureRtcPermissions(); if (!granted) return; try { final response = await _networkService.rtcApi.createRtcChannel(); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; currentRole = CurrentRole.broadcaster; isLive = true; await _joinRtcChannel( base.data!.token, base.data!.channelId, base.data!.uid, ClientRoleType.clientRoleBroadcaster, ); } else { final message = base.message.isNotEmpty ? base.message : '创建频道失败'; SmartDialog.showToast(message); } } catch (e) { SmartDialog.showToast('创建频道异常:$e'); } } Future joinChannel(String channelName) async { try { final response = await _networkService.rtcApi.getSwRtcToken(channelName); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; currentRole = CurrentRole.normalUser; await _joinRtcChannel( base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleAudience, ); } } catch (e) { SmartDialog.showToast('加入频道异常:$e'); } } Future _joinRtcChannel( String token, String channelName, int uid, ClientRoleType roleType, ) async { try { await _fetchRtcChannelDetail(channelName); await RTCManager.instance.joinChannel( token: token, channelId: channelName, uid: uid, role: roleType, ); } catch (e) { SmartDialog.showToast('加入频道失败:$e'); } } Future joinChat(CurrentRole role) async { final data = { 'channelId': RTCManager.instance.currentChannelId, 'seatNumber': role == CurrentRole.maleAudience ? 1 : 2, 'isMicrophoneOn': role != CurrentRole.normalUser ? true : false, 'isVideoOn': role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience ? true : false, }; final response = await _networkService.rtcApi.connectRtcChannel(data); if (!response.data.isSuccess) { SmartDialog.showToast(response.data.message); return; } currentRole = role; if (role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience) { await RTCManager.instance.publishVideo(role); } else { await RTCManager.instance.publishAudio(); } RtcSeatUserInfo userInfo = RtcSeatUserInfo( uid: rtcChannel.value?.uid, miId: GlobalData().userData?.id ?? '', userId: GlobalData().userData?.id ?? '', nickName: GlobalData().userData?.nickName ?? '', profilePhoto: GlobalData().userData?.profilePhoto ?? '', seatNumber: role == CurrentRole.maleAudience ? 1 : 2, isFriend: false, isMicrophoneOn: true, isVideoOn: role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience ? true : false, genderCode: GlobalData().userData?.genderCode ?? 0, ); final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, anchorInfo: rtcChannelDetail.value!.anchorInfo, maleInfo: role == CurrentRole.maleAudience ? userInfo : null, femaleInfo: role == CurrentRole.femaleAudience ? userInfo : null, ); rtcChannelDetail.value = newDetail; isLive = true; } Future leaveChat() async { final data = {'channelId': RTCManager.instance.currentChannelId}; final response = await _networkService.rtcApi.disconnectRtcChannel(data); if (response.data.isSuccess) { isLive = false; await RTCManager.instance.unpublish(currentRole); if (currentRole == CurrentRole.maleAudience) { final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, anchorInfo: rtcChannelDetail.value!.anchorInfo, maleInfo: null, femaleInfo: rtcChannelDetail.value!.femaleInfo, ); rtcChannelDetail.value = newDetail; } else if (currentRole == CurrentRole.femaleAudience) { final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, anchorInfo: rtcChannelDetail.value!.anchorInfo, maleInfo: rtcChannelDetail.value!.maleInfo, femaleInfo: null, ); rtcChannelDetail.value = newDetail; } currentRole = CurrentRole.normalUser; } } Future _fetchRtcChannelDetail(String channelName) async { try { final response = await _networkService.rtcApi.getRtcChannelDetail( channelName, ); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannelDetail.value = base.data; } } catch (e) { print('获取 RTC 频道详情失败:$e'); } } /// 发送公屏消息 Future sendChatMessage(String content) async { final channelName = RTCManager.instance.currentChannelId; final result = await _messageService.sendMessage( content: content, channelName: channelName, ); // 如果发送成功,立即添加到本地列表(优化体验,避免等待 RTM 回调) if (result.success && result.message != null) { _addMessage(result.message!); } } 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; } Future leaveChannel() async { isLive = false; if (currentRole == CurrentRole.maleAudience || currentRole == CurrentRole.femaleAudience) { await RTCManager.instance.unpublish(currentRole); } currentRole = CurrentRole.normalUser;; await RTCManager.instance.leaveChannel(); } /// 接收RTC消息 Future receiveRTCMessage(Map message) async { if (message['type'] == 'join_chat') { final response = await _networkService.rtcApi.getDatingRtcChannelUserDetail( rtcChannel.value!.channelId, message['uid'], ); if (!response.data.isSuccess) { return; } final currentDetail = rtcChannelDetail.value; if (currentDetail == null) { return; } if (message['role'] == 'male_audience') { final userData = response.data.data; // if (userData != null) { final maleInfo = RtcSeatUserInfo( miId: rtcChannelDetail.value!.anchorInfo!.miId, userId: rtcChannelDetail.value!.anchorInfo!.userId, nickName: rtcChannelDetail.value!.anchorInfo!.nickName, profilePhoto: rtcChannelDetail.value!.anchorInfo!.profilePhoto, genderCode: rtcChannelDetail.value!.anchorInfo!.genderCode, seatNumber: rtcChannelDetail.value!.anchorInfo!.seatNumber, isFriend: rtcChannelDetail.value!.anchorInfo!.isFriend, isMicrophoneOn: rtcChannelDetail.value!.anchorInfo!.isMicrophoneOn, isVideoOn: rtcChannelDetail.value!.anchorInfo!.isVideoOn, uid: message['uid'] is int ? message['uid'] as int : int.tryParse(message['uid']?.toString() ?? ''), ); final newDetail = RtcChannelDetail( channelId: currentDetail.channelId, anchorInfo: currentDetail.anchorInfo, maleInfo: maleInfo, femaleInfo: currentDetail.femaleInfo, ); rtcChannelDetail.value = newDetail; // } } else if (message['role'] == 'female_audience') { final userData = response.data.data; // if (userData != null) { final femaleInfo = RtcSeatUserInfo( miId: rtcChannelDetail.value!.anchorInfo!.miId, userId: rtcChannelDetail.value!.anchorInfo!.userId, nickName: rtcChannelDetail.value!.anchorInfo!.nickName, profilePhoto: rtcChannelDetail.value!.anchorInfo!.profilePhoto, genderCode: rtcChannelDetail.value!.anchorInfo!.genderCode, seatNumber: rtcChannelDetail.value!.anchorInfo!.seatNumber, isFriend: rtcChannelDetail.value!.anchorInfo!.isFriend, isMicrophoneOn: rtcChannelDetail.value!.anchorInfo!.isMicrophoneOn, isVideoOn: rtcChannelDetail.value!.anchorInfo!.isVideoOn, uid: message['uid'] is int ? message['uid'] as int : int.tryParse(message['uid']?.toString() ?? ''), ); final newDetail = RtcChannelDetail( channelId: currentDetail.channelId, anchorInfo: currentDetail.anchorInfo, maleInfo: currentDetail.maleInfo, femaleInfo: femaleInfo, ); rtcChannelDetail.value = newDetail; } // } }else if (message['type'] == 'leave_chat') { if (message['role'] == 'male_audience') { final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, anchorInfo: rtcChannelDetail.value!.anchorInfo, maleInfo: null, femaleInfo: rtcChannelDetail.value!.femaleInfo, ); rtcChannelDetail.value = newDetail; } else if (message['role'] == 'female_audience') { final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, anchorInfo: rtcChannelDetail.value!.anchorInfo, maleInfo: rtcChannelDetail.value!.maleInfo, femaleInfo: null, ); rtcChannelDetail.value = newDetail; } } } }