You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

459 lines
15 KiB

import 'dart:convert';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:dating_touchme_app/controller/global.dart';
import 'package:dating_touchme_app/model/live/gift_product_model.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/rtc/rtm_manager.dart';
import 'package:dating_touchme_app/service/live_chat_message_service.dart';
import 'package:flutter/material.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';
import '../../pages/mine/real_name_page.dart';
import '../../pages/setting/match_league_page.dart';
import '../../pages/setting/match_spread_page.dart';
// 当前角色
enum CurrentRole {
broadcaster, //主持
maleAudience, //男嘉宾
femaleAudience, //女嘉宾
audience, //观众
normalUser, //普通用户
}
/// 直播房间相关控制器
class RoomController extends GetxController with WidgetsBindingObserver {
RoomController({NetworkService? networkService})
: _networkService = networkService ?? Get.find<NetworkService>();
final NetworkService _networkService;
CurrentRole currentRole = CurrentRole.normalUser;
var isLive = false.obs;
var matchmakerFlag = false.obs;
/// 当前频道信息
final Rxn<RtcChannelData> rtcChannel = Rxn<RtcChannelData>();
final Rxn<RtcChannelDetail> rtcChannelDetail = Rxn<RtcChannelDetail>();
/// 聊天消息列表
final RxList<LiveChatMessage> chatMessages = <LiveChatMessage>[].obs;
/// 礼物产品列表
final RxList<GiftProductModel> giftProducts = <GiftProductModel>[].obs;
/// 消息服务实例
final LiveChatMessageService _messageService = LiveChatMessageService.instance;
// matchmakerFlag
@override
void onInit() {
super.onInit();
WidgetsBinding.instance.addObserver(this);
matchmakerFlag.value = GlobalData().userData!.matchmakerFlag!;
// 注册消息监听
_registerMessageListener();
// 加载礼物产品列表
loadGiftProducts();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if(state == AppLifecycleState.resumed){
// print('_handleAppResumed');
}
switch (state) {
case AppLifecycleState.resumed:
print('_handleAppResumed');
break;
case AppLifecycleState.paused:
// _handleAppPaused();
print('_handleAppPaused');
break;
case AppLifecycleState.inactive:
// _handleAppInactive();
print('_handleAppInactive');
break;
default:
break;
}
}
@override
void onClose() {
super.onClose();
WidgetsBinding.instance.removeObserver(this);
// 移除消息监听
_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<void> createRtcChannel() async {
if(isLive.value){
return;
}
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.value = 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<void> 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<void> _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<void> 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.value = true;
}
Future<void> leaveChat() async {
final data = {'channelId': RTCManager.instance.currentChannelId};
final response = await _networkService.rtcApi.disconnectRtcChannel(data);
if (response.data.isSuccess) {
isLive.value = 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<void> _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<void> 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<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;
}
Future<void> leaveChannel() async {
// 如果是主播,发送结束直播消息
if (currentRole == CurrentRole.broadcaster) {
final channelId = RTCManager.instance.currentChannelId;
if (channelId != null && channelId.isNotEmpty) {
await RTMManager.instance.publishChannelMessage(
channelName: channelId,
message: json.encode({'type': 'end_live'}),
);
}
}
isLive.value = false;
if (currentRole == CurrentRole.maleAudience ||
currentRole == CurrentRole.femaleAudience) {
await RTCManager.instance.unpublish(currentRole);
}
currentRole = CurrentRole.normalUser;
await RTCManager.instance.leaveChannel();
}
/// 加载礼物产品列表
Future<void> loadGiftProducts() async {
try {
final response = await _networkService.rtcApi.listGiftProduct();
final base = response.data;
if (base.isSuccess && base.data != null) {
giftProducts.assignAll(base.data!);
print('✅ 礼物产品列表加载成功,共 ${giftProducts.length}');
} else {
print('❌ 加载礼物产品列表失败: ${base.message}');
}
} catch (e) {
print('❌ 加载礼物产品列表异常: $e');
}
}
/// 接收RTC消息
Future<void> receiveRTCMessage(Map<String, dynamic> 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;
}
}
}
void registerMatch() async {
if(GlobalData().userData!.identityCard != null && GlobalData().userData!.identityCard!.isNotEmpty){
await Get.to(() => MatchSpreadPage());
} else {
SmartDialog.showToast('请先进行实名认证');
await Get.to(() => RealNamePage(type: 1));
}
print(455);
matchmakerFlag.value = GlobalData().userData!.matchmakerFlag!;
}
}