import 'dart:async'; import 'dart:convert'; import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:date_format/date_format.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:signalr_core/signalr_core.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wgshare/common/store/user_store.dart'; import 'package:wgshare/utils/count_microphone_volume.dart'; import '../../common/config/request_config.dart'; import '../../common/mixins/request_tool_mixin.dart'; import '../../common/models/common/base_structure_result.dart'; import '../../common/models/meeting_room_info.dart'; import '../../common/models/meeting_room_msg.dart'; import '../../common/models/meeting_room_user.dart'; import '../../common/store/business_store.dart'; import '../../routes/app_routes.dart'; import '../../utils/agora/AgoraUtil.dart'; import '../../utils/permission/PermissionService.dart'; import '../../utils/toast_utils.dart'; import 'meeting_main_state.dart'; class MeetingMainLogic extends GetxController with RequestToolMixin { final MeetingMainState state = MeetingMainState(); @override void onInit() { super.onInit(); // 接收参数 var data = Get.arguments; state.roomNumber.value = data["roomNumber"]; // 开启屏幕常亮 WakelockPlus.enable(); doHttpGetMeetingToken(true); } @override void onClose() { super.onClose(); state.floating.value?.close(); state.memberNameSearchController.dispose(); state.sendMsgController.dispose(); state.pageController.dispose(); stopTime(); // 关闭屏幕常亮 WakelockPlus.disable(); leaveMeetingToRtc(); leaveMeetingToSocket(); } /// 获取会议室Token Future doHttpGetMeetingToken(bool isInit) async { BaseStructureResult res = await getClient().getMeetingToken(state.roomNumber.value); state.meetingToken.value = res.data!; if (isInit == true) { signalRSocket(); }else{ initRtc(); } } /// 合并请求 /// 1.获取会议室信息 /// 2.获取会议室所有用户 Future mergeFetch(bool isAgain) async { try { if (isAgain == false) { ToastUtils.showLoading(); } var results = await Future.wait([ getClient().getMeetingRoomInfo(state.roomNumber.value), getClient().getMeetingRoomAllUser(state.roomNumber.value) ]); getMeetingRoomInfo(results[0].data as MeetingRoomInfo); getMeetingRoomAllUser(results[1].data as List); } finally { ToastUtils.dismiss(); } } /// 改变会议信息浮层显示状态 void changeMeetingInfoState(bool isShow) { state.isShowMeetingInfoFloatingLayer.value = isShow; } /// 改变音频选择浮层显示状态 void changeMeetingAudioState(bool isShow) { state.isShowMeetingAudioFloatingLayer.value = isShow; } /// 改变当前页面状态 void changePageState(int pageState) { state.pageState.value = pageState; } /// 获取会议室信息 void getMeetingRoomInfo(MeetingRoomInfo meetingRoomInfo) async { state.meetingRoomInfo.value = meetingRoomInfo; initRtc(); startTime(); } /// 获取会议室所有用户 void getMeetingRoomAllUser(List meetingRoomUsers) async { state.users.value = meetingRoomUsers; state.cacheUsers.value = meetingRoomUsers; update(); doHttpGetTvAnchor(); } /// 启动计时 void startTime() { state.stopwatch.value.start(); state.timer.value = Timer.periodic(const Duration(milliseconds: 100), upTime); } /// 更新计时 void upTime(Timer t) { if (state.stopwatch.value.isRunning) { state.duration.value = "${state.stopwatch.value.elapsed.inHours.toString().padLeft(2, "0")}" ":${(state.stopwatch.value.elapsed.inMinutes % 60).toString().padLeft(2, "0")}" ":${(state.stopwatch.value.elapsed.inSeconds % 60).toString().padLeft(2, "0")}"; } } /// 停止计时 void stopTime() { state.timer.value?.cancel(); state.stopwatch.value.stop(); } /// 搜索成员 void searchMember(String value) { if (value.isNotEmpty) { List memberSearchList = []; for (var i = 0; i < state.cacheUsers.value.length; i++) { if (state.cacheUsers.value[i].userName.contains(value)) { memberSearchList.add(state.cacheUsers.value[i]); } } state.users.value = memberSearchList; } else { state.users.value = state.cacheUsers.value; } update(); } /// 点击grid切换大屏 void checkLargeScreen(String uid){ ToastUtils.showSuccess("切换用户成功"); if(uid == UserStore.to.userInfoEntity.value!.uid) { state.remoteUid.value = "0"; }else{ if(state.isSpeak.value == true && state.isOpenCamera.value == true){ state.floating.value?.open(Get.context!); } state.remoteUid.value = uid; } changePageState(0); Future.delayed(const Duration(milliseconds: 200), () { changePageState(1); }); update(); } /// ------------------------------------------------------------------------------发言权限 /// 申请发言权限 Future doHttpApplySpeak() async { await getClient().applySpeak(state.roomNumber.value); } /// 关闭发言权限 Future doHttpCancelSpeak() async { await getClient().cancelSpeak( state.meetingRoomInfo.value!.id, state.meetingRoomInfo.value!.roomNum, UserStore.to.userInfoEntity.value!.uid); } /// 关闭发言权限合并请求 Future cancelSpeak() async { try { ToastUtils.showLoading(); await Future.wait( [doHttpCancelSpeak(), doHttpSetMicr(false), doHttpSetCamer(false)]); } finally { ToastUtils.dismiss(); } } /// ------------------------------------------------------------------------------麦克风相关(操作) /// 用户开闭麦 Future doHttpSetMicr(bool isOpenMicrophone) async { if(isOpenMicrophone == false){ state.spokesman.value = ""; state.spokesmanVolume.value = 0; } await getClient().setMicr(state.roomNumber.value, isOpenMicrophone, UserStore.to.userInfoEntity.value!.uid); } /// ------------------------------------------------------------------------------视频-屏幕相关(操作) /// 用户开闭摄像头 Future doHttpSetCamer(bool isOpenCamera) async { await getClient().setCamera(state.roomNumber.value, isOpenCamera, UserStore.to.userInfoEntity.value!.uid); } /// 设置视频是否开启 void setCameraOpen(bool isOpen) { state.isOpenCamera.value = isOpen; } /// 获取当前全员观看主播 Future doHttpGetTvAnchor() async { BaseStructureResult res = await getClient().getTvAnchor(state.roomNumber.value); state.remoteUid.value = res.data!.toString(); state.remoteAssistantUid.value = res.data!.toString(); debugPrint("wgs输出===:获取当前全员观看主播${res.data}"); if (res.data!.toString().length != 9) { // 摄像头 if (state.remoteUid.value == UserStore.to.userInfoEntity.value!.uid) { debugPrint("wgs输出===:当前全员观看是自己"); // 全员观看主播是自己 // 设置主播ID为0(自己) state.remoteUid.value = "0"; // 关闭右上角悬浮窗 state.floating.value?.close(); if (state.isSpeak.value == true && state.isOpenCamera.value == true) { // 有发言权限且开启了摄像头时,切换页面到视频状态 changePageState(1); } else { // 没有发言权限或没开启摄像头时,查看有没有其他人开摄像头,如果没有切换页面到语音状态,如果有切换到视频状态 var isOtherOpenCamera = false; for (var i = 0; i < state.cacheUsers.value.length; i++) { if (state.cacheUsers.value[i].enableCamera == true) { isOtherOpenCamera = true; } } if(isOtherOpenCamera == false){ changePageState(0); }else{ state.remoteUid.value = ""; changePageState(1); } } } else { debugPrint("wgs输出===:当前全员观看不是自己"); // 全员观看主播不是自己 // 判断当前会议室是否存在全员观看主播 var isCurrentUserIsCamera = false; for (var i = 0; i < state.cacheUsers.value.length; i++) { if (state.remoteUid.value == state.cacheUsers.value[i].uid && state.cacheUsers.value[i].enableCamera == true) { isCurrentUserIsCamera = true; } } if (isCurrentUserIsCamera == true) { // 当前会议室存在全员观看主播时,切换页面到视频状态 debugPrint("wgs输出===:当前会议室存在全员观看主播时,切换页面到视频状态"); changePageState(0); Future.delayed(const Duration(milliseconds: 200), () { changePageState(1); }); } else { // 当前会议室不存在全员观看主播时,设置主播ID为空并切换页面到视频状态 debugPrint("wgs输出===:当前会议室不存在全员观看主播时,设置主播ID为空并切换页面到视频状态"); // state.remoteUid.value = ""; state.remoteAssistantUid.value = ""; changePageState(1); } } } else { debugPrint("wgs输出===:当前全员观看是共享类型"); if (state.remoteUid.value == UserStore.to.userInfoEntity.value!.screenShareId) { // 如果是自己在共享 } else { // 共享不是自己 for (var i = 0; i < state.cacheUsers.value.length; i++) { if (state.remoteUid.value == state.cacheUsers.value[i].screenShareId) { state.cacheUsers.value[i].enableShare = true; } } changePageState(1); } } update(); } /// 设置当前全员观看主播 Future doHttpSetTvAnchor(String id) async { await getClient().setTvAnchor(state.roomNumber.value, id, UserStore.to.userInfoEntity.value!.userName); } /// 启动共享屏幕(此版本不做) /*Future startScreenCapture() async { debugPrint("wgs输出===:RTC-启动屏幕共享"); state.isOpenShare.value = true; final shareShareUid = int.tryParse(UserStore.to.userInfoEntity.value!.screenShareId); await state.rctEngine.value?.setAudioScenario(AudioScenarioType.audioScenarioGameStreaming); await state.rctEngine.value?.startScreenCapture(const ScreenCaptureParameters2(captureAudio: true, captureVideo: true)); await state.rctEngine.value?.joinChannelEx( token: state.meetingToken.value, connection: RtcConnection(channelId: state.roomNumber.value, localUid: shareShareUid), options: const ChannelMediaOptions( autoSubscribeVideo: false, autoSubscribeAudio: false, publishScreenTrack: true, publishSecondaryScreenTrack: true, publishCameraTrack: false, publishMicrophoneTrack: false, publishScreenCaptureAudio: true, publishScreenCaptureVideo: true, clientRoleType: ClientRoleType.clientRoleBroadcaster, )); doHttpSetTvAnchor(UserStore.to.userInfoEntity.value!.screenShareId); }*/ /// 停止共享屏幕(此版本不做) /*Future stopScreenCapture() async { debugPrint("wgs输出===:RTC-停止屏幕共享"); state.isOpenShare.value = false; final shareShareUid = int.tryParse(UserStore.to.userInfoEntity.value!.screenShareId); await state.rctEngine.value?.stopScreenCapture(); await state.rctEngine.value?.leaveChannelEx( connection: RtcConnection( channelId: state.roomNumber.value, localUid: shareShareUid ), options: const LeaveChannelOptions( stopMicrophoneRecording: false, stopAllEffect: false )); doHttpSetTvAnchor(UserStore.to.userInfoEntity.value!.uid); }*/ /// ------------------------------------------------------------------------------signalR Socket相关 /// Socket长连接 Future signalRSocket() async { state.hubConnection.value = HubConnectionBuilder() .withUrl( '${RequestConfig().baseUrl}/session-manage', HttpConnectionOptions( transport: HttpTransportType.webSockets, accessTokenFactory: () async => await Future.value(UserStore.to.token), logging: (level, message) => debugPrint("wgs输出===:SignalR Socket-日志:$message"), )) // 配置断线重连策略:默认重连5次,分别间隔2秒 4秒 4秒 5秒 10秒 .withAutomaticReconnect([2000, 4000, 4000, 5000, 10000]).build(); await state.hubConnection.value?.start(); // 开始重新连接时回调 state.hubConnection.value?.onreconnecting((error) { debugPrint("wgs输出===:SignalR Socket-重连$error"); if (EasyLoading.isShow == false) { ToastUtils.showLoadingToMask("网络故障,正在重连...", EasyLoadingMaskType.black); } }); // 重新连接成功时回调,5次重连任意一次成功就回调 state.hubConnection.value?.onreconnected((connectionId) { debugPrint("wgs输出===:SignalR Socket-重连成功$connectionId"); joinMeetingToSocket(true); ToastUtils.dismiss(); if (EasyLoading.isShow == false) { ToastUtils.showSuccessToMask("重连成功!", EasyLoadingMaskType.black); } }); // 重新连接失败时回调,5次重连都失败才回调 state.hubConnection.value?.onclose((error) { debugPrint("wgs输出===:SignalR Socket-重连失败$error"); ToastUtils.dismiss(); if ((state.isNormaExit.value == false && state.isShowOkAlertDialog.value == false) || null == UserStore.to.userInfoEntity.value) { showOkAlertDialog( context: Get.context!, title: "提示", message: "网络错误,请重新加入会议室", okLabel: "确定", barrierDismissible: false, ).then((OkCancelResult value) { Get.back(); Get.back(); }); } }); joinMeetingToSocket(false); /// ------------------------------------------------------------------------------发言权限相关回调 /// 开启关闭发言权限 state.hubConnection.value?.on("ManagerRefresh", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(e?[0]).runes.toList()); var jsonStr = json.encode(e?[0]); var listDynamic = jsonDecode(jsonStr); MeetingRoomUser meetingRoomUser = MeetingRoomUser.fromJson(listDynamic); if (meetingRoomUser.isRoomManager == true) { debugPrint("wgs输出===:Socket-开启发言权限:${e?[0]}--${e?[1]}"); for (MeetingRoomUser mru in state.cacheUsers.value) { if (mru.uid == meetingRoomUser.uid) { mru.roleId = meetingRoomUser.roleId; if(state.defaulOpenState.value == 1){ mru.enableMicr = true; } mru.isRoomManager = meetingRoomUser.isRoomManager; } } if (UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid) { state.isSpeak.value = true; debugPrint("wgs输出===:Socket-状态:${state.defaulOpenState.value}"); if (state.defaulOpenState.value == 1) { debugPrint("wgs输出===:Socket-状态1111111111111"); doHttpSetMicr(true); } else if (state.defaulOpenState.value == 2) { debugPrint("wgs输出===:Socket-状态2222222222222"); doHttpSetCamer(true); } // 设置声网SDK角色为主播 setClientRole("主播"); debugPrint("wgs输出===:Socket-开启发言权限:主播"); } } else { debugPrint("wgs输出===:Socket-关闭发言权限:${e?[0]}--${e?[1]}"); for (MeetingRoomUser mru in state.cacheUsers.value) { if (mru.uid == meetingRoomUser.uid) { mru.roleId = meetingRoomUser.roleId; mru.enableMicr = false; mru.enableCamera = false; mru.isRoomManager = meetingRoomUser.isRoomManager; } } if (UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid) { state.isSpeak.value = false; state.defaulOpenState.value = 0; state.isOpenMicrophone.value = false; state.isOpenCamera.value = false; state.isOpenShare.value = false; // 关闭麦克风视频 doHttpSetMicr(false); doHttpSetCamer(false); // 设置声网SDK角色为观众 setClientRole("观众"); // 取消发布视频流 muteLocalVideoStream(true); // 停止本地预览 stopPreview(); // 关闭本地预览悬浮窗 state.floating.value?.close(); // 当前全员观看主播是自己时 if (state.remoteUid.value == "0") { state.remoteUid.value = ""; changePageState(0); } // 停止共享屏幕(此版本不做) // stopScreenCapture(); debugPrint("wgs输出===:Socket-关闭发言权限:观众"); } } update(); }); /// ------------------------------------------------------------------------------麦克风相关回调 /// 所有用户开闭麦回调 state.hubConnection.value?.on("OperAllMicr", (e) { if (e?[0] == true) { debugPrint("wgs输出===:Socket-全员开麦"); } else { debugPrint("wgs输出===:Socket-全员闭麦"); } for (var i = 0; i < state.cacheUsers.value.length; i++) { state.cacheUsers.value[i].enableMicr = e?[0]; } state.users.value = state.cacheUsers.value; state.isOpenMicrophone.value = e?[0]; update(); }); /// 用户单独开闭麦回调 state.hubConnection.value?.on("OperMicr", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(e?[0]).runes.toList()); var jsonStr = json.encode(e?[0]); var listDynamic = jsonDecode(jsonStr); MeetingRoomUser meetingRoomUser = MeetingRoomUser.fromJson(listDynamic); if (meetingRoomUser.enableMicr == true) { debugPrint( "wgs输出===:Socket-用户单独开麦${meetingRoomUser.uid}-${meetingRoomUser.userName}"); for (MeetingRoomUser mru in state.cacheUsers.value) { if (mru.uid == meetingRoomUser.uid) { mru.enableMicr = true; } } if (meetingRoomUser.uid == UserStore.to.userInfoEntity.value!.uid) { state.isOpenMicrophone.value = true; // 恢复发布音频流 muteLocalAudioStream(false); } } else { debugPrint( "wgs输出===:Socket-用户单独闭麦${meetingRoomUser.uid}-${meetingRoomUser.userName}"); for (MeetingRoomUser mru in state.cacheUsers.value) { if (mru.uid == meetingRoomUser.uid) { mru.enableMicr = false; } } if (meetingRoomUser.uid == UserStore.to.userInfoEntity.value!.uid) { state.isOpenMicrophone.value = false; // 取消发布音频流 muteLocalAudioStream(true); } } update(); }); /// ------------------------------------------------------------------------------会议室进出相关回调 /// 远端用户或主播加入会议室回调 state.hubConnection.value?.on("UserJoined", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(user).runes.toList()); var jsonStr = json.encode(e); var listDynamic = jsonDecode(jsonStr); List meetingRoomUsers = (listDynamic as List) .map((e) => MeetingRoomUser.fromJson((e as Map))) .toList(); state.cacheUsers.value.addAll(meetingRoomUsers); state.users.value = state.cacheUsers.value; update(); debugPrint("wgs输出===:Socket-远端用户或主播加入会议室:$jsonStr"); }); /// 远端用户或主播离开会议室回调 state.hubConnection.value?.on("UserLeave", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(uid).runes.toList()); var jsonStr = json.encode(e); List listDynamic = jsonDecode(jsonStr); for (String uidStr in listDynamic) { for (var j = 0; j < state.cacheUsers.value.length; j++) { if (state.cacheUsers.value[j].uid == uidStr) { state.cacheUsers.value.removeAt(j); } } } state.users.value = state.cacheUsers.value; update(); if(listDynamic[0] != UserStore.to.userInfoEntity.value!.uid){ doHttpGetTvAnchor(); } debugPrint("wgs输出===:Socket-远端用户或主播离开会议室:$jsonStr"); }); /// 被移除会议回调 state.hubConnection.value?.on("ForceExitRoom", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(e).runes.toList()); var jsonStr = json.encode(e); debugPrint("wgs输出===:Socket-被移除会议:$jsonStr"); state.isNormaExit.value = true; ToastUtils.showSuccess("你已被移除会议"); Future.delayed(const Duration(seconds: 1), () { Get.back(); }); }); /// 全员结束会议回调 state.hubConnection.value?.on("AllLeave", (e) { debugPrint("wgs输出===:Socket-全员结束会议"); state.isNormaExit.value = true; ToastUtils.showSuccess("会议已结束"); Future.delayed(const Duration(seconds: 1), () { Get.back(); }); }); /// 强制退出 state.hubConnection.value?.on("ForceLogout", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(uid).runes.toList()); var jsonStr = json.encode(e); List listDynamic = jsonDecode(jsonStr); debugPrint("wgs输出===:Socket-强制退出:${listDynamic[0]}"); state.isNormaExit.value = true; ToastUtils.showSuccess("${listDynamic[0]}"); Future.delayed(const Duration(seconds: 1), () { UserStore.to.erase(); Get.offAllNamed(Routes.loginPage); }); }); /// ------------------------------------------------------------------------------会议室聊天相关回调 /// 会议室接收消息回调 state.hubConnection.value?.on("ReceiveMessage", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(e).runes.toList()); var jsonStr = json.encode(e); List list = json.decode(jsonStr); DateTime dateTime = DateTime.now(); MeetingRoomMsg meetingRoomMsg = MeetingRoomMsg(list[0], list[1], list[2], 0, formatDate(dateTime, [HH, ':', nn, ':', ss])); state.meetingRoomMsgs.value.add(meetingRoomMsg); state.msgNum.value = state.msgNum.value += 1; update(); Future.delayed(const Duration(milliseconds: 100), () { state.chatController .jumpTo(state.chatController.position.maxScrollExtent); state.msgNum.value = 0; }); debugPrint("wgs输出===:Socket-会议室接收消息:$jsonStr"); }); /// ------------------------------------------------------------------------------摄像头或者屏幕相关回调 /// 用户单独开闭摄像头回调 state.hubConnection.value?.on("OperCamera", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(e?[0]).runes.toList()); var jsonStr = json.encode(e?[0]); var listDynamic = jsonDecode(jsonStr); MeetingRoomUser meetingRoomUser = MeetingRoomUser.fromJson(listDynamic); if (meetingRoomUser.enableCamera == true) { debugPrint("wgs输出===:Socket-用户单独开摄像头${jsonStr}"); // 遍历会议室用户列表,更改摄像头状态 for (MeetingRoomUser mru in state.cacheUsers.value) { if (mru.uid == meetingRoomUser.uid) { mru.enableCamera = true; } debugPrint("wgs输出===:Socket-用户单独开摄像头---${state.remoteUid.value}--${meetingRoomUser.uid}"); if(state.remoteUid.value == meetingRoomUser.uid){ doHttpGetTvAnchor(); } } if (meetingRoomUser.uid == UserStore.to.userInfoEntity.value!.uid) { // 开启摄像头的用户是自己时 // 更改自己摄像头状态为开启 state.isOpenCamera.value = true; // 恢复发布视频流 muteLocalVideoStream(false); // 开始本地预览 startPreview(); // 切换页面到视频状态 changePageState(1); // 只有在视频大屏并且全员观看主播不是自己的时候,才打开右上角悬浮窗 if (state.pageIndex.value == 0 && state.remoteUid.value != "0") { state.floating.value?.open(state.context.value!); } } } else { debugPrint("wgs输出===:Socket-用户单独闭摄像头"); // 遍历会议室用户列表,更改摄像头状态 for (MeetingRoomUser mru in state.cacheUsers.value) { if (mru.uid == meetingRoomUser.uid) { mru.enableCamera = false; } } if (meetingRoomUser.uid == UserStore.to.userInfoEntity.value!.uid) { // 关闭摄像头的用户是自己时 // 更改自己摄像头状态为关闭 state.isOpenCamera.value = false; // 取消发布视频流 muteLocalVideoStream(true); // 停止本地预览 stopPreview(); // 当前全员观看主播是自己时 if (state.remoteUid.value == "0") { state.remoteUid.value = ""; } // 关闭本地预览悬浮窗 if (state.floating.value?.isShowing == true) { state.floating.value?.close(); } } } update(); }); /// 设置新的全员观看视频主播回调 state.hubConnection.value?.on("ShowUser", (e) { // var jsonStr = const Utf8Decoder().convert(json.encode(e).runes.toList()); var jsonStr = json.encode(e); List list = json.decode(jsonStr); doHttpGetTvAnchor(); debugPrint("wgs输出===:Socket-设置新的全员观看视频主播:${list[0]}"); }); } /// 加入会议室 /// isAgain:是否重新加入 Future joinMeetingToSocket(bool isAgain) async { await state.hubConnection.value?.invoke("joinChannel", args: [state.roomNumber.value, state.isOpenMicrophone.value, state.isOpenCamera.value, state.isSpeak.value]); mergeFetch(isAgain); /*if(isAgain == false){ mergeFetch(isAgain); initRtc(false); }else{ mergeFetch(true); }*/ } /// 离开会议室 Future leaveMeetingToSocket() async { await state.hubConnection.value ?.invoke("levelChannel", args: [state.roomNumber.value]); state.hubConnection.value?.stop(); } /// 会议室发送消息 Future sendMsg(String msg) async { DateTime dateTime = DateTime.now(); MeetingRoomMsg meetingRoomMsg = MeetingRoomMsg( UserStore.to.userInfoEntity.value!.uid, UserStore.to.userInfoEntity.value!.userName, msg, 1, formatDate(dateTime, [HH, ':', nn, ':', ss])); state.meetingRoomMsgs.value.add(meetingRoomMsg); update(); Future.delayed(const Duration(milliseconds: 100), () { state.chatController .jumpTo(state.chatController.position.maxScrollExtent); }); await state.hubConnection.value ?.invoke("sendChannelMsg", args: [state.roomNumber.value, msg]); } /// ------------------------------------------------------------------------------声网SDK相关 /// 初始化声网SDK Future initRtc() async { // 创建 RtcEngine 对象 state.rctEngine.value = createAgoraRtcEngineEx(); // 初始化 RtcEngine,设置频道场景为 channelProfileLiveBroadcasting(直播场景) await state.rctEngine.value?.initialize(RtcEngineContext( appId: state.appId, channelProfile: ChannelProfileType.channelProfileLiveBroadcasting, // logConfig:const LogConfig() )); // 音频模块默认启动,所以这里不再调用启动方法 // 启用视频模块 enableVideo(); // 设置默认音频路由为听筒 await state.rctEngine.value?.setDefaultAudioRouteToSpeakerphone(false); // 打开用户音量回调 await state.rctEngine.value?.enableAudioVolumeIndication( interval: 200, smooth: 3, reportVad: true); // 在发送端设置双流模式并设置视频小流 await state.rctEngine.value ?.setDualStreamMode(mode: SimulcastStreamMode.enableSimulcastStream); joinMeetingToRtc(); // 回调 state.rctEngine.value?.registerEventHandler( RtcEngineEventHandler( // 成功加入会议室回调 onJoinChannelSuccess: (RtcConnection connection, int elapsed) { state.isJoinSuccess.value = true; debugPrint("wgs输出===:RTC-自己加入会议室,ID:${connection.localUid}"); }, // 成功离开会议室回调 onLeaveChannel: (RtcConnection connection, RtcStats stats) { debugPrint("wgs输出===:RTC-自己离开会议室,ID:${connection.localUid}"); }, // 远端用户或主播加入当前会议室回调-主播角色才能接收该回调 onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) { debugPrint("wgs输出===:RTC-远端用户或主播加入会议室,用户或主机的ID:$remoteUid"); }, // 远端用户或主播离开当前会议室回调-主播角色才能接收该回调 onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) async { // 判断是否用户取消了共享 if (remoteUid.toString().length == 9) { for (var i = 0; i < state.cacheUsers.value.length; i++) { if (remoteUid.toString() == state.cacheUsers.value[i].screenShareId) { state.cacheUsers.value[i].enableShare = false; } } } update(); debugPrint("wgs输出===:RTC-远端用户或主播离开会议室,用户或主机的ID:$remoteUid"); }, // 音频路由发生变化回调 onAudioRoutingChanged: (int routing) { debugPrint("wgs输出===:RTC-音频路由切换:$routing"); state.communicationMode.value = routing; if (routing == 1) { debugPrint("wgs输出===:RTC-音频路由切换为听筒"); } else if (routing == 3) { debugPrint("wgs输出===:RTC-音频路由切换为扬声器"); } else { debugPrint("wgs输出===:RTC-音频路由切换为外接设备"); } }, // 音频采集开关回调 onLocalAudioStateChanged: (RtcConnection connection, LocalAudioStreamState state, LocalAudioStreamReason reason) { debugPrint("wgs输出===:RTC-音频采集开关:$state"); }, // 远端视频状态发生改变回调 onRemoteVideoStateChanged: (RtcConnection connection, int remoteUid, RemoteVideoState remoteVideoState, RemoteVideoStateReason remoteVideoStateReason, int elapsed) { debugPrint( "wgs输出===:RTC-远端视频状态发生改变:ID-$remoteUid-状态-$remoteVideoStateReason"); if (remoteVideoStateReason == RemoteVideoStateReason.remoteVideoStateReasonRemoteMuted) { // 远端用户停止发送视频流或远端用户禁用视频模块 if (remoteUid.toString().length != 9) { // 摄像头 if (remoteUid.toString() == state.remoteUid.value) { // 如果停止发送视频流或禁用视频模块的远端用户是当前全员观看主播 doHttpGetTvAnchor(); } } else { // 共享屏幕(此版本不做) } } }, // 用户音量提示回调 onAudioVolumeIndication: (RtcConnection connection, List speakers, int speakerNumber, int totalVolume) { if (speakers.isNotEmpty) { for (AudioVolumeInfo avi in speakers) { if(avi.uid == 0){ debugPrint("wgs输出===:RTC-本地用户音量提示:${avi.uid}--${avi.volume}"); for(MeetingRoomUser mru in state.cacheUsers.value){ if(UserStore.to.userInfoEntity.value!.uid == mru.uid){ mru.volume = CountMicrophoneVolume.getVolume(avi.volume!); state.microphoneVolume.value = CountMicrophoneVolume.getVolume(avi.volume!); } } }else{ debugPrint("wgs输出===:RTC-远端用户音量提示:${avi.uid}--${avi.volume}"); for(MeetingRoomUser mru in state.cacheUsers.value){ mru.volume = CountMicrophoneVolume.getVolume(avi.volume!); if (avi.volume != 0) { state.spokesman.value = mru.userName; state.spokesmanVolume.value = CountMicrophoneVolume.getVolume(avi.volume!); } else { state.spokesman.value = ""; state.spokesmanVolume.value = 0; } } } /*for (MeetingRoomUser mru in state.cacheUsers.value) { // 用于更改语音布局里的用户列表麦克风 if (avi.uid == 0) { // debugPrint("wgs输出===:RTC-用户音量提示(自己):${CountMicrophoneVolume.getVolume(avi.volume!)}"); mru.volume = CountMicrophoneVolume.getVolume(avi.volume!); state.microphoneVolume.value = CountMicrophoneVolume.getVolume(avi.volume!); } else { debugPrint("wgs输出===:RTC-用户音量提示:${avi.uid}--${mru.uid}"); if (avi.uid.toString() == mru.uid) { debugPrint("wgs输出===:RTC-用户音量提示(远端用户):${speakers[0].uid}--${speakers[0].volume}"); mru.volume = CountMicrophoneVolume.getVolume(avi.volume!); if (avi.volume != 0) { state.spokesman.value = mru.userName; state.spokesmanVolume.value = CountMicrophoneVolume.getVolume(avi.volume!); } else { state.spokesman.value = ""; state.spokesmanVolume.value = 0; } } } }*/ } } }, // 切换用户角色回调 onClientRoleChanged: (RtcConnection connection, ClientRoleType oldRole, ClientRoleType newRole, ClientRoleOptions newRoleOptions) { debugPrint( "wgs输出===:RTC-切换用户角色为:${newRole == ClientRoleType.clientRoleBroadcaster ? "主播" : "观众"}"); }, // token即将在30秒内过期回调 onTokenPrivilegeWillExpire: (RtcConnection connection, String token) { doHttpGetMeetingToken(false); }, // 本地视频状态发生改变回调 onLocalVideoStateChanged: (VideoSourceType source, LocalVideoStreamState state, LocalVideoStreamReason reason) { debugPrint("wgs输出===:RTC-本地视频状态发生改变:$source--$state--$reason"); }, // 网络连接状态回调 onConnectionStateChanged: (RtcConnection connection, ConnectionStateType stateType, ConnectionChangedReasonType reason) { debugPrint("wgs输出===:RTC-网络连接状态发生改变:" "会议室编号(${connection.channelId})," "网络状态($stateType-${AgoraUtil.getConnectionStateChangedType(stateType)})," "网络改变原因($reason-${AgoraUtil.getConnectionChangedReasonType(reason)})"); if (stateType == ConnectionStateType.connectionStateReconnecting) { if (EasyLoading.isShow == false) { ToastUtils.showLoadingToMask( "网络故障,正在重连...", EasyLoadingMaskType.black); } } else if (stateType == ConnectionStateType.connectionStateConnected && reason == ConnectionChangedReasonType.connectionChangedRejoinSuccess) { ToastUtils.dismiss(); if (EasyLoading.isShow == false) { ToastUtils.showSuccessToMask("重连成功!", EasyLoadingMaskType.black); } } else if (reason == ConnectionChangedReasonType.connectionChangedLost) { // 和服务器失去连接后,再延迟15秒(和signalR Socket一致),让SDK继续重连,如果重连不上则告知用户需要重新加入 Future.delayed(const Duration(milliseconds: 15000), () { ToastUtils.dismiss(); if (state.isShowOkAlertDialog.value == false) { showOkAlertDialog( context: Get.context!, title: "提示", message: "网络错误,请重新加入会议室", okLabel: "确定", barrierDismissible: false, ).then((OkCancelResult value) { Get.back(); Get.back(); }); } }); } } // 获取设备权限出错回调 /*onPermissionError: (PermissionType permissionType){ debugPrint("wgs输出===:RTC-获取设备权限出错:$permissionType"); if(permissionType == PermissionType.screenCapture){ // 获取共享屏幕出错(此版本不做) state.isOpenShare.value = false; stopScreenCapture(); } }*/ ), ); } /// 加入会议室 Future joinMeetingToRtc() async { await state.rctEngine.value?.joinChannel( token: state.meetingToken.value, channelId: state.roomNumber.value, uid: int.parse(UserStore.to.userInfoEntity.value!.uid), options: const ChannelMediaOptions( // 自动订阅所有视频流 autoSubscribeVideo: true, // 自动订阅所有音频流 autoSubscribeAudio: true, // 发布摄像头采集的视频 publishCameraTrack: true, // 发布麦克风采集的音频 publishMicrophoneTrack: true, // 设置用户角色为 clientRoleBroadcaster(主播)或 clientRoleAudience(观众) // 这里设置角色为clientRoleBroadcaster(主播) // 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频 // 观众:可以在频道内订阅音视频,不具备发布音视频权限 clientRoleType: ClientRoleType.clientRoleAudience), ); } /// 离开会议室 Future leaveMeetingToRtc() async { // 离开 await state.rctEngine.value?.leaveChannel(); // 释放资源 await state.rctEngine.value?.release(); } /// 设置用户角色 Future setClientRole(String roleStr) async { if (roleStr == "主播") { await state.rctEngine.value ?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); } else { await state.rctEngine.value ?.setClientRole(role: ClientRoleType.clientRoleAudience); } } /// 设置音频输出路由(没有外接设备时生效) Future setEnableSpeakerphone(int mode) async { state.communicationMode.value = mode; await state.rctEngine.value ?.setEnableSpeakerphone(mode == 1 ? false : true); } /// 设置启用视频模块 Future enableVideo() async { await state.rctEngine.value?.enableVideo(); } /// 取消或恢复发布本地音频流 Future muteLocalAudioStream(bool mute) async { await state.rctEngine.value?.muteLocalAudioStream(mute); } /// 取消或恢复发布本地视频流 Future muteLocalVideoStream(bool mute) async { await state.rctEngine.value?.muteLocalVideoStream(mute); } /// 开始本地预览 Future startPreview() async { await state.rctEngine.value?.startPreview(); } /// 停止本地预览 Future stopPreview() async { await state.rctEngine.value?.stopPreview(); } /// 切换前置/后置摄像头 Future switchCamera() async { await state.rctEngine.value?.switchCamera(); } }