From 0b981bc13b8dda15207581e00830b706585b7fe7 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Fri, 21 Nov 2025 00:08:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(live):=20=E5=AE=9E=E7=8E=B0=E7=9B=B4?= =?UTF-8?q?=E6=92=AD=E8=A7=82=E7=9C=8B=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96RTC=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增观众端加入直播间逻辑,支持主播和观众两种角色 - 优化RTC管理器,增加远程用户列表监听和通知机制 - 调整直播页面UI,完善主播展示区域和连麦用户显示 - 更新网络请求,支持通过频道ID获取RTC Token - 完善登录流程,修复用户ID存储逻辑 - 增加网络安全配置文件,提升应用安全性 - 扩展Android支持的ABI架构,提高兼容性 - 优化登录页面布局和交互细节 --- android/app/build.gradle.kts | 3 + android/app/src/main/AndroidManifest.xml | 1 + .../main/res/xml/network_security_config.xml | 13 ++ lib/controller/discover/room_controller.dart | 29 ++- lib/controller/mine/login_controller.dart | 2 +- lib/main.dart | 4 +- lib/network/rtc_api.dart | 4 +- lib/network/rtc_api.g.dart | 6 +- lib/pages/discover/discover_page.dart | 18 +- lib/pages/discover/live_room_page.dart | 2 +- lib/pages/mine/login_page.dart | 116 ++++++----- lib/rtc/rtc_manager.dart | 79 +++++--- lib/rtc/rtm_manager.dart | 14 +- .../live/live_room_anchor_showcase.dart | 184 ++++++++++-------- 14 files changed, 293 insertions(+), 182 deletions(-) create mode 100644 android/app/src/main/res/xml/network_security_config.xml diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 77dd3e4..6cf187b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -55,6 +55,9 @@ android { ndk { abiFilters.clear() abiFilters += "arm64-v8a" + abiFilters += "armeabi-v7a" + abiFilters += "x86" + abiFilters += "x86_64" // 或者:abiFilters.add("arm64-v8a") } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4a34cf8..bf4ea4c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ android:icon="@mipmap/ic_launcher"> + + + + + + + + + TLSv1.2 + + + \ No newline at end of file diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index 9451f99..a98830c 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -1,3 +1,5 @@ +import 'package:agora_rtc_engine/agora_rtc_engine.dart'; +import 'package:agora_token_generator/agora_token_generator.dart'; import 'package:dating_touchme_app/model/live/live_chat_message.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/network/network_service.dart'; @@ -77,16 +79,15 @@ class RoomController extends GetxController { /// 调用接口创建 RTC 频道 Future createRtcChannel() async { - if (isLoading.value) return; final granted = await _ensureRtcPermissions(); if (!granted) return; try { - isLoading.value = true; final response = await _networkService.rtcApi.createRtcChannel(); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; + await _joinRtcChannel(base.data!.token, base.data!.channelId, base.data!.uid, ClientRoleType.clientRoleBroadcaster); await _joinRtcChannel(base.data!.token, base.data!.channelId, base.data!.uid); } else { final message = base.message.isNotEmpty ? base.message : '创建频道失败'; @@ -94,20 +95,34 @@ class RoomController extends GetxController { } } catch (e) { SmartDialog.showToast('创建频道异常:$e'); - } finally { - isLoading.value = false; } } + + Future joinChannel(String channelName) async { + try { + final response = await _networkService.rtcApi.getSwRtcToken(channelName); + final base = response.data; + if (base.isSuccess && base.data != null) { + rtcChannel.value = base.data; + await _joinRtcChannel(base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleAudience); + } + } catch (e) { + SmartDialog.showToast('加入频道异常:$e'); + } + } + Future _joinRtcChannel( String token, String channelName, int uid, + ClientRoleType roleType ) async { try { await RTCManager.instance.joinChannel( token: token, channelId: channelName, uid: uid, + role: roleType, ); } catch (e) { SmartDialog.showToast('加入频道失败:$e'); @@ -117,7 +132,7 @@ class RoomController extends GetxController { /// 发送公屏消息 Future sendChatMessage(String content) async { final channelName = rtcChannel.value?.channelId ?? RTCManager.instance.currentChannelId; - + final result = await _messageService.sendMessage( content: content, channelName: channelName, @@ -152,8 +167,8 @@ class RoomController extends GetxController { return false; } - Future disposeRtcResources() async { - await RTCManager.instance.dispose(); + Future leaveChannel() async { + await RTCManager.instance.leaveChannel(); } } diff --git a/lib/controller/mine/login_controller.dart b/lib/controller/mine/login_controller.dart index e502737..a945bcf 100644 --- a/lib/controller/mine/login_controller.dart +++ b/lib/controller/mine/login_controller.dart @@ -115,7 +115,7 @@ class LoginController extends GetxController { GlobalData().userId = result.userId; GlobalData().qnToken = result.token; await storage.write('token', result.token); - // await storage.write('userId', result.userId); + await storage.write('userId', result.userId); // 登录成功后获取用户信息 await _handleUserInfoRetrieval(result.userId); diff --git a/lib/main.dart b/lib/main.dart index a89fe82..0576363 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -84,10 +84,10 @@ class MyApp extends StatelessWidget { // 判断token是否为空 final storage = GetStorage(); - final userId = storage.read('userId'); + final token = storage.read('token'); // 如果token不为空,显示主页;如果token为空,显示登录页面 - if (userId != null && userId.isNotEmpty) { + if (token != null && token.isNotEmpty) { return MainPage(); } else { return LoginPage(); diff --git a/lib/network/rtc_api.dart b/lib/network/rtc_api.dart index 7e4c4e8..860c19c 100644 --- a/lib/network/rtc_api.dart +++ b/lib/network/rtc_api.dart @@ -13,7 +13,9 @@ abstract class RtcApi { /// 创建实时音视频频道 @GET(ApiUrls.getSwRtcToken) - Future>> getSwRtcToken(); + Future>> getSwRtcToken( + @Query('channelId') String channelId, + ); /// 获取声网 RTM Token @GET(ApiUrls.getSwRtmToken) diff --git a/lib/network/rtc_api.g.dart b/lib/network/rtc_api.g.dart index 2cbdf56..8166434 100644 --- a/lib/network/rtc_api.g.dart +++ b/lib/network/rtc_api.g.dart @@ -20,9 +20,11 @@ class _RtcApi implements RtcApi { final ParseErrorLogger? errorLogger; @override - Future>> getSwRtcToken() async { + Future>> getSwRtcToken( + String channelId, + ) async { final _extra = {}; - final queryParameters = {}; + final queryParameters = {r'channelId': channelId}; final _headers = {}; const Map? _data = null; final _options = _setStreamType>>( diff --git a/lib/pages/discover/discover_page.dart b/lib/pages/discover/discover_page.dart index 0d121dd..e1ae252 100644 --- a/lib/pages/discover/discover_page.dart +++ b/lib/pages/discover/discover_page.dart @@ -149,11 +149,25 @@ class LiveItem extends StatefulWidget { } class _LiveItemState extends State { + + late final RoomController roomController; + + @override + void initState() { + super.initState(); + if (Get.isRegistered()) { + roomController = Get.find(); + } else { + roomController = Get.put(RoomController()); + } + } + @override Widget build(BuildContext context) { return InkWell( - onTap: () { - Get.to(() => LiveRoomPage(id: 0)); + onTap: () async{ + // Get.to(() => LiveRoomPage(id: 0)); + await roomController.joinChannel('1189028638616588288'); }, child: ClipRRect( borderRadius: BorderRadius.all(Radius.circular(10.w)), diff --git a/lib/pages/discover/live_room_page.dart b/lib/pages/discover/live_room_page.dart index 631c59e..3b9295b 100644 --- a/lib/pages/discover/live_room_page.dart +++ b/lib/pages/discover/live_room_page.dart @@ -75,7 +75,7 @@ class _LiveRoomPageState extends State { @override void dispose() { - _roomController.disposeRtcResources(); + _roomController.leaveChannel(); _messageController.dispose(); super.dispose(); } diff --git a/lib/pages/mine/login_page.dart b/lib/pages/mine/login_page.dart index 1fa50a4..281c697 100644 --- a/lib/pages/mine/login_page.dart +++ b/lib/pages/mine/login_page.dart @@ -7,7 +7,7 @@ import 'package:dating_touchme_app/controller/mine/login_controller.dart'; class LoginPage extends StatelessWidget { LoginPage({super.key}); - + // 是否同意协议 final agreeTerms = Rx(false); @@ -20,13 +20,16 @@ class LoginPage extends StatelessWidget { resizeToAvoidBottomInset: false, body: Stack( children: [ - Image.asset( - Assets.imagesLoginBg, - width: 1.sw, - height: 1.sh, - ), Container( + width: double.infinity, height: 1.sh, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.imagesLoginBg), + fit: BoxFit.cover, + alignment: Alignment.topCenter, + ), + ), padding: EdgeInsets.symmetric(horizontal: 20), child: Column( children: [ @@ -35,10 +38,7 @@ class LoginPage extends StatelessWidget { Center( child: Column( children: [ - Image.asset( - Assets.imagesLoginLogo, - height: 60, - ), + Image.asset(Assets.imagesLoginLogo, height: 60), const SizedBox(height: 10), const Text( '心动就动我 幸福马上行动', @@ -70,7 +70,10 @@ class LoginPage extends StatelessWidget { decoration: const InputDecoration( hintText: '请输入你的手机号', border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 14), + contentPadding: EdgeInsets.symmetric( + horizontal: 15, + vertical: 14, + ), counterText: '', ), keyboardType: TextInputType.phone, @@ -101,7 +104,10 @@ class LoginPage extends StatelessWidget { decoration: const InputDecoration( hintText: '请输入验证码', border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 14), + contentPadding: EdgeInsets.symmetric( + horizontal: 15, + vertical: 14, + ), counterText: '', ), keyboardType: TextInputType.number, @@ -114,18 +120,25 @@ class LoginPage extends StatelessWidget { ), // 获取验证码按钮 GestureDetector( - onTap: controller.isSendingCode.value || controller.countdownSeconds.value > 0 + onTap: + controller.isSendingCode.value || + controller.countdownSeconds.value > 0 ? null : controller.getVerificationCode, child: Container( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14), + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 14, + ), child: Text( controller.countdownSeconds.value > 0 ? '${controller.countdownSeconds.value}秒后重试' : '获取验证码', style: TextStyle( fontSize: 14, - color: (controller.isSendingCode.value || controller.countdownSeconds.value > 0) + color: + (controller.isSendingCode.value || + controller.countdownSeconds.value > 0) ? Colors.grey.shade400 : const Color.fromRGBO(74, 99, 235, 1), ), @@ -141,16 +154,19 @@ class LoginPage extends StatelessWidget { // 协议同意复选框 Row( children: [ - Obx(() => Checkbox( - value: agreeTerms.value, - onChanged: (value) { - agreeTerms.value = value ?? false; - }, - activeColor: Colors.grey, - side: const BorderSide(color: Colors.grey), - shape: const CircleBorder(), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - )), + Obx( + () => Checkbox( + value: agreeTerms.value, + onChanged: (value) { + agreeTerms.value = value ?? false; + }, + activeColor: Colors.grey, + side: const BorderSide(color: Colors.grey), + shape: const CircleBorder(), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + ), + ), const Text( '我已阅读并同意', style: TextStyle( @@ -199,18 +215,20 @@ class LoginPage extends StatelessWidget { onPressed: controller.isLoggingIn.value ? null : () { - // 登录逻辑 - if (!agreeTerms.value) { - SmartDialog.showToast('请阅读并同意用户协议和隐私政策'); - return; - } + // 登录逻辑 + if (!agreeTerms.value) { + SmartDialog.showToast('请阅读并同意用户协议和隐私政策'); + return; + } - // 调用控制器的登录方法 - controller.login(); - }, + // 调用控制器的登录方法 + controller.login(); + }, style: ElevatedButton.styleFrom( minimumSize: const Size(double.infinity, 50), - backgroundColor: agreeTerms.value ? const Color.fromRGBO(74, 99, 235, 1) : Colors.grey.shade300, + backgroundColor: agreeTerms.value + ? const Color.fromRGBO(74, 99, 235, 1) + : Colors.grey.shade300, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(25), ), @@ -218,26 +236,26 @@ class LoginPage extends StatelessWidget { ), child: controller.isLoggingIn.value ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 2, - ), - ) + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) : const Text( - '注册并登录', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), + '注册并登录', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), ), ), ], ), - ) + ), ], ), ); diff --git a/lib/rtc/rtc_manager.dart b/lib/rtc/rtc_manager.dart index f49fcd1..a88da39 100644 --- a/lib/rtc/rtc_manager.dart +++ b/lib/rtc/rtc_manager.dart @@ -11,6 +11,9 @@ import '../pages/discover/live_room_page.dart'; class RTCManager { /// 频道加入状态通知,用于UI监听 final ValueNotifier channelJoinedNotifier = ValueNotifier(false); + final ValueNotifier> remoteUsersNotifier = ValueNotifier>( + [], + ); RtcEngine? get engine => _engine; bool get isInChannel => _isInChannel; int? get currentUid => _currentUid; @@ -26,8 +29,8 @@ class RTCManager { bool _isInChannel = false; String? _currentChannelId; int? _currentUid; - int? _streamId; - + ClientRoleType _clientRole = ClientRoleType.clientRoleBroadcaster; + final List _remoteUserIds = []; // 事件回调 Function(RtcConnection connection, int elapsed)? onJoinChannelSuccess; Function(RtcConnection connection, int remoteUid, int elapsed)? onUserJoined; @@ -86,9 +89,9 @@ class RTCManager { await _engine!.initialize( RtcEngineContext(appId: appId, channelProfile: channelProfile), ); - await _engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); + await _engine?.setClientRole(role: _clientRole); await _engine?.enableVideo(); - await _engine?.startPreview(); + // await _engine?.startPreview(); // 注册事件处理器 _registerEventHandlers(); @@ -107,16 +110,19 @@ class RTCManager { _engine!.registerEventHandler( RtcEngineEventHandler( - onJoinChannelSuccess: (RtcConnection connection, int elapsed) async{ + onJoinChannelSuccess: (RtcConnection connection, int elapsed) async { _isInChannel = true; + _remoteUserIds.clear(); + remoteUsersNotifier.value = const []; channelJoinedNotifier.value = true; _currentChannelId = connection.channelId; print('加入频道成功,频道名:${connection.channelId},耗时:${elapsed}ms'); - if(connection.localUid == _currentUid){ + if (connection.localUid == _currentUid) { + await RTMManager.instance.subscribe(_currentChannelId ?? ''); await RTMManager.instance.publishChannelMessage( - channelName: _currentChannelId ?? '', - message: json.encode({'type': 'join_room', 'uid': _currentUid}) + channelName: _currentChannelId ?? '', + message: json.encode({'type': 'join_room', 'uid': _currentUid}), ); Get.to(() => const LiveRoomPage(id: 0)); } @@ -126,13 +132,11 @@ class RTCManager { }, onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) { print('用户加入,UID:$remoteUid'); + _handleRemoteUserJoined(remoteUid); if (onUserJoined != null) { onUserJoined!(connection, remoteUid, elapsed); } }, - onStreamMessage: (RtcConnection connection, int remoteUid, int streamId, Uint8List data, int length, int sentTs){ - print('收到消息,UID:$remoteUid,流ID:$streamId,数据:${utf8.decode(data)}'); - }, onUserOffline: ( RtcConnection connection, @@ -140,12 +144,15 @@ class RTCManager { UserOfflineReasonType reason, ) { print('用户离开,UID:$remoteUid,原因:$reason'); + _handleRemoteUserOffline(remoteUid); 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}秒'); @@ -343,7 +350,7 @@ class RTCManager { String? token, required String channelId, int uid = 0, - ChannelMediaOptions? options, + ClientRoleType role = ClientRoleType.clientRoleBroadcaster, }) async { if (_engine == null) { throw Exception('RTC Engine not initialized'); @@ -352,15 +359,24 @@ class RTCManager { print('已经在频道中,先离开当前频道'); await leaveChannel(); } - + await setClientRole(role: role); _currentUid = uid; + if (role == ClientRoleType.clientRoleBroadcaster) { + await _engine?.startPreview(); + } await _engine!.joinChannel( token: token ?? '', channelId: channelId, uid: uid, - options: options ?? const ChannelMediaOptions(), + options: ChannelMediaOptions( + channelProfile: ChannelProfileType.channelProfileLiveBroadcasting, + clientRoleType: role, + autoSubscribeAudio: true, + autoSubscribeVideo: true, + publishCameraTrack: true, + publishMicrophoneTrack: true, + ), ); - _streamId = await _engine?.createDataStream(DataStreamConfig(syncWithAudio: false, ordered: false)); print('正在加入频道:$channelId,UID:$uid'); } @@ -373,7 +389,7 @@ class RTCManager { print('当前不在频道中'); return; } - + await RTMManager.instance.unsubscribe(_currentChannelId ?? ''); await _engine!.leaveChannel(); _currentUid = null; print('已离开频道'); @@ -417,24 +433,18 @@ class RTCManager { if (_engine == null) { throw Exception('RTC Engine not initialized'); } + _clientRole = role; await _engine!.setClientRole(role: role, options: options); print('客户端角色已设置为:$role'); } - /// 发送消息 - Future sendMessage(String message) async { - Uint8List data = utf8.encode(message); - await _engine!.sendStreamMessage( - streamId: _streamId ?? 0, - data: data, - length: data.length, - ); - print('已发送消息:$message'); - } - /// 获取当前频道ID String? get currentChannelId => _currentChannelId; + ClientRoleType get clientRole => _clientRole; + + List get remoteUserIds => List.unmodifiable(_remoteUserIds); + /// 释放资源 Future dispose() async { try { @@ -445,6 +455,8 @@ class RTCManager { await _engine!.release(); _engine = null; } + _remoteUserIds.clear(); + remoteUsersNotifier.value = const []; _isInitialized = false; _isInChannel = false; _currentChannelId = null; @@ -455,4 +467,17 @@ class RTCManager { 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); + } + + void _handleRemoteUserOffline(int remoteUid) { + final removed = _remoteUserIds.remove(remoteUid); + if (!removed) return; + remoteUsersNotifier.value = List.unmodifiable(_remoteUserIds); + } } diff --git a/lib/rtc/rtm_manager.dart b/lib/rtc/rtm_manager.dart index a8097d3..2758b5c 100644 --- a/lib/rtc/rtm_manager.dart +++ b/lib/rtc/rtm_manager.dart @@ -74,13 +74,13 @@ class RTMManager { _currentUserId = userId; _isInitialized = true; _registerClientListeners(); - final response = await _networkService.rtcApi.getSwRtmToken(); - // 处理响应 - if (response.data.isSuccess) { - await login(response.data.data?.token ?? ''); - } else { - SmartDialog.showToast(response.data.message); - } + // final response = await _networkService.rtcApi.getSwRtmToken(); + // // 处理响应 + // if (response.data.isSuccess) { + await login('007eJxTYIhoZ/m/gMf0gdWv1PV2R6I/Lpry06W77sOR2BDuFv89JeEKDCbJRqmJlinJSSbJpiYmBqaWxokmhhYpaQZpKSmmhkYpE9/IZgrwMTDYOB4rZ2RgYmAEQhCfkcEcAPTmHg4='); + // } else { + // SmartDialog.showToast(response.data.message); + // } return true; } diff --git a/lib/widget/live/live_room_anchor_showcase.dart b/lib/widget/live/live_room_anchor_showcase.dart index 953182a..90bc023 100644 --- a/lib/widget/live/live_room_anchor_showcase.dart +++ b/lib/widget/live/live_room_anchor_showcase.dart @@ -19,114 +19,132 @@ class _LiveRoomAnchorShowcaseState extends State { return ValueListenableBuilder( valueListenable: _rtcManager.channelJoinedNotifier, builder: (context, joined, _) { - return Column( - children: [ - Stack( + return ValueListenableBuilder>( + valueListenable: _rtcManager.remoteUsersNotifier, + builder: (context, remoteUids, __) { + final int? remoteUid = remoteUids.isNotEmpty + ? remoteUids.first + : null; + return Column( children: [ - _buildAnchorVideo(joined), - Positioned( - top: 5.w, - left: 5.w, - child: Container( - width: 42.w, - height: 13.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(13.w)), - color: const Color.fromRGBO(142, 20, 186, 1), - ), - child: Center( - child: Text( - "主持人", - style: TextStyle(fontSize: 9.w, color: Colors.white), + Stack( + children: [ + _buildAnchorVideo(joined, remoteUid), + Positioned( + top: 5.w, + left: 5.w, + child: Container( + width: 42.w, + height: 13.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(13.w)), + color: const Color.fromRGBO(142, 20, 186, 1), + ), + child: Center( + child: Text( + "主持人", + style: TextStyle( + fontSize: 9.w, + color: Colors.white, + ), + ), + ), ), ), - ), - ), - Positioned( - top: 5.w, - right: 5.w, - child: Container( - width: 20.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(20.w)), - color: const Color.fromRGBO(0, 0, 0, .3), - ), - child: Center( - child: Image.asset( - Assets.imagesGiftIcon, - width: 19.w, - height: 19.w, + Positioned( + top: 5.w, + right: 5.w, + child: Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20.w)), + color: const Color.fromRGBO(0, 0, 0, .3), + ), + child: Center( + child: Image.asset( + Assets.imagesGiftIcon, + width: 19.w, + height: 19.w, + ), + ), ), ), - ), - ), - Positioned( - bottom: 5.w, - right: 5.w, - child: Container( - width: 47.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(20.w)), - color: Colors.white, - ), - child: Center( - child: Text( - "加好友", - style: TextStyle( - fontSize: 11.w, - color: const Color.fromRGBO(117, 98, 249, 1), + Positioned( + bottom: 5.w, + right: 5.w, + child: Container( + width: 47.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(20.w)), + color: Colors.white, + ), + child: Center( + child: Text( + "加好友", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(117, 98, 249, 1), + ), + ), ), ), ), - ), - ), - ], - ), - SizedBox(height: 5.w), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildSideAnchorCard( - isLeft: true, - micIcon: Assets.imagesMicClose, + ], ), - _buildSideAnchorCard( - isLeft: false, - micIcon: Assets.imagesMicOpen, + SizedBox(height: 5.w), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildSideAnchorCard( + isLeft: true, + micIcon: Assets.imagesMicClose, + ), + _buildSideAnchorCard( + isLeft: false, + micIcon: Assets.imagesMicOpen, + ), + ], ), ], - ), - ], + ); + }, ); }, ); } - Widget _buildAnchorVideo(bool joined) { + Widget _buildAnchorVideo(bool joined, int? remoteUid) { final engine = _rtcManager.engine; if (!joined || engine == null) { return _buildWaitingPlaceholder(); } - - final localUid = _rtcManager.currentUid ?? 0; + print('joined: $joined'); + ClientRoleType role = _rtcManager.clientRole; return ClipRRect( borderRadius: BorderRadius.all(Radius.circular(9.w)), child: SizedBox( width: 177.w, height: 175.w, - child: AgoraVideoView( - controller: VideoViewController( - rtcEngine: engine, - canvas: VideoCanvas( - uid: 0, - ), - ), - onAgoraVideoViewCreated: (viewId){ - engine.startPreview(); - }, - ), + child: role == ClientRoleType.clientRoleBroadcaster + ? AgoraVideoView( + controller: VideoViewController( + rtcEngine: engine, + canvas: const VideoCanvas(uid: 0), + ), + ) + : (_rtcManager.currentChannelId == null + ? _buildWaitingPlaceholder() + : AgoraVideoView( + controller: VideoViewController.remote( + rtcEngine: engine, + canvas: VideoCanvas(uid: _rtcManager.remoteUserIds.first), + connection: RtcConnection( + channelId: _rtcManager.currentChannelId!, + ), + ), + )), ), ); }