Browse Source

feat(live): 实现直播间主播展示与互动功能

- 引入 RoomController 管理房间状态
- 使用 Obx 监听 RTC 频道详情变化并更新 UI
- 重构 _buildSideAnchorCard 方法以支持用户信息动态显示
- 根据用户角色判断视频流类型(本地或远程)
- 添加麦克风图标及昵称显示逻辑
- 优化占位提示文本与样式
- 在点击事件中增加对直播状态的判断,实现加入/离开聊天的功能
- 新增 leaveChat 方法用于断开 RTC 连接并取消音视频发布
- 完善 RTC 管理器中的 publishAudio 和 unpublish 方法注释及逻辑
ios
Jolie 4 months ago
parent
commit
2d28e8f75a
4 changed files with 145 additions and 49 deletions
  1. 13
      lib/controller/discover/room_controller.dart
  2. 15
      lib/rtc/rtc_manager.dart
  3. 160
      lib/widget/live/live_room_anchor_showcase.dart
  4. 6
      lib/widget/live/live_room_notice_chat_panel.dart

13
lib/controller/discover/room_controller.dart

@ -25,6 +25,7 @@ class RoomController extends GetxController {
final NetworkService _networkService;
CurrentRole currentRole = CurrentRole.normalUser;
bool isLive = false;
///
final Rxn<RtcChannelData> rtcChannel = Rxn<RtcChannelData>();
final Rxn<RtcChannelDetail> rtcChannelDetail = Rxn<RtcChannelDetail>();
@ -98,6 +99,7 @@ class RoomController extends GetxController {
if (base.isSuccess && base.data != null) {
rtcChannel.value = base.data;
currentRole = CurrentRole.broadcaster;
isLive = true;
await _joinRtcChannel(
base.data!.token,
base.data!.channelId,
@ -169,6 +171,17 @@ class RoomController extends GetxController {
}else{
await RTCManager.instance.publishAudio();
}
isLive = true;
}
Future<void> leaveChat() async {
final data = {
'channelId': rtcChannel.value?.channelId
};
final response = await _networkService.rtcApi.disconnectRtcChannel(data);
if(response.data.isSuccess){
await RTCManager.instance.unpublish();
}
}
Future<void> _fetchRtcChannelDetail(String channelName) async {

15
lib/rtc/rtc_manager.dart

@ -497,6 +497,7 @@ class RTCManager {
);
}
///
Future<void> publishAudio() async {
await _engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
await _engine?.muteLocalAudioStream(false);
@ -510,4 +511,18 @@ class RTCManager {
}),
);
}
///
Future<void> unpublish() async {
await _engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
await _engine?.muteLocalAudioStream(true);
await _engine?.muteLocalVideoStream(true);
await RTMManager.instance.publishChannelMessage(
channelName: _currentChannelId ?? '',
message: json.encode({
'type': 'leave_chat',
'uid': _currentUid,
}),
);
}
}

160
lib/widget/live/live_room_anchor_showcase.dart

@ -1,8 +1,12 @@
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:dating_touchme_app/controller/discover/room_controller.dart';
import 'package:dating_touchme_app/controller/global.dart';
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart';
import 'package:dating_touchme_app/rtc/rtc_manager.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
class LiveRoomAnchorShowcase extends StatefulWidget {
const LiveRoomAnchorShowcase({super.key});
@ -13,6 +17,7 @@ class LiveRoomAnchorShowcase extends StatefulWidget {
class _LiveRoomAnchorShowcaseState extends State<LiveRoomAnchorShowcase> {
final RTCManager _rtcManager = RTCManager.instance;
final RoomController _roomController = Get.find<RoomController>();
@override
Widget build(BuildContext context) {
@ -94,19 +99,23 @@ class _LiveRoomAnchorShowcaseState extends State<LiveRoomAnchorShowcase> {
],
),
SizedBox(height: 5.w),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildSideAnchorCard(
isLeft: true,
micIcon: Assets.imagesMicClose,
),
_buildSideAnchorCard(
isLeft: false,
micIcon: Assets.imagesMicOpen,
),
],
),
Obx(() {
final rtcChannelDetail =
_roomController.rtcChannelDetail.value;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildSideAnchorCard(
isLeft: true,
userInfo: rtcChannelDetail?.maleInfo,
),
_buildSideAnchorCard(
isLeft: false,
userInfo: rtcChannelDetail?.femaleInfo,
),
],
);
}),
],
);
},
@ -139,7 +148,9 @@ class _LiveRoomAnchorShowcaseState extends State<LiveRoomAnchorShowcase> {
: AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: engine,
canvas: VideoCanvas(uid: _rtcManager.remoteUserIds.first),
canvas: VideoCanvas(
uid: _rtcManager.remoteUserIds.first,
),
connection: RtcConnection(
channelId: _rtcManager.currentChannelId!,
),
@ -169,17 +180,63 @@ class _LiveRoomAnchorShowcaseState extends State<LiveRoomAnchorShowcase> {
);
}
Widget _buildSideAnchorCard({required bool isLeft, required String micIcon}) {
Widget _buildSideAnchorCard({
required bool isLeft,
RtcSeatUserInfo? userInfo,
}) {
final engine = _rtcManager.engine;
final joined = _rtcManager.channelJoinedNotifier.value;
//
final bool isCurrentUser = _roomController.currentRole == CurrentRole.maleAudience || _roomController.currentRole == CurrentRole.femaleAudience;
return Stack(
children: [
Container(
width: 177.w,
height: 175.w,
decoration: BoxDecoration(
borderRadius: isLeft
? BorderRadius.horizontal(left: Radius.circular(18.w))
: BorderRadius.horizontal(right: Radius.circular(18.w)),
color: const Color.fromRGBO(47, 10, 94, 1),
ClipRRect(
borderRadius: isLeft
? BorderRadius.horizontal(left: Radius.circular(18.w))
: BorderRadius.horizontal(right: Radius.circular(18.w)),
child: SizedBox(
width: 177.w,
height: 175.w,
child:
userInfo != null &&
userInfo.uid != null &&
joined &&
engine != null
? AgoraVideoView(
controller: isCurrentUser
? VideoViewController(
rtcEngine: engine,
canvas: const VideoCanvas(uid: 0),
)
: VideoViewController.remote(
rtcEngine: engine,
canvas: VideoCanvas(uid: userInfo.uid!),
connection: RtcConnection(
channelId: _rtcManager.currentChannelId ?? '',
),
),
)
: Container(
decoration: BoxDecoration(
borderRadius: isLeft
? BorderRadius.horizontal(left: Radius.circular(18.w))
: BorderRadius.horizontal(
right: Radius.circular(18.w),
),
color: const Color.fromRGBO(47, 10, 94, 1),
),
child: Center(
child: Text(
'等待${isLeft ? "" : ""}嘉宾',
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 12.w,
),
),
),
),
),
),
Positioned(
@ -222,34 +279,41 @@ class _LiveRoomAnchorShowcaseState extends State<LiveRoomAnchorShowcase> {
),
),
),
Positioned(
left: 5.w,
bottom: 5.w,
child: Row(
children: [
Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4.w)),
color: const Color.fromRGBO(0, 0, 0, .65),
if (userInfo != null)
Positioned(
left: 5.w,
bottom: 5.w,
child: Row(
children: [
Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4.w)),
color: const Color.fromRGBO(0, 0, 0, .65),
),
child: Center(
child: Image.asset(
userInfo.isMicrophoneOn
? Assets.imagesMicOpen
: Assets.imagesMicClose,
width: 10.w,
height: 11.w,
),
),
),
child: Center(
child: Image.asset(micIcon, width: 10.w, height: 11.w),
SizedBox(width: 5.w),
Text(
userInfo.nickName,
style: TextStyle(
fontSize: 11.w,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(width: 5.w),
Text(
"飞翔的企鹅",
style: TextStyle(
fontSize: 11.w,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
],
],
),
),
),
],
);
}

6
lib/widget/live/live_room_notice_chat_panel.dart

@ -122,7 +122,11 @@ class _LiveRoomNoticeChatPanelState extends State<LiveRoomNoticeChatPanel> {
],
),
).onTap(() async{
await controller.joinChat(GlobalData().userData?.genderCode == 1 ? CurrentRole.maleAudience : CurrentRole.femaleAudience);
if(controller.isLive){
await controller.leaveChat();
}else{
await controller.joinChat(GlobalData().userData?.genderCode == 1 ? CurrentRole.maleAudience : CurrentRole.femaleAudience);
}
});
}
return const SizedBox();

Loading…
Cancel
Save