1150 lines
36 KiB
Dart
1150 lines
36 KiB
Dart
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||
import 'package:fluro/fluro.dart';
|
||
import 'package:flutter/cupertino.dart';
|
||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||
import 'package:yumi/app_localizations.dart';
|
||
import 'package:yumi/app/constants/sc_room_msg_type.dart';
|
||
import 'package:yumi/shared/tools/sc_permission_utils.dart';
|
||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||
import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository_imp.dart';
|
||
import 'package:yumi/services/room/rc_room_manager.dart';
|
||
import 'package:yumi/services/audio/rtm_manager.dart';
|
||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||
import 'package:provider/provider.dart';
|
||
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
||
import 'package:yumi/app/constants/sc_global_config.dart';
|
||
import 'package:yumi/app/routes/sc_routes.dart';
|
||
import 'package:yumi/app/routes/sc_fluro_navigator.dart';
|
||
import 'package:yumi/shared/tools/sc_lk_dialog_util.dart';
|
||
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
||
import 'package:yumi/shared/tools/sc_gift_vap_svga_manager.dart';
|
||
import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository_impl.dart';
|
||
import 'package:yumi/shared/data_sources/sources/local/data_persistence.dart';
|
||
import 'package:yumi/shared/business_logic/models/res/join_room_res.dart';
|
||
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
|
||
import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
|
||
import 'package:yumi/shared/business_logic/models/res/room_res.dart';
|
||
import 'package:yumi/modules/room/voice_room_route.dart';
|
||
import 'package:yumi/ui_kit/widgets/room/empty_mai_select.dart';
|
||
import 'package:yumi/ui_kit/widgets/room/room_user_info_card.dart';
|
||
import '../../shared/tools/sc_heartbeat_utils.dart';
|
||
import '../../shared/data_sources/models/enum/sc_heartbeat_status.dart';
|
||
import '../../shared/data_sources/models/enum/sc_room_info_event_type.dart';
|
||
import '../../shared/data_sources/models/enum/sc_room_roles_type.dart';
|
||
import '../../shared/business_logic/models/res/sc_is_follow_room_res.dart';
|
||
import '../../shared/business_logic/models/res/sc_room_red_packet_list_res.dart';
|
||
import '../../shared/business_logic/models/res/sc_room_rocket_status_res.dart';
|
||
import '../../shared/business_logic/models/res/sc_room_theme_list_res.dart';
|
||
import '../../shared/tools/sc_room_profile_cache.dart';
|
||
import '../../ui_kit/components/sc_float_ichart.dart';
|
||
|
||
typedef OnSoundVoiceChange = Function(num index, int volum);
|
||
typedef RtcProvider = RealTimeCommunicationManager;
|
||
|
||
class RealTimeCommunicationManager extends ChangeNotifier {
|
||
bool needUpDataUserInfo = false;
|
||
|
||
///当前所在房间
|
||
JoinRoomRes? currenRoom;
|
||
|
||
/// 声音音量变化监听
|
||
final List<OnSoundVoiceChange> _onSoundVoiceChangeList = [];
|
||
|
||
///麦位
|
||
Map<num, MicRes> roomWheatMap = {};
|
||
|
||
RtcEngine? engine;
|
||
BuildContext? context;
|
||
RtmProvider? rtmProvider;
|
||
|
||
SCIsFollowRoomRes? isFollowRoomRes;
|
||
SCRoomRocketStatusRes? roomRocketStatus;
|
||
|
||
///房间是否静音
|
||
bool roomIsMute = false;
|
||
|
||
bool closeFullGame = false;
|
||
|
||
///禁音开关 默认关闭
|
||
bool isMic = true;
|
||
|
||
///在线用户列表
|
||
List<SocialChatUserProfile> onlineUsers = [];
|
||
|
||
///房间管理员
|
||
List<SocialChatUserProfile> managerUsers = [];
|
||
|
||
///音乐是否正在播放
|
||
bool isMusicPlaying = false;
|
||
|
||
///房间红包列表
|
||
List<SCRoomRedPacketListRes> redPacketList = [];
|
||
|
||
num roomTaskClaimableCount = 0;
|
||
|
||
initializeRealTimeCommunicationManager(BuildContext context) {
|
||
this.context = context;
|
||
}
|
||
|
||
Future<void> joinAgoraVoiceChannel() async {
|
||
try {
|
||
engine = await _initAgoraRtcEngine();
|
||
engine?.setAudioProfile(
|
||
profile: AudioProfileType.audioProfileSpeechStandard,
|
||
scenario: AudioScenarioType.audioScenarioGameStreaming,
|
||
);
|
||
engine?.enableAudioVolumeIndication(
|
||
interval: 500,
|
||
smooth: 3,
|
||
reportVad: true,
|
||
);
|
||
await engine?.disableVideo();
|
||
await engine?.setLocalPublishFallbackOption(
|
||
StreamFallbackOptions.streamFallbackOptionAudioOnly,
|
||
);
|
||
engine?.registerEventHandler(
|
||
RtcEngineEventHandler(
|
||
onError: (ErrorCodeType err, String msg) {
|
||
print('rtc错误${err}');
|
||
},
|
||
onLocalAudioStateChanged:
|
||
(
|
||
RtcConnection connection,
|
||
LocalAudioStreamState state,
|
||
LocalAudioStreamReason reason,
|
||
) {},
|
||
onAudioRoutingChanged: (routing) {},
|
||
onAudioMixingStateChanged: (
|
||
AudioMixingStateType state,
|
||
AudioMixingReasonType reason,
|
||
) {
|
||
switch (state) {
|
||
case AudioMixingStateType.audioMixingStatePlaying:
|
||
isMusicPlaying = true;
|
||
break;
|
||
case AudioMixingStateType.audioMixingStateStopped:
|
||
case AudioMixingStateType.audioMixingStateFailed:
|
||
isMusicPlaying = false;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
},
|
||
onRemoteAudioStateChanged: (
|
||
RtcConnection connection,
|
||
int remoteUid,
|
||
RemoteAudioState state,
|
||
RemoteAudioStateReason reason,
|
||
int elapsed,
|
||
) {
|
||
// print('用户 $remoteUid 音频状态: $state, 原因: $reason');
|
||
},
|
||
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
|
||
print('rtc 自己加入 ${connection.channelId} ${connection.localUid}');
|
||
},
|
||
onUserJoined: (connection, remoteUid, elapsed) {
|
||
print('rtc用户 $remoteUid 加入了频道');
|
||
},
|
||
// 监听远端用户离开
|
||
onUserOffline: (connection, remoteUid, reason) {
|
||
print('rtc用户 $remoteUid 离开了频道 (原因: ${reason})');
|
||
},
|
||
onTokenPrivilegeWillExpire: (
|
||
RtcConnection connection,
|
||
String token,
|
||
) async {
|
||
var rtcToken = await SCAccountRepository().getRtcToken(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||
);
|
||
engine?.renewToken(rtcToken.rtcToken ?? "");
|
||
},
|
||
onAudioVolumeIndication: initializeAudioVolumeIndicationCallback,
|
||
),
|
||
);
|
||
var rtcToken = await SCAccountRepository().getRtcToken(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||
);
|
||
await engine?.joinChannel(
|
||
token: rtcToken.rtcToken ?? "",
|
||
channelId: currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
uid: int.parse(
|
||
AccountStorage().getCurrentUser()?.userProfile?.account ?? "0",
|
||
),
|
||
options: ChannelMediaOptions(
|
||
// 自动订阅所有视频流
|
||
autoSubscribeVideo: false,
|
||
// 自动订阅所有音频流
|
||
autoSubscribeAudio: true,
|
||
// 发布摄像头采集的视频
|
||
publishCameraTrack: false,
|
||
// 发布麦克风采集的音频
|
||
publishMicrophoneTrack: true,
|
||
// 设置用户角色为 clientRoleBroadcaster(主播)或 clientRoleAudience(观众)
|
||
clientRoleType: ClientRoleType.clientRoleAudience,
|
||
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
|
||
),
|
||
);
|
||
engine?.muteAllRemoteAudioStreams(roomIsMute);
|
||
} catch (e) {
|
||
SCTts.show("Join room fail");
|
||
exitCurrentVoiceRoomSession(false);
|
||
print('加入失败:${e.runtimeType},${e.toString()}');
|
||
}
|
||
}
|
||
|
||
Future<RtcEngine> _initAgoraRtcEngine() async {
|
||
RtcEngine? engine;
|
||
while (engine == null) {
|
||
engine = createAgoraRtcEngine();
|
||
await engine.initialize(
|
||
RtcEngineContext(appId: SCGlobalConfig.agoraRtcAppid),
|
||
);
|
||
}
|
||
return engine;
|
||
}
|
||
|
||
void initializeAudioVolumeIndicationCallback(
|
||
RtcConnection connection,
|
||
List<AudioVolumeInfo> speakers,
|
||
int speakerNumber,
|
||
int totalVolume,
|
||
) {
|
||
// if (user == null) {
|
||
// throw "本地User尚未初始化";
|
||
// }
|
||
// print('初始化声音监听器:${user.toJson()}');
|
||
|
||
///声音全部清零
|
||
roomWheatMap.forEach((k, v) {
|
||
roomWheatMap[k]!.setVolume = 0;
|
||
});
|
||
for (var info in speakers) {
|
||
num? uid = info.uid;
|
||
int voloum = info.volume!;
|
||
print('${info.uid}在说话---${voloum}');
|
||
|
||
if (voloum > 0) {
|
||
///是本地声音
|
||
if (uid == 0) {
|
||
uid = num.parse(
|
||
AccountStorage().getCurrentUser()!.userProfile!.account!,
|
||
);
|
||
}
|
||
|
||
///在哪个麦
|
||
roomWheatMap.forEach((k, v) {
|
||
if (v.user != null) {
|
||
if (num.parse(v.user!.account!) == uid) {
|
||
roomWheatMap[k]!.setVolume = voloum;
|
||
for (var element in _onSoundVoiceChangeList) {
|
||
element(k, voloum);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
///关注/取消房间
|
||
void followCurrentVoiceRoom() async {
|
||
var result = await SCAccountRepository().followRoom(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
if (isFollowRoomRes != null) {
|
||
isFollowRoomRes = isFollowRoomRes!.copyWith(
|
||
followRoom: !isFollowRoomRes!.followRoom!,
|
||
);
|
||
} else {
|
||
isFollowRoomRes = SCIsFollowRoomRes(followRoom: result);
|
||
}
|
||
SmartDialog.dismiss(tag: "unFollowDialog");
|
||
notifyListeners();
|
||
}
|
||
|
||
int startTime = 0;
|
||
|
||
String? _preferNonEmpty(String? primary, String? fallback) {
|
||
if ((primary ?? "").trim().isNotEmpty) {
|
||
return primary;
|
||
}
|
||
if ((fallback ?? "").trim().isNotEmpty) {
|
||
return fallback;
|
||
}
|
||
return primary ?? fallback;
|
||
}
|
||
|
||
void updateCurrentRoomBasicInfo({
|
||
String? roomCover,
|
||
String? roomName,
|
||
String? roomDesc,
|
||
}) {
|
||
final currentRoomProfile = currenRoom?.roomProfile?.roomProfile;
|
||
if (currenRoom == null || currentRoomProfile == null) {
|
||
return;
|
||
}
|
||
currenRoom = currenRoom?.copyWith(
|
||
roomProfile: currenRoom?.roomProfile?.copyWith(
|
||
roomProfile: currentRoomProfile.copyWith(
|
||
roomCover: _preferNonEmpty(roomCover, currentRoomProfile.roomCover),
|
||
roomName: _preferNonEmpty(roomName, currentRoomProfile.roomName),
|
||
roomDesc: _preferNonEmpty(roomDesc, currentRoomProfile.roomDesc),
|
||
),
|
||
),
|
||
);
|
||
SCRoomProfileCache.saveRoomProfile(
|
||
roomId: currentRoomProfile.id ?? "",
|
||
roomCover: roomCover,
|
||
roomName: roomName,
|
||
roomDesc: roomDesc,
|
||
);
|
||
notifyListeners();
|
||
}
|
||
|
||
void joinVoiceRoomSession(
|
||
BuildContext context,
|
||
String roomId, {
|
||
String? pwd,
|
||
bool clearRoomData = false,
|
||
bool needOpenRedenvelope = false,
|
||
String redPackId = "",
|
||
}) async {
|
||
int nextTime = DateTime.now().millisecondsSinceEpoch;
|
||
if (nextTime - startTime < 1000) {
|
||
//频繁点击
|
||
return;
|
||
}
|
||
startTime = nextTime;
|
||
if (clearRoomData) {
|
||
_clearData();
|
||
}
|
||
bool hasPermission = await SCPermissionUtils.checkMicrophonePermission();
|
||
if (!hasPermission) {
|
||
SCTts.show('Microphone permission is denied.');
|
||
throw ArgumentError('Microphone permission is denied.');
|
||
}
|
||
if (roomId == currenRoom?.roomProfile?.roomProfile?.id) {
|
||
///最小化进入房间,或者进入的是同一个房间
|
||
final loaded = await loadRoomInfo(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
if (!loaded) {
|
||
return;
|
||
}
|
||
if (!context.mounted) {
|
||
return;
|
||
}
|
||
Provider.of<SocialChatUserProfileManager>(
|
||
context,
|
||
listen: false,
|
||
).fetchUserProfileData();
|
||
retrieveMicrophoneList();
|
||
SCFloatIchart().remove();
|
||
SCNavigatorUtils.push(
|
||
context,
|
||
'${VoiceRoomRoute.voiceRoom}?id=$roomId',
|
||
transition: TransitionType.custom,
|
||
transitionDuration: kOpenRoomTransitionDuration,
|
||
transitionBuilder: (
|
||
BuildContext context,
|
||
Animation<double> animation,
|
||
Animation<double> secondaryAnimation,
|
||
Widget child,
|
||
) {
|
||
return kOpenRoomTransitionBuilder(
|
||
context,
|
||
animation,
|
||
secondaryAnimation,
|
||
child,
|
||
);
|
||
},
|
||
);
|
||
} else {
|
||
SCFloatIchart().remove();
|
||
if (currenRoom != null) {
|
||
await exitCurrentVoiceRoomSession(false);
|
||
}
|
||
if (!context.mounted) {
|
||
return;
|
||
}
|
||
rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||
SCLoadingManager.show(context: context);
|
||
try {
|
||
currenRoom = await SCAccountRepository().entryRoom(
|
||
roomId,
|
||
pwd: pwd,
|
||
redPackId: redPackId,
|
||
needOpenRedenvelope: needOpenRedenvelope,
|
||
);
|
||
await initializeRoomSession(
|
||
needOpenRedenvelope: needOpenRedenvelope,
|
||
redPackId: redPackId,
|
||
);
|
||
notifyListeners();
|
||
} catch (e) {
|
||
await resetLocalRoomState(fallbackRtmProvider: rtmProvider);
|
||
rethrow;
|
||
} finally {
|
||
SCLoadingManager.hide();
|
||
}
|
||
}
|
||
}
|
||
|
||
///初始化房间相关
|
||
Future initializeRoomSession({
|
||
bool needOpenRedenvelope = false,
|
||
String? redPackId,
|
||
}) async {
|
||
if ((currenRoom?.roomProfile?.roomProfile?.event ==
|
||
SCRoomInfoEventType.WAITING_CONFIRMED.name ||
|
||
currenRoom?.roomProfile?.roomProfile?.event ==
|
||
SCRoomInfoEventType.ID_CHANGE.name) &&
|
||
currenRoom?.entrants?.roles == SCRoomRolesType.HOMEOWNER.name) {
|
||
///需要去修改房间信息,创建群聊
|
||
SCNavigatorUtils.push(
|
||
context!,
|
||
"${VoiceRoomRoute.roomEdit}?need=true",
|
||
replace: false,
|
||
);
|
||
return Future;
|
||
}
|
||
SCRoomUtils.roomSCGlobalConfig(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
if (currenRoom?.roomProfile?.roomProfile?.id !=
|
||
AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||
SCChatRoomRepository()
|
||
.isFollowRoom(currenRoom?.roomProfile?.roomProfile?.id ?? "")
|
||
.then((value) {
|
||
isFollowRoomRes = value;
|
||
notifyListeners();
|
||
});
|
||
}
|
||
SCNavigatorUtils.push(context!, VoiceRoomRoute.voiceRoom, replace: false);
|
||
var joinResult = await rtmProvider?.joinRoomGroup(
|
||
currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||
"",
|
||
);
|
||
SCChatRoomRepository()
|
||
.rocketClaim(currenRoom?.roomProfile?.roomProfile?.id ?? "")
|
||
.catchError((e) {
|
||
return true; // 错误已处理
|
||
});
|
||
rtmProvider?.addMsg(
|
||
Msg(
|
||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||
msg: SCAppLocalizations.of(context!)!.systemRoomTips,
|
||
type: SCRoomMsgType.systemTips,
|
||
),
|
||
);
|
||
rtmProvider?.addMsg(
|
||
Msg(
|
||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||
msg: currenRoom?.roomProfile?.roomProfile?.roomDesc,
|
||
type: SCRoomMsgType.systemTips,
|
||
),
|
||
);
|
||
|
||
///获取麦位
|
||
retrieveMicrophoneList();
|
||
Provider.of<SocialChatRoomManager>(
|
||
context!,
|
||
listen: false,
|
||
).fetchContributionLevelData(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
await joinAgoraVoiceChannel();
|
||
if (joinResult?.code == 0) {
|
||
Msg joinMsg = Msg(
|
||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "",
|
||
user: AccountStorage().getCurrentUser()?.userProfile,
|
||
msg: "",
|
||
role: currenRoom?.entrants?.roles ?? "",
|
||
type: SCRoomMsgType.joinRoom,
|
||
);
|
||
rtmProvider?.dispatchMessage(joinMsg, addLocal: true);
|
||
if (SCGlobalConfig.isEntryVehicleAnimation) {
|
||
if (AccountStorage().getCurrentUser()?.userProfile?.getMountains() !=
|
||
null) {
|
||
Future.delayed(Duration(milliseconds: 550), () {
|
||
SCGiftVapSvgaManager().play(
|
||
AccountStorage()
|
||
.getCurrentUser()
|
||
?.userProfile
|
||
?.getMountains()
|
||
?.sourceUrl ??
|
||
"",
|
||
priority: 100,
|
||
type: 1,
|
||
);
|
||
});
|
||
}
|
||
}
|
||
if (rtmProvider?.msgUserJoinListener != null) {
|
||
rtmProvider?.msgUserJoinListener!(joinMsg);
|
||
}
|
||
}
|
||
SCChatRoomRepository()
|
||
.rocketStatus(currenRoom?.roomProfile?.roomProfile?.id ?? "")
|
||
.then((res) {
|
||
roomRocketStatus = res;
|
||
notifyListeners();
|
||
})
|
||
.catchError((e) {});
|
||
loadRoomRedPacketList(1);
|
||
|
||
fetchRoomTaskClaimableCount();
|
||
}
|
||
|
||
///更新房间火箭信息
|
||
void updateRoomRocketConfigurationStatus(SCRoomRocketStatusRes res) {
|
||
roomRocketStatus = res;
|
||
notifyListeners();
|
||
}
|
||
|
||
///获取在线用户
|
||
Future fetchOnlineUsersList() async {
|
||
onlineUsers = await SCChatRoomRepository().roomOnlineUsers(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
managerUsers.clear();
|
||
for (var user in onlineUsers) {
|
||
if (user.roles == SCRoomRolesType.ADMIN.name) {
|
||
managerUsers.add(user);
|
||
}
|
||
}
|
||
notifyListeners();
|
||
}
|
||
|
||
Future retrieveMicrophoneList() async {
|
||
bool isOnMic = false;
|
||
var roomWheatList = await SCChatRoomRepository().micList(
|
||
currenRoom!.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
for (var roomWheat in roomWheatList) {
|
||
roomWheatMap[roomWheat.micIndex!] = roomWheat;
|
||
if (roomWheat.user != null &&
|
||
roomWheat.user!.id ==
|
||
AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||
isOnMic = true;
|
||
|
||
///自己在麦上
|
||
SCHeartbeatUtils.scheduleAnchorHeartbeat(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
}
|
||
}
|
||
notifyListeners();
|
||
SCHeartbeatUtils.scheduleHeartbeat(
|
||
SCHeartbeatStatus.VOICE_LIVE.name,
|
||
isOnMic,
|
||
roomId: currenRoom?.roomProfile?.roomProfile?.id,
|
||
);
|
||
Future.delayed(Duration(milliseconds: 1500), () {
|
||
fetchOnlineUsersList();
|
||
});
|
||
}
|
||
|
||
void fetchRoomTaskClaimableCount() {
|
||
SCChatRoomRepository()
|
||
.roomTaskClaimableCount()
|
||
.then((res) {
|
||
roomTaskClaimableCount = res.claimableCount ?? 0;
|
||
notifyListeners();
|
||
})
|
||
.catchError((e) {});
|
||
}
|
||
|
||
Future<List<SCRoomRedPacketListRes>> loadRoomRedPacketList(
|
||
int current,
|
||
) async {
|
||
var result = await SCChatRoomRepository().roomRedPacketList(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
current,
|
||
);
|
||
if (current == 1) {
|
||
redPacketList = result;
|
||
notifyListeners();
|
||
}
|
||
return result;
|
||
}
|
||
|
||
Future exitCurrentVoiceRoomSession(bool isLogout) async {
|
||
try {
|
||
rtmProvider?.msgAllListener = null;
|
||
rtmProvider?.msgChatListener = null;
|
||
rtmProvider?.msgGiftListener = null;
|
||
SCLoadingManager.show(context: context);
|
||
SCGiftVapSvgaManager().dispose();
|
||
SCHeartbeatUtils.scheduleHeartbeat(SCHeartbeatStatus.ONLINE.name, false);
|
||
SCHeartbeatUtils.cancelAnchorTimer();
|
||
rtmProvider?.cleanRoomData();
|
||
await engine?.leaveChannel();
|
||
await Future.delayed(Duration(milliseconds: 100));
|
||
await engine?.release();
|
||
await rtmProvider?.quitGroup(
|
||
currenRoom!.roomProfile?.roomProfile?.roomAccount ?? "",
|
||
);
|
||
await SCAccountRepository().quitRoom(
|
||
currenRoom!.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
_clearData();
|
||
SCLoadingManager.hide();
|
||
if (!isLogout) {
|
||
SCNavigatorUtils.popUntil(context!, ModalRoute.withName(SCRoutes.home));
|
||
}
|
||
} catch (e) {
|
||
print('rtc退出房间出错: $e');
|
||
_clearData();
|
||
SCLoadingManager.hide();
|
||
if (!isLogout) {
|
||
SCNavigatorUtils.popUntil(context!, ModalRoute.withName(SCRoutes.home));
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> resetLocalRoomState({RtmProvider? fallbackRtmProvider}) async {
|
||
rtmProvider ??= fallbackRtmProvider;
|
||
try {
|
||
SCHeartbeatUtils.cancelTimer();
|
||
SCHeartbeatUtils.cancelAnchorTimer();
|
||
final groupId = currenRoom?.roomProfile?.roomProfile?.roomAccount ?? "";
|
||
if (groupId.isNotEmpty) {
|
||
await rtmProvider?.quitGroup(groupId);
|
||
}
|
||
await engine?.leaveChannel();
|
||
await Future.delayed(const Duration(milliseconds: 100));
|
||
await engine?.release();
|
||
} catch (e) {
|
||
print('rtc清理本地房间状态出错: $e');
|
||
} finally {
|
||
engine = null;
|
||
rtmProvider?.cleanRoomData();
|
||
_clearData();
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
///清空列表数据
|
||
void _clearData() {
|
||
roomRocketStatus = null;
|
||
rtmProvider
|
||
?.onNewMessageListenerGroupMap["${currenRoom?.roomProfile?.roomProfile?.roomAccount}"] =
|
||
null;
|
||
roomWheatMap.clear();
|
||
onlineUsers.clear();
|
||
needUpDataUserInfo = false;
|
||
SCRoomUtils.roomUsersMap.clear();
|
||
roomIsMute = false;
|
||
rtmProvider?.roomAllMsgList.clear();
|
||
rtmProvider?.roomChatMsgList.clear();
|
||
redPacketList.clear();
|
||
currenRoom = null;
|
||
isMic = true;
|
||
isMusicPlaying = false;
|
||
DataPersistence.setLastTimeRoomId("");
|
||
SCGiftVapSvgaManager().clearTasks();
|
||
SCRoomUtils.closeAllDialogs();
|
||
SCGlobalConfig.isEntryVehicleAnimation = true;
|
||
SCGlobalConfig.isGiftSpecialEffects = true;
|
||
SCGlobalConfig.isFloatingAnimationInGlobal = true;
|
||
SCGlobalConfig.isLuckGiftSpecialEffects = true;
|
||
}
|
||
|
||
void toggleRemoteAudioMuteForAllUsers() {
|
||
roomIsMute = !roomIsMute;
|
||
engine?.muteAllRemoteAudioStreams(roomIsMute);
|
||
notifyListeners();
|
||
}
|
||
|
||
///点击的位置
|
||
void clickSite(num index, {SocialChatUserProfile? clickUser}) {
|
||
if (index == -1) {
|
||
if (clickUser != null) {
|
||
if (clickUser.id ==
|
||
AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||
///是自己,直接打开资料卡
|
||
showBottomInCenterDialog(
|
||
context!,
|
||
RoomUserInfoCard(userId: clickUser.id),
|
||
);
|
||
} else {
|
||
showBottomInBottomDialog(
|
||
context!,
|
||
EmptyMaiSelect(index: index, clickUser: clickUser),
|
||
);
|
||
}
|
||
}
|
||
} else {
|
||
SocialChatUserProfile? roomWheatUser = roomWheatMap[index]?.user;
|
||
showBottomInBottomDialog(
|
||
context!,
|
||
EmptyMaiSelect(index: index, clickUser: roomWheatUser),
|
||
);
|
||
// if (roomWheatUser != null) {
|
||
// ///麦上有人
|
||
// if (roomWheatUser.id ==
|
||
// AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||
// ///是自己
|
||
// showBottomInBottomDialog(
|
||
// context!,
|
||
// EmptyMaiSelect(index: index, clickUser: roomWheatUser),
|
||
// );
|
||
// } else {
|
||
// showBottomInBottomDialog(
|
||
// context!,
|
||
// RoomUserInfoCard(userId: roomWheatUser.id),
|
||
// );
|
||
// }
|
||
// } else {
|
||
// showBottomInBottomDialog(
|
||
// context!,
|
||
// EmptyMaiSelect(index: index, clickUser: roomWheatUser),
|
||
// );
|
||
// }
|
||
}
|
||
}
|
||
|
||
addSoundVoiceChangeListener(OnSoundVoiceChange onSoundVoiceChange) {
|
||
_onSoundVoiceChangeList.add(onSoundVoiceChange);
|
||
print('添加监听:${_onSoundVoiceChangeList.length}');
|
||
}
|
||
|
||
removeSoundVoiceChangeListener(OnSoundVoiceChange onSoundVoiceChange) {
|
||
_onSoundVoiceChangeList.remove(onSoundVoiceChange);
|
||
print('删除监听:${_onSoundVoiceChangeList.length}');
|
||
}
|
||
|
||
void shangMai(num index, {String? eventType, String? inviterId}) async {
|
||
var myUser = AccountStorage().getCurrentUser()?.userProfile;
|
||
if (currenRoom?.roomProfile?.roomSetting?.touristMike ?? false) {
|
||
} else {
|
||
if (isTourists()) {
|
||
SCTts.show(
|
||
SCAppLocalizations.of(context!)!.touristsAreNotAllowedToGoOnTheMic,
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
try {
|
||
var micGoUpRes = await SCChatRoomRepository().micGoUp(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
index,
|
||
eventType: eventType,
|
||
inviterId: inviterId,
|
||
);
|
||
|
||
/// 设置成主播角色
|
||
engine?.renewToken(micGoUpRes.roomToken ?? "");
|
||
if (!micGoUpRes.micMute!) {
|
||
if (!isMic) {
|
||
engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||
adjustRecordingSignalVolume(100);
|
||
await engine?.muteLocalAudioStream(false);
|
||
}
|
||
} else {
|
||
engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||
await engine?.muteLocalAudioStream(true);
|
||
}
|
||
SCHeartbeatUtils.scheduleAnchorHeartbeat(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
);
|
||
var us = roomWheatMap[index];
|
||
us?.copyWith(
|
||
user: SocialChatUserProfile(
|
||
id: myUser?.id,
|
||
account: myUser?.account,
|
||
userAvatar: myUser?.userAvatar,
|
||
userNickname: myUser?.userNickname,
|
||
userSex: myUser?.userSex,
|
||
),
|
||
);
|
||
roomWheatMap[index] = us!;
|
||
if (us.micMute!) {
|
||
///房主上麦自动解禁麦位
|
||
if (isFz()) {
|
||
jieJinMai(index);
|
||
}
|
||
}
|
||
|
||
notifyListeners();
|
||
} catch (ex) {
|
||
SCTts.show('Failed to put on the microphone, $ex');
|
||
}
|
||
}
|
||
|
||
xiaMai(num index) async {
|
||
SCChatRoomRepository()
|
||
.micGoDown(currenRoom?.roomProfile?.roomProfile?.id ?? "", index)
|
||
.whenComplete(() {
|
||
//自己
|
||
if (roomWheatMap[index]?.user?.id ==
|
||
AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||
isMic = true;
|
||
engine?.muteLocalAudioStream(true);
|
||
}
|
||
SCHeartbeatUtils.cancelAnchorTimer();
|
||
|
||
/// 设置成主持人角色
|
||
engine?.renewToken("");
|
||
engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||
roomWheatMap[index]?.setUser = null;
|
||
notifyListeners();
|
||
});
|
||
}
|
||
|
||
///踢人下麦
|
||
killXiaMai(String userId) {
|
||
try {
|
||
roomWheatMap.forEach((k, v) async {
|
||
if (v.user?.id == userId) {
|
||
var canKill = await SCChatRoomRepository().kickOffMicrophone(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
userId,
|
||
);
|
||
if (canKill) {
|
||
await SCChatRoomRepository().micKill(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
k,
|
||
);
|
||
Provider.of<RtmProvider>(context!, listen: false).dispatchMessage(
|
||
Msg(
|
||
groupId: currenRoom?.roomProfile?.roomProfile?.roomAccount,
|
||
msg: userId,
|
||
type: SCRoomMsgType.killXiaMai,
|
||
),
|
||
addLocal: false,
|
||
);
|
||
roomWheatMap[k]?.setUser = null;
|
||
notifyListeners();
|
||
} else {
|
||
SCTts.show("cant kill the user");
|
||
}
|
||
}
|
||
});
|
||
} catch (e) {
|
||
SCTts.show("kill the user fail");
|
||
print('踢人下麦失败');
|
||
}
|
||
}
|
||
|
||
///自己是否在麦上
|
||
bool isOnMai() {
|
||
return roomWheatMap.values
|
||
.map((userWheat) => userWheat.user?.id)
|
||
.toList()
|
||
.contains(AccountStorage().getCurrentUser()?.userProfile?.id);
|
||
}
|
||
|
||
///自己是否在指定的麦上
|
||
bool isOnMaiInIndex(num index) {
|
||
return roomWheatMap[index]?.user?.id ==
|
||
AccountStorage().getCurrentUser()?.userProfile?.id;
|
||
}
|
||
|
||
///点击的用户在哪个麦上
|
||
num userOnMaiInIndex(String userId) {
|
||
num index = -1;
|
||
roomWheatMap.forEach((k, value) {
|
||
if (value.user?.id == userId) {
|
||
index = k;
|
||
}
|
||
});
|
||
return index;
|
||
}
|
||
|
||
///座位上的用户是否开麦
|
||
bool userOnMaiInIndexOfMute(String userId) {
|
||
bool micMute = false;
|
||
roomWheatMap.forEach((k, value) {
|
||
if (value.user?.id == userId) {
|
||
value.micMute;
|
||
}
|
||
});
|
||
return micMute;
|
||
}
|
||
|
||
///是否是房主
|
||
bool isFz() {
|
||
return currenRoom?.entrants?.roles == SCRoomRolesType.HOMEOWNER.name;
|
||
}
|
||
|
||
///是否是管理
|
||
bool isGL() {
|
||
return currenRoom?.entrants?.roles == SCRoomRolesType.ADMIN.name;
|
||
}
|
||
|
||
///是否是会员
|
||
bool isHY() {
|
||
return currenRoom?.entrants?.roles == SCRoomRolesType.MEMBER.name;
|
||
}
|
||
|
||
///是否是游客
|
||
bool isTourists() {
|
||
return currenRoom?.entrants?.roles == SCRoomRolesType.TOURIST.name;
|
||
}
|
||
|
||
///麦位变动
|
||
void micChange(List<MicRes>? mics) {
|
||
if (mics == null || mics.isEmpty) {
|
||
return;
|
||
}
|
||
roomWheatMap.clear();
|
||
for (var mic in mics) {
|
||
roomWheatMap[mic.micIndex!] = mic;
|
||
if (mic.user?.id == AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||
if (mic.micMute!) {
|
||
///麦克风静音
|
||
engine?.muteLocalAudioStream(true);
|
||
} else {
|
||
// if (!isMic) {
|
||
// engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||
// }
|
||
// engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||
///这个用户需要禁麦/解禁麦克风
|
||
engine?.muteLocalAudioStream(false);
|
||
}
|
||
}
|
||
}
|
||
notifyListeners();
|
||
|
||
///判断自己是不是在麦上
|
||
num index = userOnMaiInIndex(
|
||
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||
);
|
||
if (index == -1) {
|
||
engine?.muteLocalAudioStream(true);
|
||
}
|
||
}
|
||
|
||
///找一个空麦位 -1表示没有空位
|
||
num findWheat() {
|
||
for (var entry in roomWheatMap.entries) {
|
||
if (entry.value.user == null && !entry.value.micLock!) {
|
||
return entry.key;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
Future<bool> loadRoomInfo(String roomId) async {
|
||
try {
|
||
debugPrint("[Room Cover Sync] loadRoomInfo start roomId=$roomId");
|
||
final value = await SCChatRoomRepository().specific(roomId);
|
||
final currentRoomProfile = currenRoom?.roomProfile?.roomProfile;
|
||
debugPrint(
|
||
"[Room Cover Sync] loadRoomInfo fetched roomId=${value.id ?? roomId} roomCover=${value.roomCover ?? ""} roomName=${value.roomName ?? ""}",
|
||
);
|
||
currenRoom = currenRoom?.copyWith(
|
||
roomProfile: currenRoom?.roomProfile?.copyWith(
|
||
roomCounter: value.counter ?? currenRoom?.roomProfile?.roomCounter,
|
||
roomSetting: value.setting ?? currenRoom?.roomProfile?.roomSetting,
|
||
roomProfile: currentRoomProfile?.copyWith(
|
||
roomCover: _preferNonEmpty(
|
||
value.roomCover,
|
||
currentRoomProfile.roomCover,
|
||
),
|
||
roomName: _preferNonEmpty(
|
||
value.roomName,
|
||
currentRoomProfile.roomName,
|
||
),
|
||
roomDesc: _preferNonEmpty(
|
||
value.roomDesc,
|
||
currentRoomProfile.roomDesc,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
debugPrint(
|
||
"[Room Cover Sync] loadRoomInfo applied roomId=$roomId roomCover=${currenRoom?.roomProfile?.roomProfile?.roomCover ?? ""}",
|
||
);
|
||
|
||
///如果是游客禁止上麦,已经在麦上的游客需要下麦
|
||
bool touristMike =
|
||
currenRoom?.roomProfile?.roomSetting?.touristMike ?? false;
|
||
if (!touristMike && isTourists()) {
|
||
num index = userOnMaiInIndex(
|
||
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||
);
|
||
if (index > -1) {
|
||
xiaMai(index);
|
||
}
|
||
}
|
||
notifyListeners();
|
||
return true;
|
||
} catch (e) {
|
||
await resetLocalRoomState(fallbackRtmProvider: rtmProvider);
|
||
SCTts.show("Room not exists");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
///解锁麦位
|
||
void jieFeng(num index) async {
|
||
await SCChatRoomRepository().micLock(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
index,
|
||
false,
|
||
);
|
||
}
|
||
|
||
///锁麦
|
||
void fengMai(num index) async {
|
||
await SCChatRoomRepository().micLock(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
index,
|
||
true,
|
||
);
|
||
}
|
||
|
||
///静音麦克风
|
||
void jinMai(num index) async {
|
||
await SCChatRoomRepository().micMute(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
index,
|
||
true,
|
||
);
|
||
if (isOnMaiInIndex(index)) {
|
||
Provider.of<RtcProvider>(
|
||
context!,
|
||
listen: false,
|
||
).engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||
}
|
||
var mic = roomWheatMap[index];
|
||
if (mic != null) {
|
||
roomWheatMap[index] = mic.copyWith(micMute: true);
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
///解除静音麦克风
|
||
void jieJinMai(num index) async {
|
||
await SCChatRoomRepository().micMute(
|
||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||
index,
|
||
false,
|
||
);
|
||
if (isOnMaiInIndex(index)) {
|
||
if (!Provider.of<RtcProvider>(context!, listen: false).isMic) {
|
||
if (roomWheatMap[index]?.user != null) {
|
||
adjustRecordingSignalVolume(100);
|
||
Provider.of<RtcProvider>(
|
||
context!,
|
||
listen: false,
|
||
).engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||
}
|
||
}
|
||
}
|
||
|
||
var mic = roomWheatMap[index];
|
||
if (mic != null) {
|
||
roomWheatMap[index] = mic.copyWith(micMute: false);
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
void addOnlineUser(String groupId, SocialChatUserProfile user) {
|
||
if (groupId != currenRoom?.roomProfile?.roomProfile?.roomAccount) {
|
||
return;
|
||
}
|
||
bool isExtOnlineList = false;
|
||
for (var us in onlineUsers) {
|
||
if (us.id == user.id) {
|
||
isExtOnlineList = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!isExtOnlineList) {
|
||
Provider.of<RtcProvider>(context!, listen: false).onlineUsers.add(user);
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
void removOnlineUser(String groupId, String userId) {
|
||
if (groupId != currenRoom?.roomProfile?.roomProfile?.roomAccount) {
|
||
return;
|
||
}
|
||
SocialChatUserProfile? isExtOnlineUser;
|
||
for (var us in onlineUsers) {
|
||
if (us.id == userId) {
|
||
isExtOnlineUser = us;
|
||
break;
|
||
}
|
||
}
|
||
if (isExtOnlineUser != null) {
|
||
Provider.of<RtcProvider>(
|
||
context!,
|
||
listen: false,
|
||
).onlineUsers.remove(isExtOnlineUser);
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
void starPlayEmoji(Msg msg) {
|
||
if (msg.number! > -1) {
|
||
var mic = roomWheatMap[msg.number];
|
||
if (mic != null && mic.user != null) {
|
||
roomWheatMap[msg.number!] = mic.copyWith(
|
||
emojiPath: msg.msg,
|
||
type: msg.type,
|
||
number: msg.msg,
|
||
);
|
||
notifyListeners();
|
||
}
|
||
}
|
||
}
|
||
|
||
///更新房间背景
|
||
void updateRoomBG(SCRoomThemeListRes res) {
|
||
var roomTheme = RoomTheme(
|
||
expireTime: res.expireTime,
|
||
id: res.id,
|
||
themeStatus: res.themeStatus,
|
||
themeBack: res.themeBack,
|
||
);
|
||
currenRoom?.roomProps?.setRoomTheme(roomTheme);
|
||
notifyListeners();
|
||
}
|
||
|
||
void updateRoomSetting(RoomSetting roomSetting) {
|
||
currenRoom?.roomProfile?.setRoomSetting(roomSetting);
|
||
notifyListeners();
|
||
}
|
||
|
||
Map<num, GlobalKey<State<StatefulWidget>>> seatGlobalKeyMap = {};
|
||
|
||
void bindTargetKey(num index, GlobalKey<State<StatefulWidget>> targetKey) {
|
||
seatGlobalKeyMap[index] = targetKey;
|
||
}
|
||
|
||
GlobalKey<State<StatefulWidget>>? getSeatGlobalKeyByIndex(String userId) {
|
||
///需要有人的座位
|
||
num index = userOnMaiInIndex(userId);
|
||
if (index > -1) {
|
||
return seatGlobalKeyMap[index];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
///播放音乐
|
||
void startAudioMixing(String localPath, int cycle) {
|
||
if (localPath.isEmpty) {
|
||
return;
|
||
}
|
||
engine?.startAudioMixing(
|
||
filePath: localPath,
|
||
loopback: false,
|
||
cycle: cycle,
|
||
);
|
||
}
|
||
|
||
void adjustRecordingSignalVolume(int volume) {
|
||
engine?.adjustRecordingSignalVolume(volume);
|
||
}
|
||
}
|