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.
 
 
 
 
 

670 lines
23 KiB

import 'dart:convert';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:agora_token_generator/agora_token_generator.dart';
import 'package:dating_touchme_app/rtc/rtm_manager.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import '../controller/discover/room_controller.dart';
import '../controller/message/call_controller.dart';
import '../pages/discover/live_room_page.dart';
import '../service/live_chat_message_service.dart';
enum RTCType {
call, // 通话(语音/视频通话)
live, // 直播
}
/// RTC 管理器,负责管理声网音视频通话功能
class RTCManager {
/// 频道加入状态通知,用于UI监听
final ValueNotifier<bool> channelJoinedNotifier = ValueNotifier<bool>(false);
final ValueNotifier<List<int>> remoteUsersNotifier = ValueNotifier<List<int>>(
<int>[],
);
RtcEngine? get engine => _engine;
bool get isInChannel => _isInChannel;
int? get currentUid => _currentUid;
// 单例模式
static final RTCManager _instance = RTCManager._internal();
factory RTCManager() => _instance;
// 静态getter用于instance访问
static RTCManager get instance => _instance;
RtcEngine? _engine;
RTCType type = RTCType.live;
bool _isInitialized = false;
bool _isInChannel = false;
String? _currentChannelId;
int? _currentUid;
ClientRoleType _clientRole = ClientRoleType.clientRoleBroadcaster;
final List<int> _remoteUserIds = <int>[];
// 事件回调
Function(RtcConnection connection, int elapsed)? onJoinChannelSuccess;
Function(RtcConnection connection, int remoteUid, int elapsed)? onUserJoined;
Function(
RtcConnection connection,
int remoteUid,
UserOfflineReasonType reason,
)?
onUserOffline;
Function(RtcConnection connection, RtcStats stats)? onLeaveChannel;
Function(
RtcConnection connection,
ConnectionStateType state,
ConnectionChangedReasonType reason,
)?
onConnectionStateChanged;
Function(int uid, UserInfo userInfo)? onUserInfoUpdated;
Function(RtcConnection connection, int uid, int elapsed)?
onFirstRemoteVideoDecoded;
Function(
RtcConnection connection,
VideoSourceType sourceType,
int uid,
int width,
int height,
int rotation,
)?
onVideoSizeChanged;
Function(RtcConnection connection, int uid, bool muted)? onUserMuteAudio;
Function(RtcConnection connection, int uid, bool muted)? onUserMuteVideo;
Function(RtcConnection connection)? onConnectionLost;
Function(RtcConnection connection, int code, String msg)? onError;
RTCManager._internal() {
print('RTCManager instance created');
}
/// 初始化 RTC Engine
/// [appId] 声网 App ID
/// [channelProfile] 频道场景类型,默认为通信模式
Future<bool> initialize({
required String appId,
ChannelProfileType channelProfile =
ChannelProfileType.channelProfileLiveBroadcasting,
}) async {
try {
if (_isInitialized && _engine != null) {
print('RTC Engine already initialized');
return true;
}
// 创建 RTC Engine
_engine = createAgoraRtcEngine();
// 初始化 RTC Engine
await _engine!.initialize(
RtcEngineContext(appId: appId, channelProfile: channelProfile),
);
await _engine?.setClientRole(role: _clientRole);
await _engine?.setVideoEncoderConfiguration(VideoEncoderConfiguration(
mirrorMode: VideoMirrorModeType.videoMirrorModeDisabled
));
await _engine?.enableVideo();
// await _engine?.startPreview();
// 注册事件处理器
_registerEventHandlers();
_isInitialized = true;
print('RTC Engine initialized successfully');
return true;
} catch (e) {
print('Failed to initialize RTC Engine: $e');
return false;
}
}
/// 注册事件处理器
void _registerEventHandlers() {
if (_engine == null) return;
_engine!.registerEventHandler(
RtcEngineEventHandler(
onJoinChannelSuccess: (RtcConnection connection, int elapsed) async {
_isInChannel = true;
_remoteUserIds.clear();
remoteUsersNotifier.value = const [];
channelJoinedNotifier.value = true;
_currentChannelId = connection.channelId;
print('加入频道成功,频道名:${connection.channelId},耗时:${elapsed}ms,类型:$type');
// 根据RTC类型判断是否执行 RoomController 的逻辑
// 只有直播类型才执行 RoomController 的逻辑
if (type == RTCType.live) {
// 调用 RoomController 的 fetchRtcChannelDetail 方法
final channelId = connection.channelId;
if (Get.isRegistered<RoomController>() &&
channelId != null &&
channelId.isNotEmpty) {
final roomController = Get.find<RoomController>();
await roomController.fetchRtcChannelDetail(channelId);
}
if (connection.localUid == _currentUid) {
await RTMManager.instance.subscribe(_currentChannelId ?? '');
await RTMManager.instance.publishChannelMessage(
channelName: _currentChannelId ?? '',
message: json.encode({'type': 'join_room', 'uid': _currentUid}),
);
Get.to(() => const LiveRoomPage(id: 0));
}
}
if (onJoinChannelSuccess != null) {
onJoinChannelSuccess!(connection, elapsed);
}
},
onUserJoined:
(RtcConnection connection, int remoteUid, int elapsed) async {
print('用户加入,UID:$remoteUid');
_handleRemoteUserJoined(remoteUid);
await _engine?.setRemoteRenderMode(uid: remoteUid, renderMode: RenderModeType.renderModeHidden, mirrorMode: VideoMirrorModeType.videoMirrorModeDisabled);
// 根据RTC类型判断是否执行 RoomController 的逻辑
// 只有直播类型才执行 RoomController 的逻辑
if (type == RTCType.live) {
// 调用 RoomController 的 fetchRtcChannelDetail 方法
final channelId = connection.channelId;
if (Get.isRegistered<RoomController>() &&
channelId != null &&
channelId.isNotEmpty) {
final roomController = Get.find<RoomController>();
await roomController.fetchRtcChannelDetail(channelId);
}
} else if (type == RTCType.call) {
// 通话场景:通知 CallController 远端用户已加入
print('📞 [RTCManager] 通话场景:检测到用户加入,UID:$remoteUid');
try {
// 直接使用 CallController.instance,因为它使用单例模式会自动注册
final callController = CallController.instance;
callController.remoteUid.value = remoteUid;
print('✅ [RTCManager] 通话场景:已设置 CallController.remoteUid = $remoteUid');
} catch (e) {
print('⚠️ [RTCManager] 设置 CallController.remoteUid 失败: $e');
}
} else {
print('⚠️ [RTCManager] 未知的 RTC 类型:$type');
}
if (onUserJoined != null) {
onUserJoined!(connection, remoteUid, elapsed);
}
},
onUserOffline:
(
RtcConnection connection,
int remoteUid,
UserOfflineReasonType reason,
) async {
print('用户离开,UID:$remoteUid,原因:$reason');
_handleRemoteUserOffline(remoteUid);
// 根据RTC类型判断是否执行 RoomController 的逻辑
// 只有直播类型才执行 RoomController 的逻辑
if (type == RTCType.live) {
// 调用 RoomController 的 fetchRtcChannelDetail 方法
final channelId = connection.channelId;
if (Get.isRegistered<RoomController>() &&
channelId != null &&
channelId.isNotEmpty) {
final roomController = Get.find<RoomController>();
if(remoteUid == roomController.rtcChannelDetail.value?.anchorInfo?.uid){
await roomController.leaveChannel();
// 调用 LiveChatMessageService 处理结束直播消息
LiveChatMessageService.instance.handleEndLiveMessage();
}
await roomController.fetchRtcChannelDetail(channelId);
}
}
if (onUserOffline != null) {
onUserOffline!(connection, remoteUid, reason);
}
},
onLeaveChannel: (RtcConnection connection, RtcStats stats) {
_isInChannel = false;
_remoteUserIds.clear();
remoteUsersNotifier.value = const [];
channelJoinedNotifier.value = false;
_currentChannelId = null;
print('离开频道,统计信息:${stats.duration}');
if (onLeaveChannel != null) {
onLeaveChannel!(connection, stats);
}
},
onConnectionStateChanged:
(
RtcConnection connection,
ConnectionStateType state,
ConnectionChangedReasonType reason,
) async{
if(state == ConnectionStateType.connectionStateFailed &&
reason == ConnectionChangedReasonType.connectionChangedBannedByServer){
if (Get.isRegistered<RoomController>()) {
final roomController = Get.find<RoomController>();
// 判断不是主播(从 RoomController.currentRole 获取角色)
final isNotBroadcaster = roomController.currentRole != CurrentRole.broadcaster;
if (isNotBroadcaster) {
await roomController.leaveChannel();
// 调用 LiveChatMessageService 处理结束直播消息
LiveChatMessageService.instance.handleEndLiveMessage();
}
}
return;
}
print('连接状态改变:$state,原因:$reason, localUid: ${connection.localUid}');
if (onConnectionStateChanged != null) {
onConnectionStateChanged!(connection, state, reason);
}
},
onUserInfoUpdated: (int uid, UserInfo userInfo) {
print('用户信息更新,UID:$uid');
if (onUserInfoUpdated != null) {
onUserInfoUpdated!(uid, userInfo);
}
},
onFirstRemoteVideoDecoded:
(
RtcConnection connection,
int uid,
int width,
int height,
int elapsed,
) {
print('首次远程视频解码,UID:$uid,分辨率:${width}x${height}');
if (onFirstRemoteVideoDecoded != null) {
onFirstRemoteVideoDecoded!(connection, uid, elapsed);
}
},
onVideoSizeChanged:
(
RtcConnection connection,
VideoSourceType sourceType,
int uid,
int width,
int height,
int rotation,
) {
print('视频尺寸改变,UID:$uid,分辨率:${width}x${height}');
if (onVideoSizeChanged != null) {
onVideoSizeChanged!(
connection,
sourceType,
uid,
width,
height,
rotation,
);
}
},
onUserMuteAudio: (RtcConnection connection, int uid, bool muted) {
print('用户静音状态改变,UID:$uid,静音:$muted');
if (onUserMuteAudio != null) {
onUserMuteAudio!(connection, uid, muted);
}
},
onUserMuteVideo: (RtcConnection connection, int uid, bool muted) {
print('用户视频状态改变,UID:$uid,关闭:$muted');
if (onUserMuteVideo != null) {
onUserMuteVideo!(connection, uid, muted);
}
},
onConnectionLost: (RtcConnection connection) {
print('连接丢失');
if (onConnectionLost != null) {
onConnectionLost!(connection);
}
},
onTokenPrivilegeWillExpire: (RtcConnection connection, String token) {
String rtcToken = RtcTokenBuilder.buildTokenWithUid(
appId: '4c2ea9dcb4c5440593a418df0fdd512d',
appCertificate: '16f34b45181a4fae8acdb1a28762fcfa',
channelName: _currentChannelId ?? '',
uid: _currentUid ?? 0,
tokenExpireSeconds: 3600, // 2 hours
);
_engine?.renewToken(rtcToken);
},
onError: (ErrorCodeType err, String msg) {
print('RTC Engine 错误:$err,消息:$msg');
if (onError != null) {
onError!(
RtcConnection(
channelId: _currentChannelId ?? '',
localUid: _currentUid ?? 0,
),
err.value(),
msg,
);
}
},
),
);
}
/// 启用视频
Future<void> enableVideo() async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.enableVideo();
print('视频已启用');
}
/// 禁用视频
Future<void> disableVideo() async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.disableVideo();
print('视频已禁用');
}
/// 启用音频
Future<void> enableAudio() async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.enableAudio();
print('音频已启用');
}
/// 禁用音频
Future<void> disableAudio() async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.disableAudio();
print('音频已禁用');
}
/// 开启本地视频预览
Future<void> startPreview() async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.startPreview();
print('本地视频预览已开启');
}
/// 停止本地视频预览
Future<void> stopPreview() async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.stopPreview();
print('本地视频预览已停止');
}
/// 设置本地视频视图
/// [viewId] 视图ID
/// [mirrorMode] 镜像模式
Future<void> setupLocalVideo({
required int viewId,
VideoSourceType sourceType = VideoSourceType.videoSourceCameraPrimary,
VideoMirrorModeType mirrorMode = VideoMirrorModeType.videoMirrorModeAuto,
}) async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.setupLocalVideo(
VideoCanvas(view: viewId, sourceType: sourceType, mirrorMode: mirrorMode),
);
print('本地视频视图已设置,viewId:$viewId');
}
/// 设置远程视频视图
/// [uid] 远程用户ID
/// [viewId] 视图ID
Future<void> setupRemoteVideo({
required int uid,
required int viewId,
VideoSourceType sourceType = VideoSourceType.videoSourceCameraPrimary,
VideoMirrorModeType mirrorMode =
VideoMirrorModeType.videoMirrorModeDisabled,
}) async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.setupRemoteVideo(
VideoCanvas(
uid: uid,
view: viewId,
sourceType: sourceType,
mirrorMode: mirrorMode,
),
);
print('远程视频视图已设置,UID:$uid,viewId:$viewId');
}
/// 加入频道
/// [token] 频道令牌(可选,如果频道未开启鉴权则可以为空字符串)
/// [channelId] 频道ID
/// [uid] 用户ID(0表示自动分配)
/// [role] 客户端角色
/// [rtcType] RTC类型,用于区分通话还是直播
Future<void> joinChannel({
String? token,
required String channelId,
int uid = 0,
ClientRoleType role = ClientRoleType.clientRoleBroadcaster,
RTCType rtcType = RTCType.live,
}) async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
if (_isInChannel) {
print('已经在频道中,先离开当前频道');
await leaveChannel();
}
// 设置RTC类型
type = rtcType;
await setClientRole(role: role);
_currentUid = uid;
if (role == ClientRoleType.clientRoleBroadcaster) {
await _engine?.startPreview();
}
await _engine!.joinChannel(
token: token ?? '',
channelId: channelId,
uid: uid,
options: ChannelMediaOptions(
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
clientRoleType: role,
autoSubscribeAudio: true,
autoSubscribeVideo: true,
publishCameraTrack: true,
publishMicrophoneTrack: true,
),
);
print('正在加入频道:$channelId,UID:$uid,类型:$rtcType');
await _engine?.enableContentInspect(
enabled: true,
config: ContentInspectConfig(
modules: [
ContentInspectModule(
type: ContentInspectType.contentInspectImageModeration,
interval: 10
)
],
moduleCount: 1
)
);
}
/// 离开频道
Future<void> leaveChannel() async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
if (!_isInChannel) {
print('当前不在频道中');
return;
}
await RTMManager.instance.unsubscribe(_currentChannelId ?? '');
await _engine!.leaveChannel();
_currentUid = null;
print('已离开频道');
}
/// 切换摄像头
Future<void> switchCamera() async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.switchCamera();
print('摄像头已切换');
}
/// 静音/取消静音本地音频
/// [muted] true表示静音,false表示取消静音
Future<void> muteLocalAudio(bool muted) async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.muteLocalAudioStream(muted);
print('本地音频${muted ? "已静音" : "已取消静音"}');
}
/// 开启/关闭本地视频
/// [enabled] true表示开启,false表示关闭
Future<void> muteLocalVideo(bool enabled) async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.muteLocalVideoStream(!enabled);
print('本地视频${enabled ? "已开启" : "已关闭"}');
}
/// 设置扬声器开关
/// [enabled] true表示开启扬声器,false表示关闭(使用听筒)
Future<void> setEnableSpeakerphone(bool enabled) async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
await _engine!.setEnableSpeakerphone(enabled);
print('扬声器${enabled ? "已开启" : "已关闭"}');
}
/// 设置客户端角色(仅用于直播场景)
/// [role] 客户端角色:主播或观众
Future<void> setClientRole({
required ClientRoleType role,
ClientRoleOptions? options,
}) async {
if (_engine == null) {
throw Exception('RTC Engine not initialized');
}
_clientRole = role;
await _engine!.setClientRole(role: role, options: options);
print('客户端角色已设置为:$role');
}
/// 获取当前频道ID
String? get currentChannelId => _currentChannelId;
ClientRoleType get clientRole => _clientRole;
List<int> get remoteUserIds => List<int>.unmodifiable(_remoteUserIds);
/// 释放资源
Future<void> dispose() async {
try {
if (_isInChannel) {
await leaveChannel();
}
if (_engine != null) {
await _engine!.release();
_engine = null;
}
_remoteUserIds.clear();
remoteUsersNotifier.value = const [];
_isInitialized = false;
_isInChannel = false;
_currentChannelId = null;
_currentUid = null;
channelJoinedNotifier.value = false;
print('RTC Engine disposed');
} catch (e) {
print('Failed to dispose RTC Engine: $e');
}
}
void _handleRemoteUserJoined(int remoteUid) async {
print('用户已加入频道:$remoteUid');
if (_remoteUserIds.contains(remoteUid)) return;
_remoteUserIds.add(remoteUid);
remoteUsersNotifier.value = List<int>.unmodifiable(_remoteUserIds);
// 如果是通话场景,同时更新 CallController 的 remoteUid
if (type == RTCType.call) {
try {
// 直接使用 CallController.instance,因为它使用单例模式会自动注册
final callController = CallController.instance;
if (callController.remoteUid.value == null) {
callController.remoteUid.value = remoteUid;
print('📞 [RTCManager] _handleRemoteUserJoined: 已同步 remoteUid 到 CallController: $remoteUid');
}
} catch (e) {
print('⚠️ [RTCManager] _handleRemoteUserJoined 设置 CallController.remoteUid 失败: $e');
}
}
}
void _handleRemoteUserOffline(int remoteUid) {
final removed = _remoteUserIds.remove(remoteUid);
if (!removed) return;
remoteUsersNotifier.value = List<int>.unmodifiable(_remoteUserIds);
}
/// 发布视频
Future<void> publishVideo(CurrentRole role) async {
await _engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
await _engine?.muteLocalAudioStream(false);
await _engine?.muteLocalVideoStream(false);
await RTMManager.instance.publishChannelMessage(
channelName: _currentChannelId ?? '',
message: json.encode({
'type': 'join_chat',
'uid': _currentUid,
'role': role == CurrentRole.maleAudience
? 'male_audience'
: 'female_audience',
}),
);
}
/// 发布音频
Future<void> publishAudio() async {
await _engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
await _engine?.muteLocalAudioStream(false);
await _engine?.muteLocalVideoStream(true);
await RTMManager.instance.publishChannelMessage(
channelName: _currentChannelId ?? '',
message: json.encode({
'type': 'join_chat',
'uid': _currentUid,
'role': 'audience',
}),
);
}
/// 取消发布视频
Future<void> unpublish(CurrentRole role) async {
await _engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
await _engine?.muteLocalAudioStream(true);
await _engine?.muteLocalVideoStream(true);
await RTMManager.instance.publishChannelMessage(
channelName: _currentChannelId ?? '',
message: json.encode({
'type': 'leave_chat',
'uid': _currentUid,
'role': role == CurrentRole.maleAudience
? 'male_audience'
: 'female_audience',
}),
);
}
}