tencent cloud

实时音视频

调整视频直播挂件(Flutter)

下载
聚焦模式
字号
最后更新时间: 2026-06-02 12:03:10
LiveCoreWidget 是一个跨平台的视频直播核心组件,提供开播、观看、连麦和主播 PK 等关键能力。该组件通过挂件机制,支持在视频区域实时显示用户名、等级、PK 进度条等自定义信息。本文档指导开发者通过实现接口,在 Flutter 平台上快速定制专属的视频挂件 Widget

效果展示

连线视频挂件
PK 视频挂件



准备工作

在开始调整视频挂件前,请先参考 主播开播观众观看 完成主流程的搭建。

核心原理

LiveCoreWidget 通过 VideoWidgetBuilder 代理支持自定义视图渲染。当业务场景发生变化(例如有人上麦、开始 PK )时,LiveCoreWidget 会调用代理方法询问应该显示的视图。开发者实现对应的接口方法并返回自定义的 Widget 实例即可。
回调
描述
对应业务场景
CoGuestWidgetBuilder
创建观众连麦的挂件视图。
观众连麦、邀请上麦
CoHostWidgetBuilder
创建跨房连麦(主播连线)的挂件视图。
主播连线
BattleWidgetBuilder
创建 PK 场景中单个用户的挂件视图(例如头像、分数)。
主播 PK
BattleContainerWidgetBuilder
创建 PK 场景的整体容器视图(例如背景、分数 PK 条)。
主播 PK

调整连线场景挂件

当观众与主播进行连线或者主播间跨房连线时,直播间界面会从单人模式切换为多人布局。此时,需要在连线的视频窗口上展示特定的用户信息,例如昵称、等级或“静音”状态提示,以便区分不同麦位。

适用场景

主播和观众视频连麦时,修改麦位上的用户信息(例如昵称、等级、静音图标)。
主播与主播跨房连线时,修改对方主播的显示样式(例如昵称、等级、静音图标)。
修改无视频画面时的默认背景(例如占位头像)。
修改空麦位时的视图实现。

视图层级示意



实现步骤

说明:
您也可以参考 TUILiveKit 开源项目中的 CoGuestWidgetsCoHostWidgets 目录下文件来了解完整的实现逻辑。

步骤 1. 准备自定义挂件 Widget

定义三个基础视图类,分别用于展示用户信息、空麦位提示和无视频流时的背景。
创建自定义的用户信息视图:
import 'package:flutter/material.dart';

class CustomInfoWidget extends StatelessWidget {
final String name;
final bool isMuted;

const CustomInfoWidget({
super.key,
required this.name,
required this.isMuted,
});

@override
Widget build(BuildContext context) {
return Stack(
children: [
Text(name),
if (isMuted) const Icon(Icons.mic_off),
// 此处省略具体布局参数设置代码
],
);
}
}
创建自定义的空麦位视图:
import 'package:flutter/material.dart';

class EmptySeatWidget extends StatelessWidget {
const EmptySeatWidget({super.key});

@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.add),
Text('邀请连线'),
// 此处省略图片资源加载和布局参数设置代码
],
);
}
}
创建自定义无视频流时的头像占位视图:
import 'package:flutter/material.dart';

class CustomAvatarWidget extends StatelessWidget {
final String avatarURL;

const CustomAvatarWidget({
super.key,
required this.avatarURL,
});

@override
Widget build(BuildContext context) {
// 实际项目中可使用 CachedNetworkImage 等图片加载库加载 avatarURL
return Image.network(avatarURL);
// 此处省略具体布局参数设置代码
}
}

步骤 2. 实现 VideoWidgetBuilder 逻辑

创建一个Builder类,实现 VideoWidgetBuilder 类的回调方法 coGuestWidgetBuilder (观众连线)和 coHostWidgetBuilder (主播连线),返回刚才创建的自定义视图。
实现 VideoWidgetBuilder 中的 coGuestWidgetBuilder 回调方法,返回观众连线视频挂件。
import 'package:flutter/material.dart';

import 'package:atomic_x_core/atomicxcore.dart';

VideoWidgetBuilder(coGuestWidgetBuilder: (context, seatInfo, viewLayer) {
final isUserOnSeat = seatInfo.userInfo.userID.isNotEmpty;
if (viewLayer == ViewLayer.foreground) {
if (isUserOnSeat) {
// 非空麦位,返回自定义的前景视图
return CustomInfoWidget(
name: seatInfo.userInfo.userName, isMuted: seatInfo.userInfo.microphoneStatus == DeviceStatus.off);
} else {
// 空麦位,返回自定义空麦位视图
return EmptySeatWidget();
}
} else {
if (isUserOnSeat) {
// 返回自定义的背景视图(未开摄像头时显示)
return CustomAvatarWidget(avatarURL: seatInfo.userInfo.avatarURL);
} else {
return SizedBox.shrink();
}
}
})
实现 VideoWidgetBuilder 中的 coHostWidgetBuilder 回调方法,返回主播连线的视频挂件。
import 'package:flutter/material.dart';

import 'package:atomic_x_core/atomicxcore.dart';

VideoWidgetBuilder(coHostWidgetBuilder: (context, seatInfo, viewLayer) {
final isUserOnSeat = seatInfo.userInfo.userID.isNotEmpty;
if (viewLayer == ViewLayer.foreground) {
if (isUserOnSeat) {
// 返回自定义的前景视图,可以返回与观众连线不同的样式
return CustomInfoWidget(
name: seatInfo.userInfo.userName, isMuted: seatInfo.userInfo.microphoneStatus == DeviceStatus.off);
} else {
// 返回自定义空麦位视图,可以返回与观众连线不同的样式
return EmptySeatWidget();
}
} else {
if (isUserOnSeat) {
// 返回自定义的背景视图(未开摄像头时显示),可以返回与观众连线不同的样式
return CustomAvatarWidget(avatarURL: seatInfo.userInfo.avatarURL);
} else {
return SizedBox.shrink();
}
}
})


参数说明

参数
类型
说明
seatInfo
SeatInfo
麦位信息对象,包含麦上用户的详细信息。
seatInfo.userInfo.userName
String
麦上用户的昵称。
seatInfo.userInfo.avatarURL
String
麦上用户的头像 URL。
seatInfo.userInfo.microphoneStatus
DeviceStatus
麦上用户的麦克风状态。
seatInfo.userInfo.cameraStatus
DeviceStatus
麦上用户的摄像头状态。
viewLayer
ViewLayer
视图层级枚举。
.foreground 表示前景挂件视图,始终显示在视频画面的最上层。
.background 表示背景挂件视图,位于前景视图下层,仅在对应用户没有视频流(例如未开摄像头)的情况下显示,通常用于展示用户的默认头像或占位图。

调整主播 PK 场景挂件

PK 是直播中互动性最强的环节。在 PK 过程中,画面通常根据参与人数被分割为几部分。开发者需要在画面上方或中间区域添加 PK 专用的 UI ,分数 PK 条、每个麦位上的分数显示、倒计时动画或“VS”特效图标,以营造紧张的竞技氛围。

视图层级示意


说明:
PK 功能依赖于连线功能,所以需要先在主播连线的情况下才能发起 PK 请求

实现步骤

步骤 1. 准备自定义 UI 组件

PK 场景通常需要两类视图:
单人挂件:显示在每个主播窗口上(例如分数胶囊)。
全局容器:覆盖在整体画面上(例如 VS 动画、倒计时)。
说明:
您也可以参考 TUILiveKit 开源项目中的 BattleWidgets 目录下文件来了解完整的实现逻辑。
import 'package:flutter/material.dart';

// 示例:单人分数条
class MyBattleScoreWidget extends StatelessWidget {
const MyBattleScoreWidget({super.key});

@override
Widget build(BuildContext context) {
// 内部实现分数显示逻辑
return const Placeholder();
}
}


// 示例:全局 VS 面板
class MyBattleContainer extends StatelessWidget {
const MyBattleContainer({super.key});

@override
Widget build(BuildContext context) {
// 内部实现倒计时和 VS 动画逻辑
return const Placeholder();
}
}

步骤 2. 实现 VideoWidgetBuilder 逻辑

在之前创建的 VideoWidgetBuilder 中,实现剩余的 PK 视图方法。
import 'package:flutter/material.dart';

import 'package:atomic_x_core/atomicxcore.dart';

VideoWidgetBuilder(
// 省略 createCoGuestView / createCoHostView 实现...
// 1. 创建【PK 单人信息】挂件 (显示在每个主播视频上方)
battleWidgetBuilder: (context, seatInfo) {
return MyBattleScoreWidget();
}, // 2. 创建【PK 全局容器】挂件 (显示在整个视频区域上方)
battleContainerWidgetBuilder: (context) {
return MyBattleContainer();
})

集成与生效(重要)

这是最关键的一步。您需要将实现了代理逻辑的 VideoWidgetBuilder 注入到直播核心流程中。
主播端集成:需在初始化外层容器前,先初始化 LiveCoreWidget 并通过 videoWidgetBuilder 设置VideoWidgetBuilder
import 'package:flutter/material.dart';

import 'package:atomic_x_core/atomicxcore.dart';

class AnchorWidget extends StatefulWidget {
const AnchorWidget({super.key});

@override
State<AnchorWidget> createState() => _AnchorWidgetState();
}

class _AnchorWidgetState extends State<AnchorWidget> {
// 假设此处已获取 liveInfo
final LiveInfo liveInfo = LiveInfo();

late final LiveCoreController _liveCoreController;

@override
void initState() {
super.initState();
// 1. 初始化 LiveCoreWidget 的控制器 LiveCoreController
_liveCoreController = LiveCoreController.create(CoreViewType.pushView);
_liveCoreController.setLiveID(liveInfo.liveID);
}

@override
Widget build(BuildContext context) {
return LiveCoreWidget(
// 2. 将核心视频组件放到父 Widget 中并传入控制器
controller: _liveCoreController,
// 3. 传入前面定义的 VideoWidgetBuilder的 各个回调方法
videoWidgetBuilder: VideoWidgetBuilder(coGuestWidgetBuilder: (context, seatInfo, viewLayer) {
// 传入前面步骤中定义的 coGuestWidgetBuilder 组件实现
return Placeholder();
}, coHostWidgetBuilder: (context, seatInfo, viewLayer) {
// 传入前面步骤中定义的 coHostWidgetBuilder 组件实现
return Placeholder();
}, battleWidgetBuilder: (context, seatInfo) {
// 传入前面步骤中定义的 battleWidgetBuilder 组件实现
return Placeholder();
}, battleContainerWidgetBuilder: (context) {
// 传入前面步骤中定义的 battleContainerWidgetBuilder 组件实现
return Placeholder();
}));
}
}

观众端集成:观众端 和 主播端逻辑类似,仅需要将 LiveCoreControllerCoreViewType 类型修改为 CoreViewType.pushView 即可。
import 'package:flutter/material.dart';

import 'package:atomic_x_core/atomicxcore.dart';

class AudienceWidget extends StatefulWidget {
const AudienceWidget({super.key});

@override
State<AudienceWidget> createState() => _AudienceWidgetState();
}

class _AudienceWidgetState extends State<AudienceWidget> {
// 假设此处已获取 liveInfo
final LiveInfo liveInfo = LiveInfo();

late final LiveCoreController _liveCoreController;

@override
void initState() {
super.initState();
// 1. 初始化 LiveCoreWidget 的控制器 LiveCoreController
_liveCoreController = LiveCoreController.create(CoreViewType.pushView);
_liveCoreController.setLiveID(liveInfo.liveID);
}

@override
Widget build(BuildContext context) {
return LiveCoreWidget(
// 2. 将核心视频组件放到父 Widget 中并传入控制器
controller: _liveCoreController,
// 3. 传入前面定义的 VideoWidgetBuilder的 各个回调方法
videoWidgetBuilder: VideoWidgetBuilder(coGuestWidgetBuilder: (context, seatInfo, viewLayer) {
// 传入前面步骤中定义的 coGuestWidgetBuilder 组件实现
return Placeholder();
}, coHostWidgetBuilder: (context, seatInfo, viewLayer) {
// 传入前面步骤中定义的 coHostWidgetBuilder 组件实现
return Placeholder();
}, battleWidgetBuilder: (context, seatInfo) {
// 传入前面步骤中定义的 battleWidgetBuilder 组件实现
return Placeholder();
}, battleContainerWidgetBuilder: (context) {
// 传入前面步骤中定义的 battleContainerWidgetBuilder 组件实现
return Placeholder();
}));
}
}


进阶:获取实时业务数据

在开发 PK 等复杂场景时,您会发现 SeatInfo 只提供了基础麦位信息。如果您需要获取实时倒计时、PK 分数等业务数据,就需要在您的自定义视图中对接 AtomicXCore 中的核心数据。
Store/Component
功能描述
API 文档
CoGuestStore
观众连线数据:已连线用户列表、邀请列表,申请列表等。
CoHostStore
主播连线数据:已连线用户列表、邀请列表,申请列表等。
BattleStore
PK 数据:当前 PK 信息、PK 用户列表、PK 分数列表。

常见问题

我只想修改“连麦”视图,“PK”视图想保留默认的,怎么办?

只需要找到源代码中的 VideoWidgetBuilder coGuestWidgetBuilder, 将其返回的 Widget 替换成您的 CustomWidget 即可。

自定义挂件显示了,但无法点击?

因为前景视图 (.foreground) 始终位于视频层之上,所以您要检查您的 CustomWidget 是否位于 .foreground 层,同时确保它的父视图没有禁用交互。




帮助和支持

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

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

文档反馈