1.麦克风特效

2.开闭麦、开闭摄像头逻辑优化
3.声网SDK设置角色逻辑优化
This commit is contained in:
fuenmao 2024-11-29 14:24:29 +08:00
parent 4d750d6316
commit 175ffa873c
10 changed files with 266 additions and 44 deletions

View File

@ -36,7 +36,10 @@ class MeetingRoomUser extends Object{
@JsonKey(name: 'isRoomManager') @JsonKey(name: 'isRoomManager')
bool isRoomManager; bool isRoomManager;
MeetingRoomUser(this.uid,this.connectId,this.account,this.enableMicr,this.enableCamera,this.screenShareId,this.userName,this.roleId,this.roleName,this.isRoomManager,); @JsonKey(name: 'volume')
double? volume = 0.0;
MeetingRoomUser(this.uid,this.connectId,this.account,this.enableMicr,this.enableCamera,this.screenShareId,this.userName,this.roleId,this.roleName,this.isRoomManager,this.volume);
factory MeetingRoomUser.fromJson(Map<String, dynamic> srcJson) => _$MeetingRoomUserFromJson(srcJson); factory MeetingRoomUser.fromJson(Map<String, dynamic> srcJson) => _$MeetingRoomUserFromJson(srcJson);

View File

@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:signalr_core/signalr_core.dart'; import 'package:signalr_core/signalr_core.dart';
import 'package:wgshare/common/store/user_store.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/config/request_config.dart';
import '../../common/mixins/request_tool_mixin.dart'; import '../../common/mixins/request_tool_mixin.dart';
import '../../common/models/common/base_structure_result.dart'; import '../../common/models/common/base_structure_result.dart';
@ -137,9 +138,10 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
/// ///
Future<void> doHttpCancelSpeak() async { Future<void> doHttpCancelSpeak() async {
BaseStructureResult res = await getClient().cancelSpeak(state.meetingRoomInfo.value!.id, state.meetingRoomInfo.value!.roomNum, UserStore.to.userInfoEntity.value!.uid); BaseStructureResult res = await getClient().cancelSpeak(state.meetingRoomInfo.value!.id, state.meetingRoomInfo.value!.roomNum, UserStore.to.userInfoEntity.value!.uid);
setClientRole("观众");
} }
/// ///
void setMicrophoneOpen(bool isOpen){ void setMicrophoneOpen(bool isOpen){
state.isOpenMicrophone.value = isOpen; state.isOpenMicrophone.value = isOpen;
for(var i = 0; i < state.cacheUsers.value.length; i++){ for(var i = 0; i < state.cacheUsers.value.length; i++){
@ -148,7 +150,22 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
} }
} }
state.users.value = state.cacheUsers.value; state.users.value = state.cacheUsers.value;
setEnableLocalAudio(isOpen); if(isOpen == true){
setClientRole("主播");
}else{
setClientRole("观众");
}
}
///
void setCameraOpen(bool isOpen){
state.isOpenCamera.value = isOpen;
if(isOpen == true){
setEnableLocalVideo(isOpen);
setClientRole("主播");
}else{
setClientRole("观众");
}
} }
/// --------------------------signalR Socket相关 /// --------------------------signalR Socket相关
@ -207,7 +224,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
if(UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid){ if(UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid){
state.isSpeak.value = true; state.isSpeak.value = true;
state.isOpenMicrophone.value = true; state.isOpenMicrophone.value = true;
setEnableLocalAudio(true); setClientRole("主播");
} }
}else{ }else{
debugPrint("wgs输出===Socket-停止发言:${e?[0]}--${e?[1]}"); debugPrint("wgs输出===Socket-停止发言:${e?[0]}--${e?[1]}");
@ -221,7 +238,7 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
if(UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid){ if(UserStore.to.userInfoEntity.value!.uid == meetingRoomUser.uid){
state.isSpeak.value = false; state.isSpeak.value = false;
state.isOpenMicrophone.value = false; state.isOpenMicrophone.value = false;
setEnableLocalAudio(false); setClientRole("观众");
} }
} }
update(); update();
@ -268,7 +285,6 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
} }
state.users.value = state.cacheUsers.value; state.users.value = state.cacheUsers.value;
state.isOpenMicrophone.value = e?[0]; state.isOpenMicrophone.value = e?[0];
setEnableLocalAudio(e?[0]);
}); });
/// ///
@ -284,7 +300,6 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
} }
} }
state.isOpenMicrophone.value = true; state.isOpenMicrophone.value = true;
setEnableLocalAudio(true);
}else{ }else{
debugPrint("wgs输出===Socket-单独用户闭麦"); debugPrint("wgs输出===Socket-单独用户闭麦");
for(MeetingRoomUser mru in state.cacheUsers.value){ for(MeetingRoomUser mru in state.cacheUsers.value){
@ -293,7 +308,6 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
} }
} }
state.isOpenMicrophone.value = false; state.isOpenMicrophone.value = false;
setEnableLocalAudio(false);
} }
}); });
} }
@ -340,7 +354,11 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
)); ));
// //
state.rctEngine.value?.setDefaultAudioRouteToSpeakerphone(false); await state.rctEngine.value?.setDefaultAudioRouteToSpeakerphone(false);
//
await state.rctEngine.value?.enableAudioVolumeIndication(interval: 200, smooth: 3, reportVad: true);
//
await state.rctEngine.value?.enableAudio();
joinMeetingToRtc(); joinMeetingToRtc();
@ -384,6 +402,49 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
// //
onLocalAudioStateChanged: (RtcConnection connection, LocalAudioStreamState state, LocalAudioStreamReason reason){ onLocalAudioStateChanged: (RtcConnection connection, LocalAudioStreamState state, LocalAudioStreamReason reason){
debugPrint("wgs输出===RTC-音频采集开关:$state"); debugPrint("wgs输出===RTC-音频采集开关:$state");
},
//
onRemoteVideoStateChanged: (RtcConnection connection,
int remoteUid,
RemoteVideoState state,
RemoteVideoStateReason reason,
int elapsed){
debugPrint("wgs输出===RTC-视频采集开关:$state");
},
//
onAudioVolumeIndication: (
RtcConnection connection,
List<AudioVolumeInfo> speakers,
int speakerNumber,
int totalVolume){
if(speakers.isNotEmpty){
for(AudioVolumeInfo avi in speakers){
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{
if(avi.uid.toString() == mru.uid){
debugPrint("wgs输出===RTC-用户音量提示(远端用户):${speakers[0].uid}--${speakers[0].volume}");
mru.volume = CountMicrophoneVolume.getVolume(avi.volume!);
}
}
}
}
}
},
//
onClientRoleChanged: (
RtcConnection connection,
ClientRoleType oldRole,
ClientRoleType newRole,
ClientRoleOptions newRoleOptions){
debugPrint("wgs输出===RTC-切换用户角色");
} }
), ),
); );
@ -394,21 +455,21 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
await state.rctEngine.value?.joinChannel( await state.rctEngine.value?.joinChannel(
token: state.meetingToken.value, token: state.meetingToken.value,
channelId: state.roomNumber.value, channelId: state.roomNumber.value,
uid: 0, uid: int.parse(UserStore.to.userInfoEntity.value!.uid),
options: const ChannelMediaOptions( options: const ChannelMediaOptions(
// //
autoSubscribeVideo: true, autoSubscribeVideo: true,
// //
autoSubscribeAudio: true, autoSubscribeAudio: true,
// //
publishCameraTrack: false, publishCameraTrack: true,
// //
publishMicrophoneTrack: false, publishMicrophoneTrack: true,
// clientRoleBroadcaster clientRoleAudience // clientRoleBroadcaster clientRoleAudience
// clientRoleBroadcaster // clientRoleBroadcaster
// //
// //
clientRoleType: ClientRoleType.clientRoleBroadcaster), clientRoleType: ClientRoleType.clientRoleAudience),
); );
} }
@ -420,14 +481,34 @@ class MeetingMainLogic extends GetxController with RequestToolMixin{
await state.rctEngine.value?.release(); await state.rctEngine.value?.release();
} }
///
Future<void> setClientRole(String roleStr) async {
if(roleStr == "主播"){
await state.rctEngine.value?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
}else{
await state.rctEngine.value?.setClientRole(role: ClientRoleType.clientRoleAudience);
}
}
/// ///
Future<void> setEnableSpeakerphone(int mode) async { Future<void> setEnableSpeakerphone(int mode) async {
state.communicationMode.value = mode; state.communicationMode.value = mode;
await state.rctEngine.value?.setEnableSpeakerphone(mode == 1 ? false : true); await state.rctEngine.value?.setEnableSpeakerphone(mode == 1 ? false : true);
} }
/// ///
Future<void> setEnableLocalAudio(bool enabled) async { Future<void> setEnableLocalVideo(bool enabled) async {
await state.rctEngine.value?.enableLocalAudio(enabled); state.isOpenCamera.value = enabled;
if(enabled == true){
//
await state.rctEngine.value?.enableVideo();
//
await state.rctEngine.value?.startPreview();
}else{
//
await state.rctEngine.value?.disableVideo();
//
await state.rctEngine.value?.stopPreview();
}
} }
} }

View File

@ -55,6 +55,10 @@ class MeetingMainState {
late RxBool isSpeak = false.obs; late RxBool isSpeak = false.obs;
/// ///
late RxBool isOpenMicrophone = false.obs; late RxBool isOpenMicrophone = false.obs;
///
late RxDouble microphoneVolume = 0.0.obs;
///
late RxBool isOpenCamera = false.obs;
/// ///
late RxList<MeetingRoomMsg> meetingRoomMsgs = RxList([]); late RxList<MeetingRoomMsg> meetingRoomMsgs = RxList([]);

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:liquid_progress_indicator_v2/liquid_progress_indicator.dart';
import 'package:wgshare/common/store/user_store.dart'; import 'package:wgshare/common/store/user_store.dart';
import 'package:wgshare/pages/metting/share/meeting_main_share_view.dart'; import 'package:wgshare/pages/metting/share/meeting_main_share_view.dart';
import 'package:wgshare/pages/metting/video/meeting_main_video_view.dart'; import 'package:wgshare/pages/metting/video/meeting_main_video_view.dart';
@ -9,6 +10,7 @@ import 'package:wgshare/utils/toast_utils.dart';
import '../../utils/color_util.dart'; import '../../utils/color_util.dart';
import '../../utils/cus_behavior.dart'; import '../../utils/cus_behavior.dart';
import '../../view/view_svg_path.dart';
import 'meeting_main_logic.dart'; import 'meeting_main_logic.dart';
import 'meeting_main_state.dart'; import 'meeting_main_state.dart';
import 'voice/meeting_main_voice_view.dart'; import 'voice/meeting_main_voice_view.dart';
@ -167,19 +169,21 @@ class MeetingMainPage extends StatelessWidget {
// //
Visibility( Visibility(
visible: true, visible: state.pageState.value == 0,
child: MeetingMainVoiceComponent(users: state.cacheUsers.value) child: MeetingMainVoiceComponent(users: state.cacheUsers.value)
), ),
// //
Visibility( Visibility(
visible: false, visible: state.pageState.value == 1,
child: MeetingMainVideoComponent() child: null != state.rctEngine.value
? MeetingMainVideoComponent(rtcEngine: state.rctEngine.value!, channelId: state.roomNumber.value,isOpenCamera: state.isOpenCamera.value)
: Container()
), ),
// //
Visibility( Visibility(
visible: false, visible: state.pageState.value == 2,
child: MeetingMainShareComponent() child: MeetingMainShareComponent()
), ),
@ -247,14 +251,32 @@ class MeetingMainPage extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset( state.isSpeak.value == false
? Image.asset(
state.isSpeak.value == false state.isSpeak.value == false
? 'assets/images/meeting_main_sqfy.png' ? 'assets/images/meeting_main_sqfy.png'
: state.isOpenMicrophone.value == true : state.isOpenMicrophone.value == true
? 'assets/images/meeting_main_microphone_default.png' ? 'assets/images/meeting_main_microphone_default.png'
: 'assets/images/meeting_main_sqfy.png', : 'assets/images/meeting_main_sqfy.png',
width: 22.w, width: 20.w,
height: 22.h, height: 20.h,
)
: state.isOpenMicrophone.value == true
? Container(
width: 20.w,
height: 20.h,
child: LiquidCustomProgressIndicator(
value: state.microphoneVolume.value,
valueColor: const AlwaysStoppedAnimation(ColorUtil.Color_2_177_136),
backgroundColor: ColorUtil.Color_255_255_255,
direction: Axis.vertical,
shapePath: ViewSvgPath.getMicrpphonePath()
),
)
: Image.asset(
'assets/images/meeting_main_sqfy.png',
width: 20.w,
height: 20.h,
) , ) ,
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
@ -288,13 +310,13 @@ class MeetingMainPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset( Image.asset(
'assets/images/meeting_main_sp.png', state.isSpeak.value == true ? state.isOpenCamera.value == true ? 'assets/images/meeting_main_camera_open.png' : 'assets/images/meeting_main_camera_default.png' : 'assets/images/meeting_main_sp.png',
width: 22.w, width: 22.w,
height: 22.h, height: 22.h,
), ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
'开启视频', state.isOpenCamera.value == true ? "关闭视频" : "开启视频",
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: ColorUtil.Color_202_202_202), color: ColorUtil.Color_202_202_202),
@ -302,7 +324,13 @@ class MeetingMainPage extends StatelessWidget {
], ],
), ),
onTap: (){ onTap: (){
ToastUtils.getErrFluttertoast(context: context, msg: '开启视频...'); if(state.isSpeak.value == true){
if(state.isOpenCamera.value == true){
logic.setCameraOpen(false);
}else{
logic.setCameraOpen(true);
}
}
}, },
), ),

View File

@ -1,13 +1,19 @@
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:wgshare/common/store/user_store.dart';
import '../../../utils/color_util.dart'; import '../../../utils/color_util.dart';
import 'meeting_main_video_logic.dart'; import 'meeting_main_video_logic.dart';
import 'meeting_main_video_state.dart'; import 'meeting_main_video_state.dart';
class MeetingMainVideoComponent extends StatelessWidget { class MeetingMainVideoComponent extends StatelessWidget {
MeetingMainVideoComponent({Key? key}) : super(key: key); MeetingMainVideoComponent({super.key, required this.rtcEngine, required this.channelId, required this.isOpenCamera});
final RtcEngine rtcEngine;
final String channelId;
final bool isOpenCamera;
final MeetingMainVideoLogic logic = Get.put(MeetingMainVideoLogic()); final MeetingMainVideoLogic logic = Get.put(MeetingMainVideoLogic());
final MeetingMainVideoState state = Get.find<MeetingMainVideoLogic>().state; final MeetingMainVideoState state = Get.find<MeetingMainVideoLogic>().state;
@ -85,23 +91,28 @@ class MeetingMainVideoComponent extends StatelessWidget {
), ),
), ),
), ),
///
Positioned( Positioned(
top: 58, top: 58,
right: 13, right: 12,
child: Stack( child: Stack(
children: [ children: [
Container( Visibility(
visible: isOpenCamera,
child: SizedBox(
width: 120.w, width: 120.w,
height: 150.h, height: 150.h,
padding: const EdgeInsets.only(left: 12, right: 12), child: Center(
decoration: BoxDecoration( child: isOpenCamera
image: DecorationImage( ? AgoraVideoView(
fit: BoxFit.fill, controller: VideoViewController(
image: NetworkImage( rtcEngine: rtcEngine,
"https://tse1-mm.cn.bing.net/th/id/OIP-C.hdhK40Dw3yN_2mjNQNqFCgAAAA?w=186&h=186&c=7&r=0&o=5&pid=1.7", canvas: VideoCanvas(uid: int.parse(UserStore.to.userInfoEntity.value!.uid)),
),
), ),
) )
: const CircularProgressIndicator(),
),
),
), ),
Positioned( Positioned(
left: 4, left: 4,

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:liquid_progress_indicator_v2/liquid_progress_indicator.dart';
import '../../../common/models/meeting_room_user.dart'; import '../../../common/models/meeting_room_user.dart';
import '../../../utils/color_util.dart'; import '../../../utils/color_util.dart';
import '../../../utils/cus_behavior.dart'; import '../../../utils/cus_behavior.dart';
import '../../../view/view_svg_path.dart';
import 'meeting_main_voice_logic.dart'; import 'meeting_main_voice_logic.dart';
import 'meeting_main_voice_state.dart'; import 'meeting_main_voice_state.dart';
@ -54,13 +56,38 @@ class MeetingMainVoiceComponent extends StatelessWidget {
), ),
), ),
SizedBox(height: 6.h), SizedBox(height: 6.h),
Row( users[index].enableMicr == true
? Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 20.w,
height: 20.h,
child: LiquidCustomProgressIndicator(
value: users[index].volume ?? 0.0,
valueColor: const AlwaysStoppedAnimation(ColorUtil.Color_2_177_136),
backgroundColor: ColorUtil.Color_255_255_255,
direction: Axis.vertical,
shapePath: ViewSvgPath.getMicrpphonePath()
),
),
Text(
users[index].userName,
style: TextStyle(
fontSize: 12.sp,
color: ColorUtil.Color_255_255_255),
)
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Image.asset( Image.asset(
users[index].enableMicr == true ? 'assets/images/meeting_main_speak1.png' : 'assets/images/meeting_main_microphone_open.png', 'assets/images/meeting_main_microphone_open.png',
width: 22.w, width: 20.w,
height: 22.h, height: 20.h,
), ),
Text( Text(
users[index].userName, users[index].userName,

View File

@ -0,0 +1,17 @@
/// SDK返回的用户音量
class CountMicrophoneVolume {
static double getVolume(int volume){
var resultVolume = 0.0;
if(volume == 0){
resultVolume = 0;
}else if(volume > 0 && volume < 200){
resultVolume = volume / 200;
}else{
resultVolume = 1;
}
return resultVolume;
}
}

View File

@ -0,0 +1,40 @@
import 'dart:ui';
import 'package:flutter/material.dart';
/// SVG路径
class ViewSvgPath {
///
static Path getMicrpphonePath(){
Path mPath = Path();
mPath.moveTo(10.2188, 11.875);
mPath.cubicTo(11.9375, 11.875, 13.3438, 10.4688, 13.3438, 8.75);
mPath.lineTo(13.3438, 5);
mPath.cubicTo(13.3438, 3.28125, 11.9375, 1.875, 10.2188, 1.875);
mPath.cubicTo(8.5, 1.875, 7.09375, 3.28125, 7.09375, 5);
mPath.lineTo(7.09375, 8.75);
mPath.cubicTo(7.09375, 10.4688, 8.5, 11.875, 10.2188, 11.875);
mPath.moveTo(15.777, 9.61683);
mPath.cubicTo(15.8297, 9.27504, 15.5973, 8.95668, 15.2555, 8.90394);
mPath.cubicTo(14.9137, 8.85316, 14.5954, 9.08558, 14.5426, 9.42543);
mPath.cubicTo(14.2145, 11.5348, 12.3571, 13.1246, 10.2184, 13.1246);
mPath.cubicTo(8.07973, 13.1246, 6.22035, 11.5329, 5.89418, 9.42348);
mPath.cubicTo(5.84144, 9.08168, 5.52113, 8.84926, 5.18129, 8.90199);
mPath.cubicTo(4.83949, 8.95473, 4.60707, 9.27308, 4.6598, 9.61488);
mPath.cubicTo(5.05433, 12.1637, 7.08168, 14.0641, 9.5934, 14.3375);
mPath.lineTo(9.5934, 16.2496);
mPath.lineTo(7.7184, 16.2496);
mPath.cubicTo(7.37269, 16.2496, 7.0934, 16.5289, 7.0934, 16.8746);
mPath.cubicTo(7.0934, 17.2204, 7.37269, 17.4996, 7.7184, 17.4996);
mPath.lineTo(12.7184, 17.4996);
mPath.cubicTo(13.0641, 17.4996, 13.3434, 17.2204, 13.3434, 16.8746);
mPath.cubicTo(13.3434, 16.5289, 13.0641, 16.2496, 12.7184, 16.2496);
mPath.lineTo(10.8434, 16.2496);
mPath.lineTo(10.8434, 14.3375);
mPath.cubicTo(13.3532, 14.0641, 15.3825, 12.1657, 15.777, 9.61683);
mPath.close();
return mPath;
}
}

View File

@ -594,6 +594,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
liquid_progress_indicator_v2:
dependency: "direct main"
description:
name: liquid_progress_indicator_v2
sha256: "6bb2c675bab4936864a63ccd503be417e407974e11c62711917a4006bb9288b8"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.0"
logger: logger:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -76,6 +76,9 @@ dependencies:
# .net socket通信插件 # .net socket通信插件
signalr_core: ^1.1.1 signalr_core: ^1.1.1
# 水波效果的进度器
liquid_progress_indicator_v2: ^0.5.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter