tencent cloud

实时音视频

动态与公告
产品动态
产品近期公告
关于 TRTC Live 正式上线的公告
关于TRTC Conference 正式版上线的公告
Conference 商业化版本即将推出
关于多人音视频 Conference 开启内测公告
关于音视频通话 Call 正式版上线的公告
关于腾讯云音视频终端 SDK 播放升级及新增授权校验的公告
关于 TRTC 应用订阅套餐服务上线的相关说明
产品简介
产品概述
基本概念
产品功能
产品优势
应用场景
性能数据
购买指南
计费概述
免费时长说明
月订阅
现收现付
TRTC 逾期与暂停政策
常见问题解答
退款说明
新手指引
Demo 体验
视频通话 SDK
组件介绍
开通服务
跑通 Demo
快速接入
离线唤醒
会话聊天
云端录制
AI 降噪
界面定制
Chat 集成通话能力
更多特性
无 UI 集成
服务端 API
客户端 API
解决方案
错误码表
发布日志
常见问题
视频会议 SDK
组件介绍(TUIRoomKit)
开通服务(TUIRoomKit)
跑通 Demo(TUIRoomKit)
快速接入(TUIRoomKit)
屏幕共享(TUIRoomKit)
预定会议(TUIRoomKit)
会中呼叫(TUIRoomKit)
界面定制(TUIRoomKit)
虚拟背景(TUIRoomKit)
会议控制(TUIRoomKit)
云端录制(TUIRoomKit)
AI 降噪(TUIRoomKit)
会中聊天(TUIRoomKit)
机器人推流(TUIRoomKit)
更多特性(TUIRoomKit)
客户端 API(TUIRoomKit)
服务端 API(TUIRoomKit)
常见问题(TUIRoomKit)
错误码 (TUIRoomKit)
SDK更新日志(TUIRoomKit)
直播与语聊 SDK
Live 视频直播计费说明
组件介绍
开通服务(TUILiveKit)
跑通 Demo
无 UI 集成
UI 自定义
直播监播
视频直播
语聊房
高级功能
客户端 API
服务端 API
错误码
发布日志
常见问题
RTC Engine
开通服务
SDK 下载
API-Example
接入指引
API-参考手册
高级功能
AI 集成
概述
MCP 配置
Skills 配置
集成指南
常见问题
RTC RESTFUL API
History
Introduction
API Category
Room Management APIs
Stream mixing and relay APIs
On-cloud recording APIs
Data Monitoring APIs
Pull stream Relay Related interface
Web Record APIs
AI Service APIs
Cloud Slicing APIs
Cloud Moderation APIs
Making API Requests
Call Quality Monitoring APIs
Usage Statistics APIs
Data Types
Appendix
Error Codes
控制台指南
应用管理
套餐包管理
用量统计
监控仪表盘
开发辅助
解决方案
实时合唱
常见问题
迁移指南
计费相关
功能相关
UserSig 相关
应对防火墙限制相关
缩减安装包体积相关
Andriod 与 iOS 相关
Web 端相关
Flutter 相关
Electron 相关
TRTCCalling Web 相关
音视频质量相关
其他问题
旧版文档
RTC RoomEngine SDK(旧)
集成 TUIRoom (Web)
集成 TUIRoom (Android)
集成 TUIRoom (iOS)
集成 TUIRoom (Flutter)
集成 TUIRoom (Electron)
TUIRoom API 查询
实现云端录制与回放(旧)
监控仪表盘计费(旧)
协议与策略
安全合规认证
安全白皮书
信息安全说明
服务等级协议
苹果隐私策略:PrivacyInfo.xcprivacy
TRTC 政策
隐私协议
数据处理和安全协议
词汇表

观众上麦(Flutter)

PDF
聚焦模式
字号
最后更新时间: 2026-02-27 20:58:06
AtomicXCore 提供了 CoGuestStoreLiveSeatStore 模块,用于管理观众连麦的完整业务流程。用户无需关心复杂的状态同步和信令交互,只需调用几个简单的方法,即可为您的直播添加强大的观众与主播音视频互动功能。本文档将指导您如何使用 CoGuestStoreLiveSeatStore 在 Flutter 应用中快速实现语音连麦功能。


核心场景

CoGuestStoreLiveSeatStore 支持以下几个主流的场景:
观众申请上麦:观众主动发起连麦请求,主播在收到请求后进行同意或拒绝。
主播邀请上麦:主播可以主动向直播间内的任意一位观众发起连麦邀请。
主播管理麦位:主播可以对麦位上的用户进行强制下麦、闭麦、锁麦等操作。

实现步骤

步骤1:组件集成

请参考 快速接入 集成 AtomicXCore
说明:
在开始实现连麦功能之前,您需要获取 liveId(直播间唯一标识),用于区分不同的直播房间。获取方式如下
主播端:通过 LiveListStore.shared.createLive(liveInfo) 创建直播时指定。
观众端:通过LiveListState.liveList.value中的 liveInfo 获取,或由主播分享直播间 ID 获取。

步骤2:实现观众申请上麦

观众端实现

观众端的核心任务是发起申请、接收结果主动下麦
1. 发起连麦申请
当用户点击界面上的"申请连麦"按钮时,调用 applyForSeat 方法。
import 'package:atomic_x_core/atomicxcore.dart';

final String liveId = "房间ID";
late CoGuestStore guestStore;

@override
void initState() {
super.initState();
guestStore = CoGuestStore.create(liveId);
}

// 用户点击"申请连麦"
Future<void> requestToConnect() async {
// seatIndex: 麦位索引,从 0 开始,表示要申请的麦位位置。0 表示第一个麦位。可通过 LiveSeatState.seatList.value 查看麦位状态,选择空闲的麦位
// timeout: 请求超时时间(秒),在超时时间内观众可调用 cancelApplication 取消上麦请求,
// 到达超时时间后如该请求未被处理则会失效
final result = await guestStore.applyForSeat(
seatIndex: 0,
timeout: 30,
extraInfo: null,
);
if (result.isSuccess) {
debugPrint('连麦申请已发送,等待主播处理...');
} else {
debugPrint('申请发送失败: ${result.message}');
}
}
2. 监听主播的处理结果
通过添加 GuestListener,您可以接收到主播的处理结果。
late GuestListener _guestListener;

// 在您的 Widget 初始化时订阅事件
void subscribeGuestEvents() {
_guestListener = GuestListener(
onGuestApplicationResponded: (isAccept, hostUser) {
if (isAccept) {
debugPrint('主播 ${hostUser.userName} 同意了您的申请,准备上麦');
// 1. 打开麦克风
// DeviceStore 是 AtomicXCore 提供的设备管理模块,用于管理麦克风、摄像头等物理设备。
// 当主播同意连麦后,需要打开麦克风开始采集音频。
DeviceStore.shared.openLocalMicrophone();
// 2. 在此更新 UI,例如关闭申请按钮,显示连麦中的状态
} else {
debugPrint('主播 ${hostUser.userName} 拒绝了您的申请');
// 弹窗提示用户申请被拒绝
}
},
);
guestStore.addGuestListener(_guestListener);
}

@override
void dispose() {
guestStore.removeGuestListener(_guestListener);
super.dispose();
}
3. 主动下麦
当连麦观众想结束互动时,调用 disconnect 方法即可返回普通观众状态。
// 用户点击"下麦"按钮
Future<void> leaveSeat() async {
final result = await guestStore.disconnect();
if (result.isSuccess) {
debugPrint('已成功下麦');
} else {
debugPrint('下麦失败: ${result.message}');
}
}
4. (可选) 取消申请
如果观众在主播处理前想撤回申请,可以调用 cancelApplication
// 用户在等待时,点击"取消申请"
Future<void> cancelApplication() async {
final result = await guestStore.cancelApplication();
if (result.isSuccess) {
debugPrint('申请已取消');
} else {
debugPrint('申请取消失败: ${result.message}');
}
}

主播端实现

作为主播,您的核心任务是接收申请、展示申请列表处理申请
1. 监听新的连麦申请
通过添加 HostListener,您可以在有新观众申请时立即收到通知,并给出提示。
import 'package:atomic_x_core/atomicxcore.dart';

final String liveId = "房间ID";
late CoGuestStore guestStore;
late HostListener _hostListener;

@override
void initState() {
super.initState();
guestStore = CoGuestStore.create(liveId);

// 订阅主播事件
_hostListener = HostListener(
onGuestApplicationReceived: (guestUser) {
debugPrint('收到观众 ${guestUser.userName} 的连麦申请');
// 在此更新 UI,例如在"申请列表"按钮上显示红点
},
);
guestStore.addHostListener(_hostListener);
}

@override
void dispose() {
guestStore.removeHostListener(_hostListener);
super.dispose();
}
2. 展示申请列表
CoGuestStorecoGuestState 会实时维护当前的申请者列表,您可以订阅它来刷新您的 UI。
late final VoidCallback _applicantsListener = _onApplicantsChanged;

// 订阅状态变更
void subscribeApplicants() {
guestStore.coGuestState.applicants.addListener(_applicantsListener);
}

void _onApplicantsChanged() {
final applicants = guestStore.coGuestState.applicants.value;
debugPrint('当前申请人数: ${applicants.length}');
// 在此刷新您的"申请者列表" UI
}

@override
void dispose() {
guestStore.coGuestState.applicants.removeListener(_applicantsListener);
super.dispose();
}
3. 处理连麦申请
当您在列表中选择一位观众并点击"同意"或"拒绝"时,调用相应的方法。
// 主播点击"同意"按钮,传入申请者的 userID
Future<void> accept(String userId) async {
final result = await guestStore.acceptApplication(userId);
if (result.isSuccess) {
debugPrint('已同意 $userId 的申请,对方正在上麦');
}
}

// 主播点击"拒绝"按钮
Future<void> reject(String userId) async {
final result = await guestStore.rejectApplication(userId);
if (result.isSuccess) {
debugPrint('已拒绝 $userId 的申请');
}
}

步骤3:实现主播邀请上麦

主播端实现

1. 向观众发起邀请
当主播在观众列表中选择某人并点击"邀请连麦"时,调用 inviteToSeat 方法。
// 主播选择观众并发起邀请
Future<void> invite(String userId) async {
// inviteeID: 被邀请者 ID,seatIndex: 麦位索引,timeout: 邀请超时时间
final result = await guestStore.inviteToSeat(
inviteeID: userId,
seatIndex: 0,
timeout: 30,
extraInfo: null,
);
if (result.isSuccess) {
debugPrint('已向 $userId 发出邀请,等待对方回应...');
}
}
2. 监听观众的回应
通过 HostListener 监听 onHostInvitationResponded 事件。
// 在 HostListener 的配置中添加
_hostListener = HostListener(
onHostInvitationResponded: (isAccept, guestUser) {
if (isAccept) {
debugPrint('观众 ${guestUser.userName} 接受了您的邀请');
} else {
debugPrint('观众 ${guestUser.userName} 拒绝了您的邀请');
}
},
);

观众端实现

1. 接收主播的邀请
通过 GuestListener 监听 onHostInvitationReceived 事件。
// 在 GuestListener 的配置中添加
_guestListener = GuestListener(
onHostInvitationReceived: (hostUser) {
debugPrint('收到主播 ${hostUser.userName} 的连麦邀请');
// 在此弹出一个对话框,让用户选择"接受"或"拒绝"
// showInvitationDialog(hostUser);
},
);
2. 响应邀请
当用户在弹出的对话框中做出选择后,调用相应的方法。
final String inviterId = "发起邀请的主播 ID"; // 从 onHostInvitationReceived 事件中获取

// 用户点击"接受"
Future<void> accept() async {
final result = await guestStore.acceptInvitation(inviterId);
if (result.isSuccess) {
// 打开麦克风
// DeviceStore 是 AtomicXCore 提供的设备管理模块,用于管理麦克风、摄像头等物理设备。
// 接受邀请后,需要打开麦克风开始采集音频
DeviceStore.shared.openLocalMicrophone();
}
}

// 用户点击"拒绝"
Future<void> reject() async {
await guestStore.rejectInvitation(inviterId);
}

功能进阶

当用户上麦后,主播可能需要对麦位进行管理。以下功能主要由 LiveSeatStore 提供,它可以与 CoGuestStore 协同工作。

DeviceStore 与 LiveSeatStore 的麦克风控制流程

在使用麦位功能时,您需要了解 DeviceStoreLiveSeatStore 在麦克风控制上的区别:
DeviceStore:管理物理设备。openLocalMicrophone 启动麦克风设备进行音频采集,closeLocalMicrophone 停止音频采集并释放麦克风设备。
LiveSeatStore:管理麦位业务(即音频流)。muteMicrophone 静音,停止向远端发送本地的音频流,但麦克风设备本身仍在运行;unmuteMicrophone 解除静音,恢复向远端发送音频流。
推荐的工作流程:您应该遵循"设备只开一次,麦上只用静音切换"的原则
1. 上麦时:当观众成功上麦,调用一次 openLocalMicrophone 来启动设备。
2. 在麦上时:用户在麦位上所有的"开麦"和"闭麦"操作,都应该调用 unmuteMicrophonemuteMicrophone 来控制上行音频流的通断。
3. 下麦时:当用户下麦(例如调用 disconnect),调用 closeLocalMicrophone 来释放设备。

麦上用户自行开/关麦

麦上用户(包括主播自己)可以通过 LiveSeatStore 提供的接口来控制自己的麦克风静音状态。

实现方式:

1. 静音:调用 muteMicrophone() 方法。这是一个无回调的单向请求。
2. 解除静音:调用 unmuteMicrophone() 方法。

示例代码:

final seatStore = LiveSeatStore.create(liveId);

seatStore.muteMicrophone(); // 静音

await seatStore.unmuteMicrophone(); // 解除静音

主播远程控制麦上用户开麦或关麦

主播可以对其他麦上用户进行"强制静音"或"邀请开麦"的操作。

实现方式:

1. 强制静音(锁定):主播调用 closeRemoteMicrophone 会强制关闭对方的麦克风,并锁定对方的麦克风权限,被静音的用户会收到 LiveSeatListener 中的 onLocalMicrophoneClosedByAdmin 事件,此时其本地的"打开麦克风"按钮应变为不可点击状态。
2. 邀请开麦(解锁):主播调用 openRemoteMicrophone 并非强制打开对方麦克风,而是解除对方的"锁定状态",允许对方自行开麦。此时,被操作的用户会收到 onLocalMicrophoneOpenedByAdmin 事件,其本地的"打开麦克风"按钮应恢复为可点击状态,但用户仍处于静音中。
3. 用户自行开麦:用户在收到"解除锁定"的通知后,必须主动调用 LiveSeatStoreunmuteMicrophone() 方法解除静音,才能让房间内其他人听到自己的声音。

示例代码:

主播端:
final seatStore = LiveSeatStore.create(liveId);
final String targetUserId = "userD";

// 1. 强制静音 userD 并锁定
final result1 = await seatStore.closeRemoteMicrophone(targetUserId);
if (result1.isSuccess) {
debugPrint('已将 $targetUserId 静音并锁定');
}

// 2. 解锁 userD 的麦克风权限(此时 userD 依然是静音状态)
final result2 = await seatStore.openRemoteMicrophone(
userID: targetUserId,
policy: DeviceControlPolicy.unlockOnly,
);
if (result2.isSuccess) {
debugPrint('已解除 $targetUserId 的麦克风锁定,用户可自行开麦');
}
观众端:
late LiveSeatListener _seatListener;

// userD 监听主播的操作
void subscribeSeatEvents() {
_seatListener = LiveSeatListener(
onLocalMicrophoneClosedByAdmin: () {
debugPrint('被主播静音了');
// muteButton.isEnabled = false;
},
onLocalMicrophoneOpenedByAdmin: (policy) {
debugPrint('主播已解除静音锁定');
// muteButton.isEnabled = true;
// muteButton.text = "打开麦克风";
},
);
seatStore.addLiveSeatEventListener(_seatListener);
}

@override
void dispose() {
seatStore.removeLiveSeatEventListener(_seatListener);
super.dispose();
}

closeRemoteMicrophone 接口参数:

参数
类型
描述
userID
String
被操作的用户 userID

openRemoteMicrophone 接口参数:

参数
类型
描述
userID
String
被操作的用户 userID
policy
DeviceControlPolicy
打开麦克风的策略。

主播将麦上用户踢下麦位

实现方式:

1. 踢人下麦:主播可以调用 kickUserOutOfSeat 方法,强制将指定用户踢下麦位。
2. 监听事件通知:被踢下麦的用户会收到 GuestListener 中的 onKickedOffSeat 事件通知。

示例代码:

// 假设要踢走 "userB"
final String targetUserId = "userB";
final result = await seatStore.kickUserOutOfSeat(targetUserId);
if (result.isSuccess) {
debugPrint('已将 $targetUserId 踢下麦位');
} else {
debugPrint('踢人失败: ${result.message}');
}

// "userB" 收到被踢下麦事件
_guestListener = GuestListener(
onKickedOffSeat: (seatIndex, hostUser) {
// 弹 toast 提示
debugPrint('您已被主播踢下麦位');
},
);

kickUserOutOfSeat 接口参数:

参数
类型
描述
userID
String
被踢下麦的用户 userID

主播锁定和解锁麦位

主播可以锁定或解锁某个麦位。

实现方式:

1. 锁定麦位:主播可调用 lockSeat 方法锁定指定索引的麦位,麦位被锁定后,观众无法通过 applyForSeattakeSeat 占用该麦位,但主播可通过 inviteToSeat 邀请观众上锁定麦位。
2. 解锁麦位:调用 unlockSeat 可以解锁麦位,解锁后该麦位可以再次被占用。

示例代码:

// 锁定 2 号麦位
final result1 = await seatStore.lockSeat(2);
if (result1.isSuccess) {
debugPrint('2 号麦位已锁定');
}

// 解锁 2 号麦位
final result2 = await seatStore.unlockSeat(2);
if (result2.isSuccess) {
debugPrint('2 号麦位已解锁');
}

lockSeat 接口参数:

参数
类型
描述
seatIndex
int
需要锁定的麦位索引。

unlockSeat 接口参数:

参数
类型
描述
seatIndex
int
需要解锁的麦位索引。

移动麦位

主播和普通上麦用户可以调用 moveUserToSeat 移动麦位。

实现方式:

1. 主播移动麦位:主播可以调用此接口将任意用户移动到指定麦位。此时 userID 传入目标用户的 ID,targetIndex 是目标麦位索引,policy 参数来指定如果目标麦位有人时的移动策略,详细参见 接口参数 说明。
2. 普通上麦用户移动自己麦位:普通上麦用户也可以调用此接口来移动自己。此时 userID 必须传入用户自己的 ID,targetIndex 传入想去的新麦位索引,此时 policy 参数无效,如果目标麦位有人时会报错并停止移动。

示例代码:

final result = await seatStore.moveUserToSeat(
userID: "userC",
targetIndex: newSeatIndex,
policy: MoveSeatPolicy.abortWhenOccupied,
);
if (result.isSuccess) {
debugPrint('已成功移动到 $newSeatIndex 号麦位');
} else {
debugPrint('换座失败,可能位置有人了');
}

moveUserToSeat 接口参数:

参数
类型
描述
userID
String
需要移麦的用户 userID
targetIndex
int
目标麦位索引。
policy
MoveSeatPolicy
目标麦位已被占用时的处理策略:
abortWhenOccupied:目标麦位有人时放弃移动(默认策略)
forceReplace:强制替换目标麦位上的用户,被替换的用户将会被踢下麦。
swapPosition:与目标麦位用户交换。

API 文档

关于 CoGuestStoreLiveSeatStore 及其相关类的所有公开接口、属性和方法的详细信息,请参阅 AtomicXCore 框架的官方 API 文档。本指南使用到的相关 Store 如下:
Store/Component
功能描述
API 文档
CoGuestStore
观众连麦管理:连麦申请 / 邀请 / 同意 / 拒绝,连麦成员权限控制(麦克风 / 摄像头),状态同步。
LiveSeatStore
麦位管理:静音/解除静音,锁定/解锁麦位,踢人下麦、远程控制麦上用户麦克风,麦列表状态监听。

常见问题

语音房连麦和视频直播连麦的实现有何不同?

两者的主要区别在于业务形态与 UI 展现:
视频直播:核心是视频画面。您会使用 LiveCoreWidget 作为核心组件来渲染主播和连麦观众的视频流。UI 的重点是处理视频画面的布局、大小,以及通过 VideoViewDelegate 在视频流上添加挂件(如昵称、占位图)。您可以同时打开摄像头、麦克风。
语音房(语聊房):核心是麦位网格。您不会使用 LiveCoreWidget,而是会基于 LiveSeatStoreliveSeatState(特别是 seatList)来构建一个网格 UI(例如 GridView)。UI 的重点是实时显示每个麦位 SeatInfo 的状态:是否有人、是否静音、是否被锁定,以及是否正在说话。您只需要打开麦克风。

如何在 UI 上实时刷新麦位信息(如是否有人、是否静音)?

您应该订阅 LiveSeatState 中的 seatList 属性,它是一个 ValueListenable<List<SeatInfo>> 类型的响应式数据,每当数组变化时都会通知您重新渲染麦位列表。遍历此数组,您可以:
通过 seatInfo.userInfo 来获取麦位上的用户信息。
通过 seatInfo.isLocked 判断麦位是否被锁定。
通过 seatInfo.userInfo.microphoneStatus 判断麦上用户的麦克风状态。

帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈