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 channelJoinedNotifier = ValueNotifier(false); final ValueNotifier> remoteUsersNotifier = ValueNotifier>( [], ); 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 _remoteUserIds = []; // 事件回调 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 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() && channelId != null && channelId.isNotEmpty) { final roomController = Get.find(); 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() && channelId != null && channelId.isNotEmpty) { final roomController = Get.find(); 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() && channelId != null && channelId.isNotEmpty) { final roomController = Get.find(); 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()) { final roomController = Get.find(); // 判断不是主播(从 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 enableVideo() async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.enableVideo(); print('视频已启用'); } /// 禁用视频 Future disableVideo() async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.disableVideo(); print('视频已禁用'); } /// 启用音频 Future enableAudio() async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.enableAudio(); print('音频已启用'); } /// 禁用音频 Future disableAudio() async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.disableAudio(); print('音频已禁用'); } /// 开启本地视频预览 Future startPreview() async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.startPreview(); print('本地视频预览已开启'); } /// 停止本地视频预览 Future stopPreview() async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.stopPreview(); print('本地视频预览已停止'); } /// 设置本地视频视图 /// [viewId] 视图ID /// [mirrorMode] 镜像模式 Future 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 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 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'); } /// 离开频道 Future 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 switchCamera() async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.switchCamera(); print('摄像头已切换'); } /// 静音/取消静音本地音频 /// [muted] true表示静音,false表示取消静音 Future muteLocalAudio(bool muted) async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.muteLocalAudioStream(muted); print('本地音频${muted ? "已静音" : "已取消静音"}'); } /// 开启/关闭本地视频 /// [enabled] true表示开启,false表示关闭 Future muteLocalVideo(bool enabled) async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.muteLocalVideoStream(!enabled); print('本地视频${enabled ? "已开启" : "已关闭"}'); } /// 设置扬声器开关 /// [enabled] true表示开启扬声器,false表示关闭(使用听筒) Future setEnableSpeakerphone(bool enabled) async { if (_engine == null) { throw Exception('RTC Engine not initialized'); } await _engine!.setEnableSpeakerphone(enabled); print('扬声器${enabled ? "已开启" : "已关闭"}'); } /// 设置客户端角色(仅用于直播场景) /// [role] 客户端角色:主播或观众 Future 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 get remoteUserIds => List.unmodifiable(_remoteUserIds); /// 释放资源 Future 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) { print('用户已加入频道:$remoteUid'); if (_remoteUserIds.contains(remoteUid)) return; _remoteUserIds.add(remoteUid); remoteUsersNotifier.value = List.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.unmodifiable(_remoteUserIds); } /// 发布视频 Future 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 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 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', }), ); } }