核心功能
LiveCoreWidget 是一个专为直播场景设计的轻量级 Widget 组件,是您构建直播场景的核心,它封装了所有复杂的底层直播技术(例如推拉流、连麦、音视频渲染)。您可以将 LiveCoreWidget 作为直播画面的"画布",专注于上层 UI 与交互的开发。
通过下方的视图层级示意图,您可以直观了解 LiveCoreWidget 在直播界面中的位置和作用:

准备工作
步骤1:开通服务
步骤2:在当前项目中导入 AtomicXCore
1. 安装组件:请在您的
pubspec.yaml 文件中添加 atomic_x_core 依赖,然后执行 flutter pub get。dependencies:atomic_x_core: ^3.6.0
2. 配置工程权限:Android 和 iOS 工程都需要配置权限。
请在
android/app/src/main/AndroidManifest.xml 文件中添加相机和麦克风的使用权限说明。<uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.RECORD_AUDIO" />
请在应用
iOS 目录的 Podfile 和 ios/Runner 目录的 Info.plist 文件中添加相机和麦克风的使用权限说明。Podfile
post_install do |installer|installer.pods_project.targets.each do |target|flutter_additional_ios_build_settings(target)target.build_configurations.each do |config|config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)','PERMISSION_MICROPHONE=1','PERMISSION_CAMERA=1',]endendend
Info.plist
<key>NSCameraUsageDescription</key><string>TUILiveKit需要访问您的相机权限,开启后录制的视频才会有画面</string><key>NSMicrophoneUsageDescription</key><string>TUILiveKit需要访问您的麦克风权限,开启后录制的视频才会有声音</string>

步骤3:实现登录逻辑
在您的项目中调用
LoginStore.shared.login 完成登录,这是使用 AtomicXCore 所有功能的关键前提。重要:
推荐在您 App 自身的用户账户登录成功后,再调用 LoginStore.shared.login,以确保登录业务逻辑的清晰和一致。
import 'package:flutter/material.dart';import 'package:atomic_x_core/atomicxcore.dart';void main() async {WidgetsFlutterBinding.ensureInitialized();final result = await LoginStore.shared.login(sdkAppID: 1400000001, // 替换为您的 sdkAppIDuserID: "test_001", // 替换为您的 userIDuserSig: "xxxxxxxxxxx", // 替换为您的 userSig);if (result.isSuccess) {debugPrint("login success");} else {debugPrint("login failed code: ${result.errorCode}, message: ${result.errorMessage}");}runApp(const MyApp());}
登录接口参数说明:
参数 | 类型 | 说明 |
sdkAppID | int | |
userID | String | 当前用户的唯一 ID,仅包含英文字母、数字、连字符和下划线。为避免多端登录冲突,请勿使用 1、123等简单 ID。 |
userSig | String | 用于腾讯云鉴权的票据。请注意: 开发环境:您可以采用本地 GenerateTestUserSig.genTestSig 函数生成 userSig 或者通过 UserSig 辅助工具 生成临时的 UserSig。生产环境:为了防止密钥泄露,请务必采用服务端生成 UserSig 的方式。详细信息请参考 服务端生成 UserSig。 |
搭建基础直播间
步骤1:实现主播开播
主播开播涉及“预览”和“直播”两个阶段。由于底层视频渲染机制不同,我们需要构建一个状态机来管理视图切换,从而避免预览时出现黑屏。
1. 核心逻辑与状态定义。
首先,在您的 State 类中定义核心控制器,并增加一个 _isLiveStarted 标记,用于区分当前是“预览态”还是“直播态”。
class _YourAnchorPageState extends State<YourAnchorPage> {// 核心控制器late final LiveCoreController _controller;// 状态标记:false = 预览中; true = 直播中bool _isLiveStarted = false;@overridevoid initState() {super.initState();_controller = LiveCoreController.create(CoreViewType.pushView);_controller.setLiveID(widget.liveId);// 初始化时直接打开设备(详见下一步)_openDevices();}}
2. 打开摄像头和麦克风。
通过调用 DeviceStore 的
openLocalCamera、openLocalMicrophone 接口打开摄像头和麦克风,您无需做额外操作,LiveCoreWidget 会自动预览当前摄像头的视频流,示例代码如下:注意:
此步骤对于预览至关重要。只有打开设备,后续的 VideoView 才有画面可渲染。
void _openDevices() {// 1. 打开前置摄像头 (true 表示前置)DeviceStore.shared.openLocalCamera(true);// 2. 打开麦克风DeviceStore.shared.openLocalMicrophone();}
3. 构建预览视图。
这是最关键的一步。我们需要根据
_isLiveStarted 的状态选择不同的组件:预览态:使用
VideoView + setLocalVideoView(直接渲染本地采集数据)。直播态:使用
LiveCoreWidget(渲染直播间流数据)。// 3.1 封装预览视图组件Widget _buildLocalPreview() {return Container(color: Colors.black,child: VideoView(onViewCreated: (viewId) {// 关键:将本地摄像头画面绑定到预览 ViewTUIRoomEngine.sharedInstance().setLocalVideoView(viewId);},onViewDisposed: (viewId) {// 销毁时解绑TUIRoomEngine.sharedInstance().setLocalVideoView(0);},),);}// 3.2 组合主视图@overrideWidget build(BuildContext context) {return Scaffold(body: Stack(children: [// 视图层:根据状态自动切换Positioned.fill(child: _isLiveStarted? LiveCoreWidget(controller: _controller) // 直播中:用核心组件: _buildLocalPreview(), // 预览中:用 VideoView),// UI 层:开播按钮(仅在预览时显示)if (!_isLiveStarted)Positioned(bottom: 50, left: 0, right: 0,child: Center(child: ElevatedButton(onPressed: _startLive, // 下一步实现此方法child: const Text('开始直播'),),),),],),);}
4. 实现开播与关播逻辑。
通过调用 LiveListStore 的
createLive 接口开始视频直播,示例代码如下:Future<void> _startLive() async {// 1. 配置直播间参数final liveInfo = LiveInfo(liveID: widget.liveId, // 房间唯一标识liveName: "Atomic Live Demo", // 房间标题// 布局模式:videoDynamicGrid9Seats 代表动态 9 宫格布局seatTemplate: SeatLayoutTemplate.videoDynamicGrid9Seats,);// 2. 发起开播请求final result = await LiveListStore.shared.createLive(liveInfo);if (result.isSuccess) {debugPrint("开播成功");// 3. 关键步骤:更新状态// 将 _isLiveStarted 置为 true,触发 build 方法重新构建,// 从而将 UI 从 VideoView(预览)切换为 LiveCoreWidget(直播)。setState(() {_isLiveStarted = true;});} else {debugPrint("开播失败: ${result.errorMessage}");// 可以在此处添加 Toast 提示用户}}
LiveInfo 参数说明:
参数名 | 类型 | 属性 | 描述 |
liveID | String | 必填 | 直播间的唯一标识符。 |
liveName | String | 选填 | 直播间的标题。 |
notice | String | 选填 | 直播间的公告信息。 |
isMessageDisable | Boolean | 选填 | 是否禁言( true:是,false:否)。 |
isPublicVisible | Boolean | 选填 | 是否公开可见( true:是,false:否)。 |
seatMode | TakeSeatMode | 选填 | 上麦模式( FREE:自由上麦,APPLY:申请上麦)。 |
seatTemplate | SeatLayoutTemplate | 必填 | 麦位布局模板。 |
coverURL | String | 选填 | 直播间的封面图片地址。 |
backgroundURL | String | 选填 | 直播间的背景图片地址。 |
categoryList | List<Int> | 选填 | 直播间的分类标签列表。 |
activityStatus | Int | 选填 | 直播活动状态。 |
| | | |
| | | |
| | | |
isGiftEnabled | Boolean | 选填 | 是否启用礼物功能( true:是,false:否)。 |
5. 结束直播。
直播结束后,主播可以调用 LiveListStore 的
endLive 接口结束直播。SDK 会处理停止推流和销毁房间的逻辑。Future<void> _stopLive() async {// 1. 调用接口结束直播// SDK 内部会自动停止推流、销毁房间并清理媒体资源final result = await LiveListStore.shared.endLive();if (result.isSuccess) {debugPrint("关播成功");// 2. 退出当前页面if (mounted) {Navigator.of(context).pop();}} else {debugPrint("关播失败: ${result.errorMessage}");}}
6. 完整代码参考。
为了方便您快速集成,我们将上述步骤整合为完整的
YourAnchorPage 文件。您可以直接复制以下代码:import 'package:flutter/material.dart';import 'package:atomic_x_core/atomicxcore.dart';import 'package:rtc_room_engine/rtc_room_engine.dart'; // 引入 RTC 引擎class YourAnchorPage extends StatefulWidget {final String liveId;const YourAnchorPage({super.key, required this.liveId});@overrideState<YourAnchorPage> createState() => _YourAnchorPageState();}class _YourAnchorPageState extends State<YourAnchorPage> {late final LiveCoreController _controller;bool _isLiveStarted = false; // 直播状态标记@overridevoid initState() {super.initState();_controller = LiveCoreController.create(CoreViewType.pushView);_controller.setLiveID(widget.liveId);_openDevices(); // 步骤2:打开设备}void _openDevices() {DeviceStore.shared.openLocalCamera(true);DeviceStore.shared.openLocalMicrophone();}// 步骤3:构建预览Widget _buildLocalPreview() {return Container(color: Colors.black,child: VideoView(onViewCreated: (viewId) => TUIRoomEngine.sharedInstance().setLocalVideoView(viewId),onViewDisposed: (viewId) => TUIRoomEngine.sharedInstance().setLocalVideoView(0),),);}// 步骤4:开播逻辑Future<void> _startLive() async {final liveInfo = LiveInfo(liveID: widget.liveId,liveName: "Test Live",seatTemplate: SeatLayoutTemplate.videoDynamicGrid9Seats,keepOwnerOnSeat: true,);final result = await LiveListStore.shared.createLive(liveInfo);if (result.isSuccess) {setState(() {_isLiveStarted = true; // 切换 UI 状态});}}@overrideWidget build(BuildContext context) {return Scaffold(body: Stack(children: [// 视图自动切换Positioned.fill(child: _isLiveStarted? LiveCoreWidget(controller: _controller): _buildLocalPreview(),),// 开播按钮if (!_isLiveStarted)Positioned(bottom: 50, left: 0, right: 0,child: Center(child: ElevatedButton(onPressed: _startLive, child: const Text('开始直播')),),),],),);}}
步骤2:实现观众进房观看
观众观看流程如下,通过简单几步操作,即可实现观众观看直播。

提示:
1. 实现观众拉流页面。
在您的观众页面中,创建
LiveCoreWidget 实例,并通过 LiveCoreController 控制直播行为。注意:
必须确保 LiveCoreWidget 在调用 joinLive() 之前已经完成初始化并处于渲染树中。
原理分析:
LiveCoreWidget 在 initState 中会执行 controller.init() 以注册对 currentLive 信息的监听。如果先调用 joinLive() 导致房间信息更新,而此时 Widget 尚未渲染(监听器未注册),则无法触发内部的自动拉流逻辑,导致画面黑屏。import 'package:flutter/material.dart';import 'package:atomic_x_core/atomicxcore.dart';/// YourAudiencePage 代表您的观众观看页面class _YourAudiencePageState extends State<YourAudiencePage> {late final LiveCoreController _controller;bool _isJoining = true;@overridevoid initState() {super.initState();_controller = LiveCoreController.create(CoreViewType.playView);_controller.setLiveID(widget.liveId);// 修正:在第一帧渲染完成后再进房,确保 LiveCoreWidget 已完成 init()WidgetsBinding.instance.addPostFrameCallback((_) {_joinLive();});}Future<void> _joinLive() async {final result = await LiveListStore.shared.joinLive(widget.liveId);if (result.isSuccess) {debugPrint("joinLive success");if (mounted) setState(() => _isJoining = false);} else {debugPrint("joinLive error: ${result.errorMessage}");}}@overrideWidget build(BuildContext context) {return Scaffold(body: Stack(children: [// 必须始终渲染,不可根据 _isJoining 状态移除,否则会导致监听失效LiveCoreWidget(controller: _controller),if (_isJoining)const Center(child: CircularProgressIndicator(color: Colors.white)),],),);}}
2. 进入直播间观看。
通过调用 LiveListStore 的
joinLive 接口加入直播,您无需做额外操作,LiveCoreWidget 会自动播放当前房间的视频流,完整示例代码如下:import 'package:flutter/material.dart';import 'package:atomic_x_core/atomicxcore.dart';/// YourAudiencePage 代表您的观众观看页面class YourAudiencePage extends StatefulWidget {final String liveId;const YourAudiencePage({super.key, required this.liveId});@overrideState<YourAudiencePage> createState() => _YourAudiencePageState();}class _YourAudiencePageState extends State<YourAudiencePage> {late final LiveCoreController _controller;@overridevoid initState() {super.initState();_controller = LiveCoreController.create();_controller.setLiveID(widget.liveId);// 进入直播间_joinLive();}Future<void> _joinLive() async {// 调用 LiveListStore.shared.joinLive 进入直播间// - liveId: 与主播开播同样的 liveIdfinal result = await LiveListStore.shared.joinLive(widget.liveId);if (result.isSuccess) {debugPrint("joinLive success");} else {debugPrint("joinLive error: ${result.errorMessage}");// 进房失败,也需要退出页面// if (mounted) Navigator.of(context).pop();}}@overrideWidget build(BuildContext context) {return Scaffold(body: Stack(children: [LiveCoreWidget(controller: _controller),],),);}}
3. 退出直播。
观众退出直播间时,需要调用 LiveListStore 的
leaveLive 接口退出直播。SDK 会自动停止拉流并退出房间。import 'package:flutter/material.dart';import 'package:atomic_x_core/atomicxcore.dart';/// YourAudiencePage 代表您的观众观看页面class YourAudiencePage extends StatefulWidget {final String liveId;const YourAudiencePage({super.key, required this.liveId});@overrideState<YourAudiencePage> createState() => _YourAudiencePageState();}class _YourAudiencePageState extends State<YourAudiencePage> {// ... 其他代码 ...// 退出直播Future<void> _leaveLive() async {final result = await LiveListStore.shared.leaveLive();if (result.isSuccess) {debugPrint("leaveLive success");} else {debugPrint("leaveLive error: ${result.errorMessage}");}}// ... 其他代码 ...}
步骤3:监听直播事件
在观众加入直播间后,您还需要处理一些房间内的"被动"事件。例如,主播主动结束了直播,或者观众因为违规等原因被踢出房间。如果不监听这些事件,观众端 UI 可能会停留在黑屏页面,影响用户体验。
您可以通过
LiveListListener 来实现事件监听。import 'package:flutter/material.dart';import 'package:atomic_x_core/atomicxcore.dart';/// YourAudiencePage 代表您的观众观看页面class YourAudiencePage extends StatefulWidget {final String liveId;const YourAudiencePage({super.key, required this.liveId});@overrideState<YourAudiencePage> createState() => _YourAudiencePageState();}class _YourAudiencePageState extends State<YourAudiencePage> {late final LiveCoreController _controller;// 1. 定义 LiveListListener 来管理事件监听late final LiveListListener _liveListListener;@overridevoid initState() {super.initState();_controller = LiveCoreController.create();_controller.setLiveID(widget.liveId);// 2. 监听直播事件_setupLiveEventListener();// 3. 进入直播间_joinLive();}// 4. 新增一个方法来设置事件监听void _setupLiveEventListener() {_liveListListener = LiveListListener(onLiveEnded: (liveID, reason, message) {// 监听到直播结束debugPrint("Live ended. liveID: $liveID, reason: ${reason.value}, message: $message");// 在此处处理退出直播间的逻辑,例如关闭当前页面// if (mounted) Navigator.of(context).pop();},onKickedOutOfLive: (liveID, reason, message) {// 监听到被踢出直播debugPrint("Kicked out of live. liveID: $liveID, reason: ${reason.value}, message: $message");// 在此处处理退出直播间的逻辑// if (mounted) Navigator.of(context).pop();},);LiveListStore.shared.addLiveListListener(_liveListListener);}Future<void> _joinLive() async {final result = await LiveListStore.shared.joinLive(widget.liveId);if (result.isSuccess) {debugPrint("joinLive success");} else {debugPrint("joinLive error: ${result.errorMessage}");}}Future<void> _leaveLive() async {final result = await LiveListStore.shared.leaveLive();if (result.isSuccess) {debugPrint("leaveLive success");} else {debugPrint("leaveLive error: ${result.errorMessage}");}}@overridevoid dispose() {// 5. 移除事件监听LiveListStore.shared.removeLiveListListener(_liveListListener);super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(body: Stack(children: [LiveCoreWidget(controller: _controller),],),);}}
运行效果
| 动态宫格布局 | 浮动小窗布局 | 固定宫格布局 | 固定小窗布局 |
模板 ID | 600 | 601 | 800 | 801 |
描述 | 默认布局,可根据连麦人数动态调整宫格大小。 | 连麦嘉宾以浮动小窗形式显示。 | 连麦人数固定,每个嘉宾占据一个固定宫格。 | 连麦人数固定,嘉宾以固定小窗形式显示。 |
丰富直播场景
当您完成了基础的直播功能后,您可以参考以下功能指南来为直播添加丰富的互动玩法。
直播功能 | 功能介绍 | 功能 Stores | 实现指南 |
实现观众音视频连线 | 观众申请上麦,与主播进行实时视频互动。 | ||
实现主播跨房连线 PK | 两个不同房间的主播进行连线,实现互动或 PK。 | ||
添加弹幕聊天功能 | 观众可以在直播间发送和接收实时文字消息。 | ||
构建礼物赠送系统 | 观众可以向主播赠送虚拟礼物,增加互动和趣味性。 |
API 文档
Store/Component | 功能描述 | API 文档 |
LiveCoreWidget | 直播视频流展示与交互的核心视图组件:负责视频流渲染和视图挂件处理,支持主播直播、观众连麦、主播连线等场景。 | |
LiveCoreController | LiveCoreWidget 的控制器:用于设置直播 ID、控制预览等操作。 | |
LiveListStore | 直播间全生命周期管理:创建 / 加入 / 离开 / 销毁房间,查询房间列表,修改直播信息(名称、公告等),监听直播状态(例如被踢出、结束)。 | |
DeviceStore | 音视频设备控制:麦克风(开关 / 音量)、摄像头(开关 / 切换 / 画质)、屏幕共享,设备状态实时监听。 | |
CoGuestStore | 观众连麦管理:连麦申请 / 邀请 / 同意 / 拒绝,连麦成员权限控制(麦克风 / 摄像头),状态同步。 | |
CoHostStore | 主播跨房连线:支持多布局模板(动态网格等),发起 / 接受 / 拒绝连线,连麦主播互动管理。 | |
BattleStore | 主播 PK 对战:发起 PK(配置时长 / 对手),管理 PK 状态(开始 / 结束),同步分数,监听对战结果。 | |
GiftStore | 礼物互动:获取礼物列表,发送 / 接收礼物,监听礼物事件(含发送者、礼物详情)。 | |
BarrageStore | 弹幕功能:发送文本 / 自定义弹幕,维护弹幕列表,实时监听弹幕状态。 | |
LikeStore | 点赞互动:发送点赞,监听点赞事件,同步总点赞数。 | |
LiveAudienceStore | 观众管理:获取实时观众列表(ID / 名称 / 头像),统计观众数量,监听观众进出事件。 | |
AudioEffectStore | 音频特效:变声(童声 / 男声)、混响(KTV 等)、耳返调节,实时切换特效。 | |
BaseBeautyStore | 基础美颜:调节磨皮 / 美白 / 红润(0-100 级),重置美颜状态,同步效果参数。 |
常见问题
主播调用 createLive 或 观众调用 joinLive 后为什么画面是黑的,没有视频画面?
检查 setLiveID:请确保在调用开播或观看接口前,已经为
LiveCoreController 实例设置了正确的 liveID。检查设备权限:请确保 App 已获得摄像头和麦克风的系统使用权限。
检查主播端:主播端是否正常调用
DeviceStore.shared.openLocalCamera(true) 打开了摄像头。检查网络:请检查设备网络连接是否正常。
观众端调用了 joinLive 且返回 Success,但依然没有视频画面?
时序问题:是否在
LiveCoreWidget 挂载前就执行了 joinLive?请参考上文使用 addPostFrameCallback。Controller 类型:创建时是否正确传入了
CoreViewType.playView?Widget 持久性:在进房过程中,
LiveCoreWidget 是否因为 setState 被意外销毁或重新创建?请确保其在 Widget 树中的稳定性。Flutter 项目如何请求权限?
您可以使用
permission_handler 插件来请求摄像头和麦克风权限:import 'package:permission_handler/permission_handler.dart';Future<void> requestPermissions() async {await [Permission.camera,Permission.microphone,].request();}
如何在 Flutter 中处理页面生命周期?
建议在
dispose 方法中清理资源,例如移除事件监听器:@overridevoid dispose() {LiveListStore.shared.removeLiveListListener(_liveListListener);super.dispose();}